2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.modbus.stiebeleltron.internal.handler;
15 import static org.openhab.binding.modbus.stiebeleltron.internal.StiebelEltronBindingConstants.*;
16 import static org.openhab.core.library.unit.SIUnits.CELSIUS;
17 import static org.openhab.core.library.unit.Units.*;
19 import java.util.Optional;
21 import javax.measure.Unit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
26 import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
27 import org.openhab.binding.modbus.stiebeleltron.internal.StiebelEltronConfiguration;
28 import org.openhab.binding.modbus.stiebeleltron.internal.dto.EnergyBlock;
29 import org.openhab.binding.modbus.stiebeleltron.internal.dto.SystemInformationBlock;
30 import org.openhab.binding.modbus.stiebeleltron.internal.dto.SystemParameterBlock;
31 import org.openhab.binding.modbus.stiebeleltron.internal.dto.SystemStateBlock;
32 import org.openhab.binding.modbus.stiebeleltron.internal.parser.EnergyBlockParser;
33 import org.openhab.binding.modbus.stiebeleltron.internal.parser.SystemInfromationBlockParser;
34 import org.openhab.binding.modbus.stiebeleltron.internal.parser.SystemParameterBlockParser;
35 import org.openhab.binding.modbus.stiebeleltron.internal.parser.SystemStateBlockParser;
36 import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
37 import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
38 import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
39 import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
40 import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
41 import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
42 import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
43 import org.openhab.core.io.transport.modbus.PollTask;
44 import org.openhab.core.library.types.DecimalType;
45 import org.openhab.core.library.types.OpenClosedType;
46 import org.openhab.core.library.types.QuantityType;
47 import org.openhab.core.thing.Bridge;
48 import org.openhab.core.thing.ChannelUID;
49 import org.openhab.core.thing.Thing;
50 import org.openhab.core.thing.ThingStatus;
51 import org.openhab.core.thing.ThingStatusDetail;
52 import org.openhab.core.thing.ThingStatusInfo;
53 import org.openhab.core.thing.binding.BaseThingHandler;
54 import org.openhab.core.thing.binding.ThingHandler;
55 import org.openhab.core.types.Command;
56 import org.openhab.core.types.RefreshType;
57 import org.openhab.core.types.State;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
62 * The {@link Modbus.StiebelEltronHandler} is responsible for handling commands,
63 * which are sent to one of the channels and for polling the modbus.
65 * @author Paul Frank - Initial contribution
68 public class StiebelEltronHandler extends BaseThingHandler {
70 public abstract class AbstractBasePoller {
72 private final Logger logger = LoggerFactory.getLogger(StiebelEltronHandler.class);
74 private volatile @Nullable PollTask pollTask;
76 public synchronized void unregisterPollTask() {
77 PollTask task = pollTask;
82 ModbusCommunicationInterface mycomms = StiebelEltronHandler.this.comms;
83 if (mycomms != null) {
84 mycomms.unregisterRegularPoll(task);
90 * Register poll task This is where we set up our regular poller
92 public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) {
93 logger.debug("Setting up regular polling");
95 ModbusCommunicationInterface mycomms = StiebelEltronHandler.this.comms;
96 StiebelEltronConfiguration myconfig = StiebelEltronHandler.this.config;
97 if (myconfig == null || mycomms == null) {
98 throw new IllegalStateException("registerPollTask called without proper configuration");
101 ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(), readFunctionCode, address,
102 length, myconfig.getMaxTries());
104 long refreshMillis = myconfig.getRefreshMillis();
106 pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
107 result.getRegisters().ifPresent(this::handlePolledData);
108 if (getThing().getStatus() != ThingStatus.ONLINE) {
109 updateStatus(ThingStatus.ONLINE);
111 }, StiebelEltronHandler.this::handleReadError);
114 public synchronized void poll() {
115 PollTask task = pollTask;
116 ModbusCommunicationInterface mycomms = StiebelEltronHandler.this.comms;
117 if (task != null && mycomms != null) {
118 mycomms.submitOneTimePoll(task.getRequest(), task.getResultCallback(), task.getFailureCallback());
122 protected abstract void handlePolledData(ModbusRegisterArray registers);
128 private final Logger logger = LoggerFactory.getLogger(StiebelEltronHandler.class);
131 * Configuration instance
133 protected @Nullable StiebelEltronConfiguration config = null;
135 * Parser used to convert incoming raw messages into system blocks
137 private final SystemInfromationBlockParser systemInformationBlockParser = new SystemInfromationBlockParser();
139 * Parser used to convert incoming raw messages into system state blocks
141 private final SystemStateBlockParser systemstateBlockParser = new SystemStateBlockParser();
143 * Parser used to convert incoming raw messages into system parameter blocks
145 private final SystemParameterBlockParser systemParameterBlockParser = new SystemParameterBlockParser();
147 * Parser used to convert incoming raw messages into model blocks
149 private final EnergyBlockParser energyBlockParser = new EnergyBlockParser();
151 * This is the task used to poll the device
153 private volatile @Nullable AbstractBasePoller systemInformationPoller = null;
155 * This is the task used to poll the device
157 private volatile @Nullable AbstractBasePoller energyPoller = null;
159 * This is the task used to poll the device
161 private volatile @Nullable AbstractBasePoller systemStatePoller = null;
163 * This is the task used to poll the device
165 private volatile @Nullable AbstractBasePoller systemParameterPoller = null;
167 * Communication interface to the slave endpoint we're connecting to
169 protected volatile @Nullable ModbusCommunicationInterface comms = null;
172 * This is the slave id, we store this once initialization is complete
174 private volatile int slaveId;
177 * Instances of this handler should get a reference to the modbus manager
179 * @param thing the thing to handle
180 * @param modbusManager the modbus manager
182 public StiebelEltronHandler(Thing thing) {
187 * @param address address of the value to be written on the modbus
188 * @param shortValue value to be written on the modbus
190 protected void writeInt16(int address, short shortValue) {
191 StiebelEltronConfiguration myconfig = StiebelEltronHandler.this.config;
192 ModbusCommunicationInterface mycomms = StiebelEltronHandler.this.comms;
194 if (myconfig == null || mycomms == null) {
195 throw new IllegalStateException("registerPollTask called without proper configuration");
197 // big endian byte ordering
198 byte hi = (byte) (shortValue >> 8);
199 byte lo = (byte) shortValue;
200 ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
202 ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
203 false, myconfig.getMaxTries());
205 mycomms.submitOneTimeWrite(request, result -> {
206 if (hasConfigurationError()) {
209 logger.debug("Successful write, matching request {}", request);
210 StiebelEltronHandler.this.updateStatus(ThingStatus.ONLINE);
212 StiebelEltronHandler.this.handleWriteError(failure);
217 * @param command get the value of this command.
218 * @return short the value of the command multiplied by 10 (see datatype 2 in
219 * the stiebel eltron modbus documentation)
221 private short getScaledInt16Value(Command command) throws StiebelEltronException {
222 if (command instanceof QuantityType quantityCommand) {
223 QuantityType<?> c = quantityCommand.toUnit(CELSIUS);
225 return (short) (c.doubleValue() * 10);
227 throw new StiebelEltronException("Unsupported unit");
230 if (command instanceof DecimalType c) {
231 return (short) (c.doubleValue() * 10);
233 throw new StiebelEltronException("Unsupported command type");
237 * @param command get the value of this command.
238 * @return short the value of the command as short
240 private short getInt16Value(Command command) throws StiebelEltronException {
241 if (command instanceof DecimalType c) {
242 return c.shortValue();
244 throw new StiebelEltronException("Unsupported command type");
248 * Handle incoming commands.
251 public void handleCommand(ChannelUID channelUID, Command command) {
252 if (RefreshType.REFRESH == command) {
253 String groupId = channelUID.getGroupId();
254 if (groupId != null) {
255 AbstractBasePoller poller;
257 case GROUP_SYSTEM_STATE:
258 poller = systemStatePoller;
260 case GROUP_SYSTEM_PARAMETER:
261 poller = systemParameterPoller;
263 case GROUP_SYSTEM_INFO:
264 poller = systemInformationPoller;
266 case GROUP_ENERGY_INFO:
267 poller = energyPoller;
273 if (poller != null) {
279 if (GROUP_SYSTEM_PARAMETER.equals(channelUID.getGroupId())) {
280 switch (channelUID.getIdWithoutGroup()) {
281 case CHANNEL_OPERATION_MODE:
282 writeInt16(1500, getInt16Value(command));
284 case CHANNEL_COMFORT_TEMPERATURE_HEATING:
285 writeInt16(1501, getScaledInt16Value(command));
287 case CHANNEL_ECO_TEMPERATURE_HEATING:
288 writeInt16(1502, getScaledInt16Value(command));
290 case CHANNEL_COMFORT_TEMPERATURE_WATER:
291 writeInt16(1509, getScaledInt16Value(command));
293 case CHANNEL_ECO_TEMPERATURE_WATER:
294 writeInt16(1510, getScaledInt16Value(command));
298 } catch (StiebelEltronException error) {
299 if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
302 String cls = error.getClass().getName();
303 String msg = error.getMessage();
304 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
305 String.format("Error with: %s: %s", cls, msg));
311 * Initialization: Load the config object of the block Connect to the slave
312 * bridge Start the periodic polling
315 public void initialize() {
316 config = getConfigAs(StiebelEltronConfiguration.class);
317 logger.debug("Initializing thing with properties: {}", thing.getProperties());
323 * This method starts the operation of this handler Connect to the slave bridge
324 * Start the periodic polling1
326 private void startUp() {
331 ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
332 if (slaveEndpointThingHandler == null) {
333 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Bridge is offline");
338 slaveId = slaveEndpointThingHandler.getSlaveId();
340 comms = slaveEndpointThingHandler.getCommunicationInterface();
341 } catch (EndpointNotInitializedException e) {
342 // this will be handled below as endpoint remains null
346 @SuppressWarnings("null")
347 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
348 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
349 String.format("Bridge '%s' not completely initialized", label));
353 if (config == null) {
354 logger.debug("Invalid comms/config/manager ref for stiebel eltron handler");
358 if (systemInformationPoller == null) {
359 AbstractBasePoller poller = new AbstractBasePoller() {
361 protected void handlePolledData(ModbusRegisterArray registers) {
362 handlePolledSystemInformationData(registers);
365 poller.registerPollTask(500, 36, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
366 systemInformationPoller = poller;
368 if (energyPoller == null) {
369 AbstractBasePoller poller = new AbstractBasePoller() {
371 protected void handlePolledData(ModbusRegisterArray registers) {
372 handlePolledEnergyData(registers);
375 poller.registerPollTask(3500, 16, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
376 energyPoller = poller;
378 if (systemStatePoller == null) {
379 AbstractBasePoller poller = new AbstractBasePoller() {
381 protected void handlePolledData(ModbusRegisterArray registers) {
382 handlePolledSystemStateData(registers);
385 poller.registerPollTask(2500, 2, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
386 systemStatePoller = poller;
388 if (systemParameterPoller == null) {
389 AbstractBasePoller poller = new AbstractBasePoller() {
391 protected void handlePolledData(ModbusRegisterArray registers) {
392 handlePolledSystemParameterData(registers);
395 poller.registerPollTask(1500, 11, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
396 systemParameterPoller = poller;
398 updateStatus(ThingStatus.UNKNOWN);
402 * Dispose the binding correctly
405 public void dispose() {
410 * Unregister the poll tasks and release the endpoint reference
412 private void tearDown() {
413 AbstractBasePoller poller = systemInformationPoller;
414 if (poller != null) {
415 logger.debug("Unregistering systemInformationPoller from ModbusManager");
416 poller.unregisterPollTask();
418 systemInformationPoller = null;
421 poller = energyPoller;
422 if (poller != null) {
423 logger.debug("Unregistering energyPoller from ModbusManager");
424 poller.unregisterPollTask();
429 poller = systemStatePoller;
430 if (poller != null) {
431 logger.debug("Unregistering systemStatePoller from ModbusManager");
432 poller.unregisterPollTask();
434 systemStatePoller = null;
437 poller = systemParameterPoller;
438 if (poller != null) {
439 logger.debug("Unregistering systemParameterPoller from ModbusManager");
440 poller.unregisterPollTask();
442 systemParameterPoller = null;
449 * Returns the current slave id from the bridge
451 public int getSlaveId() {
456 * Get the endpoint handler from the bridge this handler is connected to Checks
457 * that we're connected to the right type of bridge
459 * @return the endpoint handler or null if the bridge does not exist
461 private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
462 Bridge bridge = getBridge();
463 if (bridge == null) {
464 logger.debug("Bridge is null");
467 if (bridge.getStatus() != ThingStatus.ONLINE) {
468 logger.debug("Bridge is not online");
472 ThingHandler handler = bridge.getHandler();
473 if (handler == null) {
474 logger.debug("Bridge handler is null");
478 if (handler instanceof ModbusEndpointThingHandler thingHandler) {
481 throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
486 * Returns value divided by the 10
488 * @param value the value to alter
489 * @return the scaled value as a DecimalType
491 protected State getScaled(Number value, Unit<?> unit) {
492 return QuantityType.valueOf(value.doubleValue() / 10, unit);
496 * Returns high value * 1000 + low value
498 * @param high the high value
499 * @param low the low valze
500 * @return the scaled value as a DecimalType
502 protected State getEnergyQuantity(int high, int low) {
503 double value = high * 1000 + low;
504 return QuantityType.valueOf(value, KILOWATT_HOUR);
508 * This method is called each time new data has been polled from the modbus
509 * slave The register array is first parsed, then each of the channels are
510 * updated to the new values
512 * @param registers byte array read from the modbus slave
514 protected void handlePolledSystemInformationData(ModbusRegisterArray registers) {
515 logger.trace("System Information block received, size: {}", registers.size());
517 SystemInformationBlock block = systemInformationBlockParser.parse(registers);
519 // System information group
520 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_FEK_TEMPERATURE), getScaled(block.temperatureFek, CELSIUS));
521 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_FEK_TEMPERATURE_SETPOINT),
522 getScaled(block.temperatureFekSetPoint, CELSIUS));
523 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_FEK_HUMIDITY), getScaled(block.humidityFek, PERCENT));
524 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_FEK_DEWPOINT), getScaled(block.dewpointFek, CELSIUS));
525 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_OUTDOOR_TEMPERATURE),
526 getScaled(block.temperatureOutdoor, CELSIUS));
527 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_HK1_TEMPERATURE), getScaled(block.temperatureHk1, CELSIUS));
528 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_HK1_TEMPERATURE_SETPOINT),
529 getScaled(block.temperatureHk1SetPoint, CELSIUS));
530 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_SUPPLY_TEMPERATURE),
531 getScaled(block.temperatureSupply, CELSIUS));
532 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_RETURN_TEMPERATURE),
533 getScaled(block.temperatureReturn, CELSIUS));
534 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_SOURCE_TEMPERATURE),
535 getScaled(block.temperatureSource, CELSIUS));
536 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_WATER_TEMPERATURE),
537 getScaled(block.temperatureWater, CELSIUS));
538 updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_WATER_TEMPERATURE_SETPOINT),
539 getScaled(block.temperatureWaterSetPoint, CELSIUS));
541 resetCommunicationError();
545 * This method is called each time new data has been polled from the modbus
546 * slave The register array is first parsed, then each of the channels are
547 * updated to the new values
549 * @param registers byte array read from the modbus slave
551 protected void handlePolledEnergyData(ModbusRegisterArray registers) {
552 logger.trace("Energy block received, size: {}", registers.size());
554 EnergyBlock block = energyBlockParser.parse(registers);
556 // Energy information group
557 updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_PRODUCTION_HEAT_TODAY),
558 new QuantityType<>(block.productionHeatToday, KILOWATT_HOUR));
559 updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_PRODUCTION_HEAT_TOTAL),
560 getEnergyQuantity(block.productionHeatTotalHigh, block.productionHeatTotalLow));
561 updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_PRODUCTION_WATER_TODAY),
562 new QuantityType<>(block.productionWaterToday, KILOWATT_HOUR));
563 updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_PRODUCTION_WATER_TOTAL),
564 getEnergyQuantity(block.productionWaterTotalHigh, block.productionWaterTotalLow));
565 updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_CONSUMPTION_HEAT_TODAY),
566 new QuantityType<>(block.consumptionHeatToday, KILOWATT_HOUR));
567 updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_CONSUMPTION_HEAT_TOTAL),
568 getEnergyQuantity(block.consumptionHeatTotalHigh, block.consumptionHeatTotalLow));
569 updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_CONSUMPTION_WATER_TODAY),
570 new QuantityType<>(block.consumptionWaterToday, KILOWATT_HOUR));
571 updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_CONSUMPTION_WATER_TOTAL),
572 getEnergyQuantity(block.consumptionWaterTotalHigh, block.consumptionWaterTotalLow));
574 resetCommunicationError();
578 * This method is called each time new data has been polled from the modbus
579 * slave The register array is first parsed, then each of the channels are
580 * updated to the new values
582 * @param registers byte array read from the modbus slave
584 protected void handlePolledSystemStateData(ModbusRegisterArray registers) {
585 logger.trace("System state block received, size: {}", registers.size());
587 SystemStateBlock block = systemstateBlockParser.parse(registers);
588 boolean isHeating = (block.state & 16) != 0;
589 updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_HEATING),
590 isHeating ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
591 updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_HEATING_WATER),
592 (block.state & 32) != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
593 updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_COOLING),
594 (block.state & 256) != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
595 updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_SUMMER),
596 (block.state & 128) != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
597 updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_PUMPING),
598 (block.state & 1) != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
600 resetCommunicationError();
604 * This method is called each time new data has been polled from the modbus
605 * slave The register array is first parsed, then each of the channels are
606 * updated to the new values
608 * @param registers byte array read from the modbus slave
610 protected void handlePolledSystemParameterData(ModbusRegisterArray registers) {
611 logger.trace("System state block received, size: {}", registers.size());
613 SystemParameterBlock block = systemParameterBlockParser.parse(registers);
614 updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_OPERATION_MODE), new DecimalType(block.operationMode));
616 updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_COMFORT_TEMPERATURE_HEATING),
617 getScaled(block.comfortTemperatureHeating, CELSIUS));
618 updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_ECO_TEMPERATURE_HEATING),
619 getScaled(block.ecoTemperatureHeating, CELSIUS));
620 updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_COMFORT_TEMPERATURE_WATER),
621 getScaled(block.comfortTemperatureWater, CELSIUS));
622 updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_ECO_TEMPERATURE_WATER),
623 getScaled(block.ecoTemperatureWater, CELSIUS));
625 resetCommunicationError();
629 * @param bridgeStatusInfo
632 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
633 super.bridgeStatusChanged(bridgeStatusInfo);
635 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
637 } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
643 * Handle errors received during communication
645 protected void handleReadError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
646 // Ignore all incoming data and errors if configuration is not correct
647 if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
650 String msg = failure.getCause().getMessage();
651 String cls = failure.getCause().getClass().getName();
652 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
653 String.format("Error with read: %s: %s", cls, msg));
657 * Handle errors received during communication
659 protected void handleWriteError(AsyncModbusFailure<ModbusWriteRequestBlueprint> failure) {
660 // Ignore all incoming data and errors if configuration is not correct
661 if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
664 String msg = failure.getCause().getMessage();
665 String cls = failure.getCause().getClass().getName();
666 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
667 String.format("Error with write: %s: %s", cls, msg));
671 * Returns true, if we're in a CONFIGURATION_ERROR state
675 protected boolean hasConfigurationError() {
676 ThingStatusInfo statusInfo = getThing().getStatusInfo();
677 return statusInfo.getStatus() == ThingStatus.OFFLINE
678 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
682 * Reset communication status to ONLINE if we're in an OFFLINE state
684 protected void resetCommunicationError() {
685 ThingStatusInfo statusInfo = thing.getStatusInfo();
686 if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
687 && ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
688 updateStatus(ThingStatus.ONLINE);
693 * Returns the channel UID for the specified group and channel id
695 * @param string the channel group
696 * @param string the channel id in that group
697 * @return the globally unique channel uid
699 ChannelUID channelUID(String group, String id) {
700 return new ChannelUID(getThing().getUID(), group, id);