]> git.basschouten.com Git - openhab-addons.git/blob
cccc420ec35fbaf2cf2addab7f1d03bddfb94649
[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.openwebnet.internal.handler;
14
15 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
16
17 import java.util.HashSet;
18 import java.util.Set;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
22 import org.openhab.core.library.types.DecimalType;
23 import org.openhab.core.library.types.OnOffType;
24 import org.openhab.core.library.types.QuantityType;
25 import org.openhab.core.library.types.StringType;
26 import org.openhab.core.library.unit.SIUnits;
27 import org.openhab.core.thing.ChannelUID;
28 import org.openhab.core.thing.Thing;
29 import org.openhab.core.thing.ThingStatus;
30 import org.openhab.core.thing.ThingStatusDetail;
31 import org.openhab.core.thing.ThingTypeUID;
32 import org.openhab.core.types.Command;
33 import org.openhab.core.types.UnDefType;
34 import org.openwebnet4j.communication.OWNException;
35 import org.openwebnet4j.message.BaseOpenMessage;
36 import org.openwebnet4j.message.FrameException;
37 import org.openwebnet4j.message.MalformedFrameException;
38 import org.openwebnet4j.message.Thermoregulation;
39 import org.openwebnet4j.message.Thermoregulation.WhatThermo;
40 import org.openwebnet4j.message.Where;
41 import org.openwebnet4j.message.WhereThermo;
42 import org.openwebnet4j.message.Who;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 /**
47  * The {@link OpenWebNetThermoregulationHandler} is responsible for handling
48  * commands/messages for Thermoregulation
49  * Things. It extends the abstract {@link OpenWebNetThingHandler}.
50  *
51  * @author Massimo Valla - Initial contribution. Added support for 4-zone CU
52  * @author Andrea Conte - Thermoregulation
53  * @author Gilberto Cocchi - Thermoregulation
54  */
55 @NonNullByDefault
56 public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
57
58     private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
59
60     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
61
62     private double currentSetPointTemp = 11.5d; // 11.5 is the default setTemp used in MyHomeUP mobile app
63
64     private Thermoregulation.Function currentFunction = Thermoregulation.Function.GENERIC;
65     private Thermoregulation.OperationMode currentMode = Thermoregulation.OperationMode.MANUAL;
66
67     private boolean isStandAlone = true; // true if zone is not associated to a CU
68
69     private boolean isCentralUnit = false;
70
71     private String programNumber = "1";
72
73     private static Set<String> probesInProtection = new HashSet<String>();
74     private static Set<String> probesInOFF = new HashSet<String>();
75     private static Set<String> probesInManual = new HashSet<String>();
76
77     private static final String CU_REMOTE_CONTROL_ENABLED = "ENABLED";
78     private static final String CU_REMOTE_CONTROL_DISABLED = "DISABLED";
79     private static final String CU_BATTERY_OK = "OK";
80     private static final String CU_BATTERY_KO = "KO";
81     private static final Integer UNDOCUMENTED_WHAT_4001 = 4001;
82     private static final Integer UNDOCUMENTED_WHAT_4002 = 4002;
83
84     public OpenWebNetThermoregulationHandler(Thing thing) {
85         super(thing);
86     }
87
88     @Override
89     public void initialize() {
90         super.initialize();
91
92         ThingTypeUID thingType = thing.getThingTypeUID();
93         isCentralUnit = OpenWebNetBindingConstants.THING_TYPE_BUS_THERMO_CU.equals(thingType);
94
95         if (!isCentralUnit) {
96             Object standAloneConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_STANDALONE);
97             if (standAloneConfig != null) {
98                 isStandAlone = Boolean.parseBoolean(standAloneConfig.toString());
99             }
100             logger.debug("@@@@  THERMO ZONE INITIALIZE isStandAlone={}", isStandAlone);
101         } else {
102
103             // central unit must have WHERE=#0 or WHERE=0 or WHERE=#0#n
104             String w = deviceWhere.value();
105             if (w == null || !("0".equals(w) || "#0".equals(w) || w.startsWith("#0#"))) {
106                 logger.warn("initialize() Invalid WHERE={} for Central Unit.", deviceWhere.value());
107                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
108                         "@text/offline.conf-error-where");
109                 return;
110             }
111             // reset state of signal channels (they will be setted when specific messages
112             // are received)
113             updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL, OnOffType.OFF);
114             updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_OFF, OnOffType.OFF);
115             updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_PROTECTION, OnOffType.OFF);
116             updateState(CHANNEL_CU_SCENARIO_PROGRAM_NUMBER, new DecimalType(programNumber));
117             updateState(CHANNEL_CU_WEEKLY_PROGRAM_NUMBER, new DecimalType(programNumber));
118             updateState(CHANNEL_CU_FAILURE_DISCOVERED, OnOffType.OFF);
119         }
120     }
121
122     @Override
123     protected void handleChannelCommand(ChannelUID channel, Command command) {
124         switch (channel.getId()) {
125             case CHANNEL_TEMP_SETPOINT:
126                 handleSetpoint(command);
127                 break;
128             case CHANNEL_FUNCTION:
129                 handleFunction(command);
130                 break;
131             case CHANNEL_MODE:
132                 handleMode(command);
133                 break;
134             case CHANNEL_FAN_SPEED:
135                 handleSetFanSpeed(command);
136                 break;
137             case CHANNEL_CU_WEEKLY_PROGRAM_NUMBER:
138             case CHANNEL_CU_SCENARIO_PROGRAM_NUMBER:
139                 handleSetProgramNumber(command);
140                 break;
141             default: {
142                 logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
143             }
144         }
145     }
146
147     @Override
148     protected void requestChannelState(ChannelUID channel) {
149         super.requestChannelState(channel);
150         refreshDevice(false);
151     }
152
153     @Override
154     protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
155         return new WhereThermo(wStr);
156     }
157
158     @Override
159     protected String ownIdPrefix() {
160         return Who.THERMOREGULATION.value().toString();
161     }
162
163     private void handleSetFanSpeed(Command command) {
164         if (command instanceof StringType) {
165             Where w = deviceWhere;
166             if (w != null) {
167                 try {
168                     Thermoregulation.FanCoilSpeed speed = Thermoregulation.FanCoilSpeed.valueOf(command.toString());
169                     send(Thermoregulation.requestWriteFanCoilSpeed(w.value(), speed));
170                 } catch (OWNException e) {
171                     logger.warn("handleSetFanSpeed() {}", e.getMessage());
172                 } catch (IllegalArgumentException e) {
173                     logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command,
174                             getThing().getUID());
175                     return;
176                 }
177             }
178         } else {
179             logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
180         }
181     }
182
183     private void handleSetProgramNumber(Command command) {
184         if (command instanceof DecimalType) {
185             if (!isCentralUnit) {
186                 logger.warn("handleSetProgramNumber() This command can be sent only for a Central Unit.");
187                 return;
188             }
189
190             programNumber = command.toString();
191             logger.debug("handleSetProgramNumber() Program number set to {}", programNumber);
192
193             // force OperationMode update if we are already in SCENARIO o WEEKLY mode
194             if (currentMode.isScenario() || currentMode.isWeekly()) {
195                 try {
196                     Thermoregulation.OperationMode new_mode = Thermoregulation.OperationMode
197                             .valueOf(currentMode.mode() + "_" + programNumber);
198                     logger.debug("handleSetProgramNumber() new mode {}", new_mode);
199                     send(Thermoregulation.requestWriteMode(getWhere(""), new_mode, currentFunction,
200                             currentSetPointTemp));
201                 } catch (OWNException e) {
202                     logger.warn("handleSetProgramNumber() {}", e.getMessage());
203                 } catch (IllegalArgumentException e) {
204                     logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command,
205                             getThing().getUID());
206                 }
207             }
208
209         } else {
210             logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command, getThing().getUID());
211         }
212     }
213
214     private void handleSetpoint(Command command) {
215         if (command instanceof QuantityType || command instanceof DecimalType) {
216             Where w = deviceWhere;
217             if (w != null) {
218                 double newTemp = 0;
219                 if (command instanceof QuantityType) {
220                     QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
221                     if (tempCelsius != null) {
222                         newTemp = tempCelsius.doubleValue();
223                     }
224                 } else {
225                     newTemp = ((DecimalType) command).doubleValue();
226                 }
227                 try {
228                     send(Thermoregulation.requestWriteSetpointTemperature(getWhere(w.value()), newTemp,
229                             currentFunction));
230                 } catch (MalformedFrameException | OWNException e) {
231                     logger.warn("handleSetpoint() {}", e.getMessage());
232                 }
233             }
234         } else {
235             logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
236         }
237     }
238
239     private void handleMode(Command command) {
240         if (command instanceof StringType) {
241             Where w = deviceWhere;
242             if (w != null) {
243                 try {
244                     Thermoregulation.OperationMode new_mode = Thermoregulation.OperationMode.OFF;
245
246                     if (isCentralUnit && WhatThermo.isComplex(command.toString())) {
247                         new_mode = Thermoregulation.OperationMode.valueOf(command.toString() + "_" + programNumber);
248
249                         // store current mode
250                         currentMode = new_mode;
251                     } else {
252                         new_mode = Thermoregulation.OperationMode.valueOf(command.toString());
253                     }
254                     send(Thermoregulation.requestWriteMode(getWhere(w.value()), new_mode, currentFunction,
255                             currentSetPointTemp));
256                 } catch (OWNException e) {
257                     logger.warn("handleMode() {}", e.getMessage());
258                 } catch (IllegalArgumentException e) {
259                     logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
260                     return;
261                 }
262             }
263         } else {
264             logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
265         }
266     }
267
268     private String getWhere(String where) {
269         if (isCentralUnit) {
270             return where;
271         } else {
272             return isStandAlone ? where : "#" + where;
273         }
274     }
275
276     private void handleFunction(Command command) {
277         if (command instanceof StringType) {
278             Where w = deviceWhere;
279             if (w != null) {
280                 try {
281                     Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
282                     send(Thermoregulation.requestWriteFunction(w.value(), function));
283                 } catch (OWNException e) {
284                     logger.warn("handleFunction() {}", e.getMessage());
285                 } catch (IllegalArgumentException e) {
286                     logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
287                     return;
288                 }
289             }
290         } else {
291             logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
292         }
293     }
294
295     @Override
296     protected void handleMessage(BaseOpenMessage msg) {
297         super.handleMessage(msg);
298
299         logger.debug("@@@@ Thermo.handleMessage(): {}", msg.toStringVerbose());
300
301         if (isCentralUnit) {
302             if (msg.getWhat() == null) {
303                 return;
304             }
305
306             // there isn't a message used for setting OK for battery status so let's assume
307             // it's OK and then change to KO if according message is received
308             updateCUBatteryStatus(CU_BATTERY_OK);
309
310             // same in case of Failure Discovered
311             updateCUFailureDiscovered(OnOffType.OFF);
312
313             if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_DISABLED) {
314                 updateCURemoteControlStatus(CU_REMOTE_CONTROL_DISABLED);
315             } else if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_ENABLED) {
316                 updateCURemoteControlStatus(CU_REMOTE_CONTROL_ENABLED);
317             } else if (msg.getWhat() == Thermoregulation.WhatThermo.BATTERY_KO) {
318                 updateCUBatteryStatus(CU_BATTERY_KO);
319             } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_OFF) {
320                 updateCUAtLeastOneProbeOff(OnOffType.ON);
321             } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_ANTIFREEZE) {
322                 updateCUAtLeastOneProbeProtection(OnOffType.ON);
323             } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_MANUAL) {
324                 updateCUAtLeastOneProbeManual(OnOffType.ON);
325             } else if (msg.getWhat() == Thermoregulation.WhatThermo.FAILURE_DISCOVERED) {
326                 updateCUFailureDiscovered(OnOffType.ON);
327             } // must intercept all possibile WHATs
328             else if (msg.getWhat() == Thermoregulation.WhatThermo.RELEASE_SENSOR_LOCAL_ADJUST) { // will be implemented
329                                                                                                  // soon
330                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
331             } else if (msg.getWhat().value() == UNDOCUMENTED_WHAT_4001) {
332                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
333             } else if (msg.getWhat().value() == UNDOCUMENTED_WHAT_4002) {
334                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
335             } else {
336                 // check and update values of other channel (mode, function, temp)
337                 updateModeAndFunction((Thermoregulation) msg);
338                 updateSetpoint((Thermoregulation) msg);
339             }
340             return;
341         }
342
343         if (msg.isCommand()) {
344             updateModeAndFunction((Thermoregulation) msg);
345         } else {
346             if (msg.getDim() == null) {
347                 return;
348             }
349             if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
350                     || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
351                 updateTemperature((Thermoregulation) msg);
352             } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
353                     || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
354                 updateSetpoint((Thermoregulation) msg);
355             } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
356                 updateValveStatus((Thermoregulation) msg);
357             } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
358                 updateActuatorStatus((Thermoregulation) msg);
359             } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
360                 updateFanCoilSpeed((Thermoregulation) msg);
361             } else if (msg.getDim() == Thermoregulation.DimThermo.OFFSET) {
362                 updateLocalOffset((Thermoregulation) msg);
363             } else {
364                 logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
365                         getThing().getUID(), msg);
366             }
367         }
368     }
369
370     private void updateModeAndFunction(Thermoregulation tmsg) {
371         if (tmsg.getWhat() == null) {
372             logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
373                     tmsg.getFrameValue());
374             return;
375         }
376         Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
377
378         if (w.getMode() == null) {
379             logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
380             return;
381         }
382
383         if (w.getFunction() == null) {
384             logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
385             return;
386         }
387
388         Thermoregulation.OperationMode operationMode = w.getMode();
389         Thermoregulation.Function function = w.getFunction();
390
391         // keep track of thermostats (zones) status
392         if (!isCentralUnit && (!((WhereThermo) deviceWhere).isProbe())) {
393             if (operationMode == Thermoregulation.OperationMode.OFF) {
394                 probesInManual.remove(tmsg.getWhere().value());
395                 probesInProtection.remove(tmsg.getWhere().value());
396                 if (probesInOFF.add(tmsg.getWhere().value())) {
397                     logger.debug("atLeastOneProbeInOFF: added WHERE ---> {}", tmsg.getWhere());
398                 }
399             } else if (operationMode == Thermoregulation.OperationMode.PROTECTION) {
400                 probesInManual.remove(tmsg.getWhere().value());
401                 probesInOFF.remove(tmsg.getWhere().value());
402                 if (probesInProtection.add(tmsg.getWhere().value())) {
403                     logger.debug("atLeastOneProbeInProtection: added WHERE ---> {}", tmsg.getWhere());
404                 }
405             } else if (operationMode == Thermoregulation.OperationMode.MANUAL) {
406                 probesInProtection.remove(tmsg.getWhere().value());
407                 probesInOFF.remove(tmsg.getWhere().value());
408                 if (probesInManual.add(tmsg.getWhere().value())) {
409                     logger.debug("atLeastOneProbeInManual: added WHERE ---> {}", tmsg.getWhere());
410                 }
411             }
412
413             if (probesInOFF.isEmpty()) {
414                 updateCUAtLeastOneProbeOff(OnOffType.OFF);
415             }
416             if (probesInProtection.isEmpty()) {
417                 updateCUAtLeastOneProbeProtection(OnOffType.OFF);
418             }
419             if (probesInManual.isEmpty()) {
420                 updateCUAtLeastOneProbeManual(OnOffType.OFF);
421             }
422         }
423
424         updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
425
426         // must convert from OperationMode to Mode and set ProgramNumber when necessary
427         updateState(CHANNEL_MODE, new StringType(operationMode.mode()));
428         if (operationMode.isScenario()) {
429             logger.debug("updateModeAndFunction() set SCENARIO program to: {}", operationMode.programNumber());
430             updateState(CHANNEL_CU_SCENARIO_PROGRAM_NUMBER, new DecimalType(operationMode.programNumber()));
431         }
432         if (operationMode.isWeekly()) {
433             logger.debug("updateModeAndFunction() set WEEKLY program to: {}", operationMode.programNumber());
434             updateState(CHANNEL_CU_WEEKLY_PROGRAM_NUMBER, new DecimalType(operationMode.programNumber()));
435         }
436
437         // store current function
438         currentFunction = function;
439
440         // in case of central unit store also current operation mode
441         if (isCentralUnit) {
442             currentMode = operationMode;
443         }
444     }
445
446     private void updateTemperature(Thermoregulation tmsg) {
447         try {
448             double temp = Thermoregulation.parseTemperature(tmsg);
449             updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
450         } catch (FrameException e) {
451             logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
452             updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
453         }
454     }
455
456     private void updateSetpoint(Thermoregulation tmsg) {
457         try {
458             double temp = 11.5d;
459             if (isCentralUnit) {
460                 if (tmsg.getWhat() == null) {
461                     // it should be like *4*WHAT#TTTT*#0##
462                     logger.debug("updateSetpoint() Could not parse function from {} (what is null)",
463                             tmsg.getFrameValue());
464                     return;
465                 }
466
467                 String[] parameters = tmsg.getWhatParams();
468                 if (parameters.length > 0) {
469                     temp = Thermoregulation.decodeTemperature(parameters[0]);
470                     logger.debug("updateSetpoint() parsed temperature from {}: {} ---> {}", tmsg.toStringVerbose(),
471                             parameters[0], temp);
472                 }
473             } else {
474                 temp = Thermoregulation.parseTemperature(tmsg);
475             }
476             updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
477             currentSetPointTemp = temp;
478         } catch (NumberFormatException e) {
479             logger.warn("updateSetpoint() NumberFormatException on frame {}: {}", tmsg, e.getMessage());
480             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
481         } catch (FrameException e) {
482             logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
483             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
484         }
485     }
486
487     private void updateFanCoilSpeed(Thermoregulation tmsg) {
488         try {
489             Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
490             updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
491         } catch (FrameException e) {
492             logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
493             updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
494         }
495     }
496
497     private void updateValveStatus(Thermoregulation tmsg) {
498         try {
499             Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
500                     Thermoregulation.WhatThermo.CONDITIONING);
501             updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
502
503             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
504                     Thermoregulation.WhatThermo.HEATING);
505             updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
506         } catch (FrameException e) {
507             logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
508             updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
509             updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
510         }
511     }
512
513     private void updateActuatorStatus(Thermoregulation tmsg) {
514         try {
515             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
516             updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
517         } catch (FrameException e) {
518             logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
519             updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
520         }
521     }
522
523     private void updateLocalOffset(Thermoregulation tmsg) {
524         try {
525             Thermoregulation.LocalOffset offset = Thermoregulation.parseLocalOffset(tmsg);
526             updateState(CHANNEL_LOCAL_OFFSET, new StringType(offset.toString()));
527             logger.debug("updateLocalOffset() {}: {}", tmsg, offset.toString());
528
529         } catch (FrameException e) {
530             logger.warn("updateLocalOffset() FrameException on frame {}: {}", tmsg, e.getMessage());
531             updateState(CHANNEL_LOCAL_OFFSET, UnDefType.UNDEF);
532         }
533     }
534
535     private void updateCURemoteControlStatus(String status) {
536         updateState(CHANNEL_CU_REMOTE_CONTROL, new StringType(status));
537         logger.debug("updateCURemoteControlStatus(): {}", status);
538     }
539
540     private void updateCUBatteryStatus(String status) {
541         updateState(CHANNEL_CU_BATTERY_STATUS, new StringType(status));
542
543         if (status == CU_BATTERY_KO) { // do not log default value (which is automatically setted)
544             logger.debug("updateCUBatteryStatus(): {}", status);
545         }
546     }
547
548     private void updateCUFailureDiscovered(OnOffType status) {
549         updateState(CHANNEL_CU_FAILURE_DISCOVERED, status);
550
551         if (status == OnOffType.ON) { // do not log default value (which is automatically setted)
552             logger.debug("updateCUFailureDiscovered(): {}", status);
553         }
554     }
555
556     private void updateCUAtLeastOneProbeOff(OnOffType status) {
557         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_OFF, status);
558         logger.debug("updateCUAtLeastOneProbeOff(): {}", status);
559     }
560
561     private void updateCUAtLeastOneProbeProtection(OnOffType status) {
562         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_PROTECTION, status);
563         logger.debug("updateCUAtLeastOneProbeProtection(): {}", status);
564     }
565
566     private void updateCUAtLeastOneProbeManual(OnOffType status) {
567         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL, status);
568         logger.debug("updateCUAtLeastOneProbeManual(): {}", status);
569     }
570
571     private Boolean channelExists(String channelID) {
572         return thing.getChannel(channelID) != null;
573     }
574
575     @Override
576     protected void refreshDevice(boolean refreshAll) {
577         logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
578
579         if (deviceWhere != null) {
580             String w = deviceWhere.value();
581
582             if (isCentralUnit) {
583                 // TODO: 4 zone central -> zone #0 CAN be also a zone with its temp.. with
584                 // 99-zones central no! let's assume it's a 99 zone
585                 try {
586                     send(Thermoregulation.requestStatus(w));
587                 } catch (OWNException e) {
588                     logger.warn("refreshDevice() central unit returned OWNException {}", e.getMessage());
589                 }
590                 return;
591             }
592
593             try {
594                 send(Thermoregulation.requestTemperature(w));
595
596                 if (!((WhereThermo) deviceWhere).isProbe()) {
597                     // for bus_thermo_zone request also other single channels updates
598                     send(Thermoregulation.requestSetPointTemperature(w));
599                     send(Thermoregulation.requestMode(w));
600
601                     // refresh ONLY subscribed channels
602                     if (channelExists(CHANNEL_FAN_SPEED)) {
603                         send(Thermoregulation.requestFanCoilSpeed(w));
604                     }
605
606                     if (channelExists(CHANNEL_CONDITIONING_VALVES) || channelExists(CHANNEL_HEATING_VALVES)) {
607                         send(Thermoregulation.requestValvesStatus(w));
608                     }
609
610                     if (channelExists(CHANNEL_ACTUATORS)) {
611                         send(Thermoregulation.requestActuatorsStatus(w));
612                     }
613
614                     if (channelExists(CHANNEL_LOCAL_OFFSET)) {
615                         send(Thermoregulation.requestLocalOffset(w));
616                     }
617                 }
618             } catch (OWNException e) {
619                 logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());
620             }
621         } else {
622             logger.debug("refreshDevice() where is null");
623         }
624     }
625 }