]> git.basschouten.com Git - openhab-addons.git/blob
19d9b0d4dc87295232ab7ab319e8f6aa0c19d963
[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(deviceWhere.value()), 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             if (where.charAt(0) == '#') {
271                 return where;
272             } else { // to support old configurations for CU with where="0"
273                 return "#" + where;
274             }
275         } else {
276             return isStandAlone ? where : "#" + where;
277         }
278     }
279
280     private void handleFunction(Command command) {
281         if (command instanceof StringType) {
282             Where w = deviceWhere;
283             if (w != null) {
284                 try {
285                     Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
286                     send(Thermoregulation.requestWriteFunction(w.value(), function));
287                 } catch (OWNException e) {
288                     logger.warn("handleFunction() {}", e.getMessage());
289                 } catch (IllegalArgumentException e) {
290                     logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
291                     return;
292                 }
293             }
294         } else {
295             logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
296         }
297     }
298
299     @Override
300     protected void handleMessage(BaseOpenMessage msg) {
301         super.handleMessage(msg);
302
303         logger.debug("@@@@ Thermo.handleMessage(): {}", msg.toStringVerbose());
304
305         if (isCentralUnit) {
306             if (msg.getWhat() == null) {
307                 return;
308             }
309
310             // there isn't a message used for setting OK for battery status so let's assume
311             // it's OK and then change to KO if according message is received
312             updateCUBatteryStatus(CU_BATTERY_OK);
313
314             // same in case of Failure Discovered
315             updateCUFailureDiscovered(OnOffType.OFF);
316
317             if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_DISABLED) {
318                 updateCURemoteControlStatus(CU_REMOTE_CONTROL_DISABLED);
319             } else if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_ENABLED) {
320                 updateCURemoteControlStatus(CU_REMOTE_CONTROL_ENABLED);
321             } else if (msg.getWhat() == Thermoregulation.WhatThermo.BATTERY_KO) {
322                 updateCUBatteryStatus(CU_BATTERY_KO);
323             } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_OFF) {
324                 updateCUAtLeastOneProbeOff(OnOffType.ON);
325             } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_ANTIFREEZE) {
326                 updateCUAtLeastOneProbeProtection(OnOffType.ON);
327             } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_MANUAL) {
328                 updateCUAtLeastOneProbeManual(OnOffType.ON);
329             } else if (msg.getWhat() == Thermoregulation.WhatThermo.FAILURE_DISCOVERED) {
330                 updateCUFailureDiscovered(OnOffType.ON);
331             } // must intercept all possibile WHATs
332             else if (msg.getWhat() == Thermoregulation.WhatThermo.RELEASE_SENSOR_LOCAL_ADJUST) { // will be implemented
333                                                                                                  // soon
334                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
335             } else if (msg.getWhat().value() == UNDOCUMENTED_WHAT_4001) {
336                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
337             } else if (msg.getWhat().value() == UNDOCUMENTED_WHAT_4002) {
338                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
339             } else {
340                 // check and update values of other channel (mode, function, temp)
341                 updateModeAndFunction((Thermoregulation) msg);
342                 updateSetpoint((Thermoregulation) msg);
343             }
344             return;
345         }
346
347         if (msg.isCommand()) {
348             updateModeAndFunction((Thermoregulation) msg);
349         } else {
350             if (msg.getDim() == null) {
351                 return;
352             }
353             if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
354                     || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
355                 updateTemperature((Thermoregulation) msg);
356             } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
357                     || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
358                 updateSetpoint((Thermoregulation) msg);
359             } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
360                 updateValveStatus((Thermoregulation) msg);
361             } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
362                 updateActuatorStatus((Thermoregulation) msg);
363             } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
364                 updateFanCoilSpeed((Thermoregulation) msg);
365             } else if (msg.getDim() == Thermoregulation.DimThermo.OFFSET) {
366                 updateLocalOffset((Thermoregulation) msg);
367             } else {
368                 logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
369                         getThing().getUID(), msg);
370             }
371         }
372     }
373
374     private void updateModeAndFunction(Thermoregulation tmsg) {
375         if (tmsg.getWhat() == null) {
376             logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
377                     tmsg.getFrameValue());
378             return;
379         }
380         Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
381
382         if (w.getMode() == null) {
383             logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
384             return;
385         }
386
387         if (w.getFunction() == null) {
388             logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
389             return;
390         }
391
392         Thermoregulation.OperationMode operationMode = w.getMode();
393         Thermoregulation.Function function = w.getFunction();
394
395         // keep track of thermostats (zones) status
396         if (!isCentralUnit && (!((WhereThermo) deviceWhere).isProbe())) {
397             if (operationMode == Thermoregulation.OperationMode.OFF) {
398                 probesInManual.remove(tmsg.getWhere().value());
399                 probesInProtection.remove(tmsg.getWhere().value());
400                 if (probesInOFF.add(tmsg.getWhere().value())) {
401                     logger.debug("atLeastOneProbeInOFF: added WHERE ---> {}", tmsg.getWhere());
402                 }
403             } else if (operationMode == Thermoregulation.OperationMode.PROTECTION) {
404                 probesInManual.remove(tmsg.getWhere().value());
405                 probesInOFF.remove(tmsg.getWhere().value());
406                 if (probesInProtection.add(tmsg.getWhere().value())) {
407                     logger.debug("atLeastOneProbeInProtection: added WHERE ---> {}", tmsg.getWhere());
408                 }
409             } else if (operationMode == Thermoregulation.OperationMode.MANUAL) {
410                 probesInProtection.remove(tmsg.getWhere().value());
411                 probesInOFF.remove(tmsg.getWhere().value());
412                 if (probesInManual.add(tmsg.getWhere().value())) {
413                     logger.debug("atLeastOneProbeInManual: added WHERE ---> {}", tmsg.getWhere());
414                 }
415             }
416
417             if (probesInOFF.isEmpty()) {
418                 updateCUAtLeastOneProbeOff(OnOffType.OFF);
419             }
420             if (probesInProtection.isEmpty()) {
421                 updateCUAtLeastOneProbeProtection(OnOffType.OFF);
422             }
423             if (probesInManual.isEmpty()) {
424                 updateCUAtLeastOneProbeManual(OnOffType.OFF);
425             }
426         }
427
428         updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
429
430         // must convert from OperationMode to Mode and set ProgramNumber when necessary
431         updateState(CHANNEL_MODE, new StringType(operationMode.mode()));
432         if (operationMode.isScenario()) {
433             logger.debug("updateModeAndFunction() set SCENARIO program to: {}", operationMode.programNumber());
434             updateState(CHANNEL_CU_SCENARIO_PROGRAM_NUMBER, new DecimalType(operationMode.programNumber()));
435         }
436         if (operationMode.isWeekly()) {
437             logger.debug("updateModeAndFunction() set WEEKLY program to: {}", operationMode.programNumber());
438             updateState(CHANNEL_CU_WEEKLY_PROGRAM_NUMBER, new DecimalType(operationMode.programNumber()));
439         }
440
441         // store current function
442         currentFunction = function;
443
444         // in case of central unit store also current operation mode
445         if (isCentralUnit) {
446             currentMode = operationMode;
447         }
448     }
449
450     private void updateTemperature(Thermoregulation tmsg) {
451         try {
452             double temp = Thermoregulation.parseTemperature(tmsg);
453             updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
454         } catch (FrameException e) {
455             logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
456             updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
457         }
458     }
459
460     private void updateSetpoint(Thermoregulation tmsg) {
461         try {
462             double temp = 11.5d;
463             if (isCentralUnit) {
464                 if (tmsg.getWhat() == null) {
465                     // it should be like *4*WHAT#TTTT*#0##
466                     logger.debug("updateSetpoint() Could not parse function from {} (what is null)",
467                             tmsg.getFrameValue());
468                     return;
469                 }
470
471                 String[] parameters = tmsg.getWhatParams();
472                 if (parameters.length > 0) {
473                     temp = Thermoregulation.decodeTemperature(parameters[0]);
474                     logger.debug("updateSetpoint() parsed temperature from {}: {} ---> {}", tmsg.toStringVerbose(),
475                             parameters[0], temp);
476                 }
477             } else {
478                 temp = Thermoregulation.parseTemperature(tmsg);
479             }
480             updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
481             currentSetPointTemp = temp;
482         } catch (NumberFormatException e) {
483             logger.warn("updateSetpoint() NumberFormatException on frame {}: {}", tmsg, e.getMessage());
484             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
485         } catch (FrameException e) {
486             logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
487             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
488         }
489     }
490
491     private void updateFanCoilSpeed(Thermoregulation tmsg) {
492         try {
493             Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
494             updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
495         } catch (FrameException e) {
496             logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
497             updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
498         }
499     }
500
501     private void updateValveStatus(Thermoregulation tmsg) {
502         try {
503             Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
504                     Thermoregulation.WhatThermo.CONDITIONING);
505             updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
506
507             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
508                     Thermoregulation.WhatThermo.HEATING);
509             updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
510         } catch (FrameException e) {
511             logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
512             updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
513             updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
514         }
515     }
516
517     private void updateActuatorStatus(Thermoregulation tmsg) {
518         try {
519             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
520             updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
521         } catch (FrameException e) {
522             logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
523             updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
524         }
525     }
526
527     private void updateLocalOffset(Thermoregulation tmsg) {
528         try {
529             Thermoregulation.LocalOffset offset = Thermoregulation.parseLocalOffset(tmsg);
530             updateState(CHANNEL_LOCAL_OFFSET, new StringType(offset.toString()));
531             logger.debug("updateLocalOffset() {}: {}", tmsg, offset.toString());
532
533         } catch (FrameException e) {
534             logger.warn("updateLocalOffset() FrameException on frame {}: {}", tmsg, e.getMessage());
535             updateState(CHANNEL_LOCAL_OFFSET, UnDefType.UNDEF);
536         }
537     }
538
539     private void updateCURemoteControlStatus(String status) {
540         updateState(CHANNEL_CU_REMOTE_CONTROL, new StringType(status));
541         logger.debug("updateCURemoteControlStatus(): {}", status);
542     }
543
544     private void updateCUBatteryStatus(String status) {
545         updateState(CHANNEL_CU_BATTERY_STATUS, new StringType(status));
546
547         if (status == CU_BATTERY_KO) { // do not log default value (which is automatically setted)
548             logger.debug("updateCUBatteryStatus(): {}", status);
549         }
550     }
551
552     private void updateCUFailureDiscovered(OnOffType status) {
553         updateState(CHANNEL_CU_FAILURE_DISCOVERED, status);
554
555         if (status == OnOffType.ON) { // do not log default value (which is automatically setted)
556             logger.debug("updateCUFailureDiscovered(): {}", status);
557         }
558     }
559
560     private void updateCUAtLeastOneProbeOff(OnOffType status) {
561         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_OFF, status);
562         logger.debug("updateCUAtLeastOneProbeOff(): {}", status);
563     }
564
565     private void updateCUAtLeastOneProbeProtection(OnOffType status) {
566         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_PROTECTION, status);
567         logger.debug("updateCUAtLeastOneProbeProtection(): {}", status);
568     }
569
570     private void updateCUAtLeastOneProbeManual(OnOffType status) {
571         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL, status);
572         logger.debug("updateCUAtLeastOneProbeManual(): {}", status);
573     }
574
575     private Boolean channelExists(String channelID) {
576         return thing.getChannel(channelID) != null;
577     }
578
579     @Override
580     protected void refreshDevice(boolean refreshAll) {
581         logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
582
583         if (deviceWhere != null) {
584             String w = deviceWhere.value();
585
586             if (isCentralUnit) {
587                 try {
588                     send(Thermoregulation.requestStatus(getWhere(w)));
589                 } catch (OWNException e) {
590                     logger.warn("refreshDevice() central unit returned OWNException {}", e.getMessage());
591                 }
592                 return;
593             }
594
595             try {
596                 send(Thermoregulation.requestTemperature(w));
597
598                 if (!((WhereThermo) deviceWhere).isProbe()) {
599                     // for bus_thermo_zone request also other single channels updates
600                     send(Thermoregulation.requestSetPointTemperature(w));
601                     send(Thermoregulation.requestMode(w));
602
603                     // refresh ONLY subscribed channels
604                     if (channelExists(CHANNEL_FAN_SPEED)) {
605                         send(Thermoregulation.requestFanCoilSpeed(w));
606                     }
607
608                     if (channelExists(CHANNEL_CONDITIONING_VALVES) || channelExists(CHANNEL_HEATING_VALVES)) {
609                         send(Thermoregulation.requestValvesStatus(w));
610                     }
611
612                     if (channelExists(CHANNEL_ACTUATORS)) {
613                         send(Thermoregulation.requestActuatorsStatus(w));
614                     }
615
616                     if (channelExists(CHANNEL_LOCAL_OFFSET)) {
617                         send(Thermoregulation.requestLocalOffset(w));
618                     }
619                 }
620             } catch (OWNException e) {
621                 logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());
622             }
623         } else {
624             logger.debug("refreshDevice() where is null");
625         }
626     }
627 }