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.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 VMBELPIR support, removes global alarm configuration from module (moved on bridge),
45 * reduces bus flooding on alarm value update
48 public class VelbusSensorWithAlarmClockHandler extends VelbusSensorHandler {
49 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(
50 Arrays.asList(THING_TYPE_VMB2PBN, THING_TYPE_VMB6PBN, THING_TYPE_VMB8PBU, THING_TYPE_VMBPIRC,
51 THING_TYPE_VMBPIRM, THING_TYPE_VMBRFR8S, THING_TYPE_VMBVP1));
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);
84 private static final byte ALARM_CONFIGURATION_MEMORY_SIZE = 0x09;
85 private static final byte ALARM_1_ENABLED_MASK = 0x01;
86 private static final byte ALARM_1_TYPE_MASK = 0x02;
87 private static final byte ALARM_2_ENABLED_MASK = 0x04;
88 private static final byte ALARM_2_TYPE_MASK = 0x08;
90 private static final StringType ALARM_TYPE_LOCAL = new StringType("LOCAL");
91 private static final StringType ALARM_TYPE_GLOBAL = new StringType("GLOBAL");
93 private final ChannelUID clockAlarm1Enabled = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm1Enabled");
94 private final ChannelUID clockAlarm1Type = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm1Type");
95 private final ChannelUID clockAlarm1WakeupHour = new ChannelUID(thing.getUID(), "clockAlarm",
96 "clockAlarm1WakeupHour");
97 private final ChannelUID clockAlarm1WakeupMinute = new ChannelUID(thing.getUID(), "clockAlarm",
98 "clockAlarm1WakeupMinute");
99 private final ChannelUID clockAlarm1BedtimeHour = new ChannelUID(thing.getUID(), "clockAlarm",
100 "clockAlarm1BedtimeHour");
101 private final ChannelUID clockAlarm1BedtimeMinute = new ChannelUID(thing.getUID(), "clockAlarm",
102 "clockAlarm1BedtimeMinute");
103 private final ChannelUID clockAlarm2Enabled = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm2Enabled");
104 private final ChannelUID clockAlarm2Type = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm2Type");
105 private final ChannelUID clockAlarm2WakeupHour = new ChannelUID(thing.getUID(), "clockAlarm",
106 "clockAlarm2WakeupHour");
107 private final ChannelUID clockAlarm2WakeupMinute = new ChannelUID(thing.getUID(), "clockAlarm",
108 "clockAlarm2WakeupMinute");
109 private final ChannelUID clockAlarm2BedtimeHour = new ChannelUID(thing.getUID(), "clockAlarm",
110 "clockAlarm2BedtimeHour");
111 private final ChannelUID clockAlarm2BedtimeMinute = new ChannelUID(thing.getUID(), "clockAlarm",
112 "clockAlarm2BedtimeMinute");
114 private int clockAlarmConfigurationMemoryAddress;
115 private VelbusClockAlarmConfiguration alarmClockConfiguration = new VelbusClockAlarmConfiguration();
117 private long lastUpdateAlarm1TimeMillis;
118 private long lastUpdateAlarm2TimeMillis;
120 public VelbusSensorWithAlarmClockHandler(Thing thing) {
124 public VelbusSensorWithAlarmClockHandler(Thing thing, int numberOfSubAddresses) {
125 super(thing, numberOfSubAddresses);
129 public void initialize() {
132 if (ALARM_CONFIGURATION_MEMORY_ADDRESSES.containsKey(thing.getThingTypeUID())) {
133 this.clockAlarmConfigurationMemoryAddress = ALARM_CONFIGURATION_MEMORY_ADDRESSES
134 .get(thing.getThingTypeUID());
139 public void handleCommand(ChannelUID channelUID, Command command) {
140 super.handleCommand(channelUID, command);
142 VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
143 if (velbusBridgeHandler == null) {
144 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
148 if (isAlarmClockChannel(channelUID) && command instanceof RefreshType) {
149 sendReadMemoryBlockPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 0);
150 sendReadMemoryBlockPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 4);
151 sendReadMemoryPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 8);
152 } else if (isAlarmClockChannel(channelUID)) {
153 byte alarmNumber = determineAlarmNumber(channelUID);
154 VelbusClockAlarm alarmClock = alarmClockConfiguration.getAlarmClock(alarmNumber);
156 alarmClock.setLocal(true);
158 switch (channelUID.getId()) {
159 case CHANNEL_MODULE_CLOCK_ALARM1_TYPE:
160 case CHANNEL_MODULE_CLOCK_ALARM2_TYPE: {
161 if (command instanceof OnOffType) {
162 // If AlarmType is not read only, it's an old implementation of the module, warn user and
163 // discard the command
165 "Old implementation of thing '{}', still works, but it's better to remove and recreate the thing.",
166 getThing().getUID());
170 case CHANNEL_MODULE_CLOCK_ALARM1_ENABLED:
171 case CHANNEL_MODULE_CLOCK_ALARM2_ENABLED: {
172 if (command instanceof OnOffType) {
173 boolean enabled = command == OnOffType.ON;
174 alarmClock.setEnabled(enabled);
178 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_HOUR:
179 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_HOUR: {
180 if (command instanceof DecimalType) {
181 byte wakeupHour = ((DecimalType) command).byteValue();
182 alarmClock.setWakeupHour(wakeupHour);
186 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_MINUTE:
187 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_MINUTE: {
188 if (command instanceof DecimalType) {
189 byte wakeupMinute = ((DecimalType) command).byteValue();
190 alarmClock.setWakeupMinute(wakeupMinute);
194 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_HOUR:
195 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_HOUR: {
196 if (command instanceof DecimalType) {
197 byte bedTimeHour = ((DecimalType) command).byteValue();
198 alarmClock.setBedtimeHour(bedTimeHour);
202 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_MINUTE:
203 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_MINUTE: {
204 if (command instanceof DecimalType) {
205 byte bedTimeMinute = ((DecimalType) command).byteValue();
206 alarmClock.setBedtimeMinute(bedTimeMinute);
212 if (alarmNumber == 1) {
213 lastUpdateAlarm1TimeMillis = System.currentTimeMillis();
215 lastUpdateAlarm2TimeMillis = System.currentTimeMillis();
218 VelbusSetLocalClockAlarmPacket packet = new VelbusSetLocalClockAlarmPacket(getModuleAddress().getAddress(),
219 alarmNumber, alarmClock);
220 byte[] packetBytes = packet.getBytes();
222 // Schedule the send of the packet to see if there is another update in less than 10 secondes (reduce
223 // flooding of the bus)
224 scheduler.schedule(() -> {
225 sendAlarmPacket(alarmNumber, packetBytes);
226 }, DELAY_SEND_CLOCK_ALARM_UPDATE, TimeUnit.MILLISECONDS);
228 logger.debug("The command '{}' is not supported by this handler.", command.getClass());
232 public synchronized void sendAlarmPacket(int alarmNumber, byte[] packetBytes) {
233 VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
234 if (velbusBridgeHandler == null) {
235 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
239 long timeSinceLastUpdate;
241 if (alarmNumber == 1) {
242 timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateAlarm1TimeMillis;
244 timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateAlarm2TimeMillis;
247 // If a value of the alarm has been updated, discard this old update
248 if (timeSinceLastUpdate < DELAY_SEND_CLOCK_ALARM_UPDATE) {
252 velbusBridgeHandler.sendPacket(packetBytes);
256 public void onPacketReceived(byte[] packet) {
257 super.onPacketReceived(packet);
259 logger.trace("onPacketReceived() was called");
261 if (packet[0] == VelbusPacket.STX && packet.length >= 5) {
262 byte command = packet[4];
264 if ((command == COMMAND_MEMORY_DATA_BLOCK && packet.length >= 11)
265 || (command == COMMAND_MEMORY_DATA && packet.length >= 8)) {
266 byte highMemoryAddress = packet[5];
267 byte lowMemoryAddress = packet[6];
268 int memoryAddress = ((highMemoryAddress & 0xff) << 8) | (lowMemoryAddress & 0xff);
269 byte[] data = (command == COMMAND_MEMORY_DATA_BLOCK)
270 ? new byte[] { packet[7], packet[8], packet[9], packet[10] }
271 : new byte[] { packet[7] };
273 for (int i = 0; i < data.length; i++) {
275 if (isClockAlarmConfigurationByte(memoryAddress + i)) {
276 setClockAlarmConfigurationByte(memoryAddress + i, data[i]);
279 } else if (command == COMMAND_MODULE_STATUS) {
280 int clockAlarmAndProgramSelectionIndexInModuleStatus = this
281 .getClockAlarmAndProgramSelectionIndexInModuleStatus();
282 if (packet.length >= clockAlarmAndProgramSelectionIndexInModuleStatus + 1) {
283 byte alarmAndProgramSelection = packet[clockAlarmAndProgramSelectionIndexInModuleStatus];
285 boolean alarmClock1Enabled = (alarmAndProgramSelection & 0x04) > 0;
286 boolean alarmClock1IsLocal = (alarmAndProgramSelection & 0x08) == 0;
287 VelbusClockAlarm alarmClock1 = this.alarmClockConfiguration.getAlarmClock1();
288 alarmClock1.setEnabled(alarmClock1Enabled);
289 alarmClock1.setLocal(alarmClock1IsLocal);
290 updateState(clockAlarm1Enabled, alarmClock1.isEnabled() ? OnOffType.ON : OnOffType.OFF);
291 updateState(clockAlarm1Type, alarmClock1.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
293 boolean alarmClock2Enabled = (alarmAndProgramSelection & 0x10) > 0;
294 boolean alarmClock2IsLocal = (alarmAndProgramSelection & 0x20) == 0;
295 VelbusClockAlarm alarmClock2 = this.alarmClockConfiguration.getAlarmClock2();
296 alarmClock2.setEnabled(alarmClock2Enabled);
297 alarmClock2.setLocal(alarmClock2IsLocal);
298 updateState(clockAlarm2Enabled, alarmClock2.isEnabled() ? OnOffType.ON : OnOffType.OFF);
299 updateState(clockAlarm2Type, alarmClock2.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
305 public Boolean isClockAlarmConfigurationByte(int memoryAddress) {
306 return memoryAddress >= this.clockAlarmConfigurationMemoryAddress
307 && memoryAddress < (this.clockAlarmConfigurationMemoryAddress + ALARM_CONFIGURATION_MEMORY_SIZE);
310 public void setClockAlarmConfigurationByte(int memoryAddress, byte data) {
311 VelbusClockAlarm alarmClock1 = this.alarmClockConfiguration.getAlarmClock1();
312 VelbusClockAlarm alarmClock2 = this.alarmClockConfiguration.getAlarmClock2();
314 switch (memoryAddress - this.clockAlarmConfigurationMemoryAddress) {
316 alarmClock1.setEnabled((data & ALARM_1_ENABLED_MASK) > 0);
317 alarmClock1.setLocal((data & ALARM_1_TYPE_MASK) > 0);
319 updateState(clockAlarm1Enabled, alarmClock1.isEnabled() ? OnOffType.ON : OnOffType.OFF);
320 updateState(clockAlarm1Type, alarmClock1.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
322 alarmClock2.setEnabled((data & ALARM_2_ENABLED_MASK) > 0);
323 alarmClock2.setLocal((data & ALARM_2_TYPE_MASK) > 0);
325 updateState(clockAlarm2Enabled, alarmClock2.isEnabled() ? OnOffType.ON : OnOffType.OFF);
326 updateState(clockAlarm2Type, alarmClock2.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
329 alarmClock1.setWakeupHour(data);
330 updateState(clockAlarm1WakeupHour, new DecimalType(alarmClock1.getWakeupHour()));
333 alarmClock1.setWakeupMinute(data);
334 updateState(clockAlarm1WakeupMinute, new DecimalType(alarmClock1.getWakeupMinute()));
337 alarmClock1.setBedtimeHour(data);
338 updateState(clockAlarm1BedtimeHour, new DecimalType(alarmClock1.getBedtimeHour()));
341 alarmClock1.setBedtimeMinute(data);
342 updateState(clockAlarm1BedtimeMinute, new DecimalType(alarmClock1.getBedtimeMinute()));
345 alarmClock2.setWakeupHour(data);
346 updateState(clockAlarm2WakeupHour, new DecimalType(alarmClock2.getWakeupHour()));
349 alarmClock2.setWakeupMinute(data);
350 updateState(clockAlarm2WakeupMinute, new DecimalType(alarmClock2.getWakeupMinute()));
353 alarmClock2.setBedtimeHour(data);
354 updateState(clockAlarm2BedtimeHour, new DecimalType(alarmClock2.getBedtimeHour()));
357 alarmClock2.setBedtimeMinute(data);
358 updateState(clockAlarm2BedtimeMinute, new DecimalType(alarmClock2.getBedtimeMinute()));
361 throw new IllegalArgumentException("The memory address '" + memoryAddress
362 + "' does not represent a clock alarm configuration for the thing '" + this.thing.getUID()
367 protected boolean isAlarmClockChannel(ChannelUID channelUID) {
368 switch (channelUID.getId()) {
369 case CHANNEL_MODULE_CLOCK_ALARM1_ENABLED:
370 case CHANNEL_MODULE_CLOCK_ALARM1_TYPE:
371 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_HOUR:
372 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_MINUTE:
373 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_HOUR:
374 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_MINUTE:
375 case CHANNEL_MODULE_CLOCK_ALARM2_ENABLED:
376 case CHANNEL_MODULE_CLOCK_ALARM2_TYPE:
377 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_HOUR:
378 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_MINUTE:
379 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_HOUR:
380 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_MINUTE:
386 protected byte determineAlarmNumber(ChannelUID channelUID) {
387 switch (channelUID.getId()) {
388 case CHANNEL_MODULE_CLOCK_ALARM1_ENABLED:
389 case CHANNEL_MODULE_CLOCK_ALARM1_TYPE:
390 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_HOUR:
391 case CHANNEL_MODULE_CLOCK_ALARM1_WAKEUP_MINUTE:
392 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_HOUR:
393 case CHANNEL_MODULE_CLOCK_ALARM1_BEDTIME_MINUTE:
395 case CHANNEL_MODULE_CLOCK_ALARM2_ENABLED:
396 case CHANNEL_MODULE_CLOCK_ALARM2_TYPE:
397 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_HOUR:
398 case CHANNEL_MODULE_CLOCK_ALARM2_WAKEUP_MINUTE:
399 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_HOUR:
400 case CHANNEL_MODULE_CLOCK_ALARM2_BEDTIME_MINUTE:
404 throw new IllegalArgumentException("The given channelUID is not an alarm clock channel: " + channelUID);
407 protected int getClockAlarmAndProgramSelectionIndexInModuleStatus() {