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