]> git.basschouten.com Git - openhab-addons.git/blob
0e8a03c7f183ec77272b91682e9e77654aff2cda
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.velbus.internal.handler;
14
15 import static org.openhab.binding.velbus.internal.VelbusBindingConstants.*;
16
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;
34
35 /**
36  * The {@link VelbusThermostatHandler} is responsible for handling commands, which are
37  * sent to one of the channels.
38  *
39  * @author Cedric Boon - Initial contribution
40  */
41 @NonNullByDefault
42 public abstract class VelbusThermostatHandler extends VelbusTemperatureSensorHandler {
43     private static final double THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION = 0.5;
44
45     private static final StringType OPERATING_MODE_HEATING = new StringType("HEATING");
46     private static final StringType OPERATING_MODE_COOLING = new StringType("COOLING");
47
48     private static final byte OPERATING_MODE_MASK = (byte) 0x80;
49     private static final byte COOLING_MODE_MASK = (byte) 0x80;
50
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");
55
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;
60
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");
89
90     public VelbusThermostatHandler(Thing thing, int numberOfSubAddresses, ChannelUID temperatureChannel) {
91         super(thing, numberOfSubAddresses, temperatureChannel);
92     }
93
94     @Override
95     public void handleCommand(ChannelUID channelUID, Command command) {
96         super.handleCommand(channelUID, command);
97
98         VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
99         if (velbusBridgeHandler == null) {
100             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
101             return;
102         }
103
104         if (isThermostatChannel(channelUID) && command instanceof RefreshType) {
105             VelbusSensorSettingsRequestPacket packet = new VelbusSensorSettingsRequestPacket(
106                     getModuleAddress().getAddress());
107
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<?> qt)
114                     ? qt.toUnit(SIUnits.CELSIUS)
115                     : new QuantityType<>(((DecimalType) command), SIUnits.CELSIUS);
116
117             if (temperatureInDegreesCelcius != null) {
118                 byte temperature = convertToTwoComplementByte(temperatureInDegreesCelcius.doubleValue(),
119                         THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
120
121                 VelbusSetTemperaturePacket packet = new VelbusSetTemperaturePacket(getModuleAddress().getAddress(),
122                         temperatureVariable, temperature);
123
124                 byte[] packetBytes = packet.getBytes();
125                 velbusBridgeHandler.sendPacket(packetBytes);
126             }
127         } else if (channelUID.equals(operatingModeChannel) && command instanceof StringType stringCommand) {
128             byte commandByte = stringCommand.equals(OPERATING_MODE_HEATING) ? COMMAND_SET_HEATING_MODE
129                     : COMMAND_SET_COOLING_MODE;
130
131             VelbusThermostatOperatingModePacket packet = new VelbusThermostatOperatingModePacket(
132                     getModuleAddress().getAddress(), commandByte);
133
134             byte[] packetBytes = packet.getBytes();
135             velbusBridgeHandler.sendPacket(packetBytes);
136         } else if (channelUID.equals(modeChannel) && command instanceof StringType stringCommand) {
137             byte commandByte = COMMAND_SWITCH_TO_SAFE_MODE;
138             if (stringCommand.equals(MODE_COMFORT)) {
139                 commandByte = COMMAND_SWITCH_TO_COMFORT_MODE;
140             } else if (stringCommand.equals(MODE_DAY)) {
141                 commandByte = COMMAND_SWITCH_TO_DAY_MODE;
142             } else if (stringCommand.equals(MODE_NIGHT)) {
143                 commandByte = COMMAND_SWITCH_TO_NIGHT_MODE;
144             }
145
146             VelbusThermostatModePacket packet = new VelbusThermostatModePacket(getModuleAddress().getAddress(),
147                     commandByte);
148
149             byte[] packetBytes = packet.getBytes();
150             velbusBridgeHandler.sendPacket(packetBytes);
151         } else {
152             logger.debug("The command '{}' is not supported by this handler.", command.getClass());
153         }
154     }
155
156     @Override
157     public void onPacketReceived(byte[] packet) {
158         super.onPacketReceived(packet);
159
160         logger.trace("onPacketReceived() was called");
161
162         if (packet[0] == VelbusPacket.STX && packet.length >= 5) {
163             byte address = packet[2];
164             byte command = packet[4];
165
166             if (command == COMMAND_TEMP_SENSOR_SETTINGS_PART1 && packet.length >= 9) {
167                 byte currentTemperatureSetByte = packet[5];
168                 byte heatingModeComfortTemperatureSetByte = packet[6];
169                 byte heatingModeDayTemperatureSetByte = packet[7];
170                 byte heatingModeNightTemperatureSetByte = packet[8];
171                 byte heatingModeAntiFrostTemperatureSetByte = packet[9];
172
173                 double currentTemperatureSet = convertFromTwoComplementByte(currentTemperatureSetByte,
174                         THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
175                 double heatingModeComfortTemperatureSet = convertFromTwoComplementByte(
176                         heatingModeComfortTemperatureSetByte, THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
177                 double heatingModeDayTemperatureSet = convertFromTwoComplementByte(heatingModeDayTemperatureSetByte,
178                         THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
179                 double heatingModeNightTemperatureSet = convertFromTwoComplementByte(heatingModeNightTemperatureSetByte,
180                         THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
181                 double heatingModeAntiFrostTemperatureSet = convertFromTwoComplementByte(
182                         heatingModeAntiFrostTemperatureSetByte, THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
183
184                 updateState(currentTemperatureSetpointChannel,
185                         new QuantityType<>(currentTemperatureSet, SIUnits.CELSIUS));
186                 updateState(heatingModeComfortTemperatureSetpointChannel,
187                         new QuantityType<>(heatingModeComfortTemperatureSet, SIUnits.CELSIUS));
188                 updateState(heatingModeDayTemperatureSetpointChannel,
189                         new QuantityType<>(heatingModeDayTemperatureSet, SIUnits.CELSIUS));
190                 updateState(heatingModeNightTemperatureSetpointChannel,
191                         new QuantityType<>(heatingModeNightTemperatureSet, SIUnits.CELSIUS));
192                 updateState(heatingModeAntifrostTemperatureSetpointChannel,
193                         new QuantityType<>(heatingModeAntiFrostTemperatureSet, SIUnits.CELSIUS));
194             } else if (command == COMMAND_TEMP_SENSOR_SETTINGS_PART2 && packet.length >= 8) {
195                 byte coolingModeComfortTemperatureSetByte = packet[5];
196                 byte coolingModeDayTemperatureSetByte = packet[6];
197                 byte coolingModeNightTemperatureSetByte = packet[7];
198                 byte coolingModeSafeTemperatureSetByte = packet[8];
199
200                 double coolingModeComfortTemperatureSet = convertFromTwoComplementByte(
201                         coolingModeComfortTemperatureSetByte, THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
202                 double coolingModeDayTemperatureSet = convertFromTwoComplementByte(coolingModeDayTemperatureSetByte,
203                         THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
204                 double coolingModeNightTemperatureSet = convertFromTwoComplementByte(coolingModeNightTemperatureSetByte,
205                         THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
206                 double coolingModeSafeTemperatureSet = convertFromTwoComplementByte(coolingModeSafeTemperatureSetByte,
207                         THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
208
209                 updateState(coolingModeComfortTemperatureSetpointChannel,
210                         new QuantityType<>(coolingModeComfortTemperatureSet, SIUnits.CELSIUS));
211                 updateState(coolingModeDayTemperatureSetpointChannel,
212                         new QuantityType<>(coolingModeDayTemperatureSet, SIUnits.CELSIUS));
213                 updateState(coolingModeNightTemperatureSetpointChannel,
214                         new QuantityType<>(coolingModeNightTemperatureSet, SIUnits.CELSIUS));
215                 updateState(coolingModeSafeTemperatureSetpointChannel,
216                         new QuantityType<>(coolingModeSafeTemperatureSet, SIUnits.CELSIUS));
217             } else if (command == COMMAND_TEMP_SENSOR_STATUS && packet.length >= 9) {
218                 byte operatingMode = packet[5];
219                 byte targetTemperature = packet[9];
220
221                 if ((operatingMode & OPERATING_MODE_MASK) == COOLING_MODE_MASK) {
222                     updateState(operatingModeChannel, OPERATING_MODE_COOLING);
223                 } else {
224                     updateState(operatingModeChannel, OPERATING_MODE_HEATING);
225                 }
226
227                 if ((operatingMode & MODE_MASK) == COMFORT_MODE_MASK) {
228                     updateState(modeChannel, MODE_COMFORT);
229                 } else if ((operatingMode & MODE_MASK) == DAY_MODE_MASK) {
230                     updateState(modeChannel, MODE_DAY);
231                 } else if ((operatingMode & MODE_MASK) == NIGHT_MODE_MASK) {
232                     updateState(modeChannel, MODE_NIGHT);
233                 } else {
234                     updateState(modeChannel, MODE_SAFE);
235                 }
236
237                 double targetTemperatureValue = convertFromTwoComplementByte(targetTemperature,
238                         THERMOSTAT_TEMPERATURE_SETPOINT_RESOLUTION);
239                 updateState(currentTemperatureSetpointChannel,
240                         new QuantityType<>(targetTemperatureValue, SIUnits.CELSIUS));
241             } else if (address != this.getModuleAddress().getAddress() && command == COMMAND_PUSH_BUTTON_STATUS) {
242                 byte outputChannelsJustActivated = packet[5];
243                 byte outputChannelsJustDeactivated = packet[6];
244
245                 triggerThermostatChannels(outputChannelsJustActivated, CommonTriggerEvents.PRESSED);
246                 triggerThermostatChannels(outputChannelsJustDeactivated, CommonTriggerEvents.RELEASED);
247             }
248         }
249     }
250
251     private void triggerThermostatChannels(byte outputChannels, String event) {
252         if ((outputChannels & 0x01) == 0x01) {
253             triggerChannel(heaterChannel, event);
254         }
255         if ((outputChannels & 0x02) == 0x02) {
256             triggerChannel(boostChannel, event);
257         }
258         if ((outputChannels & 0x04) == 0x04) {
259             triggerChannel(pumpChannel, event);
260         }
261         if ((outputChannels & 0x08) == 0x08) {
262             triggerChannel(coolerChannel, event);
263         }
264         if ((outputChannels & 0x10) == 0x10) {
265             triggerChannel(alarm1Channel, event);
266         }
267         if ((outputChannels & 0x20) == 0x20) {
268             triggerChannel(alarm2Channel, event);
269         }
270         if ((outputChannels & 0x40) == 0x40) {
271             triggerChannel(alarm3Channel, event);
272         }
273         if ((outputChannels & 0x80) == 0x80) {
274             triggerChannel(alarm4Channel, event);
275         }
276     }
277
278     protected boolean isThermostatChannel(ChannelUID channelUID) {
279         return channelUID.equals(currentTemperatureSetpointChannel)
280                 || channelUID.equals(heatingModeComfortTemperatureSetpointChannel)
281                 || channelUID.equals(heatingModeDayTemperatureSetpointChannel)
282                 || channelUID.equals(heatingModeNightTemperatureSetpointChannel)
283                 || channelUID.equals(heatingModeAntifrostTemperatureSetpointChannel)
284                 || channelUID.equals(coolingModeComfortTemperatureSetpointChannel)
285                 || channelUID.equals(coolingModeDayTemperatureSetpointChannel)
286                 || channelUID.equals(coolingModeNightTemperatureSetpointChannel)
287                 || channelUID.equals(coolingModeSafeTemperatureSetpointChannel)
288                 || channelUID.equals(operatingModeChannel) || channelUID.equals(modeChannel);
289     }
290
291     protected byte determineTemperatureVariable(ChannelUID channelUID) {
292         if (channelUID.equals(currentTemperatureSetpointChannel)) {
293             return 0x00;
294         } else if (channelUID.equals(heatingModeComfortTemperatureSetpointChannel)) {
295             return 0x01;
296         } else if (channelUID.equals(heatingModeDayTemperatureSetpointChannel)) {
297             return 0x02;
298         } else if (channelUID.equals(heatingModeNightTemperatureSetpointChannel)) {
299             return 0x03;
300         } else if (channelUID.equals(heatingModeAntifrostTemperatureSetpointChannel)) {
301             return 0x04;
302         } else if (channelUID.equals(coolingModeComfortTemperatureSetpointChannel)) {
303             return 0x07;
304         } else if (channelUID.equals(coolingModeDayTemperatureSetpointChannel)) {
305             return 0x08;
306         } else if (channelUID.equals(coolingModeNightTemperatureSetpointChannel)) {
307             return 0x09;
308         } else if (channelUID.equals(coolingModeSafeTemperatureSetpointChannel)) {
309             return 0x0A;
310         } else {
311             throw new IllegalArgumentException("The given channelUID is not a thermostat channel: " + channelUID);
312         }
313     }
314
315     protected double convertFromTwoComplementByte(byte value, double resolution) {
316         return ((value & 0x80) == 0x00) ? value * resolution : ((value & 0x7F) - 0x80) * resolution;
317     }
318
319     protected byte convertToTwoComplementByte(double value, double resolution) {
320         return (byte) (value / resolution);
321     }
322 }