2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.openwebnet.internal.handler;
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;
33 import java.util.HashSet;
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;
63 * The {@link OpenWebNetThermoregulationHandler} is responsible for handling commands/messages for Thermoregulation
64 * Things. It extends the abstract {@link OpenWebNetThingHandler}.
66 * @author Massimo Valla - Initial contribution
67 * @author Andrea Conte - Thermoregulation
68 * @author Gilberto Cocchi - Thermoregulation
71 public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
73 private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
75 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
77 private double currentSetPointTemp = 11.5d; // 11.5 is the default setTemp used in MyHomeUP mobile app
79 private Thermoregulation.Function currentFunction = Thermoregulation.Function.GENERIC;
81 private boolean isStandAlone = false;
83 private boolean isCentralUnit = false;
85 private String programNumber = "";
87 private static Set<String> probesInProtection = new HashSet<String>();
88 private static Set<String> probesInOFF = new HashSet<String>();
89 private static Set<String> probesInManual = new HashSet<String>();
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";
96 public OpenWebNetThermoregulationHandler(Thing thing) {
101 public void initialize() {
104 ThingTypeUID thingType = thing.getThingTypeUID();
105 isCentralUnit = OpenWebNetBindingConstants.THING_TYPE_BUS_THERMO_CU.equals(thingType);
107 if (!isCentralUnit) {
108 Object standAloneConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_STANDALONE);
109 if (standAloneConfig != null) {
110 // null in case of thermo_sensor
111 isStandAlone = Boolean.parseBoolean(standAloneConfig.toString());
114 // central unit must have WHERE=0
115 if (!deviceWhere.value().equals("0")) {
116 logger.warn("initialize() Invalid WHERE={} for Central Unit.", deviceWhere.value());
118 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
119 "@text/offline.conf-error-where");
125 protected void handleChannelCommand(ChannelUID channel, Command command) {
126 switch (channel.getId()) {
127 case CHANNEL_TEMP_SETPOINT:
128 handleSetpoint(command);
130 case CHANNEL_FUNCTION:
131 handleFunction(command);
136 case CHANNEL_FAN_SPEED:
137 handleSetFanSpeed(command);
139 case CHANNEL_CU_WEEKLY_PROGRAM_NUMBER:
140 case CHANNEL_CU_SCENARIO_PROGRAM_NUMBER:
141 handleSetProgramNumber(command);
144 logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
150 protected void requestChannelState(ChannelUID channel) {
151 super.requestChannelState(channel);
152 refreshDevice(false);
156 protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
157 return new WhereThermo(wStr);
161 protected String ownIdPrefix() {
162 return Who.THERMOREGULATION.value().toString();
165 private void handleSetFanSpeed(Command command) {
166 if (command instanceof StringType) {
167 Where w = deviceWhere;
170 Thermoregulation.FanCoilSpeed speed = Thermoregulation.FanCoilSpeed.valueOf(command.toString());
171 send(Thermoregulation.requestWriteFanCoilSpeed(w.value(), speed));
172 } catch (OWNException e) {
173 logger.warn("handleSetFanSpeed() {}", e.getMessage());
174 } catch (IllegalArgumentException e) {
175 logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command,
176 getThing().getUID());
181 logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
185 private void handleSetProgramNumber(Command command) {
186 if (command instanceof DecimalType) {
187 if (!isCentralUnit) {
188 logger.warn("handleSetProgramNumber() This command can be sent only for a Central Unit.");
192 programNumber = command.toString();
193 logger.debug("handleSetProgramNumber() Program number set to {}", programNumber);
196 logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command, getThing().getUID());
200 private void handleSetpoint(Command command) {
201 if (command instanceof QuantityType || command instanceof DecimalType) {
202 Where w = deviceWhere;
205 if (command instanceof QuantityType) {
206 QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
207 if (tempCelsius != null) {
208 newTemp = tempCelsius.doubleValue();
211 newTemp = ((DecimalType) command).doubleValue();
214 send(Thermoregulation.requestWriteSetpointTemperature(getWhere(w.value()), newTemp,
216 } catch (MalformedFrameException | OWNException e) {
217 logger.warn("handleSetpoint() {}", e.getMessage());
221 logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
225 private void handleMode(Command command) {
226 if (command instanceof StringType) {
227 Where w = deviceWhere;
230 Thermoregulation.OperationMode new_mode = Thermoregulation.OperationMode.OFF;
232 if (isCentralUnit && WhatThermo.isComplex(command.toString()))
233 new_mode = Thermoregulation.OperationMode.valueOf(command.toString() + "_" + programNumber);
235 new_mode = Thermoregulation.OperationMode.valueOf(command.toString());
237 send(Thermoregulation.requestWriteMode(getWhere(w.value()), new_mode, currentFunction,
238 currentSetPointTemp));
239 } catch (OWNException e) {
240 logger.warn("handleMode() {}", e.getMessage());
241 } catch (IllegalArgumentException e) {
242 logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
247 logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
251 private String getWhere(String where) {
255 return isStandAlone ? where : "#" + where;
259 private void handleFunction(Command command) {
260 if (command instanceof StringType) {
261 Where w = deviceWhere;
264 Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
265 send(Thermoregulation.requestWriteFunction(w.value(), function));
266 } catch (OWNException e) {
267 logger.warn("handleFunction() {}", e.getMessage());
268 } catch (IllegalArgumentException e) {
269 logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
274 logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
279 protected void handleMessage(BaseOpenMessage msg) {
280 super.handleMessage(msg);
283 // there isn't a message used for setting OK for battery status so let's assume
284 // it's OK and then change to KO if according message is received
285 updateCUBatteryStatus(CU_BATTERY_OK);
287 // same in case of Failure Discovered
288 updateCUFailureDiscovered(OnOffType.OFF);
290 if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_DISABLED) {
291 updateCURemoteControlStatus(CU_REMOTE_CONTROL_DISABLED);
292 } else if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_ENABLED) {
293 updateCURemoteControlStatus(CU_REMOTE_CONTROL_ENABLED);
294 } else if (msg.getWhat() == Thermoregulation.WhatThermo.BATTERY_KO) {
295 updateCUBatteryStatus(CU_BATTERY_KO);
296 } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_OFF) {
297 updateCUAtLeastOneProbeOff(OnOffType.ON);
298 } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_ANTIFREEZE) {
299 updateCUAtLeastOneProbeProtection(OnOffType.ON);
300 } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_MANUAL) {
301 updateCUAtLeastOneProbeManual(OnOffType.ON);
302 } else if (msg.getWhat() == Thermoregulation.WhatThermo.FAILURE_DISCOVERED) {
303 updateCUFailureDiscovered(OnOffType.ON);
304 } // must intercept all possibile WHATs (will be implemented soon)
305 else if (msg.getWhat() == Thermoregulation.WhatThermo.RELEASE_SENSOR_LOCAL_ADJUST) {
306 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
308 // check and eventually parse mode and function
309 updateModeAndFunction((Thermoregulation) msg);
314 if (msg.isCommand()) {
315 updateModeAndFunction((Thermoregulation) msg);
317 if (msg.getDim() == null) {
320 if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
321 || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
322 updateTemperature((Thermoregulation) msg);
323 } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
324 || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
325 updateSetpoint((Thermoregulation) msg);
326 } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
327 updateValveStatus((Thermoregulation) msg);
328 } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
329 updateActuatorStatus((Thermoregulation) msg);
330 } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
331 updateFanCoilSpeed((Thermoregulation) msg);
332 } else if (msg.getDim() == Thermoregulation.DimThermo.OFFSET) {
333 updateLocalOffset((Thermoregulation) msg);
335 logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
336 getThing().getUID(), msg);
341 private void updateModeAndFunction(Thermoregulation tmsg) {
342 if (tmsg.getWhat() == null) {
343 logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
344 tmsg.getFrameValue());
347 Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
349 if (w.getMode() == null) {
350 logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
353 if (w.getFunction() == null) {
354 logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
358 Thermoregulation.OperationMode mode = w.getMode();
359 Thermoregulation.Function function = w.getFunction();
361 // keep track of thermostats (zones) status
362 if (!isCentralUnit && (!((WhereThermo) deviceWhere).isProbe())) {
363 if (mode == Thermoregulation.OperationMode.OFF) {
364 probesInManual.remove(tmsg.getWhere().value());
365 probesInProtection.remove(tmsg.getWhere().value());
366 if (probesInOFF.add(tmsg.getWhere().value())) {
367 logger.debug("atLeastOneProbeInOFF: added WHERE ---> {}", tmsg.getWhere());
369 } else if (mode == Thermoregulation.OperationMode.PROTECTION) {
370 probesInManual.remove(tmsg.getWhere().value());
371 probesInOFF.remove(tmsg.getWhere().value());
372 if (probesInProtection.add(tmsg.getWhere().value())) {
373 logger.debug("atLeastOneProbeInProtection: added WHERE ---> {}", tmsg.getWhere());
375 } else if (mode == Thermoregulation.OperationMode.MANUAL) {
376 probesInProtection.remove(tmsg.getWhere().value());
377 probesInOFF.remove(tmsg.getWhere().value());
378 if (probesInManual.add(tmsg.getWhere().value())) {
379 logger.debug("atLeastOneProbeInManual: added WHERE ---> {}", tmsg.getWhere());
383 if (probesInOFF.isEmpty()) {
384 updateCUAtLeastOneProbeOff(OnOffType.OFF);
386 if (probesInProtection.isEmpty()) {
387 updateCUAtLeastOneProbeProtection(OnOffType.OFF);
389 if (probesInManual.isEmpty()) {
390 updateCUAtLeastOneProbeManual(OnOffType.OFF);
394 updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
395 updateState(CHANNEL_MODE, new StringType(mode.toString()));
397 // store current function
398 currentFunction = function;
401 private void updateTemperature(Thermoregulation tmsg) {
403 double temp = Thermoregulation.parseTemperature(tmsg);
404 updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
405 } catch (FrameException e) {
406 logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
407 updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
411 private void updateSetpoint(Thermoregulation tmsg) {
413 double temp = Thermoregulation.parseTemperature(tmsg);
414 updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
415 currentSetPointTemp = temp;
416 } catch (FrameException e) {
417 logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
418 updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
422 private void updateFanCoilSpeed(Thermoregulation tmsg) {
424 Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
425 updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
426 } catch (FrameException e) {
427 logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
428 updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
432 private void updateValveStatus(Thermoregulation tmsg) {
434 Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
435 Thermoregulation.WhatThermo.CONDITIONING);
436 updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
438 Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
439 Thermoregulation.WhatThermo.HEATING);
440 updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
441 } catch (FrameException e) {
442 logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
443 updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
444 updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
448 private void updateActuatorStatus(Thermoregulation tmsg) {
450 Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
451 updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
452 } catch (FrameException e) {
453 logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
454 updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
458 private void updateLocalOffset(Thermoregulation tmsg) {
460 Thermoregulation.LocalOffset offset = Thermoregulation.parseLocalOffset(tmsg);
461 updateState(CHANNEL_LOCAL_OFFSET, new StringType(offset.toString()));
462 logger.debug("updateLocalOffset() {}: {}", tmsg, offset.toString());
464 } catch (FrameException e) {
465 logger.warn("updateLocalOffset() FrameException on frame {}: {}", tmsg, e.getMessage());
466 updateState(CHANNEL_LOCAL_OFFSET, UnDefType.UNDEF);
470 private void updateCURemoteControlStatus(String status) {
471 updateState(CHANNEL_CU_REMOTE_CONTROL, new StringType(status));
472 logger.debug("updateCURemoteControlStatus(): {}", status);
475 private void updateCUBatteryStatus(String status) {
476 updateState(CHANNEL_CU_BATTERY_STATUS, new StringType(status));
478 if (status == CU_BATTERY_KO) { // do not log default value (which is automatically setted)
479 logger.debug("updateCUBatteryStatus(): {}", status);
483 private void updateCUFailureDiscovered(OnOffType status) {
484 updateState(CHANNEL_CU_FAILURE_DISCOVERED, status);
486 if (status == OnOffType.ON) { // do not log default value (which is automatically setted)
487 logger.debug("updateCUFailureDiscovered(): {}", status);
491 private void updateCUAtLeastOneProbeOff(OnOffType status) {
492 updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_OFF, status);
493 logger.debug("updateCUAtLeastOneProbeOff(): {}", status);
496 private void updateCUAtLeastOneProbeProtection(OnOffType status) {
497 updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_PROTECTION, status);
498 logger.debug("updateCUAtLeastOneProbeProtection(): {}", status);
501 private void updateCUAtLeastOneProbeManual(OnOffType status) {
502 updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL, status);
503 logger.debug("updateCUAtLeastOneProbeManual(): {}", status);
506 private Boolean channelExists(String channelID) {
507 return thing.getChannel("openwebnet:" + channelID) != null;
511 protected void refreshDevice(boolean refreshAll) {
512 logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
514 // TODO: 4 zone central -> zone #0 CAN be also a zone with its temp.. with 99-zones central no!
515 // let's assume it's a 99 zone
517 send(Thermoregulation.requestStatus("#0"));
518 } catch (OWNException e) {
519 logger.warn("refreshDevice() central unit returned OWNException {}", e.getMessage());
525 if (deviceWhere != null) {
527 String w = deviceWhere.value();
529 send(Thermoregulation.requestTemperature(w));
531 if (!((WhereThermo) deviceWhere).isProbe()) {
532 // for bus_thermo_zone request also other single channels updates
533 send(Thermoregulation.requestSetPointTemperature(w));
534 send(Thermoregulation.requestMode(w));
536 // refresh ONLY subscribed channels
537 if (channelExists(CHANNEL_FAN_SPEED)) {
538 send(Thermoregulation.requestFanCoilSpeed(w));
541 if (channelExists(CHANNEL_CONDITIONING_VALVES) || channelExists(CHANNEL_HEATING_VALVES)) {
542 send(Thermoregulation.requestValvesStatus(w));
545 if (channelExists(CHANNEL_ACTUATORS)) {
546 send(Thermoregulation.requestActuatorsStatus(w));
549 if (channelExists(CHANNEL_LOCAL_OFFSET)) {
550 send(Thermoregulation.requestLocalOffset(w));
553 } catch (OWNException e) {
554 logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());