]> git.basschouten.com Git - openhab-addons.git/blob
12483d91a405a08cfba5f881faff72affd8647e4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
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;
63
64 /**
65  * The {@link QolsysIQPartitionHandler} manages security partitions
66  *
67  * @author Dan Cunningham - Initial contribution
68  */
69 @NonNullByDefault
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;
80
81     public QolsysIQPartitionHandler(Bridge bridge) {
82         super(bridge);
83     }
84
85     @Override
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();
93     }
94
95     @Override
96     public void dispose() {
97         cancelExitDelayJob();
98         cancelErrorDelayJob();
99     }
100
101     @Override
102     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
103         if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
104             cancelExitDelayJob();
105             cancelErrorDelayJob();
106             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
107         } else {
108             initializePartition();
109         }
110     }
111
112     @Override
113     public void handleCommand(ChannelUID channelUID, Command command) {
114         if (command instanceof RefreshType) {
115             refresh();
116             return;
117         }
118
119         QolsysIQPanelHandler panel = panelHandler();
120         if (panel != null) {
121             if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE)) {
122                 try {
123                     panel.sendAction(new AlarmAction(AlarmActionType.valueOf(command.toString())));
124                 } catch (IllegalArgumentException e) {
125                     logger.debug("Unknown alarm type {} to channel {}", command, channelUID);
126                 }
127                 return;
128             }
129
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();
133                 String code = null;
134                 if (armingTypeName.contains(":")) {
135                     String[] split = armingTypeName.split(":");
136                     armingTypeName = split[0];
137                     if (split.length > 1 && split[1].length() > 0) {
138                         code = split[1];
139                     }
140                 }
141                 try {
142                     ArmingActionType armingType = ArmingActionType.valueOf(armingTypeName);
143                     if (code == null) {
144                         if (armingType == ArmingActionType.DISARM) {
145                             code = disarmCode;
146                         } else {
147                             code = armCode;
148                         }
149                     }
150                     panel.sendAction(new ArmingAction(armingType, getPartitionId(), code));
151                 } catch (IllegalArgumentException e) {
152                     logger.debug("Unknown arm type {} to channel {}", armingTypeName, channelUID);
153                 }
154             }
155         }
156     }
157
158     @Override
159     public Collection<Class<? extends ThingHandlerService>> getServices() {
160         return Set.of(QolsysIQChildDiscoveryService.class);
161     }
162
163     @Override
164     public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
165         this.discoveryService = service;
166     }
167
168     @Override
169     public void startDiscovery() {
170         refresh();
171     }
172
173     /**
174      * The partition id
175      *
176      * @return
177      */
178     public int getPartitionId() {
179         return partitionId;
180     }
181
182     public void zoneActiveEvent(ZoneActiveEvent event) {
183         QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
184         if (handler != null) {
185             handler.zoneActiveEvent(event);
186         }
187     }
188
189     public void zoneUpdateEvent(ZoneUpdateEvent event) {
190         QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
191         if (handler != null) {
192             handler.zoneUpdateEvent(event);
193         }
194     }
195
196     protected void alarmEvent(AlarmEvent event) {
197         if (event.alarmType != AlarmType.NONE && event.alarmType != AlarmType.ZONEOPEN) {
198             updatePartitionStatus(PartitionStatus.ALARM);
199         }
200         updateAlarmState(event.alarmType);
201     }
202
203     protected void armingEvent(ArmingEvent event) {
204         updatePartitionStatus(event.armingType);
205         updateDelay(event.delay == null ? 0 : event.delay);
206     }
207
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);
212     }
213
214     protected void secureArmInfoEvent(SecureArmInfoEvent event) {
215         setSecureArm(event.value);
216     }
217
218     public void zoneAddEvent(ZoneAddEvent event) {
219         discoverZone(event.zone);
220     }
221
222     protected void updatePartition(Partition partition) {
223         updatePartitionStatus(partition.status);
224         setSecureArm(partition.secureArm);
225         if (partition.status != PartitionStatus.ALARM) {
226             updateAlarmState(AlarmType.NONE);
227         }
228         synchronized (zones) {
229             zones.clear();
230             zones.addAll(partition.zoneList);
231             zones.forEach(z -> {
232                 QolsysIQZoneHandler zoneHandler = zoneHandler(z.zoneId);
233                 if (zoneHandler != null) {
234                     zoneHandler.updateZone(z);
235                 }
236             });
237         }
238         if (getThing().getStatus() != ThingStatus.ONLINE) {
239             updateStatus(ThingStatus.ONLINE);
240         }
241         discoverChildDevices();
242     }
243
244     protected @Nullable Zone getZone(Integer zoneId) {
245         synchronized (zones) {
246             return zones.stream().filter(z -> z.zoneId.equals(zoneId)).findAny().orElse(null);
247         }
248     }
249
250     private void initializePartition() {
251         QolsysIQPanelHandler panel = panelHandler();
252         if (panel == null) {
253             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
254         } else if (panel.getThing().getStatus() != ThingStatus.ONLINE) {
255             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
256         } else {
257             updateStatus(ThingStatus.UNKNOWN);
258             scheduler.execute(() -> {
259                 panel.refresh();
260             });
261         }
262     }
263
264     private void refresh() {
265         QolsysIQPanelHandler panel = panelHandler();
266         if (panel != null) {
267             panel.refresh();
268         }
269     }
270
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);
276             updateDelay(0);
277         }
278     }
279
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);
284     }
285
286     private void updateDelay(Integer delay) {
287         cancelExitDelayJob();
288         if (delay <= 0) {
289             updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
290             return;
291         }
292
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();
299             } else {
300                 updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY,
301                         new DecimalType(remaining / 1000));
302             }
303         }, 1, 1, TimeUnit.SECONDS);
304     }
305
306     private void updateAlarmState(AlarmType alarmType) {
307         updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE, new StringType(alarmType.toString()));
308     }
309
310     private void clearErrorEvent() {
311         updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, UnDefType.NULL);
312     }
313
314     private void cancelExitDelayJob() {
315         ScheduledFuture<?> delayFuture = this.delayFuture;
316         if (delayFuture != null) {
317             delayFuture.cancel(true);
318             this.delayFuture = null;
319         }
320         updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
321     }
322
323     private void cancelErrorDelayJob() {
324         ScheduledFuture<?> errorFuture = this.errorFuture;
325         if (errorFuture != null) {
326             errorFuture.cancel(true);
327             this.errorFuture = null;
328         }
329         clearErrorEvent();
330     }
331
332     private void discoverChildDevices() {
333         synchronized (zones) {
334             zones.forEach(z -> discoverZone(z));
335         }
336     }
337
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);
345         }
346     }
347
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) {
353                     return zoneHandler;
354                 }
355             }
356         }
357         return null;
358     }
359
360     private @Nullable QolsysIQPanelHandler panelHandler() {
361         Bridge bridge = getBridge();
362         if (bridge != null) {
363             BridgeHandler handler = bridge.getHandler();
364             if (handler instanceof QolsysIQPanelHandler panelHandler) {
365                 return panelHandler;
366             }
367         }
368         return null;
369     }
370 }