2 * Copyright (c) 2010-2024 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;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
28 import org.openhab.binding.qolsysiq.internal.client.dto.action.AlarmAction;
29 import org.openhab.binding.qolsysiq.internal.client.dto.action.AlarmActionType;
30 import org.openhab.binding.qolsysiq.internal.client.dto.action.ArmingAction;
31 import org.openhab.binding.qolsysiq.internal.client.dto.action.ArmingActionType;
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.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.AlarmType;
40 import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
41 import org.openhab.binding.qolsysiq.internal.client.dto.model.PartitionStatus;
42 import org.openhab.binding.qolsysiq.internal.client.dto.model.Zone;
43 import org.openhab.binding.qolsysiq.internal.config.QolsysIQPartitionConfiguration;
44 import org.openhab.binding.qolsysiq.internal.discovery.QolsysIQChildDiscoveryService;
45 import org.openhab.core.library.types.DecimalType;
46 import org.openhab.core.library.types.StringType;
47 import org.openhab.core.thing.Bridge;
48 import org.openhab.core.thing.ChannelUID;
49 import org.openhab.core.thing.Thing;
50 import org.openhab.core.thing.ThingStatus;
51 import org.openhab.core.thing.ThingStatusDetail;
52 import org.openhab.core.thing.ThingStatusInfo;
53 import org.openhab.core.thing.ThingUID;
54 import org.openhab.core.thing.binding.BaseBridgeHandler;
55 import org.openhab.core.thing.binding.BridgeHandler;
56 import org.openhab.core.thing.binding.ThingHandler;
57 import org.openhab.core.thing.binding.ThingHandlerService;
58 import org.openhab.core.types.Command;
59 import org.openhab.core.types.RefreshType;
60 import org.openhab.core.types.UnDefType;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
65 * The {@link QolsysIQPartitionHandler} manages security partitions
67 * @author Dan Cunningham - Initial contribution
70 public class QolsysIQPartitionHandler extends BaseBridgeHandler implements QolsysIQChildDiscoveryHandler {
71 private final Logger logger = LoggerFactory.getLogger(QolsysIQPartitionHandler.class);
72 private static final int CLEAR_ERROR_MESSSAGE_TIME = 30;
73 private @Nullable QolsysIQChildDiscoveryService discoveryService;
74 private @Nullable ScheduledFuture<?> delayFuture;
75 private @Nullable ScheduledFuture<?> errorFuture;
76 private @Nullable String armCode;
77 private @Nullable String disarmCode;
78 private List<Zone> zones = Collections.synchronizedList(new LinkedList<Zone>());
79 private int partitionId;
81 public QolsysIQPartitionHandler(Bridge bridge) {
86 public void initialize() {
87 QolsysIQPartitionConfiguration config = getConfigAs(QolsysIQPartitionConfiguration.class);
88 partitionId = config.id;
89 armCode = config.armCode.isBlank() ? null : config.armCode;
90 disarmCode = config.disarmCode.isBlank() ? null : config.disarmCode;
91 logger.debug("initialize partition {}", partitionId);
92 initializePartition();
96 public void dispose() {
98 cancelErrorDelayJob();
102 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
103 if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
104 cancelExitDelayJob();
105 cancelErrorDelayJob();
106 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
108 initializePartition();
113 public void handleCommand(ChannelUID channelUID, Command command) {
114 if (command instanceof RefreshType) {
119 QolsysIQPanelHandler panel = panelHandler();
121 if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE)) {
123 panel.sendAction(new AlarmAction(AlarmActionType.valueOf(command.toString())));
124 } catch (IllegalArgumentException e) {
125 logger.debug("Unknown alarm type {} to channel {}", command, channelUID);
130 // support ARM_AWAY and ARM_AWAY:123456 , same for other arm / disarm modes
131 if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ARM_STATE)) {
132 String armingTypeName = command.toString();
134 if (armingTypeName.contains(":")) {
135 String[] split = armingTypeName.split(":");
136 armingTypeName = split[0];
137 if (split.length > 1 && split[1].length() > 0) {
142 ArmingActionType armingType = ArmingActionType.valueOf(armingTypeName);
144 if (armingType == ArmingActionType.DISARM) {
150 panel.sendAction(new ArmingAction(armingType, getPartitionId(), code));
151 } catch (IllegalArgumentException e) {
152 logger.debug("Unknown arm type {} to channel {}", armingTypeName, channelUID);
159 public Collection<Class<? extends ThingHandlerService>> getServices() {
160 return Set.of(QolsysIQChildDiscoveryService.class);
164 public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
165 this.discoveryService = service;
169 public void startDiscovery() {
178 public int getPartitionId() {
182 public void zoneActiveEvent(ZoneActiveEvent event) {
183 QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
184 if (handler != null) {
185 handler.zoneActiveEvent(event);
189 public void zoneUpdateEvent(ZoneUpdateEvent event) {
190 QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
191 if (handler != null) {
192 handler.zoneUpdateEvent(event);
196 protected void alarmEvent(AlarmEvent event) {
197 if (event.alarmType != AlarmType.NONE && event.alarmType != AlarmType.ZONEOPEN) {
198 updatePartitionStatus(PartitionStatus.ALARM);
200 updateAlarmState(event.alarmType);
203 protected void armingEvent(ArmingEvent event) {
204 updatePartitionStatus(event.armingType);
205 updateDelay(event.delay == null ? 0 : event.delay);
208 protected void errorEvent(ErrorEvent event) {
209 cancelErrorDelayJob();
210 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, new StringType(event.description));
211 errorFuture = scheduler.schedule(this::clearErrorEvent, CLEAR_ERROR_MESSSAGE_TIME, TimeUnit.SECONDS);
214 protected void secureArmInfoEvent(SecureArmInfoEvent event) {
215 setSecureArm(event.value);
218 public void zoneAddEvent(ZoneAddEvent event) {
219 discoverZone(event.zone);
222 protected void updatePartition(Partition partition) {
223 updatePartitionStatus(partition.status);
224 setSecureArm(partition.secureArm);
225 if (partition.status != PartitionStatus.ALARM) {
226 updateAlarmState(AlarmType.NONE);
228 synchronized (zones) {
230 zones.addAll(partition.zoneList);
232 QolsysIQZoneHandler zoneHandler = zoneHandler(z.zoneId);
233 if (zoneHandler != null) {
234 zoneHandler.updateZone(z);
238 if (getThing().getStatus() != ThingStatus.ONLINE) {
239 updateStatus(ThingStatus.ONLINE);
241 discoverChildDevices();
244 protected @Nullable Zone getZone(Integer zoneId) {
245 synchronized (zones) {
246 return zones.stream().filter(z -> z.zoneId.equals(zoneId)).findAny().orElse(null);
250 private void initializePartition() {
251 QolsysIQPanelHandler panel = panelHandler();
253 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
254 } else if (panel.getThing().getStatus() != ThingStatus.ONLINE) {
255 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
257 updateStatus(ThingStatus.UNKNOWN);
258 scheduler.execute(() -> {
264 private void refresh() {
265 QolsysIQPanelHandler panel = panelHandler();
271 private void updatePartitionStatus(PartitionStatus status) {
272 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ARM_STATE, new StringType(status.toString()));
273 cancelErrorDelayJob();
274 if (status == PartitionStatus.DISARM) {
275 updateAlarmState(AlarmType.NONE);
280 private void setSecureArm(Boolean secure) {
281 Map<String, String> props = new HashMap<String, String>();
282 props.put("secureArm", String.valueOf(secure));
283 getThing().setProperties(props);
286 private void updateDelay(Integer delay) {
287 cancelExitDelayJob();
289 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
293 final long endTime = System.currentTimeMillis() + (delay * 1000);
294 delayFuture = scheduler.scheduleAtFixedRate(() -> {
295 long remaining = endTime - System.currentTimeMillis();
296 logger.debug("updateDelay remaining {}", remaining / 1000);
297 if (remaining <= 0) {
298 cancelExitDelayJob();
300 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY,
301 new DecimalType(remaining / 1000));
303 }, 1, 1, TimeUnit.SECONDS);
306 private void updateAlarmState(AlarmType alarmType) {
307 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE, new StringType(alarmType.toString()));
310 private void clearErrorEvent() {
311 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, UnDefType.NULL);
314 private void cancelExitDelayJob() {
315 ScheduledFuture<?> delayFuture = this.delayFuture;
316 if (delayFuture != null) {
317 delayFuture.cancel(true);
318 this.delayFuture = null;
320 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
323 private void cancelErrorDelayJob() {
324 ScheduledFuture<?> errorFuture = this.errorFuture;
325 if (errorFuture != null) {
326 errorFuture.cancel(true);
327 this.errorFuture = null;
332 private void discoverChildDevices() {
333 synchronized (zones) {
334 zones.forEach(z -> discoverZone(z));
338 private void discoverZone(Zone z) {
339 QolsysIQChildDiscoveryService discoveryService = this.discoveryService;
340 if (discoveryService != null) {
341 ThingUID bridgeUID = getThing().getUID();
342 ThingUID thingUID = new ThingUID(QolsysIQBindingConstants.THING_TYPE_ZONE, bridgeUID,
343 String.valueOf(z.zoneId));
344 discoveryService.discoverQolsysIQChildThing(thingUID, bridgeUID, z.zoneId, "Qolsys IQ Zone: " + z.name);
348 private @Nullable QolsysIQZoneHandler zoneHandler(int zoneId) {
349 for (Thing thing : getThing().getThings()) {
350 ThingHandler handler = thing.getHandler();
351 if (handler instanceof QolsysIQZoneHandler zoneHandler) {
352 if (zoneHandler.getZoneId() == zoneId) {
360 private @Nullable QolsysIQPanelHandler panelHandler() {
361 Bridge bridge = getBridge();
362 if (bridge != null) {
363 BridgeHandler handler = bridge.getHandler();
364 if (handler instanceof QolsysIQPanelHandler panelHandler) {