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_BATTERY_STATUS;
18 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_CU_REMOTE_CONTROL;
19 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_CU_SCENARIO_PROGRAM_NUMBER;
20 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_CU_WEEKLY_PROGRAM_NUMBER;
21 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_FAN_SPEED;
22 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_FUNCTION;
23 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_HEATING_VALVES;
24 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_LOCAL_OFFSET;
25 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_MODE;
26 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_TEMPERATURE;
27 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_TEMP_SETPOINT;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.library.unit.SIUnits;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.ThingStatusInfo;
42 import org.openhab.core.thing.ThingTypeUID;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.UnDefType;
45 import org.openwebnet4j.communication.OWNException;
46 import org.openwebnet4j.message.BaseOpenMessage;
47 import org.openwebnet4j.message.FrameException;
48 import org.openwebnet4j.message.MalformedFrameException;
49 import org.openwebnet4j.message.Thermoregulation;
50 import org.openwebnet4j.message.Thermoregulation.WhatThermo;
51 import org.openwebnet4j.message.Where;
52 import org.openwebnet4j.message.WhereThermo;
53 import org.openwebnet4j.message.Who;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * The {@link OpenWebNetThermoregulationHandler} is responsible for handling commands/messages for Thermoregulation
59 * Things. It extends the abstract {@link OpenWebNetThingHandler}.
61 * @author Massimo Valla - Initial contribution
62 * @author Andrea Conte - Thermoregulation
63 * @author Gilberto Cocchi - Thermoregulation
66 public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
68 private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
70 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
72 private double currentSetPointTemp = 11.5d; // 11.5 is the default setTemp used in MyHomeUP mobile app
74 private Thermoregulation.Function currentFunction = Thermoregulation.Function.GENERIC;
76 private boolean isStandAlone = false;
78 private boolean isCentralUnit = false;
80 private String programNumber = "";
82 private static final String CU_REMOTE_CONTROL_ENABLED = "ENABLED";
83 private static final String CU_REMOTE_CONTROL_DISABLED = "DISABLED";
84 private static final String CU_BATTERY_OK = "OK";
85 private static final String CU_BATTERY_KO = "KO";
87 public OpenWebNetThermoregulationHandler(Thing thing) {
92 public void initialize() {
95 ThingTypeUID thingType = thing.getThingTypeUID();
96 isCentralUnit = OpenWebNetBindingConstants.THING_TYPE_BUS_THERMO_CU.equals(thingType);
99 Object standAloneConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_STANDALONE);
100 if (standAloneConfig != null) {
101 // null in case of thermo_sensor
102 isStandAlone = Boolean.parseBoolean(standAloneConfig.toString());
105 // central unit must have WHERE=0
106 if (!deviceWhere.value().equals("0")) {
107 logger.warn("initialize() Invalid WHERE={} for Central Unit.", deviceWhere.value());
109 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110 "@text/offline.conf-error-where");
116 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
117 super.bridgeStatusChanged(bridgeStatusInfo);
118 // when the bridge is ONLINE request for thing states (temp, setTemp, fanSpeed...)
119 if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
120 refreshDevice(false);
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 if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_DISABLED) {
288 updateCURemoteControlStatus(CU_REMOTE_CONTROL_DISABLED);
289 } else if (msg.getWhat() == Thermoregulation.WhatThermo.REMOTE_CONTROL_ENABLED) {
290 updateCURemoteControlStatus(CU_REMOTE_CONTROL_ENABLED);
291 } else if (msg.getWhat() == Thermoregulation.WhatThermo.BATTERY_KO) {
292 updateCUBatteryStatus(CU_BATTERY_KO);
293 } // must intercept all possibile WHATs (will be implemented soon)
294 else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_OFF) {
295 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
296 } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_ANTIFREEZE) {
297 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
298 } else if (msg.getWhat() == Thermoregulation.WhatThermo.AT_LEAST_ONE_PROBE_MANUAL) {
299 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
300 } else if (msg.getWhat() == Thermoregulation.WhatThermo.FAILURE_DISCOVERED) {
301 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
302 } else if (msg.getWhat() == Thermoregulation.WhatThermo.RELEASE_SENSOR_LOCAL_ADJUST) {
303 logger.debug("handleMessage() Ignoring unsupported WHAT {}. Frame={}", msg.getWhat(), msg);
305 // check and eventually parse mode and function
306 updateModeAndFunction((Thermoregulation) msg);
311 if (msg.isCommand()) {
312 updateModeAndFunction((Thermoregulation) msg);
314 if (msg.getDim() == null) {
317 if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
318 || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
319 updateTemperature((Thermoregulation) msg);
320 } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
321 || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
322 updateSetpoint((Thermoregulation) msg);
323 } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
324 updateValveStatus((Thermoregulation) msg);
325 } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
326 updateActuatorStatus((Thermoregulation) msg);
327 } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
328 updateFanCoilSpeed((Thermoregulation) msg);
329 } else if (msg.getDim() == Thermoregulation.DimThermo.OFFSET) {
330 updateLocalOffset((Thermoregulation) msg);
332 logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
333 getThing().getUID(), msg);
338 private void updateModeAndFunction(Thermoregulation tmsg) {
339 if (tmsg.getWhat() == null) {
340 logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
341 tmsg.getFrameValue());
344 Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
346 if (w.getMode() == null) {
347 logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
350 if (w.getFunction() == null) {
351 logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
355 Thermoregulation.OperationMode mode = w.getMode();
356 Thermoregulation.Function function = w.getFunction();
358 updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
359 updateState(CHANNEL_MODE, new StringType(mode.toString()));
361 // store current function
362 currentFunction = function;
365 private void updateTemperature(Thermoregulation tmsg) {
367 double temp = Thermoregulation.parseTemperature(tmsg);
368 updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
369 } catch (FrameException e) {
370 logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
371 updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
375 private void updateSetpoint(Thermoregulation tmsg) {
377 double temp = Thermoregulation.parseTemperature(tmsg);
378 updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
379 currentSetPointTemp = temp;
380 } catch (FrameException e) {
381 logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
382 updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
386 private void updateFanCoilSpeed(Thermoregulation tmsg) {
388 Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
389 updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
390 } catch (FrameException e) {
391 logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
392 updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
396 private void updateValveStatus(Thermoregulation tmsg) {
398 Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
399 Thermoregulation.WhatThermo.CONDITIONING);
400 updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
402 Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
403 Thermoregulation.WhatThermo.HEATING);
404 updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
405 } catch (FrameException e) {
406 logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
407 updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
408 updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
412 private void updateActuatorStatus(Thermoregulation tmsg) {
414 Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
415 updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
416 } catch (FrameException e) {
417 logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
418 updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
422 private void updateLocalOffset(Thermoregulation tmsg) {
424 Thermoregulation.LocalOffset offset = Thermoregulation.parseLocalOffset(tmsg);
425 updateState(CHANNEL_LOCAL_OFFSET, new StringType(offset.toString()));
426 logger.debug("updateLocalOffset() {}: {}", tmsg, offset.toString());
428 } catch (FrameException e) {
429 logger.warn("updateLocalOffset() FrameException on frame {}: {}", tmsg, e.getMessage());
430 updateState(CHANNEL_LOCAL_OFFSET, UnDefType.UNDEF);
434 private void updateCURemoteControlStatus(String status) {
435 updateState(CHANNEL_CU_REMOTE_CONTROL, new StringType(status));
436 logger.debug("updateCURemoteControlStatus(): {}", status);
439 private void updateCUBatteryStatus(String status) {
440 updateState(CHANNEL_CU_BATTERY_STATUS, new StringType(status));
441 logger.debug("updateCUBatteryStatus(): {}", status);
444 private Boolean channelExists(String channelID) {
445 return thing.getChannel("openwebnet:" + channelID) != null;
449 protected void refreshDevice(boolean refreshAll) {
450 logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
452 // TODO: 4 zone central -> zone #0 CAN be also a zone with its temp.. with 99-zones central no!
453 // let's assume it's a 99 zone
455 send(Thermoregulation.requestStatus("#0"));
456 } catch (OWNException e) {
457 logger.warn("refreshDevice() central unit returned OWNException {}", e.getMessage());
463 if (deviceWhere != null) {
465 String w = deviceWhere.value();
467 send(Thermoregulation.requestTemperature(w));
469 if (!((WhereThermo) deviceWhere).isProbe()) {
470 // for bus_thermo_zone request also other single channels updates
471 send(Thermoregulation.requestSetPointTemperature(w));
472 send(Thermoregulation.requestMode(w));
474 // refresh ONLY subscribed channels
475 if (channelExists(CHANNEL_FAN_SPEED)) {
476 send(Thermoregulation.requestFanCoilSpeed(w));
479 if (channelExists(CHANNEL_CONDITIONING_VALVES) || channelExists(CHANNEL_HEATING_VALVES)) {
480 send(Thermoregulation.requestValvesStatus(w));
483 if (channelExists(CHANNEL_ACTUATORS)) {
484 send(Thermoregulation.requestActuatorsStatus(w));
487 if (channelExists(CHANNEL_LOCAL_OFFSET)) {
488 send(Thermoregulation.requestLocalOffset(w));
491 } catch (OWNException e) {
492 logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());