]> git.basschouten.com Git - openhab-addons.git/blob
7939ebd88ac34966d61c9f00bce134e7468e7cad
[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.qolsysiq.internal.handler;
14
15 import java.io.IOException;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
26 import org.openhab.binding.qolsysiq.internal.client.QolsysIQClientListener;
27 import org.openhab.binding.qolsysiq.internal.client.QolsysiqClient;
28 import org.openhab.binding.qolsysiq.internal.client.dto.action.Action;
29 import org.openhab.binding.qolsysiq.internal.client.dto.action.InfoAction;
30 import org.openhab.binding.qolsysiq.internal.client.dto.action.InfoActionType;
31 import org.openhab.binding.qolsysiq.internal.client.dto.event.AlarmEvent;
32 import org.openhab.binding.qolsysiq.internal.client.dto.event.ArmingEvent;
33 import org.openhab.binding.qolsysiq.internal.client.dto.event.ErrorEvent;
34 import org.openhab.binding.qolsysiq.internal.client.dto.event.SecureArmInfoEvent;
35 import org.openhab.binding.qolsysiq.internal.client.dto.event.SummaryInfoEvent;
36 import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
37 import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
38 import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
39 import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
40 import org.openhab.binding.qolsysiq.internal.config.QolsysIQPanelConfiguration;
41 import org.openhab.binding.qolsysiq.internal.discovery.QolsysIQChildDiscoveryService;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingUID;
48 import org.openhab.core.thing.binding.BaseBridgeHandler;
49 import org.openhab.core.thing.binding.ThingHandler;
50 import org.openhab.core.thing.binding.ThingHandlerService;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.RefreshType;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * The {@link QolsysIQPanelHandler} connects to a security panel and routes messages to child partitions.
58  *
59  * @author Dan Cunningham - Initial contribution
60  */
61 @NonNullByDefault
62 public class QolsysIQPanelHandler extends BaseBridgeHandler
63         implements QolsysIQClientListener, QolsysIQChildDiscoveryHandler {
64     private final Logger logger = LoggerFactory.getLogger(QolsysIQPanelHandler.class);
65     private static final int QUICK_RETRY_SECONDS = 1;
66     private static final int LONG_RETRY_SECONDS = 30;
67     private static final int HEARTBEAT_SECONDS = 30;
68     private @Nullable QolsysiqClient apiClient;
69     private @Nullable ScheduledFuture<?> retryFuture;
70     private @Nullable QolsysIQChildDiscoveryService discoveryService;
71     private List<Partition> partitions = Collections.synchronizedList(new LinkedList<Partition>());
72     private String key = "";
73
74     public QolsysIQPanelHandler(Bridge bridge) {
75         super(bridge);
76     }
77
78     @Override
79     public void handleCommand(ChannelUID channelUID, Command command) {
80         logger.debug("handleCommand {}", command);
81         if (command instanceof RefreshType) {
82             refresh();
83         }
84     }
85
86     @Override
87     public void initialize() {
88         logger.debug("initialize");
89         updateStatus(ThingStatus.UNKNOWN);
90         scheduler.execute(() -> {
91             connect();
92         });
93     }
94
95     @Override
96     public void dispose() {
97         stopRetryFuture();
98         disconnect();
99     }
100
101     @Override
102     public Collection<Class<? extends ThingHandlerService>> getServices() {
103         return Collections.singleton(QolsysIQChildDiscoveryService.class);
104     }
105
106     @Override
107     public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
108         this.discoveryService = service;
109     }
110
111     @Override
112     public void startDiscovery() {
113         refresh();
114     }
115
116     @Override
117     public void disconnected(Exception reason) {
118         logger.debug("disconnected", reason);
119         setOfflineAndReconnect(reason, QUICK_RETRY_SECONDS);
120     }
121
122     @Override
123     public void alarmEvent(AlarmEvent event) {
124         logger.debug("AlarmEvent {}", event.partitionId);
125         QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
126         if (handler != null) {
127             handler.alarmEvent(event);
128         }
129     }
130
131     @Override
132     public void armingEvent(ArmingEvent event) {
133         logger.debug("ArmingEvent {}", event.partitionId);
134         QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
135         if (handler != null) {
136             handler.armingEvent(event);
137         }
138     }
139
140     @Override
141     public void errorEvent(ErrorEvent event) {
142         logger.debug("ErrorEvent {}", event.partitionId);
143         QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
144         if (handler != null) {
145             handler.errorEvent(event);
146         }
147     }
148
149     @Override
150     public void summaryInfoEvent(SummaryInfoEvent event) {
151         logger.debug("SummaryInfoEvent");
152         synchronized (partitions) {
153             partitions.clear();
154             partitions.addAll(event.partitionList);
155         }
156         updatePartitions();
157         discoverChildDevices();
158     }
159
160     @Override
161     public void secureArmInfoEvent(SecureArmInfoEvent event) {
162         logger.debug("ArmingEvent {}", event.value);
163         QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
164         if (handler != null) {
165             handler.secureArmInfoEvent(event);
166         }
167     }
168
169     @Override
170     public void zoneActiveEvent(ZoneActiveEvent event) {
171         logger.debug("ZoneActiveEvent {} {}", event.zone.zoneId, event.zone.status);
172         partitions.forEach(p -> {
173             if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
174                 QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
175                 if (handler != null) {
176                     handler.zoneActiveEvent(event);
177                 }
178             }
179         });
180     }
181
182     @Override
183     public void zoneUpdateEvent(ZoneUpdateEvent event) {
184         logger.debug("ZoneUpdateEvent {}", event.zone.name);
185         partitions.forEach(p -> {
186             if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
187                 QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
188                 if (handler != null) {
189                     handler.zoneUpdateEvent(event);
190                 }
191             }
192         });
193     }
194
195     @Override
196     public void zoneAddEvent(ZoneAddEvent event) {
197         logger.debug("ZoneAddEvent {}", event.zone.name);
198         partitions.forEach(p -> {
199             if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
200                 QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
201                 if (handler != null) {
202                     handler.zoneAddEvent(event);
203                 }
204             }
205         });
206     }
207
208     /**
209      * Sends the action to the panel. This will replace the token of the action passed in with the one configured here
210      *
211      * @param action
212      */
213     protected void sendAction(Action action) {
214         action.token = key;
215         QolsysiqClient client = this.apiClient;
216         if (client != null) {
217             try {
218                 client.sendAction(action);
219             } catch (IOException e) {
220                 logger.debug("Could not send action", e);
221                 setOfflineAndReconnect(e, QUICK_RETRY_SECONDS);
222             }
223         }
224     }
225
226     protected synchronized void refresh() {
227         sendAction(new InfoAction(InfoActionType.SUMMARY));
228     }
229
230     /**
231      * Connect the client
232      */
233     private synchronized void connect() {
234         if (getThing().getStatus() == ThingStatus.ONLINE) {
235             logger.debug("connect: Bridge is already connected");
236             return;
237         }
238         QolsysIQPanelConfiguration config = getConfigAs(QolsysIQPanelConfiguration.class);
239         key = config.key;
240
241         try {
242             QolsysiqClient apiClient = new QolsysiqClient(config.hostname, config.port, HEARTBEAT_SECONDS, scheduler,
243                     "OH-binding-" + getThing().getUID().getAsString());
244             apiClient.connect();
245             apiClient.addListener(this);
246             this.apiClient = apiClient;
247             refresh();
248             updateStatus(ThingStatus.ONLINE);
249         } catch (IOException e) {
250             logger.debug("Could not connect");
251             setOfflineAndReconnect(e, LONG_RETRY_SECONDS);
252         }
253     }
254
255     /**
256      * Disconnects the client and removes listeners
257      */
258     private void disconnect() {
259         logger.debug("disconnect");
260         QolsysiqClient apiClient = this.apiClient;
261         if (apiClient != null) {
262             apiClient.removeListener(this);
263             apiClient.disconnect();
264             this.apiClient = null;
265         }
266     }
267
268     private void startRetryFuture(int seconds) {
269         stopRetryFuture();
270         logger.debug("startRetryFuture");
271         this.retryFuture = scheduler.schedule(this::connect, seconds, TimeUnit.SECONDS);
272     }
273
274     private void stopRetryFuture() {
275         logger.debug("stopRetryFuture");
276         ScheduledFuture<?> retryFuture = this.retryFuture;
277         if (retryFuture != null) {
278             retryFuture.cancel(true);
279             this.retryFuture = null;
280         }
281     }
282
283     private void setOfflineAndReconnect(Exception reason, int seconds) {
284         logger.debug("setOfflineAndReconnect");
285         disconnect();
286         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason.getMessage());
287         startRetryFuture(seconds);
288     }
289
290     private void updatePartitions() {
291         synchronized (partitions) {
292             partitions.forEach(p -> {
293                 QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
294                 if (handler != null) {
295                     handler.updatePartition(p);
296                 }
297             });
298         }
299     }
300
301     private void discoverChildDevices() {
302         synchronized (partitions) {
303             QolsysIQChildDiscoveryService discoveryService = this.discoveryService;
304             if (discoveryService != null) {
305                 partitions.forEach(p -> {
306                     ThingUID bridgeUID = getThing().getUID();
307                     ThingUID thingUID = new ThingUID(QolsysIQBindingConstants.THING_TYPE_PARTITION, bridgeUID,
308                             String.valueOf(p.partitionId));
309                     discoveryService.discoverQolsysIQChildThing(thingUID, bridgeUID, p.partitionId,
310                             "Qolsys IQ Partition: " + p.name);
311                 });
312             }
313         }
314     }
315
316     private @Nullable QolsysIQPartitionHandler partitionHandler(int partitionId) {
317         for (Thing thing : getThing().getThings()) {
318             ThingHandler handler = thing.getHandler();
319             if (handler instanceof QolsysIQPartitionHandler) {
320                 if (((QolsysIQPartitionHandler) handler).getPartitionId() == partitionId) {
321                     return (QolsysIQPartitionHandler) handler;
322                 }
323             }
324         }
325         return null;
326     }
327 }