]> git.basschouten.com Git - openhab-addons.git/blob
deaef021191dfba7431c1a103527f3c02fb4ad78
[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.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
91     public OpenWebNetThermoregulationHandler(Thing thing) {
92         super(thing);
93     }
94
95     @Override
96     public void initialize() {
97         super.initialize();
98         ThingTypeUID thingType = thing.getThingTypeUID();
99         isCentralUnit = OpenWebNetBindingConstants.THING_TYPE_BUS_THERMO_CU.equals(thingType);
100         if (!isCentralUnit) {
101             if (!((WhereThermo) deviceWhere).isProbe()) {
102                 Object standAloneConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_STANDALONE);
103                 if (standAloneConfig != null) {
104                     isStandAlone = Boolean.parseBoolean(standAloneConfig.toString());
105                 }
106                 logger.debug("@@@@  THERMO ZONE INITIALIZE isStandAlone={}", isStandAlone);
107             }
108         } else {
109             // central unit must have WHERE=#0 or WHERE=0 or WHERE=#0#n
110             String w = deviceWhere.value();
111             if (w == null || !("0".equals(w) || "#0".equals(w) || w.startsWith("#0#"))) {
112                 logger.warn("initialize() Invalid WHERE={} for Central Unit.", deviceWhere.value());
113                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
114                         "@text/offline.conf-error-where");
115                 return;
116             }
117         }
118     }
119
120     @Override
121     protected void handleChannelCommand(ChannelUID channel, Command command) {
122         switch (channel.getId()) {
123             case CHANNEL_TEMP_SETPOINT:
124                 handleSetpoint(command);
125                 break;
126             case CHANNEL_FUNCTION:
127                 handleFunction(command);
128                 break;
129             case CHANNEL_MODE:
130                 handleMode(command);
131                 break;
132             case CHANNEL_FAN_SPEED:
133                 handleSetFanSpeed(command);
134                 break;
135             case CHANNEL_CU_WEEKLY_PROGRAM_NUMBER:
136             case CHANNEL_CU_SCENARIO_PROGRAM_NUMBER:
137                 handleSetProgramNumber(channel, command);
138                 break;
139             default: {
140                 logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
141             }
142         }
143     }
144
145     @Override
146     protected void requestChannelState(ChannelUID channel) {
147         super.requestChannelState(channel);
148         refreshDevice(false);
149     }
150
151     @Override
152     protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
153         return new WhereThermo(wStr);
154     }
155
156     @Override
157     protected String ownIdPrefix() {
158         return Who.THERMOREGULATION.value().toString();
159     }
160
161     private void handleSetFanSpeed(Command command) {
162         if (command instanceof StringType) {
163             Where w = deviceWhere;
164             if (w != null) {
165                 try {
166                     Thermoregulation.FanCoilSpeed speed = Thermoregulation.FanCoilSpeed.valueOf(command.toString());
167                     send(Thermoregulation.requestWriteFanCoilSpeed(w.value(), speed));
168                 } catch (OWNException e) {
169                     logger.warn("handleSetFanSpeed() {}", e.getMessage());
170                 } catch (IllegalArgumentException e) {
171                     logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command,
172                             getThing().getUID());
173                     return;
174                 }
175             }
176         } else {
177             logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
178         }
179     }
180
181     private void handleSetProgramNumber(ChannelUID channel, Command command) {
182         if (command instanceof DecimalType) {
183             if (!isCentralUnit) {
184                 logger.warn("handleSetProgramNumber() This command can be sent only for a Central Unit.");
185                 return;
186             }
187             int programNumber = ((DecimalType) command).intValue();
188             boolean updateOpMode = false;
189
190             if (CHANNEL_CU_WEEKLY_PROGRAM_NUMBER.equals(channel.getId())) {
191                 updateOpMode = currentMode.isWeekly();
192                 currentWeeklyPrgNum = programNumber;
193                 logger.debug("handleSetProgramNumber() currentWeeklyPrgNum changed to: {}", programNumber);
194             } else {
195                 updateOpMode = currentMode.isScenario();
196                 currentScenarioPrgNum = programNumber;
197                 logger.debug("handleSetProgramNumber() currentScenarioPrgNum changed to: {}", programNumber);
198             }
199
200             // force OperationMode update if we are already in SCENARIO or WEEKLY mode
201             if (updateOpMode) {
202                 try {
203                     Thermoregulation.OperationMode newMode = Thermoregulation.OperationMode
204                             .valueOf(currentMode.mode() + "_" + programNumber);
205                     logger.debug("handleSetProgramNumber() new mode {}", newMode);
206                     send(Thermoregulation.requestWriteMode(getWhere(deviceWhere.value()), newMode, currentFunction,
207                             currentSetPointTemp));
208                 } catch (OWNException e) {
209                     logger.warn("handleSetProgramNumber() {}", e.getMessage());
210                 } catch (IllegalArgumentException e) {
211                     logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command,
212                             getThing().getUID());
213                 }
214             } else { // just update channel
215                 updateState(channel, new DecimalType(programNumber));
216             }
217         } else {
218             logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command, getThing().getUID());
219         }
220     }
221
222     private void handleSetpoint(Command command) {
223         if (command instanceof QuantityType || command instanceof DecimalType) {
224             Where w = deviceWhere;
225             if (w != null) {
226                 double newTemp = 0;
227                 if (command instanceof QuantityType) {
228                     QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
229                     if (tempCelsius != null) {
230                         newTemp = tempCelsius.doubleValue();
231                     }
232                 } else {
233                     newTemp = ((DecimalType) command).doubleValue();
234                 }
235                 try {
236                     send(Thermoregulation.requestWriteSetpointTemperature(getWhere(w.value()), newTemp,
237                             currentFunction));
238                 } catch (MalformedFrameException | OWNException e) {
239                     logger.warn("handleSetpoint() {}", e.getMessage());
240                 }
241             }
242         } else {
243             logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
244         }
245     }
246
247     private void handleMode(Command command) {
248         if (command instanceof StringType) {
249             Where w = deviceWhere;
250             if (w != null) {
251                 try {
252                     Thermoregulation.OperationMode newMode = Thermoregulation.OperationMode.OFF;
253
254                     if (isCentralUnit && WhatThermo.isComplex(command.toString())) {
255                         int programNumber = 0;
256                         if ("WEEKLY".equalsIgnoreCase(command.toString())) {
257                             programNumber = currentWeeklyPrgNum;
258                         } else {
259                             programNumber = currentScenarioPrgNum;
260                         }
261                         newMode = Thermoregulation.OperationMode.valueOf(command.toString() + "_" + programNumber);
262                         currentMode = newMode;
263                     } else {
264                         newMode = Thermoregulation.OperationMode.valueOf(command.toString());
265                     }
266                     send(Thermoregulation.requestWriteMode(getWhere(w.value()), newMode, currentFunction,
267                             currentSetPointTemp));
268                 } catch (OWNException e) {
269                     logger.warn("handleMode() {}", e.getMessage());
270                 } catch (IllegalArgumentException e) {
271                     logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
272                     return;
273                 }
274             }
275         } else {
276             logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
277         }
278     }
279
280     private String getWhere(String where) {
281         if (isCentralUnit) {
282             if (where.charAt(0) == '#') {
283                 return where;
284             } else { // to support old configurations for CU with where="0"
285                 return "#" + where;
286             }
287         } else {
288             return isStandAlone ? where : "#" + where;
289         }
290     }
291
292     private void handleFunction(Command command) {
293         if (command instanceof StringType) {
294             Where w = deviceWhere;
295             if (w != null) {
296                 try {
297                     Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
298                     send(Thermoregulation.requestWriteFunction(w.value(), function));
299                 } catch (OWNException e) {
300                     logger.warn("handleFunction() {}", e.getMessage());
301                 } catch (IllegalArgumentException e) {
302                     logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
303                     return;
304                 }
305             }
306         } else {
307             logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
308         }
309     }
310
311     @Override
312     protected void handleMessage(BaseOpenMessage msg) {
313         super.handleMessage(msg);
314         logger.debug("@@@@ Thermo.handleMessage(): {}", msg.toStringVerbose());
315         Thermoregulation tmsg = (Thermoregulation) msg;
316         if (isCentralUnit) {
317             WhatThermo tWhat = (WhatThermo) msg.getWhat();
318             if (tWhat == null) {
319                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", tWhat, msg);
320                 return;
321             }
322             if (tWhat.value() > 40) {
323                 // it's a CU mode event, CU state events will follow shortly, so let's reset
324                 // their values
325                 resetCUState();
326             }
327             switch (tWhat) {
328                 case AT_LEAST_ONE_PROBE_ANTIFREEZE:
329                     cuAtLeastOneProbeProtection = true;
330                     break;
331                 case AT_LEAST_ONE_PROBE_MANUAL:
332                     cuAtLeastOneProbeManual = true;
333                     break;
334                 case AT_LEAST_ONE_PROBE_OFF:
335                     cuAtLeastOneProbeOff = true;
336                     break;
337                 case BATTERY_KO:
338                     cuBatteryStatus = CU_BATTERY_KO;
339                     break;
340                 case FAILURE_DISCOVERED:
341                     cuFailureDiscovered = true;
342                     break;
343                 case RELEASE_SENSOR_LOCAL_ADJUST:
344                     logger.debug("handleMessage(): Ignoring unsupported WHAT {}. Frame={}", tWhat, msg);
345                     break;
346                 case REMOTE_CONTROL_DISABLED:
347                     updateCURemoteControlStatus(CU_REMOTE_CONTROL_DISABLED);
348                     break;
349                 case REMOTE_CONTROL_ENABLED:
350                     updateCURemoteControlStatus(CU_REMOTE_CONTROL_ENABLED);
351                     break;
352                 default:
353                     // check and update values of other channels (mode, function, temp)
354                     updateModeAndFunction(tmsg);
355                     updateSetpoint(tmsg);
356                     break;
357             }
358             return;
359         }
360
361         if (tmsg.isCommand()) {
362             updateModeAndFunction(tmsg);
363         } else {
364             DimThermo dim = (DimThermo) tmsg.getDim();
365             switch (dim) {
366                 case TEMP_SETPOINT:
367                 case COMPLETE_PROBE_STATUS:
368                     updateSetpoint(tmsg);
369                     break;
370                 case PROBE_TEMPERATURE:
371                 case TEMPERATURE:
372                     updateTemperature(tmsg);
373                     break;
374                 case ACTUATOR_STATUS:
375                     updateActuatorStatus(tmsg);
376                     break;
377                 case FAN_COIL_SPEED:
378                     updateFanCoilSpeed(tmsg);
379                     break;
380                 case OFFSET:
381                     updateLocalOffset(tmsg);
382                     break;
383                 case VALVES_STATUS:
384                     updateValveStatus(tmsg);
385                     break;
386                 default:
387                     logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", tmsg.getDim(),
388                             getThing().getUID(), tmsg);
389                     break;
390             }
391         }
392     }
393
394     private void updateModeAndFunction(Thermoregulation tmsg) {
395         if (tmsg.getWhat() == null) {
396             logger.warn("updateModeAndFunction() Could not parse Mode or Function from {} (WHAT is null)",
397                     tmsg.getFrameValue());
398             return;
399         }
400         Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
401         if (w.getMode() == null) {
402             logger.warn("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
403             return;
404         }
405         if (w.getFunction() == null) {
406             logger.warn("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
407             return;
408         }
409
410         Thermoregulation.OperationMode operationMode = null;
411         if (w != WhatThermo.HEATING && w != WhatThermo.CONDITIONING) {
412             // *4*1*z## and *4*0*z## do not tell us which mode is the zone now
413             operationMode = w.getMode();
414         }
415         Thermoregulation.Function function = w.getFunction();
416
417         updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
418
419         // must convert from OperationMode to Mode and set ProgramNumber when necessary
420         if (operationMode != null) {
421             updateState(CHANNEL_MODE, new StringType(operationMode.mode()));
422             Integer programN = 0;
423             try {
424                 @Nullable
425                 Integer prNum = operationMode.programNumber();
426                 if (prNum != null) {
427                     programN = prNum;
428                 }
429             } catch (Exception e) {
430                 logger.warn("updateModeAndFunction() Could not parse program number from: {}", tmsg.getFrameValue());
431                 return;
432             }
433             if (operationMode.isScenario()) {
434                 logger.debug("{} - updateModeAndFunction() set SCENARIO program to: {}", getThing().getUID(), programN);
435                 updateState(CHANNEL_CU_SCENARIO_PROGRAM_NUMBER, new DecimalType(programN));
436                 currentScenarioPrgNum = programN;
437             }
438             if (operationMode.isWeekly()) {
439                 logger.debug("{} - updateModeAndFunction() set WEEKLY program to: {}", getThing().getUID(), programN);
440                 updateState(CHANNEL_CU_WEEKLY_PROGRAM_NUMBER, new DecimalType(programN));
441                 currentWeeklyPrgNum = programN;
442             }
443         }
444         // store current function
445         currentFunction = function;
446         // in case of Central Unit store also current operation mode
447         if (isCentralUnit) {
448             currentMode = operationMode;
449         }
450     }
451
452     private void updateTemperature(Thermoregulation tmsg) {
453         try {
454             double temp = Thermoregulation.parseTemperature(tmsg);
455             updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
456         } catch (FrameException e) {
457             logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
458             updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
459         }
460     }
461
462     private void updateSetpoint(Thermoregulation tmsg) {
463         try {
464             double newTemp = -1;
465             if (isCentralUnit) {
466                 if (tmsg.getWhat() == null) {
467                     logger.warn("updateSetpoint() Could not parse function from {} (what is null)",
468                             tmsg.getFrameValue());
469                     return;
470                 }
471                 String[] parameters = tmsg.getWhatParams();
472                 if (parameters.length > 0) {
473                     // it should be like *4*WHAT#TTTT*#0##
474                     newTemp = Thermoregulation.decodeTemperature(parameters[0]);
475                     logger.debug("updateSetpoint() parsed temperature from {}: {} ---> {}", tmsg.toStringVerbose(),
476                             parameters[0], newTemp);
477                 }
478             } else {
479                 newTemp = Thermoregulation.parseTemperature(tmsg);
480             }
481             if (newTemp > 0) {
482                 updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(newTemp, SIUnits.CELSIUS));
483                 currentSetPointTemp = newTemp;
484             }
485         } catch (NumberFormatException e) {
486             logger.warn("updateSetpoint() NumberFormatException on frame {}: {}", tmsg, e.getMessage());
487             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
488         } catch (FrameException e) {
489             logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
490             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
491         }
492     }
493
494     private void updateFanCoilSpeed(Thermoregulation tmsg) {
495         try {
496             Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
497             updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
498         } catch (NumberFormatException e) {
499             logger.warn("updateFanCoilSpeed() NumberFormatException on frame {}: {}", tmsg, e.getMessage());
500             updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
501         } catch (FrameException e) {
502             logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
503             updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
504         }
505     }
506
507     private void updateValveStatus(Thermoregulation tmsg) {
508         try {
509             Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
510                     Thermoregulation.WhatThermo.CONDITIONING);
511             updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
512
513             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
514                     Thermoregulation.WhatThermo.HEATING);
515             updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
516         } catch (FrameException e) {
517             logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
518             updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
519             updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
520         }
521     }
522
523     private void updateActuatorStatus(Thermoregulation tmsg) {
524         try {
525             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
526             updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
527         } catch (FrameException e) {
528             logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
529             updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
530         }
531     }
532
533     private void updateLocalOffset(Thermoregulation tmsg) {
534         try {
535             Thermoregulation.LocalOffset offset = Thermoregulation.parseLocalOffset(tmsg);
536             updateState(CHANNEL_LOCAL_OFFSET, new StringType(offset.toString()));
537             logger.debug("updateLocalOffset() {}: {}", tmsg, offset.toString());
538
539         } catch (FrameException e) {
540             logger.warn("updateLocalOffset() FrameException on frame {}: {}", tmsg, e.getMessage());
541             updateState(CHANNEL_LOCAL_OFFSET, UnDefType.UNDEF);
542         }
543     }
544
545     private void updateCURemoteControlStatus(String status) {
546         updateState(CHANNEL_CU_REMOTE_CONTROL, new StringType(status));
547         logger.debug("updateCURemoteControlStatus(): {}", status);
548     }
549
550     private void resetCUState() {
551         logger.debug("########### resetting CU state");
552         cuAtLeastOneProbeOff = false;
553         cuAtLeastOneProbeProtection = false;
554         cuAtLeastOneProbeManual = false;
555         cuBatteryStatus = CU_BATTERY_OK;
556         cuFailureDiscovered = false;
557
558         cuStateChannelsUpdateSchedule = scheduler.schedule(() -> {
559             updateCUStateChannels();
560         }, CU_STATE_CHANNELS_UPDATE_DELAY, TimeUnit.MILLISECONDS);
561     }
562
563     private void updateCUStateChannels() {
564         logger.debug("@@@@  updating CU state channels");
565         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_OFF, OnOffType.from(cuAtLeastOneProbeOff));
566         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_PROTECTION, OnOffType.from(cuAtLeastOneProbeProtection));
567         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL, OnOffType.from(cuAtLeastOneProbeManual));
568         updateState(CHANNEL_CU_BATTERY_STATUS, new StringType(cuBatteryStatus));
569         updateState(CHANNEL_CU_FAILURE_DISCOVERED, OnOffType.from(cuFailureDiscovered));
570     }
571
572     private Boolean channelExists(String channelID) {
573         return thing.getChannel(channelID) != null;
574     }
575
576     @Override
577     protected void refreshDevice(boolean refreshAll) {
578         logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
579
580         if (deviceWhere != null) {
581             String whereStr = deviceWhere.value();
582
583             if (isCentralUnit) {
584                 try {
585                     send(Thermoregulation.requestStatus(getWhere(whereStr)));
586                 } catch (OWNException e) {
587                     logger.warn("refreshDevice() central unit returned OWNException {}", e.getMessage());
588                 }
589                 return;
590             }
591
592             try {
593                 send(Thermoregulation.requestTemperature(whereStr));
594
595                 if (!((WhereThermo) deviceWhere).isProbe()) {
596                     // for bus_thermo_zone request also other single channels updates
597                     send(Thermoregulation.requestSetPointTemperature(whereStr));
598                     send(Thermoregulation.requestMode(whereStr));
599
600                     // refresh ONLY subscribed channels
601                     if (channelExists(CHANNEL_FAN_SPEED)) {
602                         send(Thermoregulation.requestFanCoilSpeed(whereStr));
603                     }
604                     if (channelExists(CHANNEL_CONDITIONING_VALVES) || channelExists(CHANNEL_HEATING_VALVES)) {
605                         send(Thermoregulation.requestValvesStatus(whereStr));
606                     }
607                     if (channelExists(CHANNEL_ACTUATORS)) {
608                         send(Thermoregulation.requestActuatorsStatus(whereStr));
609                     }
610                     if (channelExists(CHANNEL_LOCAL_OFFSET)) {
611                         send(Thermoregulation.requestLocalOffset(whereStr));
612                     }
613                 }
614             } catch (OWNException e) {
615                 logger.warn("refreshDevice() where='{}' returned OWNException {}", whereStr, e.getMessage());
616             }
617         } else {
618             logger.debug("refreshDevice() where is null");
619         }
620     }
621
622     @Override
623     public void dispose() {
624         ScheduledFuture<?> s = cuStateChannelsUpdateSchedule;
625         if (s != null) {
626             s.cancel(false);
627             logger.debug("dispose() - scheduler stopped.");
628         }
629         super.dispose();
630     }
631 }