]> git.basschouten.com Git - openhab-addons.git/blob
a27df713037d22ab75fddf934fc0e5e07de899af
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.zoneminder.internal.handler;
14
15 import static org.openhab.binding.zoneminder.internal.ZmBindingConstants.*;
16
17 import java.time.ZonedDateTime;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.Map;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import javax.measure.quantity.Time;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.zoneminder.action.ZmActions;
30 import org.openhab.binding.zoneminder.internal.config.ZmMonitorConfig;
31 import org.openhab.core.i18n.TimeZoneProvider;
32 import org.openhab.core.library.types.DateTimeType;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.RawType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.library.unit.SmartHomeUnits;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.thing.binding.ThingHandlerService;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.openhab.core.types.State;
47 import org.openhab.core.types.UnDefType;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * The {@link ZmMonitorHandler} represents a Zoneminder monitor. The monitor handler
53  * interacts with the server bridge to communicate with the Zoneminder server.
54  *
55  * @author Mark Hilbush - Initial contribution
56  */
57 @NonNullByDefault
58 public class ZmMonitorHandler extends BaseThingHandler {
59
60     private final Logger logger = LoggerFactory.getLogger(ZmMonitorHandler.class);
61
62     private final TimeZoneProvider timeZoneProvider;
63
64     private @Nullable ZmBridgeHandler bridgeHandler;
65
66     private @NonNullByDefault({}) String monitorId;
67     private @Nullable Integer imageRefreshIntervalSeconds;
68     private Integer alarmDuration = DEFAULT_ALARM_DURATION_SECONDS;
69
70     private @Nullable ScheduledFuture<?> imageRefreshJob;
71     private @Nullable ScheduledFuture<?> alarmOffJob;
72
73     private final Map<String, State> monitorStatusCache = new ConcurrentHashMap<>();
74
75     public ZmMonitorHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
76         super(thing);
77         this.timeZoneProvider = timeZoneProvider;
78     }
79
80     @Override
81     public void initialize() {
82         ZmMonitorConfig config = getConfigAs(ZmMonitorConfig.class);
83         monitorId = config.monitorId;
84         imageRefreshIntervalSeconds = config.imageRefreshInterval;
85         Integer value = config.alarmDuration;
86         alarmDuration = value != null ? value : DEFAULT_ALARM_DURATION_SECONDS;
87         bridgeHandler = (ZmBridgeHandler) getBridge().getHandler();
88         monitorStatusCache.clear();
89         updateStatus(ThingStatus.ONLINE);
90         startImageRefreshJob();
91     }
92
93     @Override
94     public void dispose() {
95         stopAlarmOffJob();
96         turnAlarmOff();
97         stopImageRefreshJob();
98     }
99
100     @Override
101     public void handleCommand(ChannelUID channelUID, Command command) {
102         if (command instanceof RefreshType) {
103             State state = monitorStatusCache.get(channelUID.getId());
104             if (state != null) {
105                 updateState(channelUID, state);
106             }
107             return;
108         }
109         logger.debug("Monitor {}: Received command '{}' for channel '{}'", monitorId, command, channelUID.getId());
110         ZmBridgeHandler localHandler = bridgeHandler;
111         if (localHandler == null) {
112             logger.warn("Monitor {}: Can't execute command because bridge handler is null", monitorId);
113             return;
114         }
115         switch (channelUID.getId()) {
116             case CHANNEL_FUNCTION:
117                 if (command instanceof StringType) {
118                     try {
119                         MonitorFunction function = MonitorFunction.forValue(command.toString());
120                         localHandler.setFunction(monitorId, function);
121                         logger.debug("Monitor {}: Set monitor state to {}", monitorId, function);
122                     } catch (IllegalArgumentException e) {
123                         logger.debug("Monitor {}: Invalid function: {}", monitorId, command);
124                     }
125                 }
126                 break;
127             case CHANNEL_ENABLE:
128                 if (command instanceof OnOffType) {
129                     localHandler.setEnabled(monitorId, (OnOffType) command);
130                     logger.debug("Monitor {}: Set monitor enable to {}", monitorId, command);
131                 }
132                 break;
133             case CHANNEL_TRIGGER_ALARM:
134                 if (command instanceof OnOffType) {
135                     logger.debug("Monitor {}: Set monitor alarm to {}", monitorId, command);
136                     if (command == OnOffType.ON) {
137                         localHandler.setAlarmOn(monitorId);
138                         startAlarmOffJob(alarmDuration.intValue());
139                     } else {
140                         stopAlarmOffJob();
141                         localHandler.setAlarmOff(monitorId);
142                     }
143                 }
144                 break;
145         }
146     }
147
148     @Override
149     public Collection<Class<? extends ThingHandlerService>> getServices() {
150         return Collections.singleton(ZmActions.class);
151     }
152
153     public String getId() {
154         return monitorId;
155     }
156
157     public void actionTriggerAlarm(@Nullable Number duration) {
158         if (duration == null) {
159             return;
160         }
161         ZmBridgeHandler localHandler = bridgeHandler;
162         if (localHandler != null) {
163             logger.debug("Monitor {}: Action tell bridge to turn on alarm", monitorId);
164             localHandler.setAlarmOn(monitorId);
165             startAlarmOffJob(duration.intValue());
166         }
167     }
168
169     public void actionTriggerAlarm() {
170         actionTriggerAlarm(alarmDuration);
171     }
172
173     public void actionCancelAlarm() {
174         ZmBridgeHandler localHandler = bridgeHandler;
175         if (localHandler != null) {
176             logger.debug("Monitor {}: Action tell bridge to turn off alarm", monitorId);
177             stopAlarmOffJob();
178             localHandler.setAlarmOff(monitorId);
179         }
180     }
181
182     @SuppressWarnings("null")
183     public void updateStatus(Monitor m) {
184         logger.debug("Monitor {}: Updating: {}", m.getId(), m.toString());
185         updateChannelState(CHANNEL_ID, new StringType(m.getId()));
186         updateChannelState(CHANNEL_NAME, new StringType(m.getName()));
187         updateChannelState(CHANNEL_FUNCTION, new StringType(m.getFunction()));
188         updateChannelState(CHANNEL_ENABLE, m.isEnabled() ? OnOffType.ON : OnOffType.OFF);
189         updateChannelState(CHANNEL_HOUR_EVENTS, new DecimalType(m.getHourEvents()));
190         updateChannelState(CHANNEL_DAY_EVENTS, new DecimalType(m.getDayEvents()));
191         updateChannelState(CHANNEL_WEEK_EVENTS, new DecimalType(m.getWeekEvents()));
192         updateChannelState(CHANNEL_MONTH_EVENTS, new DecimalType(m.getMonthEvents()));
193         updateChannelState(CHANNEL_TOTAL_EVENTS, new DecimalType(m.getTotalEvents()));
194         updateChannelState(CHANNEL_IMAGE_URL, new StringType(m.getImageUrl()));
195         updateChannelState(CHANNEL_VIDEO_URL, new StringType(m.getVideoUrl()));
196         updateChannelState(CHANNEL_ALARM, m.isAlarm() ? OnOffType.ON : OnOffType.OFF);
197         updateChannelState(CHANNEL_STATE, new StringType(m.getState().toString()));
198         if (!m.isAlarm()) {
199             updateChannelState(CHANNEL_TRIGGER_ALARM, m.isAlarm() ? OnOffType.ON : OnOffType.OFF);
200         }
201         Event event = m.getLastEvent();
202         if (event == null) {
203             clearEventChannels();
204         } else if (event.getEnd() != null) {
205             // If end is null, assume event hasn't completed yet
206             logger.trace("Monitor {}: Id:{}, Frames:{}, AlarmFrames:{}, Length:{}", m.getId(), event.getId(),
207                     event.getFrames(), event.getAlarmFrames(), event.getLength());
208             updateChannelState(CHANNEL_EVENT_ID, new StringType(event.getId()));
209             updateChannelState(CHANNEL_EVENT_NAME, new StringType(event.getName()));
210             updateChannelState(CHANNEL_EVENT_CAUSE, new StringType(event.getCause()));
211             updateChannelState(CHANNEL_EVENT_NOTES, new StringType(event.getNotes()));
212             updateChannelState(CHANNEL_EVENT_START, new DateTimeType(
213                     ZonedDateTime.ofInstant(event.getStart().toInstant(), timeZoneProvider.getTimeZone())));
214             updateChannelState(CHANNEL_EVENT_END, new DateTimeType(
215                     ZonedDateTime.ofInstant(event.getEnd().toInstant(), timeZoneProvider.getTimeZone())));
216             updateChannelState(CHANNEL_EVENT_FRAMES, new DecimalType(event.getFrames()));
217             updateChannelState(CHANNEL_EVENT_ALARM_FRAMES, new DecimalType(event.getAlarmFrames()));
218             updateChannelState(CHANNEL_EVENT_LENGTH, new QuantityType<Time>(event.getLength(), SmartHomeUnits.SECOND));
219         }
220     }
221
222     private void clearEventChannels() {
223         updateChannelState(CHANNEL_EVENT_ID, UnDefType.NULL);
224         updateChannelState(CHANNEL_EVENT_NAME, UnDefType.NULL);
225         updateChannelState(CHANNEL_EVENT_CAUSE, UnDefType.NULL);
226         updateChannelState(CHANNEL_EVENT_NOTES, UnDefType.NULL);
227         updateChannelState(CHANNEL_EVENT_START, UnDefType.NULL);
228         updateChannelState(CHANNEL_EVENT_END, UnDefType.NULL);
229         updateChannelState(CHANNEL_EVENT_FRAMES, UnDefType.NULL);
230         updateChannelState(CHANNEL_EVENT_ALARM_FRAMES, UnDefType.NULL);
231         updateChannelState(CHANNEL_EVENT_LENGTH, UnDefType.NULL);
232     }
233
234     private void refreshImage() {
235         if (isLinked(CHANNEL_IMAGE)) {
236             getImage();
237         } else {
238             logger.trace("Monitor {}: Can't update image because '{}' channel is not linked", CHANNEL_IMAGE, monitorId);
239         }
240     }
241
242     private void getImage() {
243         ZmBridgeHandler localHandler = bridgeHandler;
244         if (localHandler != null) {
245             logger.debug("Monitor {}: Updating image channel", monitorId);
246             RawType image = localHandler.getImage(monitorId, imageRefreshIntervalSeconds);
247             updateChannelState(CHANNEL_IMAGE, image != null ? image : UnDefType.UNDEF);
248         }
249     }
250
251     private void updateChannelState(String channelId, State state) {
252         updateState(channelId, state);
253         monitorStatusCache.put(channelId, state);
254     }
255
256     private void startImageRefreshJob() {
257         Integer interval = imageRefreshIntervalSeconds;
258         if (interval != null) {
259             long delay = getRandomDelay(interval);
260             imageRefreshJob = scheduler.scheduleWithFixedDelay(this::refreshImage, delay, interval, TimeUnit.SECONDS);
261             logger.debug("Monitor {}: Scheduled image refresh job will run every {} seconds starting in {} seconds",
262                     monitorId, interval, delay);
263         }
264     }
265
266     private void stopImageRefreshJob() {
267         ScheduledFuture<?> localImageRefreshJob = imageRefreshJob;
268         if (localImageRefreshJob != null) {
269             logger.debug("Monitor {}: Canceled image refresh job", monitorId);
270             localImageRefreshJob.cancel(true);
271             imageRefreshJob = null;
272         }
273     }
274
275     private void turnAlarmOff() {
276         ZmBridgeHandler localHandler = bridgeHandler;
277         if (alarmOffJob != null && localHandler != null) {
278             logger.debug("Monitor {}: Tell bridge to turn off alarm", monitorId);
279             localHandler.setAlarmOff(monitorId);
280         }
281     }
282
283     private void startAlarmOffJob(int duration) {
284         stopAlarmOffJob();
285         if (duration != 0) {
286             alarmOffJob = scheduler.schedule(this::turnAlarmOff, duration, TimeUnit.SECONDS);
287             logger.debug("Monitor {}: Scheduled alarm off job in {} seconds", monitorId, duration);
288         }
289     }
290
291     private void stopAlarmOffJob() {
292         ScheduledFuture<?> localAlarmOffJob = alarmOffJob;
293         if (localAlarmOffJob != null) {
294             logger.debug("Monitor {}: Canceled alarm off job", monitorId);
295             localAlarmOffJob.cancel(true);
296             alarmOffJob = null;
297         }
298     }
299
300     private long getRandomDelay(int interval) {
301         return System.currentTimeMillis() % interval;
302     }
303 }