[bambulab] Support REFRESH command for all channels (#18457)

* allow refresh

Signed-off-by: Martin Grześlowski <martin.grzeslowski@gmail.com>
This commit is contained in:
Martin
2025-03-29 10:51:00 +01:00
committed by GitHub
parent 52ef611008
commit 64bfd7b6ca
13 changed files with 295 additions and 239 deletions
@@ -30,6 +30,7 @@ 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.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,11 +39,12 @@ import org.slf4j.LoggerFactory;
* @author Martin Grześlowski - Initial contribution
*/
@NonNullByDefault
public class AmsDeviceHandlerFactory extends BaseThingHandler {
private Logger logger = LoggerFactory.getLogger(AmsDeviceHandlerFactory.class);
public class AmsDeviceHandler extends BaseThingHandler {
private Logger logger = LoggerFactory.getLogger(AmsDeviceHandler.class);
private @Nullable AmsDeviceConfiguration config;
private @Nullable PrinterHandler printer;
public AmsDeviceHandlerFactory(Thing thing) {
public AmsDeviceHandler(Thing thing) {
super(thing);
}
@@ -57,11 +59,11 @@ public class AmsDeviceHandlerFactory extends BaseThingHandler {
}
private void internalInitialize() throws InitializationException {
var printer = validateBridge();
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));
logger = LoggerFactory.getLogger(
"%s.%s.%d".formatted(AmsDeviceHandler.class.getName(), printer.getSerialNumber(), config.number));
updateStatus(ONLINE);
}
@@ -80,7 +82,11 @@ public class AmsDeviceHandlerFactory extends BaseThingHandler {
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// no commands to handle
if (command == RefreshType.REFRESH) {
Optional.ofNullable(printer)//
.flatMap(p -> p.findLatestAms(getAmsNumber()))//
.ifPresent(this::updateAms);
}
}
public void updateAms(Map<String, Object> ams) {
@@ -222,7 +228,8 @@ public class AmsDeviceHandlerFactory extends BaseThingHandler {
@Override
public void dispose() {
printer = null;
config = null;
logger = LoggerFactory.getLogger(AmsDeviceHandlerFactory.class);
logger = LoggerFactory.getLogger(AmsDeviceHandler.class);
}
}
@@ -41,7 +41,7 @@ public class BambuLabBindingConstants {
public static final ThingTypeUID AMS_THING_TYPE = new ThingTypeUID(BINDING_ID, "ams-device");
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public enum Channel {
public enum PrinterChannel {
CHANNEL_COMMAND("command", true),
CHANNEL_NOZZLE_TEMPERATURE("nozzle-temperature"),
CHANNEL_NOZZLE_TARGET_TEMPERATURE("nozzle-target-temperature"),
@@ -51,6 +51,7 @@ public class BambuLabBindingConstants {
CHANNEL_MC_PRINT_STAGE("mc-print-stage"),
CHANNEL_MC_PERCENT("mc-percent"),
CHANNEL_MC_REMAINING_TIME("mc-remaining-time"),
CHANNEL_END_DATE("end-date"),
CHANNEL_WIFI_SIGNAL("wifi-signal"),
CHANNEL_BED_TYPE("bed-type"),
CHANNEL_GCODE_FILE("gcode-file", true),
@@ -96,12 +97,12 @@ public class BambuLabBindingConstants {
private final String name;
private final boolean supportCommand;
Channel(String name, boolean supportCommand) {
PrinterChannel(String name, boolean supportCommand) {
this.name = name;
this.supportCommand = supportCommand;
}
private Channel(String name) {
private PrinterChannel(String name) {
this(name, false);
}
@@ -121,6 +122,12 @@ public class BambuLabBindingConstants {
public boolean is(ChannelUID channelUID) {
return name.equals(channelUID.getId());
}
public static Optional<PrinterChannel> findChannel(ChannelUID channel) {
return stream(values())//
.filter(c -> c.is(channel))//
.findAny();
}
}
public static class AmsChannel {
@@ -52,7 +52,7 @@ public class BambuLabHandlerFactory extends BaseThingHandlerFactory {
}
if (AMS_THING_TYPE.equals(thingTypeUID)) {
return new AmsDeviceHandlerFactory(thing);
return new AmsDeviceHandler(thing);
}
return null;
@@ -14,8 +14,8 @@ package org.openhab.binding.bambulab.internal;
import static java.lang.Thread.interrupted;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.Channel.*;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.NO_CAMERA_CERT;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.PrinterChannel.*;
import static org.openhab.binding.bambulab.internal.PrinterConfiguration.Series.X;
import static org.openhab.core.library.types.OnOffType.*;
import static org.openhab.core.thing.ThingStatus.*;
@@ -19,10 +19,8 @@ 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.BambuLabBindingConstants.PrinterChannel.*;
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.thing.ThingStatus.OFFLINE;
import static org.openhab.core.thing.ThingStatus.ONLINE;
import static org.openhab.core.thing.ThingStatus.UNKNOWN;
@@ -34,11 +32,10 @@ import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PushingCommand
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClientConfig.requiredFields;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
@@ -48,9 +45,8 @@ 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.binding.bambulab.internal.BambuLabBindingConstants.PrinterChannel;
import org.openhab.core.library.types.OnOffType;
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;
@@ -60,6 +56,7 @@ import org.openhab.core.thing.ThingStatusDetail;
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.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -90,7 +87,7 @@ public class PrinterHandler extends BaseBridgeHandler
private final AtomicReference<@Nullable ScheduledFuture<?>> reconnectSchedule = new AtomicReference<>();
private final PrinterWatcher printerWatcher = new PrinterWatcher();
private @Nullable PrinterClientConfig config;
private final Collection<AmsDeviceHandlerFactory> amses = synchronizedList(new ArrayList<>());
private final Collection<AmsDeviceHandler> amses = synchronizedList(new ArrayList<>());
private final AtomicReference<@Nullable Report> latestPrinterState = new AtomicReference<>();
public PrinterHandler(Bridge bridge) {
@@ -99,7 +96,12 @@ public class PrinterHandler extends BaseBridgeHandler
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (CHANNEL_LED_CHAMBER_LIGHT.is(channelUID) || CHANNEL_LED_WORK_LIGHT.is(channelUID)) {
if (command instanceof RefreshType) {
Optional.of(latestPrinterState)//
.map(AtomicReference::get)//
.map(Report::print)//
.ifPresent(printer -> updateState(channelUID, printer));
} else if (CHANNEL_LED_CHAMBER_LIGHT.is(channelUID) || CHANNEL_LED_WORK_LIGHT.is(channelUID)) {
var ledNode = CHANNEL_LED_CHAMBER_LIGHT.is(channelUID) ? CHAMBER_LIGHT : WORK_LIGHT;
var bambuCommand = "ON".equals(command.toFullString()) ? on(ledNode) : off(ledNode);
sendCommand(bambuCommand);
@@ -130,6 +132,16 @@ public class PrinterHandler extends BaseBridgeHandler
}
}
private void updateState(ChannelUID channelUid, Report.Print print) {
var someChannel = findChannel(channelUid);
if (someChannel.isEmpty()) {
logger.warn("Cannot find channel for {}", channelUid);
return;
}
var channel = someChannel.get();
updateState(channel, print);
}
@Override
public void initialize() {
try {
@@ -305,132 +317,143 @@ public class PrinterHandler extends BaseBridgeHandler
if (delta == null) {
return;
}
updatePrinterChannels(delta);
var print = delta.print();
if (print == null) {
return;
}
stream(PrinterChannel.values()).forEach(channel -> updateState(channel, print));
// if got new Printer state (and not failed) then make sure that thing status in ONLINE
updateStatus(ONLINE);
}
private void updatePrinterChannels(Report state) {
// Print
var print = state.print();
if (print == null) {
return;
}
// tempers
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, 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, print.mcPercent());
updatePercentState(CHANNEL_GCODE_FILE_PREPARE_PERCENT, print.gcodeFilePreparePercent());
// decimal
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, new StringType(speedLevel.toString()));
}
// boolean
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, 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 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 updateState(PrinterChannel channel, Report.Print print) {
var vtray = Optional.of(print).map(Report.Print::vtTray);
Optional<State> state = switch (channel) {
// temper
case CHANNEL_NOZZLE_TEMPERATURE -> parseTemperatureType(print.nozzleTemper());
case CHANNEL_NOZZLE_TARGET_TEMPERATURE -> parseTemperatureType(print.nozzleTargetTemper());
case CHANNEL_BED_TEMPERATURE -> parseTemperatureType(print.bedTemper());
case CHANNEL_BED_TARGET_TEMPERATURE -> parseTemperatureType(print.bedTargetTemper());
case CHANNEL_CHAMBER_TEMPERATURE -> parseTemperatureType(print.chamberTemper());
// string
case CHANNEL_MC_PRINT_STAGE -> parseStringType(print.mcPrintStage());
case CHANNEL_BED_TYPE -> parseStringType(print.bedType());
case CHANNEL_GCODE_FILE -> parseStringType(print.gcodeFile());
case CHANNEL_GCODE_STATE -> parseStringType(print.gcodeState());
case CHANNEL_REASON -> parseStringType(print.reason());
case CHANNEL_RESULT -> parseStringType(print.result());
// percents
case CHANNEL_MC_PERCENT -> parsePercentType(print.mcPercent());
case CHANNEL_GCODE_FILE_PREPARE_PERCENT -> parsePercentType(print.gcodeFilePreparePercent());
// time
case CHANNEL_MC_REMAINING_TIME -> parseTimeMinutes(print.mcRemainingTime());
// wifi
case CHANNEL_WIFI_SIGNAL -> parseWifiChannel(print.wifiSignal());
// decimal
case CHANNEL_BIG_FAN_1_SPEED -> parseDecimalType(print.bigFan1Speed());
case CHANNEL_BIG_FAN_2_SPEED -> parseDecimalType(print.bigFan2Speed());
case CHANNEL_HEAT_BREAK_FAN_SPEED -> parseDecimalType(print.heatbreakFanSpeed());
case CHANNEL_LAYER_NUM -> parseDecimalType(print.layerNum());
// boolean
case CHANNEL_TIME_LAPS -> parseOnOffType(print.timelapse());
case CHANNEL_USE_AMS -> parseOnOffType(print.useAms());
case CHANNEL_VIBRATION_CALIBRATION -> parseOnOffType(print.vibrationCali());
// lights
case CHANNEL_LED_CHAMBER_LIGHT -> parseChamberLightType(print.lightsReport());
case CHANNEL_LED_WORK_LIGHT -> parseWorkLightType(print.lightsReport());
// vtray
case CHANNEL_VTRAY_TRAY_TYPE -> //
vtray.map(Report.Print.VtTray::trayType)//
.flatMap(StateParserHelper::parseTrayType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TRAY_COLOR -> //
vtray.map(Report.Print.VtTray::trayColor)//
.map(StateParserHelper::parseColor)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_NOZZLE_TEMPERATURE_MAX -> //
vtray.map(Report.Print.VtTray::nozzleTempMax)//
.flatMap(StateParserHelper::parseTemperatureType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_NOZZLE_TEMPERATURE_MIN -> //
vtray.map(Report.Print.VtTray::nozzleTempMin)//
.flatMap(StateParserHelper::parseTemperatureType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_REMAIN -> //
vtray.map(Report.Print.VtTray::remain)//
.flatMap(StateParserHelper::parsePercentType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_K -> //
vtray.map(Report.Print.VtTray::k)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_N -> //
vtray.map(Report.Print.VtTray::n)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TAG_UUID -> //
vtray.map(Report.Print.VtTray::trayUuid)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TRAY_ID_NAME -> //
vtray.map(Report.Print.VtTray::trayIdName)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TRAY_INFO_IDX -> //
vtray.map(Report.Print.VtTray::trayInfoIdx)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TRAY_SUB_BRANDS -> //
vtray.map(Report.Print.VtTray::traySubBrands)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TRAY_WEIGHT -> //
vtray.map(Report.Print.VtTray::trayWeight)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TRAY_DIAMETER -> //
vtray.map(Report.Print.VtTray::trayDiameter)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TRAY_TEMPERATURE -> //
vtray.map(Report.Print.VtTray::trayTemp)//
.flatMap(StateParserHelper::parseTemperatureType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_TRAY_TIME -> //
vtray.map(Report.Print.VtTray::trayTime)//
.flatMap(StateParserHelper::parseDecimalType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_BED_TEMPERATURE_TYPE -> //
vtray.map(Report.Print.VtTray::bedTempType)//
.flatMap(StateParserHelper::parseStringType)//
.or(StateParserHelper::undef);
case CHANNEL_VTRAY_BED_TEMPERATURE -> //
vtray.map(Report.Print.VtTray::bedTemp)//
.flatMap(StateParserHelper::parseTemperatureType)//
.or(StateParserHelper::undef);
// ams
case CHANNEL_AMS_TRAY_NOW -> //
Optional.of(print)//
.map(Report.Print::ams)//
.map(Report.Print.Ams::trayNow)//
.flatMap(TrayHelper::findStateForTrayLoaded);
case CHANNEL_AMS_TRAY_PREVIOUS -> //
Optional.of(print)//
.map(Report.Print::ams)//
.map(Report.Print.Ams::trayPre)//
.flatMap(TrayHelper::findStateForTrayLoaded);
// misc
case CHANNEL_SPEED_LEVEL -> parseSpeedLevel(print.spdLvl());
case CHANNEL_END_DATE -> //
Optional.ofNullable(print.mcRemainingTime())//
.map(time -> ZonedDateTime.now().plusMinutes(time))//
.map(StateParserHelper::parseDateTimeType);
// surrogate channels
case CHANNEL_COMMAND -> Optional.empty();
case CHANNEL_CAMERA_IMAGE -> Optional.empty();
case CHANNEL_CAMERA_RECORD -> Optional.empty();
};
state.ifPresent(s -> updateState(channel, s));
}
private void updateAms(Map<String, Object> amsMap) {
@@ -448,68 +471,6 @@ public class PrinterHandler extends BaseBridgeHandler
.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(BambuLabBindingConstants.Channel channelId, @Nullable String string) {
if (string == null) {
return;
}
updateState(channelId, new StringType(string));
}
private void updateDecimalState(BambuLabBindingConstants.Channel channelId, @Nullable Number number) {
if (number == null) {
return;
}
updateState(channelId, new DecimalType(number));
}
private void updateDecimalState(BambuLabBindingConstants.Channel channelId, @Nullable String number) {
if (number == null) {
return;
}
try {
var state = new DecimalType(Double.parseDouble(number));
updateState(channelId, state);
} catch (NumberFormatException e) {
logger.debug("Cannot parse decimal number {}", number, e);
updateState(channelId, UNDEF);
}
}
private void updateBooleanState(BambuLabBindingConstants.Channel channelId, @Nullable Boolean bool) {
if (bool == null) {
return;
}
updateState(channelId, OnOffType.from(bool));
}
private void updatePercentState(BambuLabBindingConstants.Channel channelId, @Nullable Integer integer) {
parsePercentType(integer).ifPresent(state -> updateState(channelId, state));
}
private void updatePercentState(BambuLabBindingConstants.Channel channelId, @Nullable String integer) {
parsePercentType(integer).ifPresent(state -> updateState(channelId, state));
}
private void updateLightState(String lightName, BambuLabBindingConstants.Channel channel,
@Nullable List<Map<String, String>> lights) {
Optional.ofNullable(lights)//
.stream()//
.flatMap(Collection::stream)//
.filter(map -> lightName.equalsIgnoreCase(map.get("node")))//
.map(map -> map.get("mode"))//
.filter(Objects::nonNull)//
.map(OnOffType::from)//
.findAny()//
.ifPresent(command -> updateState(channel, command));
}
public void sendCommand(String command) {
sendCommand(CommandParser.parseCommand(command));
}
@@ -528,7 +489,7 @@ public class PrinterHandler extends BaseBridgeHandler
}
}
private void updateState(BambuLabBindingConstants.Channel channelID, State state) {
private void updateState(PrinterChannel channelID, State state) {
updateState(channelID.getName(), state);
}
@@ -559,11 +520,15 @@ public class PrinterHandler extends BaseBridgeHandler
@Override
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
if (!(childHandler instanceof AmsDeviceHandlerFactory ams)) {
if (!(childHandler instanceof AmsDeviceHandler ams)) {
return;
}
amses.add(ams);
Optional.of(latestPrinterState)//
findLatestAms(ams.getAmsNumber()).ifPresent(ams::updateAms);
}
public Optional<Map<String, Object>> findLatestAms(int amsNumber) {
return Optional.of(latestPrinterState)//
.map(AtomicReference::get)//
.map(Report::print).map(Report.Print::ams)//
.map(Report.Print.Ams::ams)//
@@ -572,13 +537,12 @@ public class PrinterHandler extends BaseBridgeHandler
.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);
.filter(map -> parseInt(map.get("id").toString()) + 1 == amsNumber).findAny();
}
@Override
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
if (!(childHandler instanceof AmsDeviceHandlerFactory ams)) {
if (!(childHandler instanceof AmsDeviceHandler ams)) {
return;
}
var removed = amses.remove(ams);
@@ -18,12 +18,19 @@ 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.time.ZonedDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
@@ -31,6 +38,8 @@ import org.openhab.core.util.ColorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.PrintSpeedCommand;
/**
* @author Martin Grześlowski - Initial contribution
*/
@@ -44,9 +53,15 @@ public class StateParserHelper {
.map(time -> new QuantityType<>(time, MINUTE));
}
public static Optional<State> parseTemperatureType(@Nullable Number value) {
return Optional.ofNullable(value).map(d -> new QuantityType<>(d, CELSIUS));
}
public static Optional<State> parseTemperatureType(@Nullable String value) {
try {
return Optional.ofNullable(value).map(Double::parseDouble).map(d -> new QuantityType<>(d, CELSIUS));
return Optional.ofNullable(value)//
.map(Double::parseDouble)//
.flatMap(StateParserHelper::parseTemperatureType);
} catch (NumberFormatException ex) {
logger.debug("Cannot parse: {}", value, ex);
return Optional.of(UNDEF);
@@ -85,20 +100,23 @@ public class StateParserHelper {
return Optional.ofNullable(string).map(StringType::new);
}
public static State parseWifiChannel(String wifi) {
public static Optional<State> parseWifiChannel(@Nullable String wifi) {
if (wifi == null) {
return Optional.empty();
}
var matcher = DBM_PATTERN.matcher(wifi);
if (!matcher.matches()) {
logger.debug("Cannot match {} to {}", wifi, DBM_PATTERN);
return UNDEF;
return Optional.of(UNDEF);
}
var integer = matcher.group(1);
try {
var value = parseInt(integer);
return new QuantityType<>(value, DECIBEL_MILLIWATTS);
return Optional.of(new QuantityType<>(value, DECIBEL_MILLIWATTS));
} catch (NumberFormatException e) {
logger.debug("Cannot parse integer {} from wifi {}", integer, wifi, e);
return UNDEF;
return Optional.of(UNDEF);
}
}
@@ -115,4 +133,46 @@ public class StateParserHelper {
public static Optional<State> undef() {
return Optional.of(UNDEF);
}
public static Optional<State> parseOnOffType(@Nullable Boolean bool) {
return Optional.ofNullable(bool).map(OnOffType::from);
}
public static Optional<State> parseSpeedLevel(@Nullable Integer speedLvl) {
return Optional.ofNullable(speedLvl)//
.map(PrintSpeedCommand::findByLevel)//
.map(Object::toString)//
.flatMap(StateParserHelper::parseStringType);
}
public static Optional<State> parseTrayType(@Nullable String trayType) {
return Optional.ofNullable(trayType).map(Object::toString)//
.flatMap(BambuLabBindingConstants.AmsChannel.TrayType::findTrayType)//
.map(Enum::name)//
.flatMap(StateParserHelper::parseStringType);
}
public static Optional<State> parseChamberLightType(@Nullable List<Map<String, String>> lights) {
return parseLightType("chamber_light", lights);
}
public static Optional<State> parseWorkLightType(@Nullable List<Map<String, String>> lights) {
return parseLightType("work_light", lights);
}
private static Optional<State> parseLightType(String lightName, @Nullable List<Map<String, String>> lights) {
return Optional.ofNullable(lights)//
.stream()//
.flatMap(Collection::stream)//
.filter(map -> lightName.equalsIgnoreCase(map.get("node")))//
.map(map -> map.get("mode"))//
.filter(Objects::nonNull)//
.map(OnOffType::from)//
.map(state -> (State) state)//
.findAny();
}
public static State parseDateTimeType(ZonedDateTime dateTime) {
return new DateTimeType(dateTime);
}
}
@@ -16,6 +16,8 @@ import static java.lang.Integer.parseInt;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.AmsChannel.*;
import static org.openhab.core.types.UnDefType.UNDEF;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.StringType;
@@ -31,20 +33,20 @@ 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) {
static Optional<State> findStateForTrayLoaded(@Nullable String tray) {
if (tray == null) {
return UNDEF;
return Optional.empty();
}
try {
var integer = parseInt(tray);
return findStateForTrayLoaded(integer);
return Optional.of(parseTrayLoaded(integer));
} catch (NumberFormatException e) {
logger.debug("Cannot parse: {}", tray, e);
return UNDEF;
return Optional.of(UNDEF);
}
}
private static State findStateForTrayLoaded(int tray) {
private static State parseTrayLoaded(int tray) {
if (tray < 0) {
return UNDEF;
}
@@ -30,6 +30,8 @@ thing-type.bambulab.printer.channel.chamber-temperature.label = Chamber Temperat
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 <a href="https://github.com/openhab/openhab-addons/tree/main/bundles/org.openhab.binding.bambulab#sendcommand">Actions > sendCommand</a>
thing-type.bambulab.printer.channel.end-date.label = End Time of the Print
thing-type.bambulab.printer.channel.end-date.description = Estimated end date of the print.
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
@@ -170,6 +172,7 @@ 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.timestamp.label = Time Stamp
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
@@ -63,6 +63,12 @@
<label>Time (min)</label>
<state readOnly="true" pattern="%1$tHh:%1$tMm"/>
</channel-type>
<channel-type id="timestamp">
<item-type>DateTime</item-type>
<label>Time Stamp</label>
<category>time</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="number-advanced" advanced="true">
<item-type>Number</item-type>
<label>Advanced Number Channel</label>
@@ -47,6 +47,10 @@
<label>Remaining Print Time</label>
<description>Estimated time remaining for the print in seconds.</description>
</channel>
<channel id="end-date" typeId="timestamp">
<label>End Time of the Print</label>
<description>Estimated end date of the print.</description>
</channel>
<channel id="wifi-signal" typeId="wifi">
<label>WiFi Signal Strength</label>
<description>Current WiFi signal strength.</description>
@@ -15,7 +15,7 @@ package org.openhab.binding.bambulab.internal;
import static java.util.Arrays.stream;
import static java.util.function.Predicate.not;
import static org.mockito.Mockito.*;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.Channel.*;
import static org.openhab.binding.bambulab.internal.BambuLabBindingConstants.PrinterChannel.*;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.*;
import static pl.grzeslowski.jbambuapi.mqtt.PrinterClient.Channel.LedControlCommand.LedNode.*;
@@ -31,7 +31,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.bambulab.internal.BambuLabBindingConstants.Channel;
import org.openhab.binding.bambulab.internal.BambuLabBindingConstants.PrinterChannel;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
@@ -57,7 +57,7 @@ class PrinterHandlerTest {
@ParameterizedTest(name = "Should handle {0} command for {1} channel and send {2}")
@MethodSource
public void testSendLightCommand(OnOffType command, Channel channel, Command sendCommand) {
public void testSendLightCommand(OnOffType command, PrinterChannel channel, Command sendCommand) {
// Given
var channelUID = new ChannelUID("bambulab:printer:test:" + channel);
@@ -109,7 +109,7 @@ class PrinterHandlerTest {
@ParameterizedTest(name = "Command to channel {0} should not invoke `client.sendCommand`")
@MethodSource
public void notImplementedCommands(Channel channel) {
public void notImplementedCommands(PrinterChannel channel) {
// Given
var channelUID = new ChannelUID("bambulab:printer:test:" + channel);
@@ -122,8 +122,8 @@ class PrinterHandlerTest {
}
static Stream<Arguments> notImplementedCommands() {
return stream(Channel.values())//
.filter(not(Channel::isSupportCommand))//
return stream(PrinterChannel.values())//
.filter(not(PrinterChannel::isSupportCommand))//
.map(Arguments::of);
}
}
@@ -100,9 +100,11 @@ class StateParserHelperTest {
var wifiString = "-75dBm";
// When
var result = StateParserHelper.parseWifiChannel(wifiString);
var someResult = StateParserHelper.parseWifiChannel(wifiString);
// Then
assertThat(someResult).isNotEmpty();
var result = someResult.get();
assertThat(result).isInstanceOf(QuantityType.class);
var quantityType = (QuantityType<?>) result;
assertThat(quantityType.intValue()).isEqualTo(-75);
@@ -28,6 +28,7 @@ import org.openhab.core.library.types.StringType;
/**
* @author Martin Grześlowski - Initial contribution
*/
@SuppressWarnings("OptionalGetWithoutIsPresent")
@NonNullByDefault
class TrayHelperTest {
@@ -38,7 +39,7 @@ class TrayHelperTest {
var tray = "255";
// When
var result = TrayHelper.updateTrayLoaded(tray);
var result = TrayHelper.findStateForTrayLoaded(tray).get();
// Then
assertThat(result).isInstanceOf(StringType.class);
@@ -52,7 +53,7 @@ class TrayHelperTest {
var tray = "254";
// When
var result = TrayHelper.updateTrayLoaded(tray);
var result = TrayHelper.findStateForTrayLoaded(tray).get();
// Then
assertThat(result).isInstanceOf(StringType.class);
@@ -63,7 +64,7 @@ class TrayHelperTest {
@MethodSource
public void happyPath(int tray, String expected) {
// When
var result = TrayHelper.updateTrayLoaded(tray + "");
var result = TrayHelper.findStateForTrayLoaded(tray + "").get();
// Then
assertThat(result.toString()).isEqualTo(expected);
@@ -100,10 +101,10 @@ class TrayHelperTest {
String tray = null;
// When
var result = TrayHelper.updateTrayLoaded(tray);
var result = TrayHelper.findStateForTrayLoaded(tray);
// Then
assertThat(result).isEqualTo(UNDEF);
assertThat(result).isEmpty();
}
@Test
@@ -113,7 +114,7 @@ class TrayHelperTest {
var tray = "not-a-number";
// When
var result = TrayHelper.updateTrayLoaded(tray);
var result = TrayHelper.findStateForTrayLoaded(tray).get();
// Then
assertThat(result).isEqualTo(UNDEF);
@@ -126,7 +127,7 @@ class TrayHelperTest {
var tray = "";
// When
var result = TrayHelper.updateTrayLoaded(tray);
var result = TrayHelper.findStateForTrayLoaded(tray).get();
// Then
assertThat(result).isEqualTo(UNDEF);
@@ -139,7 +140,7 @@ class TrayHelperTest {
var tray = "-5";
// When
var result = TrayHelper.updateTrayLoaded(tray);
var result = TrayHelper.findStateForTrayLoaded(tray).get();
// Then
assertThat(result).isEqualTo(UNDEF);
@@ -152,7 +153,7 @@ class TrayHelperTest {
var tray = (TrayHelper.MAX_TRAY_VALUE + 1) + "";
// When
var result = TrayHelper.updateTrayLoaded(tray);
var result = TrayHelper.findStateForTrayLoaded(tray).get();
// Then
assertThat(result).isEqualTo(UNDEF);
@@ -165,7 +166,7 @@ class TrayHelperTest {
var tray = TrayHelper.MAX_TRAY_VALUE + "";
// When
var result = TrayHelper.updateTrayLoaded(tray);
var result = TrayHelper.findStateForTrayLoaded(tray).get();
// Then
assertThat(result.toString()).isEqualTo("AMS_4_4");