]> git.basschouten.com Git - openhab-addons.git/blob
45619e94b0969de6ddf613fe1d5c53e30bd94fda
[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.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.internal.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.Units;
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 Monitor: {}", m.getId(), m);
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.getMostRecentCompletedEvent();
202         if (event == null) {
203             // No most recent event, so clear out the event channels
204             clearEventChannels();
205             return;
206         }
207         // Update channels for most recent completed event
208         logger.debug("Monitor {}: Updating Event Id:{}, Name:{}, Frames:{}, AlarmFrames:{}, Length:{}", m.getId(),
209                 event.getId(), event.getName(), event.getFrames(), event.getAlarmFrames(), event.getLength());
210         updateChannelState(CHANNEL_EVENT_ID, new StringType(event.getId()));
211         updateChannelState(CHANNEL_EVENT_NAME, new StringType(event.getName()));
212         updateChannelState(CHANNEL_EVENT_CAUSE, new StringType(event.getCause()));
213         updateChannelState(CHANNEL_EVENT_NOTES, new StringType(event.getNotes()));
214         updateChannelState(CHANNEL_EVENT_START, new DateTimeType(
215                 ZonedDateTime.ofInstant(event.getStart().toInstant(), timeZoneProvider.getTimeZone())));
216         updateChannelState(CHANNEL_EVENT_END,
217                 new DateTimeType(ZonedDateTime.ofInstant(event.getEnd().toInstant(), timeZoneProvider.getTimeZone())));
218         updateChannelState(CHANNEL_EVENT_FRAMES, new DecimalType(event.getFrames()));
219         updateChannelState(CHANNEL_EVENT_ALARM_FRAMES, new DecimalType(event.getAlarmFrames()));
220         updateChannelState(CHANNEL_EVENT_LENGTH, new QuantityType<Time>(event.getLength(), Units.SECOND));
221     }
222
223     private void clearEventChannels() {
224         updateChannelState(CHANNEL_EVENT_ID, UnDefType.NULL);
225         updateChannelState(CHANNEL_EVENT_NAME, UnDefType.NULL);
226         updateChannelState(CHANNEL_EVENT_CAUSE, UnDefType.NULL);
227         updateChannelState(CHANNEL_EVENT_NOTES, UnDefType.NULL);
228         updateChannelState(CHANNEL_EVENT_START, UnDefType.NULL);
229         updateChannelState(CHANNEL_EVENT_END, UnDefType.NULL);
230         updateChannelState(CHANNEL_EVENT_FRAMES, UnDefType.NULL);
231         updateChannelState(CHANNEL_EVENT_ALARM_FRAMES, UnDefType.NULL);
232         updateChannelState(CHANNEL_EVENT_LENGTH, UnDefType.NULL);
233     }
234
235     private void refreshImage() {
236         if (isLinked(CHANNEL_IMAGE)) {
237             getImage();
238         } else {
239             logger.trace("Monitor {}: Can't update image because '{}' channel is not linked", CHANNEL_IMAGE, monitorId);
240         }
241     }
242
243     private void getImage() {
244         ZmBridgeHandler localHandler = bridgeHandler;
245         if (localHandler != null) {
246             logger.debug("Monitor {}: Updating image channel", monitorId);
247             RawType image = localHandler.getImage(monitorId, imageRefreshIntervalSeconds);
248             updateChannelState(CHANNEL_IMAGE, image != null ? image : UnDefType.UNDEF);
249         }
250     }
251
252     private void updateChannelState(String channelId, State state) {
253         updateState(channelId, state);
254         monitorStatusCache.put(channelId, state);
255     }
256
257     private void startImageRefreshJob() {
258         stopImageRefreshJob();
259         Integer interval = imageRefreshIntervalSeconds;
260         if (interval != null) {
261             long delay = getRandomDelay(interval);
262             imageRefreshJob = scheduler.scheduleWithFixedDelay(this::refreshImage, delay, interval, TimeUnit.SECONDS);
263             logger.debug("Monitor {}: Scheduled image refresh job will run every {} seconds starting in {} seconds",
264                     monitorId, interval, delay);
265         }
266     }
267
268     private void stopImageRefreshJob() {
269         ScheduledFuture<?> localImageRefreshJob = imageRefreshJob;
270         if (localImageRefreshJob != null) {
271             logger.debug("Monitor {}: Canceled image refresh job", monitorId);
272             localImageRefreshJob.cancel(true);
273             imageRefreshJob = null;
274         }
275     }
276
277     private void turnAlarmOff() {
278         ZmBridgeHandler localHandler = bridgeHandler;
279         if (alarmOffJob != null && localHandler != null) {
280             logger.debug("Monitor {}: Tell bridge to turn off alarm", monitorId);
281             localHandler.setAlarmOff(monitorId);
282         }
283     }
284
285     private void startAlarmOffJob(int duration) {
286         stopAlarmOffJob();
287         if (duration != 0) {
288             alarmOffJob = scheduler.schedule(this::turnAlarmOff, duration, TimeUnit.SECONDS);
289             logger.debug("Monitor {}: Scheduled alarm off job in {} seconds", monitorId, duration);
290         }
291     }
292
293     private void stopAlarmOffJob() {
294         ScheduledFuture<?> localAlarmOffJob = alarmOffJob;
295         if (localAlarmOffJob != null) {
296             logger.debug("Monitor {}: Canceled alarm off job", monitorId);
297             localAlarmOffJob.cancel(true);
298             alarmOffJob = null;
299         }
300     }
301
302     private long getRandomDelay(int interval) {
303         return System.currentTimeMillis() % interval;
304     }
305 }