2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.zoneminder.internal.handler;
15 import static org.openhab.binding.zoneminder.internal.ZmBindingConstants.*;
17 import java.time.ZonedDateTime;
18 import java.util.Collection;
19 import java.util.Collections;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
25 import javax.measure.quantity.Time;
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;
52 * The {@link ZmMonitorHandler} represents a Zoneminder monitor. The monitor handler
53 * interacts with the server bridge to communicate with the Zoneminder server.
55 * @author Mark Hilbush - Initial contribution
58 public class ZmMonitorHandler extends BaseThingHandler {
60 private final Logger logger = LoggerFactory.getLogger(ZmMonitorHandler.class);
62 private final TimeZoneProvider timeZoneProvider;
64 private @Nullable ZmBridgeHandler bridgeHandler;
66 private @NonNullByDefault({}) String monitorId;
67 private @Nullable Integer imageRefreshIntervalSeconds;
68 private Integer alarmDuration = DEFAULT_ALARM_DURATION_SECONDS;
70 private @Nullable ScheduledFuture<?> imageRefreshJob;
71 private @Nullable ScheduledFuture<?> alarmOffJob;
73 private final Map<String, State> monitorStatusCache = new ConcurrentHashMap<>();
75 public ZmMonitorHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
77 this.timeZoneProvider = timeZoneProvider;
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();
94 public void dispose() {
97 stopImageRefreshJob();
101 public void handleCommand(ChannelUID channelUID, Command command) {
102 if (command instanceof RefreshType) {
103 State state = monitorStatusCache.get(channelUID.getId());
105 updateState(channelUID, state);
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);
115 switch (channelUID.getId()) {
116 case CHANNEL_FUNCTION:
117 if (command instanceof StringType) {
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);
128 if (command instanceof OnOffType) {
129 localHandler.setEnabled(monitorId, (OnOffType) command);
130 logger.debug("Monitor {}: Set monitor enable to {}", monitorId, command);
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());
141 localHandler.setAlarmOff(monitorId);
149 public Collection<Class<? extends ThingHandlerService>> getServices() {
150 return Collections.singleton(ZmActions.class);
153 public String getId() {
157 public void actionTriggerAlarm(@Nullable Number duration) {
158 if (duration == null) {
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());
169 public void actionTriggerAlarm() {
170 actionTriggerAlarm(alarmDuration);
173 public void actionCancelAlarm() {
174 ZmBridgeHandler localHandler = bridgeHandler;
175 if (localHandler != null) {
176 logger.debug("Monitor {}: Action tell bridge to turn off alarm", monitorId);
178 localHandler.setAlarmOff(monitorId);
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()));
199 updateChannelState(CHANNEL_TRIGGER_ALARM, m.isAlarm() ? OnOffType.ON : OnOffType.OFF);
201 Event event = m.getLastEvent();
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(), Units.SECOND));
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);
234 private void refreshImage() {
235 if (isLinked(CHANNEL_IMAGE)) {
238 logger.trace("Monitor {}: Can't update image because '{}' channel is not linked", CHANNEL_IMAGE, monitorId);
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);
251 private void updateChannelState(String channelId, State state) {
252 updateState(channelId, state);
253 monitorStatusCache.put(channelId, state);
256 private void startImageRefreshJob() {
257 stopImageRefreshJob();
258 Integer interval = imageRefreshIntervalSeconds;
259 if (interval != null) {
260 long delay = getRandomDelay(interval);
261 imageRefreshJob = scheduler.scheduleWithFixedDelay(this::refreshImage, delay, interval, TimeUnit.SECONDS);
262 logger.debug("Monitor {}: Scheduled image refresh job will run every {} seconds starting in {} seconds",
263 monitorId, interval, delay);
267 private void stopImageRefreshJob() {
268 ScheduledFuture<?> localImageRefreshJob = imageRefreshJob;
269 if (localImageRefreshJob != null) {
270 logger.debug("Monitor {}: Canceled image refresh job", monitorId);
271 localImageRefreshJob.cancel(true);
272 imageRefreshJob = null;
276 private void turnAlarmOff() {
277 ZmBridgeHandler localHandler = bridgeHandler;
278 if (alarmOffJob != null && localHandler != null) {
279 logger.debug("Monitor {}: Tell bridge to turn off alarm", monitorId);
280 localHandler.setAlarmOff(monitorId);
284 private void startAlarmOffJob(int duration) {
287 alarmOffJob = scheduler.schedule(this::turnAlarmOff, duration, TimeUnit.SECONDS);
288 logger.debug("Monitor {}: Scheduled alarm off job in {} seconds", monitorId, duration);
292 private void stopAlarmOffJob() {
293 ScheduledFuture<?> localAlarmOffJob = alarmOffJob;
294 if (localAlarmOffJob != null) {
295 logger.debug("Monitor {}: Canceled alarm off job", monitorId);
296 localAlarmOffJob.cancel(true);
301 private long getRandomDelay(int interval) {
302 return System.currentTimeMillis() % interval;