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