2 * Copyright (c) 2010-2021 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;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.velbus.internal.VelbusClockAlarm;
24 import org.openhab.binding.velbus.internal.VelbusClockAlarmConfiguration;
25 import org.openhab.binding.velbus.internal.packets.VelbusPacket;
26 import org.openhab.binding.velbus.internal.packets.VelbusSetLocalClockAlarmPacket;
27 import org.openhab.core.library.types.DecimalType;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.StringType;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.RefreshType;
39 * The {@link VelbusSensorWithAlarmClockHandler} is responsible for handling commands, which are
40 * sent to one of the channels.
42 * @author Cedric Boon - Initial contribution
45 public class VelbusSensorWithAlarmClockHandler extends VelbusSensorHandler {
46 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_VMB2PBN,
47 THING_TYPE_VMB6PBN, THING_TYPE_VMB8PBU, THING_TYPE_VMBPIRC, THING_TYPE_VMBPIRM, THING_TYPE_VMBRFR8S));
48 private static final HashMap<ThingTypeUID, Integer> ALARM_CONFIGURATION_MEMORY_ADDRESSES = new HashMap<ThingTypeUID, Integer>();
51 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB2PBN, 0x0093);
52 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB4AN, 0x0046);
53 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB6PBN, 0x0093);
54 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB7IN, 0x0093);
55 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMB8PBU, 0x0093);
56 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBEL1, 0x0357);
57 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBEL2, 0x0357);
58 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBEL4, 0x0357);
59 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBELO, 0x0593);
60 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBPIRC, 0x0031);
61 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBPIRM, 0x0031);
62 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBPIRO, 0x0031);
63 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBMETEO, 0x0083);
64 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP1, 0x00A4);
65 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP1_2, 0x00A4);
66 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP2, 0x00A4);
67 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP2_2, 0x00A4);
68 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP4, 0x00A4);
69 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP4_2, 0x00A4);
70 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP4PIR, 0x00A4);
71 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGP4PIR_2, 0x00A4);
72 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGPO, 0x0284);
73 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGPOD, 0x0284);
74 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBGPOD_2, 0x0284);
75 ALARM_CONFIGURATION_MEMORY_ADDRESSES.put(THING_TYPE_VMBRFR8S, 0x0093);
78 private static final byte ALARM_CONFIGURATION_MEMORY_SIZE = 0x09;
79 private static final byte ALARM_1_ENABLED_MASK = 0x01;
80 private static final byte ALARM_1_TYPE_MASK = 0x02;
81 private static final byte ALARM_2_ENABLED_MASK = 0x04;
82 private static final byte ALARM_2_TYPE_MASK = 0x08;
84 private static final StringType ALARM_TYPE_LOCAL = new StringType("LOCAL");
85 private static final StringType ALARM_TYPE_GLOBAL = new StringType("GLOBAL");
87 private final ChannelUID clockAlarm1Enabled = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm1Enabled");
88 private final ChannelUID clockAlarm1Type = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm1Type");
89 private final ChannelUID clockAlarm1WakeupHour = new ChannelUID(thing.getUID(), "clockAlarm",
90 "clockAlarm1WakeupHour");
91 private final ChannelUID clockAlarm1WakeupMinute = new ChannelUID(thing.getUID(), "clockAlarm",
92 "clockAlarm1WakeupMinute");
93 private final ChannelUID clockAlarm1BedtimeHour = new ChannelUID(thing.getUID(), "clockAlarm",
94 "clockAlarm1BedtimeHour");
95 private final ChannelUID clockAlarm1BedtimeMinute = new ChannelUID(thing.getUID(), "clockAlarm",
96 "clockAlarm1BedtimeMinute");
97 private final ChannelUID clockAlarm2Enabled = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm2Enabled");
98 private final ChannelUID clockAlarm2Type = new ChannelUID(thing.getUID(), "clockAlarm", "clockAlarm2Type");
99 private final ChannelUID clockAlarm2WakeupHour = new ChannelUID(thing.getUID(), "clockAlarm",
100 "clockAlarm2WakeupHour");
101 private final ChannelUID clockAlarm2WakeupMinute = new ChannelUID(thing.getUID(), "clockAlarm",
102 "clockAlarm2WakeupMinute");
103 private final ChannelUID clockAlarm2BedtimeHour = new ChannelUID(thing.getUID(), "clockAlarm",
104 "clockAlarm2BedtimeHour");
105 private final ChannelUID clockAlarm2BedtimeMinute = new ChannelUID(thing.getUID(), "clockAlarm",
106 "clockAlarm2BedtimeMinute");
108 private int clockAlarmConfigurationMemoryAddress;
109 private VelbusClockAlarmConfiguration alarmClockConfiguration = new VelbusClockAlarmConfiguration();
111 public VelbusSensorWithAlarmClockHandler(Thing thing) {
115 public VelbusSensorWithAlarmClockHandler(Thing thing, int numberOfSubAddresses) {
116 super(thing, numberOfSubAddresses);
120 public void initialize() {
123 if (ALARM_CONFIGURATION_MEMORY_ADDRESSES.containsKey(thing.getThingTypeUID())) {
124 this.clockAlarmConfigurationMemoryAddress = ALARM_CONFIGURATION_MEMORY_ADDRESSES
125 .get(thing.getThingTypeUID());
130 public void handleCommand(ChannelUID channelUID, Command command) {
131 super.handleCommand(channelUID, command);
133 VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
134 if (velbusBridgeHandler == null) {
135 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
139 if (isAlarmClockChannel(channelUID) && command instanceof RefreshType) {
140 sendReadMemoryBlockPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 0);
141 sendReadMemoryBlockPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 4);
142 sendReadMemoryPacket(velbusBridgeHandler, this.clockAlarmConfigurationMemoryAddress + 8);
143 } else if (isAlarmClockChannel(channelUID)) {
144 byte alarmNumber = determineAlarmNumber(channelUID);
145 VelbusClockAlarm alarmClock = alarmClockConfiguration.getAlarmClock(alarmNumber);
147 if ((channelUID.equals(clockAlarm1Enabled) || channelUID.equals(clockAlarm2Enabled))
148 && command instanceof OnOffType) {
149 boolean enabled = command == OnOffType.ON;
150 alarmClock.setEnabled(enabled);
151 } else if ((channelUID.equals(clockAlarm1Type) || channelUID.equals(clockAlarm2Type))
152 && command instanceof StringType) {
153 boolean isLocal = ((StringType) command).equals(ALARM_TYPE_LOCAL);
154 alarmClock.setLocal(isLocal);
155 } else if (channelUID.equals(clockAlarm1WakeupHour)
156 || channelUID.equals(clockAlarm2WakeupHour) && command instanceof DecimalType) {
157 byte wakeupHour = ((DecimalType) command).byteValue();
158 alarmClock.setWakeupHour(wakeupHour);
159 } else if (channelUID.equals(clockAlarm1WakeupMinute)
160 || channelUID.equals(clockAlarm2WakeupMinute) && command instanceof DecimalType) {
161 byte wakeupMinute = ((DecimalType) command).byteValue();
162 alarmClock.setWakeupMinute(wakeupMinute);
163 } else if (channelUID.equals(clockAlarm1BedtimeHour)
164 || channelUID.equals(clockAlarm2BedtimeHour) && command instanceof DecimalType) {
165 byte bedTimeHour = ((DecimalType) command).byteValue();
166 alarmClock.setBedtimeHour(bedTimeHour);
167 } else if (channelUID.equals(clockAlarm1BedtimeMinute)
168 || channelUID.equals(clockAlarm2BedtimeMinute) && command instanceof DecimalType) {
169 byte bedTimeMinute = ((DecimalType) command).byteValue();
170 alarmClock.setBedtimeMinute(bedTimeMinute);
173 byte address = alarmClock.isLocal() ? getModuleAddress().getAddress() : 0x00;
174 VelbusSetLocalClockAlarmPacket packet = new VelbusSetLocalClockAlarmPacket(address, alarmNumber,
176 byte[] packetBytes = packet.getBytes();
177 velbusBridgeHandler.sendPacket(packetBytes);
179 logger.debug("The command '{}' is not supported by this handler.", command.getClass());
184 public void onPacketReceived(byte[] packet) {
185 super.onPacketReceived(packet);
187 logger.trace("onPacketReceived() was called");
189 if (packet[0] == VelbusPacket.STX && packet.length >= 5) {
190 byte command = packet[4];
192 if ((command == COMMAND_MEMORY_DATA_BLOCK && packet.length >= 11)
193 || (command == COMMAND_MEMORY_DATA && packet.length >= 8)) {
194 byte highMemoryAddress = packet[5];
195 byte lowMemoryAddress = packet[6];
196 int memoryAddress = ((highMemoryAddress & 0xff) << 8) | (lowMemoryAddress & 0xff);
197 byte[] data = (command == COMMAND_MEMORY_DATA_BLOCK)
198 ? new byte[] { packet[7], packet[8], packet[9], packet[10] }
199 : new byte[] { packet[7] };
201 for (int i = 0; i < data.length; i++) {
203 if (isClockAlarmConfigurationByte(memoryAddress + i)) {
204 setClockAlarmConfigurationByte(memoryAddress + i, data[i]);
207 } else if (command == COMMAND_MODULE_STATUS) {
208 int clockAlarmAndProgramSelectionIndexInModuleStatus = this
209 .getClockAlarmAndProgramSelectionIndexInModuleStatus();
210 if (packet.length >= clockAlarmAndProgramSelectionIndexInModuleStatus + 1) {
211 byte alarmAndProgramSelection = packet[clockAlarmAndProgramSelectionIndexInModuleStatus];
213 boolean alarmClock1Enabled = (alarmAndProgramSelection & 0x04) > 0;
214 boolean alarmClock1IsLocal = (alarmAndProgramSelection & 0x08) == 0;
215 VelbusClockAlarm alarmClock1 = this.alarmClockConfiguration.getAlarmClock1();
216 alarmClock1.setEnabled(alarmClock1Enabled);
217 alarmClock1.setLocal(alarmClock1IsLocal);
218 updateState(clockAlarm1Enabled, alarmClock1.isEnabled() ? OnOffType.ON : OnOffType.OFF);
219 updateState(clockAlarm1Type, alarmClock1.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
221 boolean alarmClock2Enabled = (alarmAndProgramSelection & 0x10) > 0;
222 boolean alarmClock2IsLocal = (alarmAndProgramSelection & 0x20) == 0;
223 VelbusClockAlarm alarmClock2 = this.alarmClockConfiguration.getAlarmClock2();
224 alarmClock2.setEnabled(alarmClock2Enabled);
225 alarmClock2.setLocal(alarmClock2IsLocal);
226 updateState(clockAlarm2Enabled, alarmClock2.isEnabled() ? OnOffType.ON : OnOffType.OFF);
227 updateState(clockAlarm2Type, alarmClock2.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
233 public Boolean isClockAlarmConfigurationByte(int memoryAddress) {
234 return memoryAddress >= this.clockAlarmConfigurationMemoryAddress
235 && memoryAddress < (this.clockAlarmConfigurationMemoryAddress + ALARM_CONFIGURATION_MEMORY_SIZE);
238 public void setClockAlarmConfigurationByte(int memoryAddress, byte data) {
239 VelbusClockAlarm alarmClock1 = this.alarmClockConfiguration.getAlarmClock1();
240 VelbusClockAlarm alarmClock2 = this.alarmClockConfiguration.getAlarmClock2();
242 switch (memoryAddress - this.clockAlarmConfigurationMemoryAddress) {
244 alarmClock1.setEnabled((data & ALARM_1_ENABLED_MASK) > 0);
245 alarmClock1.setLocal((data & ALARM_1_TYPE_MASK) > 0);
247 updateState(clockAlarm1Enabled, alarmClock1.isEnabled() ? OnOffType.ON : OnOffType.OFF);
248 updateState(clockAlarm1Type, alarmClock1.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
250 alarmClock2.setEnabled((data & ALARM_2_ENABLED_MASK) > 0);
251 alarmClock2.setLocal((data & ALARM_2_TYPE_MASK) > 0);
253 updateState(clockAlarm2Enabled, alarmClock2.isEnabled() ? OnOffType.ON : OnOffType.OFF);
254 updateState(clockAlarm2Type, alarmClock2.isLocal() ? ALARM_TYPE_LOCAL : ALARM_TYPE_GLOBAL);
257 alarmClock1.setWakeupHour(data);
258 updateState(clockAlarm1WakeupHour, new DecimalType(alarmClock1.getWakeupHour()));
261 alarmClock1.setWakeupMinute(data);
262 updateState(clockAlarm1WakeupMinute, new DecimalType(alarmClock1.getWakeupMinute()));
265 alarmClock1.setBedtimeHour(data);
266 updateState(clockAlarm1BedtimeHour, new DecimalType(alarmClock1.getBedtimeHour()));
269 alarmClock1.setBedtimeMinute(data);
270 updateState(clockAlarm1BedtimeMinute, new DecimalType(alarmClock1.getBedtimeMinute()));
273 alarmClock2.setWakeupHour(data);
274 updateState(clockAlarm2WakeupHour, new DecimalType(alarmClock2.getWakeupHour()));
277 alarmClock2.setWakeupMinute(data);
278 updateState(clockAlarm2WakeupMinute, new DecimalType(alarmClock2.getWakeupMinute()));
281 alarmClock2.setBedtimeHour(data);
282 updateState(clockAlarm2BedtimeHour, new DecimalType(alarmClock2.getBedtimeHour()));
285 alarmClock2.setBedtimeMinute(data);
286 updateState(clockAlarm2BedtimeMinute, new DecimalType(alarmClock2.getBedtimeMinute()));
289 throw new IllegalArgumentException("The memory address '" + memoryAddress
290 + "' does not represent a clock alarm configuration for the thing '" + this.thing.getUID()
295 protected boolean isAlarmClockChannel(ChannelUID channelUID) {
296 return channelUID.equals(clockAlarm1Enabled) || channelUID.equals(clockAlarm1Type)
297 || channelUID.equals(clockAlarm1WakeupHour) || channelUID.equals(clockAlarm1WakeupMinute)
298 || channelUID.equals(clockAlarm1BedtimeHour) || channelUID.equals(clockAlarm1BedtimeMinute)
299 || channelUID.equals(clockAlarm2Enabled) || channelUID.equals(clockAlarm2Type)
300 || channelUID.equals(clockAlarm2WakeupHour) || channelUID.equals(clockAlarm2WakeupMinute)
301 || channelUID.equals(clockAlarm2BedtimeHour) || channelUID.equals(clockAlarm2BedtimeMinute);
304 protected byte determineAlarmNumber(ChannelUID channelUID) {
305 if (channelUID.equals(clockAlarm1Enabled) || channelUID.equals(clockAlarm1Type)
306 || channelUID.equals(clockAlarm1WakeupHour) || channelUID.equals(clockAlarm1WakeupMinute)
307 || channelUID.equals(clockAlarm1BedtimeHour) || channelUID.equals(clockAlarm1BedtimeMinute)) {
309 } else if (channelUID.equals(clockAlarm2Enabled) || channelUID.equals(clockAlarm2Type)
310 || channelUID.equals(clockAlarm2WakeupHour) || channelUID.equals(clockAlarm2WakeupMinute)
311 || channelUID.equals(clockAlarm2BedtimeHour) || channelUID.equals(clockAlarm2BedtimeMinute)) {
314 throw new IllegalArgumentException("The given channelUID is not an alarm clock channel: " + channelUID);
318 protected int getClockAlarmAndProgramSelectionIndexInModuleStatus() {