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