]> git.basschouten.com Git - openhab-addons.git/blob
9b79ade7be036a09e5cdf0cd0f9ff506eafa7994
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.modbus.stiebeleltron.internal.handler;
14
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.*;
18
19 import java.util.Optional;
20
21 import javax.measure.Unit;
22
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;
60
61 /**
62  * The {@link StiebelEltronHandler} is responsible for handling commands,
63  * which are sent to one of the channels and for polling the modbus.
64  *
65  * @author Paul Frank - Initial contribution
66  */
67 @NonNullByDefault
68 public class StiebelEltronHandler extends BaseThingHandler {
69
70     public abstract class AbstractBasePoller {
71
72         private final Logger logger = LoggerFactory.getLogger(StiebelEltronHandler.class);
73
74         private volatile @Nullable PollTask pollTask;
75
76         public synchronized void unregisterPollTask() {
77             PollTask task = pollTask;
78             if (task == null) {
79                 return;
80             }
81
82             ModbusCommunicationInterface mycomms = StiebelEltronHandler.this.comms;
83             if (mycomms != null) {
84                 mycomms.unregisterRegularPoll(task);
85             }
86             pollTask = null;
87         }
88
89         /**
90          * Register poll task This is where we set up our regular poller
91          */
92         public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) {
93             logger.debug("Setting up regular polling");
94
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");
99             }
100
101             ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(), readFunctionCode, address,
102                     length, myconfig.getMaxTries());
103
104             long refreshMillis = myconfig.getRefreshMillis();
105
106             pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
107                 result.getRegisters().ifPresent(this::handlePolledData);
108                 if (getThing().getStatus() != ThingStatus.ONLINE) {
109                     updateStatus(ThingStatus.ONLINE);
110                 }
111             }, StiebelEltronHandler.this::handleReadError);
112         }
113
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());
119             }
120         }
121
122         protected abstract void handlePolledData(ModbusRegisterArray registers);
123     }
124
125     /**
126      * Logger instance
127      */
128     private final Logger logger = LoggerFactory.getLogger(StiebelEltronHandler.class);
129
130     /**
131      * Configuration instance
132      */
133     protected @Nullable StiebelEltronConfiguration config = null;
134     /**
135      * Parser used to convert incoming raw messages into system blocks
136      */
137     private final SystemInfromationBlockParser systemInformationBlockParser = new SystemInfromationBlockParser();
138     /**
139      * Parser used to convert incoming raw messages into system state blocks
140      */
141     private final SystemStateBlockParser systemstateBlockParser = new SystemStateBlockParser();
142     /**
143      * Parser used to convert incoming raw messages into system parameter blocks
144      */
145     private final SystemParameterBlockParser systemParameterBlockParser = new SystemParameterBlockParser();
146     /**
147      * Parser used to convert incoming raw messages into model blocks
148      */
149     private final EnergyBlockParser energyBlockParser = new EnergyBlockParser();
150     /**
151      * This is the task used to poll the device
152      */
153     private volatile @Nullable AbstractBasePoller systemInformationPoller = null;
154     /**
155      * This is the task used to poll the device
156      */
157     private volatile @Nullable AbstractBasePoller energyPoller = null;
158     /**
159      * This is the task used to poll the device
160      */
161     private volatile @Nullable AbstractBasePoller systemStatePoller = null;
162     /**
163      * This is the task used to poll the device
164      */
165     private volatile @Nullable AbstractBasePoller systemParameterPoller = null;
166     /**
167      * Communication interface to the slave endpoint we're connecting to
168      */
169     protected volatile @Nullable ModbusCommunicationInterface comms = null;
170
171     /**
172      * This is the slave id, we store this once initialization is complete
173      */
174     private volatile int slaveId;
175
176     /**
177      * Instances of this handler should get a reference to the modbus manager
178      *
179      * @param thing the thing to handle
180      */
181     public StiebelEltronHandler(Thing thing) {
182         super(thing);
183     }
184
185     /**
186      * @param address address of the value to be written on the modbus
187      * @param shortValue value to be written on the modbus
188      */
189     protected void writeInt16(int address, short shortValue) {
190         StiebelEltronConfiguration myconfig = StiebelEltronHandler.this.config;
191         ModbusCommunicationInterface mycomms = StiebelEltronHandler.this.comms;
192
193         if (myconfig == null || mycomms == null) {
194             throw new IllegalStateException("registerPollTask called without proper configuration");
195         }
196         // big endian byte ordering
197         byte hi = (byte) (shortValue >> 8);
198         byte lo = (byte) shortValue;
199         ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
200
201         ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
202                 false, myconfig.getMaxTries());
203
204         mycomms.submitOneTimeWrite(request, result -> {
205             if (hasConfigurationError()) {
206                 return;
207             }
208             logger.debug("Successful write, matching request {}", request);
209             StiebelEltronHandler.this.updateStatus(ThingStatus.ONLINE);
210         }, failure -> {
211             StiebelEltronHandler.this.handleWriteError(failure);
212         });
213     }
214
215     /**
216      * @param command get the value of this command.
217      * @return short the value of the command multiplied by 10 (see datatype 2 in
218      *         the stiebel eltron modbus documentation)
219      */
220     private short getScaledInt16Value(Command command) throws StiebelEltronException {
221         if (command instanceof QuantityType quantityCommand) {
222             QuantityType<?> c = quantityCommand.toUnit(CELSIUS);
223             if (c != null) {
224                 return (short) (c.doubleValue() * 10);
225             } else {
226                 throw new StiebelEltronException("Unsupported unit");
227             }
228         }
229         if (command instanceof DecimalType c) {
230             return (short) (c.doubleValue() * 10);
231         }
232         throw new StiebelEltronException("Unsupported command type");
233     }
234
235     /**
236      * @param command get the value of this command.
237      * @return short the value of the command as short
238      */
239     private short getInt16Value(Command command) throws StiebelEltronException {
240         if (command instanceof DecimalType c) {
241             return c.shortValue();
242         }
243         throw new StiebelEltronException("Unsupported command type");
244     }
245
246     /**
247      * Handle incoming commands.
248      */
249     @Override
250     public void handleCommand(ChannelUID channelUID, Command command) {
251         if (RefreshType.REFRESH == command) {
252             String groupId = channelUID.getGroupId();
253             if (groupId != null) {
254                 AbstractBasePoller poller;
255                 switch (groupId) {
256                     case GROUP_SYSTEM_STATE:
257                         poller = systemStatePoller;
258                         break;
259                     case GROUP_SYSTEM_PARAMETER:
260                         poller = systemParameterPoller;
261                         break;
262                     case GROUP_SYSTEM_INFO:
263                         poller = systemInformationPoller;
264                         break;
265                     case GROUP_ENERGY_INFO:
266                         poller = energyPoller;
267                         break;
268                     default:
269                         poller = null;
270                         break;
271                 }
272                 if (poller != null) {
273                     poller.poll();
274                 }
275             }
276         } else {
277             try {
278                 if (GROUP_SYSTEM_PARAMETER.equals(channelUID.getGroupId())) {
279                     switch (channelUID.getIdWithoutGroup()) {
280                         case CHANNEL_OPERATION_MODE:
281                             writeInt16(1500, getInt16Value(command));
282                             break;
283                         case CHANNEL_COMFORT_TEMPERATURE_HEATING:
284                             writeInt16(1501, getScaledInt16Value(command));
285                             break;
286                         case CHANNEL_ECO_TEMPERATURE_HEATING:
287                             writeInt16(1502, getScaledInt16Value(command));
288                             break;
289                         case CHANNEL_COMFORT_TEMPERATURE_WATER:
290                             writeInt16(1509, getScaledInt16Value(command));
291                             break;
292                         case CHANNEL_ECO_TEMPERATURE_WATER:
293                             writeInt16(1510, getScaledInt16Value(command));
294                             break;
295                     }
296                 }
297             } catch (StiebelEltronException error) {
298                 if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
299                     return;
300                 }
301                 String cls = error.getClass().getName();
302                 String msg = error.getMessage();
303                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
304                         String.format("Error with: %s: %s", cls, msg));
305             }
306         }
307     }
308
309     /**
310      * Initialization: Load the config object of the block Connect to the slave
311      * bridge Start the periodic polling
312      */
313     @Override
314     public void initialize() {
315         config = getConfigAs(StiebelEltronConfiguration.class);
316         logger.debug("Initializing thing with properties: {}", thing.getProperties());
317
318         startUp();
319     }
320
321     /*
322      * This method starts the operation of this handler Connect to the slave bridge
323      * Start the periodic polling1
324      */
325     private void startUp() {
326         if (comms != null) {
327             return;
328         }
329
330         ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
331         if (slaveEndpointThingHandler == null) {
332             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Bridge is offline");
333             return;
334         }
335
336         try {
337             slaveId = slaveEndpointThingHandler.getSlaveId();
338
339             comms = slaveEndpointThingHandler.getCommunicationInterface();
340         } catch (EndpointNotInitializedException e) {
341             // this will be handled below as endpoint remains null
342         }
343
344         if (comms == null) {
345             @SuppressWarnings("null")
346             String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
347             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
348                     String.format("Bridge '%s' not completely initialized", label));
349             return;
350         }
351
352         if (config == null) {
353             logger.debug("Invalid comms/config/manager ref for stiebel eltron handler");
354             return;
355         }
356
357         if (systemInformationPoller == null) {
358             AbstractBasePoller poller = new AbstractBasePoller() {
359                 @Override
360                 protected void handlePolledData(ModbusRegisterArray registers) {
361                     handlePolledSystemInformationData(registers);
362                 }
363             };
364             poller.registerPollTask(500, 36, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
365             systemInformationPoller = poller;
366         }
367         if (energyPoller == null) {
368             AbstractBasePoller poller = new AbstractBasePoller() {
369                 @Override
370                 protected void handlePolledData(ModbusRegisterArray registers) {
371                     handlePolledEnergyData(registers);
372                 }
373             };
374             poller.registerPollTask(3500, 16, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
375             energyPoller = poller;
376         }
377         if (systemStatePoller == null) {
378             AbstractBasePoller poller = new AbstractBasePoller() {
379                 @Override
380                 protected void handlePolledData(ModbusRegisterArray registers) {
381                     handlePolledSystemStateData(registers);
382                 }
383             };
384             poller.registerPollTask(2500, 2, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
385             systemStatePoller = poller;
386         }
387         if (systemParameterPoller == null) {
388             AbstractBasePoller poller = new AbstractBasePoller() {
389                 @Override
390                 protected void handlePolledData(ModbusRegisterArray registers) {
391                     handlePolledSystemParameterData(registers);
392                 }
393             };
394             poller.registerPollTask(1500, 11, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
395             systemParameterPoller = poller;
396         }
397         updateStatus(ThingStatus.UNKNOWN);
398     }
399
400     /**
401      * Dispose the binding correctly
402      */
403     @Override
404     public void dispose() {
405         tearDown();
406     }
407
408     /**
409      * Unregister the poll tasks and release the endpoint reference
410      */
411     private void tearDown() {
412         AbstractBasePoller poller = systemInformationPoller;
413         if (poller != null) {
414             logger.debug("Unregistering systemInformationPoller from ModbusManager");
415             poller.unregisterPollTask();
416
417             systemInformationPoller = null;
418         }
419
420         poller = energyPoller;
421         if (poller != null) {
422             logger.debug("Unregistering energyPoller from ModbusManager");
423             poller.unregisterPollTask();
424
425             energyPoller = null;
426         }
427
428         poller = systemStatePoller;
429         if (poller != null) {
430             logger.debug("Unregistering systemStatePoller from ModbusManager");
431             poller.unregisterPollTask();
432
433             systemStatePoller = null;
434         }
435
436         poller = systemParameterPoller;
437         if (poller != null) {
438             logger.debug("Unregistering systemParameterPoller from ModbusManager");
439             poller.unregisterPollTask();
440
441             systemParameterPoller = null;
442         }
443
444         comms = null;
445     }
446
447     /**
448      * Returns the current slave id from the bridge
449      */
450     public int getSlaveId() {
451         return slaveId;
452     }
453
454     /**
455      * Get the endpoint handler from the bridge this handler is connected to Checks
456      * that we're connected to the right type of bridge
457      *
458      * @return the endpoint handler or null if the bridge does not exist
459      */
460     private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
461         Bridge bridge = getBridge();
462         if (bridge == null) {
463             logger.debug("Bridge is null");
464             return null;
465         }
466         if (bridge.getStatus() != ThingStatus.ONLINE) {
467             logger.debug("Bridge is not online");
468             return null;
469         }
470
471         ThingHandler handler = bridge.getHandler();
472         if (handler == null) {
473             logger.debug("Bridge handler is null");
474             return null;
475         }
476
477         if (handler instanceof ModbusEndpointThingHandler thingHandler) {
478             return thingHandler;
479         } else {
480             throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
481         }
482     }
483
484     /**
485      * Returns value divided by the 10
486      *
487      * @param value the value to alter
488      * @return the scaled value as a DecimalType
489      */
490     protected State getScaled(Number value, Unit<?> unit) {
491         return QuantityType.valueOf(value.doubleValue() / 10, unit);
492     }
493
494     /**
495      * Returns high value * 1000 + low value
496      *
497      * @param high the high value
498      * @param low the low valze
499      * @return the scaled value as a DecimalType
500      */
501     protected State getEnergyQuantity(int high, int low) {
502         double value = high * 1000 + low;
503         return QuantityType.valueOf(value, KILOWATT_HOUR);
504     }
505
506     /**
507      * This method is called each time new data has been polled from the modbus
508      * slave The register array is first parsed, then each of the channels are
509      * updated to the new values
510      *
511      * @param registers byte array read from the modbus slave
512      */
513     protected void handlePolledSystemInformationData(ModbusRegisterArray registers) {
514         logger.trace("System Information block received, size: {}", registers.size());
515
516         SystemInformationBlock block = systemInformationBlockParser.parse(registers);
517
518         // System information group
519         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_FEK_TEMPERATURE), getScaled(block.temperatureFek, CELSIUS));
520         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_FEK_TEMPERATURE_SETPOINT),
521                 getScaled(block.temperatureFekSetPoint, CELSIUS));
522         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_FEK_HUMIDITY), getScaled(block.humidityFek, PERCENT));
523         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_FEK_DEWPOINT), getScaled(block.dewpointFek, CELSIUS));
524         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_OUTDOOR_TEMPERATURE),
525                 getScaled(block.temperatureOutdoor, CELSIUS));
526         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_HK1_TEMPERATURE), getScaled(block.temperatureHk1, CELSIUS));
527         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_HK1_TEMPERATURE_SETPOINT),
528                 getScaled(block.temperatureHk1SetPoint, CELSIUS));
529         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_SUPPLY_TEMPERATURE),
530                 getScaled(block.temperatureSupply, CELSIUS));
531         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_RETURN_TEMPERATURE),
532                 getScaled(block.temperatureReturn, CELSIUS));
533         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_SOURCE_TEMPERATURE),
534                 getScaled(block.temperatureSource, CELSIUS));
535         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_WATER_TEMPERATURE),
536                 getScaled(block.temperatureWater, CELSIUS));
537         updateState(channelUID(GROUP_SYSTEM_INFO, CHANNEL_WATER_TEMPERATURE_SETPOINT),
538                 getScaled(block.temperatureWaterSetPoint, CELSIUS));
539
540         resetCommunicationError();
541     }
542
543     /**
544      * This method is called each time new data has been polled from the modbus
545      * slave The register array is first parsed, then each of the channels are
546      * updated to the new values
547      *
548      * @param registers byte array read from the modbus slave
549      */
550     protected void handlePolledEnergyData(ModbusRegisterArray registers) {
551         logger.trace("Energy block received, size: {}", registers.size());
552
553         EnergyBlock block = energyBlockParser.parse(registers);
554
555         // Energy information group
556         updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_PRODUCTION_HEAT_TODAY),
557                 new QuantityType<>(block.productionHeatToday, KILOWATT_HOUR));
558         updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_PRODUCTION_HEAT_TOTAL),
559                 getEnergyQuantity(block.productionHeatTotalHigh, block.productionHeatTotalLow));
560         updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_PRODUCTION_WATER_TODAY),
561                 new QuantityType<>(block.productionWaterToday, KILOWATT_HOUR));
562         updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_PRODUCTION_WATER_TOTAL),
563                 getEnergyQuantity(block.productionWaterTotalHigh, block.productionWaterTotalLow));
564         updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_CONSUMPTION_HEAT_TODAY),
565                 new QuantityType<>(block.consumptionHeatToday, KILOWATT_HOUR));
566         updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_CONSUMPTION_HEAT_TOTAL),
567                 getEnergyQuantity(block.consumptionHeatTotalHigh, block.consumptionHeatTotalLow));
568         updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_CONSUMPTION_WATER_TODAY),
569                 new QuantityType<>(block.consumptionWaterToday, KILOWATT_HOUR));
570         updateState(channelUID(GROUP_ENERGY_INFO, CHANNEL_CONSUMPTION_WATER_TOTAL),
571                 getEnergyQuantity(block.consumptionWaterTotalHigh, block.consumptionWaterTotalLow));
572
573         resetCommunicationError();
574     }
575
576     /**
577      * This method is called each time new data has been polled from the modbus
578      * slave The register array is first parsed, then each of the channels are
579      * updated to the new values
580      *
581      * @param registers byte array read from the modbus slave
582      */
583     protected void handlePolledSystemStateData(ModbusRegisterArray registers) {
584         logger.trace("System state block received, size: {}", registers.size());
585
586         SystemStateBlock block = systemstateBlockParser.parse(registers);
587         boolean isHeating = (block.state & 16) != 0;
588         updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_HEATING),
589                 isHeating ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
590         updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_HEATING_WATER),
591                 (block.state & 32) != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
592         updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_COOLING),
593                 (block.state & 256) != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
594         updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_SUMMER),
595                 (block.state & 128) != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
596         updateState(channelUID(GROUP_SYSTEM_STATE, CHANNEL_IS_PUMPING),
597                 (block.state & 1) != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
598
599         resetCommunicationError();
600     }
601
602     /**
603      * This method is called each time new data has been polled from the modbus
604      * slave The register array is first parsed, then each of the channels are
605      * updated to the new values
606      *
607      * @param registers byte array read from the modbus slave
608      */
609     protected void handlePolledSystemParameterData(ModbusRegisterArray registers) {
610         logger.trace("System state block received, size: {}", registers.size());
611
612         SystemParameterBlock block = systemParameterBlockParser.parse(registers);
613         updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_OPERATION_MODE), new DecimalType(block.operationMode));
614
615         updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_COMFORT_TEMPERATURE_HEATING),
616                 getScaled(block.comfortTemperatureHeating, CELSIUS));
617         updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_ECO_TEMPERATURE_HEATING),
618                 getScaled(block.ecoTemperatureHeating, CELSIUS));
619         updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_COMFORT_TEMPERATURE_WATER),
620                 getScaled(block.comfortTemperatureWater, CELSIUS));
621         updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_ECO_TEMPERATURE_WATER),
622                 getScaled(block.ecoTemperatureWater, CELSIUS));
623
624         resetCommunicationError();
625     }
626
627     /**
628      * @param bridgeStatusInfo
629      */
630     @Override
631     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
632         super.bridgeStatusChanged(bridgeStatusInfo);
633
634         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
635             startUp();
636         } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
637             tearDown();
638         }
639     }
640
641     /**
642      * Handle errors received during communication
643      */
644     protected void handleReadError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
645         // Ignore all incoming data and errors if configuration is not correct
646         if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
647             return;
648         }
649         String msg = failure.getCause().getMessage();
650         String cls = failure.getCause().getClass().getName();
651         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
652                 String.format("Error with read: %s: %s", cls, msg));
653     }
654
655     /**
656      * Handle errors received during communication
657      */
658     protected void handleWriteError(AsyncModbusFailure<ModbusWriteRequestBlueprint> failure) {
659         // Ignore all incoming data and errors if configuration is not correct
660         if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
661             return;
662         }
663         String msg = failure.getCause().getMessage();
664         String cls = failure.getCause().getClass().getName();
665         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
666                 String.format("Error with write: %s: %s", cls, msg));
667     }
668
669     /**
670      * Returns true, if we're in a CONFIGURATION_ERROR state
671      *
672      * @return
673      */
674     protected boolean hasConfigurationError() {
675         ThingStatusInfo statusInfo = getThing().getStatusInfo();
676         return statusInfo.getStatus() == ThingStatus.OFFLINE
677                 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
678     }
679
680     /**
681      * Reset communication status to ONLINE if we're in an OFFLINE state
682      */
683     protected void resetCommunicationError() {
684         ThingStatusInfo statusInfo = thing.getStatusInfo();
685         if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
686                 && ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
687             updateStatus(ThingStatus.ONLINE);
688         }
689     }
690
691     /**
692      * Returns the channel UID for the specified group and channel id
693      *
694      * @param string the channel group
695      * @param string the channel id in that group
696      * @return the globally unique channel uid
697      */
698     ChannelUID channelUID(String group, String id) {
699         return new ChannelUID(getThing().getUID(), group, id);
700     }
701 }