2 * Copyright (c) 2010-2023 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.velbus.internal.handler;
15 import static org.openhab.binding.velbus.internal.VelbusBindingConstants.*;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.openhab.binding.velbus.internal.packets.VelbusPacket;
19 import org.openhab.binding.velbus.internal.packets.VelbusSensorSettingsRequestPacket;
20 import org.openhab.binding.velbus.internal.packets.VelbusSetTemperaturePacket;
21 import org.openhab.binding.velbus.internal.packets.VelbusThermostatModePacket;
22 import org.openhab.binding.velbus.internal.packets.VelbusThermostatOperatingModePacket;
23 import org.openhab.core.library.types.DecimalType;
24 import org.openhab.core.library.types.QuantityType;
25 import org.openhab.core.library.types.StringType;
26 import org.openhab.core.library.unit.SIUnits;
27 import org.openhab.core.thing.ChannelUID;
28 import org.openhab.core.thing.CommonTriggerEvents;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.types.Command;
33 import org.openhab.core.types.RefreshType;
36 * The {@link VelbusThermostatHandler} is responsible for handling commands, which are
37 * sent to one of the channels.
39 * @author Cedric Boon - Initial contribution
42 public abstract class VelbusThermostatHandler extends VelbusTemperatureSensorHandler {
43 private static final double THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION = 0.5;
45 private static final StringType OPERATING_MODE_HEATING = new StringType("HEATING");
46 private static final StringType OPERATING_MODE_COOLING = new StringType("COOLING");
48 private static final byte OPERATING_MODE_MASK = (byte) 0x80;
49 private static final byte COOLING_MODE_MASK = (byte) 0x80;
51 private static final StringType MODE_COMFORT = new StringType("COMFORT");
52 private static final StringType MODE_DAY = new StringType("DAY");
53 private static final StringType MODE_NIGHT = new StringType("NIGHT");
54 private static final StringType MODE_SAFE = new StringType("SAFE");
56 private static final byte MODE_MASK = (byte) 0x70;
57 private static final byte COMFORT_MODE_MASK = (byte) 0x40;
58 private static final byte DAY_MODE_MASK = (byte) 0x20;
59 private static final byte NIGHT_MODE_MASK = (byte) 0x10;
61 private final ChannelUID currentTemperatureSetpointChannel = new ChannelUID(thing.getUID(), "thermostat",
62 "currentTemperatureSetpoint");
63 private final ChannelUID heatingModeComfortTemperatureSetpointChannel = new ChannelUID(thing.getUID(), "thermostat",
64 "heatingModeComfortTemperatureSetpoint");
65 private final ChannelUID heatingModeDayTemperatureSetpointChannel = new ChannelUID(thing.getUID(), "thermostat",
66 "heatingModeDayTemperatureSetpoint");
67 private final ChannelUID heatingModeNightTemperatureSetpointChannel = new ChannelUID(thing.getUID(), "thermostat",
68 "heatingModeNightTemperatureSetpoint");
69 private final ChannelUID heatingModeAntifrostTemperatureSetpointChannel = new ChannelUID(thing.getUID(),
70 "thermostat", "heatingModeAntiFrostTemperatureSetpoint");
71 private final ChannelUID coolingModeComfortTemperatureSetpointChannel = new ChannelUID(thing.getUID(), "thermostat",
72 "coolingModeComfortTemperatureSetpoint");
73 private final ChannelUID coolingModeDayTemperatureSetpointChannel = new ChannelUID(thing.getUID(), "thermostat",
74 "coolingModeDayTemperatureSetpoint");
75 private final ChannelUID coolingModeNightTemperatureSetpointChannel = new ChannelUID(thing.getUID(), "thermostat",
76 "coolingModeNightTemperatureSetpoint");
77 private final ChannelUID coolingModeSafeTemperatureSetpointChannel = new ChannelUID(thing.getUID(), "thermostat",
78 "coolingModeSafeTemperatureSetpoint");
79 private final ChannelUID operatingModeChannel = new ChannelUID(thing.getUID(), "thermostat", "operatingMode");
80 private final ChannelUID modeChannel = new ChannelUID(thing.getUID(), "thermostat", "mode");
81 private final ChannelUID heaterChannel = new ChannelUID(thing.getUID(), "thermostat", "heater");
82 private final ChannelUID boostChannel = new ChannelUID(thing.getUID(), "thermostat", "boost");
83 private final ChannelUID pumpChannel = new ChannelUID(thing.getUID(), "thermostat", "pump");
84 private final ChannelUID coolerChannel = new ChannelUID(thing.getUID(), "thermostat", "cooler");
85 private final ChannelUID alarm1Channel = new ChannelUID(thing.getUID(), "thermostat", "alarm1");
86 private final ChannelUID alarm2Channel = new ChannelUID(thing.getUID(), "thermostat", "alarm2");
87 private final ChannelUID alarm3Channel = new ChannelUID(thing.getUID(), "thermostat", "alarm3");
88 private final ChannelUID alarm4Channel = new ChannelUID(thing.getUID(), "thermostat", "alarm4");
90 public VelbusThermostatHandler(Thing thing, int numberOfSubAddresses, ChannelUID temperatureChannel) {
91 super(thing, numberOfSubAddresses, temperatureChannel);
95 public void handleCommand(ChannelUID channelUID, Command command) {
96 super.handleCommand(channelUID, command);
98 VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
99 if (velbusBridgeHandler == null) {
100 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
104 if (isThermostatChannel(channelUID) && command instanceof RefreshType) {
105 VelbusSensorSettingsRequestPacket packet = new VelbusSensorSettingsRequestPacket(
106 getModuleAddress().getAddress());
108 byte[] packetBytes = packet.getBytes();
109 velbusBridgeHandler.sendPacket(packetBytes);
110 } else if (isThermostatChannel(channelUID)
111 && (command instanceof QuantityType<?> || command instanceof DecimalType)) {
112 byte temperatureVariable = determineTemperatureVariable(channelUID);
113 QuantityType<?> temperatureInDegreesCelcius = (command instanceof QuantityType<?>)
114 ? ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS)
115 : new QuantityType<>(((DecimalType) command), SIUnits.CELSIUS);
117 if (temperatureInDegreesCelcius != null) {
118 byte temperature = convertToTwoComplementByte(temperatureInDegreesCelcius.doubleValue(),
119 THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
121 VelbusSetTemperaturePacket packet = new VelbusSetTemperaturePacket(getModuleAddress().getAddress(),
122 temperatureVariable, temperature);
124 byte[] packetBytes = packet.getBytes();
125 velbusBridgeHandler.sendPacket(packetBytes);
127 } else if (channelUID.equals(operatingModeChannel) && command instanceof StringType) {
128 byte commandByte = ((StringType) command).equals(OPERATING_MODE_HEATING) ? COMMAND_SET_HEATING_MODE
129 : COMMAND_SET_COOLING_MODE;
131 VelbusThermostatOperatingModePacket packet = new VelbusThermostatOperatingModePacket(
132 getModuleAddress().getAddress(), commandByte);
134 byte[] packetBytes = packet.getBytes();
135 velbusBridgeHandler.sendPacket(packetBytes);
136 } else if (channelUID.equals(modeChannel) && command instanceof StringType) {
137 byte commandByte = COMMAND_SWITCH_TO_SAFE_MODE;
139 StringType stringTypeCommand = (StringType) command;
140 if (stringTypeCommand.equals(MODE_COMFORT)) {
141 commandByte = COMMAND_SWITCH_TO_COMFORT_MODE;
142 } else if (stringTypeCommand.equals(MODE_DAY)) {
143 commandByte = COMMAND_SWITCH_TO_DAY_MODE;
144 } else if (stringTypeCommand.equals(MODE_NIGHT)) {
145 commandByte = COMMAND_SWITCH_TO_NIGHT_MODE;
148 VelbusThermostatModePacket packet = new VelbusThermostatModePacket(getModuleAddress().getAddress(),
151 byte[] packetBytes = packet.getBytes();
152 velbusBridgeHandler.sendPacket(packetBytes);
154 logger.debug("The command '{}' is not supported by this handler.", command.getClass());
159 public void onPacketReceived(byte[] packet) {
160 super.onPacketReceived(packet);
162 logger.trace("onPacketReceived() was called");
164 if (packet[0] == VelbusPacket.STX && packet.length >= 5) {
165 byte address = packet[2];
166 byte command = packet[4];
168 if (command == COMMAND_TEMP_SENSOR_SETTINGS_PART1 && packet.length >= 9) {
169 byte currentTemperatureSetByte = packet[5];
170 byte heatingModeComfortTemperatureSetByte = packet[6];
171 byte heatingModeDayTemperatureSetByte = packet[7];
172 byte heatingModeNightTemperatureSetByte = packet[8];
173 byte heatingModeAntiFrostTemperatureSetByte = packet[9];
175 double currentTemperatureSet = convertFromTwoComplementByte(currentTemperatureSetByte,
176 THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
177 double heatingModeComfortTemperatureSet = convertFromTwoComplementByte(
178 heatingModeComfortTemperatureSetByte, THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
179 double heatingModeDayTemperatureSet = convertFromTwoComplementByte(heatingModeDayTemperatureSetByte,
180 THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
181 double heatingModeNightTemperatureSet = convertFromTwoComplementByte(heatingModeNightTemperatureSetByte,
182 THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
183 double heatingModeAntiFrostTemperatureSet = convertFromTwoComplementByte(
184 heatingModeAntiFrostTemperatureSetByte, THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
186 updateState(currentTemperatureSetpointChannel,
187 new QuantityType<>(currentTemperatureSet, SIUnits.CELSIUS));
188 updateState(heatingModeComfortTemperatureSetpointChannel,
189 new QuantityType<>(heatingModeComfortTemperatureSet, SIUnits.CELSIUS));
190 updateState(heatingModeDayTemperatureSetpointChannel,
191 new QuantityType<>(heatingModeDayTemperatureSet, SIUnits.CELSIUS));
192 updateState(heatingModeNightTemperatureSetpointChannel,
193 new QuantityType<>(heatingModeNightTemperatureSet, SIUnits.CELSIUS));
194 updateState(heatingModeAntifrostTemperatureSetpointChannel,
195 new QuantityType<>(heatingModeAntiFrostTemperatureSet, SIUnits.CELSIUS));
196 } else if (command == COMMAND_TEMP_SENSOR_SETTINGS_PART2 && packet.length >= 8) {
197 byte coolingModeComfortTemperatureSetByte = packet[5];
198 byte coolingModeDayTemperatureSetByte = packet[6];
199 byte coolingModeNightTemperatureSetByte = packet[7];
200 byte coolingModeSafeTemperatureSetByte = packet[8];
202 double coolingModeComfortTemperatureSet = convertFromTwoComplementByte(
203 coolingModeComfortTemperatureSetByte, THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
204 double coolingModeDayTemperatureSet = convertFromTwoComplementByte(coolingModeDayTemperatureSetByte,
205 THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
206 double coolingModeNightTemperatureSet = convertFromTwoComplementByte(coolingModeNightTemperatureSetByte,
207 THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
208 double coolingModeSafeTemperatureSet = convertFromTwoComplementByte(coolingModeSafeTemperatureSetByte,
209 THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
211 updateState(coolingModeComfortTemperatureSetpointChannel,
212 new QuantityType<>(coolingModeComfortTemperatureSet, SIUnits.CELSIUS));
213 updateState(coolingModeDayTemperatureSetpointChannel,
214 new QuantityType<>(coolingModeDayTemperatureSet, SIUnits.CELSIUS));
215 updateState(coolingModeNightTemperatureSetpointChannel,
216 new QuantityType<>(coolingModeNightTemperatureSet, SIUnits.CELSIUS));
217 updateState(coolingModeSafeTemperatureSetpointChannel,
218 new QuantityType<>(coolingModeSafeTemperatureSet, SIUnits.CELSIUS));
219 } else if (command == COMMAND_TEMP_SENSOR_STATUS && packet.length >= 9) {
220 byte operatingMode = packet[5];
221 byte targetTemperature = packet[9];
223 if ((operatingMode & OPERATING_MODE_MASK) == COOLING_MODE_MASK) {
224 updateState(operatingModeChannel, OPERATING_MODE_COOLING);
226 updateState(operatingModeChannel, OPERATING_MODE_HEATING);
229 if ((operatingMode & MODE_MASK) == COMFORT_MODE_MASK) {
230 updateState(modeChannel, MODE_COMFORT);
231 } else if ((operatingMode & MODE_MASK) == DAY_MODE_MASK) {
232 updateState(modeChannel, MODE_DAY);
233 } else if ((operatingMode & MODE_MASK) == NIGHT_MODE_MASK) {
234 updateState(modeChannel, MODE_NIGHT);
236 updateState(modeChannel, MODE_SAFE);
239 double targetTemperatureValue = convertFromTwoComplementByte(targetTemperature,
240 THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
241 updateState(currentTemperatureSetpointChannel,
242 new QuantityType<>(targetTemperatureValue, SIUnits.CELSIUS));
243 } else if (address != this.getModuleAddress().getAddress() && command == COMMAND_PUSH_BUTTON_STATUS) {
244 byte outputChannelsJustActivated = packet[5];
245 byte outputChannelsJustDeactivated = packet[6];
247 triggerThermostatChannels(outputChannelsJustActivated, CommonTriggerEvents.PRESSED);
248 triggerThermostatChannels(outputChannelsJustDeactivated, CommonTriggerEvents.RELEASED);
253 private void triggerThermostatChannels(byte outputChannels, String event) {
254 if ((outputChannels & 0x01) == 0x01) {
255 triggerChannel(heaterChannel, event);
257 if ((outputChannels & 0x02) == 0x02) {
258 triggerChannel(boostChannel, event);
260 if ((outputChannels & 0x04) == 0x04) {
261 triggerChannel(pumpChannel, event);
263 if ((outputChannels & 0x08) == 0x08) {
264 triggerChannel(coolerChannel, event);
266 if ((outputChannels & 0x10) == 0x10) {
267 triggerChannel(alarm1Channel, event);
269 if ((outputChannels & 0x20) == 0x20) {
270 triggerChannel(alarm2Channel, event);
272 if ((outputChannels & 0x40) == 0x40) {
273 triggerChannel(alarm3Channel, event);
275 if ((outputChannels & 0x80) == 0x80) {
276 triggerChannel(alarm4Channel, event);
280 protected boolean isThermostatChannel(ChannelUID channelUID) {
281 return channelUID.equals(currentTemperatureSetpointChannel)
282 || channelUID.equals(heatingModeComfortTemperatureSetpointChannel)
283 || channelUID.equals(heatingModeDayTemperatureSetpointChannel)
284 || channelUID.equals(heatingModeNightTemperatureSetpointChannel)
285 || channelUID.equals(heatingModeAntifrostTemperatureSetpointChannel)
286 || channelUID.equals(coolingModeComfortTemperatureSetpointChannel)
287 || channelUID.equals(coolingModeDayTemperatureSetpointChannel)
288 || channelUID.equals(coolingModeNightTemperatureSetpointChannel)
289 || channelUID.equals(coolingModeSafeTemperatureSetpointChannel)
290 || channelUID.equals(operatingModeChannel) || channelUID.equals(modeChannel);
293 protected byte determineTemperatureVariable(ChannelUID channelUID) {
294 if (channelUID.equals(currentTemperatureSetpointChannel)) {
296 } else if (channelUID.equals(heatingModeComfortTemperatureSetpointChannel)) {
298 } else if (channelUID.equals(heatingModeDayTemperatureSetpointChannel)) {
300 } else if (channelUID.equals(heatingModeNightTemperatureSetpointChannel)) {
302 } else if (channelUID.equals(heatingModeAntifrostTemperatureSetpointChannel)) {
304 } else if (channelUID.equals(coolingModeComfortTemperatureSetpointChannel)) {
306 } else if (channelUID.equals(coolingModeDayTemperatureSetpointChannel)) {
308 } else if (channelUID.equals(coolingModeNightTemperatureSetpointChannel)) {
310 } else if (channelUID.equals(coolingModeSafeTemperatureSetpointChannel)) {
313 throw new IllegalArgumentException("The given channelUID is not a thermostat channel: " + channelUID);
317 protected double convertFromTwoComplementByte(byte value, double resolution) {
318 return ((value & 0x80) == 0x00) ? value * resolution : ((value & 0x7F) - 0x80) * resolution;
321 protected byte convertToTwoComplementByte(double value, double resolution) {
322 return (byte) (value / resolution);