[bambulab] Fix AMS and use JBambuAPI 1.4.0 (#18477)

* Fix

Signed-off-by: Martin Grześlowski <martin.grzeslowski@gmail.com>
This commit is contained in:
Martin
2025-04-07 21:52:19 +02:00
committed by GitHub
parent 0284a06be4
commit a6395afae6
10 changed files with 199 additions and 195 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
<dependency>
<groupId>pl.grzeslowski</groupId>
<artifactId>JBambuAPI</artifactId>
<version>1.3.0</version>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
@@ -12,10 +12,11 @@
*/
package org.openhab.binding.bambulab.internal;
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.AmsChannel.MAX_AMS_TRAYS;
import static org.openhab.core.thing.ThingStatus.*;
import static org.openhab.core.thing.ThingStatusDetail.BRIDGE_UNINITIALIZED;
import static org.openhab.core.types.UnDefType.UNDEF;
import java.util.Collection;
import java.util.Map;
@@ -83,9 +84,14 @@ public class AmsDeviceHandler extends BaseThingHandler {
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == RefreshType.REFRESH) {
var amsChannel = AmsChannel.findAmsChannel(channelUID);
if (amsChannel.isEmpty()) {
logger.warn("Could not find AmsChannel for channel UUID: [{}]!", channelUID);
return;
}
Optional.ofNullable(printer)//
.flatMap(p -> p.findLatestAms(getAmsNumber()))//
.ifPresent(this::updateAms);
.ifPresent(ams -> updateAms(amsChannel.get(), ams));
}
}
@@ -97,6 +103,10 @@ public class AmsDeviceHandler extends BaseThingHandler {
.orElse("?!");
logger.debug("Updating AMS #{}", number);
}
stream(AmsChannel.values()).forEach(channel -> updateAms(channel, ams));
}
private void updateAms(AmsChannel channel, Map<String, Object> ams) {
Optional.of(ams)//
.map(map -> map.get("tray"))//
.filter(obj -> obj instanceof Collection<?>)//
@@ -105,117 +115,56 @@ public class AmsDeviceHandler extends BaseThingHandler {
.flatMap(Collection::stream)//
.filter(obj -> obj instanceof Map<?, ?>)//
.map(obj -> (Map<?, ?>) obj)//
.forEach(this::updateAmsTray);
.forEach(map -> updateAmsTray(channel, map));
}
private void updateAmsTray(Map<?, ?> map) {
@SuppressWarnings("DuplicateBranchesInSwitch")
private void updateAmsTray(AmsChannel channel, Map<?, ?> map) {
var someId = findKey(map, "id")//
.map(Object::toString)//
.map(Integer::parseInt)
// tray ID in api starts from 0 and for channels it starts for 1
.map(t -> t + 1);
.map(Integer::parseInt)//
.flatMap(AmsChannel.TrayId::parseFromApi);
if (someId.isEmpty()) {
logger.warn("There is no tray ID in {}", map);
return;
}
int trayId = someId.get();
if (trayId > MAX_AMS_TRAYS) {
logger.warn("Tray ID needs to be lower that {}. Was {}", MAX_AMS_TRAYS, trayId);
return;
}
findKey(map, "tray_type")//
.map(Object::toString)//
.flatMap(AmsChannel.TrayType::findTrayType)//
.map(Enum::name)//
.map(value -> (State) StringType.valueOf(value))//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTrayTypeChannel(trayId), value));
findKey(map, "tray_color")//
.map(Object::toString)//
.map(StateParserHelper::parseColor)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTrayColorChannel(trayId), value));
findKey(map, "nozzle_temp_max")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseTemperatureType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getNozzleTemperatureMaxChannel(trayId), value));
findKey(map, "nozzle_temp_min")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseTemperatureType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getNozzleTemperatureMinChannel(trayId), value));
findKey(map, "remain")//
.map(Object::toString)//
.flatMap(StateParserHelper::parsePercentType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getRemainChannel(trayId), value));
findKey(map, "k")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getKChannel(trayId), value));
findKey(map, "n")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getNChannel(trayId), value));
findKey(map, "tag_uuid")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTagUuidChannel(trayId), value));
findKey(map, "tray_id_name")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTrayIdNameChannel(trayId), value));
findKey(map, "tray_info_idx")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTrayInfoIdxChannel(trayId), value));
findKey(map, "tray_sub_brands")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTraySubBrandsChannel(trayId), value));
findKey(map, "tray_weight")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTrayWeightChannel(trayId), value));
findKey(map, "tray_diameter")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTrayDiameterChannel(trayId), value));
findKey(map, "tray_temp")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseTemperatureType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTrayTemperatureChannel(trayId), value));
findKey(map, "tray_time")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getTrayTimeChannel(trayId), value));
findKey(map, "bed_temp_type")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getBedTemperatureTypeChannel(trayId), value));
findKey(map, "bed_temp")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseTemperatureType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getBedTemperatureChannel(trayId), value));
findKey(map, "ctype")//
.map(Object::toString)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef)//
.ifPresent(value -> updateState(AmsChannel.getCtypeChannel(trayId), value));
var trayId = someId.get();
var key = findKey(map, channel.getJsonKey()).map(Object::toString);
var state = switch (channel) {
case CHANNEL_TRAY_TYPE -> //
key.flatMap(name -> {
var trayType = AmsChannel.TrayType.findTrayType(name);
if (trayType.isEmpty()) {
var msg = "Cannot parse tray type from [{}]! Please report this on https://github.com/openhab/openhab-addons .";
if (logger.isDebugEnabled()) {
logger.debug(msg + " Full map: {}", name, map);
} else {
logger.warn(msg, name);
}
}
return trayType;
})//
.map(Enum::name)//
.map(value -> (State) StringType.valueOf(value));
case CHANNEL_TRAY_COLOR -> key.map(StateParserHelper::parseColor);
case CHANNEL_NOZZLE_TEMPERATURE_MAX -> key.flatMap(StateParserHelper::parseTemperatureType);
case CHANNEL_NOZZLE_TEMPERATURE_MIN -> key.flatMap(StateParserHelper::parseTemperatureType);
case CHANNEL_REMAIN -> key.flatMap(StateParserHelper::parsePercentType);
case CHANNEL_K -> key.flatMap(StateParserHelper::parseDecimalType);
case CHANNEL_N -> key.flatMap(StateParserHelper::parseDecimalType);
case CHANNEL_TAG_UUID -> key.flatMap(StateParserHelper::parseStringType);
case CHANNEL_TRAY_ID_NAME -> key.flatMap(StateParserHelper::parseStringType);
case CHANNEL_TRAY_INFO_IDX -> key.flatMap(StateParserHelper::parseStringType);
case CHANNEL_TRAY_SUB_BRANDS -> key.flatMap(StateParserHelper::parseStringType);
case CHANNEL_TRAY_WEIGHT -> key.flatMap(StateParserHelper::parseDecimalType);
case CHANNEL_TRAY_DIAMETER -> key.flatMap(StateParserHelper::parseDecimalType);
case CHANNEL_TRAY_TEMPERATURE -> key.flatMap(StateParserHelper::parseTemperatureType);
case CHANNEL_TRAY_TIME -> key.flatMap(StateParserHelper::parseDecimalType);
case CHANNEL_BED_TEMPERATURE_TYPE -> key.flatMap(StateParserHelper::parseStringType);
case CHANNEL_BED_TEMPERATURE -> key.flatMap(StateParserHelper::parseTemperatureType);
case CHANNEL_CTYPE -> key.flatMap(StateParserHelper::parseDecimalType);
};
updateState(channel.findType(trayId), requireNonNull(state.orElse(UNDEF)));
}
private static Optional<?> findKey(Map<?, ?> map, String key) {
@@ -130,98 +130,83 @@ public class BambuLabBindingConstants {
}
}
public static class AmsChannel {
public enum AmsChannel {
CHANNEL_TRAY_TYPE("ams-tray-type", "tray_type"),
CHANNEL_TRAY_COLOR("ams-tray-color", "tray_color"),
CHANNEL_NOZZLE_TEMPERATURE_MAX("ams-nozzle-temperature-max", "nozzle_temp_max"),
CHANNEL_NOZZLE_TEMPERATURE_MIN("ams-nozzle-temperature-min", "nozzle_temp_min"),
CHANNEL_REMAIN("ams-remain", "remain"),
CHANNEL_K("ams-k", "k"),
CHANNEL_N("ams-n", "n"),
CHANNEL_TAG_UUID("ams-tag-uuid", "tag_uuid"),
CHANNEL_TRAY_ID_NAME("ams-tray-id-name", "tray_id_name"),
CHANNEL_TRAY_INFO_IDX("ams-tray-info-idx", "tray_info_idx"),
CHANNEL_TRAY_SUB_BRANDS("ams-tray-sub-brands", "tray_sub_brands"),
CHANNEL_TRAY_WEIGHT("ams-tray-weight", "tray_weight"),
CHANNEL_TRAY_DIAMETER("ams-tray-diameter", "tray_diameter"),
CHANNEL_TRAY_TEMPERATURE("ams-tray-temperature", "tray_temp"),
CHANNEL_TRAY_TIME("ams-tray-time", "tray_time"),
CHANNEL_BED_TEMPERATURE_TYPE("ams-bed-temp-type", "bed_temp_type"),
CHANNEL_BED_TEMPERATURE("ams-bed-temperature", "bed_temp"),
CHANNEL_CTYPE("ams-ctype", "ctype");
public static final int MIN_AMS = 1;
/**
* According to Bambu Lab documentation, you can attach up to 4 AMS
*/
public static final int MAX_AMS = 4;
/**
* Each AMS device has 4 trays
*/
public static final int MAX_AMS_TRAYS = 4;
private final String group;
private final String jsonKey;
public static String getTrayTypeChannel(int trayId) {
return prefix(trayId) + "ams-tray-type";
AmsChannel(String group, String jsonKey) {
this.group = group;
this.jsonKey = jsonKey;
}
public static String getTrayColorChannel(int trayId) {
return prefix(trayId) + "ams-tray-color";
public String getJsonKey() {
return jsonKey;
}
public static String getNozzleTemperatureMaxChannel(int trayId) {
return prefix(trayId) + "ams-nozzle-temperature-max";
public String findType(TrayId trayId) {
return "ams-tray-%s#%s".formatted(trayId.getIdx(), group);
}
public static String getNozzleTemperatureMinChannel(int trayId) {
return prefix(trayId) + "ams-nozzle-temperature-min";
public static Optional<AmsChannel> findAmsChannel(ChannelUID channel) {
return Optional.of(channel)//
.map(ChannelUID::getGroupId)//
.flatMap(group -> stream(values()).filter(c -> c.group.equals(group)).findAny());
}
public static String getRemainChannel(int trayId) {
return prefix(trayId) + "ams-remain";
}
public static enum TrayId {
TRAY_1(1),
TRAY_2(2),
TRAY_3(3),
TRAY_4(4);
public static String getKChannel(int trayId) {
return prefix(trayId) + "ams-k";
}
/**
* Each AMS device has 4 trays
*/
public static final int MAX_AMS_TRAYS = values().length;
public static String getNChannel(int trayId) {
return prefix(trayId) + "ams-n";
}
private final int idx;
public static String getTagUuidChannel(int trayId) {
return prefix(trayId) + "ams-tag-uuid";
}
TrayId(int idx) {
this.idx = idx;
}
public static String getTrayIdNameChannel(int trayId) {
return prefix(trayId) + "ams-tray-id-name";
}
public int getIdx() {
return idx;
}
public static String getTrayInfoIdxChannel(int trayId) {
return prefix(trayId) + "ams-tray-info-idx";
}
public static String getTraySubBrandsChannel(int trayId) {
return prefix(trayId) + "ams-tray-sub-brands";
}
public static String getTrayWeightChannel(int trayId) {
return prefix(trayId) + "ams-tray-weight";
}
public static String getTrayDiameterChannel(int trayId) {
return prefix(trayId) + "ams-tray-diameter";
}
public static String getTrayTemperatureChannel(int trayId) {
return prefix(trayId) + "ams-tray-temperature";
}
public static String getTrayTimeChannel(int trayId) {
return prefix(trayId) + "ams-tray-time";
}
public static String getBedTemperatureTypeChannel(int trayId) {
return prefix(trayId) + "ams-bed-temp-type";
}
public static String getBedTemperatureChannel(int trayId) {
return prefix(trayId) + "ams-bed-temperature";
}
public static String getCtypeChannel(int trayId) {
return prefix(trayId) + "ams-ctype";
}
private static String prefix(int trayId) {
checkTrayId(trayId);
return "ams-tray-%s#".formatted(trayId + 1);
}
private static void checkTrayId(int trayId) {
if (trayId <= 0 || trayId > MAX_AMS_TRAYS) {
throw new IllegalArgumentException(
"Invalid tray ID: %d. Allowed range: 1 to %d.".formatted(trayId, MAX_AMS_TRAYS));
public static Optional<TrayId> parseFromApi(int idx) {
// tray ID in api starts from 0 and for channels it starts for 1
return switch (idx) {
case 0 -> Optional.of(TRAY_1);
case 1 -> Optional.of(TRAY_2);
case 2 -> Optional.of(TRAY_3);
case 3 -> Optional.of(TRAY_4);
default -> Optional.empty();
};
}
}
@@ -239,11 +224,9 @@ public class BambuLabBindingConstants {
private static final Logger log = LoggerFactory.getLogger(TrayType.class);
public static Optional<TrayType> findTrayType(String name) {
var any = stream(values()).filter(t -> t.name().equalsIgnoreCase(name)).findAny();
if (any.isEmpty()) {
log.warn("Cannot parse TrayType from {}!", name);
}
return any;
return stream(values())//
.filter(t -> t.name().equalsIgnoreCase(name))//
.findAny();
}
}
}
@@ -143,7 +143,7 @@ public class CommandParser {
private static PrinterClient.Channel.PrintSpeedCommand parsePrintSpeedCommand(String[] commandLine) {
requireLength(commandLine, 1);
return PrinterClient.Channel.PrintSpeedCommand.valueOf(commandLine[0]);
return PrinterClient.Channel.PrintSpeedCommand.findByName(commandLine[0]);
}
private static PrinterClient.Channel.GCodeFileCommand parseGCodeFileCommand(String[] commandLine) {
@@ -109,10 +109,12 @@ public class PrinterHandler extends BaseBridgeHandler
var bambuCommand = new GCodeFileCommand(command.toString());
sendCommand(bambuCommand);
} else if (CHANNEL_SPEED_LEVEL.is(channelUID) && command instanceof StringType) {
stream(PrintSpeedCommand.values())//
.filter(type -> type.name().equalsIgnoreCase(command.toString()))//
.findAny()//
.ifPresent(this::sendCommand);
var bambuCommand = PrintSpeedCommand.findByName(command.toString());
if (bambuCommand.canSend()) {
sendCommand(bambuCommand);
} else {
logger.warn("Cannot send command: {}", bambuCommand);
}
} else if (CHANNEL_CAMERA_RECORD.is(channelUID) && command instanceof OnOffType onOffCommand) {
requireNonNull(camera).handleCommand(onOffCommand);
} else if (CHANNEL_COMMAND.is(channelUID) && command instanceof StringType) {
@@ -323,6 +325,13 @@ public class PrinterHandler extends BaseBridgeHandler
}
stream(PrinterChannel.values()).forEach(channel -> updateState(channel, print));
Optional.of(print)//
.map(Report.Print::ams)//
.map(Report.Print.Ams::ams)//
.stream()//
.flatMap(Collection::stream)//
.forEach(this::updateAms);
// if got new Printer state (and not failed) then make sure that thing status in ONLINE
updateStatus(ONLINE);
}
@@ -141,8 +141,12 @@ public class StateParserHelper {
public static Optional<State> parseSpeedLevel(@Nullable Integer speedLvl) {
return Optional.ofNullable(speedLvl)//
.map(PrintSpeedCommand::findByLevel)//
.map(Object::toString)//
.flatMap(StateParserHelper::parseStringType);
.map(lvl -> {
if (!lvl.canSend()) {
return UNDEF;
}
return new StringType(lvl.getName());
});
}
public static Optional<State> parseTrayType(@Nullable String trayType) {
@@ -14,6 +14,7 @@ package org.openhab.binding.bambulab.internal;
import static java.lang.Integer.parseInt;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.AmsChannel.*;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.AmsChannel.TrayId.MAX_AMS_TRAYS;
import static org.openhab.core.types.UnDefType.UNDEF;
import java.util.Optional;
@@ -15,6 +15,7 @@ package org.openhab.binding.bambulab.internal;
import static org.assertj.core.api.Assertions.*;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.LedMode.*;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.LedNode.*;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand.*;
import java.util.Arrays;
import java.util.List;
@@ -101,8 +102,8 @@ class CommandParserTest {
new PrinterClient.Channel.AmsFilamentSettingCommand(11, 22, "s3", "s4", 55, 66, "s7")));
var amsControlCommandStream = Arrays.stream(PrinterClient.Channel.AmsControlCommand.values())//
.map(value -> Arguments.of("AmsControl:" + value.name(), value));
var printSpeedCommandStream = Arrays.stream(PrinterClient.Channel.PrintSpeedCommand.values())//
.map(value -> Arguments.of("PrintSpeed:" + value.name(), value));
var printSpeedCommandStream = Stream.of(SILENT, STANDARD, SPORT, LUDICROUS)//
.map(value -> Arguments.of("PrintSpeed:" + value.getName(), value));
var gCodeFileCommandStream = stream(
Arguments.of("GCodeFile:s1", new PrinterClient.Channel.GCodeFileCommand("s1")));
var gCodeLineCommandStream = stream(Arguments.of("""
@@ -18,6 +18,7 @@ import static org.mockito.Mockito.*;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.PrinterChannel.*;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.*;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.LedNode.*;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand.*;
import java.util.stream.Stream;
@@ -103,8 +104,8 @@ class PrinterHandlerTest {
}
static Stream<Arguments> speedLevel() {
return stream(PrintSpeedCommand.values())//
.map(command -> Arguments.of(command.name(), command));
return Stream.of(SILENT, STANDARD, SPORT, LUDICROUS)//
.map(command -> Arguments.of(command.getName(), command));
}
@ParameterizedTest(name = "Command to channel {0} should not invoke `client.sendCommand`")
@@ -16,15 +16,23 @@ import static org.assertj.core.api.Assertions.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.library.unit.Units.DECIBEL_MILLIWATTS;
import static org.openhab.core.types.UnDefType.UNDEF;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand.*;
import static tech.units.indriya.unit.Units.PERCENT;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import pl.grzeslowski.jbambuapi.mqtt.PrinterClient;
/**
* @author Martin Grześlowski - Initial contribution
*/
@@ -521,4 +529,52 @@ class StateParserHelperTest {
assertThat(quantityType.doubleValue()).isEqualTo(Double.MIN_VALUE);
assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
}
@ParameterizedTest(name = "{index}: should properly parse {0}")
@MethodSource
void speedLevel(PrinterClient.Channel.PrintSpeedCommand command) {
// Given
var speedLevel = command.getLevel();
// When
var result = StateParserHelper.parseSpeedLevel(speedLevel);
// Then
assertThat(result).isPresent();
assertThat(result.get()).isInstanceOf(StringType.class);
assertThat(((StringType) result.get()).toString()).isEqualTo(command.getName());
}
static Stream<Arguments> speedLevel() {
return Stream.of(SILENT, STANDARD, SPORT, LUDICROUS).map(Arguments::of);
}
// Handles null input by returning empty Optional
@Test
@DisplayName("Given null input, when parseSpeedLevel is called, then returns empty Optional")
public void testHandlesNullInput() {
// Given
Integer speedLevel = null;
// When
var result = StateParserHelper.parseSpeedLevel(speedLevel);
// Then
assertThat(result).isEmpty();
}
// Returns UNDEF for non-basic speed levels
@Test
@DisplayName("Given non-basic speed level, when parseSpeedLevel is called, then returns UNDEF")
public void testReturnsUndefForNonBasicSpeedLevels() {
// Given
var speedLevel = 101;
// When
var result = StateParserHelper.parseSpeedLevel(speedLevel);
// Then
assertThat(result).isPresent();
assertThat(result.get()).isEqualTo(UNDEF);
}
}