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 java.util.Arrays;
18 import java.util.HashMap;
19 import java.util.HashSet;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.velbus.internal.VelbusClockAlarm;
25 import org.openhab.binding.velbus.internal.VelbusClockAlarmConfiguration;
26 import org.openhab.binding.velbus.internal.packets.VelbusPacket;
27 import org.openhab.binding.velbus.internal.packets.VelbusSetLocalClockAlarmPacket;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.ThingTypeUID;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
40 * The {@link VelbusSensorWithAlarmClockHandler} is responsible for handling commands, which are
41 * sent to one of the channels.
43 * @author Cedric Boon - Initial contribution
44 * @author Daniel Rosengarten - Add new module support, removes global alarm configuration from module (moved on
45 * bridge), reduces bus flooding on alarm value update
48 public class VelbusSensorWithAlarmClockHandler extends VelbusSensorHandler {
49 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_VMB2PBN,
50 THING_TYPE_VMB6PBN, THING_TYPE_VMB8PBU, THING_TYPE_VMBPIRC, THING_TYPE_VMBPIRM, THING_TYPE_VMBRFR8S,
51 THING_TYPE_VMBVP1, THING_TYPE_VMBKP, THING_TYPE_VMBIN, THING_TYPE_VMB4PB));
52 private static final HashMap<ThingTypeUID, Integer> ALARM_CONFIGURATION_MEMORY_ADDRESSES = new HashMap<ThingTypeUID, Integer>();
55 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB2PBN, 0x0093);
56 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB4AN, 0x0046);
57 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB6PBN, 0x0093);
58 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB7IN, 0x0093);
59 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB8PBU, 0x0093);
60 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBEL1, 0x0357);
61 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBEL2, 0x0357);
62 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBEL4, 0x0357);
63 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBELO, 0x0593);
64 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBELPIR, 0x030F);
65 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBPIRC, 0x0031);
66 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBPIRM, 0x0031);
67 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBPIRO, 0x0031);
68 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBMETEO, 0x0083);
69 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP1, 0x00A4);
70 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP1_2, 0x00A4);
71 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP2, 0x00A4);
72 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP2_2, 0x00A4);
73 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP4, 0x00A4);
74 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP4_2, 0x00A4);
75 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP4PIR, 0x00A4);
76 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP4PIR_2, 0x00A4);
77 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGPO, 0x0284);
78 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGPOD, 0x0284);
79 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGPOD_2, 0x0284);
80 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBRFR8S, 0x0093);
81 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBVP1, 0x002B);
82 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBKP, 0x00A7);
83 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBIN, 0x00A7);
84 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB4PB, 0x00A7);
87 private static final byte ALARM_CONFIGURATION_MEMORY_SIZE = 0x09;
88 private static final byte ALARM_1_ENABLED_MASK = 0x01;
89 private static final byte ALARM_1_TYPE_MASK = 0x02;
90 private static final byte ALARM_2_ENABLED_MASK = 0x04;
91 private static final byte ALARM_2_TYPE_MASK = 0x08;
93 private static final StringType ALARM_TYPE_LOCAL = new StringType("LOCAL");
94 private static final StringType ALARM_TYPE_GLOBAL = new StringType("GLOBAL");
96 private final ChannelUID clockAlarm1Enabled = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm1Enabled");
97 private final ChannelUID clockAlarm1Type = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm1Type");
98 private final ChannelUID clockAlarm1WakeupHour = new ChannelUID(thing.getUID(), "clockAlarm",
99 "clockAlarm1WakeupHour");
100 private final ChannelUID clockAlarm1WakeupMinute = new ChannelUID(thing.getUID(), "clockAlarm",
101 "clockAlarm1WakeupMinute");
102 private final ChannelUID clockAlarm1BedtimeHour = new ChannelUID(thing.getUID(), "clockAlarm",
103 "clockAlarm1BedtimeHour");
104 private final ChannelUID clockAlarm1BedtimeMinute = new ChannelUID(thing.getUID(), "clockAlarm",
105 "clockAlarm1BedtimeMinute");
106 private final ChannelUID clockAlarm2Enabled = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm2Enabled");
107 private final ChannelUID clockAlarm2Type = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm2Type");
108 private final ChannelUID clockAlarm2WakeupHour = new ChannelUID(thing.getUID(), "clockAlarm",
109 "clockAlarm2WakeupHour");
110 private final ChannelUID clockAlarm2WakeupMinute = new ChannelUID(thing.getUID(), "clockAlarm",
111 "clockAlarm2WakeupMinute");
112 private final ChannelUID clockAlarm2BedtimeHour = new ChannelUID(thing.getUID(), "clockAlarm",
113 "clockAlarm2BedtimeHour");
114 private final ChannelUID clockAlarm2BedtimeMinute = new ChannelUID(thing.getUID(), "clockAlarm",
115 "clockAlarm2BedtimeMinute");
117 private int clockAlarmConfigurationMemoryAddress;
118 private VelbusClockAlarmConfiguration alarmClockConfiguration = new VelbusClockAlarmConfiguration();
120 private long lastUpdateAlarm1TimeMillis;
121 private long lastUpdateAlarm2TimeMillis;
123 public VelbusSensorWithAlarmClockHandler(Thing thing) {
127 public VelbusSensorWithAlarmClockHandler(Thing thing, int numberOfSubAddresses) {
128 super(thing, numberOfSubAddresses);
132 public void initialize() {
135 if (ALARM_CONFIGURATION_MEMORY_ADDRESSES.containsKey(thing.getThingTypeUID())) {
136 this.clockAlarmConfigurationMemoryAddress = ALARM_CONFIGURATION_MEMORY_ADDRESSES
137 .get(thing.getThingTypeUID());
142 public void handleCommand(ChannelUID channelUID, Command command) {
143 super.handleCommand(channelUID, command);
145 VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
146 if (velbusBridgeHandler == null) {
147 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
151 if (isAlarmClockChannel(channelUID) && command instanceof RefreshType) {
152 sendReadMemoryBlockPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 0);
153 sendReadMemoryBlockPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 4);
154 sendReadMemoryPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 8);
155 } else if (isAlarmClockChannel(channelUID)) {
156 byte alarmNumber = determineAlarmNumber(channelUID);
157 VelbusClockAlarm alarmClock = alarmClockConfiguration.getAlarmClock(alarmNumber);
159 alarmClock.setLocal(true);
161 switch (channelUID.getId()) {
162 case CHANNEL_MODULE_CLOCK_ALARM1_TYPE:
163 case CHANNEL_MODULE_CLOCK_ALARM2_TYPE: {
164 if (command instanceof OnOffType) {
165 // If AlarmType is not read only, it's an old implementation of the module, warn user and
166 // discard the command
168 "Old implementation of thing '{}', still works, but it's better to remove and recreate the thing.",
169 getThing().getUID());
173 case CHANNEL_MODULE_CLOCK_ALARM1_ENABLED:
174 case CHANNEL_MODULE_CLOCK_ALARM2_ENABLED: {
175 if (command instanceof OnOffType) {
176 boolean enabled = command == OnOffType.ON;
177 alarmClock.setEnabled(enabled);
181 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_HOUR:
182 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_HOUR: {
183 if (command instanceof DecimalType decimalCommand) {
184 byte wakeupHour = decimalCommand.byteValue();
185 alarmClock.setWakeupHour(wakeupHour);
189 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_MINUTE:
190 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_MINUTE: {
191 if (command instanceof DecimalType decimalCommand) {
192 byte wakeupMinute = decimalCommand.byteValue();
193 alarmClock.setWakeupMinute(wakeupMinute);
197 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_HOUR:
198 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_HOUR: {
199 if (command instanceof DecimalType decimalCommand) {
200 byte bedTimeHour = decimalCommand.byteValue();
201 alarmClock.setBedtimeHour(bedTimeHour);
205 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_MINUTE:
206 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_MINUTE: {
207 if (command instanceof DecimalType decimalCommand) {
208 byte bedTimeMinute = decimalCommand.byteValue();
209 alarmClock.setBedtimeMinute(bedTimeMinute);
215 if (alarmNumber == 1) {
216 lastUpdateAlarm1TimeMillis = System.currentTimeMillis();
218 lastUpdateAlarm2TimeMillis = System.currentTimeMillis();
221 VelbusSetLocalClockAlarmPacket packet = new VelbusSetLocalClockAlarmPacket(getModuleAddress().getAddress(),
222 alarmNumber, alarmClock);
223 byte[] packetBytes = packet.getBytes();
225 // Schedule the send of the packet to see if there is another update in less than 10 secondes (reduce
226 // flooding of the bus)
227 scheduler.schedule(() -> {
228 sendAlarmPacket(alarmNumber, packetBytes);
229 }, DELAY_SEND_CLOCK_ALARM_UPDATE, TimeUnit.MILLISECONDS);
231 logger.debug("The command '{}' is not supported by this handler.", command.getClass());
235 public synchronized void sendAlarmPacket(int alarmNumber, byte[] packetBytes) {
236 VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
237 if (velbusBridgeHandler == null) {
238 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
242 long timeSinceLastUpdate;
244 if (alarmNumber == 1) {
245 timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateAlarm1TimeMillis;
247 timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateAlarm2TimeMillis;
250 // If a value of the alarm has been updated, discard this old update
251 if (timeSinceLastUpdate < DELAY_SEND_CLOCK_ALARM_UPDATE) {
255 velbusBridgeHandler.sendPacket(packetBytes);
259 public void onPacketReceived(byte[] packet) {
260 super.onPacketReceived(packet);
262 logger.trace("onPacketReceived() was called");
264 if (packet[0] == VelbusPacket.STX && packet.length >= 5) {
265 byte command = packet[4];
267 if ((command == COMMAND_MEMORY_DATA_BLOCK && packet.length >= 11)
268 || (command == COMMAND_MEMORY_DATA && packet.length >= 8)) {
269 byte highMemoryAddress = packet[5];
270 byte lowMemoryAddress = packet[6];
271 int memoryAddress = ((highMemoryAddress & 0xff) << 8) | (lowMemoryAddress & 0xff);
272 byte[] data = (command == COMMAND_MEMORY_DATA_BLOCK)
273 ? new byte[] { packet[7], packet[8], packet[9], packet[10] }
274 : new byte[] { packet[7] };
276 for (int i = 0; i < data.length; i++) {
278 if (isClockAlarmConfigurationByte(memoryAddress + i)) {
279 setClockAlarmConfigurationByte(memoryAddress + i, data[i]);
282 } else if (command == COMMAND_MODULE_STATUS) {
283 int clockAlarmAndProgramSelectionIndexInModuleStatus = this
284 .getClockAlarmAndProgramSelectionIndexInModuleStatus();
285 if (packet.length >= clockAlarmAndProgramSelectionIndexInModuleStatus + 1) {
286 byte alarmAndProgramSelection = packet[clockAlarmAndProgramSelectionIndexInModuleStatus];
288 boolean alarmClock1Enabled = (alarmAndProgramSelection & 0x04) > 0;
289 boolean alarmClock1IsLocal = (alarmAndProgramSelection & 0x08) == 0;
290 VelbusClockAlarm alarmClock1 = this.alarmClockConfiguration.getAlarmClock1();
291 alarmClock1.setEnabled(alarmClock1Enabled);
292 alarmClock1.setLocal(alarmClock1IsLocal);
293 updateState(clockAlarm1Enabled, OnOffType.from(alarmClock1.isEnabled()));
294 updateState(clockAlarm1Type, alarmClock1.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
296 boolean alarmClock2Enabled = (alarmAndProgramSelection & 0x10) > 0;
297 boolean alarmClock2IsLocal = (alarmAndProgramSelection & 0x20) == 0;
298 VelbusClockAlarm alarmClock2 = this.alarmClockConfiguration.getAlarmClock2();
299 alarmClock2.setEnabled(alarmClock2Enabled);
300 alarmClock2.setLocal(alarmClock2IsLocal);
301 updateState(clockAlarm2Enabled, OnOffType.from(alarmClock2.isEnabled()));
302 updateState(clockAlarm2Type, alarmClock2.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
308 public Boolean isClockAlarmConfigurationByte(int memoryAddress) {
309 return memoryAddress >= this.clockAlarmConfigurationMemoryAddress
310 && memoryAddress < (this.clockAlarmConfigurationMemoryAddress + ALARM_CONFIGURATION_MEMORY_SIZE);
313 public void setClockAlarmConfigurationByte(int memoryAddress, byte data) {
314 VelbusClockAlarm alarmClock1 = this.alarmClockConfiguration.getAlarmClock1();
315 VelbusClockAlarm alarmClock2 = this.alarmClockConfiguration.getAlarmClock2();
317 switch (memoryAddress - this.clockAlarmConfigurationMemoryAddress) {
319 alarmClock1.setEnabled((data & ALARM_1_ENABLED_MASK) > 0);
320 alarmClock1.setLocal((data & ALARM_1_TYPE_MASK) > 0);
322 updateState(clockAlarm1Enabled, OnOffType.from(alarmClock1.isEnabled()));
323 updateState(clockAlarm1Type, alarmClock1.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
325 alarmClock2.setEnabled((data & ALARM_2_ENABLED_MASK) > 0);
326 alarmClock2.setLocal((data & ALARM_2_TYPE_MASK) > 0);
328 updateState(clockAlarm2Enabled, OnOffType.from(alarmClock2.isEnabled()));
329 updateState(clockAlarm2Type, alarmClock2.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
332 alarmClock1.setWakeupHour(data);
333 updateState(clockAlarm1WakeupHour, new DecimalType(alarmClock1.getWakeupHour()));
336 alarmClock1.setWakeupMinute(data);
337 updateState(clockAlarm1WakeupMinute, new DecimalType(alarmClock1.getWakeupMinute()));
340 alarmClock1.setBedtimeHour(data);
341 updateState(clockAlarm1BedtimeHour, new DecimalType(alarmClock1.getBedtimeHour()));
344 alarmClock1.setBedtimeMinute(data);
345 updateState(clockAlarm1BedtimeMinute, new DecimalType(alarmClock1.getBedtimeMinute()));
348 alarmClock2.setWakeupHour(data);
349 updateState(clockAlarm2WakeupHour, new DecimalType(alarmClock2.getWakeupHour()));
352 alarmClock2.setWakeupMinute(data);
353 updateState(clockAlarm2WakeupMinute, new DecimalType(alarmClock2.getWakeupMinute()));
356 alarmClock2.setBedtimeHour(data);
357 updateState(clockAlarm2BedtimeHour, new DecimalType(alarmClock2.getBedtimeHour()));
360 alarmClock2.setBedtimeMinute(data);
361 updateState(clockAlarm2BedtimeMinute, new DecimalType(alarmClock2.getBedtimeMinute()));
364 throw new IllegalArgumentException("The memory address '" + memoryAddress
365 + "' does not represent a clock alarm configuration for the thing '" + this.thing.getUID()
370 protected boolean isAlarmClockChannel(ChannelUID channelUID) {
371 switch (channelUID.getId()) {
372 case CHANNEL_MODULE_CLOCK_ALARM1_ENABLED:
373 case CHANNEL_MODULE_CLOCK_ALARM1_TYPE:
374 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_HOUR:
375 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_MINUTE:
376 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_HOUR:
377 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_MINUTE:
378 case CHANNEL_MODULE_CLOCK_ALARM2_ENABLED:
379 case CHANNEL_MODULE_CLOCK_ALARM2_TYPE:
380 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_HOUR:
381 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_MINUTE:
382 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_HOUR:
383 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_MINUTE:
389 protected byte determineAlarmNumber(ChannelUID channelUID) {
390 switch (channelUID.getId()) {
391 case CHANNEL_MODULE_CLOCK_ALARM1_ENABLED:
392 case CHANNEL_MODULE_CLOCK_ALARM1_TYPE:
393 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_HOUR:
394 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_MINUTE:
395 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_HOUR:
396 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_MINUTE:
398 case CHANNEL_MODULE_CLOCK_ALARM2_ENABLED:
399 case CHANNEL_MODULE_CLOCK_ALARM2_TYPE:
400 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_HOUR:
401 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_MINUTE:
402 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_HOUR:
403 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_MINUTE:
407 throw new IllegalArgumentException("The given channelUID is not an alarm clock channel: " + channelUID);
410 protected int getClockAlarmAndProgramSelectionIndexInModuleStatus() {