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.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
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.dto.action.AlarmAction;
28 import org.openhab.binding.qolsysiq.internal.client.dto.action.AlarmActionType;
29 import org.openhab.binding.qolsysiq.internal.client.dto.action.ArmingAction;
30 import org.openhab.binding.qolsysiq.internal.client.dto.action.ArmingActionType;
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.ZoneActiveEvent;
36 import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
37 import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
38 import org.openhab.binding.qolsysiq.internal.client.dto.model.AlarmType;
39 import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
40 import org.openhab.binding.qolsysiq.internal.client.dto.model.PartitionStatus;
41 import org.openhab.binding.qolsysiq.internal.client.dto.model.Zone;
42 import org.openhab.binding.qolsysiq.internal.config.QolsysIQPartitionConfiguration;
43 import org.openhab.binding.qolsysiq.internal.discovery.QolsysIQChildDiscoveryService;
44 import org.openhab.core.library.types.DecimalType;
45 import org.openhab.core.library.types.StringType;
46 import org.openhab.core.thing.Bridge;
47 import org.openhab.core.thing.ChannelUID;
48 import org.openhab.core.thing.Thing;
49 import org.openhab.core.thing.ThingStatus;
50 import org.openhab.core.thing.ThingStatusDetail;
51 import org.openhab.core.thing.ThingStatusInfo;
52 import org.openhab.core.thing.ThingUID;
53 import org.openhab.core.thing.binding.BaseBridgeHandler;
54 import org.openhab.core.thing.binding.BridgeHandler;
55 import org.openhab.core.thing.binding.ThingHandler;
56 import org.openhab.core.thing.binding.ThingHandlerService;
57 import org.openhab.core.types.Command;
58 import org.openhab.core.types.RefreshType;
59 import org.openhab.core.types.UnDefType;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
64 * The {@link QolsysIQPartitionHandler} manages security partitions
66 * @author Dan Cunningham - Initial contribution
69 public class QolsysIQPartitionHandler extends BaseBridgeHandler implements QolsysIQChildDiscoveryHandler {
70 private final Logger logger = LoggerFactory.getLogger(QolsysIQPartitionHandler.class);
71 private static final int CLEAR_ERROR_MESSSAGE_TIME = 30;
72 private @Nullable QolsysIQChildDiscoveryService discoveryService;
73 private @Nullable ScheduledFuture<?> delayFuture;
74 private @Nullable ScheduledFuture<?> errorFuture;
75 private @Nullable String armCode;
76 private @Nullable String disarmCode;
77 private List<Zone> zones = Collections.synchronizedList(new LinkedList<Zone>());
78 private int partitionId;
80 public QolsysIQPartitionHandler(Bridge bridge) {
85 public void initialize() {
86 QolsysIQPartitionConfiguration config = getConfigAs(QolsysIQPartitionConfiguration.class);
87 partitionId = config.id;
88 armCode = config.armCode.isBlank() ? null : config.armCode;
89 disarmCode = config.disarmCode.isBlank() ? null : config.disarmCode;
90 logger.debug("initialize partition {}", partitionId);
91 initializePartition();
95 public void dispose() {
97 cancelErrorDelayJob();
101 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
102 if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
103 cancelExitDelayJob();
104 cancelErrorDelayJob();
105 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
107 initializePartition();
112 public void handleCommand(ChannelUID channelUID, Command command) {
113 if (command instanceof RefreshType) {
118 QolsysIQPanelHandler panel = panelHandler();
120 if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE)) {
122 panel.sendAction(new AlarmAction(AlarmActionType.valueOf(command.toString())));
123 } catch (IllegalArgumentException e) {
124 logger.debug("Unknown alarm type {} to channel {}", command, channelUID);
129 // support ARM_AWAY and ARM_AWAY:123456 , same for other arm / disarm modes
130 if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ARM_STATE)) {
131 String armingTypeName = command.toString();
133 if (armingTypeName.contains(":")) {
134 String[] split = armingTypeName.split(":");
135 armingTypeName = split[0];
136 if (split.length > 1 && split[1].length() > 0) {
141 ArmingActionType armingType = ArmingActionType.valueOf(armingTypeName);
143 if (armingType == ArmingActionType.DISARM) {
149 panel.sendAction(new ArmingAction(armingType, getPartitionId(), code));
150 } catch (IllegalArgumentException e) {
151 logger.debug("Unknown arm type {} to channel {}", armingTypeName, channelUID);
158 public Collection<Class<? extends ThingHandlerService>> getServices() {
159 return Collections.singleton(QolsysIQChildDiscoveryService.class);
163 public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
164 this.discoveryService = service;
168 public void startDiscovery() {
177 public int getPartitionId() {
181 public void zoneActiveEvent(ZoneActiveEvent event) {
182 QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
183 if (handler != null) {
184 handler.zoneActiveEvent(event);
188 public void zoneUpdateEvent(ZoneUpdateEvent event) {
189 QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
190 if (handler != null) {
191 handler.zoneUpdateEvent(event);
195 protected void alarmEvent(AlarmEvent event) {
196 if (event.alarmType != AlarmType.NONE && event.alarmType != AlarmType.ZONEOPEN) {
197 updatePartitionStatus(PartitionStatus.ALARM);
199 updateAlarmState(event.alarmType);
202 protected void armingEvent(ArmingEvent event) {
203 updatePartitionStatus(event.armingType);
204 updateDelay(event.delay == null ? 0 : event.delay);
207 protected void errorEvent(ErrorEvent event) {
208 cancelErrorDelayJob();
209 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, new StringType(event.description));
210 errorFuture = scheduler.schedule(this::clearErrorEvent, CLEAR_ERROR_MESSSAGE_TIME, TimeUnit.SECONDS);
213 protected void secureArmInfoEvent(SecureArmInfoEvent event) {
214 setSecureArm(event.value);
217 public void zoneAddEvent(ZoneAddEvent event) {
218 discoverZone(event.zone);
221 protected void updatePartition(Partition partition) {
222 updatePartitionStatus(partition.status);
223 setSecureArm(partition.secureArm);
224 if (partition.status != PartitionStatus.ALARM) {
225 updateAlarmState(AlarmType.NONE);
227 synchronized (zones) {
229 zones.addAll(partition.zoneList);
231 QolsysIQZoneHandler zoneHandler = zoneHandler(z.zoneId);
232 if (zoneHandler != null) {
233 zoneHandler.updateZone(z);
237 if (getThing().getStatus() != ThingStatus.ONLINE) {
238 updateStatus(ThingStatus.ONLINE);
240 discoverChildDevices();
243 protected @Nullable Zone getZone(Integer zoneId) {
244 synchronized (zones) {
245 return zones.stream().filter(z -> z.zoneId.equals(zoneId)).findAny().orElse(null);
249 private void initializePartition() {
250 QolsysIQPanelHandler panel = panelHandler();
252 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
253 } else if (panel.getThing().getStatus() != ThingStatus.ONLINE) {
254 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
256 updateStatus(ThingStatus.UNKNOWN);
257 scheduler.execute(() -> {
263 private void refresh() {
264 QolsysIQPanelHandler panel = panelHandler();
270 private void updatePartitionStatus(PartitionStatus status) {
271 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ARM_STATE, new StringType(status.toString()));
272 cancelErrorDelayJob();
273 if (status == PartitionStatus.DISARM) {
274 updateAlarmState(AlarmType.NONE);
279 private void setSecureArm(Boolean secure) {
280 Map<String, String> props = new HashMap<String, String>();
281 props.put("secureArm", String.valueOf(secure));
282 getThing().setProperties(props);
285 private void updateDelay(Integer delay) {
286 cancelExitDelayJob();
288 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
292 final long endTime = System.currentTimeMillis() + (delay * 1000);
293 delayFuture = scheduler.scheduleAtFixedRate(() -> {
294 long remaining = endTime - System.currentTimeMillis();
295 logger.debug("updateDelay remaining {}", remaining / 1000);
296 if (remaining <= 0) {
297 cancelExitDelayJob();
299 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY,
300 new DecimalType(remaining / 1000));
302 }, 1, 1, TimeUnit.SECONDS);
305 private void updateAlarmState(AlarmType alarmType) {
306 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE, new StringType(alarmType.toString()));
309 private void clearErrorEvent() {
310 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, UnDefType.NULL);
313 private void cancelExitDelayJob() {
314 ScheduledFuture<?> delayFuture = this.delayFuture;
315 if (delayFuture != null) {
316 delayFuture.cancel(true);
317 this.delayFuture = null;
319 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
322 private void cancelErrorDelayJob() {
323 ScheduledFuture<?> errorFuture = this.errorFuture;
324 if (errorFuture != null) {
325 errorFuture.cancel(true);
326 this.errorFuture = null;
331 private void discoverChildDevices() {
332 synchronized (zones) {
333 zones.forEach(z -> discoverZone(z));
337 private void discoverZone(Zone z) {
338 QolsysIQChildDiscoveryService discoveryService = this.discoveryService;
339 if (discoveryService != null) {
340 ThingUID bridgeUID = getThing().getUID();
341 ThingUID thingUID = new ThingUID(QolsysIQBindingConstants.THING_TYPE_ZONE, bridgeUID,
342 String.valueOf(z.zoneId));
343 discoveryService.discoverQolsysIQChildThing(thingUID, bridgeUID, z.zoneId, "Qolsys IQ Zone: " + z.name);
347 private @Nullable QolsysIQZoneHandler zoneHandler(int zoneId) {
348 for (Thing thing : getThing().getThings()) {
349 ThingHandler handler = thing.getHandler();
350 if (handler instanceof QolsysIQZoneHandler) {
351 if (((QolsysIQZoneHandler) handler).getZoneId() == zoneId) {
352 return (QolsysIQZoneHandler) handler;
359 private @Nullable QolsysIQPanelHandler panelHandler() {
360 Bridge bridge = getBridge();
361 if (bridge != null) {
362 BridgeHandler handler = bridge.getHandler();
363 if (handler instanceof QolsysIQPanelHandler) {
364 return (QolsysIQPanelHandler) handler;