2 * Copyright (c) 2010-2023 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;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
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;
58 * The {@link QolsysIQPanelHandler} connects to a security panel and routes messages to child partitions.
60 * @author Dan Cunningham - Initial contribution
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 = "";
75 public QolsysIQPanelHandler(Bridge bridge) {
80 public void handleCommand(ChannelUID channelUID, Command command) {
81 logger.debug("handleCommand {}", command);
82 if (command instanceof RefreshType) {
88 public void initialize() {
89 logger.debug("initialize");
90 updateStatus(ThingStatus.UNKNOWN);
91 scheduler.execute(() -> {
97 public void dispose() {
103 public Collection<Class<? extends ThingHandlerService>> getServices() {
104 return Set.of(QolsysIQChildDiscoveryService.class);
108 public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
109 this.discoveryService = service;
113 public void startDiscovery() {
118 public void disconnected(Exception reason) {
119 logger.debug("disconnected", reason);
120 setOfflineAndReconnect(reason, QUICK_RETRY_SECONDS);
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);
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);
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);
151 public void summaryInfoEvent(SummaryInfoEvent event) {
152 logger.debug("SummaryInfoEvent");
153 synchronized (partitions) {
155 partitions.addAll(event.partitionList);
158 discoverChildDevices();
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);
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);
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);
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);
210 * Sends the action to the panel. This will replace the token of the action passed in with the one configured here
214 protected void sendAction(Action action) {
216 QolsysiqClient client = this.apiClient;
217 if (client != null) {
219 client.sendAction(action);
220 } catch (IOException e) {
221 logger.debug("Could not send action", e);
222 setOfflineAndReconnect(e, QUICK_RETRY_SECONDS);
227 protected synchronized void refresh() {
228 sendAction(new InfoAction(InfoActionType.SUMMARY));
234 private synchronized void connect() {
235 if (getThing().getStatus() == ThingStatus.ONLINE) {
236 logger.debug("connect: Bridge is already connected");
239 QolsysIQPanelConfiguration config = getConfigAs(QolsysIQPanelConfiguration.class);
243 QolsysiqClient apiClient = new QolsysiqClient(config.hostname, config.port, HEARTBEAT_SECONDS, scheduler,
244 "OH-binding-" + getThing().getUID().getAsString());
246 apiClient.addListener(this);
247 this.apiClient = apiClient;
249 updateStatus(ThingStatus.ONLINE);
250 } catch (IOException e) {
251 logger.debug("Could not connect");
252 setOfflineAndReconnect(e, LONG_RETRY_SECONDS);
257 * Disconnects the client and removes listeners
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;
269 private void startRetryFuture(int seconds) {
271 logger.debug("startRetryFuture");
272 this.retryFuture = scheduler.schedule(this::connect, seconds, TimeUnit.SECONDS);
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;
284 private void setOfflineAndReconnect(Exception reason, int seconds) {
285 logger.debug("setOfflineAndReconnect");
287 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason.getMessage());
288 startRetryFuture(seconds);
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);
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);
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;