2 * Copyright (c) 2010-2020 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.Collections;
22 import java.util.TooManyListenersException;
23 import java.util.concurrent.ConcurrentHashMap;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.caddx.internal.CaddxBindingConstants;
28 import org.openhab.binding.caddx.internal.CaddxCommunicator;
29 import org.openhab.binding.caddx.internal.CaddxEvent;
30 import org.openhab.binding.caddx.internal.CaddxMessage;
31 import org.openhab.binding.caddx.internal.CaddxMessageType;
32 import org.openhab.binding.caddx.internal.CaddxPanelListener;
33 import org.openhab.binding.caddx.internal.CaddxProtocol;
34 import org.openhab.binding.caddx.internal.CaddxSource;
35 import org.openhab.binding.caddx.internal.config.CaddxBridgeConfiguration;
36 import org.openhab.binding.caddx.internal.config.CaddxKeypadConfiguration;
37 import org.openhab.binding.caddx.internal.config.CaddxPartitionConfiguration;
38 import org.openhab.binding.caddx.internal.config.CaddxZoneConfiguration;
39 import org.openhab.binding.caddx.internal.discovery.CaddxDiscoveryService;
40 import org.openhab.core.io.transport.serial.PortInUseException;
41 import org.openhab.core.io.transport.serial.SerialPortManager;
42 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
43 import org.openhab.core.library.types.StringType;
44 import org.openhab.core.thing.Bridge;
45 import org.openhab.core.thing.Channel;
46 import org.openhab.core.thing.ChannelUID;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.binding.BaseBridgeHandler;
51 import org.openhab.core.thing.binding.ThingHandler;
52 import org.openhab.core.thing.binding.ThingHandlerService;
53 import org.openhab.core.types.Command;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * The bridge handler for the Caddx RS232 Serial interface.
60 * @author Georgios Moutsos - Initial contribution
63 public class CaddxBridgeHandler extends BaseBridgeHandler implements CaddxPanelListener {
64 private final Logger logger = LoggerFactory.getLogger(CaddxBridgeHandler.class);
66 static final byte[] DISCOVERY_PARTITION_STATUS_REQUEST_0 = { 0x26, 0x00 };
67 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_00 = { 0x25, 0x00 };
68 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_10 = { 0x25, 0x10 };
69 static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_20 = { 0x25, 0x20 };
70 static final byte[] DISCOVERY_PARTITIONS_SNAPSHOT_REQUEST = { 0x27 };
72 private final SerialPortManager portManager;
73 private @Nullable CaddxDiscoveryService discoveryService = null;
74 private CaddxProtocol protocol = CaddxProtocol.Binary;
75 private String serialPortName = "";
77 private @Nullable CaddxCommunicator communicator = null;
79 // Things served by the bridge
80 private Map<BigDecimal, Thing> thingZonesMap = new ConcurrentHashMap<>();
81 private Map<BigDecimal, Thing> thingPartitionsMap = new ConcurrentHashMap<>();
82 private Map<BigDecimal, Thing> thingKeypadsMap = new ConcurrentHashMap<>();
83 private @Nullable Thing thingPanel = null;
85 public @Nullable CaddxDiscoveryService getDiscoveryService() {
86 return discoveryService;
89 public void setDiscoveryService(CaddxDiscoveryService discoveryService) {
90 this.discoveryService = discoveryService;
98 public CaddxBridgeHandler(SerialPortManager portManager, Bridge bridge) {
101 this.portManager = portManager;
105 public void initialize() {
106 CaddxBridgeConfiguration configuration = getConfigAs(CaddxBridgeConfiguration.class);
108 String portName = configuration.getSerialPort();
109 if (portName == null) {
110 logger.debug("Serial port is not defined in the configuration");
113 serialPortName = portName;
114 protocol = configuration.getProtocol();
115 baudRate = configuration.getBaudrate();
116 updateStatus(ThingStatus.OFFLINE);
118 // create & start panel interface
119 logger.debug("Starting interface at port {} with baudrate {} and protocol {}", serialPortName, baudRate,
123 communicator = new CaddxCommunicator(portManager, protocol, serialPortName, baudRate);
124 } catch (IOException | TooManyListenersException | UnsupportedCommOperationException | PortInUseException e) {
125 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
126 "Communication cannot be initialized. " + e.toString());
131 CaddxCommunicator comm = communicator;
133 comm.addListener(this);
135 // Send discovery commands for the things
136 comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_00, false));
137 comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_10, false));
138 comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_20, false));
139 comm.transmit(new CaddxMessage(DISCOVERY_PARTITION_STATUS_REQUEST_0, false));
140 comm.transmit(new CaddxMessage(DISCOVERY_PARTITIONS_SNAPSHOT_REQUEST, false));
144 if (logger.isTraceEnabled()) {
145 logger.trace("list all {} channels:", getThing().getChannels().size());
146 for (Channel c : getThing().getChannels()) {
147 logger.trace("Channel Type {} UID {}", c.getChannelTypeUID(), c.getUID());
153 public void dispose() {
154 CaddxCommunicator comm = communicator;
160 if (discoveryService != null) {
161 unregisterDiscoveryService();
167 public @Nullable Thing findThing(CaddxThingType caddxThingType, @Nullable Integer partition, @Nullable Integer zone,
168 @Nullable Integer keypad) {
169 switch (caddxThingType) {
171 if (partition != null) {
172 return thingPartitionsMap.get(BigDecimal.valueOf(partition));
176 return thingZonesMap.get(BigDecimal.valueOf(zone));
179 if (keypad != null) {
180 return thingKeypadsMap.get(BigDecimal.valueOf(keypad));
189 public void handleCommand(ChannelUID channelUID, Command command) {
190 logger.trace("handleCommand(), channelUID: {}, command: {}", channelUID, command);
192 switch (channelUID.getId()) {
194 if (!command.toString().isEmpty()) {
195 String[] tokens = command.toString().split("\\|");
197 String cmd = tokens[0];
199 if (tokens.length > 1) {
203 sendCommand(cmd, data);
205 updateState(channelUID, new StringType(""));
209 logger.debug("Unknown command {}", command);
215 * Sends a command to the panel
217 * @param command The command to be send
218 * @param data The associated command data
220 public boolean sendCommand(String command, String data) {
221 logger.trace("sendCommand(): Attempting to send Command: command - {} - data: {}", command, data);
223 CaddxMessage msg = null;
226 case CaddxBindingConstants.ZONE_BYPASSED:
227 msg = new CaddxMessage(CaddxMessageType.ZONE_BYPASS_TOGGLE, data);
229 case CaddxBindingConstants.ZONE_STATUS_REQUEST:
230 msg = new CaddxMessage(CaddxMessageType.ZONE_STATUS_REQUEST, data);
232 case CaddxBindingConstants.ZONE_NAME_REQUEST:
233 msg = new CaddxMessage(CaddxMessageType.ZONE_NAME_REQUEST, data);
235 case CaddxBindingConstants.PARTITION_STATUS_REQUEST:
236 msg = new CaddxMessage(CaddxMessageType.PARTITION_STATUS_REQUEST, data);
238 case CaddxBindingConstants.PARTITION_PRIMARY_COMMAND_WITH_PIN:
239 msg = new CaddxMessage(CaddxMessageType.PRIMARY_KEYPAD_FUNCTION_WITH_PIN, data);
241 case CaddxBindingConstants.PARTITION_SECONDARY_COMMAND:
242 msg = new CaddxMessage(CaddxMessageType.SECONDARY_KEYPAD_FUNCTION, data);
244 case CaddxBindingConstants.PANEL_SYSTEM_STATUS_REQUEST:
245 msg = new CaddxMessage(CaddxMessageType.SYSTEM_STATUS_REQUEST, data);
247 case CaddxBindingConstants.PANEL_INTERFACE_CONFIGURATION_REQUEST:
248 msg = new CaddxMessage(CaddxMessageType.INTERFACE_CONFIGURATION_REQUEST, data);
250 case CaddxBindingConstants.PANEL_LOG_EVENT_REQUEST:
251 msg = new CaddxMessage(CaddxMessageType.LOG_EVENT_REQUEST, data);
254 logger.debug("Unknown command {}", command);
258 CaddxCommunicator comm = communicator;
267 * Register the Discovery Service.
269 * @param discoveryService
271 public void registerDiscoveryService(CaddxDiscoveryService discoveryService) {
272 this.discoveryService = discoveryService;
273 logger.trace("registerDiscoveryService(): Discovery Service Registered!");
277 * Unregister the Discovery Service.
279 public void unregisterDiscoveryService() {
280 logger.trace("unregisterDiscoveryService(): Discovery Service Unregistered!");
281 discoveryService = null;
285 public void caddxMessage(CaddxCommunicator communicator, CaddxMessage caddxMessage) {
286 CaddxSource source = caddxMessage.getSource();
288 if (source != CaddxSource.NONE) {
289 CaddxThingType caddxThingType = null;
291 Integer partition = null;
295 Integer keypad = null;
299 caddxThingType = CaddxThingType.PANEL;
302 caddxThingType = CaddxThingType.PARTITION;
303 partition = Integer.parseInt(caddxMessage.getPropertyById("partition_number")) + 1;
306 caddxThingType = CaddxThingType.ZONE;
307 zone = Integer.parseInt(caddxMessage.getPropertyById("zone_number")) + 1;
310 caddxThingType = CaddxThingType.KEYPAD;
311 keypad = Integer.parseInt(caddxMessage.getPropertyById("keypad_address"));
314 logger.debug("Source has illegal value");
318 CaddxEvent event = new CaddxEvent(caddxMessage, partition, zone, keypad);
321 Thing thing = findThing(caddxThingType, partition, zone, keypad);
322 CaddxDiscoveryService discoveryService = getDiscoveryService();
324 CaddxBaseThingHandler thingHandler = (CaddxBaseThingHandler) thing.getHandler();
325 if (thingHandler != null) {
326 thingHandler.caddxEventReceived(event, thing);
329 if (discoveryService != null) {
330 discoveryService.addThing(getThing(), caddxThingType, event);
334 // Handle specific messages that add multiple discovered things
335 if (discoveryService != null) {
336 switch (caddxMessage.getCaddxMessageType()) {
337 case PARTITIONS_SNAPSHOT_MESSAGE:
338 for (int i = 1; i <= 8; i++) {
339 if (caddxMessage.getPropertyById("partition_" + i + "_valid").equals("true")) {
340 thing = findThing(CaddxThingType.PARTITION, i, null, null);
345 event = new CaddxEvent(caddxMessage, i, null, null);
346 discoveryService.addThing(getThing(), CaddxThingType.PARTITION, event);
350 case ZONES_SNAPSHOT_MESSAGE:
351 int zoneOffset = Integer.parseInt(caddxMessage.getPropertyById("zone_offset"));
352 for (int i = 1; i <= 16; i++) {
353 if (caddxMessage.getPropertyById("zone_" + i + "_trouble").equals("false")) {
354 thing = findThing(CaddxThingType.ZONE, null, zoneOffset + i, null);
359 event = new CaddxEvent(caddxMessage, null, zoneOffset + i, null);
360 discoveryService.addThing(getThing(), CaddxThingType.ZONE, event);
362 logger.debug("troubled zone: {}", zoneOffset + i);
372 updateStatus(ThingStatus.ONLINE);
376 public Collection<Class<? extends ThingHandlerService>> getServices() {
377 return Collections.singleton(CaddxDiscoveryService.class);
381 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
382 if (childHandler instanceof ThingHandlerPartition) {
383 BigDecimal id = (BigDecimal) childThing.getConfiguration()
384 .get(CaddxPartitionConfiguration.PARTITION_NUMBER);
385 thingPartitionsMap.put(id, childThing);
386 } else if (childHandler instanceof ThingHandlerZone) {
387 BigDecimal id = (BigDecimal) childThing.getConfiguration().get(CaddxZoneConfiguration.ZONE_NUMBER);
388 thingZonesMap.put(id, childThing);
389 } else if (childHandler instanceof ThingHandlerKeypad) {
390 BigDecimal id = (BigDecimal) childThing.getConfiguration().get(CaddxKeypadConfiguration.KEYPAD_ADDRESS);
391 thingKeypadsMap.put(id, childThing);
392 } else if (childHandler instanceof ThingHandlerPanel) {
393 thingPanel = childThing;
396 super.childHandlerInitialized(childHandler, childThing);
400 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
401 if (childHandler instanceof ThingHandlerPartition) {
402 BigDecimal id = (BigDecimal) childThing.getConfiguration()
403 .get(CaddxPartitionConfiguration.PARTITION_NUMBER);
404 thingPartitionsMap.remove(id);
405 } else if (childHandler instanceof ThingHandlerZone) {
406 BigDecimal id = (BigDecimal) childThing.getConfiguration().get(CaddxZoneConfiguration.ZONE_NUMBER);
407 thingZonesMap.remove(id);
408 } else if (childHandler instanceof ThingHandlerKeypad) {
409 BigDecimal id = (BigDecimal) childThing.getConfiguration().get(CaddxKeypadConfiguration.KEYPAD_ADDRESS);
410 thingKeypadsMap.remove(id);
411 } else if (childHandler instanceof ThingHandlerPanel) {
415 super.childHandlerDisposed(childHandler, childThing);