]> git.basschouten.com Git - openhab-addons.git/blob
49d1d38090bee62949f053d022cc10af23e7adac
[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 };
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 };
71
72     private final SerialPortManager portManager;
73     private @Nullable CaddxDiscoveryService discoveryService = null;
74     private CaddxProtocol protocol = CaddxProtocol.Binary;
75     private String serialPortName = "";
76     private int baudRate;
77     private @Nullable CaddxCommunicator communicator = null;
78
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;
84
85     public @Nullable CaddxDiscoveryService getDiscoveryService() {
86         return discoveryService;
87     }
88
89     public void setDiscoveryService(CaddxDiscoveryService discoveryService) {
90         this.discoveryService = discoveryService;
91     }
92
93     /**
94      * Constructor.
95      *
96      * @param bridge
97      */
98     public CaddxBridgeHandler(SerialPortManager portManager, Bridge bridge) {
99         super(bridge);
100
101         this.portManager = portManager;
102     }
103
104     @Override
105     public void initialize() {
106         CaddxBridgeConfiguration configuration = getConfigAs(CaddxBridgeConfiguration.class);
107
108         String portName = configuration.getSerialPort();
109         if (portName == null) {
110             logger.debug("Serial port is not defined in the configuration");
111             return;
112         }
113         serialPortName = portName;
114         protocol = configuration.getProtocol();
115         baudRate = configuration.getBaudrate();
116         updateStatus(ThingStatus.OFFLINE);
117
118         // create & start panel interface
119         logger.debug("Starting interface at port {} with baudrate {} and protocol {}", serialPortName, baudRate,
120                 protocol);
121
122         try {
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());
127
128             return;
129         }
130
131         CaddxCommunicator comm = communicator;
132         if (comm != null) {
133             comm.addListener(this);
134
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));
141         }
142
143         // list all channels
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());
148             }
149         }
150     }
151
152     @Override
153     public void dispose() {
154         CaddxCommunicator comm = communicator;
155         if (comm != null) {
156             comm.stop();
157             comm = null;
158         }
159
160         if (discoveryService != null) {
161             unregisterDiscoveryService();
162         }
163
164         super.dispose();
165     }
166
167     public @Nullable Thing findThing(CaddxThingType caddxThingType, @Nullable Integer partition, @Nullable Integer zone,
168             @Nullable Integer keypad) {
169         switch (caddxThingType) {
170             case PARTITION:
171                 if (partition != null) {
172                     return thingPartitionsMap.get(BigDecimal.valueOf(partition));
173                 }
174             case ZONE:
175                 if (zone != null) {
176                     return thingZonesMap.get(BigDecimal.valueOf(zone));
177                 }
178             case KEYPAD:
179                 if (keypad != null) {
180                     return thingKeypadsMap.get(BigDecimal.valueOf(keypad));
181                 }
182             case PANEL:
183                 return thingPanel;
184         }
185         return null;
186     }
187
188     @Override
189     public void handleCommand(ChannelUID channelUID, Command command) {
190         logger.trace("handleCommand(), channelUID: {}, command: {}", channelUID, command);
191
192         switch (channelUID.getId()) {
193             case SEND_COMMAND:
194                 if (!command.toString().isEmpty()) {
195                     String[] tokens = command.toString().split("\\|");
196
197                     String cmd = tokens[0];
198                     String data = "";
199                     if (tokens.length > 1) {
200                         data = tokens[1];
201                     }
202
203                     sendCommand(cmd, data);
204
205                     updateState(channelUID, new StringType(""));
206                 }
207                 break;
208             default:
209                 logger.debug("Unknown command {}", command);
210                 break;
211         }
212     }
213
214     /**
215      * Sends a command to the panel
216      *
217      * @param command The command to be send
218      * @param data The associated command data
219      */
220     public boolean sendCommand(String command, String data) {
221         logger.trace("sendCommand(): Attempting to send Command: command - {} - data: {}", command, data);
222
223         CaddxMessage msg = null;
224
225         switch (command) {
226             case CaddxBindingConstants.ZONE_BYPASSED:
227                 msg = new CaddxMessage(CaddxMessageType.ZONE_BYPASS_TOGGLE, data);
228                 break;
229             case CaddxBindingConstants.ZONE_STATUS_REQUEST:
230                 msg = new CaddxMessage(CaddxMessageType.ZONE_STATUS_REQUEST, data);
231                 break;
232             case CaddxBindingConstants.ZONE_NAME_REQUEST:
233                 msg = new CaddxMessage(CaddxMessageType.ZONE_NAME_REQUEST, data);
234                 break;
235             case CaddxBindingConstants.PARTITION_STATUS_REQUEST:
236                 msg = new CaddxMessage(CaddxMessageType.PARTITION_STATUS_REQUEST, data);
237                 break;
238             case CaddxBindingConstants.PARTITION_PRIMARY_COMMAND_WITH_PIN:
239                 msg = new CaddxMessage(CaddxMessageType.PRIMARY_KEYPAD_FUNCTION_WITH_PIN, data);
240                 break;
241             case CaddxBindingConstants.PARTITION_SECONDARY_COMMAND:
242                 msg = new CaddxMessage(CaddxMessageType.SECONDARY_KEYPAD_FUNCTION, data);
243                 break;
244             case CaddxBindingConstants.PANEL_SYSTEM_STATUS_REQUEST:
245                 msg = new CaddxMessage(CaddxMessageType.SYSTEM_STATUS_REQUEST, data);
246                 break;
247             case CaddxBindingConstants.PANEL_INTERFACE_CONFIGURATION_REQUEST:
248                 msg = new CaddxMessage(CaddxMessageType.INTERFACE_CONFIGURATION_REQUEST, data);
249                 break;
250             case CaddxBindingConstants.PANEL_LOG_EVENT_REQUEST:
251                 msg = new CaddxMessage(CaddxMessageType.LOG_EVENT_REQUEST, data);
252                 break;
253             default:
254                 logger.debug("Unknown command {}", command);
255                 return false;
256         }
257
258         CaddxCommunicator comm = communicator;
259         if (comm != null) {
260             comm.transmit(msg);
261         }
262
263         return true;
264     }
265
266     /**
267      * Register the Discovery Service.
268      *
269      * @param discoveryService
270      */
271     public void registerDiscoveryService(CaddxDiscoveryService discoveryService) {
272         this.discoveryService = discoveryService;
273         logger.trace("registerDiscoveryService(): Discovery Service Registered!");
274     }
275
276     /**
277      * Unregister the Discovery Service.
278      */
279     public void unregisterDiscoveryService() {
280         logger.trace("unregisterDiscoveryService(): Discovery Service Unregistered!");
281         discoveryService = null;
282     }
283
284     @Override
285     public void caddxMessage(CaddxCommunicator communicator, CaddxMessage caddxMessage) {
286         CaddxSource source = caddxMessage.getSource();
287
288         if (source != CaddxSource.NONE) {
289             CaddxThingType caddxThingType = null;
290             @Nullable
291             Integer partition = null;
292             @Nullable
293             Integer zone = null;
294             @Nullable
295             Integer keypad = null;
296
297             switch (source) {
298                 case PANEL:
299                     caddxThingType = CaddxThingType.PANEL;
300                     break;
301                 case PARTITION:
302                     caddxThingType = CaddxThingType.PARTITION;
303                     partition = Integer.parseInt(caddxMessage.getPropertyById("partition_number")) + 1;
304                     break;
305                 case ZONE:
306                     caddxThingType = CaddxThingType.ZONE;
307                     zone = Integer.parseInt(caddxMessage.getPropertyById("zone_number")) + 1;
308                     break;
309                 case KEYPAD:
310                     caddxThingType = CaddxThingType.KEYPAD;
311                     keypad = Integer.parseInt(caddxMessage.getPropertyById("keypad_address"));
312                     break;
313                 default:
314                     logger.debug("Source has illegal value");
315                     return;
316             }
317
318             CaddxEvent event = new CaddxEvent(caddxMessage, partition, zone, keypad);
319
320             // Find the thing
321             Thing thing = findThing(caddxThingType, partition, zone, keypad);
322             CaddxDiscoveryService discoveryService = getDiscoveryService();
323             if (thing != null) {
324                 CaddxBaseThingHandler thingHandler = (CaddxBaseThingHandler) thing.getHandler();
325                 if (thingHandler != null) {
326                     thingHandler.caddxEventReceived(event, thing);
327                 }
328             } else {
329                 if (discoveryService != null) {
330                     discoveryService.addThing(getThing(), caddxThingType, event);
331                 }
332             }
333
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);
341                                 if (thing != null) {
342                                     continue;
343                                 }
344
345                                 event = new CaddxEvent(caddxMessage, i, null, null);
346                                 discoveryService.addThing(getThing(), CaddxThingType.PARTITION, event);
347                             }
348                         }
349                         break;
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);
355                                 if (thing != null) {
356                                     continue;
357                                 }
358
359                                 event = new CaddxEvent(caddxMessage, null, zoneOffset + i, null);
360                                 discoveryService.addThing(getThing(), CaddxThingType.ZONE, event);
361                             } else {
362                                 logger.debug("troubled zone: {}", zoneOffset + i);
363                             }
364                         }
365                         break;
366                     default:
367                         break;
368                 }
369             }
370         }
371
372         updateStatus(ThingStatus.ONLINE);
373     }
374
375     @Override
376     public Collection<Class<? extends ThingHandlerService>> getServices() {
377         return Collections.singleton(CaddxDiscoveryService.class);
378     }
379
380     @Override
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;
394         }
395
396         super.childHandlerInitialized(childHandler, childThing);
397     }
398
399     @Override
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) {
412             thingPanel = null;
413         }
414
415         super.childHandlerDisposed(childHandler, childThing);
416     }
417 }