2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.qolsysiq.internal.handler;
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;
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;
57 * The {@link QolsysIQPanelHandler} connects to a security panel and routes messages to child partitions.
59 * @author Dan Cunningham - Initial contribution
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 = "";
74 public QolsysIQPanelHandler(Bridge bridge) {
79 public void handleCommand(ChannelUID channelUID, Command command) {
80 logger.debug("handleCommand {}", command);
81 if (command instanceof RefreshType) {
87 public void initialize() {
88 logger.debug("initialize");
89 updateStatus(ThingStatus.UNKNOWN);
90 scheduler.execute(() -> {
96 public void dispose() {
102 public Collection<Class<? extends ThingHandlerService>> getServices() {
103 return Collections.singleton(QolsysIQChildDiscoveryService.class);
107 public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
108 this.discoveryService = service;
112 public void startDiscovery() {
117 public void disconnected(Exception reason) {
118 logger.debug("disconnected", reason);
119 setOfflineAndReconnect(reason, QUICK_RETRY_SECONDS);
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);
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);
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);
150 public void summaryInfoEvent(SummaryInfoEvent event) {
151 logger.debug("SummaryInfoEvent");
152 synchronized (partitions) {
154 partitions.addAll(event.partitionList);
157 discoverChildDevices();
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);
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);
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);
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);
209 * Sends the action to the panel. This will replace the token of the action passed in with the one configured here
213 protected void sendAction(Action action) {
215 QolsysiqClient client = this.apiClient;
216 if (client != null) {
218 client.sendAction(action);
219 } catch (IOException e) {
220 logger.debug("Could not send action", e);
221 setOfflineAndReconnect(e, QUICK_RETRY_SECONDS);
226 protected synchronized void refresh() {
227 sendAction(new InfoAction(InfoActionType.SUMMARY));
233 private synchronized void connect() {
234 if (getThing().getStatus() == ThingStatus.ONLINE) {
235 logger.debug("connect: Bridge is already connected");
238 QolsysIQPanelConfiguration config = getConfigAs(QolsysIQPanelConfiguration.class);
242 QolsysiqClient apiClient = new QolsysiqClient(config.hostname, config.port, HEARTBEAT_SECONDS, scheduler,
243 "OH-binding-" + getThing().getUID().getAsString());
245 apiClient.addListener(this);
246 this.apiClient = apiClient;
248 updateStatus(ThingStatus.ONLINE);
249 } catch (IOException e) {
250 logger.debug("Could not connect");
251 setOfflineAndReconnect(e, LONG_RETRY_SECONDS);
256 * Disconnects the client and removes listeners
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;
268 private void startRetryFuture(int seconds) {
270 logger.debug("startRetryFuture");
271 this.retryFuture = scheduler.schedule(this::connect, seconds, TimeUnit.SECONDS);
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;
283 private void setOfflineAndReconnect(Exception reason, int seconds) {
284 logger.debug("setOfflineAndReconnect");
286 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason.getMessage());
287 startRetryFuture(seconds);
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);
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);
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;