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