]> git.basschouten.com Git - openhab-addons.git/blob
cd0c2517ee043f72b7e3741acc498267cec55c76
[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.verisure.internal.handler;
14
15 import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.time.ZoneId;
19 import java.time.ZonedDateTime;
20 import java.util.ArrayList;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.verisure.internal.DeviceStatusListener;
26 import org.openhab.binding.verisure.internal.VerisureSession;
27 import org.openhab.binding.verisure.internal.VerisureThingConfiguration;
28 import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
29 import org.openhab.core.library.types.DateTimeType;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.StringType;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusInfo;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.thing.binding.BridgeHandler;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.RefreshType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import com.google.gson.Gson;
45
46 /**
47  * Base class and handler for some of the different thing types that Verisure provides.
48  *
49  * @author Jarle Hjortland - Initial contribution
50  * @author Jan Gustafsson - Further development
51  *
52  */
53 @NonNullByDefault
54 public abstract class VerisureThingHandler<T extends VerisureThingDTO> extends BaseThingHandler
55         implements DeviceStatusListener<T> {
56
57     protected final Logger logger = LoggerFactory.getLogger(VerisureThingHandler.class);
58     protected final Gson gson = new Gson();
59     protected VerisureThingConfiguration config = new VerisureThingConfiguration();
60
61     public VerisureThingHandler(Thing thing) {
62         super(thing);
63     }
64
65     @Override
66     public void handleCommand(ChannelUID channelUID, Command command) {
67         logger.debug("VerisureThingHandler handleCommand, channel: {}, command: {}", channelUID, command);
68         if (command instanceof RefreshType) {
69             Bridge bridge = getBridge();
70             if (bridge != null) {
71                 BridgeHandler bridgeHandler = bridge.getHandler();
72                 if (bridgeHandler != null) {
73                     bridgeHandler.handleCommand(channelUID, command);
74                     String deviceId = config.getDeviceId();
75                     VerisureSession session = getSession();
76                     if (session != null) {
77                         @Nullable
78                         T thing = session.getVerisureThing(deviceId, getVerisureThingClass());
79                         if (thing != null) {
80                             update(thing);
81                         } else {
82                             logger.trace("Thing is null!");
83                         }
84                     } else {
85                         logger.debug("Session is null!");
86                     }
87                 } else {
88                     logger.debug("BridgeHandler is null!");
89                 }
90             } else {
91                 logger.warn("Bridge is null!");
92             }
93         } else {
94             logger.warn("Unknown command! {}", command);
95         }
96     }
97
98     @Override
99     public void initialize() {
100         // Do not go online
101         config = getConfigAs(VerisureThingConfiguration.class);
102         // Set status to UNKNOWN and let background task set correct status
103         updateStatus(ThingStatus.UNKNOWN);
104         Bridge bridge = getBridge();
105         if (bridge != null) {
106             this.bridgeStatusChanged(bridge.getStatusInfo());
107         }
108     }
109
110     @Override
111     public void dispose() {
112         logger.debug("dispose on thing: {}", thing);
113         VerisureSession session = getSession();
114         if (session != null) {
115             session.unregisterDeviceStatusListener(this);
116             session.removeVerisureThingHandler(config.getDeviceId());
117         }
118     }
119
120     @Override
121     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
122         if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
123             VerisureSession session = getSession();
124             if (session != null) {
125                 String deviceId = config.getDeviceId();
126                 @Nullable
127                 T thing = session.getVerisureThing(deviceId, getVerisureThingClass());
128                 if (thing != null) {
129                     update(thing);
130                 } else {
131                     logger.warn("Please check that you have configured correct deviceId for thing!");
132                 }
133                 session.registerDeviceStatusListener(this);
134                 session.setVerisureThingHandler(this, config.getDeviceId());
135             }
136         }
137         super.bridgeStatusChanged(bridgeStatusInfo);
138     }
139
140     @Override
141     public void onDeviceStateChanged(T thing) {
142         String deviceId = thing.getDeviceId();
143         // Make sure device id is normalized
144         if (config.getDeviceId().equalsIgnoreCase((VerisureThingConfiguration.normalizeDeviceId(deviceId)))) {
145             update(thing);
146         }
147     }
148
149     public abstract void update(T thing);
150
151     public abstract void updateTriggerChannel(String event);
152
153     protected void updateInstallationChannels(T thing) {
154         BigDecimal siteId = thing.getSiteId();
155         updateState(CHANNEL_INSTALLATION_ID, new DecimalType(siteId.longValue()));
156         updateState(CHANNEL_INSTALLATION_NAME, new StringType(thing.getSiteName()));
157     }
158
159     protected void updateTriggerChannel(ArrayList<Event> newEvents) {
160         VerisureSession session = getSession();
161         int delay = 1;
162         for (Event newEvent : newEvents) {
163             String deviceId = newEvent.getDeviceId();
164             String eventType = newEvent.getEventType();
165             String eventCategory = newEvent.getEventCategory();
166             logger.debug("Trigger event type {}, event category {} for thing {}", eventType, eventCategory, deviceId);
167             if (session != null && eventType != null && deviceId != null) {
168                 String deviceIdTransformed = VerisureThingConfiguration.normalizeDeviceId(deviceId);
169                 @Nullable
170                 T thing = session.getVerisureThing(deviceIdTransformed);
171                 if (thing != null) {
172                     logger.debug("Trigger event {} on deviceId {} on  thing {}", eventType, deviceIdTransformed, thing);
173                     VerisureThingHandler<?> vth = session.getVerisureThinghandler(deviceIdTransformed);
174                     if (vth == null) {
175                         logger.debug("No VerisureThingHandler found for thing {}", thing);
176                         return;
177                     }
178                     String eventTranslation = "UNKNOWN_EVENT_TYPE";
179                     switch (eventType) {
180                         case "BA":
181                             eventTranslation = TRIGGER_EVENT_INSTRUSION;
182                             break;
183                         case "FA":
184                             eventTranslation = TRIGGER_EVENT_FIRE;
185                             break;
186                         case "XT":
187                             eventTranslation = TRIGGER_EVENT_BATTERY_LOW;
188                             break;
189                         case "XR":
190                             eventTranslation = TRIGGER_EVENT_BATTERY_RESTORED;
191                             break;
192                         case "SB":
193                         case "BP":
194                             eventTranslation = TRIGGER_EVENT_COM_TEST;
195                             break;
196                         case "YC":
197                             eventTranslation = TRIGGER_EVENT_COM_FAILURE;
198                             break;
199                         case "YK":
200                             eventTranslation = TRIGGER_EVENT_COM_RESTORED;
201                             break;
202                         case "TA":
203                             eventTranslation = TRIGGER_EVENT_SABOTAGE_ALARM;
204                             break;
205                         case "TR":
206                             eventTranslation = TRIGGER_EVENT_SABOTAGE_RESTORED;
207                             break;
208                         case "CO":
209                         case "CL":
210                         case "CT":
211                             eventTranslation = TRIGGER_EVENT_ARM;
212                             break;
213                         case "OP":
214                         case "OO":
215                         case "OT":
216                         case "OH":
217                             eventTranslation = TRIGGER_EVENT_DISARM;
218                             break;
219                         case "LM":
220                         case "LO":
221                         case "LC":
222                         case "LD":
223                             eventTranslation = TRIGGER_EVENT_LOCK;
224                             break;
225                         case "FK":
226                             eventTranslation = TRIGGER_EVENT_LOCK_FAILURE;
227                             break;
228                         case "UA":
229                         case "DC":
230                         case "DO":
231                         case "DK":
232                             eventTranslation = TRIGGER_EVENT_UNLOCK;
233                             break;
234                         case "WA":
235                             eventTranslation = TRIGGER_EVENT_WATER;
236                             break;
237                         case "IA":
238                             eventTranslation = TRIGGER_EVENT_MICE;
239                             break;
240                         case "DOORWINDOW_STATE_CHANGE_OPENED":
241                             eventTranslation = TRIGGER_EVENT_DOORWINDOW_OPENED;
242                             break;
243                         case "DOORWINDOW_STATE_CHANGE_CLOSED":
244                             eventTranslation = TRIGGER_EVENT_DOORWINDOW_CLOSED;
245                             break;
246                         case "LOCATION_HOME":
247                             eventTranslation = TRIGGER_EVENT_LOCATION_HOME;
248                             break;
249                         case "LOCATION_AWAY":
250                             eventTranslation = TRIGGER_EVENT_LOCATION_AWAY;
251                             break;
252                         default:
253                             logger.debug("Unhandled event type: {}, event category: {}", eventType, eventCategory);
254                     }
255                     logger.debug("Schedule vth {} and event {} with delay {}", vth, eventTranslation, delay);
256                     scheduler.schedule(new EventTrigger(vth, eventTranslation), delay, TimeUnit.MILLISECONDS);
257                     delay = delay + config.getEventTriggerDelay();
258                 } else {
259                     logger.debug("Thing is null!");
260                 }
261             }
262         }
263     }
264
265     protected void updateTimeStamp(@Nullable String lastUpdatedTimeStamp) {
266         updateTimeStamp(lastUpdatedTimeStamp, CHANNEL_TIMESTAMP);
267     }
268
269     protected void updateTimeStamp(@Nullable String lastUpdatedTimeStamp, ChannelUID cuid) {
270         if (lastUpdatedTimeStamp != null) {
271             try {
272                 logger.trace("Parsing date {} for channel {}", lastUpdatedTimeStamp, cuid);
273                 ZonedDateTime zdt = ZonedDateTime.parse(lastUpdatedTimeStamp);
274                 ZonedDateTime zdtLocal = zdt.withZoneSameInstant(ZoneId.systemDefault());
275                 logger.trace("Parsing datetime successful. Using date. {}", new DateTimeType(zdtLocal));
276                 updateState(cuid, new DateTimeType(zdtLocal));
277             } catch (IllegalArgumentException e) {
278                 logger.warn("Parsing date failed: {}.", e.getMessage(), e);
279             }
280         } else {
281             logger.debug("Timestamp is null!");
282         }
283     }
284
285     protected void updateTimeStamp(@Nullable String lastUpdatedTimeStamp, String channel) {
286         ChannelUID cuid = new ChannelUID(getThing().getUID(), channel);
287         updateTimeStamp(lastUpdatedTimeStamp, cuid);
288     }
289
290     protected @Nullable VerisureSession getSession() {
291         Bridge bridge = getBridge();
292         if (bridge != null) {
293             VerisureBridgeHandler vbh = (VerisureBridgeHandler) bridge.getHandler();
294             if (vbh != null) {
295                 return vbh.getSession();
296             }
297         }
298         return null;
299     }
300
301     protected void scheduleImmediateRefresh(int refreshDelay) {
302         logger.debug("scheduleImmediateRefresh on thing: {}", thing);
303         Bridge bridge = getBridge();
304         if (bridge != null && bridge.getHandler() != null) {
305             VerisureBridgeHandler vbh = (VerisureBridgeHandler) bridge.getHandler();
306             if (vbh != null) {
307                 vbh.scheduleImmediateRefresh(refreshDelay);
308             }
309         }
310     }
311
312     private class EventTrigger implements Runnable {
313         private @Nullable VerisureThingHandler<?> vth;
314         private @Nullable String event;
315
316         public EventTrigger(@Nullable VerisureThingHandler<?> vth, @Nullable String event) {
317             this.vth = vth;
318             this.event = event;
319         }
320
321         @Override
322         public void run() {
323             logger.debug("Trigger Event {} on {} at time {}", event, vth, ZonedDateTime.now());
324             String localEvent = event;
325             if (vth != null && localEvent != null) {
326                 vth.updateTriggerChannel(localEvent);
327             }
328         }
329     }
330
331     protected class Event {
332         private @Nullable String deviceId;
333         private @Nullable String eventType;
334         private @Nullable String eventCategory;
335
336         public Event(@Nullable String deviceId, @Nullable String eventType, @Nullable String eventCategory) {
337             this.deviceId = deviceId;
338             this.eventType = eventType;
339             this.eventCategory = eventCategory;
340         }
341
342         public @Nullable String getDeviceId() {
343             return deviceId;
344         }
345
346         public @Nullable String getEventType() {
347             return eventType;
348         }
349
350         public @Nullable String getEventCategory() {
351             return eventCategory;
352         }
353     }
354 }