]> git.basschouten.com Git - openhab-addons.git/blob
20f2804478080964a9fafba61636f92c74bfcf9b
[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 Modbus.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      * @param modbusManager the modbus manager
181      */
182     public StiebelEltronHandler(Thing thing) {
183         super(thing);
184     }
185
186     /**
187      * @param address address of the value to be written on the modbus
188      * @param shortValue value to be written on the modbus
189      */
190     protected void writeInt16(int address, short shortValue) {
191         StiebelEltronConfiguration myconfig = StiebelEltronHandler.this.config;
192         ModbusCommunicationInterface mycomms = StiebelEltronHandler.this.comms;
193
194         if (myconfig == null || mycomms == null) {
195             throw new IllegalStateException("registerPollTask called without proper configuration");
196         }
197         // big endian byte ordering
198         byte hi = (byte) (shortValue >> 8);
199         byte lo = (byte) shortValue;
200         ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
201
202         ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
203                 false, myconfig.getMaxTries());
204
205         mycomms.submitOneTimeWrite(request, result -> {
206             if (hasConfigurationError()) {
207                 return;
208             }
209             logger.debug("Successful write, matching request {}", request);
210             StiebelEltronHandler.this.updateStatus(ThingStatus.ONLINE);
211         }, failure -> {
212             StiebelEltronHandler.this.handleWriteError(failure);
213         });
214     }
215
216     /**
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)
220      */
221     private short getScaledInt16Value(Command command) throws StiebelEltronException {
222         if (command instanceof QuantityType quantityCommand) {
223             QuantityType<?> c = quantityCommand.toUnit(CELSIUS);
224             if (c != null) {
225                 return (short) (c.doubleValue() * 10);
226             } else {
227                 throw new StiebelEltronException("Unsupported unit");
228             }
229         }
230         if (command instanceof DecimalType c) {
231             return (short) (c.doubleValue() * 10);
232         }
233         throw new StiebelEltronException("Unsupported command type");
234     }
235
236     /**
237      * @param command get the value of this command.
238      * @return short the value of the command as short
239      */
240     private short getInt16Value(Command command) throws StiebelEltronException {
241         if (command instanceof DecimalType c) {
242             return c.shortValue();
243         }
244         throw new StiebelEltronException("Unsupported command type");
245     }
246
247     /**
248      * Handle incoming commands.
249      */
250     @Override
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;
256                 switch (groupId) {
257                     case GROUP_SYSTEM_STATE:
258                         poller = systemStatePoller;
259                         break;
260                     case GROUP_SYSTEM_PARAMETER:
261                         poller = systemParameterPoller;
262                         break;
263                     case GROUP_SYSTEM_INFO:
264                         poller = systemInformationPoller;
265                         break;
266                     case GROUP_ENERGY_INFO:
267                         poller = energyPoller;
268                         break;
269                     default:
270                         poller = null;
271                         break;
272                 }
273                 if (poller != null) {
274                     poller.poll();
275                 }
276             }
277         } else {
278             try {
279                 if (GROUP_SYSTEM_PARAMETER.equals(channelUID.getGroupId())) {
280                     switch (channelUID.getIdWithoutGroup()) {
281                         case CHANNEL_OPERATION_MODE:
282                             writeInt16(1500, getInt16Value(command));
283                             break;
284                         case CHANNEL_COMFORT_TEMPERATURE_HEATING:
285                             writeInt16(1501, getScaledInt16Value(command));
286                             break;
287                         case CHANNEL_ECO_TEMPERATURE_HEATING:
288                             writeInt16(1502, getScaledInt16Value(command));
289                             break;
290                         case CHANNEL_COMFORT_TEMPERATURE_WATER:
291                             writeInt16(1509, getScaledInt16Value(command));
292                             break;
293                         case CHANNEL_ECO_TEMPERATURE_WATER:
294                             writeInt16(1510, getScaledInt16Value(command));
295                             break;
296                     }
297                 }
298             } catch (StiebelEltronException error) {
299                 if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
300                     return;
301                 }
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));
306             }
307         }
308     }
309
310     /**
311      * Initialization: Load the config object of the block Connect to the slave
312      * bridge Start the periodic polling
313      */
314     @Override
315     public void initialize() {
316         config = getConfigAs(StiebelEltronConfiguration.class);
317         logger.debug("Initializing thing with properties: {}", thing.getProperties());
318
319         startUp();
320     }
321
322     /*
323      * This method starts the operation of this handler Connect to the slave bridge
324      * Start the periodic polling1
325      */
326     private void startUp() {
327         if (comms != null) {
328             return;
329         }
330
331         ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
332         if (slaveEndpointThingHandler == null) {
333             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Bridge is offline");
334             return;
335         }
336
337         try {
338             slaveId = slaveEndpointThingHandler.getSlaveId();
339
340             comms = slaveEndpointThingHandler.getCommunicationInterface();
341         } catch (EndpointNotInitializedException e) {
342             // this will be handled below as endpoint remains null
343         }
344
345         if (comms == 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));
350             return;
351         }
352
353         if (config == null) {
354             logger.debug("Invalid comms/config/manager ref for stiebel eltron handler");
355             return;
356         }
357
358         if (systemInformationPoller == null) {
359             AbstractBasePoller poller = new AbstractBasePoller() {
360                 @Override
361                 protected void handlePolledData(ModbusRegisterArray registers) {
362                     handlePolledSystemInformationData(registers);
363                 }
364             };
365             poller.registerPollTask(500, 36, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
366             systemInformationPoller = poller;
367         }
368         if (energyPoller == null) {
369             AbstractBasePoller poller = new AbstractBasePoller() {
370                 @Override
371                 protected void handlePolledData(ModbusRegisterArray registers) {
372                     handlePolledEnergyData(registers);
373                 }
374             };
375             poller.registerPollTask(3500, 16, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
376             energyPoller = poller;
377         }
378         if (systemStatePoller == null) {
379             AbstractBasePoller poller = new AbstractBasePoller() {
380                 @Override
381                 protected void handlePolledData(ModbusRegisterArray registers) {
382                     handlePolledSystemStateData(registers);
383                 }
384             };
385             poller.registerPollTask(2500, 2, ModbusReadFunctionCode.READ_INPUT_REGISTERS);
386             systemStatePoller = poller;
387         }
388         if (systemParameterPoller == null) {
389             AbstractBasePoller poller = new AbstractBasePoller() {
390                 @Override
391                 protected void handlePolledData(ModbusRegisterArray registers) {
392                     handlePolledSystemParameterData(registers);
393                 }
394             };
395             poller.registerPollTask(1500, 11, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
396             systemParameterPoller = poller;
397         }
398         updateStatus(ThingStatus.UNKNOWN);
399     }
400
401     /**
402      * Dispose the binding correctly
403      */
404     @Override
405     public void dispose() {
406         tearDown();
407     }
408
409     /**
410      * Unregister the poll tasks and release the endpoint reference
411      */
412     private void tearDown() {
413         AbstractBasePoller poller = systemInformationPoller;
414         if (poller != null) {
415             logger.debug("Unregistering systemInformationPoller from ModbusManager");
416             poller.unregisterPollTask();
417
418             systemInformationPoller = null;
419         }
420
421         poller = energyPoller;
422         if (poller != null) {
423             logger.debug("Unregistering energyPoller from ModbusManager");
424             poller.unregisterPollTask();
425
426             energyPoller = null;
427         }
428
429         poller = systemStatePoller;
430         if (poller != null) {
431             logger.debug("Unregistering systemStatePoller from ModbusManager");
432             poller.unregisterPollTask();
433
434             systemStatePoller = null;
435         }
436
437         poller = systemParameterPoller;
438         if (poller != null) {
439             logger.debug("Unregistering systemParameterPoller from ModbusManager");
440             poller.unregisterPollTask();
441
442             systemParameterPoller = null;
443         }
444
445         comms = null;
446     }
447
448     /**
449      * Returns the current slave id from the bridge
450      */
451     public int getSlaveId() {
452         return slaveId;
453     }
454
455     /**
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
458      *
459      * @return the endpoint handler or null if the bridge does not exist
460      */
461     private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
462         Bridge bridge = getBridge();
463         if (bridge == null) {
464             logger.debug("Bridge is null");
465             return null;
466         }
467         if (bridge.getStatus() != ThingStatus.ONLINE) {
468             logger.debug("Bridge is not online");
469             return null;
470         }
471
472         ThingHandler handler = bridge.getHandler();
473         if (handler == null) {
474             logger.debug("Bridge handler is null");
475             return null;
476         }
477
478         if (handler instanceof ModbusEndpointThingHandler thingHandler) {
479             return thingHandler;
480         } else {
481             throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
482         }
483     }
484
485     /**
486      * Returns value divided by the 10
487      *
488      * @param value the value to alter
489      * @return the scaled value as a DecimalType
490      */
491     protected State getScaled(Number value, Unit<?> unit) {
492         return QuantityType.valueOf(value.doubleValue() / 10, unit);
493     }
494
495     /**
496      * Returns high value * 1000 + low value
497      *
498      * @param high the high value
499      * @param low the low valze
500      * @return the scaled value as a DecimalType
501      */
502     protected State getEnergyQuantity(int high, int low) {
503         double value = high * 1000 + low;
504         return QuantityType.valueOf(value, KILOWATT_HOUR);
505     }
506
507     /**
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
511      *
512      * @param registers byte array read from the modbus slave
513      */
514     protected void handlePolledSystemInformationData(ModbusRegisterArray registers) {
515         logger.trace("System Information block received, size: {}", registers.size());
516
517         SystemInformationBlock block = systemInformationBlockParser.parse(registers);
518
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));
540
541         resetCommunicationError();
542     }
543
544     /**
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
548      *
549      * @param registers byte array read from the modbus slave
550      */
551     protected void handlePolledEnergyData(ModbusRegisterArray registers) {
552         logger.trace("Energy block received, size: {}", registers.size());
553
554         EnergyBlock block = energyBlockParser.parse(registers);
555
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));
573
574         resetCommunicationError();
575     }
576
577     /**
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
581      *
582      * @param registers byte array read from the modbus slave
583      */
584     protected void handlePolledSystemStateData(ModbusRegisterArray registers) {
585         logger.trace("System state block received, size: {}", registers.size());
586
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);
599
600         resetCommunicationError();
601     }
602
603     /**
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
607      *
608      * @param registers byte array read from the modbus slave
609      */
610     protected void handlePolledSystemParameterData(ModbusRegisterArray registers) {
611         logger.trace("System state block received, size: {}", registers.size());
612
613         SystemParameterBlock block = systemParameterBlockParser.parse(registers);
614         updateState(channelUID(GROUP_SYSTEM_PARAMETER, CHANNEL_OPERATION_MODE), new DecimalType(block.operationMode));
615
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));
624
625         resetCommunicationError();
626     }
627
628     /**
629      * @param bridgeStatusInfo
630      */
631     @Override
632     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
633         super.bridgeStatusChanged(bridgeStatusInfo);
634
635         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
636             startUp();
637         } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
638             tearDown();
639         }
640     }
641
642     /**
643      * Handle errors received during communication
644      */
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) {
648             return;
649         }
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));
654     }
655
656     /**
657      * Handle errors received during communication
658      */
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) {
662             return;
663         }
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));
668     }
669
670     /**
671      * Returns true, if we're in a CONFIGURATION_ERROR state
672      *
673      * @return
674      */
675     protected boolean hasConfigurationError() {
676         ThingStatusInfo statusInfo = getThing().getStatusInfo();
677         return statusInfo.getStatus() == ThingStatus.OFFLINE
678                 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
679     }
680
681     /**
682      * Reset communication status to ONLINE if we're in an OFFLINE state
683      */
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);
689         }
690     }
691
692     /**
693      * Returns the channel UID for the specified group and channel id
694      *
695      * @param string the channel group
696      * @param string the channel id in that group
697      * @return the globally unique channel uid
698      */
699     ChannelUID channelUID(String group, String id) {
700         return new ChannelUID(getThing().getUID(), group, id);
701     }
702 }