]> git.basschouten.com Git - openhab-addons.git/blob
e00ef4aca27539418e811adfe753f8c6e215e728
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.caddx.internal.handler;
14
15 import static org.openhab.binding.caddx.internal.CaddxBindingConstants.SEND_COMMAND;
16
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.util.Collection;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.TooManyListenersException;
24 import java.util.concurrent.ConcurrentHashMap;
25
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;
59
60 /**
61  * The bridge handler for the Caddx RS232 Serial interface.
62  *
63  * @author Georgios Moutsos - Initial contribution
64  */
65 @NonNullByDefault
66 public class CaddxBridgeHandler extends BaseBridgeHandler implements CaddxPanelListener {
67     private final Logger logger = LoggerFactory.getLogger(CaddxBridgeHandler.class);
68
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 };
82
83     private final SerialPortManager portManager;
84     private @Nullable CaddxDiscoveryService discoveryService = null;
85     private CaddxProtocol protocol = CaddxProtocol.Binary;
86     private String serialPortName = "";
87     private int baudRate;
88     private int maxZoneNumber;
89     private boolean isIgnoreZoneStatusTransitions;
90     private @Nullable CaddxCommunicator communicator = null;
91
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;
97
98     public @Nullable CaddxDiscoveryService getDiscoveryService() {
99         return discoveryService;
100     }
101
102     public void setDiscoveryService(CaddxDiscoveryService discoveryService) {
103         this.discoveryService = discoveryService;
104     }
105
106     public CaddxBridgeHandler(SerialPortManager portManager, Bridge bridge) {
107         super(bridge);
108
109         this.portManager = portManager;
110     }
111
112     @Override
113     public void initialize() {
114         CaddxBridgeConfiguration configuration = getConfigAs(CaddxBridgeConfiguration.class);
115
116         String portName = configuration.getSerialPort();
117         if (portName == null) {
118             logger.debug("Serial port is not defined in the configuration");
119             return;
120         }
121         serialPortName = portName;
122         protocol = configuration.getProtocol();
123         baudRate = configuration.getBaudrate();
124         maxZoneNumber = configuration.getMaxZoneNumber();
125         isIgnoreZoneStatusTransitions = configuration.isIgnoreZoneStatusTransitions();
126         updateStatus(ThingStatus.OFFLINE);
127
128         // create & start panel interface
129         logger.debug("Starting interface at port {} with baudrate {} and protocol {}", serialPortName, baudRate,
130                 protocol);
131
132         try {
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());
138
139             return;
140         }
141
142         CaddxCommunicator comm = communicator;
143         if (comm != null) {
144             comm.addListener(this);
145
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));
159
160             // Send discovery commands for the partitions
161             comm.transmit(
162                     new CaddxMessage(CaddxMessageContext.DISCOVERY, DISCOVERY_PARTITIONS_SNAPSHOT_REQUEST, false));
163
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)));
169         }
170
171         // list all channels
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());
176             }
177         }
178     }
179
180     @Override
181     public void dispose() {
182         CaddxCommunicator comm = communicator;
183         if (comm != null) {
184             comm.stop();
185             comm = null;
186         }
187
188         if (discoveryService != null) {
189             unregisterDiscoveryService();
190         }
191
192         super.dispose();
193     }
194
195     public @Nullable Thing findThing(CaddxThingType caddxThingType, @Nullable Integer partition, @Nullable Integer zone,
196             @Nullable Integer keypad) {
197         switch (caddxThingType) {
198             case PARTITION:
199                 if (partition != null) {
200                     return thingPartitionsMap.get(Integer.valueOf(partition));
201                 }
202             case ZONE:
203                 if (zone != null) {
204                     return thingZonesMap.get(Integer.valueOf(zone));
205                 }
206             case KEYPAD:
207                 if (keypad != null) {
208                     return thingKeypadsMap.get(Integer.valueOf(keypad));
209                 }
210             case PANEL:
211                 return thingPanel;
212         }
213         return null;
214     }
215
216     @Override
217     public void handleCommand(ChannelUID channelUID, Command command) {
218         logger.trace("handleCommand(), channelUID: {}, command: {}", channelUID, command);
219
220         switch (channelUID.getId()) {
221             case SEND_COMMAND:
222                 if (!command.toString().isEmpty()) {
223                     String[] tokens = command.toString().split("\\|");
224
225                     String cmd = tokens[0];
226                     String data = "";
227                     if (tokens.length > 1) {
228                         data = tokens[1];
229                     }
230
231                     sendCommand(CaddxMessageContext.COMMAND, cmd, data);
232
233                     updateState(channelUID, new StringType(""));
234                 }
235                 break;
236             default:
237                 logger.debug("Unknown command {}", command);
238                 break;
239         }
240     }
241
242     /**
243      * Sends a command to the panel
244      *
245      * @param command The command to be send
246      * @param data The associated command data
247      */
248     public boolean sendCommand(CaddxMessageContext context, String command, String data) {
249         logger.trace("sendCommand(): Attempting to send Command: command - {} - data: {}", command, data);
250
251         CaddxMessage msg = null;
252
253         switch (command) {
254             case CaddxBindingConstants.ZONE_BYPASSED:
255                 msg = new CaddxMessage(context, CaddxMessageType.ZONE_BYPASS_TOGGLE, data);
256                 break;
257             case CaddxBindingConstants.ZONE_STATUS_REQUEST:
258                 msg = new CaddxMessage(context, CaddxMessageType.ZONE_STATUS_REQUEST, data);
259                 break;
260             case CaddxBindingConstants.ZONE_NAME_REQUEST:
261                 msg = new CaddxMessage(context, CaddxMessageType.ZONE_NAME_REQUEST, data);
262                 break;
263             case CaddxBindingConstants.PARTITION_STATUS_REQUEST:
264                 msg = new CaddxMessage(context, CaddxMessageType.PARTITION_STATUS_REQUEST, data);
265                 break;
266             case CaddxBindingConstants.PARTITION_PRIMARY_COMMAND_WITH_PIN:
267                 msg = new CaddxMessage(context, CaddxMessageType.PRIMARY_KEYPAD_FUNCTION_WITH_PIN, data);
268                 break;
269             case CaddxBindingConstants.PARTITION_SECONDARY_COMMAND:
270                 msg = new CaddxMessage(context, CaddxMessageType.SECONDARY_KEYPAD_FUNCTION, data);
271                 break;
272             case CaddxBindingConstants.PANEL_SYSTEM_STATUS_REQUEST:
273                 msg = new CaddxMessage(context, CaddxMessageType.SYSTEM_STATUS_REQUEST, data);
274                 break;
275             case CaddxBindingConstants.PANEL_INTERFACE_CONFIGURATION_REQUEST:
276                 msg = new CaddxMessage(context, CaddxMessageType.INTERFACE_CONFIGURATION_REQUEST, data);
277                 break;
278             case CaddxBindingConstants.PANEL_LOG_EVENT_REQUEST:
279                 msg = new CaddxMessage(context, CaddxMessageType.LOG_EVENT_REQUEST, data);
280                 break;
281             case CaddxBindingConstants.KEYPAD_TERMINAL_MODE_REQUEST:
282                 msg = new CaddxMessage(context, CaddxMessageType.KEYPAD_TERMINAL_MODE_REQUEST, data);
283                 break;
284             case CaddxBindingConstants.KEYPAD_SEND_KEYPAD_TEXT_MESSAGE:
285                 msg = new CaddxMessage(context, CaddxMessageType.SEND_KEYPAD_TEXT_MESSAGE, data);
286                 break;
287             default:
288                 logger.debug("Unknown command {}", command);
289                 return false;
290         }
291
292         CaddxCommunicator comm = communicator;
293         if (comm != null) {
294             comm.transmit(msg);
295         }
296
297         return true;
298     }
299
300     /**
301      * Register the Discovery Service.
302      *
303      * @param discoveryService
304      */
305     public void registerDiscoveryService(CaddxDiscoveryService discoveryService) {
306         this.discoveryService = discoveryService;
307         logger.trace("registerDiscoveryService(): Discovery Service Registered!");
308     }
309
310     /**
311      * Unregister the Discovery Service.
312      */
313     public void unregisterDiscoveryService() {
314         logger.trace("unregisterDiscoveryService(): Discovery Service Unregistered!");
315         discoveryService = null;
316     }
317
318     @Override
319     public void caddxMessage(CaddxMessage caddxMessage) {
320         CaddxSource source = caddxMessage.getSource();
321
322         if (source != CaddxSource.NONE) {
323             CaddxThingType caddxThingType = null;
324             Integer partition = null;
325             Integer zone = null;
326             Integer keypad = null;
327
328             switch (source) {
329                 case PANEL:
330                     caddxThingType = CaddxThingType.PANEL;
331                     break;
332                 case PARTITION:
333                     caddxThingType = CaddxThingType.PARTITION;
334                     partition = Integer.parseInt(caddxMessage.getPropertyById("partition_number")) + 1;
335                     break;
336                 case ZONE:
337                     caddxThingType = CaddxThingType.ZONE;
338                     zone = Integer.parseInt(caddxMessage.getPropertyById("zone_number")) + 1;
339                     break;
340                 case KEYPAD:
341                     caddxThingType = CaddxThingType.KEYPAD;
342                     keypad = Integer.parseInt(caddxMessage.getPropertyById("keypad_address"));
343                     break;
344                 default:
345                     logger.debug("Source has illegal value");
346                     return;
347             }
348
349             CaddxEvent event = new CaddxEvent(caddxMessage, partition, zone, keypad);
350
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);
356                 return;
357             }
358
359             // Find the thing
360             Thing thing = findThing(caddxThingType, partition, zone, keypad);
361             CaddxDiscoveryService discoveryService = getDiscoveryService();
362             if (thing != null) {
363                 CaddxBaseThingHandler thingHandler = (CaddxBaseThingHandler) thing.getHandler();
364                 if (thingHandler != null) {
365                     thingHandler.caddxEventReceived(event, thing);
366                 }
367             } else {
368                 if (discoveryService != null) {
369                     discoveryService.addThing(getThing(), caddxThingType, event);
370                 }
371             }
372
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 (caddxMessage.getPropertyById("partition_" + i + "_valid").equals("true")) {
379                                 thing = findThing(CaddxThingType.PARTITION, i, null, null);
380                                 if (thing != null) {
381                                     continue;
382                                 }
383
384                                 event = new CaddxEvent(caddxMessage, i, null, null);
385                                 discoveryService.addThing(getThing(), CaddxThingType.PARTITION, event);
386                             }
387                         }
388                         break;
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);
394                                 if (thing != null) {
395                                     continue;
396                                 }
397
398                                 event = new CaddxEvent(caddxMessage, null, zoneOffset + i, null);
399                                 discoveryService.addThing(getThing(), CaddxThingType.ZONE, event);
400                             }
401                         }
402                         break;
403                     default:
404                         break;
405                 }
406             }
407         }
408
409         updateStatus(ThingStatus.ONLINE);
410     }
411
412     @Override
413     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
414         if (childHandler instanceof ThingHandlerPartition) {
415             int id = ((BigDecimal) childThing.getConfiguration().get(CaddxPartitionConfiguration.PARTITION_NUMBER))
416                     .intValue();
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))
423                     .intValue();
424             thingKeypadsMap.put(id, childThing);
425         } else if (childHandler instanceof ThingHandlerPanel) {
426             thingPanel = childThing;
427         }
428
429         super.childHandlerInitialized(childHandler, childThing);
430     }
431
432     @Override
433     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
434         if (childHandler instanceof ThingHandlerPartition) {
435             int id = ((BigDecimal) childThing.getConfiguration().get(CaddxPartitionConfiguration.PARTITION_NUMBER))
436                     .intValue();
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))
443                     .intValue();
444             thingKeypadsMap.remove(id);
445         } else if (childHandler instanceof ThingHandlerPanel) {
446             thingPanel = null;
447         }
448
449         super.childHandlerDisposed(childHandler, childThing);
450     }
451
452     @Override
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);
457         return set;
458     }
459
460     public void restart() {
461         // Stop the currently running communicator
462         CaddxCommunicator comm = communicator;
463         if (comm != null) {
464             comm.stop();
465             comm = null;
466         }
467
468         // Initialize again
469         initialize();
470     }
471 }