]> git.basschouten.com Git - openhab-addons.git/blob
73a6657e75c560adba7f568d1ec2d3274762f5a0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.CaddxMessageType;
33 import org.openhab.binding.caddx.internal.CaddxPanelListener;
34 import org.openhab.binding.caddx.internal.CaddxProtocol;
35 import org.openhab.binding.caddx.internal.CaddxSource;
36 import org.openhab.binding.caddx.internal.action.CaddxBridgeActions;
37 import org.openhab.binding.caddx.internal.config.CaddxBridgeConfiguration;
38 import org.openhab.binding.caddx.internal.config.CaddxKeypadConfiguration;
39 import org.openhab.binding.caddx.internal.config.CaddxPartitionConfiguration;
40 import org.openhab.binding.caddx.internal.config.CaddxZoneConfiguration;
41 import org.openhab.binding.caddx.internal.discovery.CaddxDiscoveryService;
42 import org.openhab.core.io.transport.serial.PortInUseException;
43 import org.openhab.core.io.transport.serial.SerialPortManager;
44 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
45 import org.openhab.core.library.types.StringType;
46 import org.openhab.core.thing.Bridge;
47 import org.openhab.core.thing.Channel;
48 import org.openhab.core.thing.ChannelUID;
49 import org.openhab.core.thing.Thing;
50 import org.openhab.core.thing.ThingStatus;
51 import org.openhab.core.thing.ThingStatusDetail;
52 import org.openhab.core.thing.binding.BaseBridgeHandler;
53 import org.openhab.core.thing.binding.ThingHandler;
54 import org.openhab.core.thing.binding.ThingHandlerService;
55 import org.openhab.core.types.Command;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 /**
60  * The bridge handler for the Caddx RS232 Serial interface.
61  *
62  * @author Georgios Moutsos - Initial contribution
63  */
64 @NonNullByDefault
65 public class CaddxBridgeHandler extends BaseBridgeHandler implements CaddxPanelListener {
66     private final Logger logger = LoggerFactory.getLogger(CaddxBridgeHandler.class);
67
68     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_00 = { 0x25, 0x00 }; // 1 - 16
69     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_10 = { 0x25, 0x01 }; // 17 - 32
70     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_20 = { 0x25, 0x02 }; // 33 - 48
71     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_30 = { 0x25, 0x03 }; // 49 - 64
72     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_40 = { 0x25, 0x04 }; // 65 - 80
73     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_50 = { 0x25, 0x05 }; // 81 - 96
74     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_60 = { 0x25, 0x06 }; // 97 - 112
75     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_70 = { 0x25, 0x07 }; // 113 - 64
76     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_80 = { 0x25, 0x08 }; // 129 - 144
77     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_90 = { 0x25, 0x09 }; // 145 - 160
78     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_A0 = { 0x25, 0x0A }; // 161 - 176
79     static final byte[] DISCOVERY_ZONES_SNAPSHOT_REQUEST_B0 = { 0x25, 0x0B }; // 177 - 192
80     static final byte[] DISCOVERY_PARTITIONS_SNAPSHOT_REQUEST = { 0x27 };
81
82     private final SerialPortManager portManager;
83     private @Nullable CaddxDiscoveryService discoveryService = null;
84     private CaddxProtocol protocol = CaddxProtocol.Binary;
85     private String serialPortName = "";
86     private int baudRate;
87     private int maxZoneNumber;
88     private @Nullable CaddxCommunicator communicator = null;
89
90     // Things served by the bridge
91     private Map<BigDecimal, Thing> thingZonesMap = new ConcurrentHashMap<>();
92     private Map<BigDecimal, Thing> thingPartitionsMap = new ConcurrentHashMap<>();
93     private Map<BigDecimal, Thing> thingKeypadsMap = new ConcurrentHashMap<>();
94     private @Nullable Thing thingPanel = null;
95
96     public @Nullable CaddxDiscoveryService getDiscoveryService() {
97         return discoveryService;
98     }
99
100     public void setDiscoveryService(CaddxDiscoveryService discoveryService) {
101         this.discoveryService = discoveryService;
102     }
103
104     public CaddxBridgeHandler(SerialPortManager portManager, Bridge bridge) {
105         super(bridge);
106
107         this.portManager = portManager;
108     }
109
110     @Override
111     public void initialize() {
112         CaddxBridgeConfiguration configuration = getConfigAs(CaddxBridgeConfiguration.class);
113
114         String portName = configuration.getSerialPort();
115         if (portName == null) {
116             logger.debug("Serial port is not defined in the configuration");
117             return;
118         }
119         serialPortName = portName;
120         protocol = configuration.getProtocol();
121         baudRate = configuration.getBaudrate();
122         maxZoneNumber = configuration.getMaxZoneNumber();
123         updateStatus(ThingStatus.OFFLINE);
124
125         // create & start panel interface
126         logger.debug("Starting interface at port {} with baudrate {} and protocol {}", serialPortName, baudRate,
127                 protocol);
128
129         try {
130             communicator = new CaddxCommunicator(getThing().getUID().getAsString(), portManager, protocol,
131                     serialPortName, baudRate);
132         } catch (IOException | TooManyListenersException | UnsupportedCommOperationException | PortInUseException e) {
133             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
134                     "Communication cannot be initialized. " + e.toString());
135
136             return;
137         }
138
139         CaddxCommunicator comm = communicator;
140         if (comm != null) {
141             comm.addListener(this);
142
143             // Send discovery commands for the zones
144             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_00, false));
145             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_10, false));
146             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_20, false));
147             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_30, false));
148             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_40, false));
149             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_50, false));
150             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_60, false));
151             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_70, false));
152             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_80, false));
153             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_90, false));
154             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_A0, false));
155             comm.transmit(new CaddxMessage(DISCOVERY_ZONES_SNAPSHOT_REQUEST_B0, false));
156
157             // Send discovery commands for the partitions
158             comm.transmit(new CaddxMessage(DISCOVERY_PARTITIONS_SNAPSHOT_REQUEST, false));
159
160             // Send status commands to the zones and partitions
161             thingZonesMap.forEach((k, v) -> sendCommand(CaddxBindingConstants.ZONE_STATUS_REQUEST,
162                     k.subtract(BigDecimal.ONE).toString()));
163             thingPartitionsMap.forEach((k, v) -> sendCommand(CaddxBindingConstants.PARTITION_STATUS_REQUEST,
164                     k.subtract(BigDecimal.ONE).toString()));
165         }
166
167         // list all channels
168         if (logger.isTraceEnabled()) {
169             logger.trace("list all {} channels:", getThing().getChannels().size());
170             for (Channel c : getThing().getChannels()) {
171                 logger.trace("Channel Type {} UID {}", c.getChannelTypeUID(), c.getUID());
172             }
173         }
174     }
175
176     @Override
177     public void dispose() {
178         CaddxCommunicator comm = communicator;
179         if (comm != null) {
180             comm.stop();
181             comm = null;
182         }
183
184         if (discoveryService != null) {
185             unregisterDiscoveryService();
186         }
187
188         super.dispose();
189     }
190
191     public @Nullable Thing findThing(CaddxThingType caddxThingType, @Nullable Integer partition, @Nullable Integer zone,
192             @Nullable Integer keypad) {
193         switch (caddxThingType) {
194             case PARTITION:
195                 if (partition != null) {
196                     return thingPartitionsMap.get(BigDecimal.valueOf(partition));
197                 }
198             case ZONE:
199                 if (zone != null) {
200                     return thingZonesMap.get(BigDecimal.valueOf(zone));
201                 }
202             case KEYPAD:
203                 if (keypad != null) {
204                     return thingKeypadsMap.get(BigDecimal.valueOf(keypad));
205                 }
206             case PANEL:
207                 return thingPanel;
208         }
209         return null;
210     }
211
212     @Override
213     public void handleCommand(ChannelUID channelUID, Command command) {
214         logger.trace("handleCommand(), channelUID: {}, command: {}", channelUID, command);
215
216         switch (channelUID.getId()) {
217             case SEND_COMMAND:
218                 if (!command.toString().isEmpty()) {
219                     String[] tokens = command.toString().split("\\|");
220
221                     String cmd = tokens[0];
222                     String data = "";
223                     if (tokens.length > 1) {
224                         data = tokens[1];
225                     }
226
227                     sendCommand(cmd, data);
228
229                     updateState(channelUID, new StringType(""));
230                 }
231                 break;
232             default:
233                 logger.debug("Unknown command {}", command);
234                 break;
235         }
236     }
237
238     /**
239      * Sends a command to the panel
240      *
241      * @param command The command to be send
242      * @param data The associated command data
243      */
244     public boolean sendCommand(String command, String data) {
245         logger.trace("sendCommand(): Attempting to send Command: command - {} - data: {}", command, data);
246
247         CaddxMessage msg = null;
248
249         switch (command) {
250             case CaddxBindingConstants.ZONE_BYPASSED:
251                 msg = new CaddxMessage(CaddxMessageType.ZONE_BYPASS_TOGGLE, data);
252                 break;
253             case CaddxBindingConstants.ZONE_STATUS_REQUEST:
254                 msg = new CaddxMessage(CaddxMessageType.ZONE_STATUS_REQUEST, data);
255                 break;
256             case CaddxBindingConstants.ZONE_NAME_REQUEST:
257                 msg = new CaddxMessage(CaddxMessageType.ZONE_NAME_REQUEST, data);
258                 break;
259             case CaddxBindingConstants.PARTITION_STATUS_REQUEST:
260                 msg = new CaddxMessage(CaddxMessageType.PARTITION_STATUS_REQUEST, data);
261                 break;
262             case CaddxBindingConstants.PARTITION_PRIMARY_COMMAND_WITH_PIN:
263                 msg = new CaddxMessage(CaddxMessageType.PRIMARY_KEYPAD_FUNCTION_WITH_PIN, data);
264                 break;
265             case CaddxBindingConstants.PARTITION_SECONDARY_COMMAND:
266                 msg = new CaddxMessage(CaddxMessageType.SECONDARY_KEYPAD_FUNCTION, data);
267                 break;
268             case CaddxBindingConstants.PANEL_SYSTEM_STATUS_REQUEST:
269                 msg = new CaddxMessage(CaddxMessageType.SYSTEM_STATUS_REQUEST, data);
270                 break;
271             case CaddxBindingConstants.PANEL_INTERFACE_CONFIGURATION_REQUEST:
272                 msg = new CaddxMessage(CaddxMessageType.INTERFACE_CONFIGURATION_REQUEST, data);
273                 break;
274             case CaddxBindingConstants.PANEL_LOG_EVENT_REQUEST:
275                 msg = new CaddxMessage(CaddxMessageType.LOG_EVENT_REQUEST, data);
276                 break;
277             case CaddxBindingConstants.KEYPAD_TERMINAL_MODE_REQUEST:
278                 msg = new CaddxMessage(CaddxMessageType.KEYPAD_TERMINAL_MODE_REQUEST, data);
279                 break;
280             case CaddxBindingConstants.KEYPAD_SEND_KEYPAD_TEXT_MESSAGE:
281                 msg = new CaddxMessage(CaddxMessageType.SEND_KEYPAD_TEXT_MESSAGE, data);
282                 break;
283             default:
284                 logger.debug("Unknown command {}", command);
285                 return false;
286         }
287
288         CaddxCommunicator comm = communicator;
289         if (comm != null) {
290             comm.transmit(msg);
291         }
292
293         return true;
294     }
295
296     /**
297      * Register the Discovery Service.
298      *
299      * @param discoveryService
300      */
301     public void registerDiscoveryService(CaddxDiscoveryService discoveryService) {
302         this.discoveryService = discoveryService;
303         logger.trace("registerDiscoveryService(): Discovery Service Registered!");
304     }
305
306     /**
307      * Unregister the Discovery Service.
308      */
309     public void unregisterDiscoveryService() {
310         logger.trace("unregisterDiscoveryService(): Discovery Service Unregistered!");
311         discoveryService = null;
312     }
313
314     @Override
315     public void caddxMessage(CaddxCommunicator communicator, CaddxMessage caddxMessage) {
316         CaddxSource source = caddxMessage.getSource();
317
318         if (source != CaddxSource.NONE) {
319             CaddxThingType caddxThingType = null;
320             @Nullable
321             Integer partition = null;
322             @Nullable
323             Integer zone = null;
324             @Nullable
325             Integer keypad = null;
326
327             switch (source) {
328                 case PANEL:
329                     caddxThingType = CaddxThingType.PANEL;
330                     break;
331                 case PARTITION:
332                     caddxThingType = CaddxThingType.PARTITION;
333                     partition = Integer.parseInt(caddxMessage.getPropertyById("partition_number")) + 1;
334                     break;
335                 case ZONE:
336                     caddxThingType = CaddxThingType.ZONE;
337                     zone = Integer.parseInt(caddxMessage.getPropertyById("zone_number")) + 1;
338                     break;
339                 case KEYPAD:
340                     caddxThingType = CaddxThingType.KEYPAD;
341                     keypad = Integer.parseInt(caddxMessage.getPropertyById("keypad_address"));
342                     break;
343                 default:
344                     logger.debug("Source has illegal value");
345                     return;
346             }
347
348             CaddxEvent event = new CaddxEvent(caddxMessage, partition, zone, keypad);
349
350             // Find the thing
351             Thing thing = findThing(caddxThingType, partition, zone, keypad);
352             CaddxDiscoveryService discoveryService = getDiscoveryService();
353             if (thing != null) {
354                 CaddxBaseThingHandler thingHandler = (CaddxBaseThingHandler) thing.getHandler();
355                 if (thingHandler != null) {
356                     thingHandler.caddxEventReceived(event, thing);
357                 }
358             } else {
359                 if (discoveryService != null) {
360                     discoveryService.addThing(getThing(), caddxThingType, event);
361                 }
362             }
363
364             // Handle specific messages that add multiple discovered things
365             if (discoveryService != null) {
366                 switch (caddxMessage.getCaddxMessageType()) {
367                     case PARTITIONS_SNAPSHOT_MESSAGE:
368                         for (int i = 1; i <= 8; i++) {
369                             if (caddxMessage.getPropertyById("partition_" + i + "_valid").equals("true")) {
370                                 thing = findThing(CaddxThingType.PARTITION, i, null, null);
371                                 if (thing != null) {
372                                     continue;
373                                 }
374
375                                 event = new CaddxEvent(caddxMessage, i, null, null);
376                                 discoveryService.addThing(getThing(), CaddxThingType.PARTITION, event);
377                             }
378                         }
379                         break;
380                     case ZONES_SNAPSHOT_MESSAGE:
381                         int zoneOffset = Integer.parseInt(caddxMessage.getPropertyById("zone_offset")) * 16;
382                         for (int i = 1; i <= 16; i++) {
383                             if (zoneOffset + i <= maxZoneNumber) {
384                                 thing = findThing(CaddxThingType.ZONE, null, zoneOffset + i, null);
385                                 if (thing != null) {
386                                     continue;
387                                 }
388
389                                 event = new CaddxEvent(caddxMessage, null, zoneOffset + i, null);
390                                 discoveryService.addThing(getThing(), CaddxThingType.ZONE, event);
391                             }
392                         }
393                         break;
394                     default:
395                         break;
396                 }
397             }
398         }
399
400         updateStatus(ThingStatus.ONLINE);
401     }
402
403     @Override
404     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
405         if (childHandler instanceof ThingHandlerPartition) {
406             BigDecimal id = (BigDecimal) childThing.getConfiguration()
407                     .get(CaddxPartitionConfiguration.PARTITION_NUMBER);
408             thingPartitionsMap.put(id, childThing);
409         } else if (childHandler instanceof ThingHandlerZone) {
410             BigDecimal id = (BigDecimal) childThing.getConfiguration().get(CaddxZoneConfiguration.ZONE_NUMBER);
411             thingZonesMap.put(id, childThing);
412         } else if (childHandler instanceof ThingHandlerKeypad) {
413             BigDecimal id = (BigDecimal) childThing.getConfiguration().get(CaddxKeypadConfiguration.KEYPAD_ADDRESS);
414             thingKeypadsMap.put(id, childThing);
415         } else if (childHandler instanceof ThingHandlerPanel) {
416             thingPanel = childThing;
417         }
418
419         super.childHandlerInitialized(childHandler, childThing);
420     }
421
422     @Override
423     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
424         if (childHandler instanceof ThingHandlerPartition) {
425             BigDecimal id = (BigDecimal) childThing.getConfiguration()
426                     .get(CaddxPartitionConfiguration.PARTITION_NUMBER);
427             thingPartitionsMap.remove(id);
428         } else if (childHandler instanceof ThingHandlerZone) {
429             BigDecimal id = (BigDecimal) childThing.getConfiguration().get(CaddxZoneConfiguration.ZONE_NUMBER);
430             thingZonesMap.remove(id);
431         } else if (childHandler instanceof ThingHandlerKeypad) {
432             BigDecimal id = (BigDecimal) childThing.getConfiguration().get(CaddxKeypadConfiguration.KEYPAD_ADDRESS);
433             thingKeypadsMap.remove(id);
434         } else if (childHandler instanceof ThingHandlerPanel) {
435             thingPanel = null;
436         }
437
438         super.childHandlerDisposed(childHandler, childThing);
439     }
440
441     @Override
442     public Collection<Class<? extends ThingHandlerService>> getServices() {
443         Set<Class<? extends ThingHandlerService>> set = new HashSet<Class<? extends ThingHandlerService>>(2);
444         set.add(CaddxDiscoveryService.class);
445         set.add(CaddxBridgeActions.class);
446         return set;
447     }
448
449     public void restart() {
450         // Stop the currently running communicator
451         CaddxCommunicator comm = communicator;
452         if (comm != null) {
453             comm.stop();
454             comm = null;
455         }
456
457         // Initialize again
458         initialize();
459     }
460 }