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