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