]> git.basschouten.com Git - openhab-addons.git/blob
c0e60027178beb6cdffe672a909f15d65735bc9f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.Set;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
24 import org.openhab.core.library.types.DecimalType;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.library.types.QuantityType;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.library.unit.SIUnits;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.ThingTypeUID;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.UnDefType;
36 import org.openwebnet4j.communication.OWNException;
37 import org.openwebnet4j.message.BaseOpenMessage;
38 import org.openwebnet4j.message.FrameException;
39 import org.openwebnet4j.message.MalformedFrameException;
40 import org.openwebnet4j.message.Thermoregulation;
41 import org.openwebnet4j.message.Thermoregulation.DimThermo;
42 import org.openwebnet4j.message.Thermoregulation.WhatThermo;
43 import org.openwebnet4j.message.Where;
44 import org.openwebnet4j.message.WhereThermo;
45 import org.openwebnet4j.message.Who;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * The {@link OpenWebNetThermoregulationHandler} is responsible for handling
51  * commands/messages for Thermoregulation Things. It extends the abstract
52  * {@link OpenWebNetThingHandler}.
53  *
54  * @author Massimo Valla - Initial contribution. Added support for 4-zones CU.
55  *         Rafactoring and fixed CU state channels updates.
56  * @author Gilberto Cocchi - Initial contribution.
57  * @author Andrea Conte - Added support for 99-zone CU and CU state channels.
58  */
59 @NonNullByDefault
60 public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
61
62     private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
63
64     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
65
66     private double currentSetPointTemp = 20.0d;
67
68     private Thermoregulation.@Nullable Function currentFunction = null;
69     private Thermoregulation.@Nullable OperationMode currentMode = null;
70     private int currentWeeklyPrgNum = 1;
71     private int currentScenarioPrgNum = 1;
72
73     private boolean isStandAlone = true; // true if zone is not associated to a CU
74     private boolean isCentralUnit = false;
75
76     private boolean cuAtLeastOneProbeOff = false;
77     private boolean cuAtLeastOneProbeProtection = false;
78     private boolean cuAtLeastOneProbeManual = false;
79     private String cuBatteryStatus = CU_BATTERY_OK;
80     private boolean cuFailureDiscovered = false;
81
82     private @Nullable ScheduledFuture<?> cuStateChannelsUpdateSchedule;
83
84     public static final int CU_STATE_CHANNELS_UPDATE_DELAY = 1500; // msec
85
86     private static final String CU_REMOTE_CONTROL_ENABLED = "ENABLED";
87     private static final String CU_REMOTE_CONTROL_DISABLED = "DISABLED";
88     private static final String CU_BATTERY_OK = "OK";
89     private static final String CU_BATTERY_KO = "KO";
90     private static final String MODE_WEEKLY = "WEEKLY";
91     private static final String MODE_AUTO = "AUTO";
92
93     public OpenWebNetThermoregulationHandler(Thing thing) {
94         super(thing);
95     }
96
97     @Override
98     public void initialize() {
99         super.initialize();
100         ThingTypeUID thingType = thing.getThingTypeUID();
101         isCentralUnit = OpenWebNetBindingConstants.THING_TYPE_BUS_THERMO_CU.equals(thingType);
102         if (!isCentralUnit) {
103             if (!((WhereThermo) deviceWhere).isProbe()) {
104                 Object standAloneConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_STANDALONE);
105                 if (standAloneConfig != null) {
106                     isStandAlone = Boolean.parseBoolean(standAloneConfig.toString());
107                 }
108                 logger.debug("@@@@  THERMO ZONE INITIALIZE isStandAlone={}", isStandAlone);
109             }
110         } else {
111             // central unit must have WHERE=#0 or WHERE=0 or WHERE=#0#n
112             String w = deviceWhere.value();
113             if (w == null || !("0".equals(w) || "#0".equals(w) || w.startsWith("#0#"))) {
114                 logger.warn("initialize() Invalid WHERE={} for Central Unit.", deviceWhere.value());
115                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
116                         "@text/offline.conf-error-where");
117                 return;
118             }
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(channel, 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(ChannelUID channel, 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             int programNumber = ((DecimalType) command).intValue();
190             boolean updateOpMode = false;
191
192             if (CHANNEL_CU_WEEKLY_PROGRAM_NUMBER.equals(channel.getId())) {
193                 updateOpMode = currentMode.isWeekly();
194                 currentWeeklyPrgNum = programNumber;
195                 logger.debug("handleSetProgramNumber() currentWeeklyPrgNum changed to: {}", programNumber);
196             } else {
197                 updateOpMode = currentMode.isScenario();
198                 currentScenarioPrgNum = programNumber;
199                 logger.debug("handleSetProgramNumber() currentScenarioPrgNum changed to: {}", programNumber);
200             }
201
202             // force OperationMode update if we are already in SCENARIO or WEEKLY mode
203             if (updateOpMode) {
204                 try {
205                     Thermoregulation.OperationMode newMode = Thermoregulation.OperationMode
206                             .valueOf(currentMode.mode() + "_" + programNumber);
207                     logger.debug("handleSetProgramNumber() new mode {}", newMode);
208                     send(Thermoregulation.requestWriteMode(getWhere(deviceWhere.value()), newMode, currentFunction,
209                             currentSetPointTemp));
210                 } catch (OWNException e) {
211                     logger.warn("handleSetProgramNumber() {}", e.getMessage());
212                 } catch (IllegalArgumentException e) {
213                     logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command,
214                             getThing().getUID());
215                 }
216             } else { // just update channel
217                 updateState(channel, new DecimalType(programNumber));
218             }
219         } else {
220             logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command, getThing().getUID());
221         }
222     }
223
224     private void handleSetpoint(Command command) {
225         if (command instanceof QuantityType || command instanceof DecimalType) {
226             Where w = deviceWhere;
227             if (w != null) {
228                 double newTemp = 0;
229                 if (command instanceof QuantityType) {
230                     QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
231                     if (tempCelsius != null) {
232                         newTemp = tempCelsius.doubleValue();
233                     }
234                 } else {
235                     newTemp = ((DecimalType) command).doubleValue();
236                 }
237                 try {
238                     send(Thermoregulation.requestWriteSetpointTemperature(getWhere(w.value()), newTemp,
239                             currentFunction));
240                 } catch (MalformedFrameException | OWNException e) {
241                     logger.warn("handleSetpoint() {}", e.getMessage());
242                 }
243             }
244         } else {
245             logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
246         }
247     }
248
249     private void handleMode(Command command) {
250         if (command instanceof StringType) {
251             Where w = deviceWhere;
252             if (w != null) {
253                 try {
254                     Thermoregulation.OperationMode newMode = Thermoregulation.OperationMode.OFF;
255
256                     if (isCentralUnit && WhatThermo.isComplex(command.toString())) {
257                         int programNumber = 0;
258                         if (MODE_WEEKLY.equalsIgnoreCase(command.toString())) {
259                             programNumber = currentWeeklyPrgNum;
260                         } else {
261                             programNumber = currentScenarioPrgNum;
262                         }
263                         newMode = Thermoregulation.OperationMode.valueOf(command.toString() + "_" + programNumber);
264                         currentMode = newMode;
265                     } else {
266                         if (MODE_AUTO.equalsIgnoreCase(command.toString())) {
267                             newMode = Thermoregulation.OperationMode.PROGRAM;
268                         } else {
269                             newMode = Thermoregulation.OperationMode.valueOf(command.toString());
270                         }
271                     }
272                     send(Thermoregulation.requestWriteMode(getWhere(w.value()), newMode, currentFunction,
273                             currentSetPointTemp));
274                 } catch (OWNException e) {
275                     logger.warn("handleMode() {}", e.getMessage());
276                 } catch (IllegalArgumentException e) {
277                     logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
278                     return;
279                 }
280             }
281         } else {
282             logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
283         }
284     }
285
286     private String getWhere(String where) {
287         if (isCentralUnit) {
288             if (where.charAt(0) == '#') {
289                 return where;
290             } else { // to support old configurations for CU with where="0"
291                 return "#" + where;
292             }
293         } else {
294             return isStandAlone ? where : "#" + where;
295         }
296     }
297
298     private void handleFunction(Command command) {
299         if (command instanceof StringType) {
300             Where w = deviceWhere;
301             if (w != null) {
302                 try {
303                     Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
304                     send(Thermoregulation.requestWriteFunction(w.value(), function));
305                 } catch (OWNException e) {
306                     logger.warn("handleFunction() {}", e.getMessage());
307                 } catch (IllegalArgumentException e) {
308                     logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
309                     return;
310                 }
311             }
312         } else {
313             logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
314         }
315     }
316
317     @Override
318     protected void handleMessage(BaseOpenMessage msg) {
319         super.handleMessage(msg);
320         logger.debug("@@@@ Thermo.handleMessage(): {}", msg.toStringVerbose());
321         Thermoregulation tmsg = (Thermoregulation) msg;
322         if (isCentralUnit) {
323             WhatThermo tWhat = (WhatThermo) msg.getWhat();
324             if (tWhat == null) {
325                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", tWhat, msg);
326                 return;
327             }
328             if (tWhat.value() > 40) {
329                 // it's a CU mode event, CU state events will follow shortly, so let's reset
330                 // their values
331                 resetCUState();
332             }
333             switch (tWhat) {
334                 case AT_LEAST_ONE_PROBE_ANTIFREEZE:
335                     cuAtLeastOneProbeProtection = true;
336                     break;
337                 case AT_LEAST_ONE_PROBE_MANUAL:
338                     cuAtLeastOneProbeManual = true;
339                     break;
340                 case AT_LEAST_ONE_PROBE_OFF:
341                     cuAtLeastOneProbeOff = true;
342                     break;
343                 case BATTERY_KO:
344                     cuBatteryStatus = CU_BATTERY_KO;
345                     break;
346                 case FAILURE_DISCOVERED:
347                     cuFailureDiscovered = true;
348                     break;
349                 case RELEASE_SENSOR_LOCAL_ADJUST:
350                     logger.debug("handleMessage(): Ignoring unsupported WHAT {}. Frame={}", tWhat, msg);
351                     break;
352                 case REMOTE_CONTROL_DISABLED:
353                     updateCURemoteControlStatus(CU_REMOTE_CONTROL_DISABLED);
354                     break;
355                 case REMOTE_CONTROL_ENABLED:
356                     updateCURemoteControlStatus(CU_REMOTE_CONTROL_ENABLED);
357                     break;
358                 default:
359                     // check and update values of other channels (mode, function, temp)
360                     updateModeAndFunction(tmsg);
361                     updateSetpoint(tmsg);
362                     break;
363             }
364             return;
365         }
366
367         if (tmsg.isCommand()) {
368             updateModeAndFunction(tmsg);
369         } else {
370             DimThermo dim = (DimThermo) tmsg.getDim();
371             switch (dim) {
372                 case TEMP_SETPOINT:
373                 case COMPLETE_PROBE_STATUS:
374                     updateSetpoint(tmsg);
375                     break;
376                 case PROBE_TEMPERATURE:
377                 case TEMPERATURE:
378                     updateTemperature(tmsg);
379                     break;
380                 case ACTUATOR_STATUS:
381                     updateActuatorStatus(tmsg);
382                     break;
383                 case FAN_COIL_SPEED:
384                     updateFanCoilSpeed(tmsg);
385                     break;
386                 case OFFSET:
387                     updateLocalOffset(tmsg);
388                     break;
389                 case VALVES_STATUS:
390                     updateValveStatus(tmsg);
391                     break;
392                 default:
393                     logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", tmsg.getDim(),
394                             getThing().getUID(), tmsg);
395                     break;
396             }
397         }
398     }
399
400     private void updateModeAndFunction(Thermoregulation tmsg) {
401         if (tmsg.getWhat() == null) {
402             logger.warn("updateModeAndFunction() Could not parse Mode or Function from {} (WHAT is null)",
403                     tmsg.getFrameValue());
404             return;
405         }
406         Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
407         if (w.getMode() == null) {
408             logger.warn("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
409             return;
410         }
411         if (w.getFunction() == null) {
412             logger.warn("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
413             return;
414         }
415
416         Thermoregulation.OperationMode operationMode = null;
417         if (w != WhatThermo.HEATING && w != WhatThermo.CONDITIONING) {
418             // *4*1*z## and *4*0*z## do not tell us which mode is the zone now
419             operationMode = w.getMode();
420         }
421         Thermoregulation.Function function = w.getFunction();
422
423         updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
424
425         // must convert from OperationMode to Mode and set ProgramNumber when necessary
426         if (operationMode != null) {
427             String newMode;
428             if (operationMode == Thermoregulation.OperationMode.PROGRAM) { // translate PROGRAM -> AUTO
429                 newMode = MODE_AUTO;
430             } else {
431                 newMode = operationMode.mode();
432             }
433             updateState(CHANNEL_MODE, new StringType(newMode));
434             Integer programN = 0;
435             try {
436                 @Nullable
437                 Integer prNum = operationMode.programNumber();
438                 if (prNum != null) {
439                     programN = prNum;
440                 }
441             } catch (Exception e) {
442                 logger.warn("updateModeAndFunction() Could not parse program number from: {}", tmsg.getFrameValue());
443                 return;
444             }
445             if (operationMode.isScenario()) {
446                 logger.debug("{} - updateModeAndFunction() set SCENARIO program to: {}", getThing().getUID(), programN);
447                 updateState(CHANNEL_CU_SCENARIO_PROGRAM_NUMBER, new DecimalType(programN));
448                 currentScenarioPrgNum = programN;
449             }
450             if (operationMode.isWeekly()) {
451                 logger.debug("{} - updateModeAndFunction() set WEEKLY program to: {}", getThing().getUID(), programN);
452                 updateState(CHANNEL_CU_WEEKLY_PROGRAM_NUMBER, new DecimalType(programN));
453                 currentWeeklyPrgNum = programN;
454             }
455         }
456         // store current function
457         currentFunction = function;
458         // in case of Central Unit store also current operation mode
459         if (isCentralUnit) {
460             currentMode = operationMode;
461         }
462     }
463
464     private void updateTemperature(Thermoregulation tmsg) {
465         try {
466             double temp = Thermoregulation.parseTemperature(tmsg);
467             updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
468         } catch (FrameException e) {
469             logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
470             updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
471         }
472     }
473
474     private void updateSetpoint(Thermoregulation tmsg) {
475         try {
476             double newTemp = -1;
477             if (isCentralUnit) {
478                 if (tmsg.getWhat() == null) {
479                     logger.warn("updateSetpoint() Could not parse function from {} (what is null)",
480                             tmsg.getFrameValue());
481                     return;
482                 }
483                 String[] parameters = tmsg.getWhatParams();
484                 if (parameters.length > 0) {
485                     // it should be like *4*WHAT#TTTT*#0##
486                     newTemp = Thermoregulation.decodeTemperature(parameters[0]);
487                     logger.debug("updateSetpoint() parsed temperature from {}: {} ---> {}", tmsg.toStringVerbose(),
488                             parameters[0], newTemp);
489                 }
490             } else {
491                 newTemp = Thermoregulation.parseTemperature(tmsg);
492             }
493             if (newTemp > 0) {
494                 updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(newTemp, SIUnits.CELSIUS));
495                 currentSetPointTemp = newTemp;
496             }
497         } catch (NumberFormatException e) {
498             logger.warn("updateSetpoint() NumberFormatException on frame {}: {}", tmsg, e.getMessage());
499             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
500         } catch (FrameException e) {
501             logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
502             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
503         }
504     }
505
506     private void updateFanCoilSpeed(Thermoregulation tmsg) {
507         try {
508             Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
509             updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
510         } catch (NumberFormatException e) {
511             logger.warn("updateFanCoilSpeed() NumberFormatException on frame {}: {}", tmsg, e.getMessage());
512             updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
513         } catch (FrameException e) {
514             logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
515             updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
516         }
517     }
518
519     private void updateValveStatus(Thermoregulation tmsg) {
520         try {
521             Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
522                     Thermoregulation.WhatThermo.CONDITIONING);
523             updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
524
525             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
526                     Thermoregulation.WhatThermo.HEATING);
527             updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
528         } catch (FrameException e) {
529             logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
530             updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
531             updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
532         }
533     }
534
535     private void updateActuatorStatus(Thermoregulation tmsg) {
536         try {
537             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
538             updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
539         } catch (FrameException e) {
540             logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
541             updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
542         }
543     }
544
545     private void updateLocalOffset(Thermoregulation tmsg) {
546         try {
547             Thermoregulation.LocalOffset offset = Thermoregulation.parseLocalOffset(tmsg);
548             updateState(CHANNEL_LOCAL_OFFSET, new StringType(offset.toString()));
549             logger.debug("updateLocalOffset() {}: {}", tmsg, offset.toString());
550
551         } catch (FrameException e) {
552             logger.warn("updateLocalOffset() FrameException on frame {}: {}", tmsg, e.getMessage());
553             updateState(CHANNEL_LOCAL_OFFSET, UnDefType.UNDEF);
554         }
555     }
556
557     private void updateCURemoteControlStatus(String status) {
558         updateState(CHANNEL_CU_REMOTE_CONTROL, new StringType(status));
559         logger.debug("updateCURemoteControlStatus(): {}", status);
560     }
561
562     private void resetCUState() {
563         logger.debug("########### resetting CU state");
564         cuAtLeastOneProbeOff = false;
565         cuAtLeastOneProbeProtection = false;
566         cuAtLeastOneProbeManual = false;
567         cuBatteryStatus = CU_BATTERY_OK;
568         cuFailureDiscovered = false;
569
570         cuStateChannelsUpdateSchedule = scheduler.schedule(() -> {
571             updateCUStateChannels();
572         }, CU_STATE_CHANNELS_UPDATE_DELAY, TimeUnit.MILLISECONDS);
573     }
574
575     private void updateCUStateChannels() {
576         logger.debug("@@@@  updating CU state channels");
577         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_OFF, OnOffType.from(cuAtLeastOneProbeOff));
578         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_PROTECTION, OnOffType.from(cuAtLeastOneProbeProtection));
579         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL, OnOffType.from(cuAtLeastOneProbeManual));
580         updateState(CHANNEL_CU_BATTERY_STATUS, new StringType(cuBatteryStatus));
581         updateState(CHANNEL_CU_FAILURE_DISCOVERED, OnOffType.from(cuFailureDiscovered));
582     }
583
584     private Boolean channelExists(String channelID) {
585         return thing.getChannel(channelID) != null;
586     }
587
588     @Override
589     protected void refreshDevice(boolean refreshAll) {
590         logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
591
592         if (deviceWhere != null) {
593             String whereStr = deviceWhere.value();
594
595             if (isCentralUnit) {
596                 try {
597                     send(Thermoregulation.requestStatus(getWhere(whereStr)));
598                 } catch (OWNException e) {
599                     logger.warn("refreshDevice() central unit returned OWNException {}", e.getMessage());
600                 }
601                 return;
602             }
603
604             try {
605                 send(Thermoregulation.requestTemperature(whereStr));
606
607                 if (!((WhereThermo) deviceWhere).isProbe()) {
608                     // for bus_thermo_zone request also other single channels updates
609                     send(Thermoregulation.requestSetPointTemperature(whereStr));
610                     send(Thermoregulation.requestMode(whereStr));
611
612                     // refresh ONLY subscribed channels
613                     if (channelExists(CHANNEL_FAN_SPEED)) {
614                         send(Thermoregulation.requestFanCoilSpeed(whereStr));
615                     }
616                     if (channelExists(CHANNEL_CONDITIONING_VALVES) || channelExists(CHANNEL_HEATING_VALVES)) {
617                         send(Thermoregulation.requestValvesStatus(whereStr));
618                     }
619                     if (channelExists(CHANNEL_ACTUATORS)) {
620                         send(Thermoregulation.requestActuatorsStatus(whereStr));
621                     }
622                     if (channelExists(CHANNEL_LOCAL_OFFSET)) {
623                         send(Thermoregulation.requestLocalOffset(whereStr));
624                     }
625                 }
626             } catch (OWNException e) {
627                 logger.warn("refreshDevice() where='{}' returned OWNException {}", whereStr, e.getMessage());
628             }
629         } else {
630             logger.debug("refreshDevice() where is null");
631         }
632     }
633
634     @Override
635     public void dispose() {
636         ScheduledFuture<?> s = cuStateChannelsUpdateSchedule;
637         if (s != null) {
638             s.cancel(false);
639             logger.debug("dispose() - scheduler stopped.");
640         }
641         super.dispose();
642     }
643 }