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