2 * Copyright (c) 2010-2024 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.caddx.internal.handler;
15 import static org.openhab.binding.caddx.internal.CaddxBindingConstants.SEND_COMMAND;
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.util.Collection;
20 import java.util.HashSet;
23 import java.util.TooManyListenersException;
24 import java.util.concurrent.ConcurrentHashMap;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.caddx.internal.CaddxBindingConstants;
29 import org.openhab.binding.caddx.internal.CaddxCommunicator;
30 import org.openhab.binding.caddx.internal.CaddxEvent;
31 import org.openhab.binding.caddx.internal.CaddxMessage;
32 import org.openhab.binding.caddx.internal.CaddxMessageContext;
33 import org.openhab.binding.caddx.internal.CaddxMessageType;
34 import org.openhab.binding.caddx.internal.CaddxPanelListener;
35 import org.openhab.binding.caddx.internal.CaddxProtocol;
36 import org.openhab.binding.caddx.internal.CaddxSource;
37 import org.openhab.binding.caddx.internal.action.CaddxBridgeActions;
38 import org.openhab.binding.caddx.internal.config.CaddxBridgeConfiguration;
39 import org.openhab.binding.caddx.internal.config.CaddxKeypadConfiguration;
40 import org.openhab.binding.caddx.internal.config.CaddxPartitionConfiguration;
41 import org.openhab.binding.caddx.internal.config.CaddxZoneConfiguration;
42 import org.openhab.binding.caddx.internal.discovery.CaddxDiscoveryService;
43 import org.openhab.core.io.transport.serial.PortInUseException;
44 import org.openhab.core.io.transport.serial.SerialPortManager;
45 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
46 import org.openhab.core.library.types.StringType;
47 import org.openhab.core.thing.Bridge;
48 import org.openhab.core.thing.Channel;
49 import org.openhab.core.thing.ChannelUID;
50 import org.openhab.core.thing.Thing;
51 import org.openhab.core.thing.ThingStatus;
52 import org.openhab.core.thing.ThingStatusDetail;
53 import org.openhab.core.thing.binding.BaseBridgeHandler;
54 import org.openhab.core.thing.binding.ThingHandler;
55 import org.openhab.core.thing.binding.ThingHandlerService;
56 import org.openhab.core.types.Command;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
61 * The bridge handler for the Caddx RS232 Serial interface.
63 * @author Georgios Moutsos - Initial contribution
66 public class CaddxBridgeHandler extends BaseBridgeHandler implements CaddxPanelListener {
67 private final Logger logger = LoggerFactory.getLogger(CaddxBridgeHandler.class);
69 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_00 = { 0x25, 0x00 }; // 1 - 16
70 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_10 = { 0x25, 0x01 }; // 17 - 32
71 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_20 = { 0x25, 0x02 }; // 33 - 48
72 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_30 = { 0x25, 0x03 }; // 49 - 64
73 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_40 = { 0x25, 0x04 }; // 65 - 80
74 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_50 = { 0x25, 0x05 }; // 81 - 96
75 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_60 = { 0x25, 0x06 }; // 97 - 112
76 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_70 = { 0x25, 0x07 }; // 113 - 64
77 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_80 = { 0x25, 0x08 }; // 129 - 144
78 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_90 = { 0x25, 0x09 }; // 145 - 160
79 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_A0 = { 0x25, 0x0A }; // 161 - 176
80 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_B0 = { 0x25, 0x0B }; // 177 - 192
81 static final byte[] DISCOVERY_PARTITIONS_SNAPSHOT_REQUEST = { 0x27 };
83 private final SerialPortManager portManager;
84 private @Nullable CaddxDiscoveryService discoveryService = null;
85 private CaddxProtocol protocol = CaddxProtocol.Binary;
86 private String serialPortName = "";
88 private int maxZoneNumber;
89 private boolean isIgnoreZoneStatusTransitions;
90 private @Nullable CaddxCommunicator communicator = null;
92 // Things served by the bridge
93 private Map<Integer, Thing> thingZonesMap = new ConcurrentHashMap<>();
94 private Map<Integer, Thing> thingPartitionsMap = new ConcurrentHashMap<>();
95 private Map<Integer, Thing> thingKeypadsMap = new ConcurrentHashMap<>();
96 private @Nullable Thing thingPanel = null;
98 public @Nullable CaddxDiscoveryService getDiscoveryService() {
99 return discoveryService;
102 public void setDiscoveryService(CaddxDiscoveryService discoveryService) {
103 this.discoveryService = discoveryService;
106 public CaddxBridgeHandler(SerialPortManager portManager, Bridge bridge) {
109 this.portManager = portManager;
113 public void initialize() {
114 CaddxBridgeConfiguration configuration = getConfigAs(CaddxBridgeConfiguration.class);
116 String portName = configuration.getSerialPort();
117 if (portName == null) {
118 logger.debug("Serial port is not defined in the configuration");
121 serialPortName = portName;
122 protocol = configuration.getProtocol();
123 baudRate = configuration.getBaudrate();
124 maxZoneNumber = configuration.getMaxZoneNumber();
125 isIgnoreZoneStatusTransitions = configuration.isIgnoreZoneStatusTransitions();
126 updateStatus(ThingStatus.OFFLINE);
128 // create & start panel interface
129 logger.debug("Starting interface at port {} with baudrate {} and protocol {}", serialPortName, baudRate,
133 communicator = new CaddxCommunicator(getThing().getUID().getAsString(), portManager, protocol,
134 serialPortName, baudRate);
135 } catch (IOException | TooManyListenersException | UnsupportedCommOperationException | PortInUseException e) {
136 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
137 "Communication cannot be initialized. " + e.toString());
142 CaddxCommunicator comm = communicator;
144 comm.addListener(this);
146 // Send discovery commands for the zones
147 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_00, false));
148 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_10, false));
149 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_20, false));
150 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_30, false));
151 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_40, false));
152 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_50, false));
153 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_60, false));
154 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_70, false));
155 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_80, false));
156 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_90, false));
157 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_A0, false));
158 comm.transmit(new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_ZONES_SNAPSHOT_REQUEST_B0, false));
160 // Send discovery commands for the partitions
162 new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_PARTITIONS_SNAPSHOT_REQUEST, false));
164 // Send status commands to the zones and partitions
165 thingZonesMap.forEach((k, v) -> sendCommand(CaddxMessageContext.COMMAND,
166 CaddxBindingConstants.ZONE_STATUS_REQUEST, String.valueOf(k - 1)));
167 thingPartitionsMap.forEach((k, v) -> sendCommand(CaddxMessageContext.COMMAND,
168 CaddxBindingConstants.PARTITION_STATUS_REQUEST, String.valueOf(k - 1)));
172 if (logger.isTraceEnabled()) {
173 logger.trace("list all {} channels:", getThing().getChannels().size());
174 for (Channel c : getThing().getChannels()) {
175 logger.trace("Channel Type {} UID {}", c.getChannelTypeUID(), c.getUID());
181 public void dispose() {
182 CaddxCommunicator comm = communicator;
188 if (discoveryService != null) {
189 unregisterDiscoveryService();
195 public @Nullable Thing findThing(CaddxThingType caddxThingType, @Nullable Integer partition, @Nullable Integer zone,
196 @Nullable Integer keypad) {
197 switch (caddxThingType) {
199 if (partition != null) {
200 return thingPartitionsMap.get(Integer.valueOf(partition));
204 return thingZonesMap.get(Integer.valueOf(zone));
207 if (keypad != null) {
208 return thingKeypadsMap.get(Integer.valueOf(keypad));
217 public void handleCommand(ChannelUID channelUID, Command command) {
218 logger.trace("handleCommand(), channelUID: {}, command: {}", channelUID, command);
220 switch (channelUID.getId()) {
222 if (!command.toString().isEmpty()) {
223 String[] tokens = command.toString().split("\\|");
225 String cmd = tokens[0];
227 if (tokens.length > 1) {
231 sendCommand(CaddxMessageContext.COMMAND, cmd, data);
233 updateState(channelUID, new StringType(""));
237 logger.debug("Unknown command {}", command);
243 * Sends a command to the panel
245 * @param command The command to be send
246 * @param data The associated command data
248 public boolean sendCommand(CaddxMessageContext context, String command, String data) {
249 logger.trace("sendCommand(): Attempting to send Command: command - {} - data: {}", command, data);
251 CaddxMessage msg = null;
254 case CaddxBindingConstants.ZONE_BYPASSED:
255 msg = new CaddxMessage(context, CaddxMessageType.ZONE_BYPASS_TOGGLE, data);
257 case CaddxBindingConstants.ZONE_STATUS_REQUEST:
258 msg = new CaddxMessage(context, CaddxMessageType.ZONE_STATUS_REQUEST, data);
260 case CaddxBindingConstants.ZONE_NAME_REQUEST:
261 msg = new CaddxMessage(context, CaddxMessageType.ZONE_NAME_REQUEST, data);
263 case CaddxBindingConstants.PARTITION_STATUS_REQUEST:
264 msg = new CaddxMessage(context, CaddxMessageType.PARTITION_STATUS_REQUEST, data);
266 case CaddxBindingConstants.PARTITION_PRIMARY_COMMAND_WITH_PIN:
267 msg = new CaddxMessage(context, CaddxMessageType.PRIMARY_KEYPAD_FUNCTION_WITH_PIN, data);
269 case CaddxBindingConstants.PARTITION_SECONDARY_COMMAND:
270 msg = new CaddxMessage(context, CaddxMessageType.SECONDARY_KEYPAD_FUNCTION, data);
272 case CaddxBindingConstants.PANEL_SYSTEM_STATUS_REQUEST:
273 msg = new CaddxMessage(context, CaddxMessageType.SYSTEM_STATUS_REQUEST, data);
275 case CaddxBindingConstants.PANEL_INTERFACE_CONFIGURATION_REQUEST:
276 msg = new CaddxMessage(context, CaddxMessageType.INTERFACE_CONFIGURATION_REQUEST, data);
278 case CaddxBindingConstants.PANEL_LOG_EVENT_REQUEST:
279 msg = new CaddxMessage(context, CaddxMessageType.LOG_EVENT_REQUEST, data);
281 case CaddxBindingConstants.KEYPAD_TERMINAL_MODE_REQUEST:
282 msg = new CaddxMessage(context, CaddxMessageType.KEYPAD_TERMINAL_MODE_REQUEST, data);
284 case CaddxBindingConstants.KEYPAD_SEND_KEYPAD_TEXT_MESSAGE:
285 msg = new CaddxMessage(context, CaddxMessageType.SEND_KEYPAD_TEXT_MESSAGE, data);
288 logger.debug("Unknown command {}", command);
292 CaddxCommunicator comm = communicator;
301 * Register the Discovery Service.
303 * @param discoveryService
305 public void registerDiscoveryService(CaddxDiscoveryService discoveryService) {
306 this.discoveryService = discoveryService;
307 logger.trace("registerDiscoveryService(): Discovery Service Registered!");
311 * Unregister the Discovery Service.
313 public void unregisterDiscoveryService() {
314 logger.trace("unregisterDiscoveryService(): Discovery Service Unregistered!");
315 discoveryService = null;
319 public void caddxMessage(CaddxMessage caddxMessage) {
320 CaddxSource source = caddxMessage.getSource();
322 if (source != CaddxSource.NONE) {
323 CaddxThingType caddxThingType = null;
324 Integer partition = null;
326 Integer keypad = null;
330 caddxThingType = CaddxThingType.PANEL;
333 caddxThingType = CaddxThingType.PARTITION;
334 partition = Integer.parseInt(caddxMessage.getPropertyById("partition_number")) + 1;
337 caddxThingType = CaddxThingType.ZONE;
338 zone = Integer.parseInt(caddxMessage.getPropertyById("zone_number")) + 1;
341 caddxThingType = CaddxThingType.KEYPAD;
342 keypad = Integer.parseInt(caddxMessage.getPropertyById("keypad_address"));
345 logger.debug("Source has illegal value");
349 CaddxEvent event = new CaddxEvent(caddxMessage, partition, zone, keypad);
351 // Ignore Zone Status messages according to the configuration
352 if (isIgnoreZoneStatusTransitions
353 && caddxMessage.getCaddxMessageType() == CaddxMessageType.ZONE_STATUS_MESSAGE
354 && caddxMessage.getContext() == CaddxMessageContext.NONE) {
355 logger.debug("Zone {} Transition ignored.", zone);
360 Thing thing = findThing(caddxThingType, partition, zone, keypad);
361 CaddxDiscoveryService discoveryService = getDiscoveryService();
363 CaddxBaseThingHandler thingHandler = (CaddxBaseThingHandler) thing.getHandler();
364 if (thingHandler != null) {
365 thingHandler.caddxEventReceived(event, thing);
368 if (discoveryService != null) {
369 discoveryService.addThing(getThing(), caddxThingType, event);
373 // Handle specific messages that add multiple discovered things
374 if (discoveryService != null) {
375 switch (caddxMessage.getCaddxMessageType()) {
376 case PARTITIONS_SNAPSHOT_MESSAGE:
377 for (int i = 1; i <= 8; i++) {
378 if ("true".equals(caddxMessage.getPropertyById("partition_" + i + "_valid"))) {
379 thing = findThing(CaddxThingType.PARTITION, i, null, null);
384 event = new CaddxEvent(caddxMessage, i, null, null);
385 discoveryService.addThing(getThing(), CaddxThingType.PARTITION, event);
389 case ZONES_SNAPSHOT_MESSAGE:
390 int zoneOffset = Integer.parseInt(caddxMessage.getPropertyById("zone_offset")) * 16;
391 for (int i = 1; i <= 16; i++) {
392 if (zoneOffset + i <= maxZoneNumber) {
393 thing = findThing(CaddxThingType.ZONE, null, zoneOffset + i, null);
398 event = new CaddxEvent(caddxMessage, null, zoneOffset + i, null);
399 discoveryService.addThing(getThing(), CaddxThingType.ZONE, event);
409 updateStatus(ThingStatus.ONLINE);
413 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
414 if (childHandler instanceof ThingHandlerPartition) {
415 int id = ((BigDecimal) childThing.getConfiguration().get(CaddxPartitionConfiguration.PARTITION_NUMBER))
417 thingPartitionsMap.put(id, childThing);
418 } else if (childHandler instanceof ThingHandlerZone) {
419 int id = ((BigDecimal) childThing.getConfiguration().get(CaddxZoneConfiguration.ZONE_NUMBER)).intValue();
420 thingZonesMap.put(id, childThing);
421 } else if (childHandler instanceof ThingHandlerKeypad) {
422 int id = ((BigDecimal) childThing.getConfiguration().get(CaddxKeypadConfiguration.KEYPAD_ADDRESS))
424 thingKeypadsMap.put(id, childThing);
425 } else if (childHandler instanceof ThingHandlerPanel) {
426 thingPanel = childThing;
429 super.childHandlerInitialized(childHandler, childThing);
433 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
434 if (childHandler instanceof ThingHandlerPartition) {
435 int id = ((BigDecimal) childThing.getConfiguration().get(CaddxPartitionConfiguration.PARTITION_NUMBER))
437 thingPartitionsMap.remove(id);
438 } else if (childHandler instanceof ThingHandlerZone) {
439 int id = ((BigDecimal) childThing.getConfiguration().get(CaddxZoneConfiguration.ZONE_NUMBER)).intValue();
440 thingZonesMap.remove(id);
441 } else if (childHandler instanceof ThingHandlerKeypad) {
442 int id = ((BigDecimal) childThing.getConfiguration().get(CaddxKeypadConfiguration.KEYPAD_ADDRESS))
444 thingKeypadsMap.remove(id);
445 } else if (childHandler instanceof ThingHandlerPanel) {
449 super.childHandlerDisposed(childHandler, childThing);
453 public Collection<Class<? extends ThingHandlerService>> getServices() {
454 Set<Class<? extends ThingHandlerService>> set = new HashSet<Class<? extends ThingHandlerService>>(2);
455 set.add(CaddxDiscoveryService.class);
456 set.add(CaddxBridgeActions.class);
460 public void restart() {
461 // Stop the currently running communicator
462 CaddxCommunicator comm = communicator;