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