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.ThingStatusInfo;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.UnDefType;
51 import org.openwebnet4j.communication.OWNException;
52 import org.openwebnet4j.message.BaseOpenMessage;
53 import org.openwebnet4j.message.FrameException;
54 import org.openwebnet4j.message.MalformedFrameException;
55 import org.openwebnet4j.message.Thermoregulation;
56 import org.openwebnet4j.message.Thermoregulation.WhatThermo;
57 import org.openwebnet4j.message.Where;
58 import org.openwebnet4j.message.WhereThermo;
59 import org.openwebnet4j.message.Who;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
64 * The {@link OpenWebNetThermoregulationHandler} is responsible for handling commands/messages for Thermoregulation
65 * Things. It extends the abstract {@link OpenWebNetThingHandler}.
67 * @author Massimo Valla - Initial contribution
68 * @author Andrea Conte - Thermoregulation
69 * @author Gilberto Cocchi - Thermoregulation
72 public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
74 private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
76 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
78 private double currentSetPointTemp = 11.5d; // 11.5 is the default setTemp used in MyHomeUP mobile app
80 private Thermoregulation.Function currentFunction = Thermoregulation.Function.GENERIC;
82 private boolean isStandAlone = false;
84 private boolean isCentralUnit = false;
86 private String programNumber = "";
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>();
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";
97 public OpenWebNetThermoregulationHandler(Thing thing) {
102 public void initialize() {
105 ThingTypeUID thingType = thing.getThingTypeUID();
106 isCentralUnit = OpenWebNetBindingConstants.THING_TYPE_BUS_THERMO_CU.equals(thingType);
108 if (!isCentralUnit) {
109 Object standAloneConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_STANDALONE);
110 if (standAloneConfig != null) {
111 // null in case of thermo_sensor
112 isStandAlone = Boolean.parseBoolean(standAloneConfig.toString());
115 // central unit must have WHERE=0
116 if (!deviceWhere.value().equals("0")) {
117 logger.warn("initialize() Invalid WHERE={} for Central Unit.", deviceWhere.value());
119 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
120 "@text/offline.conf-error-where");
126 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
127 super.bridgeStatusChanged(bridgeStatusInfo);
128 // when the bridge is ONLINE request for thing states (temp, setTemp, fanSpeed...)
129 if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
130 refreshDevice(false);
135 protected void handleChannelCommand(ChannelUID channel, Command command) {
136 switch (channel.getId()) {
137 case CHANNEL_TEMP_SETPOINT:
138 handleSetpoint(command);
140 case CHANNEL_FUNCTION:
141 handleFunction(command);
146 case CHANNEL_FAN_SPEED:
147 handleSetFanSpeed(command);
149 case CHANNEL_CU_WEEKLY_PROGRAM_NUMBER:
150 case CHANNEL_CU_SCENARIO_PROGRAM_NUMBER:
151 handleSetProgramNumber(command);
154 logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
160 protected void requestChannelState(ChannelUID channel) {
161 super.requestChannelState(channel);
162 refreshDevice(false);
166 protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
167 return new WhereThermo(wStr);
171 protected String ownIdPrefix() {
172 return Who.THERMOREGULATION.value().toString();
175 private void handleSetFanSpeed(Command command) {
176 if (command instanceof StringType) {
177 Where w = deviceWhere;
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());
191 logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
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.");
202 programNumber = command.toString();
203 logger.debug("handleSetProgramNumber() Program number set to {}", programNumber);
206 logger.warn("handleSetProgramNumber() Unsupported command {} for thing {}", command, getThing().getUID());
210 private void handleSetpoint(Command command) {
211 if (command instanceof QuantityType || command instanceof DecimalType) {
212 Where w = deviceWhere;
215 if (command instanceof QuantityType) {
216 QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
217 if (tempCelsius != null) {
218 newTemp = tempCelsius.doubleValue();
221 newTemp = ((DecimalType) command).doubleValue();
224 send(Thermoregulation.requestWriteSetpointTemperature(getWhere(w.value()), newTemp,
226 } catch (MalformedFrameException | OWNException e) {
227 logger.warn("handleSetpoint() {}", e.getMessage());
231 logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
235 private void handleMode(Command command) {
236 if (command instanceof StringType) {
237 Where w = deviceWhere;
240 Thermoregulation.OperationMode new_mode = Thermoregulation.OperationMode.OFF;
242 if (isCentralUnit && WhatThermo.isComplex(command.toString()))
243 new_mode = Thermoregulation.OperationMode.valueOf(command.toString() + "_" + programNumber);
245 new_mode = Thermoregulation.OperationMode.valueOf(command.toString());
247 send(Thermoregulation.requestWriteMode(getWhere(w.value()), new_mode, currentFunction,
248 currentSetPointTemp));
249 } catch (OWNException e) {
250 logger.warn("handleMode() {}", e.getMessage());
251 } catch (IllegalArgumentException e) {
252 logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
257 logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
261 private String getWhere(String where) {
265 return isStandAlone ? where : "#" + where;
269 private void handleFunction(Command command) {
270 if (command instanceof StringType) {
271 Where w = deviceWhere;
274 Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
275 send(Thermoregulation.requestWriteFunction(w.value(), function));
276 } catch (OWNException e) {
277 logger.warn("handleFunction() {}", e.getMessage());
278 } catch (IllegalArgumentException e) {
279 logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
284 logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
289 protected void handleMessage(BaseOpenMessage msg) {
290 super.handleMessage(msg);
293 // there isn't a message used for setting OK for battery status so let's assume
294 // it's OK and then change to KO if according message is received
295 updateCUBatteryStatus(CU_BATTERY_OK);
297 // same in case of Failure Discovered
298 updateCUFailureDiscovered(OnOffType.OFF);
300 if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_DISABLED) {
301 updateCURemoteControlStatus(CU_REMOTE_CONTROL_DISABLED);
302 } else if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_ENABLED) {
303 updateCURemoteControlStatus(CU_REMOTE_CONTROL_ENABLED);
304 } else if (msg.getWhat() == Thermoregulation.WhatThermo.BATTERY_KO) {
305 updateCUBatteryStatus(CU_BATTERY_KO);
306 } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_OFF) {
307 updateCUAtLeastOneProbeOff(OnOffType.ON);
308 } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_ANTIFREEZE) {
309 updateCUAtLeastOneProbeProtection(OnOffType.ON);
310 } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_MANUAL) {
311 updateCUAtLeastOneProbeManual(OnOffType.ON);
312 } else if (msg.getWhat() == Thermoregulation.WhatThermo.FAILURE_DISCOVERED) {
313 updateCUFailureDiscovered(OnOffType.ON);
314 } // must intercept all possibile WHATs (will be implemented soon)
315 else if (msg.getWhat() == Thermoregulation.WhatThermo.RELEASE_SENSOR_LOCAL_ADJUST) {
316 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
318 // check and eventually parse mode and function
319 updateModeAndFunction((Thermoregulation) msg);
324 if (msg.isCommand()) {
325 updateModeAndFunction((Thermoregulation) msg);
327 if (msg.getDim() == null) {
330 if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
331 || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
332 updateTemperature((Thermoregulation) msg);
333 } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
334 || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
335 updateSetpoint((Thermoregulation) msg);
336 } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
337 updateValveStatus((Thermoregulation) msg);
338 } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
339 updateActuatorStatus((Thermoregulation) msg);
340 } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
341 updateFanCoilSpeed((Thermoregulation) msg);
342 } else if (msg.getDim() == Thermoregulation.DimThermo.OFFSET) {
343 updateLocalOffset((Thermoregulation) msg);
345 logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
346 getThing().getUID(), msg);
351 private void updateModeAndFunction(Thermoregulation tmsg) {
352 if (tmsg.getWhat() == null) {
353 logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
354 tmsg.getFrameValue());
357 Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
359 if (w.getMode() == null) {
360 logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
363 if (w.getFunction() == null) {
364 logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
368 Thermoregulation.OperationMode mode = w.getMode();
369 Thermoregulation.Function function = w.getFunction();
371 // keep track of thermostats (zones) status
372 if (!isCentralUnit && (!((WhereThermo) deviceWhere).isProbe())) {
373 if (mode == Thermoregulation.OperationMode.OFF) {
374 probesInManual.remove(tmsg.getWhere().value());
375 probesInProtection.remove(tmsg.getWhere().value());
376 if (probesInOFF.add(tmsg.getWhere().value())) {
377 logger.debug("atLeastOneProbeInOFF: added WHERE ---> {}", tmsg.getWhere());
379 } else if (mode == Thermoregulation.OperationMode.PROTECTION) {
380 probesInManual.remove(tmsg.getWhere().value());
381 probesInOFF.remove(tmsg.getWhere().value());
382 if (probesInProtection.add(tmsg.getWhere().value())) {
383 logger.debug("atLeastOneProbeInProtection: added WHERE ---> {}", tmsg.getWhere());
385 } else if (mode == Thermoregulation.OperationMode.MANUAL) {
386 probesInProtection.remove(tmsg.getWhere().value());
387 probesInOFF.remove(tmsg.getWhere().value());
388 if (probesInManual.add(tmsg.getWhere().value())) {
389 logger.debug("atLeastOneProbeInManual: added WHERE ---> {}", tmsg.getWhere());
393 if (probesInOFF.isEmpty()) {
394 updateCUAtLeastOneProbeOff(OnOffType.OFF);
396 if (probesInProtection.isEmpty()) {
397 updateCUAtLeastOneProbeProtection(OnOffType.OFF);
399 if (probesInManual.isEmpty()) {
400 updateCUAtLeastOneProbeManual(OnOffType.OFF);
404 updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
405 updateState(CHANNEL_MODE, new StringType(mode.toString()));
407 // store current function
408 currentFunction = function;
411 private void updateTemperature(Thermoregulation tmsg) {
413 double temp = Thermoregulation.parseTemperature(tmsg);
414 updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
415 } catch (FrameException e) {
416 logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
417 updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
421 private void updateSetpoint(Thermoregulation tmsg) {
423 double temp = Thermoregulation.parseTemperature(tmsg);
424 updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
425 currentSetPointTemp = temp;
426 } catch (FrameException e) {
427 logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
428 updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
432 private void updateFanCoilSpeed(Thermoregulation tmsg) {
434 Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
435 updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
436 } catch (FrameException e) {
437 logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
438 updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
442 private void updateValveStatus(Thermoregulation tmsg) {
444 Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
445 Thermoregulation.WhatThermo.CONDITIONING);
446 updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
448 Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
449 Thermoregulation.WhatThermo.HEATING);
450 updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
451 } catch (FrameException e) {
452 logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
453 updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
454 updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
458 private void updateActuatorStatus(Thermoregulation tmsg) {
460 Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
461 updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
462 } catch (FrameException e) {
463 logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
464 updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
468 private void updateLocalOffset(Thermoregulation tmsg) {
470 Thermoregulation.LocalOffset offset = Thermoregulation.parseLocalOffset(tmsg);
471 updateState(CHANNEL_LOCAL_OFFSET, new StringType(offset.toString()));
472 logger.debug("updateLocalOffset() {}: {}", tmsg, offset.toString());
474 } catch (FrameException e) {
475 logger.warn("updateLocalOffset() FrameException on frame {}: {}", tmsg, e.getMessage());
476 updateState(CHANNEL_LOCAL_OFFSET, UnDefType.UNDEF);
480 private void updateCURemoteControlStatus(String status) {
481 updateState(CHANNEL_CU_REMOTE_CONTROL, new StringType(status));
482 logger.debug("updateCURemoteControlStatus(): {}", status);
485 private void updateCUBatteryStatus(String status) {
486 updateState(CHANNEL_CU_BATTERY_STATUS, new StringType(status));
488 if (status == CU_BATTERY_KO) { // do not log default value (which is automatically setted)
489 logger.debug("updateCUBatteryStatus(): {}", status);
493 private void updateCUFailureDiscovered(OnOffType status) {
494 updateState(CHANNEL_CU_FAILURE_DISCOVERED, status);
496 if (status == OnOffType.ON) { // do not log default value (which is automatically setted)
497 logger.debug("updateCUFailureDiscovered(): {}", status);
501 private void updateCUAtLeastOneProbeOff(OnOffType status) {
502 updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_OFF, status);
503 logger.debug("updateCUAtLeastOneProbeOff(): {}", status);
506 private void updateCUAtLeastOneProbeProtection(OnOffType status) {
507 updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_PROTECTION, status);
508 logger.debug("updateCUAtLeastOneProbeProtection(): {}", status);
511 private void updateCUAtLeastOneProbeManual(OnOffType status) {
512 updateState(CHANNEL_CU_AT_LEAST_ONE_PROBE_MANUAL, status);
513 logger.debug("updateCUAtLeastOneProbeManual(): {}", status);
516 private Boolean channelExists(String channelID) {
517 return thing.getChannel("openwebnet:" + channelID) != null;
521 protected void refreshDevice(boolean refreshAll) {
522 logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
524 // TODO: 4 zone central -> zone #0 CAN be also a zone with its temp.. with 99-zones central no!
525 // let's assume it's a 99 zone
527 send(Thermoregulation.requestStatus("#0"));
528 } catch (OWNException e) {
529 logger.warn("refreshDevice() central unit returned OWNException {}", e.getMessage());
535 if (deviceWhere != null) {
537 String w = deviceWhere.value();
539 send(Thermoregulation.requestTemperature(w));
541 if (!((WhereThermo) deviceWhere).isProbe()) {
542 // for bus_thermo_zone request also other single channels updates
543 send(Thermoregulation.requestSetPointTemperature(w));
544 send(Thermoregulation.requestMode(w));
546 // refresh ONLY subscribed channels
547 if (channelExists(CHANNEL_FAN_SPEED)) {
548 send(Thermoregulation.requestFanCoilSpeed(w));
551 if (channelExists(CHANNEL_CONDITIONING_VALVES) || channelExists(CHANNEL_HEATING_VALVES)) {
552 send(Thermoregulation.requestValvesStatus(w));
555 if (channelExists(CHANNEL_ACTUATORS)) {
556 send(Thermoregulation.requestActuatorsStatus(w));
559 if (channelExists(CHANNEL_LOCAL_OFFSET)) {
560 send(Thermoregulation.requestLocalOffset(w));
563 } catch (OWNException e) {
564 logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());