]> git.basschouten.com Git - openhab-addons.git/blob
9eb2af59d76c3a9d6e0db927b12b63224f816fc3
[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.Function;
43 import org.openwebnet4j.message.Thermoregulation.OperationMode;
44 import org.openwebnet4j.message.Thermoregulation.WhatThermo;
45 import org.openwebnet4j.message.Thermoregulation.WhatThermoType;
46 import org.openwebnet4j.message.Where;
47 import org.openwebnet4j.message.WhereThermo;
48 import org.openwebnet4j.message.Who;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * The {@link OpenWebNetThermoregulationHandler} is responsible for handling
54  * commands/messages for Thermoregulation Things. It extends the abstract
55  * {@link OpenWebNetThingHandler}.
56  *
57  * @author Massimo Valla - Initial contribution. Added support for 4-zones CU.
58  *         Rafactoring and fixed CU state channels updates. Completed support
59  *         for HOLIDAY/VACATION modes and refactored mode handling.
60  * @author Gilberto Cocchi - Initial contribution.
61  * @author Andrea Conte - Added support for 99-zone CU and CU state channels.
62  */
63 @NonNullByDefault
64 public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
65
66     private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
67
68     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
69
70     private double currentSetPointTemp = 20.0d;
71
72     private Thermoregulation.@Nullable Function currentFunction = Function.HEATING;
73     private Thermoregulation.@Nullable OperationMode currentMode = OperationMode.PROTECTION;
74     private int currentWeeklyPrgNum = 1;
75     private int currentScenarioPrgNum = 1;
76     private int currentVacationDays = 1;
77
78     private boolean isStandAlone = true; // true if zone is not associated to a CU
79     private boolean isCentralUnit = false;
80
81     private boolean cuAtLeastOneProbeOff = false;
82     private boolean cuAtLeastOneProbeProtection = false;
83     private boolean cuAtLeastOneProbeManual = false;
84     private String cuBatteryStatus = CU_BATTERY_OK;
85     private boolean cuFailureDiscovered = false;
86
87     private @Nullable ScheduledFuture<?> cuStateChannelsUpdateSchedule;
88
89     public static final int CU_STATE_CHANNELS_UPDATE_DELAY = 1500; // msec
90
91     private static final String CU_REMOTE_CONTROL_ENABLED = "ENABLED";
92     private static final String CU_REMOTE_CONTROL_DISABLED = "DISABLED";
93     private static final String CU_BATTERY_OK = "OK";
94     private static final String CU_BATTERY_KO = "KO";
95     private static final String MODE_WEEKLY = "WEEKLY";
96
97     public OpenWebNetThermoregulationHandler(Thing thing) {
98         super(thing);
99     }
100
101     @Override
102     public void initialize() {
103         super.initialize();
104         ThingTypeUID thingType = thing.getThingTypeUID();
105         isCentralUnit = OpenWebNetBindingConstants.THING_TYPE_BUS_THERMO_CU.equals(thingType);
106         if (!isCentralUnit) {
107             if (!((WhereThermo) deviceWhere).isProbe()) {
108                 Object standAloneConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_STANDALONE);
109                 if (standAloneConfig != null) {
110                     isStandAlone = Boolean.parseBoolean(standAloneConfig.toString());
111                 }
112                 logger.debug("@@@@  THERMO ZONE INITIALIZE isStandAlone={}", isStandAlone);
113             }
114         } else {
115             // central unit must have WHERE=#0 or WHERE=0 or WHERE=#0#n
116             String w = deviceWhere.value();
117             if (w == null || !("0".equals(w) || "#0".equals(w) || w.startsWith("#0#"))) {
118                 logger.warn("initialize() Invalid WHERE={} for Central Unit.", deviceWhere.value());
119                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
120                         "@text/offline.conf-error-where");
121                 return;
122             }
123         }
124     }
125
126     @Override
127     protected void handleChannelCommand(ChannelUID channel, Command command) {
128         switch (channel.getId()) {
129             case CHANNEL_TEMP_SETPOINT:
130                 handleSetpoint(command);
131                 break;
132             case CHANNEL_FUNCTION:
133                 handleFunction(command);
134                 break;
135             case CHANNEL_MODE:
136                 handleMode(command);
137                 break;
138             case CHANNEL_FAN_SPEED:
139                 handleSetFanSpeed(command);
140                 break;
141             case CHANNEL_CU_WEEKLY_PROGRAM_NUMBER:
142             case CHANNEL_CU_SCENARIO_PROGRAM_NUMBER:
143                 handleSetProgramNumber(channel, command);
144                 break;
145             case CHANNEL_CU_VACATION_DAYS:
146                 handleVacationDays(channel, command);
147                 break;
148             default: {
149                 logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
150             }
151         }
152     }
153
154     @Override
155     protected void requestChannelState(ChannelUID channel) {
156         super.requestChannelState(channel);
157         refreshDevice(false);
158     }
159
160     @Override
161     protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
162         return new WhereThermo(wStr);
163     }
164
165     @Override
166     protected String ownIdPrefix() {
167         return Who.THERMOREGULATION.value().toString();
168     }
169
170     private void handleSetFanSpeed(Command command) {
171         if (command instanceof StringType) {
172             Where w = deviceWhere;
173             if (w == null) {
174                 logger.warn("handleSetFanSpeed() Where is null for thing {}", getThing().getUID());
175                 return;
176             }
177             try {
178                 Thermoregulation.FanCoilSpeed speed = Thermoregulation.FanCoilSpeed.valueOf(command.toString());
179                 send(Thermoregulation.requestWriteFanCoilSpeed(w.value(), speed));
180             } catch (OWNException e) {
181                 logger.warn("handleSetFanSpeed() {}", e.getMessage());
182             } catch (IllegalArgumentException e) {
183                 logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
184                 return;
185             }
186         } else {
187             logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
188         }
189     }
190
191     private void handleSetProgramNumber(ChannelUID channel, Command command) {
192         if (command instanceof DecimalType) {
193             if (!isCentralUnit) {
194                 logger.warn("handleSetProgramNumber() This command can be sent only for a Central Unit.");
195                 return;
196             }
197             Where w = deviceWhere;
198             if (w == null) {
199                 logger.warn("handleSetProgramNumber() Where is null for thing {}", getThing().getUID());
200                 return;
201             }
202
203             int programNumber = ((DecimalType) command).intValue();
204             boolean updateOpMode = false;
205
206             if (CHANNEL_CU_WEEKLY_PROGRAM_NUMBER.equals(channel.getId())) {
207                 if (programNumber < 1 || programNumber > 3) {
208                     logger.warn("handleSetProgramNumber() Invalid program number {} for thing {}", command,
209                             getThing().getUID());
210                     return;
211                 }
212                 updateOpMode = (currentMode == Thermoregulation.OperationMode.WEEKLY);
213                 currentWeeklyPrgNum = programNumber;
214                 logger.debug("handleSetProgramNumber() currentWeeklyPrgNum changed to: {}", programNumber);
215             } else {
216                 if (programNumber < 1 || programNumber > 16) {
217                     logger.warn("handleSetProgramNumber() Invalid program number {} for thing {}", command,
218                             getThing().getUID());
219                     return;
220                 }
221                 updateOpMode = (currentMode == Thermoregulation.OperationMode.SCENARIO);
222                 currentScenarioPrgNum = programNumber;
223                 logger.debug("handleSetProgramNumber() currentScenarioPrgNum changed to: {}", programNumber);
224             }
225
226             // force OperationMode update if we are already in SCENARIO or WEEKLY mode
227             if (updateOpMode) {
228                 try {
229                     send(Thermoregulation.requestWriteWeeklyScenarioMode(getWhere(w.value()), currentMode,
230                             currentFunction, programNumber));
231                 } catch (OWNException e) {
232                     logger.warn("handleSetProgramNumber() {}", e.getMessage());
233                 }
234             } else { // just update channel
235                 updateState(channel, new DecimalType(programNumber));
236             }
237         } else {
238             logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command, getThing().getUID());
239         }
240     }
241
242     private void handleVacationDays(ChannelUID channel, Command command) {
243         if (command instanceof DecimalType) {
244             Where w = deviceWhere;
245             if (w == null) {
246                 logger.warn("handleVacationDays() Where is null for thing {}", getThing().getUID());
247                 return;
248             }
249             if (!isCentralUnit) {
250                 logger.warn("handleVacationDays() This command can be sent only for a Central Unit.");
251                 return;
252             }
253             int vacationDays = ((DecimalType) command).intValue();
254
255             if (vacationDays < 1 || vacationDays > 255) {
256                 logger.warn("handleVacationDays() vacation days must be between 1 and 255");
257                 return;
258             }
259
260             currentVacationDays = vacationDays;
261             logger.debug("handleVacationDays() currentVacationDays changed to: {}", currentVacationDays);
262
263             // force OperationMode update if we are already in VACATION mode
264             if (currentMode == Thermoregulation.OperationMode.VACATION) {
265                 try {
266                     send(Thermoregulation.requestWriteVacationMode(getWhere(w.value()), currentFunction,
267                             currentVacationDays, currentWeeklyPrgNum));
268                 } catch (OWNException e) {
269                     logger.warn("handleVacationDays() {}", e.getMessage());
270                 } catch (IllegalArgumentException e) {
271                     logger.warn("handleVacationDays() Unsupported command {} for thing {}", command,
272                             getThing().getUID());
273                 }
274             } else { // just update channel
275                 updateState(channel, new DecimalType(currentVacationDays));
276             }
277         } else {
278             logger.warn("handleVacationDays() Unsupported command {} for thing {}", command, getThing().getUID());
279         }
280     }
281
282     private void handleSetpoint(Command command) {
283         if (command instanceof QuantityType || command instanceof DecimalType) {
284             Where w = deviceWhere;
285             if (w == null) {
286                 logger.warn("handleSetpoint() Where is null for thing {}", getThing().getUID());
287                 return;
288             }
289             double newTemp = 0;
290             if (command instanceof QuantityType) {
291                 QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
292                 if (tempCelsius != null) {
293                     newTemp = tempCelsius.doubleValue();
294                 }
295             } else {
296                 newTemp = ((DecimalType) command).doubleValue();
297             }
298             try {
299                 send(Thermoregulation.requestWriteSetpointTemperature(getWhere(w.value()), newTemp, currentFunction));
300             } catch (MalformedFrameException | OWNException e) {
301                 logger.warn("handleSetpoint() {}", e.getMessage());
302             }
303         } else {
304             logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
305         }
306     }
307
308     private void handleMode(Command command) {
309         if (command instanceof StringType) {
310             Where w = deviceWhere;
311             if (w == null) {
312                 logger.warn("handleMode() Where is null for thing {}", getThing().getUID());
313                 return;
314             }
315
316             String whStr = getWhere(w.value());
317             String cmdStr = command.toString().toUpperCase();
318             OperationMode newMode;
319             try {
320                 newMode = OperationMode.valueOf(cmdStr);
321             } catch (IllegalArgumentException e) {
322                 logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
323                 return;
324             }
325             Thermoregulation tMsg = null;
326             if (isCentralUnit) { // CU accepted modes: MANUAL, OFF, PROTECTION, WEEKLY, SCENARIO, HOLIDAY,
327                                  // VACATION
328                 switch (newMode) {
329                     case MANUAL:
330                     case OFF:
331                     case PROTECTION:
332                         tMsg = Thermoregulation.requestWriteMode(whStr, newMode, currentFunction, currentSetPointTemp);
333                         break;
334                     case WEEKLY:
335                     case SCENARIO:
336                         int programNumber = (MODE_WEEKLY.equals(cmdStr) ? currentWeeklyPrgNum : currentScenarioPrgNum);
337                         tMsg = Thermoregulation.requestWriteWeeklyScenarioMode(whStr, newMode, currentFunction,
338                                 programNumber);
339                         break;
340                     case HOLIDAY:
341                         tMsg = Thermoregulation.requestWriteHolidayMode(whStr, currentFunction, currentWeeklyPrgNum);
342                         break;
343                     case VACATION:
344                         tMsg = Thermoregulation.requestWriteVacationMode(whStr, currentFunction, currentVacationDays,
345                                 currentWeeklyPrgNum);
346                         break;
347                     default:
348                         logger.warn("handleMode() Unsupported command {} for CU thing {}", command,
349                                 getThing().getUID());
350                 }
351             } else {// Zone accepted modes: MANUAL, OFF, PROTECTION, AUTO
352                 switch (newMode) {
353                     case MANUAL:
354                         tMsg = Thermoregulation.requestWriteMode(whStr, newMode, currentFunction, currentSetPointTemp);
355                         break;
356                     case OFF:
357                     case PROTECTION:
358                     case AUTO:
359                         tMsg = Thermoregulation.requestWriteMode(whStr, newMode, Function.GENERIC, currentSetPointTemp);
360                         break;
361                     default:
362                         logger.warn("handleMode() Unsupported command {} for ZONE thing {}", command,
363                                 getThing().getUID());
364                 }
365             }
366
367             if (tMsg != null) {
368                 try {
369                     send(tMsg);
370                 } catch (OWNException e) {
371                     logger.warn("handleMode() {}", e.getMessage());
372                 }
373             } else {
374                 logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
375             }
376         }
377     }
378
379     private String getWhere(String where) {
380         if (isCentralUnit) {
381             if (where.charAt(0) == '#') {
382                 return where;
383             } else { // to support old configurations for CU with where="0"
384                 return "#" + where;
385             }
386         } else {
387             return isStandAlone ? where : "#" + where;
388         }
389     }
390
391     private void handleFunction(Command command) {
392         if (command instanceof StringType) {
393             Where w = deviceWhere;
394             if (w == null) {
395                 logger.warn("handleFunction() Where is null for thing {}", getThing().getUID());
396                 return;
397             }
398             try {
399                 Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
400                 send(Thermoregulation.requestWriteFunction(w.value(), function));
401             } catch (OWNException e) {
402                 logger.warn("handleFunction() {}", e.getMessage());
403             } catch (IllegalArgumentException e) {
404                 logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
405                 return;
406             }
407         } else {
408             logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
409         }
410     }
411
412     @Override
413     protected void handleMessage(BaseOpenMessage msg) {
414         super.handleMessage(msg);
415         logger.debug("@@@@ Thermo.handleMessage(): {}", msg.toStringVerbose());
416         Thermoregulation tmsg = (Thermoregulation) msg;
417         if (isCentralUnit) {
418             WhatThermo tWhat = (WhatThermo) msg.getWhat();
419             if (tWhat == null) {
420                 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", tWhat, msg);
421                 return;
422             }
423             if (tWhat.value() > 40) {
424                 // it's a CU mode event, CU state events will follow shortly, so let's reset
425                 // their values
426                 resetCUState();
427             }
428             switch (tWhat.getType()) {
429                 case AT_LEAST_ONE_PROBE_ANTIFREEZE:
430                     cuAtLeastOneProbeProtection = true;
431                     break;
432                 case AT_LEAST_ONE_PROBE_MANUAL:
433                     cuAtLeastOneProbeManual = true;
434                     break;
435                 case AT_LEAST_ONE_PROBE_OFF:
436                     cuAtLeastOneProbeOff = true;
437                     break;
438                 case BATTERY_KO:
439                     cuBatteryStatus = CU_BATTERY_KO;
440                     break;
441                 case FAILURE_DISCOVERED:
442                     cuFailureDiscovered = true;
443                     break;
444                 case RELEASE_SENSOR_LOCAL_ADJUST:
445                     logger.debug("handleMessage(): Ignoring unsupported WHAT {}. Frame={}", tWhat, msg);
446                     break;
447                 case REMOTE_CONTROL_DISABLED:
448                     updateCURemoteControlStatus(CU_REMOTE_CONTROL_DISABLED);
449                     break;
450                 case REMOTE_CONTROL_ENABLED:
451                     updateCURemoteControlStatus(CU_REMOTE_CONTROL_ENABLED);
452                     break;
453                 default:
454                     // check and update values of other channels (mode, function, temp)
455                     updateModeAndFunction(tmsg);
456                     updateSetpoint(tmsg);
457                     break;
458             }
459             return;
460         }
461
462         if (tmsg.isCommand()) {
463             updateModeAndFunction(tmsg);
464         } else {
465             DimThermo dim = (DimThermo) tmsg.getDim();
466             switch (dim) {
467                 case TEMP_SETPOINT:
468                 case COMPLETE_PROBE_STATUS:
469                     updateSetpoint(tmsg);
470                     break;
471                 case PROBE_TEMPERATURE:
472                 case TEMPERATURE:
473                     updateTemperature(tmsg);
474                     break;
475                 case ACTUATOR_STATUS:
476                     updateActuatorStatus(tmsg);
477                     break;
478                 case FAN_COIL_SPEED:
479                     updateFanCoilSpeed(tmsg);
480                     break;
481                 case OFFSET:
482                     updateLocalOffset(tmsg);
483                     break;
484                 case VALVES_STATUS:
485                     updateValveStatus(tmsg);
486                     break;
487                 default:
488                     logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", tmsg.getDim(),
489                             getThing().getUID(), tmsg);
490                     break;
491             }
492         }
493     }
494
495     private void updateModeAndFunction(Thermoregulation tmsg) {
496         if (tmsg.getWhat() == null) {
497             logger.warn("updateModeAndFunction() Could not parse What {} (WHAT is null)", tmsg.getFrameValue());
498             return;
499         }
500         Thermoregulation.WhatThermo what = tmsg.new WhatThermo(tmsg.getWhat().value());
501         if (what.getMode() == null) {
502             logger.warn("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
503             return;
504         }
505         if (what.getFunction() == null) {
506             logger.warn("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
507             return;
508         }
509
510         // update Function if it's not GENERIC
511         Thermoregulation.Function function = what.getFunction();
512         if (function != Function.GENERIC) {
513             updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
514             currentFunction = function;
515         }
516
517         // then update Mode
518         Thermoregulation.OperationMode operationMode = null;
519         if (what.getType() != WhatThermoType.HEATING && what.getType() != WhatThermoType.CONDITIONING) {
520             // *4*1*z## and *4*0*z## do not tell us which mode is the zone now
521             operationMode = what.getMode();
522         }
523
524         // set ProgramNumber/vacationDays channels when necessary
525         if (operationMode != null) {
526             switch (operationMode) {
527                 case VACATION:
528                     updateVacationDays(tmsg);
529                     break;
530                 case WEEKLY:
531                 case SCENARIO:
532                     updateProgramNumber(tmsg);
533                     break;
534                 default:
535                     break;
536             }
537             if (!isCentralUnit && !(operationMode == OperationMode.AUTO || operationMode == OperationMode.OFF
538                     || operationMode == OperationMode.PROTECTION || operationMode == OperationMode.MANUAL)) {
539                 logger.warn("updateModeAndFunction() Unsupported mode for zone {} from message: {}",
540                         getThing().getUID(), tmsg.getFrameValue());
541                 return;
542             } else {
543                 updateState(CHANNEL_MODE, new StringType(operationMode.name()));
544                 currentMode = operationMode;
545             }
546         } else {
547             logger.debug("updateModeAndFunction() Unrecognized mode from message: {}", tmsg.getFrameValue());
548         }
549     }
550
551     private void updateVacationDays(Thermoregulation tmsg) {
552         @Nullable
553         Integer vDays = ((WhatThermo) tmsg.getWhat()).vacationDays();
554         if (vDays != null) {
555             logger.debug("{} - updateVacationDays() set VACATION DAYS to: {}", getThing().getUID(), vDays);
556             updateState(CHANNEL_CU_VACATION_DAYS, new DecimalType(vDays));
557             currentVacationDays = vDays;
558         }
559     }
560
561     private void updateProgramNumber(Thermoregulation tmsg) {
562         @Nullable
563         WhatThermo wt = (WhatThermo) tmsg.getWhat();
564         if (wt == null) {
565             return;
566         }
567         Integer prNum = wt.programNumber();
568         if (prNum != null) {
569             if (wt.getMode() == OperationMode.SCENARIO) {
570                 logger.debug("{} - updateProgramNumber() set SCENARIO program to: {}", getThing().getUID(), prNum);
571                 updateState(CHANNEL_CU_SCENARIO_PROGRAM_NUMBER, new DecimalType(prNum));
572                 currentScenarioPrgNum = prNum;
573             } else if (wt.getMode() == OperationMode.WEEKLY) {
574                 logger.debug("{} - updateProgramNumber() set WEEKLY program to: {}", getThing().getUID(), prNum);
575                 updateState(CHANNEL_CU_WEEKLY_PROGRAM_NUMBER, new DecimalType(prNum));
576                 currentWeeklyPrgNum = prNum;
577             }
578         }
579     }
580
581     private void updateTemperature(Thermoregulation tmsg) {
582         try {
583             double temp = Thermoregulation.parseTemperature(tmsg);
584             updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
585         } catch (FrameException e) {
586             logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
587             updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
588         }
589     }
590
591     private void updateSetpoint(Thermoregulation tmsg) {
592         try {
593             double newTemp = -1;
594             if (isCentralUnit) {
595                 WhatThermo tw = (WhatThermo) tmsg.getWhat();
596                 if (tw == null) {
597                     logger.warn("updateSetpoint() Could not parse what from {} (what is null)", tmsg.getFrameValue());
598                     return;
599                 }
600                 String[] parameters = tmsg.getWhatParams();
601                 if (parameters.length > 0 && tw.getType() == WhatThermoType.MANUAL) {
602                     // it should be like *4*110#TTTT*#0##
603                     newTemp = Thermoregulation.decodeTemperature(parameters[0]);
604                     logger.debug("updateSetpoint() parsed temperature from {}: {} ---> {}", tmsg.toStringVerbose(),
605                             parameters[0], newTemp);
606                 }
607             } else {
608                 newTemp = Thermoregulation.parseTemperature(tmsg);
609             }
610             if (newTemp > 0) {
611                 updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(newTemp, SIUnits.CELSIUS));
612                 currentSetPointTemp = newTemp;
613             }
614         } catch (NumberFormatException e) {
615             logger.warn("updateSetpoint() NumberFormatException on frame {}: {}", tmsg, e.getMessage());
616             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
617         } catch (FrameException e) {
618             logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
619             updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
620         }
621     }
622
623     private void updateFanCoilSpeed(Thermoregulation tmsg) {
624         try {
625             Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
626             updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
627         } catch (NumberFormatException e) {
628             logger.warn("updateFanCoilSpeed() NumberFormatException on frame {}: {}", tmsg, e.getMessage());
629             updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
630         } catch (FrameException e) {
631             logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
632             updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
633         }
634     }
635
636     private void updateValveStatus(Thermoregulation tmsg) {
637         try {
638             Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
639                     Thermoregulation.WhatThermoType.CONDITIONING);
640             updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
641
642             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
643                     Thermoregulation.WhatThermoType.HEATING);
644             updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
645         } catch (FrameException e) {
646             logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
647             updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
648             updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
649         }
650     }
651
652     private void updateActuatorStatus(Thermoregulation tmsg) {
653         try {
654             Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
655             updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
656         } catch (FrameException e) {
657             logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
658             updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
659         }
660     }
661
662     private void updateLocalOffset(Thermoregulation tmsg) {
663         try {
664             Thermoregulation.LocalOffset offset = Thermoregulation.parseLocalOffset(tmsg);
665             updateState(CHANNEL_LOCAL_OFFSET, new StringType(offset.toString()));
666             logger.debug("updateLocalOffset() {}: {}", tmsg, offset.toString());
667
668         } catch (FrameException e) {
669             logger.warn("updateLocalOffset() FrameException on frame {}: {}", tmsg, e.getMessage());
670             updateState(CHANNEL_LOCAL_OFFSET, UnDefType.UNDEF);
671         }
672     }
673
674     private void updateCURemoteControlStatus(String status) {
675         updateState(CHANNEL_CU_REMOTE_CONTROL, new StringType(status));
676         logger.debug("updateCURemoteControlStatus(): {}", status);
677     }
678
679     private void resetCUState() {
680         logger.debug("########### resetting CU state");
681         cuAtLeastOneProbeOff = false;
682         cuAtLeastOneProbeProtection = false;
683         cuAtLeastOneProbeManual = false;
684         cuBatteryStatus = CU_BATTERY_OK;
685         cuFailureDiscovered = false;
686
687         cuStateChannelsUpdateSchedule = scheduler.schedule(() -> {
688             updateCUStateChannels();
689         }, CU_STATE_CHANNELS_UPDATE_DELAY, TimeUnit.MILLISECONDS);
690     }
691
692     private void updateCUStateChannels() {
693         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_OFF, OnOffType.from(cuAtLeastOneProbeOff));
694         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_PROTECTION, OnOffType.from(cuAtLeastOneProbeProtection));
695         updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL, OnOffType.from(cuAtLeastOneProbeManual));
696         updateState(CHANNEL_CU_BATTERY_STATUS, new StringType(cuBatteryStatus));
697         updateState(CHANNEL_CU_FAILURE_DISCOVERED, OnOffType.from(cuFailureDiscovered));
698     }
699
700     private Boolean channelExists(String channelID) {
701         return thing.getChannel(channelID) != null;
702     }
703
704     @Override
705     protected void refreshDevice(boolean refreshAll) {
706         logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
707
708         Where w = deviceWhere;
709         if (w == null) {
710             logger.warn("refreshDevice() Where is null for thing {}", getThing().getUID());
711             return;
712         }
713
714         String whereStr = w.value();
715
716         if (isCentralUnit) {
717             try {
718                 send(Thermoregulation.requestStatus(getWhere(whereStr)));
719             } catch (OWNException e) {
720                 logger.warn("refreshDevice() central unit returned OWNException {}", e.getMessage());
721             }
722             return;
723         }
724
725         try {
726             send(Thermoregulation.requestTemperature(whereStr));
727
728             if (!((WhereThermo) w).isProbe()) {
729                 // for bus_thermo_zone request also other single channels updates
730                 send(Thermoregulation.requestSetPointTemperature(whereStr));
731                 send(Thermoregulation.requestMode(whereStr));
732
733                 // refresh ONLY subscribed channels
734                 if (channelExists(CHANNEL_FAN_SPEED)) {
735                     send(Thermoregulation.requestFanCoilSpeed(whereStr));
736                 }
737                 if (channelExists(CHANNEL_CONDITIONING_VALVES) || channelExists(CHANNEL_HEATING_VALVES)) {
738                     send(Thermoregulation.requestValvesStatus(whereStr));
739                 }
740                 if (channelExists(CHANNEL_ACTUATORS)) {
741                     send(Thermoregulation.requestActuatorsStatus(whereStr));
742                 }
743                 if (channelExists(CHANNEL_LOCAL_OFFSET)) {
744                     send(Thermoregulation.requestLocalOffset(whereStr));
745                 }
746             }
747         } catch (OWNException e) {
748             logger.warn("refreshDevice() where='{}' returned OWNException {}", whereStr, e.getMessage());
749         }
750     }
751
752     @Override
753     public void dispose() {
754         ScheduledFuture<?> s = cuStateChannelsUpdateSchedule;
755         if (s != null) {
756             s.cancel(false);
757             logger.debug("dispose() - scheduler stopped.");
758         }
759         super.dispose();
760     }
761 }