diff --git a/bundles/org.openhab.binding.bambulab/README.md b/bundles/org.openhab.binding.bambulab/README.md
index b21f2e035b..5781e14b7b 100644
--- a/bundles/org.openhab.binding.bambulab/README.md
+++ b/bundles/org.openhab.binding.bambulab/README.md
@@ -65,6 +65,8 @@ Use `us.mqtt.bambulab.com` as the hostname.
| `hostname` | N/A | Text | Yes | IP address of the printer or `us.mqtt.bambulab.com` for cloud mode. |
| `accessCode` | N/A | Text | Yes | Access code for the printer. The method of obtaining this varies between local and cloud modes. |
| `username` | N/A | Text | No | `bblp` for local mode or your Bambu Lab user (starting with `u_`). (Advanced) |
+| `reconnectTime` | MQTT | Integer | No | Time in seconds to wait before reconnect. (Advanced) |
+| `reconnectMax` | MQTT | Integer | No | Max times to try reconnections. (Advanced) |
| `scheme` | MQTT | Text | No | URI scheme. (Advanced) |
| `port` | MQTT | Integer | No | URI port. (Advanced) |
| `cameraPort` | Camera | Integer | No | Camera server port. (Advanced) |
@@ -72,35 +74,36 @@ Use `us.mqtt.bambulab.com` as the hostname.
## Channels
-| Channel ID | Type | Description |
-|------------------------------|---------------------|------------------------------------------------------------------|
-| `nozzle-temperature` | Temperature Channel | Current temperature of the nozzle. |
-| `nozzle-target-temperature` | Temperature Channel | Target temperature of the nozzle. |
-| `bed-temperature` | Temperature Channel | Current temperature of the heated bed. |
-| `bed-target-temperature` | Temperature Channel | Target temperature of the heated bed. |
-| `chamber-temperature` | Temperature Channel | Current temperature inside the printer chamber. |
-| `mc-print-stage` | String Channel | Current stage of the print process. |
-| `mc-percent` | Percent Channel | Percentage of the print completed. |
-| `mc-remaining-time` | Number Channel | Estimated time remaining for the print (in seconds). |
-| `wifi-signal` | WiFi Channel | Current WiFi signal strength. |
-| `bed-type` | String Channel | Type of the printer's heated bed. |
-| `gcode-file` | String Channel | Name of the currently loaded G-code file. |
-| `gcode-state` | String Channel | Current state of the G-code execution. |
-| `reason` | String Channel | Reason for pausing or stopping the print. |
-| `result` | String Channel | Final result or status of the print job. |
-| `gcode-file-prepare-percent` | Percent Channel | Percentage of G-code file preparation completed. |
-| `big-fan1-speed` | Number Channel | Speed of the first large cooling fan (RPM). |
-| `big-fan2-speed` | Number Channel | Speed of the second large cooling fan (RPM). |
-| `heat-break-fan-speed` | Number Channel | Speed of the heat break cooling fan (RPM). |
-| `layer-num` | Number Channel | Current layer being printed. |
-| `speed-level` | Number Channel | Current speed setting of the print job. |
-| `time-laps` | Boolean Channel | Indicates whether time-lapse recording is enabled. |
-| `use-ams` | Boolean Channel | Indicates whether the Automatic Material System (AMS) is active. |
-| `vibration-calibration` | Boolean Channel | Indicates whether vibration calibration has been performed. |
-| `led-chamber` | On/Off Command | Controls the LED lighting inside the printer chamber. |
-| `led-work` | On/Off Command | Controls the LED lighting for the work area. |
-| `camera-record` | Boolean RW Command | Turns on/off OpenHAB's capability of recording. |
-| `camera-image` | Image | Current image from the printer's camera. |
+| Channel ID | Type | Description |
+|------------------------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------|
+| `command` | String RW Channel | Send yor commands to this channel so they will be send to the printer. Check `Actions` > `sendCommand` for detailed documentation. |
+| `nozzle-temperature` | Temperature Channel | Current temperature of the nozzle. |
+| `nozzle-target-temperature` | Temperature Channel | Target temperature of the nozzle. |
+| `bed-temperature` | Temperature Channel | Current temperature of the heated bed. |
+| `bed-target-temperature` | Temperature Channel | Target temperature of the heated bed. |
+| `chamber-temperature` | Temperature Channel | Current temperature inside the printer chamber. |
+| `mc-print-stage` | String Channel | Current stage of the print process. |
+| `mc-percent` | Percent Channel | Percentage of the print completed. |
+| `mc-remaining-time` | Number Channel | Estimated time remaining for the print (in seconds). |
+| `wifi-signal` | WiFi Channel | Current WiFi signal strength. |
+| `bed-type` | String Channel | Type of the printer's heated bed. |
+| `gcode-file` | String Channel | Name of the currently loaded G-code file. |
+| `gcode-state` | String Channel | Current state of the G-code execution. |
+| `reason` | String Channel | Reason for pausing or stopping the print. |
+| `result` | String Channel | Final result or status of the print job. |
+| `gcode-file-prepare-percent` | Percent Channel | Percentage of G-code file preparation completed. |
+| `big-fan1-speed` | Number Channel | Speed of the first large cooling fan (RPM). |
+| `big-fan2-speed` | Number Channel | Speed of the second large cooling fan (RPM). |
+| `heat-break-fan-speed` | Number Channel | Speed of the heat break cooling fan (RPM). |
+| `layer-num` | Number Channel | Current layer being printed. |
+| `speed-level` | Number Channel | Current speed setting of the print job. |
+| `time-laps` | Boolean Channel | Indicates whether time-lapse recording is enabled. |
+| `use-ams` | Boolean Channel | Indicates whether the Automatic Material System (AMS) is active. |
+| `vibration-calibration` | Boolean Channel | Indicates whether vibration calibration has been performed. |
+| `led-chamber` | On/Off Command | Controls the LED lighting inside the printer chamber. |
+| `led-work` | On/Off Command | Controls the LED lighting for the work area. |
+| `camera-record` | Boolean RW Command | Turns on/off OpenHAB's capability of recording. |
+| `camera-image` | Image | Current image from the printer's camera. |
## Full Example
@@ -165,7 +168,7 @@ CommandType:Parameter1:Parameter2:...
| `ChangeFilament` | `target(int)`,`currentTemperature(int))`,`targetTemperature(int)` | Changes filament using. |
| `AmsUserSetting` | `amsId(int)`,`startupReadOption(boolean)`,`trayReadOption(boolean)` | Sets AMS user settings. |
| `AmsFilamentSetting` | `amsId(int)`,`trayId(int)`,`trayInfoIdx(string)`,`trayColor(string)`,`nozzleTempMin(int)`,`nozzleTempMax(int)`,`trayType(string)` | Configures filament settings. |
-| `AmsControl` | ` RESUME` / `RESET` / `PAUSE` | Sends an AMS control command. |
+| `AmsControl` | `RESUME` / `RESET` / `PAUSE` | Sends an AMS control command. |
| `PrintSpeed` | `SILENT` / `STANDARD` / `SPORT` / `LUDICROUS` | Adjusts print speed. |
| `GCodeFile` | `filename(string)` | Loads a G-code file. |
| `GCodeLine` | `userId(string)\nlines(string...)` | Sends multiple G-code lines. Lines are enter (`\n`) separated |
diff --git a/bundles/org.openhab.binding.bambulab/pom.xml b/bundles/org.openhab.binding.bambulab/pom.xml
index 15f9bd3014..dec64ecca0 100644
--- a/bundles/org.openhab.binding.bambulab/pom.xml
+++ b/bundles/org.openhab.binding.bambulab/pom.xml
@@ -18,7 +18,7 @@
pl.grzeslowski
JBambuAPI
- 1.1.0
+ 1.3.0
org.eclipse.paho
@@ -34,6 +34,7 @@
org.slf4j
slf4j-api
2.0.16
+ provided
@@ -49,6 +50,12 @@
5.11.0
test
+
+ org.reflections
+ reflections
+ 0.10.2
+ test
+
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/AmsDeviceConfiguration.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/AmsDeviceConfiguration.java
new file mode 100644
index 0000000000..5dce32753d
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/AmsDeviceConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bambulab.internal;
+
+import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.AmsChannel.*;
+import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Martin Grześlowski - Initial contribution
+ */
+@NonNullByDefault
+public class AmsDeviceConfiguration {
+ public int number = -1;
+
+ public void validateNumber() throws InitializationException {
+ if (number < MIN_AMS || number > MAX_AMS) {
+ var message = "AMS number has to be between %s and %s!".formatted(MIN_AMS, MAX_AMS);
+ throw new InitializationException(CONFIGURATION_ERROR, message);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/AmsDeviceHandlerFactory.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/AmsDeviceHandlerFactory.java
new file mode 100644
index 0000000000..4b1ba35ced
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/AmsDeviceHandlerFactory.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bambulab.internal;
+
+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 java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.bambulab.internal.BambuLabBindingConstants.AmsChannel;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Martin Grześlowski - Initial contribution
+ */
+@NonNullByDefault
+public class AmsDeviceHandlerFactory extends BaseThingHandler {
+ private Logger logger = LoggerFactory.getLogger(AmsDeviceHandlerFactory.class);
+ private @Nullable AmsDeviceConfiguration config;
+
+ public AmsDeviceHandlerFactory(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ try {
+ internalInitialize();
+ } catch (InitializationException e) {
+ logger.debug("Error during initialization", e);
+ updateStatus(OFFLINE, e.getThingStatusDetail(), e.getDescription());
+ }
+ }
+
+ private void internalInitialize() throws InitializationException {
+ var printer = validateBridge();
+ var config = this.config = getConfigAs(AmsDeviceConfiguration.class);
+ config.validateNumber();
+ logger = LoggerFactory.getLogger("%s.%s.%d".formatted(AmsDeviceHandlerFactory.class.getName(),
+ printer.getSerialNumber(), config.number));
+ updateStatus(ONLINE);
+ }
+
+ private PrinterHandler validateBridge() throws InitializationException {
+ var bridge = getBridge();
+ if (bridge == null) {
+ throw new InitializationException(BRIDGE_UNINITIALIZED,
+ "@text/thing-type.config.bambulab.ams-device.init.no-bridge");
+ }
+ if (!(bridge.getHandler() instanceof PrinterHandler printer)) {
+ throw new InitializationException(BRIDGE_UNINITIALIZED,
+ "@text/thing-type.config.bambulab.ams-device.init.bridge-wrong-type");
+ }
+ return printer;
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ // no commands to handle
+ }
+
+ public void updateAms(Map ams) {
+ if (logger.isDebugEnabled()) {
+ var number = Optional.ofNullable(config)//
+ .map(c -> c.number)//
+ .map(Objects::toString)//
+ .orElse("?!");
+ logger.debug("Updating AMS #{}", number);
+ }
+ Optional.of(ams)//
+ .map(map -> map.get("tray"))//
+ .filter(obj -> obj instanceof Collection>)//
+ .map(obj -> (Collection>) obj)//
+ .stream()//
+ .flatMap(Collection::stream)//
+ .filter(obj -> obj instanceof Map, ?>)//
+ .map(obj -> (Map, ?>) obj)//
+ .forEach(this::updateAmsTray);
+ }
+
+ private void updateAmsTray(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);
+ 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));
+ }
+
+ private static Optional> findKey(Map, ?> map, String key) {
+ return Optional.of(map).map(m -> m.get(key));
+ }
+
+ public int getAmsNumber() {
+ return requireNonNull(config, "At this point config should be initialized").number;
+ }
+
+ @Override
+ public void dispose() {
+ config = null;
+ logger = LoggerFactory.getLogger(AmsDeviceHandlerFactory.class);
+ }
+}
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuHandler.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuHandler.java
index 054c6f3a4f..88c928780f 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuHandler.java
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuHandler.java
@@ -59,7 +59,7 @@ public interface BambuHandler {
/**
* Returns current scheduler
- *
+ *
* @return scheduler
*/
ScheduledExecutorService getScheduler();
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuLabBindingConstants.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuLabBindingConstants.java
index b736ffc192..3c1e0b7bd1 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuLabBindingConstants.java
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuLabBindingConstants.java
@@ -12,9 +12,15 @@
*/
package org.openhab.binding.bambulab.internal;
+import static java.util.Arrays.stream;
+
+import java.util.Optional;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingTypeUID;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* The {@link BambuLabBindingConstants} class defines common constants, which are
@@ -32,9 +38,11 @@ public class BambuLabBindingConstants {
// List of all Thing Type UIDs
public static final ThingTypeUID PRINTER_THING_TYPE = new ThingTypeUID(BINDING_ID, "printer");
+ public static final ThingTypeUID AMS_THING_TYPE = new ThingTypeUID(BINDING_ID, "ams-device");
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public enum Channel {
+ CHANNEL_COMMAND("command", true),
CHANNEL_NOZZLE_TEMPERATURE("nozzle-temperature"),
CHANNEL_NOZZLE_TARGET_TEMPERATURE("nozzle-target-temperature"),
CHANNEL_BED_TEMPERATURE("bed-temperature"),
@@ -60,6 +68,28 @@ public class BambuLabBindingConstants {
CHANNEL_VIBRATION_CALIBRATION("vibration-calibration"),
CHANNEL_CAMERA_RECORD("camera-record", true),
CHANNEL_CAMERA_IMAGE("camera-image"),
+ // vtray
+ CHANNEL_VTRAY_TRAY_TYPE("vtray-tray-type"),
+ CHANNEL_VTRAY_TRAY_COLOR("vtray-tray-color"),
+ CHANNEL_VTRAY_NOZZLE_TEMPERATURE_MAX("vtray-nozzle-temperature-max"),
+ CHANNEL_VTRAY_NOZZLE_TEMPERATURE_MIN("vtray-nozzle-temperature-min"),
+ CHANNEL_VTRAY_REMAIN("vtray-remain"),
+ CHANNEL_VTRAY_K("vtray-k"),
+ CHANNEL_VTRAY_N("vtray-n"),
+ CHANNEL_VTRAY_TAG_UUID("vtray-tag-uuid"),
+ CHANNEL_VTRAY_TRAY_ID_NAME("vtray-tray-id-name"),
+ CHANNEL_VTRAY_TRAY_INFO_IDX("vtray-tray-info-idx"),
+ CHANNEL_VTRAY_TRAY_SUB_BRANDS("vtray-tray-sub-brands"),
+ CHANNEL_VTRAY_TRAY_WEIGHT("vtray-tray-weight"),
+ CHANNEL_VTRAY_TRAY_DIAMETER("vtray-tray-diameter"),
+ CHANNEL_VTRAY_TRAY_TEMPERATURE("vtray-tray-temperature"),
+ CHANNEL_VTRAY_TRAY_TIME("vtray-tray-time"),
+ CHANNEL_VTRAY_BED_TEMPERATURE_TYPE("vtray-bed-temp-type"),
+ CHANNEL_VTRAY_BED_TEMPERATURE("vtray-bed-temperature"),
+ // AMS generic
+ CHANNEL_AMS_TRAY_NOW("ams-tray-now"),
+ CHANNEL_AMS_TRAY_PREVIOUS("ams-tray-previous"),
+ // leds
CHANNEL_LED_CHAMBER_LIGHT("led-chamber", true),
CHANNEL_LED_WORK_LIGHT("led-work", true);
@@ -92,4 +122,122 @@ public class BambuLabBindingConstants {
return name.equals(channelUID.getId());
}
}
+
+ public static class AmsChannel {
+ 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;
+
+ public static String getTrayTypeChannel(int trayId) {
+ return prefix(trayId) + "ams-tray-type";
+ }
+
+ public static String getTrayColorChannel(int trayId) {
+ return prefix(trayId) + "ams-tray-color";
+ }
+
+ public static String getNozzleTemperatureMaxChannel(int trayId) {
+ return prefix(trayId) + "ams-nozzle-temperature-max";
+ }
+
+ public static String getNozzleTemperatureMinChannel(int trayId) {
+ return prefix(trayId) + "ams-nozzle-temperature-min";
+ }
+
+ public static String getRemainChannel(int trayId) {
+ return prefix(trayId) + "ams-remain";
+ }
+
+ public static String getKChannel(int trayId) {
+ return prefix(trayId) + "ams-k";
+ }
+
+ public static String getNChannel(int trayId) {
+ return prefix(trayId) + "ams-n";
+ }
+
+ public static String getTagUuidChannel(int trayId) {
+ return prefix(trayId) + "ams-tag-uuid";
+ }
+
+ public static String getTrayIdNameChannel(int trayId) {
+ return prefix(trayId) + "ams-tray-id-name";
+ }
+
+ 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 enum TrayType {
+ PLA,
+ PETG,
+ ABS,
+ TPU,
+ ASA,
+ PA,
+ PC,
+ PVA,
+ HIPS;
+
+ private static final Logger log = LoggerFactory.getLogger(TrayType.class);
+
+ public static Optional 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;
+ }
+ }
+ }
}
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuLabHandlerFactory.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuLabHandlerFactory.java
index 62f05f625c..9b883454a4 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuLabHandlerFactory.java
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/BambuLabHandlerFactory.java
@@ -12,12 +12,13 @@
*/
package org.openhab.binding.bambulab.internal;
-import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.PRINTER_THING_TYPE;
+import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
@@ -35,7 +36,7 @@ import org.osgi.service.component.annotations.Component;
@Component(configurationPid = "binding.bambulab", service = ThingHandlerFactory.class)
public class BambuLabHandlerFactory extends BaseThingHandlerFactory {
- private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(PRINTER_THING_TYPE);
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(PRINTER_THING_TYPE, AMS_THING_TYPE);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@@ -46,8 +47,12 @@ public class BambuLabHandlerFactory extends BaseThingHandlerFactory {
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
- if (PRINTER_THING_TYPE.equals(thingTypeUID)) {
- return new PrinterHandler(thing);
+ if (PRINTER_THING_TYPE.equals(thingTypeUID) && thing instanceof Bridge bridge) {
+ return new PrinterHandler(bridge);
+ }
+
+ if (AMS_THING_TYPE.equals(thingTypeUID)) {
+ return new AmsDeviceHandlerFactory(thing);
}
return null;
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/CommandParser.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/CommandParser.java
new file mode 100644
index 0000000000..927ca038c9
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/CommandParser.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bambulab.internal;
+
+import static java.lang.Boolean.parseBoolean;
+import static java.lang.Integer.parseInt;
+import static java.util.Arrays.copyOfRange;
+import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.LedMode.FLASHING;
+
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import pl.grzeslowski.jbambuapi.mqtt.PrinterClient;
+
+/**
+ * @author Martin Grzeslowski - Initial contribution
+ */
+@NonNullByDefault
+public class CommandParser {
+
+ public static PrinterClient.Channel.Command parseCommand(String stringCommand) {
+ var split = stringCommand.split(":");
+ if (split.length <= 1) {
+ throw new IllegalArgumentException("Command too short, class name not passed. Command: " + stringCommand);
+ }
+ var commandName = split[0] + "Command";
+ var tail = tail(split);
+ if (commandName.equals(PrinterClient.Channel.InfoCommand.class.getSimpleName())) {
+ return parseInfoCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.PushingCommand.class.getSimpleName())) {
+ return parsePushingCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.PrintCommand.class.getSimpleName())) {
+ return parsePrintCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.ChangeFilamentCommand.class.getSimpleName())) {
+ return parseChangeFilamentCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.AmsUserSettingCommand.class.getSimpleName())) {
+ return parseAmsUserSettingCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.AmsFilamentSettingCommand.class.getSimpleName())) {
+ return parseAmsFilamentSettingCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.AmsControlCommand.class.getSimpleName())) {
+ return parseAmsControlCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.PrintSpeedCommand.class.getSimpleName())) {
+ return parsePrintSpeedCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.GCodeFileCommand.class.getSimpleName())) {
+ return parseGCodeFileCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.GCodeLineCommand.class.getSimpleName())) {
+ var gcodeLineSplit = stringCommand.split(":", 2);
+ requireLength(gcodeLineSplit, 2);
+ return parseGCodeLineCommand(gcodeLineSplit[1]);
+ }
+ if (commandName.equals(PrinterClient.Channel.LedControlCommand.class.getSimpleName())) {
+ return parseLedControlCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.SystemCommand.class.getSimpleName())) {
+ return parseSystemCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.IpCamRecordCommand.class.getSimpleName())) {
+ return parseIpCamRecordCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.IpCamTimelapsCommand.class.getSimpleName())) {
+ return parseIpCamTimelapsCommand(tail);
+ }
+ if (commandName.equals(PrinterClient.Channel.XCamControlCommand.class.getSimpleName())) {
+ return parseXCamControlCommand(tail);
+ }
+
+ throw new IllegalArgumentException("Unknown command name: " + commandName);
+ }
+
+ private static String[] tail(String[] command) {
+ return copyOfRange(command, 1, command.length);
+ }
+
+ private static void requireLength(String[] commandLine, int length) {
+ if (commandLine.length != length) {
+ throw new IllegalArgumentException("Command line length does not match! Should be %s, but was %s!"
+ .formatted(length, commandLine.length));
+ }
+ }
+
+ private static PrinterClient.Channel.InfoCommand parseInfoCommand(String[] commandLine) {
+ requireLength(commandLine, 1);
+ return PrinterClient.Channel.InfoCommand.valueOf(commandLine[0]);
+ }
+
+ private static PrinterClient.Channel.PushingCommand parsePushingCommand(String[] commandLine) {
+ if (commandLine.length == 0) {
+ return PrinterClient.Channel.PushingCommand.defaultPushingCommand();
+ }
+ requireLength(commandLine, 2);
+ return new PrinterClient.Channel.PushingCommand(parseInt(commandLine[0]), parseInt(commandLine[1]));
+ }
+
+ private static PrinterClient.Channel.PrintCommand parsePrintCommand(String[] commandLine) {
+ requireLength(commandLine, 1);
+ return PrinterClient.Channel.PrintCommand.valueOf(commandLine[0]);
+ }
+
+ private static PrinterClient.Channel.ChangeFilamentCommand parseChangeFilamentCommand(String[] commandLine) {
+ requireLength(commandLine, 3);
+ return new PrinterClient.Channel.ChangeFilamentCommand(parseInt(commandLine[0]), parseInt(commandLine[1]),
+ parseInt(commandLine[2]));
+ }
+
+ private static PrinterClient.Channel.AmsUserSettingCommand parseAmsUserSettingCommand(String[] commandLine) {
+ requireLength(commandLine, 3);
+ return new PrinterClient.Channel.AmsUserSettingCommand(parseInt(commandLine[0]), parseBoolean(commandLine[1]),
+ parseBoolean(commandLine[2]));
+ }
+
+ private static PrinterClient.Channel.AmsFilamentSettingCommand parseAmsFilamentSettingCommand(
+ String[] commandLine) {
+ requireLength(commandLine, 7);
+ return new PrinterClient.Channel.AmsFilamentSettingCommand(parseInt(commandLine[0]), parseInt(commandLine[1]),
+ commandLine[2], commandLine[3], parseInt(commandLine[4]), parseInt(commandLine[5]), commandLine[6]);
+ }
+
+ private static PrinterClient.Channel.AmsControlCommand parseAmsControlCommand(String[] commandLine) {
+ requireLength(commandLine, 1);
+ return PrinterClient.Channel.AmsControlCommand.valueOf(commandLine[0]);
+ }
+
+ private static PrinterClient.Channel.PrintSpeedCommand parsePrintSpeedCommand(String[] commandLine) {
+ requireLength(commandLine, 1);
+ return PrinterClient.Channel.PrintSpeedCommand.valueOf(commandLine[0]);
+ }
+
+ private static PrinterClient.Channel.GCodeFileCommand parseGCodeFileCommand(String[] commandLine) {
+ requireLength(commandLine, 1);
+ return new PrinterClient.Channel.GCodeFileCommand(commandLine[0]);
+ }
+
+ private static PrinterClient.Channel.GCodeLineCommand parseGCodeLineCommand(String commandLine) {
+ var split = commandLine.split("\n");
+ if (split.length < 2) {
+ throw new IllegalArgumentException("There are no lines for GCodeLineCommand!");
+ }
+ var lines = Arrays.stream(split).skip(1).toList();
+ return new PrinterClient.Channel.GCodeLineCommand(lines, split[0]);
+ }
+
+ private static PrinterClient.Channel.LedControlCommand parseLedControlCommand(String[] commandLine) {
+ if (commandLine.length < 2) {
+ throw new IllegalArgumentException(
+ "Command line length does not match! Should be %s, but was %s!".formatted(2, commandLine.length));
+ }
+ var ledNode = PrinterClient.Channel.LedControlCommand.LedNode.valueOf(commandLine[0]);
+ var ledMode = PrinterClient.Channel.LedControlCommand.LedMode.valueOf(commandLine[1]);
+ @Nullable
+ Integer ledOnTime = null, ledOffTime = null, loopTimes = null, intervalTime = null;
+ if (ledMode == FLASHING) {
+ requireLength(commandLine, 6);
+ ledOnTime = parseInt(commandLine[2]);
+ ledOffTime = parseInt(commandLine[3]);
+ loopTimes = parseInt(commandLine[4]);
+ intervalTime = parseInt(commandLine[5]);
+ }
+ return new PrinterClient.Channel.LedControlCommand(ledNode, ledMode, ledOnTime, ledOffTime, loopTimes,
+ intervalTime);
+ }
+
+ private static PrinterClient.Channel.SystemCommand parseSystemCommand(String[] commandLine) {
+ requireLength(commandLine, 1);
+ return PrinterClient.Channel.SystemCommand.valueOf(commandLine[0]);
+ }
+
+ private static PrinterClient.Channel.IpCamRecordCommand parseIpCamRecordCommand(String[] commandLine) {
+ requireLength(commandLine, 1);
+ return new PrinterClient.Channel.IpCamRecordCommand(parseBoolean(commandLine[0]));
+ }
+
+ private static PrinterClient.Channel.IpCamTimelapsCommand parseIpCamTimelapsCommand(String[] commandLine) {
+ requireLength(commandLine, 1);
+ return new PrinterClient.Channel.IpCamTimelapsCommand(parseBoolean(commandLine[0]));
+ }
+
+ private static PrinterClient.Channel.XCamControlCommand parseXCamControlCommand(String[] commandLine) {
+ requireLength(commandLine, 3);
+ return new PrinterClient.Channel.XCamControlCommand(
+ PrinterClient.Channel.XCamControlCommand.Module.valueOf(commandLine[0]), parseBoolean(commandLine[1]),
+ parseBoolean(commandLine[2]));
+ }
+}
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterActions.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterActions.java
index ebe0b20729..33b49a6830 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterActions.java
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterActions.java
@@ -12,14 +12,8 @@
*/
package org.openhab.binding.bambulab.internal;
-import static java.lang.Boolean.parseBoolean;
-import static java.lang.Integer.parseInt;
-import static java.util.Arrays.copyOfRange;
import static java.util.Objects.requireNonNull;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.BINDING_ID;
-import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.LedMode.FLASHING;
-
-import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -29,25 +23,6 @@ import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.AmsControlCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.AmsFilamentSettingCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.AmsUserSettingCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.ChangeFilamentCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.Command;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.GCodeFileCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.GCodeLineCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.InfoCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.IpCamRecordCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.IpCamTimelapsCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.LedMode;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.LedNode;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PushingCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.SystemCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.XCamControlCommand;
-
/**
* @author Martin Grzeslowski - Initial contribution
*/
@@ -74,176 +49,7 @@ public class PrinterActions implements ThingActions {
return;
}
- var command = parseCommand(stringCommand);
- localHandler.sendCommand(command);
- }
-
- private Command parseCommand(String stringCommand) {
- var split = stringCommand.split(":");
- if (split.length <= 1) {
- throw new IllegalArgumentException("Command too short, class name not passed. Command: " + stringCommand);
- }
- var commandName = split[0] + "Command";
- var tail = tail(split);
- if (commandName.equals(InfoCommand.class.getSimpleName())) {
- return parseInfoCommand(tail);
- }
- if (commandName.equals(PushingCommand.class.getSimpleName())) {
- return parsePushingCommand(tail);
- }
- if (commandName.equals(PrintCommand.class.getSimpleName())) {
- return parsePrintCommand(tail);
- }
- if (commandName.equals(ChangeFilamentCommand.class.getSimpleName())) {
- return parseChangeFilamentCommand(tail);
- }
- if (commandName.equals(AmsUserSettingCommand.class.getSimpleName())) {
- return parseAmsUserSettingCommand(tail);
- }
- if (commandName.equals(AmsFilamentSettingCommand.class.getSimpleName())) {
- return parseAmsFilamentSettingCommand(tail);
- }
- if (commandName.equals(AmsControlCommand.class.getSimpleName())) {
- return parseAmsControlCommand(tail);
- }
- if (commandName.equals(PrintSpeedCommand.class.getSimpleName())) {
- return parsePrintSpeedCommand(tail);
- }
- if (commandName.equals(GCodeFileCommand.class.getSimpleName())) {
- return parseGCodeFileCommand(tail);
- }
- if (commandName.equals(GCodeLineCommand.class.getSimpleName())) {
- var gcodeLineSplit = stringCommand.split(":", 2);
- requireLength(gcodeLineSplit, 2);
- return parseGCodeLineCommand(gcodeLineSplit[1]);
- }
- if (commandName.equals(LedControlCommand.class.getSimpleName())) {
- return parseLedControlCommand(tail);
- }
- if (commandName.equals(SystemCommand.class.getSimpleName())) {
- return parseSystemCommand(tail);
- }
- if (commandName.equals(IpCamRecordCommand.class.getSimpleName())) {
- return parseIpCamRecordCommand(tail);
- }
- if (commandName.equals(IpCamTimelapsCommand.class.getSimpleName())) {
- return parseIpCamTimelapsCommand(tail);
- }
- if (commandName.equals(XCamControlCommand.class.getSimpleName())) {
- return parseXCamControlCommand(tail);
- }
-
- throw new IllegalArgumentException("Unknown command name: " + commandName);
- }
-
- private String[] tail(String[] command) {
- return copyOfRange(command, 1, command.length);
- }
-
- private void requireLength(String[] commandLine, int length) {
- if (commandLine.length != length) {
- throw new IllegalArgumentException("Command line length does not match! Should be %s, but was %s!"
- .formatted(length, commandLine.length));
- }
- }
-
- private InfoCommand parseInfoCommand(String[] commandLine) {
- requireLength(commandLine, 1);
- return InfoCommand.valueOf(commandLine[0]);
- }
-
- private PushingCommand parsePushingCommand(String[] commandLine) {
- if (commandLine.length == 0) {
- return PushingCommand.defaultPushingCommand();
- }
- requireLength(commandLine, 2);
- return new PushingCommand(parseInt(commandLine[0]), parseInt(commandLine[1]));
- }
-
- private PrintCommand parsePrintCommand(String[] commandLine) {
- requireLength(commandLine, 1);
- return PrintCommand.valueOf(commandLine[0]);
- }
-
- private ChangeFilamentCommand parseChangeFilamentCommand(String[] commandLine) {
- requireLength(commandLine, 3);
- return new ChangeFilamentCommand(parseInt(commandLine[0]), parseInt(commandLine[1]), parseInt(commandLine[2]));
- }
-
- private AmsUserSettingCommand parseAmsUserSettingCommand(String[] commandLine) {
- requireLength(commandLine, 3);
- return new AmsUserSettingCommand(parseInt(commandLine[0]), parseBoolean(commandLine[1]),
- parseBoolean(commandLine[2]));
- }
-
- private AmsFilamentSettingCommand parseAmsFilamentSettingCommand(String[] commandLine) {
- requireLength(commandLine, 7);
- return new AmsFilamentSettingCommand(parseInt(commandLine[0]), parseInt(commandLine[1]), commandLine[2],
- commandLine[3], parseInt(commandLine[4]), parseInt(commandLine[5]), commandLine[6]);
- }
-
- private AmsControlCommand parseAmsControlCommand(String[] commandLine) {
- requireLength(commandLine, 1);
- return AmsControlCommand.valueOf(commandLine[0]);
- }
-
- private PrintSpeedCommand parsePrintSpeedCommand(String[] commandLine) {
- requireLength(commandLine, 1);
- return PrintSpeedCommand.valueOf(commandLine[0]);
- }
-
- private GCodeFileCommand parseGCodeFileCommand(String[] commandLine) {
- requireLength(commandLine, 1);
- return new GCodeFileCommand(commandLine[0]);
- }
-
- private GCodeLineCommand parseGCodeLineCommand(String commandLine) {
- var split = commandLine.split("\n");
- if (split.length < 2) {
- throw new IllegalArgumentException("There are no lines for GCodeLineCommand!");
- }
- var lines = Arrays.stream(split).skip(1).toList();
- return new GCodeLineCommand(lines, split[0]);
- }
-
- private LedControlCommand parseLedControlCommand(String[] commandLine) {
- if (commandLine.length < 2) {
- throw new IllegalArgumentException(
- "Command line length does not match! Should be %s, but was %s!".formatted(2, commandLine.length));
- }
- var ledNode = LedNode.valueOf(commandLine[0]);
- var ledMode = LedMode.valueOf(commandLine[1]);
- @Nullable
- Integer ledOnTime = null, ledOffTime = null, loopTimes = null, intervalTime = null;
- if (ledMode == FLASHING) {
- requireLength(commandLine, 6);
- ledOnTime = parseInt(commandLine[2]);
- ledOffTime = parseInt(commandLine[3]);
- loopTimes = parseInt(commandLine[4]);
- intervalTime = parseInt(commandLine[5]);
- }
- return new LedControlCommand(ledNode, ledMode, ledOnTime, ledOffTime, loopTimes, intervalTime);
- }
-
- private SystemCommand parseSystemCommand(String[] commandLine) {
- requireLength(commandLine, 1);
- return SystemCommand.valueOf(commandLine[0]);
- }
-
- private IpCamRecordCommand parseIpCamRecordCommand(String[] commandLine) {
- requireLength(commandLine, 1);
- return new IpCamRecordCommand(parseBoolean(commandLine[0]));
- }
-
- private IpCamTimelapsCommand parseIpCamTimelapsCommand(String[] commandLine) {
- requireLength(commandLine, 1);
- return new IpCamTimelapsCommand(parseBoolean(commandLine[0]));
- }
-
- private XCamControlCommand parseXCamControlCommand(String[] commandLine) {
- requireLength(commandLine, 3);
- return new XCamControlCommand(XCamControlCommand.Module.valueOf(commandLine[0]), parseBoolean(commandLine[1]),
- parseBoolean(commandLine[2]));
+ localHandler.sendCommand(stringCommand);
}
public static void sendCommand(@Nullable ThingActions actions, String stringCommand) {
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterConfiguration.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterConfiguration.java
index 46b02ad252..2c72cb346a 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterConfiguration.java
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterConfiguration.java
@@ -36,6 +36,8 @@ public class PrinterConfiguration {
public String hostname = "";
public String accessCode = "";
public String username = LOCAL_USERNAME;
+ public int reconnectTime = 300;
+ public int reconnectMax = 5;
// MQTT
public String scheme = SCHEME;
public int port = DEFAULT_PORT;
@@ -73,7 +75,7 @@ public class PrinterConfiguration {
return new URI(rawUri);
} catch (URISyntaxException e) {
throw new InitializationException(CONFIGURATION_ERROR,
- "@text/printer.handler.init.invalidHostname[\"%s\"]".formatted(rawUri), e);
+ "@text/printer.handler.init.invalidHostname [\"%s\"]".formatted(rawUri), e);
}
}
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterHandler.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterHandler.java
index dcdd540c86..ebea8281dd 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterHandler.java
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/PrinterHandler.java
@@ -12,10 +12,17 @@
*/
package org.openhab.binding.bambulab.internal;
+import static java.lang.Integer.parseInt;
+import static java.util.Arrays.stream;
+import static java.util.Collections.synchronizedList;
import static java.util.Objects.requireNonNull;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static java.util.function.Function.identity;
+import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.AmsChannel.*;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.Channel.*;
+import static org.openhab.binding.bambulab.internal.StateParserHelper.*;
+import static org.openhab.binding.bambulab.internal.TrayHelper.updateTrayLoaded;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
-import static org.openhab.core.library.unit.Units.DECIBEL_MILLIWATTS;
import static org.openhab.core.thing.ThingStatus.OFFLINE;
import static org.openhab.core.thing.ThingStatus.ONLINE;
import static org.openhab.core.thing.ThingStatus.UNKNOWN;
@@ -27,6 +34,7 @@ import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PushingCommand
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClientConfig.requiredFields;
import java.net.URI;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -34,28 +42,33 @@ import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
-import java.util.regex.Pattern;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import pl.grzeslowski.jbambuapi.mqtt.ConnectionCallback;
import pl.grzeslowski.jbambuapi.mqtt.PrinterClient;
import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.GCodeFileCommand;
import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand;
+import pl.grzeslowski.jbambuapi.mqtt.PrinterClientConfig;
import pl.grzeslowski.jbambuapi.mqtt.PrinterWatcher;
import pl.grzeslowski.jbambuapi.mqtt.Report;
@@ -66,31 +79,54 @@ import pl.grzeslowski.jbambuapi.mqtt.Report;
* @author Martin Grześlowski - Initial contribution
*/
@NonNullByDefault
-public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.StateSubscriber, BambuHandler {
- private static final Pattern DBM_PATTERN = Pattern.compile("^(-?\\d+)dBm$");
+public class PrinterHandler extends BaseBridgeHandler
+ implements PrinterWatcher.StateSubscriber, BambuHandler, ConnectionCallback {
+ private static final String INTERNAL_COMMAND_PREFIX = ">";
private Logger logger = LoggerFactory.getLogger(PrinterHandler.class);
private @Nullable PrinterClient client;
private @Nullable Camera camera;
+ private final AtomicInteger reconnectTimes = new AtomicInteger();
+ private final AtomicReference<@Nullable ScheduledFuture>> reconnectSchedule = new AtomicReference<>();
+ private final PrinterWatcher printerWatcher = new PrinterWatcher();
+ private @Nullable PrinterClientConfig config;
+ private final Collection amses = synchronizedList(new ArrayList<>());
+ private final AtomicReference<@Nullable Report> latestPrinterState = new AtomicReference<>();
- public PrinterHandler(Thing thing) {
- super(thing);
+ public PrinterHandler(Bridge bridge) {
+ super(bridge);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (CHANNEL_LED_CHAMBER_LIGHT.is(channelUID) || CHANNEL_LED_WORK_LIGHT.is(channelUID)) {
- var ledNode = CHANNEL_LED_CHAMBER_LIGHT.getName().equals(channelUID.getId()) ? CHAMBER_LIGHT : WORK_LIGHT;
+ var ledNode = CHANNEL_LED_CHAMBER_LIGHT.is(channelUID) ? CHAMBER_LIGHT : WORK_LIGHT;
var bambuCommand = "ON".equals(command.toFullString()) ? on(ledNode) : off(ledNode);
sendCommand(bambuCommand);
} else if (CHANNEL_GCODE_FILE.is(channelUID)) {
var bambuCommand = new GCodeFileCommand(command.toString());
sendCommand(bambuCommand);
- } else if (CHANNEL_SPEED_LEVEL.is(channelUID)) {
- var bambuCommand = PrintSpeedCommand.valueOf(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);
} else if (CHANNEL_CAMERA_RECORD.is(channelUID) && command instanceof OnOffType onOffCommand) {
requireNonNull(camera).handleCommand(onOffCommand);
+ } else if (CHANNEL_COMMAND.is(channelUID) && command instanceof StringType) {
+ var commandString = command.toString();
+ if (commandString.startsWith(INTERNAL_COMMAND_PREFIX)) {
+ logger.debug("Command {} updates just the state, ignoring...", commandString);
+ return;
+ }
+ String result;
+ try {
+ sendCommand(commandString);
+ result = INTERNAL_COMMAND_PREFIX + " SUCCESS: " + commandString;
+ } catch (Exception ex) {
+ result = INTERNAL_COMMAND_PREFIX + " ERROR: " + ex.getLocalizedMessage();
+ }
+ updateState(channelUID, StringType.valueOf(result));
}
}
@@ -104,7 +140,7 @@ public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.S
}
private void internalInitialize() throws InitializationException {
- PrinterConfiguration config = getConfigAs(PrinterConfiguration.class);
+ var config = getConfigAs(PrinterConfiguration.class);
config.validateSerial();
logger = LoggerFactory.getLogger(PrinterHandler.class.getName() + "." + config.serial);
@@ -115,41 +151,88 @@ public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.S
// always turn off camera recording when starting thing
camera = new Camera(config, this);
- updateState(CHANNEL_CAMERA_RECORD.getName(), OnOffType.OFF);
- updateState(CHANNEL_CAMERA_IMAGE.getName(), UNDEF);
+ updateState(CHANNEL_CAMERA_RECORD, OnOffType.OFF);
+ updateState(CHANNEL_CAMERA_IMAGE, UNDEF);
+
+ // turn off the command channel
+ updateState(CHANNEL_COMMAND, StringType.valueOf(INTERNAL_COMMAND_PREFIX + " Ready to accept commands"));
var localClient = client = buildLocalClient(uri, config);
// the status will be unknown until the first message form MQTT arrives
updateStatus(UNKNOWN);
try {
- scheduler.execute(() -> initMqtt(localClient));
+ scheduler.execute(() -> initMqtt(localClient, config));
} catch (RejectedExecutionException ex) {
logger.debug("Task was rejected", ex);
throw new InitializationException(CONFIGURATION_ERROR, ex);
}
}
- private void initMqtt(PrinterClient client) {
+ private void initMqtt(PrinterClient client, PrinterConfiguration config) {
try {
logger.debug("Trying to connect to the printer broker");
- client.connect();
- var printerWatcher = new PrinterWatcher();
+ client.connect(this);
client.subscribe(printerWatcher);
printerWatcher.subscribe(this);
- // send request to update all channels
- refreshChannels();
- updateStatus(ONLINE);
+ // update to online done in `connectComplete`
} catch (Exception e) {
logger.debug("Cannot connect to MQTT client", e);
updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getLocalizedMessage());
+ reconnect(config);
+ }
+ }
+
+ @Override
+ public void connectComplete(boolean reconnect) {
+ logger.debug("connectComplete({})", reconnect);
+ // send request to update all channels
+ scheduler.schedule(this::refreshChannels, 1, SECONDS);
+ reconnectTimes.set(0);
+ updateStatus(ONLINE);
+ }
+
+ @Override
+ public void connectionLost(@Nullable Throwable throwable) {
+ logger.debug("Connection lost. Restarting thing", throwable);
+ var message = throwable != null ? throwable.getLocalizedMessage() : " getConfigAs(PrinterConfiguration.class));
+ var currentReconnections = reconnectTimes.getAndAdd(1);
+ if (currentReconnections >= configNotNull.reconnectMax) {
+ logger.warn("Do not reconnecting any more, because max reconnections was reach!");
+ return;
+ }
+ logger.debug("Will reconnect in {} seconds...", configNotNull.reconnectTime);
+ try {
+ reconnectSchedule.set(//
+ scheduler.schedule(() -> {
+ logger.debug("Reconnecting...");
+ reconnectSchedule.set(null);
+ dispose();
+ initialize();
+ }, configNotNull.reconnectTime, SECONDS));
+ } catch (RejectedExecutionException ex) {
+ logger.debug("Task was rejected", ex);
+ updateStatus(OFFLINE, COMMUNICATION_ERROR, ex.getLocalizedMessage());
}
}
private PrinterClient buildLocalClient(URI uri, PrinterConfiguration config) throws InitializationException {
try {
- return new PrinterClient(
- requiredFields(uri, config.username, config.serial, config.accessCode.toCharArray()));
+ var localConfig = this.config = requiredFields(uri, config.username, config.serial,
+ config.accessCode.toCharArray());
+ return new PrinterClient(localConfig);
} catch (Exception e) {
logger.debug("Cannot create MQTT client", e);
throw new InitializationException(COMMUNICATION_ERROR, e);
@@ -163,28 +246,60 @@ public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.S
@Override
public void dispose() {
try {
- var localClient = client;
- client = null;
- if (localClient != null) {
- try {
- localClient.close();
- } catch (Exception e) {
- logger.warn("Could not correctly dispose PrinterClient", e);
- }
- }
- var localCamera = camera;
- camera = null;
- if (localCamera != null) {
- localCamera.close();
- }
+ latestPrinterState.set(null);
+ printerWatcher.close();
+ amses.clear();
+ closeConfig();
+ closeReconnectSchedule();
+ closeCamera();
+ closeClient();
} finally {
logger = LoggerFactory.getLogger(PrinterHandler.class);
}
}
+ private void closeConfig() {
+ var localConfig = config;
+ config = null;
+ if (localConfig != null) {
+ localConfig.close();
+ }
+ }
+
+ private void closeClient() {
+ var localClient = client;
+ client = null;
+ if (localClient != null) {
+ try {
+ localClient.close();
+ } catch (Exception e) {
+ logger.warn("Could not correctly dispose PrinterClient", e);
+ }
+ }
+ }
+
+ private void closeCamera() {
+ var localCamera = camera;
+ camera = null;
+ if (localCamera != null) {
+ localCamera.close();
+ }
+ }
+
+ private void closeReconnectSchedule() {
+ var localReconnectSchedule = reconnectSchedule.get();
+ reconnectSchedule.set(null);
+ if (localReconnectSchedule != null) {
+ localReconnectSchedule.cancel(true);
+ }
+ }
+
@Override
public void newState(@Nullable Report delta, @Nullable Report fullState) {
logger.trace("New Printer state from delta {}", delta);
+ if (fullState != null) {
+ latestPrinterState.set(fullState);
+ }
// only need to update channels from delta
// do not need to use full state, because at some point in past channels was already updated with its values
if (delta == null) {
@@ -202,66 +317,159 @@ public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.S
return;
}
// tempers
- updateCelsiusState(CHANNEL_NOZZLE_TEMPERATURE.getName(), print.nozzleTemper());
- updateCelsiusState(CHANNEL_NOZZLE_TARGET_TEMPERATURE.getName(), print.nozzleTargetTemper());
- updateCelsiusState(CHANNEL_BED_TEMPERATURE.getName(), print.bedTemper());
- updateCelsiusState(CHANNEL_BED_TARGET_TEMPERATURE.getName(), print.bedTargetTemper());
- updateCelsiusState(CHANNEL_CHAMBER_TEMPERATURE.getName(), print.chamberTemper());
+ updateCelsiusState(CHANNEL_NOZZLE_TEMPERATURE, print.nozzleTemper());
+ updateCelsiusState(CHANNEL_NOZZLE_TARGET_TEMPERATURE, print.nozzleTargetTemper());
+ updateCelsiusState(CHANNEL_BED_TEMPERATURE, print.bedTemper());
+ updateCelsiusState(CHANNEL_BED_TARGET_TEMPERATURE, print.bedTargetTemper());
+ updateCelsiusState(CHANNEL_CHAMBER_TEMPERATURE, print.chamberTemper());
// string
- updateStringState(CHANNEL_MC_PRINT_STAGE.getName(), print.mcPrintStage());
- updateStringState(CHANNEL_BED_TYPE.getName(), print.bedType());
- updateStringState(CHANNEL_GCODE_FILE.getName(), print.gcodeFile());
- updateStringState(CHANNEL_GCODE_STATE.getName(), print.gcodeState());
- updateStringState(CHANNEL_REASON.getName(), print.reason());
- updateStringState(CHANNEL_RESULT.getName(), print.result());
+ updateStringState(CHANNEL_MC_PRINT_STAGE, print.mcPrintStage());
+ updateStringState(CHANNEL_BED_TYPE, print.bedType());
+ updateStringState(CHANNEL_GCODE_FILE, print.gcodeFile());
+ updateStringState(CHANNEL_GCODE_STATE, print.gcodeState());
+ updateStringState(CHANNEL_REASON, print.reason());
+ updateStringState(CHANNEL_RESULT, print.result());
// percent
- updatePercentState(CHANNEL_MC_PERCENT.getName(), print.mcPercent());
- updatePercentState(CHANNEL_GCODE_FILE_PREPARE_PERCENT.getName(), print.gcodeFilePreparePercent());
+ updatePercentState(CHANNEL_MC_PERCENT, print.mcPercent());
+ updatePercentState(CHANNEL_GCODE_FILE_PREPARE_PERCENT, print.gcodeFilePreparePercent());
// decimal
- updateDecimalState(CHANNEL_MC_REMAINING_TIME.getName(), print.mcRemainingTime());
- updateDecimalState(CHANNEL_BIG_FAN_1_SPEED.getName(), print.bigFan1Speed());
- updateDecimalState(CHANNEL_BIG_FAN_2_SPEED.getName(), print.bigFan2Speed());
- updateDecimalState(CHANNEL_HEAT_BREAK_FAN_SPEED.getName(), print.heatbreakFanSpeed());
- updateDecimalState(CHANNEL_LAYER_NUM.getName(), print.layerNum());
+ parseTimeMinutes(print.mcRemainingTime())//
+ .ifPresent(time -> updateState(CHANNEL_MC_REMAINING_TIME, time));
+ updateDecimalState(CHANNEL_BIG_FAN_1_SPEED, print.bigFan1Speed());
+ updateDecimalState(CHANNEL_BIG_FAN_2_SPEED, print.bigFan2Speed());
+ updateDecimalState(CHANNEL_HEAT_BREAK_FAN_SPEED, print.heatbreakFanSpeed());
+ updateDecimalState(CHANNEL_LAYER_NUM, print.layerNum());
if (print.spdLvl() != null) {
var speedLevel = PrintSpeedCommand.findByLevel(print.spdLvl());
- updateState(CHANNEL_SPEED_LEVEL.getName(), new StringType(speedLevel.toString()));
+ updateState(CHANNEL_SPEED_LEVEL, new StringType(speedLevel.toString()));
}
// boolean
- updateBooleanState(CHANNEL_TIME_LAPS.getName(), print.timelapse());
- updateBooleanState(CHANNEL_USE_AMS.getName(), print.useAms());
- updateBooleanState(CHANNEL_VIBRATION_CALIBRATION.getName(), print.vibrationCali());
+ updateBooleanState(CHANNEL_TIME_LAPS, print.timelapse());
+ updateBooleanState(CHANNEL_USE_AMS, print.useAms());
+ updateBooleanState(CHANNEL_VIBRATION_CALIBRATION, print.vibrationCali());
// lights
updateLightState("chamber_light", CHANNEL_LED_CHAMBER_LIGHT, print.lightsReport());
updateLightState("work_light", CHANNEL_LED_WORK_LIGHT, print.lightsReport());
// other
if (print.wifiSignal() != null) {
- updateState(CHANNEL_WIFI_SIGNAL.getName(), parseWifiChannel(print.wifiSignal()));
+ updateState(CHANNEL_WIFI_SIGNAL, parseWifiChannel(print.wifiSignal()));
}
+ // ams
+ Optional.of(print)//
+ .map(Report.Print::ams)//
+ .ifPresent(ams -> {
+ updateState(CHANNEL_AMS_TRAY_NOW, updateTrayLoaded(ams.trayNow()));
+ updateState(CHANNEL_AMS_TRAY_PREVIOUS, updateTrayLoaded(ams.trayPre()));
+ });
+ Optional.of(print)//
+ .map(Report.Print::ams)//
+ .map(Report.Print.Ams::ams)//
+ .stream()//
+ .flatMap(Collection::stream)//
+ .forEach(this::updateAms);
+ // vtray
+ Optional.of(print)//
+ .map(Report.Print::vtTray)//
+ .ifPresent(this::updateVtray);
}
- private void updateCelsiusState(String channelId, @Nullable Double temperature) {
+ private void updateVtray(Report.Print.VtTray vtTray) {
+ Optional.ofNullable(vtTray.trayType())//
+ .map(Object::toString)//
+ .flatMap(TrayType::findTrayType)//
+ .map(Enum::name)//
+ .flatMap(StateParserHelper::parseStringType)//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_TYPE, trayType));
+ Optional.ofNullable(vtTray.trayColor())//
+ .map(Object::toString)//
+ .map(StateParserHelper::parseColor)//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_COLOR, trayType));
+ parseTemperatureType(vtTray.nozzleTempMax())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_NOZZLE_TEMPERATURE_MAX, trayType));
+ parseTemperatureType(vtTray.nozzleTempMin())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_NOZZLE_TEMPERATURE_MIN, trayType));
+ Optional.ofNullable(vtTray.remain())//
+ .flatMap(StateParserHelper::parsePercentType)//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_REMAIN, trayType));
+ parseDecimalType(vtTray.k())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_K, trayType));
+ parseDecimalType(vtTray.n())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_N, trayType));
+ parseStringType(vtTray.tagUid())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TAG_UUID, trayType));
+ parseStringType(vtTray.trayIdName())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_ID_NAME, trayType));
+ parseStringType(vtTray.trayInfoIdx())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_INFO_IDX, trayType));
+ parseStringType(vtTray.traySubBrands())
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_SUB_BRANDS, trayType));
+ parseDecimalType(vtTray.trayWeight())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_WEIGHT, trayType));
+ parseDecimalType(vtTray.trayDiameter())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_DIAMETER, trayType));
+ parseTemperatureType(vtTray.trayTemp())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_TEMPERATURE, trayType));
+ parseDecimalType(vtTray.trayTime())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_TRAY_TIME, trayType));
+ parseStringType(vtTray.bedTempType())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_BED_TEMPERATURE_TYPE, trayType));
+ parseTemperatureType(vtTray.bedTemp())//
+ .or(StateParserHelper::undef)//
+ .ifPresent(trayType -> updateState(CHANNEL_VTRAY_BED_TEMPERATURE, trayType));
+ }
+
+ private void updateAms(Map amsMap) {
+ Optional.of(amsMap)//
+ .map(map -> map.get("id"))//
+ .map(Object::toString)//
+ .map(Integer::parseInt)//
+ // in code, we are using 1-4 ordering; in API 0-3 ordering is used
+ .map(id -> id + 1) //
+ .filter(id -> id >= MIN_AMS)//
+ .filter(id -> id <= MAX_AMS)//
+ .map(id -> amses.stream().filter(ams -> ams.getAmsNumber() == id))//
+ .stream()//
+ .flatMap(identity())//
+ .forEach(ams -> ams.updateAms(amsMap));
+ }
+
+ private void updateCelsiusState(BambuLabBindingConstants.Channel channelId, @Nullable Double temperature) {
if (temperature == null) {
return;
}
updateState(channelId, new QuantityType<>(temperature, CELSIUS));
}
- private void updateStringState(String channelId, @Nullable String string) {
+ private void updateStringState(BambuLabBindingConstants.Channel channelId, @Nullable String string) {
if (string == null) {
return;
}
updateState(channelId, new StringType(string));
}
- private void updateDecimalState(String channelId, @Nullable Number number) {
+ private void updateDecimalState(BambuLabBindingConstants.Channel channelId, @Nullable Number number) {
if (number == null) {
return;
}
updateState(channelId, new DecimalType(number));
}
- private void updateDecimalState(String channelId, @Nullable String number) {
+ private void updateDecimalState(BambuLabBindingConstants.Channel channelId, @Nullable String number) {
if (number == null) {
return;
}
@@ -274,31 +482,19 @@ public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.S
}
}
- private void updateBooleanState(String channelId, @Nullable Boolean bool) {
+ private void updateBooleanState(BambuLabBindingConstants.Channel channelId, @Nullable Boolean bool) {
if (bool == null) {
return;
}
updateState(channelId, OnOffType.from(bool));
}
- private void updatePercentState(String channelId, @Nullable Integer integer) {
- if (integer == null) {
- return;
- }
- updateState(channelId, new PercentType(integer));
+ private void updatePercentState(BambuLabBindingConstants.Channel channelId, @Nullable Integer integer) {
+ parsePercentType(integer).ifPresent(state -> updateState(channelId, state));
}
- private void updatePercentState(String channelId, @Nullable String integer) {
- if (integer == null) {
- return;
- }
- try {
- var state = new PercentType(integer);
- updateState(channelId, state);
- } catch (NumberFormatException e) {
- logger.debug("Cannot parse percent number {}", integer, e);
- updateState(channelId, UNDEF);
- }
+ private void updatePercentState(BambuLabBindingConstants.Channel channelId, @Nullable String integer) {
+ parsePercentType(integer).ifPresent(state -> updateState(channelId, state));
}
private void updateLightState(String lightName, BambuLabBindingConstants.Channel channel,
@@ -311,23 +507,11 @@ public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.S
.filter(Objects::nonNull)//
.map(OnOffType::from)//
.findAny()//
- .ifPresent(command -> updateState(channel.getName(), command));
+ .ifPresent(command -> updateState(channel, command));
}
- private State parseWifiChannel(String wifi) {
- var matcher = DBM_PATTERN.matcher(wifi);
- if (!matcher.matches()) {
- return UNDEF;
- }
-
- var integer = matcher.group(1);
- try {
- var value = Integer.parseInt(integer);
- return new QuantityType<>(value, DECIBEL_MILLIWATTS);
- } catch (NumberFormatException e) {
- logger.debug("Cannot parse integer {} from wifi {}", integer, wifi, e);
- return UNDEF;
- }
+ public void sendCommand(String command) {
+ sendCommand(CommandParser.parseCommand(command));
}
public void sendCommand(PrinterClient.Channel.Command command) {
@@ -344,6 +528,10 @@ public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.S
}
}
+ private void updateState(BambuLabBindingConstants.Channel channelID, State state) {
+ updateState(channelID.getName(), state);
+ }
+
@Override
public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
super.updateStatus(status, statusDetail, description);
@@ -368,4 +556,38 @@ public class PrinterHandler extends BaseThingHandler implements PrinterWatcher.S
public Logger getLogger() {
return logger;
}
+
+ @Override
+ public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
+ if (!(childHandler instanceof AmsDeviceHandlerFactory ams)) {
+ return;
+ }
+ amses.add(ams);
+ Optional.of(latestPrinterState)//
+ .map(AtomicReference::get)//
+ .map(Report::print).map(Report.Print::ams)//
+ .map(Report.Print.Ams::ams)//
+ .stream()//
+ .flatMap(Collection::stream)//
+ .filter(map -> map.containsKey("id"))//
+ .filter(map -> map.get("id") != null)//
+ // in code, we are using 1-4 ordering; in API 0-3 ordering is used
+ .filter(map -> parseInt(map.get("id").toString()) + 1 == ams.getAmsNumber())//
+ .forEach(ams::updateAms);
+ }
+
+ @Override
+ public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
+ if (!(childHandler instanceof AmsDeviceHandlerFactory ams)) {
+ return;
+ }
+ var removed = amses.remove(ams);
+ if (!removed) {
+ logger.warn("Did not remove ams handler {}", ams);
+ }
+ }
+
+ public String getSerialNumber() {
+ return requireNonNull(config).serial();
+ }
}
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/StateParserHelper.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/StateParserHelper.java
new file mode 100644
index 0000000000..515c6717a2
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/StateParserHelper.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bambulab.internal;
+
+import static java.lang.Integer.parseInt;
+import static org.openhab.core.library.unit.SIUnits.CELSIUS;
+import static org.openhab.core.library.unit.Units.*;
+import static org.openhab.core.types.UnDefType.UNDEF;
+import static tech.units.indriya.unit.Units.PERCENT;
+
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.types.State;
+import org.openhab.core.util.ColorUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Martin Grześlowski - Initial contribution
+ */
+@NonNullByDefault
+public class StateParserHelper {
+ private static final Pattern DBM_PATTERN = Pattern.compile("^(-?\\d+)dBm$");
+ private static final Logger logger = LoggerFactory.getLogger(StateParserHelper.class);
+
+ public static Optional parseTimeMinutes(@Nullable Integer integer) {
+ return Optional.ofNullable(integer)//
+ .map(time -> new QuantityType<>(time, MINUTE));
+ }
+
+ public static Optional parseTemperatureType(@Nullable String value) {
+ try {
+ return Optional.ofNullable(value).map(Double::parseDouble).map(d -> new QuantityType<>(d, CELSIUS));
+ } catch (NumberFormatException ex) {
+ logger.debug("Cannot parse: {}", value, ex);
+ return Optional.of(UNDEF);
+ }
+ }
+
+ public static Optional parseDecimalType(@Nullable Number number) {
+ return Optional.ofNullable(number).map(DecimalType::new);
+ }
+
+ public static Optional parseDecimalType(@Nullable String value) {
+ try {
+ return Optional.ofNullable(value).map(DecimalType::new);
+ } catch (NumberFormatException ex) {
+ logger.debug("Cannot parse {}", value, ex);
+ return Optional.of(UNDEF);
+ }
+ }
+
+ public static Optional parsePercentType(@Nullable Number percent) {
+ return Optional.ofNullable(percent).map(d -> new QuantityType<>(d, PERCENT));
+ }
+
+ public static Optional parsePercentType(@Nullable String percent) {
+ try {
+ return Optional.ofNullable(percent)//
+ .map(Double::parseDouble)//
+ .map(d -> new QuantityType<>(d, PERCENT));
+ } catch (NumberFormatException ex) {
+ logger.debug("Cannot parse {}", percent, ex);
+ return Optional.of(UNDEF);
+ }
+ }
+
+ public static Optional parseStringType(@Nullable String string) {
+ return Optional.ofNullable(string).map(StringType::new);
+ }
+
+ public static State parseWifiChannel(String wifi) {
+ var matcher = DBM_PATTERN.matcher(wifi);
+ if (!matcher.matches()) {
+ logger.debug("Cannot match {} to {}", wifi, DBM_PATTERN);
+ return UNDEF;
+ }
+
+ var integer = matcher.group(1);
+ try {
+ var value = parseInt(integer);
+ return new QuantityType<>(value, DECIBEL_MILLIWATTS);
+ } catch (NumberFormatException e) {
+ logger.debug("Cannot parse integer {} from wifi {}", integer, wifi, e);
+ return UNDEF;
+ }
+ }
+
+ public static State parseColor(String colorHex) {
+ if (colorHex.length() < 6) {
+ return UNDEF;
+ }
+ int r = parseInt(colorHex.substring(0, 2), 16);
+ int g = parseInt(colorHex.substring(2, 4), 16);
+ int b = parseInt(colorHex.substring(4, 6), 16);
+ return ColorUtil.rgbToHsb(new int[] { r, g, b });
+ }
+
+ public static Optional undef() {
+ return Optional.of(UNDEF);
+ }
+}
diff --git a/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/TrayHelper.java b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/TrayHelper.java
new file mode 100644
index 0000000000..14a871dbfd
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/main/java/org/openhab/binding/bambulab/internal/TrayHelper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+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.core.types.UnDefType.UNDEF;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Martin Grześlowski - Initial contribution
+ */
+@NonNullByDefault
+class TrayHelper {
+ static final int MAX_TRAY_VALUE = MAX_AMS * MAX_AMS_TRAYS - 1;
+ private static final Logger logger = LoggerFactory.getLogger(TrayHelper.class);
+
+ static State updateTrayLoaded(@Nullable String tray) {
+ if (tray == null) {
+ return UNDEF;
+ }
+ try {
+ var integer = parseInt(tray);
+ return findStateForTrayLoaded(integer);
+ } catch (NumberFormatException e) {
+ logger.debug("Cannot parse: {}", tray, e);
+ return UNDEF;
+ }
+ }
+
+ private static State findStateForTrayLoaded(int tray) {
+ if (tray < 0) {
+ return UNDEF;
+ }
+ if (tray == 255) {
+ return StringType.valueOf("EMPTY");
+ }
+ if (tray == 254) {
+ return StringType.valueOf("VTRAY");
+ }
+ if (tray > MAX_TRAY_VALUE) {
+ logger.warn("There should never be tray with value {}", tray);
+ return UNDEF;
+ }
+ var amsNr = (tray / MAX_AMS) + 1;
+ var trayNr = (tray % MAX_AMS_TRAYS) + 1;
+ return StringType.valueOf("AMS_%s_%s".formatted(amsNr, trayNr));
+ }
+}
diff --git a/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/i18n/bambulab.properties b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/i18n/bambulab.properties
index 0f8e099e8a..8d6076ee1c 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/i18n/bambulab.properties
+++ b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/i18n/bambulab.properties
@@ -5,8 +5,15 @@ addon.bambulab.description = This is the binding for BambuLab.
# thing types
+thing-type.bambulab.ams-device.label = Bambu Lab AMS Device
+thing-type.bambulab.ams-device.description = Represents a Bambu Lab AMS connected to a printer.
+thing-type.bambulab.ams-device.group.ams-tray-1.label = AMS Tray #1
+thing-type.bambulab.ams-device.group.ams-tray-2.label = AMS Tray #2
+thing-type.bambulab.ams-device.group.ams-tray-3.label = AMS Tray #3
+thing-type.bambulab.ams-device.group.ams-tray-4.label = AMS Tray #4
thing-type.bambulab.printer.label = Bambu Lab Printer
thing-type.bambulab.printer.description = Represents a Bambu Lab 3D printer connected via MQTT
+thing-type.bambulab.printer.channel.ams-tray-previous.label = Previously Loaded Tray
thing-type.bambulab.printer.channel.bed-target-temperature.label = Bed Target Temperature
thing-type.bambulab.printer.channel.bed-target-temperature.description = Target temperature of the heated bed.
thing-type.bambulab.printer.channel.bed-temperature.label = Bed Temperature
@@ -21,6 +28,8 @@ thing-type.bambulab.printer.channel.camera-record.label = Camera Record
thing-type.bambulab.printer.channel.camera-record.description = Turns on/off openHAB's capability of recording.
thing-type.bambulab.printer.channel.chamber-temperature.label = Chamber Temperature
thing-type.bambulab.printer.channel.chamber-temperature.description = Current temperature inside the printer chamber.
+thing-type.bambulab.printer.channel.command.label = Command
+thing-type.bambulab.printer.channel.command.description = Send command to run. See Actions > sendCommand
thing-type.bambulab.printer.channel.gcode-file.label = G-code File
thing-type.bambulab.printer.channel.gcode-file.description = Name of the currently loaded G-code file.
thing-type.bambulab.printer.channel.gcode-file-prepare-percent.label = G-code Preparation Progress
@@ -55,11 +64,31 @@ thing-type.bambulab.printer.channel.use-ams.label = AMS System in Use
thing-type.bambulab.printer.channel.use-ams.description = Indicates whether the Automatic Material System (AMS) is active.
thing-type.bambulab.printer.channel.vibration-calibration.label = Vibration Calibration
thing-type.bambulab.printer.channel.vibration-calibration.description = Indicates whether vibration calibration has been performed.
+thing-type.bambulab.printer.channel.vtray-bed-temp-type.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.vtray-bed-temperature.label = Bed Temperature
+thing-type.bambulab.printer.channel.vtray-k.label = k
+thing-type.bambulab.printer.channel.vtray-n.label = n
+thing-type.bambulab.printer.channel.vtray-nozzle-temperature-max.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.vtray-nozzle-temperature-max.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.vtray-nozzle-temperature-min.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.vtray-nozzle-temperature-min.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.vtray-remain.label = Remain
+thing-type.bambulab.printer.channel.vtray-remain.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.vtray-tag-uuid.label = Tag UUID
+thing-type.bambulab.printer.channel.vtray-tray-diameter.label = Tray Diameter
+thing-type.bambulab.printer.channel.vtray-tray-id-name.label = Tray ID Name
+thing-type.bambulab.printer.channel.vtray-tray-info-idx.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.vtray-tray-sub-brands.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.vtray-tray-temperature.label = Tray Temperature
+thing-type.bambulab.printer.channel.vtray-tray-time.label = Tray Time
+thing-type.bambulab.printer.channel.vtray-tray-weight.label = Tray Weight
thing-type.bambulab.printer.channel.wifi-signal.label = WiFi Signal Strength
thing-type.bambulab.printer.channel.wifi-signal.description = Current WiFi signal strength.
# thing types config
+thing-type.config.bambulab.ams-device.number.label = AMS Number
+thing-type.config.bambulab.ams-device.number.description = Order of the AMS
thing-type.config.bambulab.printer.accessCode.label = Access Code
thing-type.config.bambulab.printer.accessCode.description = Access code of the printer. The method of obtaining it differs for local and cloud modes.
thing-type.config.bambulab.printer.cameraCertificate.label = TLS Certificate
@@ -74,6 +103,10 @@ thing-type.config.bambulab.printer.hostname.label = Hostname
thing-type.config.bambulab.printer.hostname.description = IP address of the printer or `us.mqtt.bambulab.com` for cloud mode.
thing-type.config.bambulab.printer.port.label = Port
thing-type.config.bambulab.printer.port.description = URI port.
+thing-type.config.bambulab.printer.reconnectMax.label = Max Reconnections
+thing-type.config.bambulab.printer.reconnectMax.description = Max times to try reconnections.
+thing-type.config.bambulab.printer.reconnectTime.label = Reconnect Time
+thing-type.config.bambulab.printer.reconnectTime.description = Time in seconds to wait before reconnect.
thing-type.config.bambulab.printer.scheme.label = Scheme
thing-type.config.bambulab.printer.scheme.description = URI scheme.
thing-type.config.bambulab.printer.serial.label = Serial Number
@@ -86,18 +119,38 @@ thing-type.config.bambulab.printer.series.option.X = X Series
thing-type.config.bambulab.printer.username.label = Username
thing-type.config.bambulab.printer.username.description = `bblp` for local mode or your Bambu Lab user (starts with `u_`).
+# channel group types
+
+channel-group-type.bambulab.ams-tray.label = AMS Tray
+channel-group-type.bambulab.ams-tray.description = This channel group represents a single AMS tray.
+channel-group-type.bambulab.ams-tray.channel.ams-bed-temp-type.label = Bed Temperature Type
+channel-group-type.bambulab.ams-tray.channel.ams-bed-temperature.label = Bed Temperature
+channel-group-type.bambulab.ams-tray.channel.ams-ctype.label = ctype
+channel-group-type.bambulab.ams-tray.channel.ams-k.label = k
+channel-group-type.bambulab.ams-tray.channel.ams-n.label = n
+channel-group-type.bambulab.ams-tray.channel.ams-nozzle-temperature-max.label = Nozzle Temperature Max
+channel-group-type.bambulab.ams-tray.channel.ams-nozzle-temperature-max.description = Maximum nozzle temperature for tray.
+channel-group-type.bambulab.ams-tray.channel.ams-nozzle-temperature-min.label = Nozzle Temperature Min
+channel-group-type.bambulab.ams-tray.channel.ams-nozzle-temperature-min.description = Minimum nozzle temperature for tray.
+channel-group-type.bambulab.ams-tray.channel.ams-remain.label = Remain
+channel-group-type.bambulab.ams-tray.channel.ams-remain.description = Amount of remaining filament.
+channel-group-type.bambulab.ams-tray.channel.ams-tag-uuid.label = Tag UUID
+channel-group-type.bambulab.ams-tray.channel.ams-tray-diameter.label = Tray Diameter
+channel-group-type.bambulab.ams-tray.channel.ams-tray-id-name.label = Tray ID Name
+channel-group-type.bambulab.ams-tray.channel.ams-tray-info-idx.label = Tray Info IDX Name
+channel-group-type.bambulab.ams-tray.channel.ams-tray-sub-brands.label = Tray Sub Brands
+channel-group-type.bambulab.ams-tray.channel.ams-tray-temperature.label = Tray Temperature
+channel-group-type.bambulab.ams-tray.channel.ams-tray-time.label = Tray Time
+channel-group-type.bambulab.ams-tray.channel.ams-tray-weight.label = Tray Weight
+
# channel types
channel-type.bambulab.boolean-rw.label = Boolean Channel
channel-type.bambulab.boolean.label = Boolean Channel
channel-type.bambulab.camera-image.label = Camera Image
channel-type.bambulab.camera-image.description = Current image from the printer's camera.
+channel-type.bambulab.number-advanced.label = Advanced Number Channel
channel-type.bambulab.number.label = Number Channel
-channel-type.bambulab.on-off-command.label = ON/OFF Channel
-channel-type.bambulab.on-off-command.state.option.ON = ON
-channel-type.bambulab.on-off-command.state.option.OFF = OFF
-channel-type.bambulab.on-off-command.command.option.ON = ON
-channel-type.bambulab.on-off-command.command.option.OFF = OFF
channel-type.bambulab.percent.label = Percent Channel
channel-type.bambulab.speed-level.label = Print Speed Level
channel-type.bambulab.speed-level.description = Current speed setting of the print job.
@@ -109,18 +162,687 @@ channel-type.bambulab.speed-level.command.option.SILENT = Silent
channel-type.bambulab.speed-level.command.option.STANDARD = Standard
channel-type.bambulab.speed-level.command.option.SPORT = Sport
channel-type.bambulab.speed-level.command.option.LUDICROUS = Ludicrous
+channel-type.bambulab.string-advanced.label = Advanced String Channel
channel-type.bambulab.string-rw.label = RW String Channel
channel-type.bambulab.string.label = String Channel
+channel-type.bambulab.temperature-measurement-advanced.label = Current Temperature
channel-type.bambulab.temperature-measurement.label = Current Temperature
channel-type.bambulab.temperature-setpoint.label = Target Temperature
+channel-type.bambulab.time-min.label = Time (min)
+channel-type.bambulab.time-min.state.pattern = %1$tHh:%1$tMm
+channel-type.bambulab.tray-color.label = Tray Color
+channel-type.bambulab.tray-loaded.label = Currently Loaded Tray
+channel-type.bambulab.tray-loaded.state.option.EMPTY = No tray loaded
+channel-type.bambulab.tray-loaded.state.option.VTRAY = External Spool
+channel-type.bambulab.tray-loaded.state.option.AMS_1_1 = AMS #1 / Tray 1
+channel-type.bambulab.tray-loaded.state.option.AMS_1_2 = AMS #1 / Tray 2
+channel-type.bambulab.tray-loaded.state.option.AMS_1_3 = AMS #1 / Tray 3
+channel-type.bambulab.tray-loaded.state.option.AMS_1_4 = AMS #1 / Tray 4
+channel-type.bambulab.tray-loaded.state.option.AMS_2_1 = AMS #2 / Tray 1
+channel-type.bambulab.tray-loaded.state.option.AMS_2_2 = AMS #2 / Tray 2
+channel-type.bambulab.tray-loaded.state.option.AMS_2_3 = AMS #2 / Tray 3
+channel-type.bambulab.tray-loaded.state.option.AMS_2_4 = AMS #2 / Tray 4
+channel-type.bambulab.tray-loaded.state.option.AMS_3_1 = AMS #3 / Tray 1
+channel-type.bambulab.tray-loaded.state.option.AMS_3_2 = AMS #3 / Tray 2
+channel-type.bambulab.tray-loaded.state.option.AMS_3_3 = AMS #3 / Tray 3
+channel-type.bambulab.tray-loaded.state.option.AMS_3_4 = AMS #3 / Tray 4
+channel-type.bambulab.tray-loaded.state.option.AMS_4_1 = AMS #4 / Tray 1
+channel-type.bambulab.tray-loaded.state.option.AMS_4_2 = AMS #4 / Tray 2
+channel-type.bambulab.tray-loaded.state.option.AMS_4_3 = AMS #4 / Tray 3
+channel-type.bambulab.tray-loaded.state.option.AMS_4_4 = AMS #4 / Tray 4
+channel-type.bambulab.tray-type.label = Tray Type
+channel-type.bambulab.tray-type.state.option.PLA = PLA
+channel-type.bambulab.tray-type.state.option.PETG = PETG
+channel-type.bambulab.tray-type.state.option.ABS = ABS
+channel-type.bambulab.tray-type.state.option.TPU = TPU
+channel-type.bambulab.tray-type.state.option.ASA = ASA
+channel-type.bambulab.tray-type.state.option.PA = PA
+channel-type.bambulab.tray-type.state.option.PC = PC
+channel-type.bambulab.tray-type.state.option.PVA = PVA
+channel-type.bambulab.tray-type.state.option.HIPS = HIPS
channel-type.bambulab.wifi.label = Wi-Fi Signal Strength
channel-type.bambulab.wifi.description = Current Wi-Fi signal strength.
# thing types
+thing-type.bambulab.ams-device.channel.ams-bed-temp-type-id-1.label = Bed Temperature Type
+thing-type.bambulab.ams-device.channel.ams-bed-temp-type-id-2.label = Bed Temperature Type
+thing-type.bambulab.ams-device.channel.ams-bed-temp-type-id-3.label = Bed Temperature Type
+thing-type.bambulab.ams-device.channel.ams-bed-temp-type-id-4.label = Bed Temperature Type
+thing-type.bambulab.ams-device.channel.ams-bed-temperature-id-1.label = Bed Temperature
+thing-type.bambulab.ams-device.channel.ams-bed-temperature-id-2.label = Bed Temperature
+thing-type.bambulab.ams-device.channel.ams-bed-temperature-id-3.label = Bed Temperature
+thing-type.bambulab.ams-device.channel.ams-bed-temperature-id-4.label = Bed Temperature
+thing-type.bambulab.ams-device.channel.ams-ctype-id-1.label = ctype
+thing-type.bambulab.ams-device.channel.ams-ctype-id-2.label = ctype
+thing-type.bambulab.ams-device.channel.ams-ctype-id-3.label = ctype
+thing-type.bambulab.ams-device.channel.ams-ctype-id-4.label = ctype
+thing-type.bambulab.ams-device.channel.ams-k-id-1.label = k
+thing-type.bambulab.ams-device.channel.ams-k-id-2.label = k
+thing-type.bambulab.ams-device.channel.ams-k-id-3.label = k
+thing-type.bambulab.ams-device.channel.ams-k-id-4.label = k
+thing-type.bambulab.ams-device.channel.ams-n-id-1.label = n
+thing-type.bambulab.ams-device.channel.ams-n-id-2.label = n
+thing-type.bambulab.ams-device.channel.ams-n-id-3.label = n
+thing-type.bambulab.ams-device.channel.ams-n-id-4.label = n
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-max-id-1.label = Nozzle Temperature Max
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-max-id-1.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-max-id-2.label = Nozzle Temperature Max
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-max-id-2.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-max-id-3.label = Nozzle Temperature Max
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-max-id-3.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-max-id-4.label = Nozzle Temperature Max
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-max-id-4.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-min-id-1.label = Nozzle Temperature Min
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-min-id-1.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-min-id-2.label = Nozzle Temperature Min
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-min-id-2.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-min-id-3.label = Nozzle Temperature Min
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-min-id-3.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-min-id-4.label = Nozzle Temperature Min
+thing-type.bambulab.ams-device.channel.ams-nozzle-temperature-min-id-4.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.ams-device.channel.ams-remain-id-1.label = Remain
+thing-type.bambulab.ams-device.channel.ams-remain-id-1.description = Amount of remaining filament.
+thing-type.bambulab.ams-device.channel.ams-remain-id-2.label = Remain
+thing-type.bambulab.ams-device.channel.ams-remain-id-2.description = Amount of remaining filament.
+thing-type.bambulab.ams-device.channel.ams-remain-id-3.label = Remain
+thing-type.bambulab.ams-device.channel.ams-remain-id-3.description = Amount of remaining filament.
+thing-type.bambulab.ams-device.channel.ams-remain-id-4.label = Remain
+thing-type.bambulab.ams-device.channel.ams-remain-id-4.description = Amount of remaining filament.
+thing-type.bambulab.ams-device.channel.ams-tag-uuid-id-1.label = Tag UUID
+thing-type.bambulab.ams-device.channel.ams-tag-uuid-id-2.label = Tag UUID
+thing-type.bambulab.ams-device.channel.ams-tag-uuid-id-3.label = Tag UUID
+thing-type.bambulab.ams-device.channel.ams-tag-uuid-id-4.label = Tag UUID
+thing-type.bambulab.ams-device.channel.ams-tray-diameter-id-1.label = Tray Diameter
+thing-type.bambulab.ams-device.channel.ams-tray-diameter-id-2.label = Tray Diameter
+thing-type.bambulab.ams-device.channel.ams-tray-diameter-id-3.label = Tray Diameter
+thing-type.bambulab.ams-device.channel.ams-tray-diameter-id-4.label = Tray Diameter
+thing-type.bambulab.ams-device.channel.ams-tray-id-name-id-1.label = Tray ID Name
+thing-type.bambulab.ams-device.channel.ams-tray-id-name-id-2.label = Tray ID Name
+thing-type.bambulab.ams-device.channel.ams-tray-id-name-id-3.label = Tray ID Name
+thing-type.bambulab.ams-device.channel.ams-tray-id-name-id-4.label = Tray ID Name
+thing-type.bambulab.ams-device.channel.ams-tray-info-idx-id-1.label = Tray Info IDX Name
+thing-type.bambulab.ams-device.channel.ams-tray-info-idx-id-2.label = Tray Info IDX Name
+thing-type.bambulab.ams-device.channel.ams-tray-info-idx-id-3.label = Tray Info IDX Name
+thing-type.bambulab.ams-device.channel.ams-tray-info-idx-id-4.label = Tray Info IDX Name
+thing-type.bambulab.ams-device.channel.ams-tray-sub-brands-id-1.label = Tray Sub Brands
+thing-type.bambulab.ams-device.channel.ams-tray-sub-brands-id-2.label = Tray Sub Brands
+thing-type.bambulab.ams-device.channel.ams-tray-sub-brands-id-3.label = Tray Sub Brands
+thing-type.bambulab.ams-device.channel.ams-tray-sub-brands-id-4.label = Tray Sub Brands
+thing-type.bambulab.ams-device.channel.ams-tray-temperature-id-1.label = Tray Temperature
+thing-type.bambulab.ams-device.channel.ams-tray-temperature-id-2.label = Tray Temperature
+thing-type.bambulab.ams-device.channel.ams-tray-temperature-id-3.label = Tray Temperature
+thing-type.bambulab.ams-device.channel.ams-tray-temperature-id-4.label = Tray Temperature
+thing-type.bambulab.ams-device.channel.ams-tray-time-id-1.label = Tray Time
+thing-type.bambulab.ams-device.channel.ams-tray-time-id-2.label = Tray Time
+thing-type.bambulab.ams-device.channel.ams-tray-time-id-3.label = Tray Time
+thing-type.bambulab.ams-device.channel.ams-tray-time-id-4.label = Tray Time
+thing-type.bambulab.ams-device.channel.ams-tray-weight-id-1.label = Tray Weight
+thing-type.bambulab.ams-device.channel.ams-tray-weight-id-2.label = Tray Weight
+thing-type.bambulab.ams-device.channel.ams-tray-weight-id-3.label = Tray Weight
+thing-type.bambulab.ams-device.channel.ams-tray-weight-id-4.label = Tray Weight
+
+# thing types
+
+thing-type.bambulab.ams-device.channel.ams-tray-previous.label = Previously Loaded Tray
+
+# channel types
+
+channel-type.bambulab.on-off-command.label = ON/OFF Channel
+channel-type.bambulab.on-off-command.state.option.ON = ON
+channel-type.bambulab.on-off-command.state.option.OFF = OFF
+channel-type.bambulab.on-off-command.command.option.ON = ON
+channel-type.bambulab.on-off-command.command.option.OFF = OFF
+channel-type.bambulab.tray-type.command.option.PLA = PLA
+channel-type.bambulab.tray-type.command.option.PETG = PETG
+channel-type.bambulab.tray-type.command.option.ABS = ABS
+channel-type.bambulab.tray-type.command.option.TPU = TPU
+channel-type.bambulab.tray-type.command.option.ASA = ASA
+channel-type.bambulab.tray-type.command.option.PA = PA
+channel-type.bambulab.tray-type.command.option.PC = PC
+channel-type.bambulab.tray-type.command.option.PVA = PVA
+channel-type.bambulab.tray-type.command.option.HIPS = HIPS
+
+# thing types
+
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-1x1.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-1x2.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-1x3.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-1x4.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-2x1.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-2x2.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-2x3.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-2x4.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-3x1.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-3x2.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-3x3.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-3x4.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-4x1.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-4x2.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-4x3.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-4x4.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-1x1.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-1x2.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-1x3.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-1x4.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-2x1.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-2x2.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-2x3.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-2x4.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-3x1.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-3x2.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-3x3.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-3x4.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-4x1.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-4x2.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-4x3.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-4x4.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-ctype-id-1x1.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-1x2.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-1x3.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-1x4.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-2x1.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-2x2.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-2x3.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-2x4.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-3x1.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-3x2.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-3x3.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-3x4.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-4x1.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-4x2.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-4x3.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-4x4.label = ctype
+thing-type.bambulab.printer.channel.ams-k-id-1x1.label = k
+thing-type.bambulab.printer.channel.ams-k-id-1x2.label = k
+thing-type.bambulab.printer.channel.ams-k-id-1x3.label = k
+thing-type.bambulab.printer.channel.ams-k-id-1x4.label = k
+thing-type.bambulab.printer.channel.ams-k-id-2x1.label = k
+thing-type.bambulab.printer.channel.ams-k-id-2x2.label = k
+thing-type.bambulab.printer.channel.ams-k-id-2x3.label = k
+thing-type.bambulab.printer.channel.ams-k-id-2x4.label = k
+thing-type.bambulab.printer.channel.ams-k-id-3x1.label = k
+thing-type.bambulab.printer.channel.ams-k-id-3x2.label = k
+thing-type.bambulab.printer.channel.ams-k-id-3x3.label = k
+thing-type.bambulab.printer.channel.ams-k-id-3x4.label = k
+thing-type.bambulab.printer.channel.ams-k-id-4x1.label = k
+thing-type.bambulab.printer.channel.ams-k-id-4x2.label = k
+thing-type.bambulab.printer.channel.ams-k-id-4x3.label = k
+thing-type.bambulab.printer.channel.ams-k-id-4x4.label = k
+thing-type.bambulab.printer.channel.ams-n-id-1x1.label = n
+thing-type.bambulab.printer.channel.ams-n-id-1x2.label = n
+thing-type.bambulab.printer.channel.ams-n-id-1x3.label = n
+thing-type.bambulab.printer.channel.ams-n-id-1x4.label = n
+thing-type.bambulab.printer.channel.ams-n-id-2x1.label = n
+thing-type.bambulab.printer.channel.ams-n-id-2x2.label = n
+thing-type.bambulab.printer.channel.ams-n-id-2x3.label = n
+thing-type.bambulab.printer.channel.ams-n-id-2x4.label = n
+thing-type.bambulab.printer.channel.ams-n-id-3x1.label = n
+thing-type.bambulab.printer.channel.ams-n-id-3x2.label = n
+thing-type.bambulab.printer.channel.ams-n-id-3x3.label = n
+thing-type.bambulab.printer.channel.ams-n-id-3x4.label = n
+thing-type.bambulab.printer.channel.ams-n-id-4x1.label = n
+thing-type.bambulab.printer.channel.ams-n-id-4x2.label = n
+thing-type.bambulab.printer.channel.ams-n-id-4x3.label = n
+thing-type.bambulab.printer.channel.ams-n-id-4x4.label = n
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x1.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x1.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x2.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x2.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x3.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x3.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x4.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x4.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x1.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x1.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x2.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x2.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x3.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x3.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x4.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x4.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x1.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x1.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x2.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x2.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x3.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x3.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x4.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x4.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-4x1.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-4x1.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-4x2.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-4x2.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-4x3.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-4x3.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-4x4.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-4x4.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x1.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x1.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x2.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x2.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x3.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x3.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x4.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x4.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x1.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x1.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x2.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x2.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x3.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x3.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x4.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x4.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x1.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x1.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x2.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x2.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x3.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x3.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x4.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x4.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-4x1.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-4x1.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-4x2.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-4x2.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-4x3.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-4x3.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-4x4.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-4x4.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-remain-id-1x1.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-1x1.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-1x2.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-1x2.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-1x3.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-1x3.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-1x4.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-1x4.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-2x1.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-2x1.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-2x2.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-2x2.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-2x3.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-2x3.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-2x4.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-2x4.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-3x1.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-3x1.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-3x2.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-3x2.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-3x3.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-3x3.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-3x4.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-3x4.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-4x1.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-4x1.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-4x2.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-4x2.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-4x3.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-4x3.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-4x4.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-4x4.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-1x1.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-1x2.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-1x3.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-1x4.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-2x1.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-2x2.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-2x3.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-2x4.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-3x1.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-3x2.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-3x3.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-3x4.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-4x1.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-4x2.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-4x3.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-4x4.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-1x1.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-1x2.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-1x3.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-1x4.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-2x1.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-2x2.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-2x3.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-2x4.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-3x1.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-3x2.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-3x3.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-3x4.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-4x1.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-4x2.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-4x3.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-4x4.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-1x1.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-1x2.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-1x3.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-1x4.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-2x1.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-2x2.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-2x3.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-2x4.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-3x1.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-3x2.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-3x3.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-3x4.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-4x1.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-4x2.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-4x3.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-4x4.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-1x1.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-1x2.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-1x3.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-1x4.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-2x1.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-2x2.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-2x3.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-2x4.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-3x1.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-3x2.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-3x3.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-3x4.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-4x1.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-4x2.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-4x3.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-4x4.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-1x1.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-1x2.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-1x3.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-1x4.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-2x1.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-2x2.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-2x3.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-2x4.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-3x1.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-3x2.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-3x3.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-3x4.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-4x1.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-4x2.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-4x3.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-4x4.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-1x1.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-1x2.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-1x3.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-1x4.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-2x1.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-2x2.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-2x3.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-2x4.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-3x1.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-3x2.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-3x3.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-3x4.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-4x1.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-4x2.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-4x3.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-4x4.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-time-id-1x1.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-1x2.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-1x3.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-1x4.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-2x1.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-2x2.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-2x3.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-2x4.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-3x1.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-3x2.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-3x3.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-3x4.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-4x1.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-4x2.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-4x3.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-4x4.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-weight-id-1x1.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-1x2.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-1x3.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-1x4.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-2x1.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-2x2.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-2x3.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-2x4.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-3x1.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-3x2.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-3x3.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-3x4.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-4x1.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-4x2.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-4x3.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-4x4.label = Tray Weight
+
+# thing types config
+
+thing-type.config.bambulab.ams-device.init.no-bridge = There is no bridge!
+thing-type.config.bambulab.ams-device.init.bridge-wrong-type = Bridge has wrong type!
+
+# thing types
+
+thing-type.bambulab.ams.label = Bambu Lab AMS Device
+thing-type.bambulab.ams.description = Represents a Bambu Lab AMS connected to a printer.
+thing-type.bambulab.ams.channel.ams-bed-temp-type-id-1.label = Bed Temperature Type
+thing-type.bambulab.ams.channel.ams-bed-temp-type-id-2.label = Bed Temperature Type
+thing-type.bambulab.ams.channel.ams-bed-temp-type-id-3.label = Bed Temperature Type
+thing-type.bambulab.ams.channel.ams-bed-temp-type-id-4.label = Bed Temperature Type
+thing-type.bambulab.ams.channel.ams-bed-temperature-id-1.label = Bed Temperature
+thing-type.bambulab.ams.channel.ams-bed-temperature-id-2.label = Bed Temperature
+thing-type.bambulab.ams.channel.ams-bed-temperature-id-3.label = Bed Temperature
+thing-type.bambulab.ams.channel.ams-bed-temperature-id-4.label = Bed Temperature
+thing-type.bambulab.ams.channel.ams-ctype-id-1.label = ctype
+thing-type.bambulab.ams.channel.ams-ctype-id-2.label = ctype
+thing-type.bambulab.ams.channel.ams-ctype-id-3.label = ctype
+thing-type.bambulab.ams.channel.ams-ctype-id-4.label = ctype
+thing-type.bambulab.ams.channel.ams-k-id-1.label = k
+thing-type.bambulab.ams.channel.ams-k-id-2.label = k
+thing-type.bambulab.ams.channel.ams-k-id-3.label = k
+thing-type.bambulab.ams.channel.ams-k-id-4.label = k
+thing-type.bambulab.ams.channel.ams-n-id-1.label = n
+thing-type.bambulab.ams.channel.ams-n-id-2.label = n
+thing-type.bambulab.ams.channel.ams-n-id-3.label = n
+thing-type.bambulab.ams.channel.ams-n-id-4.label = n
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-max-id-1.label = Nozzle Temperature Max
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-max-id-1.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-max-id-2.label = Nozzle Temperature Max
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-max-id-2.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-max-id-3.label = Nozzle Temperature Max
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-max-id-3.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-max-id-4.label = Nozzle Temperature Max
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-max-id-4.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-min-id-1.label = Nozzle Temperature Min
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-min-id-1.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-min-id-2.label = Nozzle Temperature Min
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-min-id-2.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-min-id-3.label = Nozzle Temperature Min
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-min-id-3.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-min-id-4.label = Nozzle Temperature Min
+thing-type.bambulab.ams.channel.ams-nozzle-temperature-min-id-4.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.ams.channel.ams-remain-id-1.label = Remain
+thing-type.bambulab.ams.channel.ams-remain-id-1.description = Amount of remaining filament.
+thing-type.bambulab.ams.channel.ams-remain-id-2.label = Remain
+thing-type.bambulab.ams.channel.ams-remain-id-2.description = Amount of remaining filament.
+thing-type.bambulab.ams.channel.ams-remain-id-3.label = Remain
+thing-type.bambulab.ams.channel.ams-remain-id-3.description = Amount of remaining filament.
+thing-type.bambulab.ams.channel.ams-remain-id-4.label = Remain
+thing-type.bambulab.ams.channel.ams-remain-id-4.description = Amount of remaining filament.
+thing-type.bambulab.ams.channel.ams-tag-uuid-id-1.label = Tag UUID
+thing-type.bambulab.ams.channel.ams-tag-uuid-id-2.label = Tag UUID
+thing-type.bambulab.ams.channel.ams-tag-uuid-id-3.label = Tag UUID
+thing-type.bambulab.ams.channel.ams-tag-uuid-id-4.label = Tag UUID
+thing-type.bambulab.ams.channel.ams-tray-diameter-id-1.label = Tray Diameter
+thing-type.bambulab.ams.channel.ams-tray-diameter-id-2.label = Tray Diameter
+thing-type.bambulab.ams.channel.ams-tray-diameter-id-3.label = Tray Diameter
+thing-type.bambulab.ams.channel.ams-tray-diameter-id-4.label = Tray Diameter
+thing-type.bambulab.ams.channel.ams-tray-id-name-id-1.label = Tray ID Name
+thing-type.bambulab.ams.channel.ams-tray-id-name-id-2.label = Tray ID Name
+thing-type.bambulab.ams.channel.ams-tray-id-name-id-3.label = Tray ID Name
+thing-type.bambulab.ams.channel.ams-tray-id-name-id-4.label = Tray ID Name
+thing-type.bambulab.ams.channel.ams-tray-info-idx-id-1.label = Tray Info IDX Name
+thing-type.bambulab.ams.channel.ams-tray-info-idx-id-2.label = Tray Info IDX Name
+thing-type.bambulab.ams.channel.ams-tray-info-idx-id-3.label = Tray Info IDX Name
+thing-type.bambulab.ams.channel.ams-tray-info-idx-id-4.label = Tray Info IDX Name
+thing-type.bambulab.ams.channel.ams-tray-previous.label = Previously Loaded Tray
+thing-type.bambulab.ams.channel.ams-tray-sub-brands-id-1.label = Tray Sub Brands
+thing-type.bambulab.ams.channel.ams-tray-sub-brands-id-2.label = Tray Sub Brands
+thing-type.bambulab.ams.channel.ams-tray-sub-brands-id-3.label = Tray Sub Brands
+thing-type.bambulab.ams.channel.ams-tray-sub-brands-id-4.label = Tray Sub Brands
+thing-type.bambulab.ams.channel.ams-tray-temperature-id-1.label = Tray Temperature
+thing-type.bambulab.ams.channel.ams-tray-temperature-id-2.label = Tray Temperature
+thing-type.bambulab.ams.channel.ams-tray-temperature-id-3.label = Tray Temperature
+thing-type.bambulab.ams.channel.ams-tray-temperature-id-4.label = Tray Temperature
+thing-type.bambulab.ams.channel.ams-tray-time-id-1.label = Tray Time
+thing-type.bambulab.ams.channel.ams-tray-time-id-2.label = Tray Time
+thing-type.bambulab.ams.channel.ams-tray-time-id-3.label = Tray Time
+thing-type.bambulab.ams.channel.ams-tray-time-id-4.label = Tray Time
+thing-type.bambulab.ams.channel.ams-tray-weight-id-1.label = Tray Weight
+thing-type.bambulab.ams.channel.ams-tray-weight-id-2.label = Tray Weight
+thing-type.bambulab.ams.channel.ams-tray-weight-id-3.label = Tray Weight
+thing-type.bambulab.ams.channel.ams-tray-weight-id-4.label = Tray Weight
+
+# thing types config
+
+thing-type.config.bambulab.ams.number.label = AMS Number
+thing-type.config.bambulab.ams.number.description = Order of the AMS
+
+# thing types
+
thing-type.bambulab.printer.channel.speed-level.label = Print Speed Level
thing-type.bambulab.printer.channel.speed-level.description = Current speed setting of the print job.
+# thing types
+
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-0x0.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-0x1.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-0x2.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-0x3.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-1x0.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-2x0.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temp-type-id-3x0.label = Bed Temperature Type
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-0x0.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-0x1.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-0x2.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-0x3.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-1x0.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-2x0.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-bed-temperature-id-3x0.label = Bed Temperature
+thing-type.bambulab.printer.channel.ams-ctype-id-0x0.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-0x1.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-0x2.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-0x3.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-1x0.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-2x0.label = ctype
+thing-type.bambulab.printer.channel.ams-ctype-id-3x0.label = ctype
+thing-type.bambulab.printer.channel.ams-k-id-0x0.label = k
+thing-type.bambulab.printer.channel.ams-k-id-0x1.label = k
+thing-type.bambulab.printer.channel.ams-k-id-0x2.label = k
+thing-type.bambulab.printer.channel.ams-k-id-0x3.label = k
+thing-type.bambulab.printer.channel.ams-k-id-1x0.label = k
+thing-type.bambulab.printer.channel.ams-k-id-2x0.label = k
+thing-type.bambulab.printer.channel.ams-k-id-3x0.label = k
+thing-type.bambulab.printer.channel.ams-n-id-0x0.label = n
+thing-type.bambulab.printer.channel.ams-n-id-0x1.label = n
+thing-type.bambulab.printer.channel.ams-n-id-0x2.label = n
+thing-type.bambulab.printer.channel.ams-n-id-0x3.label = n
+thing-type.bambulab.printer.channel.ams-n-id-1x0.label = n
+thing-type.bambulab.printer.channel.ams-n-id-2x0.label = n
+thing-type.bambulab.printer.channel.ams-n-id-3x0.label = n
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-0x0.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-0x0.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-0x1.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-0x1.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-0x2.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-0x2.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-0x3.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-0x3.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x0.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-1x0.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x0.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-2x0.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x0.label = Nozzle Temperature Max
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-max-id-3x0.description = Maximum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-0x0.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-0x0.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-0x1.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-0x1.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-0x2.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-0x2.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-0x3.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-0x3.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x0.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-1x0.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x0.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-2x0.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x0.label = Nozzle Temperature Min
+thing-type.bambulab.printer.channel.ams-nozzle-temperature-min-id-3x0.description = Minimum nozzle temperature for tray.
+thing-type.bambulab.printer.channel.ams-remain-id-0x0.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-0x0.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-0x1.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-0x1.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-0x2.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-0x2.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-0x3.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-0x3.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-1x0.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-1x0.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-2x0.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-2x0.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-remain-id-3x0.label = Remain
+thing-type.bambulab.printer.channel.ams-remain-id-3x0.description = Amount of remaining filament.
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-0x0.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-0x1.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-0x2.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-0x3.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-1x0.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-2x0.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tag-uuid-id-3x0.label = Tag UUID
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-0x0.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-0x1.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-0x2.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-0x3.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-1x0.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-2x0.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-diameter-id-3x0.label = Tray Diameter
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-0x0.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-0x1.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-0x2.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-0x3.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-1x0.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-2x0.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-id-name-id-3x0.label = Tray ID Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-0x0.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-0x1.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-0x2.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-0x3.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-1x0.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-2x0.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-info-idx-id-3x0.label = Tray Info IDX Name
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-0x0.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-0x1.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-0x2.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-0x3.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-1x0.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-2x0.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-sub-brands-id-3x0.label = Tray Sub Brands
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-0x0.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-0x1.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-0x2.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-0x3.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-1x0.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-2x0.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-temperature-id-3x0.label = Tray Temperature
+thing-type.bambulab.printer.channel.ams-tray-time-id-0x0.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-0x1.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-0x2.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-0x3.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-1x0.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-2x0.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-time-id-3x0.label = Tray Time
+thing-type.bambulab.printer.channel.ams-tray-weight-id-0x0.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-0x1.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-0x2.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-0x3.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-1x0.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-2x0.label = Tray Weight
+thing-type.bambulab.printer.channel.ams-tray-weight-id-3x0.label = Tray Weight
+
# channel types
channel-type.bambulab.temperature.label = Temperature
@@ -191,8 +913,6 @@ channel-type.bambulab.wifi-channel.description = Current WiFi signal strength.
# thing types
-thing-type.bambulab.printer.channel.command.label = Printer Command
-thing-type.bambulab.printer.channel.command.description = Current command being executed by the printer.
thing-type.bambulab.printer.channel.message.label = Message Code
thing-type.bambulab.printer.channel.message.description = Message code from the printer.
thing-type.bambulab.printer.channel.sequenceId.label = Sequence ID
@@ -240,4 +960,5 @@ printer.handler.init.noHostname = Please pass hostname!
printer.handler.init.invalidHostname = Invalid hostname "{0}"!
printer.handler.init.cannotConnectCamera = Cannot connect to camera! {0}
printer.handler.init.noSerial = Please pass serial!
+printer.handler.init.connectionLost = Connection lost! {0}
printer.handler.init.noAccessCode = Please pass access code!
diff --git a/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/ams-device-type.xml b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/ams-device-type.xml
new file mode 100644
index 0000000000..a329268f4c
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/ams-device-type.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+ Represents a Bambu Lab AMS connected to a printer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+
+ Order of the AMS
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/channel-group-types.xml b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/channel-group-types.xml
new file mode 100644
index 0000000000..e7cf258f36
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/channel-group-types.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ This channel group represents a single AMS tray.
+
+
+
+
+
+ Maximum nozzle temperature for tray.
+
+
+
+ Minimum nozzle temperature for tray.
+
+
+
+ Amount of remaining filament.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/channel-types.xml
index 44faac80cd..662258f349 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/channel-types.xml
+++ b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/channel-types.xml
@@ -11,7 +11,17 @@
Measurement
Temperature
-
+
+
+
+ Number:Temperature
+
+ Temperature
+
+ Measurement
+ Temperature
+
+
Number:Temperature
@@ -21,7 +31,7 @@
Setpoint
Temperature
-
+
String
@@ -32,6 +42,11 @@
String
+
+ String
+
+
+
Number:Power
@@ -43,10 +58,20 @@
+
+ Number:Time
+
+
+
+
+ Number
+
+
+
Number:Dimensionless
-
+
Switch
@@ -86,21 +111,58 @@
-
+
+
String
-
- Lightbulb
-
+
+
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+ Color
+
+
+
+
+ String
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/thing-types.xml
index 31beff2d50..d8fb5028e9 100644
--- a/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/thing-types.xml
+++ b/bundles/org.openhab.binding.bambulab/src/main/resources/OH-INF/thing/thing-types.xml
@@ -4,11 +4,17 @@
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
+
Represents a Bambu Lab 3D printer connected via MQTT
+
+
+ Actions > sendCommand
+ ]]>
+
Current temperature of the nozzle.
@@ -37,7 +43,7 @@
Percentage of the print completed.
-
+
Estimated time remaining for the print in seconds.
@@ -105,14 +111,73 @@
-
+
Controls the LED lighting inside the printer chamber.
-
+
Controls the LED lighting for the work area.
+
+
+
+
+
+
+
+
+
+
+
+
+ Maximum nozzle temperature for tray.
+
+
+
+ Minimum nozzle temperature for tray.
+
+
+
+ Amount of remaining filament.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
serial
@@ -157,6 +222,18 @@
`bblp` for local mode or your Bambu Lab user (starts with `u_`).
true
+
+
+ Time in seconds to wait before reconnect.
+ 300
+ true
+
+
+
+ Max times to try reconnections.
+ 5
+ true
+
@@ -187,6 +264,6 @@
-
+
diff --git a/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/PrinterActionsTest.java b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/CommandParserTest.java
similarity index 61%
rename from bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/PrinterActionsTest.java
rename to bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/CommandParserTest.java
index 51ba7e29a0..5e6bf1148b 100644
--- a/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/PrinterActionsTest.java
+++ b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/CommandParserTest.java
@@ -12,9 +12,7 @@
*/
package org.openhab.binding.bambulab.internal;
-import static java.util.Objects.requireNonNull;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.verify;
+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.*;
@@ -24,45 +22,20 @@ import java.util.stream.Stream;
import org.assertj.core.api.ThrowableAssert;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
+import org.reflections.Reflections;
import pl.grzeslowski.jbambuapi.mqtt.PrinterClient;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.AmsControlCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.AmsFilamentSettingCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.AmsUserSettingCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.ChangeFilamentCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.Command;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.InfoCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.IpCamRecordCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PushingCommand;
-import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.SystemCommand;
/**
* @author Martin Grzeslowski - Initial contribution
*/
@NonNullByDefault
-@ExtendWith(MockitoExtension.class)
-class PrinterActionsTest {
- PrinterActions printerActions = new PrinterActions();
- @Mock
- @Nullable
- PrinterHandler printerHandler;
-
- @BeforeEach
- void setUp() {
- printerActions.setThingHandler(requireNonNull(printerHandler));
- }
+class CommandParserTest {
@Test
@DisplayName("should correctly parse GCodeLineCommand")
@@ -77,10 +50,10 @@ class PrinterActionsTest {
M104 S200 ; Set hotend temperature to 200°C""";
// when
- printerActions.sendCommand(command);
+ var parsedCommand = CommandParser.parseCommand(command);
// then
- verify(requireNonNull(printerHandler)).sendCommand(new PrinterClient.Channel.GCodeLineCommand(
+ assertThat(parsedCommand).isEqualTo(new PrinterClient.Channel.GCodeLineCommand(
List.of("G28 ; Home all axes", "G90 ; Set to absolute positioning",
"G1 X50 Y50 Z10 F1500 ; Move to position (50,50,10) at 1500 mm/min",
"G1 X100 Y100 Z20 F2000 ; Move to position (100,100,20) at 2000 mm/min",
@@ -95,7 +68,7 @@ class PrinterActionsTest {
var command = "GCodeLine:123";
// when
- ThrowableAssert.ThrowingCallable when = () -> printerActions.sendCommand(command);
+ ThrowableAssert.ThrowingCallable when = () -> CommandParser.parseCommand(command);
// then
assertThatThrownBy(when)//
@@ -105,29 +78,30 @@ class PrinterActionsTest {
@ParameterizedTest(name = "{index}: should {0}")
@MethodSource
- void shouldRunCommand(String command, Command expectedCommand) {
+ void shouldRunCommand(String command, PrinterClient.Channel.Command expectedCommand) {
// when
- printerActions.sendCommand(command);
+ var parsedCommand = CommandParser.parseCommand(command);
// then
- verify(requireNonNull(printerHandler)).sendCommand(expectedCommand);
+ assertThat(parsedCommand).isEqualTo(expectedCommand);
}
static Stream shouldRunCommand() {
- var infoCommandStream = Arrays.stream(InfoCommand.values())//
+ var infoCommandStream = Arrays.stream(PrinterClient.Channel.InfoCommand.values())//
.map(value -> Arguments.of("Info:" + value.name(), value));
- var pushingCommandStream = stream(Arguments.of("Pushing:11:22", new PushingCommand(11, 22)));
+ var pushingCommandStream = stream(
+ Arguments.of("Pushing:11:22", new PrinterClient.Channel.PushingCommand(11, 22)));
var printCommandStream = Arrays.stream(PrinterClient.Channel.PrintCommand.values())//
.map(value -> Arguments.of("Print:" + value.name(), value));
var changeFilamentCommandStream = stream(
- Arguments.of("ChangeFilament:11:22:33", new ChangeFilamentCommand(11, 22, 33)));
- var amsUserSettingCommandStream = stream(
- Arguments.of("AmsUserSetting:11:tRuE:FaLsE", new AmsUserSettingCommand(11, true, false)));
+ Arguments.of("ChangeFilament:11:22:33", new PrinterClient.Channel.ChangeFilamentCommand(11, 22, 33)));
+ var amsUserSettingCommandStream = stream(Arguments.of("AmsUserSetting:11:tRuE:FaLsE",
+ new PrinterClient.Channel.AmsUserSettingCommand(11, true, false)));
var amsFilamentSettingCommandStream = stream(Arguments.of("AmsFilamentSetting:11:22:s3:s4:55:66:s7",
- new AmsFilamentSettingCommand(11, 22, "s3", "s4", 55, 66, "s7")));
- var amsControlCommandStream = Arrays.stream(AmsControlCommand.values())//
+ 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(PrintSpeedCommand.values())//
+ var printSpeedCommandStream = Arrays.stream(PrinterClient.Channel.PrintSpeedCommand.values())//
.map(value -> Arguments.of("PrintSpeed:" + value.name(), value));
var gCodeFileCommandStream = stream(
Arguments.of("GCodeFile:s1", new PrinterClient.Channel.GCodeFileCommand("s1")));
@@ -138,17 +112,17 @@ class PrinterActionsTest {
l3""", new PrinterClient.Channel.GCodeLineCommand(List.of("l1", "l2", "l3"), "s1")));
var ledControlCommandStream = stream(//
Arguments.of("LedControl:CHAMBER_LIGHT:ON",
- new LedControlCommand(CHAMBER_LIGHT, ON, null, null, null, null)), //
+ new PrinterClient.Channel.LedControlCommand(CHAMBER_LIGHT, ON, null, null, null, null)), //
Arguments.of("LedControl:WORK_LIGHT:OFF",
- new LedControlCommand(WORK_LIGHT, OFF, null, null, null, null)), //
+ new PrinterClient.Channel.LedControlCommand(WORK_LIGHT, OFF, null, null, null, null)), //
Arguments.of("LedControl:CHAMBER_LIGHT:FLASHING:11:22:33:44",
- new LedControlCommand(CHAMBER_LIGHT, FLASHING, 11, 22, 33, 44))//
+ new PrinterClient.Channel.LedControlCommand(CHAMBER_LIGHT, FLASHING, 11, 22, 33, 44))//
);
- var systemCommandStream = Arrays.stream(SystemCommand.values())//
+ var systemCommandStream = Arrays.stream(PrinterClient.Channel.SystemCommand.values())//
.map(value -> Arguments.of("System:" + value.name(), value));
var ipCamRecordCommandStream = stream(//
- Arguments.of("IpCamRecord:tRue", new IpCamRecordCommand(true)), //
- Arguments.of("IpCamRecord:fAlSe", new IpCamRecordCommand(false))//
+ Arguments.of("IpCamRecord:tRue", new PrinterClient.Channel.IpCamRecordCommand(true)), //
+ Arguments.of("IpCamRecord:fAlSe", new PrinterClient.Channel.IpCamRecordCommand(false))//
);
var ipCamTimelapsCommandStream = stream(//
Arguments.of("IpCamTimelaps:tRue", new PrinterClient.Channel.IpCamTimelapsCommand(true)), //
@@ -173,4 +147,32 @@ class PrinterActionsTest {
static Stream stream(T... values) {
return Stream.of(values);
}
+
+ @ParameterizedTest(name = "{index}: should support command {0}")
+ @MethodSource
+ void shouldSupportCommand(String command) {
+ // when
+ ThrowableAssert.ThrowingCallable when = () -> CommandParser.parseCommand(command);
+
+ // then
+ assertThatThrownBy(when)//
+ .message()//
+ // if an error message starts with "Unknown..." it means that the giant if statement did not cover
+ // command.
+ //
+ // it might happen if JBambuApi library adds a new subclass of Command and addon developers would not
+ // add it to PrinterActions.
+ //
+ // it might happen that there will be some other IllegalArgumentException (like not enough params) would
+ // be thrown, but none of them starts with "Unknown...".
+ .doesNotStartWith("Unknown command name: " + command);
+ }
+
+ static Stream shouldSupportCommand() {
+ return new Reflections(PrinterClient.Channel.Command.class)//
+ .getSubTypesOf(PrinterClient.Channel.Command.class)//
+ .stream()//
+ .map(Class::getSimpleName)//
+ .map(command -> command.replace("Command", "")).map(Arguments::of);
+ }
}
diff --git a/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/PrinterHandlerTest.java b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/PrinterHandlerTest.java
index c8e6689a1c..ae542cf33b 100644
--- a/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/PrinterHandlerTest.java
+++ b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/PrinterHandlerTest.java
@@ -34,10 +34,11 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.bambulab.internal.BambuLabBindingConstants.Channel;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
import pl.grzeslowski.jbambuapi.mqtt.PrinterClient;
+import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.Command;
import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand;
/**
@@ -47,16 +48,16 @@ import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand;
@NonNullByDefault
class PrinterHandlerTest {
@Spy
- PrinterHandler printerHandler = new PrinterHandler(mock(Thing.class));
+ PrinterHandler printerHandler = new PrinterHandler(mock(Bridge.class));
@BeforeEach
void setUp() {
- lenient().doNothing().when(printerHandler).sendCommand(any(PrinterClient.Channel.Command.class));
+ lenient().doNothing().when(printerHandler).sendCommand(any(Command.class));
}
@ParameterizedTest(name = "Should handle {0} command for {1} channel and send {2}")
@MethodSource
- public void testSendLightCommand(OnOffType command, Channel channel, PrinterClient.Channel.Command sendCommand) {
+ public void testSendLightCommand(OnOffType command, Channel channel, Command sendCommand) {
// Given
var channelUID = new ChannelUID("bambulab:printer:test:" + channel);
@@ -116,7 +117,8 @@ class PrinterHandlerTest {
printerHandler.handleCommand(channelUID, OnOffType.ON);
// Then
- verify(printerHandler, never()).sendCommand(any());
+ verify(printerHandler, never()).sendCommand(any(Command.class));
+ verify(printerHandler, never()).sendCommand(anyString());
}
static Stream notImplementedCommands() {
diff --git a/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/StateParserHelperTest.java b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/StateParserHelperTest.java
new file mode 100644
index 0000000000..279e34586d
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/StateParserHelperTest.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bambulab.internal;
+
+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 tech.units.indriya.unit.Units.PERCENT;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+
+/**
+ * @author Martin Grześlowski - Initial contribution
+ */
+@NonNullByDefault
+class StateParserHelperTest {
+
+ @Test
+ @DisplayName("Given a valid temperature string, when parseTemperatureType is called, then it returns QuantityType with CELSIUS unit")
+ void testParseTemperatureTypeConvertsValidStringToQuantityType() {
+ // Given
+ var temperatureString = "23.5";
+
+ // When
+ var result = StateParserHelper.parseTemperatureType(temperatureString);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isInstanceOf(QuantityType.class);
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(23.5);
+ assertThat(quantityType.getUnit()).isEqualTo(CELSIUS);
+ }
+
+ @Test
+ @DisplayName("Given a valid Number, when parseDecimalType is called, then it returns DecimalType")
+ void testParseDecimalTypeConvertsValidNumberToDecimalType() {
+ // Given
+ var number = 42.5;
+
+ // When
+ var result = StateParserHelper.parseDecimalType(number);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isInstanceOf(DecimalType.class);
+ assertThat(((DecimalType) result.get()).doubleValue()).isEqualTo(42.5);
+ }
+
+ @Test
+ @DisplayName("Given a valid numeric string, when parseDecimalType is called, then it returns DecimalType")
+ void testParseDecimalTypeConvertsValidStringToDecimalType() {
+ // Given
+ var decimalString = "123.45";
+
+ // When
+ var result = StateParserHelper.parseDecimalType(decimalString);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isInstanceOf(DecimalType.class);
+ assertThat(((DecimalType) result.get()).doubleValue()).isEqualTo(123.45);
+ }
+
+ @Test
+ @DisplayName("Given a non-null string, when parseStringType is called, then it returns StringType")
+ void testParseStringTypeConvertsNonNullStringToStringType() {
+ // Given
+ var inputString = "test string";
+
+ // When
+ var result = StateParserHelper.parseStringType(inputString);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isInstanceOf(StringType.class);
+ assertThat(result.get().toString()).isEqualTo("test string");
+ }
+
+ @Test
+ @DisplayName("Given a valid dBm pattern string, when parseWifiChannel is called, then it returns QuantityType with DECIBEL_MILLIWATTS unit")
+ void testParseWifiChannelConvertsValidDbmPatternToQuantityType() {
+ // Given
+ var wifiString = "-75dBm";
+
+ // When
+ var result = StateParserHelper.parseWifiChannel(wifiString);
+
+ // Then
+ assertThat(result).isInstanceOf(QuantityType.class);
+ var quantityType = (QuantityType>) result;
+ assertThat(quantityType.intValue()).isEqualTo(-75);
+ assertThat(quantityType.getUnit()).isEqualTo(DECIBEL_MILLIWATTS);
+ }
+
+ @Test
+ @DisplayName("Given a valid 6-character hex string, when parseColor is called, then it returns HSB color State")
+ void testParseColorConvertsValidHexStringToHsbColor() {
+ // Given
+ var colorHex = "FF0000"; // Red in RGB
+
+ // When
+ var result = StateParserHelper.parseColor(colorHex);
+
+ // Then
+ assertThat(result).isNotEqualTo(UNDEF);
+
+ // HSB for pure red should have H=0, S=100, B=100
+ var hsbValues = result.toString().split(",");
+ assertThat(Double.parseDouble(hsbValues[0])).isCloseTo(0.0, within(0.1));
+ assertThat(Double.parseDouble(hsbValues[1])).isCloseTo(100.0, within(0.1));
+ assertThat(Double.parseDouble(hsbValues[2])).isCloseTo(100.0, within(0.1));
+ }
+
+ @Test
+ @DisplayName("Given a null input, when parseTemperatureType is called, then it returns empty Optional")
+ void testParseTemperatureTypeReturnsEmptyOptionalForNullInput() {
+ // Given
+ String temperatureString = null;
+
+ // When
+ var result = StateParserHelper.parseTemperatureType(temperatureString);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Given a non-numeric string, when parseTemperatureType is called, then it returns empty Optional")
+ void testParseTemperatureTypeReturnsEmptyOptionalForNonNumericString() {
+ // Given
+ var temperatureString = "not-a-number";
+
+ // When
+ var result = StateParserHelper.parseTemperatureType(temperatureString);
+
+ // Then
+ assertThat(result).contains(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Given a null Number, when parseDecimalType is called, then it returns empty Optional")
+ void testParseDecimalTypeReturnsEmptyOptionalForNullNumber() {
+ // Given
+ Number number = null;
+
+ // When
+ var result = StateParserHelper.parseDecimalType(number);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Given a null String, when parseDecimalType is called, then it returns empty Optional")
+ void testParseDecimalTypeReturnsEmptyOptionalForNullString() {
+ // Given
+ String decimalString = null;
+
+ // When
+ var result = StateParserHelper.parseDecimalType(decimalString);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Given a non-numeric String, when parseDecimalType is called, then it returns empty Optional")
+ void testParseDecimalTypeReturnsEmptyOptionalForNonNumericString() {
+ // Given
+ var decimalString = "not-a-decimal";
+
+ // When
+ var result = StateParserHelper.parseDecimalType(decimalString);
+
+ // Then
+ assertThat(result).contains(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Given a null String, when parseStringType is called, then it returns empty Optional")
+ void testParseStringTypeReturnsEmptyOptionalForNullString() {
+ // Given
+ String inputString = null;
+
+ // When
+ var result = StateParserHelper.parseStringType(inputString);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Given a valid numeric value, when parsePercentType is called, then it returns Optional with QuantityType")
+ public void testValidNumericReturnsQuantityTypeWithPercentUnit() {
+ // Given
+ var numericValue = 75;
+
+ // When
+ var result = StateParserHelper.parsePercentType(numericValue);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isInstanceOf(QuantityType.class);
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ assertThat(quantityType.doubleValue()).isEqualTo(75.0);
+ }
+
+ @Test
+ @DisplayName("Given an integer value, when parsePercentType is called, then it returns correct QuantityType")
+ public void testIntegerValuesAreCorrectlyParsed() {
+ // Given
+ var integerValue = 100;
+
+ // When
+ var result = StateParserHelper.parsePercentType(integerValue);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(100.0);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Given a decimal value, when parsePercentType is called, then it returns correct QuantityType")
+ public void testDecimalValuesAreCorrectlyParsed() {
+ // Given
+ var decimalValue = 50.5;
+
+ // When
+ var result = StateParserHelper.parsePercentType(decimalValue);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(50.5);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Given a zero value, when parsePercentType is called, then it returns QuantityType with zero value")
+ public void testZeroValueIsCorrectlyParsed2() {
+ // Given
+ var zeroValue = 0;
+
+ // When
+ var result = StateParserHelper.parsePercentType(zeroValue);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isZero();
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Given a negative value, when parsePercentType is called, then it returns QuantityType with negative value")
+ public void testNegativeValuesAreCorrectlyParsed2() {
+ // Given
+ var negativeValue = -10;
+
+ // When
+ var result = StateParserHelper.parsePercentType(negativeValue);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(-10.0);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Given a null input, when parsePercentType is called, then it returns Optional.empty")
+ public void testNullInputReturnsEmptyOptional2() {
+ // Given
+ Number nullValue = null;
+
+ // When
+ var result = StateParserHelper.parsePercentType(nullValue);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Given an empty string input, when parsePercentType is called, then it returns Optional.empty")
+ public void testEmptyStringInputReturnsEmptyOptional2() {
+ // This test is not applicable as the method only accepts Number objects
+ // The method signature is: parsePercentType(@Nullable Number percent)
+ // Empty strings would be handled by a different method before this one is called
+
+ // For completeness, we'll verify that null returns empty
+ // Given
+ Number nullValue = null;
+
+ // When
+ var result = StateParserHelper.parsePercentType(nullValue);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Given a non-numeric string input, when parsePercentType is called, then it returns Optional.empty")
+ public void testNonNumericStringReturnsEmptyOptional2() {
+ // This test is not applicable as the method only accepts Number objects
+ // The method signature is: parsePercentType(@Nullable Number percent)
+ // Non-numeric strings would be handled by a different method before this one is called
+
+ // For completeness, we'll verify that null returns empty
+ // Given
+ Number nullValue = null;
+
+ // When
+ var result = StateParserHelper.parsePercentType(nullValue);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Given a very large number, when parsePercentType is called, then it returns correct QuantityType")
+ public void testVeryLargeNumbersAreHandledCorrectly2() {
+ // Given
+ var largeValue = Double.MAX_VALUE / 2;
+
+ // When
+ var result = StateParserHelper.parsePercentType(largeValue);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(largeValue);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Given a very small number, when parsePercentType is called, then it returns correct QuantityType")
+ public void testVerySmallNumbersAreHandledCorrectly2() {
+ // Given
+ var smallValue = Double.MIN_VALUE;
+
+ // When
+ var result = StateParserHelper.parsePercentType(smallValue);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(smallValue);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Valid numeric string returns Optional containing QuantityType with PERCENT unit")
+ public void testValidNumericStringReturnsQuantityTypeWithPercentUnit() {
+ // Given
+ var percentString = "75";
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isInstanceOf(QuantityType.class);
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ assertThat(quantityType.doubleValue()).isEqualTo(75.0);
+ }
+
+ @Test
+ @DisplayName("Integer string values like '100' are correctly parsed")
+ public void testIntegerStringValuesAreCorrectlyParsed() {
+ // Given
+ var percentString = "100";
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(100.0);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Decimal string values like '50.5' are correctly parsed")
+ public void testDecimalStringValuesAreCorrectlyParsed() {
+ // Given
+ var percentString = "50.5";
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(50.5);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Zero value '0' is correctly parsed")
+ public void testZeroValueIsCorrectlyParsed() {
+ // Given
+ var percentString = "0";
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(0.0);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Negative values like '-10' are correctly parsed")
+ public void testNegativeValuesAreCorrectlyParsed() {
+ // Given
+ var percentString = "-10";
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(-10.0);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Null input returns Optional.empty()")
+ public void testNullInputReturnsEmptyOptional() {
+ // Given
+ String percentString = null;
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Empty string input throws NumberFormatException and returns Optional.empty()")
+ public void testEmptyStringInputReturnsEmptyOptional() {
+ // Given
+ var percentString = "";
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).contains(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Non-numeric string like 'abc' throws NumberFormatException and returns Optional.empty()")
+ public void testNonNumericStringReturnsEmptyOptional() {
+ // Given
+ var percentString = "abc";
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).contains(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Very large numbers near Double.MAX_VALUE are handled correctly")
+ public void testVeryLargeNumbersAreHandledCorrectly() {
+ // Given
+ var percentString = String.valueOf(Double.MAX_VALUE);
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(Double.MAX_VALUE);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+
+ @Test
+ @DisplayName("Very small numbers near Double.MIN_VALUE are handled correctly")
+ public void testVerySmallNumbersAreHandledCorrectly() {
+ // Given
+ var percentString = String.valueOf(Double.MIN_VALUE);
+
+ // When
+ var result = StateParserHelper.parsePercentType(percentString);
+
+ // Then
+ assertThat(result).isPresent();
+ var quantityType = (QuantityType>) result.get();
+ assertThat(quantityType.doubleValue()).isEqualTo(Double.MIN_VALUE);
+ assertThat(quantityType.getUnit()).isEqualTo(PERCENT);
+ }
+}
diff --git a/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/TrayHelperTest.java b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/TrayHelperTest.java
new file mode 100644
index 0000000000..8daec36d30
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/TrayHelperTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bambulab.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.openhab.core.types.UnDefType.UNDEF;
+
+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.StringType;
+
+/**
+ * @author Martin Grześlowski - Initial contribution
+ */
+@NonNullByDefault
+class TrayHelperTest {
+
+ @Test
+ @DisplayName("String '255' returns EMPTY state")
+ public void testString255ReturnsEmpty() {
+ // Given
+ var tray = "255";
+
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray);
+
+ // Then
+ assertThat(result).isInstanceOf(StringType.class);
+ assertThat(result.toString()).isEqualTo("EMPTY");
+ }
+
+ @Test
+ @DisplayName("String '254' returns VTRAY state")
+ public void testString254ReturnsVtray() {
+ // Given
+ var tray = "254";
+
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray);
+
+ // Then
+ assertThat(result).isInstanceOf(StringType.class);
+ assertThat(result.toString()).isEqualTo("VTRAY");
+ }
+
+ @ParameterizedTest(name = "{index}: should parse {0} as {1}")
+ @MethodSource
+ public void happyPath(int tray, String expected) {
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray + "");
+
+ // Then
+ assertThat(result.toString()).isEqualTo(expected);
+ }
+
+ static Stream happyPath() {
+ return Stream.of(
+ // AMS 1
+ Arguments.of(0, "AMS_1_1"), //
+ Arguments.of(1, "AMS_1_2"), //
+ Arguments.of(2, "AMS_1_3"), //
+ Arguments.of(3, "AMS_1_4"), //
+ // AMS 2
+ Arguments.of(0 + 4, "AMS_2_1"), //
+ Arguments.of(1 + 4, "AMS_2_2"), //
+ Arguments.of(2 + 4, "AMS_2_3"), //
+ Arguments.of(3 + 4, "AMS_2_4"), //
+ // AMS 3
+ Arguments.of(0 + 8, "AMS_3_1"), //
+ Arguments.of(1 + 8, "AMS_3_2"), //
+ Arguments.of(2 + 8, "AMS_3_3"), //
+ Arguments.of(3 + 8, "AMS_3_4"), //
+ // AMS 4
+ Arguments.of(0 + 12, "AMS_4_1"), //
+ Arguments.of(1 + 12, "AMS_4_2"), //
+ Arguments.of(2 + 12, "AMS_4_3"), //
+ Arguments.of(3 + 12, "AMS_4_4"));
+ }
+
+ @Test
+ @DisplayName("Null input returns UNDEF")
+ public void testNullInputReturnsUndef() {
+ // Given
+ String tray = null;
+
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray);
+
+ // Then
+ assertThat(result).isEqualTo(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Non-numeric string returns UNDEF")
+ public void testNonNumericStringReturnsUndef() {
+ // Given
+ var tray = "not-a-number";
+
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray);
+
+ // Then
+ assertThat(result).isEqualTo(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Empty string returns UNDEF")
+ public void testEmptyStringReturnsUndef() {
+ // Given
+ var tray = "";
+
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray);
+
+ // Then
+ assertThat(result).isEqualTo(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Negative numbers are processed as undef")
+ public void negatives() {
+ // Given
+ var tray = "-5";
+
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray);
+
+ // Then
+ assertThat(result).isEqualTo(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Exceeding maximum tray value returns UNDEF with warning")
+ public void testExceedingMaxValueReturnsUndefWithWarning() {
+ // Given
+ var tray = (TrayHelper.MAX_TRAY_VALUE + 1) + "";
+
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray);
+
+ // Then
+ assertThat(result).isEqualTo(UNDEF);
+ }
+
+ @Test
+ @DisplayName("Value for `MAX_TRAY_VALUE` should return `AMS_4_4`")
+ public void testForMaxTray() {
+ // Given
+ var tray = TrayHelper.MAX_TRAY_VALUE + "";
+
+ // When
+ var result = TrayHelper.updateTrayLoaded(tray);
+
+ // Then
+ assertThat(result.toString()).isEqualTo("AMS_4_4");
+ }
+}
diff --git a/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/TrayTypeTest.java b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/TrayTypeTest.java
new file mode 100644
index 0000000000..6ccce366a8
--- /dev/null
+++ b/bundles/org.openhab.binding.bambulab/src/test/java/org/openhab/binding/bambulab/internal/TrayTypeTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bambulab.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+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.binding.bambulab.internal.BambuLabBindingConstants.AmsChannel.TrayType;
+
+/**
+ * @author Martin Grześlowski - Initial contribution
+ */
+@NonNullByDefault
+class TrayTypeTest {
+ @Test
+ @DisplayName("Should find ABS when lowercase 'abs' is passed")
+ public void testSuccessfullyFindsAbsWhenLowercaseAbsIsPassed() {
+ // Given
+ var input = "abs";
+
+ // When
+ var result = TrayType.findTrayType(input);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isEqualTo(TrayType.ABS);
+ }
+
+ @Test
+ @DisplayName("Should find TPU when uppercase 'TPU' is passed")
+ public void testSuccessfullyFindsTpuWhenUppercaseTpuIsPassed() {
+ // Given
+ var input = "TPU";
+
+ // When
+ var result = TrayType.findTrayType(input);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isEqualTo(TrayType.TPU);
+ }
+
+ @Test
+ @DisplayName("Should return empty Optional when input doesn't match any TrayType")
+ public void testReturnsEmptyOptionalWhenInputDoesntMatchAnyTrayType() {
+ // Given
+ var input = "UNKNOWN_MATERIAL";
+
+ // When
+ var result = TrayType.findTrayType(input);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Should handle empty string input")
+ public void testHandlesEmptyStringInput() {
+ // Given
+ var input = "";
+
+ // When
+ var result = TrayType.findTrayType(input);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Should handle mixed case inputs like 'PeTg' correctly")
+ public void testHandlesMixedCaseInputsCorrectly() {
+ // Given
+ var input = "PeTg";
+
+ // When
+ var result = TrayType.findTrayType(input);
+
+ // Then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isEqualTo(TrayType.PETG);
+ }
+
+ @Test
+ @DisplayName("Should handle inputs with leading/trailing whitespace")
+ public void testHandlesInputsWithLeadingAndTrailingWhitespace() {
+ // Given
+ var input = " ABS ";
+
+ // When
+ var result = TrayType.findTrayType(input);
+
+ // Then
+ assertThat(result).isEmpty();
+ }
+
+ @ParameterizedTest(name = "{index}: should parse {0} to {1}")
+ @MethodSource
+ void testPerformsCaseInsensitiveComparisonForAllEnumValues(String given, TrayType trayType) {
+ assertThat(TrayType.findTrayType(given))//
+ .isPresent()//
+ .hasValueSatisfying(type -> assertThat(type).isEqualTo(trayType));
+ }
+
+ static Stream testPerformsCaseInsensitiveComparisonForAllEnumValues() {
+ return Arrays.stream(TrayType.values())//
+ .flatMap(trayType -> Stream.of(//
+ Arguments.of(trayType.name().toUpperCase(), trayType), //
+ Arguments.of(trayType.name().toLowerCase(), trayType), //
+ Arguments.of(trayType.name().charAt(0) + trayType.name().substring(1).toLowerCase(),
+ trayType)));
+ }
+}