[zoneminder] Replacement for ZoneMinder binding (#8530)

Signed-off-by: Mark Hilbush <mark@hilbush.com>
This commit is contained in:
Mark Hilbush
2020-10-06 14:11:13 -04:00
committed by GitHub
parent cd16c680eb
commit 7615e5fd09
52 changed files with 3325 additions and 3458 deletions

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.zoneminder-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
</repository>
<feature name="openhab-binding-zoneminder" description="ZoneMinder Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.action;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link IZmActions} defines the interface for all thing actions supported by the binding.
* These methods, parameters, and return types are explained in {@link ZmActions}.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public interface IZmActions {
public void triggerAlarm(@Nullable Number alarmDuration);
public void triggerAlarm();
public void cancelAlarm();
}

View File

@@ -0,0 +1,136 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.action;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.zoneminder.internal.handler.ZmMonitorHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ZmActions} defines the thing actions provided by this binding.
*
* <b>Note:</b>The static method <b>invokeMethodOf</b> handles the case where
* the test <i>actions instanceof ZmActions</i> fails. This test can fail
* due to an issue in openHAB core v2.5.0 where the {@link ZmActions} class
* can be loaded by a different classloader than the <i>actions</i> instance.
*
* @author Mark Hilbush - Initial contribution
*/
@ThingActionsScope(name = "zm")
@NonNullByDefault
public class ZmActions implements ThingActions, IZmActions {
private final Logger logger = LoggerFactory.getLogger(ZmActions.class);
private @Nullable ZmMonitorHandler handler;
@Override
public @Nullable ThingHandler getThingHandler() {
return this.handler;
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof ZmMonitorHandler) {
this.handler = (ZmMonitorHandler) handler;
}
}
private static IZmActions invokeMethodOf(@Nullable ThingActions actions) {
if (actions == null) {
throw new IllegalArgumentException("actions cannot be null");
}
if (actions.getClass().getName().equals(ZmActions.class.getName())) {
if (actions instanceof IZmActions) {
return (IZmActions) actions;
} else {
return (IZmActions) Proxy.newProxyInstance(IZmActions.class.getClassLoader(),
new Class[] { IZmActions.class }, (Object proxy, Method method, Object[] args) -> {
Method m = actions.getClass().getDeclaredMethod(method.getName(),
method.getParameterTypes());
return m.invoke(actions, args);
});
}
}
throw new IllegalArgumentException("Actions is not an instance of ZmActions");
}
/**
* The Trigger Alarm function triggers an alarm that will run for the number of seconds
* specified by the supplied parameter duration.
*/
@Override
@RuleAction(label = "TriggerAlarm", description = "Trigger an alarm on the monitor.")
public void triggerAlarm(
@ActionInput(name = "duration", description = "The duration of the alarm in seconds.") @Nullable Number duration) {
logger.debug("ZmActions: Action 'TriggerAlarm' called");
ZmMonitorHandler localHandler = handler;
if (localHandler == null) {
logger.warn("ZmActions: Action service ThingHandler is null!");
return;
}
localHandler.actionTriggerAlarm(duration);
}
public static void triggerAlarm(@Nullable ThingActions actions, @Nullable Number alarmDuration) {
invokeMethodOf(actions).triggerAlarm(alarmDuration);
}
/**
* The Trigger Alarm function triggers an alarm that will run for the number of seconds
* specified in the thing configuration.
*/
@Override
@RuleAction(label = "TriggerAlarm", description = "Trigger an alarm on the monitor.")
public void triggerAlarm() {
logger.debug("ZmActions: Action 'TriggerAlarm' called");
ZmMonitorHandler localHandler = handler;
if (localHandler == null) {
logger.warn("ZmActions: Action service ThingHandler is null!");
return;
}
localHandler.actionTriggerAlarm();
}
public static void triggerAlarm(@Nullable ThingActions actions) {
invokeMethodOf(actions).triggerAlarm();
}
/**
* The Cancel Alarm function cancels a running alarm.
*/
@Override
@RuleAction(label = "CancelAlarm", description = "Cancel a running alarm.")
public void cancelAlarm() {
logger.debug("ZmActions: Action 'CancelAlarm' called");
ZmMonitorHandler localHandler = handler;
if (localHandler == null) {
logger.warn("ZmActions: Action service ThingHandler is null!");
return;
}
localHandler.actionCancelAlarm();
}
public static void cancelAlarm(@Nullable ThingActions actions) {
invokeMethodOf(actions).cancelAlarm();
}
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link ZmBindingConstants} class defines common constants that are
* used across the whole binding.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class ZmBindingConstants {
private static final String BINDING_ID = "zoneminder";
// Bridge thing
public static final String THING_TYPE_SERVER = "server";
public static final ThingTypeUID UID_SERVER = new ThingTypeUID(BINDING_ID, THING_TYPE_SERVER);
public static final Set<ThingTypeUID> SUPPORTED_SERVER_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(UID_SERVER).collect(Collectors.toSet()));
// Monitor things
public static final String THING_TYPE_MONITOR = "monitor";
public static final ThingTypeUID UID_MONITOR = new ThingTypeUID(BINDING_ID, THING_TYPE_MONITOR);
// Collection of monitor thing types
public static final Set<ThingTypeUID> SUPPORTED_MONITOR_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(UID_MONITOR).collect(Collectors.toSet()));
// Collection of all supported thing types
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.concat(SUPPORTED_MONITOR_THING_TYPES_UIDS.stream(), SUPPORTED_SERVER_THING_TYPES_UIDS.stream())
.collect(Collectors.toSet()));
// Config parameters
// Server
public static final String CONFIG_HOST = "host";
public static final String CONFIG_PORT_NUMBER = "portNumber";
public static final String CONFIG_URL_PATH = "urlPath";
public static final String CONFIG_DEFAULT_ALARM_DURATION = "defaultAlarmDuration";
public static final String CONFIG_DEFAULT_IMAGE_REFRESH_INTERVAL = "defaultImageRefreshInterval";
// Monitor
public static final String CONFIG_MONITOR_ID = "monitorId";
public static final String CONFIG_IMAGE_REFRESH_INTERVAL = "imageRefreshInterval";
public static final String CONFIG_ALARM_DURATION = "alarmDuration";
public static final int DEFAULT_ALARM_DURATION_SECONDS = 60;
public static final String DEFAULT_URL_PATH = "/zm";
// List of all channel ids
public static final String CHANNEL_IMAGE_MONITOR_ID = "imageMonitorId";
public static final String CHANNEL_VIDEO_MONITOR_ID = "videoMonitorId";
public static final String CHANNEL_ID = "id";
public static final String CHANNEL_NAME = "name";
public static final String CHANNEL_IMAGE = "image";
public static final String CHANNEL_FUNCTION = "function";
public static final String CHANNEL_ENABLE = "enable";
public static final String CHANNEL_ALARM = "alarm";
public static final String CHANNEL_STATE = "state";
public static final String CHANNEL_TRIGGER_ALARM = "triggerAlarm";
public static final String CHANNEL_HOUR_EVENTS = "hourEvents";
public static final String CHANNEL_DAY_EVENTS = "dayEvents";
public static final String CHANNEL_WEEK_EVENTS = "weekEvents";
public static final String CHANNEL_MONTH_EVENTS = "monthEvents";
public static final String CHANNEL_TOTAL_EVENTS = "totalEvents";
public static final String CHANNEL_IMAGE_URL = "imageUrl";
public static final String CHANNEL_VIDEO_URL = "videoUrl";
public static final String CHANNEL_EVENT_ID = "eventId";
public static final String CHANNEL_EVENT_NAME = "eventName";
public static final String CHANNEL_EVENT_CAUSE = "eventCause";
public static final String CHANNEL_EVENT_NOTES = "eventNotes";
public static final String CHANNEL_EVENT_START = "eventStart";
public static final String CHANNEL_EVENT_END = "eventEnd";
public static final String CHANNEL_EVENT_FRAMES = "eventFrames";
public static final String CHANNEL_EVENT_ALARM_FRAMES = "eventAlarmFrames";
public static final String CHANNEL_EVENT_LENGTH = "eventLength";
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal;
import static org.openhab.binding.zoneminder.internal.ZmBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.zoneminder.internal.handler.ZmBridgeHandler;
import org.openhab.binding.zoneminder.internal.handler.ZmMonitorHandler;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link ZmHandlerFactory} is responsible for creating thing handlers.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.zm", service = ThingHandlerFactory.class)
public class ZmHandlerFactory extends BaseThingHandlerFactory {
private final TimeZoneProvider timeZoneProvider;
private final HttpClient httpClient;
private final ZmStateDescriptionOptionsProvider stateDescriptionProvider;
@Activate
public ZmHandlerFactory(@Reference TimeZoneProvider timeZoneProvider,
@Reference HttpClientFactory httpClientFactory,
@Reference ZmStateDescriptionOptionsProvider stateDescriptionProvider) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.timeZoneProvider = timeZoneProvider;
this.stateDescriptionProvider = stateDescriptionProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (SUPPORTED_SERVER_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new ZmBridgeHandler((Bridge) thing, httpClient, stateDescriptionProvider);
} else if (SUPPORTED_MONITOR_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new ZmMonitorHandler(thing, timeZoneProvider);
}
return null;
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link ZmStateDescriptionOptionsProvider} is used to populate custom state options
* on the Zoneminder channel(s).
*
* @author Mark Hilbush - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, ZmStateDescriptionOptionsProvider.class })
@NonNullByDefault
public class ZmStateDescriptionOptionsProvider extends BaseDynamicStateDescriptionProvider {
@Activate
public ZmStateDescriptionOptionsProvider(
@Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@@ -1,104 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link ZoneMinderConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Martin S. Eskildsen - Initial contribution
*/
@NonNullByDefault
public class ZoneMinderConstants {
public static final String BINDING_ID = "zoneminder";
// ZoneMinder Server Bridge
public static final String BRIDGE_ZONEMINDER_SERVER = "server";
// ZoneMinder Monitor thing
public static final String THING_ZONEMINDER_MONITOR = "monitor";
// ZoneMinder Server displayable name
public static final String ZONEMINDER_SERVER_NAME = "ZoneMinder Server";
// ZoneMinder Monitor displayable name
public static final String ZONEMINDER_MONITOR_NAME = "ZoneMinder Monitor";
/*
* ZoneMinder Server Constants
*/
// Thing Type UID for Server
public static final ThingTypeUID THING_TYPE_BRIDGE_ZONEMINDER_SERVER = new ThingTypeUID(BINDING_ID,
BRIDGE_ZONEMINDER_SERVER);
// Shared channel for all bridges / things
public static final String CHANNEL_ONLINE = "online";
// Channel Id's for the ZoneMinder Server
public static final String CHANNEL_SERVER_DISKUSAGE = "disk-usage";
public static final String CHANNEL_SERVER_CPULOAD = "cpu-load";
// Parameters for the ZoneMinder Server
public static final String PARAM_HOSTNAME = "hostname";
public static final String PARAM_PORT = "port";
public static final String PARAM_REFRESH_INTERVAL = "refresh_interval";
public static final String PARAM_REFRESH_INTERVAL_DISKUSAGE = "refresh_interval_disk_usage";
// Default values for Monitor parameters
public static final Integer DEFAULT_HTTP_PORT = 80;
public static final Integer DEFAULT_TELNET_PORT = 6802;
/*
* ZoneMinder Monitor Constants
*/
// Thing Type UID for Monitor
public static final ThingTypeUID THING_TYPE_THING_ZONEMINDER_MONITOR = new ThingTypeUID(BINDING_ID,
THING_ZONEMINDER_MONITOR);
/*
* Channel Id's for the ZoneMinder Monitor
*/
public static final String CHANNEL_MONITOR_ENABLED = "enabled";
public static final String CHANNEL_MONITOR_FORCE_ALARM = "force-alarm";
public static final String CHANNEL_MONITOR_EVENT_STATE = "alarm";
public static final String CHANNEL_MONITOR_EVENT_CAUSE = "event-cause";
public static final String CHANNEL_MONITOR_RECORD_STATE = "recording";
public static final String CHANNEL_MONITOR_DETAILED_STATUS = "detailed-status";
public static final String CHANNEL_MONITOR_FUNCTION = "function";
public static final String CHANNEL_MONITOR_CAPTURE_DAEMON_STATE = "capture-daemon";
public static final String CHANNEL_MONITOR_ANALYSIS_DAEMON_STATE = "analysis-daemon";
public static final String CHANNEL_MONITOR_FRAME_DAEMON_STATE = "frame-daemon";
// Parameters for the ZoneMinder Monitor
public static final String PARAMETER_MONITOR_ID = "monitorId";
public static final String PARAMETER_MONITOR_TRIGGER_TIMEOUT = "monitorTriggerTimeout";
public static final String PARAMETER_MONITOR_EVENTTEXT = "monitorEventText";
// Default values for Monitor parameters
public static final Integer PARAMETER_MONITOR_TRIGGER_TIMEOUT_DEFAULTVALUE = 60;
public static final String PARAMETER_MONITOR_EVENTNOTE_DEFAULTVALUE = "openHAB triggered event";
// ZoneMinder Event types
public static final String MONITOR_EVENT_NONE = "";
public static final String MONITOR_EVENT_SIGNAL = "Signal";
public static final String MONITOR_EVENT_MOTION = "Motion";
public static final String MONITOR_EVENT_FORCED_WEB = "Forced Web";
public static final String MONITOR_EVENT_OPENHAB = "openHAB";
}

View File

@@ -1,66 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.zoneminder.internal.handler.ZoneMinderServerBridgeHandler;
import org.openhab.binding.zoneminder.internal.handler.ZoneMinderThingMonitorHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ZoneMinderHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Martin S. Eskildsen - Initial contribution
*
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.zoneminder")
public class ZoneMinderHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(ZoneMinderHandlerFactory.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
.unmodifiableSet(Stream.concat(ZoneMinderServerBridgeHandler.SUPPORTED_THING_TYPES.stream(),
ZoneMinderThingMonitorHandler.SUPPORTED_THING_TYPES.stream()).collect(Collectors.toSet()));
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(ZoneMinderConstants.THING_TYPE_BRIDGE_ZONEMINDER_SERVER)) {
logger.debug("[FACTORY]: creating handler for bridge thing '{}'", thing);
return new ZoneMinderServerBridgeHandler((Bridge) thing);
} else if (thingTypeUID.equals(ZoneMinderConstants.THING_TYPE_THING_ZONEMINDER_MONITOR)) {
return new ZoneMinderThingMonitorHandler(thing);
}
return null;
}
}

View File

@@ -1,37 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal;
/**
*
* @author Martin S. Eskildsen - Initial contribution
*/
public class ZoneMinderProperties {
public static final String PROPERTY_ID = "Id";
public static final String PROPERTY_SERVER_VERSION = "Version";
public static final String PROPERTY_SERVER_API_VERSION = "API Version";
public static final String PROPERTY_SERVER_USE_API = "API Enabled";
public static final String PROPERTY_SERVER_USE_AUTHENTIFICATION = "Use Authentification";
public static final String PROPERTY_SERVER_TRIGGERS_ENABLED = "Triggers enabled";
public static final String PROPERTY_MONITOR_NAME = "Name";
public static final String PROPERTY_MONITOR_SOURCETYPE = "Sourcetype";
public static final String PROPERTY_MONITOR_ANALYSIS_FPS = "Analysis FPS";
public static final String PROPERTY_MONITOR_MAXIMUM_FPS = "Maximum FPS";
public static final String PROPERTY_MONITOR_ALARM_MAXIMUM = "Alarm Maximum FPS";
public static final String PROPERTY_MONITOR_IMAGE_WIDTH = "Width";
public static final String PROPERTY_MONITOR_IMAGE_HEIGHT = "Height";
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.config;
import static org.openhab.binding.zoneminder.internal.ZmBindingConstants.DEFAULT_URL_PATH;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link ZmBridgeConfig} class contains fields mapping thing configuration parameters.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class ZmBridgeConfig {
/**
* Host name or IP address of Zoneminder server
*/
public String host = "";
/**
* Use http or https
*/
public Boolean useSSL = Boolean.FALSE;
/**
* Port number
*/
public @Nullable Integer portNumber;
/**
* URL fragment (e.g. /zm)
*/
public String urlPath = DEFAULT_URL_PATH;
/**
* Frequency at which monitor status will be updated
*/
public @Nullable Integer refreshInterval;
/**
* Enable/disable monitor discovery
*/
public @Nullable Boolean discoveryEnabled;
/**
* Frequency at which the binding will try to discover monitors
*/
public @Nullable Integer discoveryInterval;
/**
* Alarm duration set on monitor things when they're discovered
*/
public @Nullable Integer defaultAlarmDuration;
/**
* Default image refresh interval set on monitor things when they're discovered
*/
public @Nullable Integer defaultImageRefreshInterval;
/**
* Zoneminder user name
*/
public @Nullable String user;
/**
* Zoneminder password
*/
public @Nullable String pass;
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link ZmMonitorConfig} class contains fields mapping thing configuration parameters.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class ZmMonitorConfig {
/**
* Monitor Id
*/
public @Nullable String monitorId;
/**
* Interval in seconds with which image is refreshed. If null, image
* will not be refreshed.
*/
public @Nullable Integer imageRefreshInterval;
/**
* Duration in seconds after which the alarm will be turned off
*/
public @Nullable Integer alarmDuration;
}

View File

@@ -1,129 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.config;
import org.openhab.binding.zoneminder.internal.ZoneMinderConstants;
/**
* Configuration data according to zoneminderserver.xml
*
* @author Martin S. Eskildsen - Initial contribution
*/
public class ZoneMinderBridgeServerConfig extends ZoneMinderConfig {
private String hostname;
private Integer http_port;
private Integer telnet_port;
private String protocol;
private String urlpath;
private String user;
private String password;
private Integer refresh_interval;
private Integer refresh_interval_disk_usage;
private Boolean autodiscover_things;
@Override
public String getConfigId() {
return ZoneMinderConstants.BRIDGE_ZONEMINDER_SERVER;
}
public String getHostName() {
return hostname;
}
public void setHostName(String hostName) {
this.hostname = hostName;
}
public Integer getHttpPort() {
if ((http_port == null) || (http_port == 0)) {
if (getProtocol().equalsIgnoreCase("http")) {
http_port = 80;
} else {
http_port = 443;
}
}
return http_port;
}
public void setHttpPort(Integer port) {
this.http_port = port;
}
public Integer getTelnetPort() {
return telnet_port;
}
public void setTelnetPort(Integer telnetPort) {
this.telnet_port = telnetPort;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getServerBasePath() {
return urlpath;
}
public void setServerBasePath(String urlpath) {
this.urlpath = urlpath;
}
public String getUserName() {
return user;
}
public void setUserName(String userName) {
this.user = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getRefreshInterval() {
return refresh_interval;
}
public void setRefreshInterval(Integer refreshInterval) {
this.refresh_interval = refreshInterval;
}
public Integer getRefreshIntervalLowPriorityTask() {
return refresh_interval_disk_usage;
}
public void setRefreshIntervalDiskUsage(Integer refreshIntervalDiskUsage) {
this.refresh_interval_disk_usage = refreshIntervalDiskUsage;
}
public Boolean getAutodiscoverThings() {
return autodiscover_things;
}
public void setAutodiscoverThings(Boolean autodiscoverThings) {
this.autodiscover_things = autodiscoverThings;
}
}

View File

@@ -1,22 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.config;
/**
* base class containing Configuration in openHAB
*
* @author Martin S. Eskildsen - Initial contribution
*/
public abstract class ZoneMinderConfig {
public abstract String getConfigId();
}

View File

@@ -1,30 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.config;
import org.openhab.binding.zoneminder.internal.ZoneMinderConstants;
/**
* Monitor configuration from openHAB.
*
* @author Martin S. Eskildsen - Initial contribution
*/
public abstract class ZoneMinderThingConfig extends ZoneMinderConfig {
public abstract String getZoneMinderId();
@Override
public String getConfigId() {
return ZoneMinderConstants.THING_ZONEMINDER_MONITOR;
}
}

View File

@@ -1,40 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.config;
import org.openhab.binding.zoneminder.internal.ZoneMinderConstants;
/**
* Specific configuration class for Monitor COnfig.
*
* @author Martin S. Eskildsen - Initial contribution
*/
public class ZoneMinderThingMonitorConfig extends ZoneMinderThingConfig {
// Parameters
private Integer monitorId;
@Override
public String getConfigId() {
return ZoneMinderConstants.THING_ZONEMINDER_MONITOR;
}
public String getId() {
return monitorId.toString();
}
@Override
public String getZoneMinderId() {
return monitorId.toString();
}
}

View File

@@ -0,0 +1,128 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.discovery;
import static org.openhab.binding.zoneminder.internal.ZmBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.zoneminder.internal.handler.Monitor;
import org.openhab.binding.zoneminder.internal.handler.ZmBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MonitorDiscoveryService} is responsible for discovering the Zoneminder monitors
* associated with a Zoneminder server.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class MonitorDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(MonitorDiscoveryService.class);
private @Nullable ZmBridgeHandler bridgeHandler;
public MonitorDiscoveryService() {
super(30);
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof ZmBridgeHandler) {
((ZmBridgeHandler) handler).setDiscoveryService(this);
this.bridgeHandler = (ZmBridgeHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return SUPPORTED_MONITOR_THING_TYPES_UIDS;
}
@Override
public void startBackgroundDiscovery() {
logger.trace("Discovery: Performing background discovery scan for {}", getBridgeUID());
discoverMonitors();
}
@Override
public void startScan() {
logger.debug("Discovery: Starting monitor discovery scan for {}", getBridgeUID());
discoverMonitors();
}
private @Nullable ThingUID getBridgeUID() {
ZmBridgeHandler localBridgeHandler = bridgeHandler;
return localBridgeHandler != null ? localBridgeHandler.getThing().getUID() : null;
}
private synchronized void discoverMonitors() {
ZmBridgeHandler localBridgeHandler = bridgeHandler;
ThingUID bridgeUID = getBridgeUID();
if (localBridgeHandler != null && bridgeUID != null) {
Integer alarmDuration = localBridgeHandler.getDefaultAlarmDuration();
Integer imageRefreshInterval = localBridgeHandler.getDefaultImageRefreshInterval();
for (Monitor monitor : localBridgeHandler.getSavedMonitors()) {
String id = monitor.getId();
String name = monitor.getName();
ThingUID thingUID = new ThingUID(UID_MONITOR, bridgeUID, monitor.getId());
Map<String, Object> properties = new HashMap<>();
properties.put(CONFIG_MONITOR_ID, id);
properties.put(CONFIG_ALARM_DURATION, alarmDuration);
if (imageRefreshInterval != null) {
properties.put(CONFIG_IMAGE_REFRESH_INTERVAL, imageRefreshInterval);
}
thingDiscovered(createDiscoveryResult(thingUID, bridgeUID, id, name, properties));
logger.trace("Discovery: Monitor with id '{}' and name '{}' added to Inbox with UID '{}'",
monitor.getId(), monitor.getName(), thingUID);
}
}
}
private DiscoveryResult createDiscoveryResult(ThingUID monitorUID, ThingUID bridgeUID, String id, String name,
Map<String, Object> properties) {
return DiscoveryResultBuilder.create(monitorUID).withProperties(properties).withBridge(bridgeUID)
.withLabel(buildLabel(name)).withRepresentationProperty(CONFIG_MONITOR_ID).build();
}
private String buildLabel(String name) {
return String.format("Zoneminder Monitor %s", name);
}
}

View File

@@ -1,152 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.discovery;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.zoneminder.internal.ZoneMinderConstants;
import org.openhab.binding.zoneminder.internal.handler.ZoneMinderServerBridgeHandler;
import org.openhab.binding.zoneminder.internal.handler.ZoneMinderThingMonitorHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import name.eskildsen.zoneminder.IZoneMinderMonitorData;
/**
*
* @author Martin S. Eskildsen - Initial contribution
*/
public class ZoneMinderDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(ZoneMinderDiscoveryService.class);
private @NonNullByDefault({}) ZoneMinderServerBridgeHandler serverHandler;
public ZoneMinderDiscoveryService() {
super(30);
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof ZoneMinderServerBridgeHandler) {
this.serverHandler = (ZoneMinderServerBridgeHandler) handler;
this.serverHandler.setDiscoveryService(this);
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return serverHandler;
}
@Override
public void activate() {
logger.debug("[DISCOVERY]: Activating ZoneMinder discovery service for {}", serverHandler.getThing().getUID());
}
@Override
public void deactivate() {
logger.debug("[DISCOVERY]: Deactivating ZoneMinder discovery service for {}",
serverHandler.getThing().getUID());
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return ZoneMinderThingMonitorHandler.SUPPORTED_THING_TYPES;
}
@Override
public void startBackgroundDiscovery() {
logger.debug("[DISCOVERY]: Performing background discovery scan for {}", serverHandler.getThing().getUID());
discoverMonitors();
}
@Override
public void startScan() {
logger.debug("[DISCOVERY]: Starting discovery scan for {}", serverHandler.getThing().getUID());
discoverMonitors();
}
@Override
public synchronized void abortScan() {
super.abortScan();
}
@Override
protected synchronized void stopScan() {
super.stopScan();
}
protected String buildMonitorLabel(String id, String name) {
return String.format("%s [%s]", ZoneMinderConstants.ZONEMINDER_MONITOR_NAME, name);
}
protected synchronized void discoverMonitors() {
// Add all existing devices
for (IZoneMinderMonitorData monitor : serverHandler.getMonitors()) {
deviceAdded(monitor);
}
}
private boolean monitorThingExists(ThingUID newThingUID) {
return serverHandler.getThing().getThing(newThingUID) != null ? true : false;
}
/**
* This is called once the node is fully discovered. At this point we know most of the information about
* the device including manufacturer information.
*
* @param node the node to be added
*/
public void deviceAdded(IZoneMinderMonitorData monitor) {
try {
ThingUID bridgeUID = serverHandler.getThing().getUID();
String monitorUID = String.format("%s-%s", ZoneMinderConstants.THING_ZONEMINDER_MONITOR, monitor.getId());
ThingUID thingUID = new ThingUID(ZoneMinderConstants.THING_TYPE_THING_ZONEMINDER_MONITOR, bridgeUID,
monitorUID);
// Does Monitor exist?
if (!monitorThingExists(thingUID)) {
logger.info("[DISCOVERY]: Monitor with Id='{}' and Name='{}' added", monitor.getId(),
monitor.getName());
Map<String, Object> properties = new HashMap<>(0);
properties.put(ZoneMinderConstants.PARAMETER_MONITOR_ID, Integer.valueOf(monitor.getId()));
properties.put(ZoneMinderConstants.PARAMETER_MONITOR_TRIGGER_TIMEOUT,
ZoneMinderConstants.PARAMETER_MONITOR_TRIGGER_TIMEOUT_DEFAULTVALUE);
properties.put(ZoneMinderConstants.PARAMETER_MONITOR_EVENTTEXT,
ZoneMinderConstants.MONITOR_EVENT_OPENHAB);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withBridge(bridgeUID).withLabel(buildMonitorLabel(monitor.getId(), monitor.getName())).build();
thingDiscovered(discoveryResult);
}
} catch (Exception ex) {
logger.error("[DISCOVERY]: Error occurred when calling 'monitorAdded' from Discovery. Exception={}",
ex.getMessage());
}
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link AbstractResponseDTO} represents the common exception object included in
* all responses.
*
* @author Mark Hilbush - Initial contribution
*/
public abstract class AbstractResponseDTO {
/**
* Common exception object used to convey errors from the API
*/
@SerializedName("exception")
public ExceptionDTO exception;
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link AuthResponseDTO} represents the response to an authentication request.
* When authentication is enabled in Zoneminder, this object contains the access and
* refresh tokens, as well as the number of seconds until the tokens expire.
*
* @author Mark Hilbush - Initial contribution
*/
public class AuthResponseDTO extends AbstractResponseDTO {
/**
* Access token to be used in all API calls
*/
@SerializedName("access_token")
public String accessToken;
/**
* Number of seconds until the access token expires
*/
@SerializedName("access_token_expires")
public String accessTokenExpires;
/**
* Refresh token to be used to request a new access token. A new access token
* should be requested slightly before it is about to expire
*/
@SerializedName("refresh_token")
public String refreshToken;
/**
* Number of seconds until the refresh token expires
*/
@SerializedName("refresh_token_expires")
public String refreshTokenExpires;
/**
* Zoneminder version number
*/
@SerializedName("version")
public String version;
/**
* Zoneminder API version number
*/
@SerializedName("apiversion")
public String apiVersion;
}

View File

@@ -10,13 +10,20 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal;
package org.openhab.binding.zoneminder.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link EventContainerDTO} holds a Zoneminder event object.
*
* @author Martin S. Eskildsen - Initial contribution
* @author Mark Hilbush - Initial contribution
*/
public enum DataRefreshPriorityEnum {
SCHEDULED,
HIGH_PRIORITY
public class EventContainerDTO {
/**
* Zoneminder event object
*/
@SerializedName("Event")
public EventDTO event;
}

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import java.util.Date;
import com.google.gson.annotations.SerializedName;
/**
* The {@link EventDTO} represents a Zoneminder event.
*
* @author Mark Hilbush - Initial contribution
*/
public class EventDTO {
/**
* Id of the event
*/
@SerializedName("Id")
public String eventId;
/**
* Monitor Id associated with this event
*/
@SerializedName("MonitorId")
public String monitorId;
/**
* Name of the event
*/
@SerializedName("Name")
public String name;
/**
* Cause of the event
*/
@SerializedName("Cause")
public String cause;
/**
* Date/time when the event started
*/
@SerializedName("StartTime")
public Date startTime;
/**
* Date/time when the event ended
*/
@SerializedName("EndTime")
public Date endTime;
/**
* Number of frames in the event
*/
@SerializedName("Frames")
public Integer frames;
/**
* Number of alarm frames in the event
*/
@SerializedName("AlarmFrames")
public Integer alarmFrames;
/**
* Length of the event in seconds
*/
@SerializedName("Length")
public Double length;
/**
* Total score of the event
*/
@SerializedName("TotScore")
public String totalScore;
/**
* Average score of the event
*/
@SerializedName("AvgScore")
public String averageScore;
/**
* Maximum score of the event
*/
@SerializedName("MaxScore")
public String maximumScore;
/**
* Event notes
*/
@SerializedName("Notes")
public String notes;
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* The {@link EventsDTO} contains the list of events that match the
* query's selection criteria.
*
* @author Mark Hilbush - Initial contribution
*/
public class EventsDTO extends AbstractResponseDTO {
/**
* List of events matching the selection criteria
*/
@SerializedName("events")
public List<EventContainerDTO> eventsList;
/**
* Pagination information (currently not used)
*/
@SerializedName("pagination")
public Object pagination;
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link ExceptionDTO} is responsible for
*
* @author Mark Hilbush - Initial contribution
*/
public class ExceptionDTO {
/**
* Class where the error occurred
*/
@SerializedName("class")
public String clazz;
/**
* Error code
*/
@SerializedName("code")
public String code;
/**
* Error message
*/
@SerializedName("message")
public String message;
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MonitorDTO} contains information about how the monitor is
* defined in Zoneminder.
*
* @author Mark Hilbush - Initial contribution
*/
public class MonitorDTO {
/**
* Monitor Id
*/
@SerializedName("Id")
public String id;
/**
* Monitor name
*/
@SerializedName("Name")
public String name;
/**
* Current monitor function (e.g. Nodect, Record, etc.)
*/
@SerializedName("Function")
public String function;
/**
* Monitor enabled ("1") or disabled ("0")
*/
@SerializedName("Enabled")
public String enabled;
/**
* Number of events in last hour
*/
@SerializedName("HourEvents")
public String hourEvents;
/**
* Number of events in last day
*/
@SerializedName("DayEvents")
public String dayEvents;
/**
* Number of events in last week
*/
@SerializedName("WeekEvents")
public String weekEvents;
/**
* Number of events in last month
*/
@SerializedName("MonthEvents")
public String monthEvents;
/**
* Total number of events
*/
@SerializedName("TotalEvents")
public String totalEvents;
/**
* Video with in pixels
*/
@SerializedName("Width")
public String width;
/**
* Video height in pixels
*/
@SerializedName("Height")
public String height;
/**
* Path to video stream
*/
@SerializedName("path")
public String videoStreamPath;
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MonitorItemDTO} holds monitor and monitor status information.
*
* @author Mark Hilbush - Initial contribution
*/
public class MonitorItemDTO {
/**
* Information about how the monitor is defined in Zoneminder
*/
@SerializedName("Monitor")
public MonitorDTO monitor;
/**
* Current status of the monitor in Zoneminder
*/
@SerializedName("Monitor_Status")
public MonitorStatusDTO monitorStatus;
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import org.openhab.binding.zoneminder.internal.handler.MonitorState;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MonitorStateDTO} represents the current state of the monitor.
* Possible states are IDLE, PREALERT, ALAARM, ALERT, TAPE, UNKNOWN
*
* @author Mark Hilbush - Initial contribution
*/
public class MonitorStateDTO extends AbstractResponseDTO {
/**
* The current state of the monitor (e.g. IDLE, ALERT, ALARM, etc.)
*/
@SerializedName("status")
public MonitorState state;
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MonitorStatusDTO} contains the current status of the monitor.
*
* @author Mark Hilbush - Initial contribution
*/
public class MonitorStatusDTO {
/**
* Monitor Id
*/
@SerializedName("MonitorId")
public String monitorId;
/**
* Status of the monitor (e.g. Connected)
*/
@SerializedName("Status")
public String status;
/**
* Analysis frames per second
*/
@SerializedName("AnalysisFPS")
public String analysisFPS;
/**
* Video capture bandwidth
*/
@SerializedName("CaptureBandwidth")
public String captureBandwidth;
/**
* Video capture frames per second
*/
@SerializedName("CaptureFPS")
public String captureFPS;
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MonitorsDTO} contains the list of monitors returned
* from the Zoneminder API.
*
* @author Mark Hilbush - Initial contribution
*/
public class MonitorsDTO extends AbstractResponseDTO {
/**
* List of monitors
*/
@SerializedName("monitors")
public List<MonitorItemDTO> monitorItems;
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link VersionDTO} provides the software version and API version.
*
* @author Mark Hilbush - Initial contribution
*/
public class VersionDTO extends AbstractResponseDTO {
/**
* Zoneminder version (e.g. "1.34.2")
*/
@SerializedName("version")
public String version;
/**
* API version number (e.g. "2.0")
*/
@SerializedName("apiversion")
public String apiVersion;
}

View File

@@ -0,0 +1,134 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import java.util.Date;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link Event} represents the attributes of a Zoneminder event
* that are relevant to this binding.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class Event {
private String id;
private String name;
private String cause;
private String notes;
private Date start;
private Date end;
private int frames;
private int alarmFrames;
private double length;
public Event(String id, String name, String cause, String notes, Date start, Date end) {
this.id = id;
this.name = name;
this.cause = cause;
this.notes = notes;
this.start = start;
this.end = end;
}
public String getId() {
return id;
}
public String setId(String id) {
return this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCause() {
return cause;
}
public void setCause(String cause) {
this.cause = cause;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public Date getStart() {
return start;
}
public void setStart(Date start) {
this.start = start;
}
public Date getEnd() {
return end;
}
public void setEnd(Date end) {
this.end = end;
}
public int getFrames() {
return frames;
}
public void setFrames(@Nullable Integer frames) {
this.frames = frames != null ? frames : 0;
}
public int getAlarmFrames() {
return alarmFrames;
}
public void setAlarmFrames(@Nullable Integer alarmFrames) {
this.alarmFrames = alarmFrames != null ? alarmFrames : 0;
}
public double getLength() {
return length;
}
public void setLength(@Nullable Double length) {
this.length = length != null ? length : 0;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("id=").append(id);
sb.append(", name=").append(name);
sb.append(", cause=").append(cause);
sb.append(", start=").append(start.toString());
sb.append(", end=").append(end.toString());
sb.append(", frames=").append(String.format("%d", frames));
sb.append(", alarmFrames=").append(String.format("%d", alarmFrames));
sb.append(", length=").append(String.format("%6.2", length));
sb.append(")");
return sb.toString();
}
}

View File

@@ -0,0 +1,220 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link Monitor} represents the attributes of a Zoneminder monitor
* that are relevant to this binding.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class Monitor {
private final Logger logger = LoggerFactory.getLogger(Monitor.class);
private String id;
private String name;
private String function;
private Boolean enabled;
private String status;
private Boolean alarm = Boolean.FALSE;
private MonitorState state = MonitorState.UNKNOWN;
private Integer hourEvents = 0;
private Integer dayEvents = 0;
private Integer weekEvents = 0;
private Integer monthEvents = 0;
private Integer totalEvents = 0;
private String imageUrl = "";
private String videoUrl = "";
private @Nullable Event lastEvent = null;
public Monitor(String monitorId, String name, String function, String enabled, String status) {
this.id = monitorId;
this.name = name;
this.function = function;
this.enabled = "1".equals(enabled);
this.status = status;
}
public String getId() {
return id;
}
public String setId(String id) {
return this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFunction() {
return function;
}
public void setFunction(String function) {
this.function = function;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Boolean isAlarm() {
return alarm;
}
public MonitorState getState() {
return state;
}
public void setState(MonitorState state) {
this.alarm = (state != MonitorState.IDLE && state != MonitorState.UNKNOWN);
this.state = state;
}
public Integer getHourEvents() {
return hourEvents;
}
public void setHourEvents(@Nullable String hourEvents) {
if (hourEvents != null) {
try {
this.hourEvents = Integer.parseInt(hourEvents);
} catch (NumberFormatException e) {
logger.debug("Monitor object contains invalid hourEvents: {}", hourEvents);
}
}
}
public Integer getDayEvents() {
return dayEvents;
}
public void setDayEvents(@Nullable String dayEvents) {
if (dayEvents != null) {
try {
this.dayEvents = Integer.parseInt(dayEvents);
} catch (NumberFormatException e) {
logger.debug("Monitor object contains invalid dayEvents: {}", dayEvents);
}
}
}
public Integer getWeekEvents() {
return weekEvents;
}
public void setWeekEvents(@Nullable String weekEvents) {
if (weekEvents != null) {
try {
this.weekEvents = Integer.parseInt(weekEvents);
} catch (NumberFormatException e) {
logger.debug("Monitor object contains invalid totalEvents: {}", weekEvents);
}
}
}
public Integer getMonthEvents() {
return monthEvents;
}
public void setMonthEvents(@Nullable String monthEvents) {
if (monthEvents != null) {
try {
this.monthEvents = Integer.parseInt(monthEvents);
} catch (NumberFormatException e) {
logger.debug("Monitor object contains invalid monthEvents: {}", monthEvents);
}
}
}
public Integer getTotalEvents() {
return totalEvents;
}
public void setTotalEvents(@Nullable String totalEvents) {
if (totalEvents != null) {
try {
this.totalEvents = Integer.parseInt(totalEvents);
} catch (NumberFormatException e) {
logger.debug("Monitor object contains invalid totalEvents: {}", totalEvents);
}
}
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getVideoUrl() {
return videoUrl;
}
public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
public @Nullable Event getLastEvent() {
return lastEvent;
}
public void setLastEvent(@Nullable Event lastEvent) {
this.lastEvent = lastEvent;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("id=").append(id);
sb.append(", name=").append(name);
sb.append(", function=").append(function);
sb.append(", enabled=").append(enabled);
sb.append(", status=").append(status);
sb.append(", alarm=").append(alarm);
sb.append(", state=").append(state);
sb.append(", events=(").append(hourEvents);
sb.append(",").append(dayEvents);
sb.append(",").append(weekEvents);
sb.append(",").append(monthEvents);
sb.append(",").append(totalEvents);
sb.append(")");
return sb.toString();
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link MonitorFunction} represents the valid functions for a Zoneminder monitor.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public enum MonitorFunction {
NONE("None"),
MONITOR("Monitor"),
MODECT("Modect"),
RECORD("Record"),
MOCORD("Mocord"),
NODECT("Nodect");
private final String type;
private MonitorFunction(String type) {
this.type = type;
}
public static MonitorFunction forValue(@Nullable String v) {
if (v != null) {
for (MonitorFunction at : MonitorFunction.values()) {
if (at.type.equals(v)) {
return at;
}
}
}
throw new IllegalArgumentException(String.format("Invalid or null monitor function: %s" + v));
}
@Override
public String toString() {
return this.type;
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MonitorState} represents the possible states of a Zoneminder monitor.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public enum MonitorState {
@SerializedName("0")
IDLE("IDLE"),
@SerializedName("1")
PREALERT("PREALERT"),
@SerializedName("2")
ALARM("ALARM"),
@SerializedName("3")
ALERT("ALERT"),
@SerializedName("4")
TAPE("TAPE"),
UNKNOWN("UNKNOWN");
private final String type;
private MonitorState(String type) {
this.type = type;
}
public static MonitorState forValue(@Nullable String v) {
if (v != null) {
for (MonitorState at : MonitorState.values()) {
if (at.type.equals(v)) {
return at;
}
}
}
throw new IllegalArgumentException(String.format("Invalid or null monitor state: %s" + v));
}
@Override
public String toString() {
return this.type;
}
}

View File

@@ -0,0 +1,173 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.zoneminder.internal.dto.AuthResponseDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link ZmAuth} manages the authentication process when Zoneminder
* authentication is enabled. This class requests access and refresh tokens based
* on the expiration times provided by the Zoneminder server.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class ZmAuth {
private final Logger logger = LoggerFactory.getLogger(ZmAuth.class);
private final ZmBridgeHandler bridgeHandler;
private final String authContent;
private final boolean usingAuthorization;
private boolean isAuthorized;
private @Nullable String refreshToken;
private long refreshTokenExpiresAt;
private @Nullable String accessToken;
private long accessTokenExpiresAt;
public ZmAuth(ZmBridgeHandler handler) {
this(handler, null, null);
}
public ZmAuth(ZmBridgeHandler handler, @Nullable String user, @Nullable String pass) {
this.bridgeHandler = handler;
if (user == null || pass == null) {
logger.debug("ZmAuth: Authorization is disabled");
usingAuthorization = false;
isAuthorized = true;
authContent = "";
} else {
logger.debug("ZmAuth: Authorization is enabled");
usingAuthorization = true;
isAuthorized = false;
String encodedUser = null;
String encodedPass = null;
try {
encodedUser = URLEncoder.encode(user, StandardCharsets.UTF_8.name());
encodedPass = URLEncoder.encode(pass, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
logger.warn("ZmAuth: Unable to encode user name and password");
}
authContent = encodedUser == null ? ""
: String.format("user=%s&pass=%s&stateful=1", encodedUser, encodedPass);
}
}
public String getAccessToken() {
String localAccessToken = accessToken;
return localAccessToken != null ? localAccessToken : "";
}
public boolean usingAuthorization() {
return usingAuthorization;
}
public boolean isAuthorized() {
if (usingAuthorization()) {
checkTokens();
}
return isAuthorized;
}
private void checkTokens() {
if (isExpired(refreshTokenExpiresAt)) {
getNewRefreshToken();
} else if (isExpired(accessTokenExpiresAt)) {
getNewAccessToken();
}
}
@SuppressWarnings("null")
private synchronized void getNewRefreshToken() {
// First check to see if another thread has updated it
if (!isExpired(refreshTokenExpiresAt)) {
return;
}
String url = bridgeHandler.buildLoginUrl();
logger.debug("ZmAuth: Update expired REFRESH token using url '{}'", url);
String response = bridgeHandler.executePost(url, authContent, "application/x-www-form-urlencoded");
if (response != null) {
Gson gson = bridgeHandler.getGson();
AuthResponseDTO auth = gson.fromJson(response, AuthResponseDTO.class);
if (auth != null && auth.exception == null && auth.refreshToken != null && auth.accessToken != null) {
updateRefreshToken(auth);
updateAccessToken(auth);
isAuthorized = true;
return;
}
}
isAuthorized = false;
}
@SuppressWarnings("null")
private synchronized void getNewAccessToken() {
// First check to see if another thread has updated it
if (!isExpired(accessTokenExpiresAt)) {
return;
}
String url = bridgeHandler.buildLoginUrl(String.format("?token=%s", refreshToken));
logger.debug("ZmAuth: Update expired ACCESS token using url '{}'", url);
String response = bridgeHandler.executeGet(url);
if (response != null) {
Gson gson = bridgeHandler.getGson();
AuthResponseDTO auth = gson.fromJson(response, AuthResponseDTO.class);
if (auth != null && auth.exception == null && auth.accessToken != null) {
updateAccessToken(auth);
isAuthorized = true;
return;
}
}
isAuthorized = false;
}
private void updateAccessToken(AuthResponseDTO auth) {
accessToken = auth.accessToken;
accessTokenExpiresAt = getExpiresAt(auth.accessTokenExpires);
logger.trace("ZmAuth: New access token: {}", accessToken);
logger.trace("ZmAuth: New access token expires in {} sec", getExpiresIn(accessTokenExpiresAt));
}
private void updateRefreshToken(AuthResponseDTO auth) {
refreshToken = auth.refreshToken;
refreshTokenExpiresAt = getExpiresAt(auth.refreshTokenExpires);
logger.trace("ZmAuth: New refresh token: {}", refreshToken);
logger.trace("ZmAuth: New refresh token expires in {} sec", getExpiresIn(refreshTokenExpiresAt));
}
private boolean isExpired(long expiresAt) {
return (System.currentTimeMillis() / 1000) > expiresAt;
}
private long getExpiresAt(String expiresInSeconds) {
try {
return (System.currentTimeMillis() / 1000) + (Integer.parseInt(expiresInSeconds) - 300);
} catch (NumberFormatException e) {
return 0;
}
}
private long getExpiresIn(long expiresAtSeconds) {
return expiresAtSeconds - (System.currentTimeMillis() / 1000);
}
}

View File

@@ -0,0 +1,628 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import static org.openhab.binding.zoneminder.internal.ZmBindingConstants.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.zoneminder.internal.ZmStateDescriptionOptionsProvider;
import org.openhab.binding.zoneminder.internal.config.ZmBridgeConfig;
import org.openhab.binding.zoneminder.internal.discovery.MonitorDiscoveryService;
import org.openhab.binding.zoneminder.internal.dto.EventDTO;
import org.openhab.binding.zoneminder.internal.dto.EventsDTO;
import org.openhab.binding.zoneminder.internal.dto.MonitorDTO;
import org.openhab.binding.zoneminder.internal.dto.MonitorItemDTO;
import org.openhab.binding.zoneminder.internal.dto.MonitorStateDTO;
import org.openhab.binding.zoneminder.internal.dto.MonitorStatusDTO;
import org.openhab.binding.zoneminder.internal.dto.MonitorsDTO;
import org.openhab.binding.zoneminder.internal.dto.VersionDTO;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
* The {@link ZmBridgeHandler} represents the Zoneminder server. It handles all communication
* with the Zoneminder server.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class ZmBridgeHandler extends BaseBridgeHandler {
private static final int REFRESH_INTERVAL_SECONDS = 1;
private static final int REFRESH_STARTUP_DELAY_SECONDS = 3;
private static final int MONITORS_INTERVAL_SECONDS = 5;
private static final int MONITORS_INITIAL_DELAY_SECONDS = 3;
private static final int DISCOVERY_INTERVAL_SECONDS = 300;
private static final int DISCOVERY_INITIAL_DELAY_SECONDS = 10;
private static final int API_TIMEOUT_MSEC = 10000;
private static final String LOGIN_PATH = "/api/host/login.json";
private static final String STREAM_IMAGE = "single";
private static final String STREAM_VIDEO = "jpeg";
private static final List<String> EMPTY_LIST = Collections.emptyList();
private static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
private final Logger logger = LoggerFactory.getLogger(ZmBridgeHandler.class);
private @Nullable Future<?> refreshMonitorsJob;
private final AtomicInteger monitorsCounter = new AtomicInteger();
private @Nullable MonitorDiscoveryService discoveryService;
private final AtomicInteger discoveryCounter = new AtomicInteger();
private List<Monitor> savedMonitors = new ArrayList<>();
private String host = "";
private boolean useSSL;
private @Nullable String portNumber;
private String urlPath = DEFAULT_URL_PATH;
private int monitorsInterval;
private int discoveryInterval;
private boolean discoveryEnabled;
private int defaultAlarmDuration;
private @Nullable Integer defaultImageRefreshInterval;
private final HttpClient httpClient;
private final ZmStateDescriptionOptionsProvider stateDescriptionProvider;
private ZmAuth zmAuth;
// Maintain mapping of handler and monitor id
private final Map<String, ZmMonitorHandler> monitorHandlers = new ConcurrentHashMap<>();
public ZmBridgeHandler(Bridge thing, HttpClient httpClient,
ZmStateDescriptionOptionsProvider stateDescriptionProvider) {
super(thing);
this.httpClient = httpClient;
this.stateDescriptionProvider = stateDescriptionProvider;
// Default to use no authentication
zmAuth = new ZmAuth(this);
}
@Override
public void initialize() {
ZmBridgeConfig config = getConfigAs(ZmBridgeConfig.class);
Integer value;
value = config.refreshInterval;
monitorsInterval = value == null ? MONITORS_INTERVAL_SECONDS : value;
value = config.discoveryInterval;
discoveryInterval = value == null ? DISCOVERY_INTERVAL_SECONDS : value;
value = config.defaultAlarmDuration;
defaultAlarmDuration = value == null ? DEFAULT_ALARM_DURATION_SECONDS : value;
defaultImageRefreshInterval = config.defaultImageRefreshInterval;
discoveryEnabled = config.discoveryEnabled == null ? false : config.discoveryEnabled.booleanValue();
host = config.host;
useSSL = config.useSSL.booleanValue();
portNumber = config.portNumber != null ? Integer.toString(config.portNumber) : null;
urlPath = config.urlPath;
// If user and password are configured, then use Zoneminder authentication
if (config.user != null && config.pass != null) {
zmAuth = new ZmAuth(this, config.user, config.pass);
}
if (isHostValid()) {
updateStatus(ThingStatus.ONLINE);
scheduleRefreshJob();
}
}
@Override
public void dispose() {
cancelRefreshJob();
}
@Override
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
String monitorId = (String) childThing.getConfiguration().get(CONFIG_MONITOR_ID);
monitorHandlers.put(monitorId, (ZmMonitorHandler) childHandler);
logger.debug("Bridge: Monitor handler was initialized for {} with id {}", childThing.getUID(), monitorId);
}
@Override
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
String monitorId = (String) childThing.getConfiguration().get(CONFIG_MONITOR_ID);
monitorHandlers.remove(monitorId);
logger.debug("Bridge: Monitor handler was disposed for {} with id {}", childThing.getUID(), monitorId);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case CHANNEL_IMAGE_MONITOR_ID:
handleMonitorIdCommand(command, CHANNEL_IMAGE_MONITOR_ID, CHANNEL_IMAGE_URL, STREAM_IMAGE);
break;
case CHANNEL_VIDEO_MONITOR_ID:
handleMonitorIdCommand(command, CHANNEL_VIDEO_MONITOR_ID, CHANNEL_VIDEO_URL, STREAM_VIDEO);
break;
}
}
private void handleMonitorIdCommand(Command command, String monitorIdChannelId, String urlChannelId, String type) {
if (command instanceof RefreshType || command == OnOffType.OFF) {
updateState(monitorIdChannelId, UnDefType.UNDEF);
updateState(urlChannelId, UnDefType.UNDEF);
} else if (command instanceof StringType) {
String id = command.toString();
if (isMonitorIdValid(id)) {
updateState(urlChannelId, new StringType(buildStreamUrl(id, type)));
} else {
updateState(monitorIdChannelId, UnDefType.UNDEF);
updateState(urlChannelId, UnDefType.UNDEF);
}
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(MonitorDiscoveryService.class);
}
public void setDiscoveryService(MonitorDiscoveryService discoveryService) {
this.discoveryService = discoveryService;
}
public boolean isDiscoveryEnabled() {
return discoveryEnabled;
}
public Integer getDefaultAlarmDuration() {
return defaultAlarmDuration;
}
public @Nullable Integer getDefaultImageRefreshInterval() {
return defaultImageRefreshInterval;
}
public List<Monitor> getSavedMonitors() {
return savedMonitors;
}
public Gson getGson() {
return GSON;
}
public void setFunction(String id, MonitorFunction function) {
if (!zmAuth.isAuthorized()) {
return;
}
logger.debug("Bridge: Setting monitor {} function to {}", id, function);
executePost(buildUrl(String.format("/api/monitors/%s.json", id)),
String.format("Monitor[Function]=%s", function.toString()));
}
public void setEnabled(String id, OnOffType enabled) {
if (!zmAuth.isAuthorized()) {
return;
}
logger.debug("Bridge: Setting monitor {} to {}", id, enabled);
executePost(buildUrl(String.format("/api/monitors/%s.json", id)),
String.format("Monitor[Enabled]=%s", enabled == OnOffType.ON ? "1" : "0"));
}
public void setAlarmOn(String id) {
if (!zmAuth.isAuthorized()) {
return;
}
logger.debug("Bridge: Turning alarm ON for monitor {}", id);
setAlarm(buildUrl(String.format("/api/monitors/alarm/id:%s/command:on.json", id)));
}
public void setAlarmOff(String id) {
if (!zmAuth.isAuthorized()) {
return;
}
logger.debug("Bridge: Turning alarm OFF for monitor {}", id);
setAlarm(buildUrl(String.format("/api/monitors/alarm/id:%s/command:off.json", id)));
}
public @Nullable RawType getImage(String id, @Nullable Integer imageRefreshIntervalSeconds) {
Integer localRefreshInterval = imageRefreshIntervalSeconds;
if (localRefreshInterval == null || localRefreshInterval.intValue() < 1 || !zmAuth.isAuthorized()) {
return null;
}
// Call should timeout just before the refresh interval
int timeout = Math.min((localRefreshInterval * 1000) - 500, API_TIMEOUT_MSEC);
Request request = httpClient.newRequest(buildStreamUrl(id, STREAM_IMAGE));
request.method(HttpMethod.GET);
request.timeout(timeout, TimeUnit.MILLISECONDS);
String errorMsg;
try {
ContentResponse response = request.send();
if (response.getStatus() == HttpStatus.OK_200) {
RawType image = new RawType(response.getContent(), response.getHeaders().get(HttpHeader.CONTENT_TYPE));
return image;
} else {
errorMsg = String.format("HTTP GET failed: %d, %s", response.getStatus(), response.getReason());
}
} catch (TimeoutException e) {
errorMsg = String.format("TimeoutException: Call to Zoneminder API timed out after {} msec", timeout);
} catch (ExecutionException e) {
errorMsg = String.format("ExecutionException: %s", e.getMessage());
} catch (InterruptedException e) {
errorMsg = String.format("InterruptedException: %s", e.getMessage());
Thread.currentThread().interrupt();
}
logger.debug("{}", errorMsg);
return null;
}
@SuppressWarnings("null")
private synchronized List<Monitor> getMonitors() {
List<Monitor> monitorList = new ArrayList<>();
if (!zmAuth.isAuthorized()) {
return monitorList;
}
try {
String response = executeGet(buildUrl("/api/monitors.json"));
MonitorsDTO monitors = GSON.fromJson(response, MonitorsDTO.class);
if (monitors != null && monitors.monitorItems != null) {
List<StateOption> options = new ArrayList<>();
for (MonitorItemDTO monitorItem : monitors.monitorItems) {
MonitorDTO m = monitorItem.monitor;
MonitorStatusDTO mStatus = monitorItem.monitorStatus;
if (m != null && mStatus != null) {
Monitor monitor = new Monitor(m.id, m.name, m.function, m.enabled, mStatus.status);
monitor.setHourEvents(m.hourEvents);
monitor.setDayEvents(m.dayEvents);
monitor.setWeekEvents(m.weekEvents);
monitor.setMonthEvents(m.monthEvents);
monitor.setTotalEvents(m.totalEvents);
monitor.setImageUrl(buildStreamUrl(m.id, STREAM_IMAGE));
monitor.setVideoUrl(buildStreamUrl(m.id, STREAM_VIDEO));
monitorList.add(monitor);
options.add(new StateOption(m.id, "Monitor " + m.id));
}
stateDescriptionProvider
.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_IMAGE_MONITOR_ID), options);
stateDescriptionProvider
.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_VIDEO_MONITOR_ID), options);
}
// Only update alarm and event info for monitors whose handlers are initialized
Set<String> ids = monitorHandlers.keySet();
for (Monitor m : monitorList) {
if (ids.contains(m.getId())) {
m.setState(getState(m.getId()));
m.setLastEvent(getLastEvent(m.getId()));
}
}
}
} catch (JsonSyntaxException e) {
logger.debug("Bridge: JsonSyntaxException: {}", e.getMessage(), e);
}
return monitorList;
}
@SuppressWarnings("null")
private @Nullable Event getLastEvent(String id) {
if (!zmAuth.isAuthorized()) {
return null;
}
try {
List<String> parameters = new ArrayList<>();
parameters.add("sort=StartTime");
parameters.add("direction=desc");
parameters.add("limit=1");
String response = executeGet(
buildUrlWithParameters(String.format("/api/events/index/MonitorId:%s.json", id), parameters));
EventsDTO events = GSON.fromJson(response, EventsDTO.class);
if (events != null && events.eventsList != null && events.eventsList.size() == 1) {
EventDTO e = events.eventsList.get(0).event;
Event event = new Event(e.eventId, e.name, e.cause, e.notes, e.startTime, e.endTime);
event.setFrames(e.frames);
event.setAlarmFrames(e.alarmFrames);
event.setLength(e.length);
return event;
}
} catch (JsonSyntaxException e) {
logger.debug("Bridge: JsonSyntaxException: {}", e.getMessage(), e);
}
return null;
}
private @Nullable VersionDTO getVersion() {
if (!zmAuth.isAuthorized()) {
return null;
}
VersionDTO version = null;
try {
String response = executeGet(buildUrl("/api/host/getVersion.json"));
version = GSON.fromJson(response, VersionDTO.class);
} catch (JsonSyntaxException e) {
logger.debug("Bridge: JsonSyntaxException: {}", e.getMessage(), e);
}
return version;
}
private void setAlarm(String url) {
executeGet(url);
}
@SuppressWarnings("null")
private MonitorState getState(String id) {
if (!zmAuth.isAuthorized()) {
return MonitorState.UNKNOWN;
}
try {
String response = executeGet(buildUrl(String.format("/api/monitors/alarm/id:%s/command:status.json", id)));
MonitorStateDTO monitorState = GSON.fromJson(response, MonitorStateDTO.class);
if (monitorState != null) {
MonitorState state = monitorState.state;
return state != null ? state : MonitorState.UNKNOWN;
}
} catch (JsonSyntaxException e) {
logger.debug("Bridge: JsonSyntaxException: {}", e.getMessage(), e);
}
return MonitorState.UNKNOWN;
}
public @Nullable String executeGet(String url) {
try {
long startTime = System.currentTimeMillis();
String response = HttpUtil.executeUrl("GET", url, API_TIMEOUT_MSEC);
logger.trace("Bridge: Http GET of '{}' returned '{}' in {} ms", url, response,
System.currentTimeMillis() - startTime);
return response;
} catch (IOException e) {
logger.debug("Bridge: IOException on GET request, url='{}': {}", url, e.getMessage());
}
return null;
}
private @Nullable String executePost(String url, String content) {
return executePost(url, content, "application/x-www-form-urlencoded");
}
public @Nullable String executePost(String url, String content, String contentType) {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
long startTime = System.currentTimeMillis();
String response = HttpUtil.executeUrl("POST", url, inputStream, contentType, API_TIMEOUT_MSEC);
logger.trace("Bridge: Http POST content '{}' to '{}' returned: {} in {} ms", content, url, response,
System.currentTimeMillis() - startTime);
return response;
} catch (IOException e) {
logger.debug("Bridge: IOException on POST request, url='{}': {}", url, e.getMessage());
}
return null;
}
public String buildLoginUrl() {
return buildBaseUrl(LOGIN_PATH).toString();
}
public String buildLoginUrl(String tokenParameter) {
StringBuilder sb = buildBaseUrl(LOGIN_PATH);
sb.append(tokenParameter);
return sb.toString();
}
private String buildStreamUrl(String id, String streamType) {
List<String> parameters = new ArrayList<>();
parameters.add(String.format("mode=%s", streamType));
parameters.add(String.format("monitor=%s", id));
return buildUrlWithParameters("/cgi-bin/zms", parameters);
}
private String buildUrl(String path) {
return buildUrlWithParameters(path, EMPTY_LIST);
}
private String buildUrlWithParameters(String path, List<String> parameters) {
StringBuilder sb = buildBaseUrl(path);
String joiner = "?";
for (String parameter : parameters) {
sb.append(joiner).append(parameter);
joiner = "&";
}
if (zmAuth.usingAuthorization()) {
sb.append(joiner).append("token=").append(zmAuth.getAccessToken());
}
return sb.toString();
}
private StringBuilder buildBaseUrl(String path) {
StringBuilder sb = new StringBuilder();
sb.append(useSSL ? "https://" : "http://");
sb.append(host);
if (portNumber != null) {
sb.append(":").append(portNumber);
}
sb.append(urlPath);
sb.append(path);
return sb;
}
private boolean isMonitorIdValid(String id) {
return savedMonitors.stream().filter(monitor -> id.equals(monitor.getId())).findAny().isPresent();
}
private boolean isHostValid() {
logger.debug("Bridge: Checking for valid Zoneminder host: {}", host);
VersionDTO version = getVersion();
if (version != null) {
if (checkSoftwareVersion(version.version) && checkApiVersion(version.apiVersion)) {
return true;
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Can't get version information");
}
return false;
}
private boolean checkSoftwareVersion(@Nullable String softwareVersion) {
logger.debug("Bridge: Zoneminder software version is {}", softwareVersion);
if (softwareVersion != null) {
String[] versionParts = softwareVersion.split("\\.");
if (versionParts.length >= 2) {
try {
int versionMajor = Integer.parseInt(versionParts[0]);
int versionMinor = Integer.parseInt(versionParts[1]);
if (versionMajor == 1 && versionMinor >= 34) {
logger.debug("Bridge: Zoneminder software version check OK");
return true;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String
.format("Current Zoneminder version: %s. Requires version >= 1.34.0", softwareVersion));
}
} catch (NumberFormatException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
String.format("Badly formatted version number: %s", softwareVersion));
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
String.format("Can't parse software version: %s", softwareVersion));
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Software version is null");
}
return false;
}
private boolean checkApiVersion(@Nullable String apiVersion) {
logger.debug("Bridge: Zoneminder API version is {}", apiVersion);
if (apiVersion != null) {
String[] versionParts = apiVersion.split("\\.");
if (versionParts.length >= 2) {
try {
int versionMajor = Integer.parseInt(versionParts[0]);
if (versionMajor >= 2) {
logger.debug("Bridge: Zoneminder API version check OK");
return true;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String
.format("Requires API version >= 2.0. This Zoneminder is API version {}", apiVersion));
}
} catch (NumberFormatException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
String.format("Badly formatted API version: %s", apiVersion));
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
String.format("Can't parse API version: %s", apiVersion));
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "API version is null");
}
return false;
}
/*
* The refresh job is executed every second
* - updates the monitor handlers every monitorsInterval seconds, and
* - runs the monitor discovery every discoveryInterval seconds
*/
private void refresh() {
refreshMonitors();
discoverMonitors();
}
@SuppressWarnings("null")
private void refreshMonitors() {
if (monitorsCounter.getAndDecrement() == 0) {
monitorsCounter.set(monitorsInterval);
List<Monitor> monitors = getMonitors();
savedMonitors = monitors;
for (Monitor monitor : monitors) {
ZmMonitorHandler handler = monitorHandlers.get(monitor.getId());
if (handler != null) {
handler.updateStatus(monitor);
}
}
}
}
private void discoverMonitors() {
if (isDiscoveryEnabled()) {
if (discoveryCounter.getAndDecrement() == 0) {
discoveryCounter.set(discoveryInterval);
MonitorDiscoveryService localDiscoveryService = discoveryService;
if (localDiscoveryService != null) {
logger.trace("Bridge: Running monitor discovery");
localDiscoveryService.startBackgroundDiscovery();
}
}
}
}
private void scheduleRefreshJob() {
logger.debug("Bridge: Scheduling monitors refresh job");
cancelRefreshJob();
monitorsCounter.set(MONITORS_INITIAL_DELAY_SECONDS);
discoveryCounter.set(DISCOVERY_INITIAL_DELAY_SECONDS);
refreshMonitorsJob = scheduler.scheduleWithFixedDelay(this::refresh, REFRESH_STARTUP_DELAY_SECONDS,
REFRESH_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
private void cancelRefreshJob() {
Future<?> localRefreshThermostatsJob = refreshMonitorsJob;
if (localRefreshThermostatsJob != null) {
localRefreshThermostatsJob.cancel(true);
logger.debug("Bridge: Canceling monitors refresh job");
}
}
}

View File

@@ -0,0 +1,303 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import static org.openhab.binding.zoneminder.internal.ZmBindingConstants.*;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Time;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.zoneminder.action.ZmActions;
import org.openhab.binding.zoneminder.internal.config.ZmMonitorConfig;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ZmMonitorHandler} represents a Zoneminder monitor. The monitor handler
* interacts with the server bridge to communicate with the Zoneminder server.
*
* @author Mark Hilbush - Initial contribution
*/
@NonNullByDefault
public class ZmMonitorHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(ZmMonitorHandler.class);
private final TimeZoneProvider timeZoneProvider;
private @Nullable ZmBridgeHandler bridgeHandler;
private @NonNullByDefault({}) String monitorId;
private @Nullable Integer imageRefreshIntervalSeconds;
private Integer alarmDuration = DEFAULT_ALARM_DURATION_SECONDS;
private @Nullable ScheduledFuture<?> imageRefreshJob;
private @Nullable ScheduledFuture<?> alarmOffJob;
private final Map<String, State> monitorStatusCache = new ConcurrentHashMap<>();
public ZmMonitorHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
super(thing);
this.timeZoneProvider = timeZoneProvider;
}
@Override
public void initialize() {
ZmMonitorConfig config = getConfigAs(ZmMonitorConfig.class);
monitorId = config.monitorId;
imageRefreshIntervalSeconds = config.imageRefreshInterval;
Integer value = config.alarmDuration;
alarmDuration = value != null ? value : DEFAULT_ALARM_DURATION_SECONDS;
bridgeHandler = (ZmBridgeHandler) getBridge().getHandler();
monitorStatusCache.clear();
updateStatus(ThingStatus.ONLINE);
startImageRefreshJob();
}
@Override
public void dispose() {
stopAlarmOffJob();
turnAlarmOff();
stopImageRefreshJob();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
State state = monitorStatusCache.get(channelUID.getId());
if (state != null) {
updateState(channelUID, state);
}
return;
}
logger.debug("Monitor {}: Received command '{}' for channel '{}'", monitorId, command, channelUID.getId());
ZmBridgeHandler localHandler = bridgeHandler;
if (localHandler == null) {
logger.warn("Monitor {}: Can't execute command because bridge handler is null", monitorId);
return;
}
switch (channelUID.getId()) {
case CHANNEL_FUNCTION:
if (command instanceof StringType) {
try {
MonitorFunction function = MonitorFunction.forValue(command.toString());
localHandler.setFunction(monitorId, function);
logger.debug("Monitor {}: Set monitor state to {}", monitorId, function);
} catch (IllegalArgumentException e) {
logger.debug("Monitor {}: Invalid function: {}", monitorId, command);
}
}
break;
case CHANNEL_ENABLE:
if (command instanceof OnOffType) {
localHandler.setEnabled(monitorId, (OnOffType) command);
logger.debug("Monitor {}: Set monitor enable to {}", monitorId, command);
}
break;
case CHANNEL_TRIGGER_ALARM:
if (command instanceof OnOffType) {
logger.debug("Monitor {}: Set monitor alarm to {}", monitorId, command);
if (command == OnOffType.ON) {
localHandler.setAlarmOn(monitorId);
startAlarmOffJob(alarmDuration.intValue());
} else {
stopAlarmOffJob();
localHandler.setAlarmOff(monitorId);
}
}
break;
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(ZmActions.class);
}
public String getId() {
return monitorId;
}
public void actionTriggerAlarm(@Nullable Number duration) {
if (duration == null) {
return;
}
ZmBridgeHandler localHandler = bridgeHandler;
if (localHandler != null) {
logger.debug("Monitor {}: Action tell bridge to turn on alarm", monitorId);
localHandler.setAlarmOn(monitorId);
startAlarmOffJob(duration.intValue());
}
}
public void actionTriggerAlarm() {
actionTriggerAlarm(alarmDuration);
}
public void actionCancelAlarm() {
ZmBridgeHandler localHandler = bridgeHandler;
if (localHandler != null) {
logger.debug("Monitor {}: Action tell bridge to turn off alarm", monitorId);
stopAlarmOffJob();
localHandler.setAlarmOff(monitorId);
}
}
@SuppressWarnings("null")
public void updateStatus(Monitor m) {
logger.debug("Monitor {}: Updating: {}", m.getId(), m.toString());
updateChannelState(CHANNEL_ID, new StringType(m.getId()));
updateChannelState(CHANNEL_NAME, new StringType(m.getName()));
updateChannelState(CHANNEL_FUNCTION, new StringType(m.getFunction()));
updateChannelState(CHANNEL_ENABLE, m.isEnabled() ? OnOffType.ON : OnOffType.OFF);
updateChannelState(CHANNEL_HOUR_EVENTS, new DecimalType(m.getHourEvents()));
updateChannelState(CHANNEL_DAY_EVENTS, new DecimalType(m.getDayEvents()));
updateChannelState(CHANNEL_WEEK_EVENTS, new DecimalType(m.getWeekEvents()));
updateChannelState(CHANNEL_MONTH_EVENTS, new DecimalType(m.getMonthEvents()));
updateChannelState(CHANNEL_TOTAL_EVENTS, new DecimalType(m.getTotalEvents()));
updateChannelState(CHANNEL_IMAGE_URL, new StringType(m.getImageUrl()));
updateChannelState(CHANNEL_VIDEO_URL, new StringType(m.getVideoUrl()));
updateChannelState(CHANNEL_ALARM, m.isAlarm() ? OnOffType.ON : OnOffType.OFF);
updateChannelState(CHANNEL_STATE, new StringType(m.getState().toString()));
if (!m.isAlarm()) {
updateChannelState(CHANNEL_TRIGGER_ALARM, m.isAlarm() ? OnOffType.ON : OnOffType.OFF);
}
Event event = m.getLastEvent();
if (event == null) {
clearEventChannels();
} else if (event.getEnd() != null) {
// If end is null, assume event hasn't completed yet
logger.trace("Monitor {}: Id:{}, Frames:{}, AlarmFrames:{}, Length:{}", m.getId(), event.getId(),
event.getFrames(), event.getAlarmFrames(), event.getLength());
updateChannelState(CHANNEL_EVENT_ID, new StringType(event.getId()));
updateChannelState(CHANNEL_EVENT_NAME, new StringType(event.getName()));
updateChannelState(CHANNEL_EVENT_CAUSE, new StringType(event.getCause()));
updateChannelState(CHANNEL_EVENT_NOTES, new StringType(event.getNotes()));
updateChannelState(CHANNEL_EVENT_START, new DateTimeType(
ZonedDateTime.ofInstant(event.getStart().toInstant(), timeZoneProvider.getTimeZone())));
updateChannelState(CHANNEL_EVENT_END, new DateTimeType(
ZonedDateTime.ofInstant(event.getEnd().toInstant(), timeZoneProvider.getTimeZone())));
updateChannelState(CHANNEL_EVENT_FRAMES, new DecimalType(event.getFrames()));
updateChannelState(CHANNEL_EVENT_ALARM_FRAMES, new DecimalType(event.getAlarmFrames()));
updateChannelState(CHANNEL_EVENT_LENGTH, new QuantityType<Time>(event.getLength(), SmartHomeUnits.SECOND));
}
}
private void clearEventChannels() {
updateChannelState(CHANNEL_EVENT_ID, UnDefType.NULL);
updateChannelState(CHANNEL_EVENT_NAME, UnDefType.NULL);
updateChannelState(CHANNEL_EVENT_CAUSE, UnDefType.NULL);
updateChannelState(CHANNEL_EVENT_NOTES, UnDefType.NULL);
updateChannelState(CHANNEL_EVENT_START, UnDefType.NULL);
updateChannelState(CHANNEL_EVENT_END, UnDefType.NULL);
updateChannelState(CHANNEL_EVENT_FRAMES, UnDefType.NULL);
updateChannelState(CHANNEL_EVENT_ALARM_FRAMES, UnDefType.NULL);
updateChannelState(CHANNEL_EVENT_LENGTH, UnDefType.NULL);
}
private void refreshImage() {
if (isLinked(CHANNEL_IMAGE)) {
getImage();
} else {
logger.trace("Monitor {}: Can't update image because '{}' channel is not linked", CHANNEL_IMAGE, monitorId);
}
}
private void getImage() {
ZmBridgeHandler localHandler = bridgeHandler;
if (localHandler != null) {
logger.debug("Monitor {}: Updating image channel", monitorId);
RawType image = localHandler.getImage(monitorId, imageRefreshIntervalSeconds);
updateChannelState(CHANNEL_IMAGE, image != null ? image : UnDefType.UNDEF);
}
}
private void updateChannelState(String channelId, State state) {
updateState(channelId, state);
monitorStatusCache.put(channelId, state);
}
private void startImageRefreshJob() {
Integer interval = imageRefreshIntervalSeconds;
if (interval != null) {
long delay = getRandomDelay(interval);
imageRefreshJob = scheduler.scheduleWithFixedDelay(this::refreshImage, delay, interval, TimeUnit.SECONDS);
logger.debug("Monitor {}: Scheduled image refresh job will run every {} seconds starting in {} seconds",
monitorId, interval, delay);
}
}
private void stopImageRefreshJob() {
ScheduledFuture<?> localImageRefreshJob = imageRefreshJob;
if (localImageRefreshJob != null) {
logger.debug("Monitor {}: Canceled image refresh job", monitorId);
localImageRefreshJob.cancel(true);
imageRefreshJob = null;
}
}
private void turnAlarmOff() {
ZmBridgeHandler localHandler = bridgeHandler;
if (alarmOffJob != null && localHandler != null) {
logger.debug("Monitor {}: Tell bridge to turn off alarm", monitorId);
localHandler.setAlarmOff(monitorId);
}
}
private void startAlarmOffJob(int duration) {
stopAlarmOffJob();
if (duration != 0) {
alarmOffJob = scheduler.schedule(this::turnAlarmOff, duration, TimeUnit.SECONDS);
logger.debug("Monitor {}: Scheduled alarm off job in {} seconds", monitorId, duration);
}
}
private void stopAlarmOffJob() {
ScheduledFuture<?> localAlarmOffJob = alarmOffJob;
if (localAlarmOffJob != null) {
logger.debug("Monitor {}: Canceled alarm off job", monitorId);
localAlarmOffJob.cancel(true);
alarmOffJob = null;
}
}
private long getRandomDelay(int interval) {
return System.currentTimeMillis() % interval;
}
}

View File

@@ -1,394 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.openhab.binding.zoneminder.internal.DataRefreshPriorityEnum;
import org.openhab.binding.zoneminder.internal.ZoneMinderConstants;
import org.openhab.binding.zoneminder.internal.config.ZoneMinderThingConfig;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import name.eskildsen.zoneminder.IZoneMinderConnectionInfo;
import name.eskildsen.zoneminder.IZoneMinderSession;
import name.eskildsen.zoneminder.ZoneMinderFactory;
import name.eskildsen.zoneminder.exception.ZoneMinderUrlNotFoundException;
/**
* The {@link ZoneMinderBaseThingHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Martin S. Eskildsen - Initial contribution
*/
public abstract class ZoneMinderBaseThingHandler extends BaseThingHandler implements ZoneMinderHandler {
/** Logger for the Thing. */
private final Logger logger = LoggerFactory.getLogger(ZoneMinderBaseThingHandler.class);
/** Bridge Handler for the Thing. */
public ZoneMinderServerBridgeHandler zoneMinderBridgeHandler;
/** This refresh status. */
private boolean thingRefreshed;
private Lock lockSession = new ReentrantLock();
private IZoneMinderSession zoneMinderSession;
/** Configuration from openHAB */
protected ZoneMinderThingConfig configuration;
private DataRefreshPriorityEnum refreshPriority = DataRefreshPriorityEnum.SCHEDULED;
protected boolean isOnline() {
if (zoneMinderSession == null) {
return false;
}
if (!zoneMinderSession.isConnected()) {
return false;
}
return true;
}
public DataRefreshPriorityEnum getRefreshPriority() {
return refreshPriority;
}
public ZoneMinderBaseThingHandler(Thing thing) {
super(thing);
}
/**
* Initializes the monitor.
*
* @author Martin S. Eskildsen
*
*/
@Override
public void initialize() {
updateStatus(ThingStatus.ONLINE);
}
protected boolean isConnected() {
if (zoneMinderSession == null) {
return false;
}
return zoneMinderSession.isConnected();
}
protected IZoneMinderSession aquireSession() {
lockSession.lock();
return zoneMinderSession;
}
protected void releaseSession() {
lockSession.unlock();
}
/**
* Method to start a priority data refresh task.
*/
protected boolean startPriorityRefresh() {
logger.info("[MONITOR-{}]: Starting High Priority Refresh", getZoneMinderId());
refreshPriority = DataRefreshPriorityEnum.HIGH_PRIORITY;
return true;
}
/**
* Method to stop the data Refresh task.
*/
protected void stopPriorityRefresh() {
logger.info("{}: Stopping Priority Refresh for Monitor", getLogIdentifier());
refreshPriority = DataRefreshPriorityEnum.SCHEDULED;
}
@Override
public void dispose() {
}
/**
* Helper method for getting ChannelUID from ChannelId.
*
*/
public ChannelUID getChannelUIDFromChannelId(String id) {
Channel ch = thing.getChannel(id);
if (ch == null) {
return null;
} else {
return ch.getUID();
}
}
protected abstract void onFetchData();
/**
* Method to Refresh Thing Handler.
*/
public final synchronized void refreshThing(IZoneMinderSession session, DataRefreshPriorityEnum refreshPriority) {
if ((refreshPriority != getRefreshPriority()) && (!isConnected())) {
return;
}
if (refreshPriority == DataRefreshPriorityEnum.HIGH_PRIORITY) {
logger.debug("{}: Performing HIGH PRIORITY refresh", getLogIdentifier());
} else {
logger.debug("{}: Performing refresh", getLogIdentifier());
}
if (getZoneMinderBridgeHandler() != null) {
if (isConnected()) {
logger.debug("{}: refreshThing(): Bridge '{}' Found for Thing '{}'!", getLogIdentifier(),
getThing().getUID(), this.getThing().getUID());
onFetchData();
}
}
Thing thing = getThing();
List<Channel> channels = thing.getChannels();
logger.debug("{}: refreshThing(): Refreshing Thing - {}", getLogIdentifier(), thing.getUID());
for (Channel channel : channels) {
updateChannel(channel.getUID());
}
this.setThingRefreshed(true);
logger.debug("[{}: refreshThing(): Thing Refreshed - {}", getLogIdentifier(), thing.getUID());
}
/**
* Get the Bridge Handler for ZoneMinder.
*
* @return zoneMinderBridgeHandler
*/
public synchronized ZoneMinderServerBridgeHandler getZoneMinderBridgeHandler() {
if (this.zoneMinderBridgeHandler == null) {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("{}: getZoneMinderBridgeHandler(): Unable to get bridge!", getLogIdentifier());
return null;
}
logger.debug("{}: getZoneMinderBridgeHandler(): Bridge for '{}' - '{}'", getLogIdentifier(),
getThing().getUID(), bridge.getUID());
ThingHandler handler = null;
try {
handler = bridge.getHandler();
} catch (Exception ex) {
logger.debug("{}: Exception in 'getZoneMinderBridgeHandler()': {}", getLogIdentifier(),
ex.getMessage());
}
if (handler instanceof ZoneMinderServerBridgeHandler) {
this.zoneMinderBridgeHandler = (ZoneMinderServerBridgeHandler) handler;
} else {
logger.debug("{}: getZoneMinderBridgeHandler(): Unable to get bridge handler!", getLogIdentifier());
}
}
return this.zoneMinderBridgeHandler;
}
/**
* Method to Update a Channel
*
* @param channel
*/
@Override
public void updateChannel(ChannelUID channel) {
switch (channel.getId()) {
case ZoneMinderConstants.CHANNEL_ONLINE:
updateState(channel, getChannelBoolAsOnOffState(isOnline()));
break;
default:
logger.error(
"{}: updateChannel() in base class, called for an unknown channel '{}', this channel must be handled in super class.",
getLogIdentifier(), channel.getId());
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
@Override
public void onBridgeConnected(ZoneMinderServerBridgeHandler bridge, IZoneMinderConnectionInfo connection)
throws IllegalArgumentException, GeneralSecurityException, IOException, ZoneMinderUrlNotFoundException {
lockSession.lock();
try {
zoneMinderSession = ZoneMinderFactory.CreateSession(connection);
} finally {
lockSession.unlock();
}
}
@Override
public void onBridgeDisconnected(ZoneMinderServerBridgeHandler bridge) {
if (bridge.getThing().getUID().equals(getThing().getBridgeUID())) {
this.setThingRefreshed(false);
}
lockSession.lock();
try {
zoneMinderSession = null;
} finally {
lockSession.unlock();
}
}
/**
* Get Channel by ChannelUID.
*
* @param {ChannelUID} channelUID Identifier of Channel
*/
public Channel getChannel(ChannelUID channelUID) {
Channel channel = null;
List<Channel> channels = getThing().getChannels();
for (Channel ch : channels) {
if (channelUID == ch.getUID()) {
channel = ch;
break;
}
}
return channel;
}
/**
* Get Thing Handler refresh status.
*
* @return thingRefresh
*/
public boolean isThingRefreshed() {
return thingRefreshed;
}
/**
* Set Thing Handler refresh status.
*
* @param {boolean} refreshed Sets status refreshed of thing
*/
public void setThingRefreshed(boolean refreshed) {
this.thingRefreshed = refreshed;
}
protected abstract String getZoneMinderThingType();
private Object getConfigValue(String configKey) {
return getThing().getConfiguration().getProperties().get(configKey);
}
/*
* Helper to get a value from configuration as a String
*
* @author Martin S. Eskildsen
*
*/
protected String getConfigValueAsString(String configKey) {
return (String) getConfigValue(configKey);
}
/*
* Helper to get a value from configuration as a Integer
*
* @author Martin S. Eskildsen
*
*/
protected Integer getConfigValueAsInteger(String configKey) {
return (Integer) getConfigValue(configKey);
}
protected BigDecimal getConfigValueAsBigDecimal(String configKey) {
return (BigDecimal) getConfigValue(configKey);
}
protected State getChannelStringAsStringState(String channelValue) {
State state = UnDefType.UNDEF;
try {
if (isConnected()) {
state = new StringType(channelValue);
}
} catch (Exception ex) {
logger.error("{}", ex.getMessage());
}
return state;
}
protected State getChannelBoolAsOnOffState(boolean value) {
State state = UnDefType.UNDEF;
try {
if (isConnected()) {
state = value ? OnOffType.ON : OnOffType.OFF;
}
} catch (Exception ex) {
logger.error("{}: Exception occurred in 'getChannelBoolAsOnOffState()' (Exception='{}')",
getLogIdentifier(), ex.getMessage());
}
return state;
}
@Override
public abstract String getLogIdentifier();
protected void updateThingStatus(ThingStatus thingStatus, ThingStatusDetail statusDetail,
String statusDescription) {
ThingStatusInfo curStatusInfo = thing.getStatusInfo();
String curDescription = ((curStatusInfo.getDescription() == null) ? "" : curStatusInfo.getDescription());
// Status changed
if ((curStatusInfo.getStatus() != thingStatus) || (curStatusInfo.getStatusDetail() != statusDetail)
|| (curDescription != statusDescription)) {
// Update Status correspondingly
if ((thingStatus == ThingStatus.OFFLINE) && (statusDetail != ThingStatusDetail.NONE)) {
logger.info("{}: Thing status changed from '{}' to '{}' (DetailedStatus='{}', Description='{}')",
getLogIdentifier(), thing.getStatus(), thingStatus, statusDetail, statusDescription);
updateStatus(thingStatus, statusDetail, statusDescription);
} else {
logger.info("{}: Thing status changed from '{}' to '{}'", getLogIdentifier(), thing.getStatus(),
thingStatus);
updateStatus(thingStatus);
}
}
}
}

View File

@@ -1,45 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import java.io.IOException;
import java.security.GeneralSecurityException;
import org.openhab.core.thing.ChannelUID;
import name.eskildsen.zoneminder.IZoneMinderConnectionInfo;
import name.eskildsen.zoneminder.exception.ZoneMinderUrlNotFoundException;
/**
* Interface for ZoneMinder handlers.
*
* @author Martin S. Eskildsen - Initial contribution
*/
public interface ZoneMinderHandler {
String getZoneMinderId();
/**
* Method used to relate a log entry to a thing
*/
String getLogIdentifier();
void updateAvaliabilityStatus(IZoneMinderConnectionInfo connection);
void updateChannel(ChannelUID channel);
void onBridgeConnected(ZoneMinderServerBridgeHandler bridge, IZoneMinderConnectionInfo connection)
throws IllegalArgumentException, GeneralSecurityException, IOException, ZoneMinderUrlNotFoundException;
void onBridgeDisconnected(ZoneMinderServerBridgeHandler bridge);
}

View File

@@ -1,836 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import javax.security.auth.login.FailedLoginException;
import org.openhab.binding.zoneminder.internal.DataRefreshPriorityEnum;
import org.openhab.binding.zoneminder.internal.ZoneMinderConstants;
import org.openhab.binding.zoneminder.internal.ZoneMinderProperties;
import org.openhab.binding.zoneminder.internal.config.ZoneMinderThingMonitorConfig;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import name.eskildsen.zoneminder.IZoneMinderConnectionInfo;
import name.eskildsen.zoneminder.IZoneMinderDaemonStatus;
import name.eskildsen.zoneminder.IZoneMinderEventData;
import name.eskildsen.zoneminder.IZoneMinderEventSubscriber;
import name.eskildsen.zoneminder.IZoneMinderMonitor;
import name.eskildsen.zoneminder.IZoneMinderMonitorData;
import name.eskildsen.zoneminder.IZoneMinderSession;
import name.eskildsen.zoneminder.ZoneMinderFactory;
import name.eskildsen.zoneminder.api.event.ZoneMinderEvent;
import name.eskildsen.zoneminder.api.telnet.ZoneMinderTriggerEvent;
import name.eskildsen.zoneminder.common.ZoneMinderMonitorFunctionEnum;
import name.eskildsen.zoneminder.common.ZoneMinderMonitorStatusEnum;
import name.eskildsen.zoneminder.exception.ZoneMinderUrlNotFoundException;
/**
* The {@link ZoneMinderThingMonitorHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Martin S. Eskildsen - Initial contribution
*/
public class ZoneMinderThingMonitorHandler extends ZoneMinderBaseThingHandler implements IZoneMinderEventSubscriber {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
.singleton(ZoneMinderConstants.THING_TYPE_THING_ZONEMINDER_MONITOR);
/** Make sure we can log errors, warnings or what ever somewhere */
private final Logger logger = LoggerFactory.getLogger(ZoneMinderThingMonitorHandler.class);
private ZoneMinderThingMonitorConfig config;
private ZoneMinderEvent curEvent;
/**
* Channels
*/
private ZoneMinderMonitorFunctionEnum channelFunction = ZoneMinderMonitorFunctionEnum.NONE;
private Boolean channelEnabled = false;
private boolean channelRecordingState;
private boolean channelAlarmedState;
private String channelEventCause = "";
private ZoneMinderMonitorStatusEnum channelMonitorStatus = ZoneMinderMonitorStatusEnum.UNKNOWN;
private boolean channelDaemonCapture;
private boolean channelDaemonAnalysis;
private boolean channelDaemonFrame;
private boolean channelForceAlarm;
private int forceAlarmManualState = -1;
public ZoneMinderThingMonitorHandler(Thing thing) {
super(thing);
logger.info("{}: Starting ZoneMinder Server Thing Handler (Thing='{}')", getLogIdentifier(), thing.getUID());
}
@Override
public void dispose() {
}
@Override
public String getZoneMinderId() {
if (config == null) {
logger.error("{}: Configuration for Thing '{}' is not loaded correctly.", getLogIdentifier(),
getThing().getUID());
return "";
}
return config.getZoneMinderId().toString();
}
@Override
public void onBridgeConnected(ZoneMinderServerBridgeHandler bridge, IZoneMinderConnectionInfo connection)
throws IllegalArgumentException, GeneralSecurityException, IOException, ZoneMinderUrlNotFoundException {
try {
logger.info("{}: Bridge '{}' connected", getLogIdentifier(), bridge.getThing().getUID().getAsString());
super.onBridgeConnected(bridge, connection);
ZoneMinderFactory.SubscribeMonitorEvents(connection, config.getZoneMinderId(), this);
IZoneMinderSession session = aquireSession();
IZoneMinderMonitor monitor = ZoneMinderFactory.getMonitorProxy(session, config.getZoneMinderId());
IZoneMinderMonitorData monitorData = monitor.getMonitorData();
logger.debug("{}: SourceType: {}", getLogIdentifier(), monitorData.getSourceType().name());
logger.debug("{}: Format: {}", getLogIdentifier(), monitorData.getFormat());
logger.debug("{}: AlarmFrameCount: {}", getLogIdentifier(), monitorData.getAlarmFrameCount());
logger.debug("{}: AlarmMaxFPS: {}", getLogIdentifier(), monitorData.getAlarmMaxFPS());
logger.debug("{}: AnalysisFPS: {}", getLogIdentifier(), monitorData.getAnalysisFPS());
logger.debug("{}: Height x Width: {} x {}", getLogIdentifier(), monitorData.getHeight(),
monitorData.getWidth());
updateMonitorProperties(session);
} catch (Exception ex) {
logger.error("{}: Exception occurred when calling 'onBridgeConencted()'. Exception='{}'",
getLogIdentifier(), ex.getMessage());
} finally {
releaseSession();
}
}
@Override
public void onBridgeDisconnected(ZoneMinderServerBridgeHandler bridge) {
try {
logger.info("{}: Bridge '{}' disconnected", getLogIdentifier(), bridge.getThing().getUID().getAsString());
logger.info("{}: Unsubscribing from Monitor Events: {}", getLogIdentifier(),
bridge.getThing().getUID().getAsString());
ZoneMinderFactory.UnsubscribeMonitorEvents(config.getZoneMinderId(), this);
logger.debug("{}: Calling parent onBridgeConnected()", getLogIdentifier());
super.onBridgeDisconnected(bridge);
} catch (Exception ex) {
logger.error("{}: Exception occurred when calling 'onBridgeDisonencted()'. Exception='{}'",
getLogIdentifier(), ex.getMessage());
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
try {
logger.debug("{}: Channel '{}' in monitor '{}' received command='{}'", getLogIdentifier(), channelUID,
getZoneMinderId(), command);
// Allow refresh of channels
if (command == RefreshType.REFRESH) {
updateChannel(channelUID);
return;
}
// Communication TO Monitor
switch (channelUID.getId()) {
// Done via Telnet connection
case ZoneMinderConstants.CHANNEL_MONITOR_FORCE_ALARM:
logger.debug(
"{}: 'handleCommand' => CHANNEL_MONITOR_FORCE_ALARM: Command '{}' received for monitor '{}'",
getLogIdentifier(), command, channelUID.getId());
if ((command == OnOffType.OFF) || (command == OnOffType.ON)) {
String eventText = getConfigValueAsString(ZoneMinderConstants.PARAMETER_MONITOR_EVENTTEXT);
BigDecimal eventTimeout = getConfigValueAsBigDecimal(
ZoneMinderConstants.PARAMETER_MONITOR_TRIGGER_TIMEOUT);
ZoneMinderServerBridgeHandler bridge = getZoneMinderBridgeHandler();
if (bridge == null) {
logger.warn("'handleCommand()': Bridge is 'null'!");
}
IZoneMinderMonitor monitorProxy = ZoneMinderFactory.getMonitorProxy(aquireSession(),
getZoneMinderId());
try {
if (command == OnOffType.ON) {
forceAlarmManualState = 1;
logger.info("{}: Activate 'ForceAlarm' to '{}' (Reason='{}', Timeout='{}')",
getLogIdentifier(), command, eventText, eventTimeout.intValue());
monitorProxy.activateForceAlarm(255, ZoneMinderConstants.MONITOR_EVENT_OPENHAB,
eventText, "", eventTimeout.intValue());
}
else if (command == OnOffType.OFF) {
forceAlarmManualState = 0;
logger.info("{}: Cancel 'ForceAlarm'", getLogIdentifier());
monitorProxy.deactivateForceAlarm();
}
} finally {
releaseSession();
}
recalculateChannelStates();
handleCommand(channelUID, RefreshType.REFRESH);
handleCommand(getChannelUIDFromChannelId(ZoneMinderConstants.CHANNEL_MONITOR_EVENT_STATE),
RefreshType.REFRESH);
handleCommand(getChannelUIDFromChannelId(ZoneMinderConstants.CHANNEL_MONITOR_RECORD_STATE),
RefreshType.REFRESH);
// Force a refresh
startPriorityRefresh();
}
break;
case ZoneMinderConstants.CHANNEL_MONITOR_ENABLED:
logger.debug(
"{}: 'handleCommand' => CHANNEL_MONITOR_ENABLED: Command '{}' received for monitor '{}'",
getLogIdentifier(), command, channelUID.getId());
if ((command == OnOffType.OFF) || (command == OnOffType.ON)) {
boolean newState = ((command == OnOffType.ON) ? true : false);
IZoneMinderMonitor monitorProxy = ZoneMinderFactory.getMonitorProxy(aquireSession(),
getZoneMinderId());
try {
monitorProxy.SetEnabled(newState);
} finally {
releaseSession();
}
channelEnabled = newState;
logger.info("{}: Setting enabled to '{}'", getLogIdentifier(), command);
}
handleCommand(channelUID, RefreshType.REFRESH);
break;
case ZoneMinderConstants.CHANNEL_MONITOR_FUNCTION:
String commandString = "";
if (ZoneMinderMonitorFunctionEnum.isValid(command.toString())) {
commandString = ZoneMinderMonitorFunctionEnum.getEnum(command.toString()).toString();
IZoneMinderMonitor monitorProxy = ZoneMinderFactory.getMonitorProxy(aquireSession(),
getZoneMinderId());
try {
monitorProxy.SetFunction(commandString);
} finally {
releaseSession();
}
// Make sure local copy is set to new value
channelFunction = ZoneMinderMonitorFunctionEnum.getEnum(command.toString());
logger.info("{}: Setting function to '{}'", getLogIdentifier(), commandString);
} else {
logger.error(
"{}: Value '{}' for monitor channel is not valid. Accepted values is: 'None', 'Monitor', 'Modect', Record', 'Mocord', 'Nodect'",
getLogIdentifier(), commandString);
}
handleCommand(channelUID, RefreshType.REFRESH);
break;
// They are all readonly in the channel config.
case ZoneMinderConstants.CHANNEL_MONITOR_EVENT_STATE:
case ZoneMinderConstants.CHANNEL_MONITOR_DETAILED_STATUS:
case ZoneMinderConstants.CHANNEL_MONITOR_RECORD_STATE:
case ZoneMinderConstants.CHANNEL_ONLINE:
case ZoneMinderConstants.CHANNEL_MONITOR_EVENT_CAUSE:
case ZoneMinderConstants.CHANNEL_MONITOR_CAPTURE_DAEMON_STATE:
case ZoneMinderConstants.CHANNEL_MONITOR_ANALYSIS_DAEMON_STATE:
case ZoneMinderConstants.CHANNEL_MONITOR_FRAME_DAEMON_STATE:
// Do nothing, they are all read only
break;
default:
logger.warn("{}: Command received for an unknown channel: {}", getLogIdentifier(),
channelUID.getId());
break;
}
} catch (Exception ex) {
logger.error("{}: handleCommand: Command='{}' failed for channel='{}' Exception='{}'", getLogIdentifier(),
command, channelUID.getId(), ex.getMessage());
}
}
@Override
public void initialize() {
try {
super.initialize();
this.config = getMonitorConfig();
logger.info("{}: ZoneMinder Monitor Handler Initialized", getLogIdentifier());
logger.debug("{}: Monitor Id: {}", getLogIdentifier(), config.getZoneMinderId());
} catch (Exception ex) {
logger.error("{}: Exception occurred when calling 'initialize()'. Exception='{}'", getLogIdentifier(),
ex.getMessage());
}
}
@Override
public void onTrippedForceAlarm(ZoneMinderTriggerEvent event) {
try {
logger.info("{}: Received forceAlarm for monitor {}", getLogIdentifier(), event.getMonitorId());
// Set Current Event to actual event
if (event.getState()) {
startPriorityRefresh();
} else {
curEvent = null;
}
} catch (Exception ex) {
logger.error("{}: Exception occurred inTrippedForceAlarm() Exception='{}'", getLogIdentifier(),
ex.getMessage());
}
}
protected ZoneMinderThingMonitorConfig getMonitorConfig() {
return this.getConfigAs(ZoneMinderThingMonitorConfig.class);
}
@Override
protected String getZoneMinderThingType() {
return ZoneMinderConstants.THING_ZONEMINDER_MONITOR;
}
@Override
public void updateAvaliabilityStatus(IZoneMinderConnectionInfo connection) {
// Assume success
ThingStatus newThingStatus = ThingStatus.ONLINE;
ThingStatusDetail thingStatusDetailed = ThingStatusDetail.NONE;
String thingStatusDescription = "";
ThingStatus curThingStatus = this.getThing().getStatus();
// Is connected to ZoneMinder and thing is ONLINE
if (isConnected() && curThingStatus == ThingStatus.ONLINE) {
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
return;
}
try {
ZoneMinderFactory.validateConnection(connection);
} catch (IllegalArgumentException e) {
logger.error("{}: validateConnection failed with exception='{}'", getLogIdentifier(), e.getMessage());
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.COMMUNICATION_ERROR;
thingStatusDescription = "Could not connect to thing";
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
return;
}
try {
String msg;
final Bridge bridge = getBridge();
// 1. Is there a Bridge assigned?
if (bridge == null) {
msg = String.format("No Bridge assigned to monitor '%s'", thing.getUID());
logger.error("{}: {}", getLogIdentifier(), msg);
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.BRIDGE_OFFLINE;
thingStatusDescription = "No Bridge assigned to monitor";
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
return;
} else {
logger.debug("{}: ThingAvailability: Thing '{}' has Bridge '{}' defined (Check PASSED)",
getLogIdentifier(), thing.getUID(), bridge.getBridgeUID());
}
// 2. Is Bridge Online?
if (bridge.getStatus() != ThingStatus.ONLINE) {
msg = String.format("Bridge '%s' is OFFLINE", bridge.getBridgeUID());
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.BRIDGE_OFFLINE;
thingStatusDescription = msg;
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
logger.error("{}: {}", getLogIdentifier(), msg);
return;
} else {
logger.debug("{}: ThingAvailability: Bridge '{}' is ONLINE (Check PASSED)", getLogIdentifier(),
bridge.getBridgeUID());
}
// 3. Is Configuration OK?
if (getMonitorConfig() == null) {
msg = String.format("No valid configuration found for '%s'", thing.getUID());
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.CONFIGURATION_ERROR;
thingStatusDescription = msg;
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
logger.error("{}: {}", getLogIdentifier(), msg);
return;
} else {
logger.debug("{}: ThingAvailability: Thing '{}' has valid configuration (Check PASSED)",
getLogIdentifier(), thing.getUID());
}
// ZoneMinder Id for Monitor not set, we are pretty much lost then
if (getMonitorConfig().getZoneMinderId().isEmpty()) {
msg = String.format("No Id is specified for monitor '%s'", thing.getUID());
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.CONFIGURATION_ERROR;
thingStatusDescription = msg;
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
logger.error("{}: {}", getLogIdentifier(), msg);
return;
} else {
logger.debug("{}: ThingAvailability: ZoneMinder Id for Thing '{}' defined (Check PASSED)",
getLogIdentifier(), thing.getUID());
}
IZoneMinderMonitor monitorProxy = null;
IZoneMinderDaemonStatus captureDaemon = null;
// TODO:: Also look at Analysis and Frame Daemons (only if they are supposed to be running)
// IZoneMinderSession session = aquireSession();
IZoneMinderSession curSession = null;
try {
curSession = ZoneMinderFactory.CreateSession(connection);
} catch (FailedLoginException | IllegalArgumentException | IOException
| ZoneMinderUrlNotFoundException ex) {
logger.error("{}: Create Session failed with exception {}", getLogIdentifier(), ex.getMessage());
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.COMMUNICATION_ERROR;
thingStatusDescription = "Failed to connect. (Check Log)";
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
return;
}
if (curSession != null) {
monitorProxy = ZoneMinderFactory.getMonitorProxy(curSession, getZoneMinderId());
captureDaemon = monitorProxy.getCaptureDaemonStatus();
}
if (captureDaemon == null) {
msg = String.format("Capture Daemon not accssible");
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.COMMUNICATION_ERROR;
thingStatusDescription = msg;
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
logger.error("{}: {}", getLogIdentifier(), msg);
return;
} else if (!captureDaemon.getStatus()) {
msg = String.format("Capture Daemon is not running");
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.COMMUNICATION_ERROR;
thingStatusDescription = msg;
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
logger.error("{}: {}", getLogIdentifier(), msg);
return;
}
newThingStatus = ThingStatus.ONLINE;
} catch (Exception exception) {
newThingStatus = ThingStatus.OFFLINE;
thingStatusDetailed = ThingStatusDetail.COMMUNICATION_ERROR;
thingStatusDescription = "Error occurred (Check log)";
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
logger.error("{}: 'ThingMonitorHandler.updateAvailabilityStatus()': Exception occurred '{}'",
getLogIdentifier(), exception.getMessage());
return;
}
updateThingStatus(newThingStatus, thingStatusDetailed, thingStatusDescription);
}
/*
* From here we update states in openHAB
*
* @see
* org.openhab.binding.zoneminder.handler.ZoneMinderBaseThingHandler#updateChannel(org.openhab.core.thing.
* ChannelUID)
*/
@Override
public void updateChannel(ChannelUID channel) {
State state = null;
try {
switch (channel.getId()) {
case ZoneMinderConstants.CHANNEL_MONITOR_ENABLED:
state = getChannelBoolAsOnOffState(channelEnabled);
break;
case ZoneMinderConstants.CHANNEL_ONLINE:
// Ask super class to handle, because this channel is shared for all things
super.updateChannel(channel);
break;
case ZoneMinderConstants.CHANNEL_MONITOR_FORCE_ALARM:
state = getChannelBoolAsOnOffState(channelForceAlarm);
break;
case ZoneMinderConstants.CHANNEL_MONITOR_EVENT_STATE:
state = getChannelBoolAsOnOffState(channelAlarmedState);
break;
case ZoneMinderConstants.CHANNEL_MONITOR_RECORD_STATE:
state = getChannelBoolAsOnOffState(channelRecordingState);
break;
case ZoneMinderConstants.CHANNEL_MONITOR_DETAILED_STATUS:
state = getDetailedStatus();
break;
case ZoneMinderConstants.CHANNEL_MONITOR_EVENT_CAUSE:
state = getChannelStringAsStringState(channelEventCause);
break;
case ZoneMinderConstants.CHANNEL_MONITOR_FUNCTION:
state = getChannelStringAsStringState(channelFunction.toString());
break;
case ZoneMinderConstants.CHANNEL_MONITOR_CAPTURE_DAEMON_STATE:
state = getChannelBoolAsOnOffState(channelDaemonCapture);
break;
case ZoneMinderConstants.CHANNEL_MONITOR_ANALYSIS_DAEMON_STATE:
state = getChannelBoolAsOnOffState(channelDaemonAnalysis);
break;
case ZoneMinderConstants.CHANNEL_MONITOR_FRAME_DAEMON_STATE:
state = getChannelBoolAsOnOffState(channelDaemonFrame);
break;
default:
logger.warn("{}: updateChannel(): Monitor '{}': No handler defined for channel='{}'",
getLogIdentifier(), thing.getLabel(), channel.getAsString());
// Ask super class to handle
super.updateChannel(channel);
}
if (state != null) {
logger.debug("{}: Setting channel '{}' to '{}'", getLogIdentifier(), channel.toString(),
state.toString());
updateState(channel.getId(), state);
}
} catch (Exception ex) {
logger.error("{}: Error when 'updateChannel' was called (channelId='{}'state='{}', exception'{}')",
getLogIdentifier(), channel, state, ex.getMessage());
}
}
@Override
public void updateStatus(ThingStatus status) {
super.updateStatus(status);
updateState(ZoneMinderConstants.CHANNEL_ONLINE,
((status == ThingStatus.ONLINE) ? OnOffType.ON : OnOffType.OFF));
}
protected void recalculateChannelStates() {
boolean recordingFunction = false;
boolean recordingDetailedState = false;
boolean alarmedFunction = false;
boolean alarmedDetailedState = false;
// Calculate based on state of Function
switch (channelFunction) {
case NONE:
case MONITOR:
alarmedFunction = false;
recordingFunction = false;
break;
case MODECT:
alarmedFunction = true;
recordingFunction = true;
break;
case RECORD:
alarmedFunction = false;
recordingFunction = true;
break;
case MOCORD:
alarmedFunction = true;
recordingFunction = true;
break;
case NODECT:
alarmedFunction = false;
recordingFunction = true;
break;
default:
recordingFunction = (curEvent != null) ? true : false;
}
logger.debug(
"{}: Recalculate channel states based on Function: Function='{}' -> alarmState='{}', recordingState='{}'",
getLogIdentifier(), channelFunction.name(), alarmedFunction, recordingFunction);
// Calculated based on detailed Monitor Status
switch (channelMonitorStatus) {
case IDLE:
alarmedDetailedState = false;
recordingDetailedState = false;
channelForceAlarm = false;
channelEventCause = "";
break;
case PRE_ALARM:
alarmedDetailedState = true;
recordingDetailedState = true;
channelForceAlarm = false;
break;
case ALARM:
alarmedDetailedState = true;
recordingDetailedState = true;
channelForceAlarm = true;
break;
case ALERT:
alarmedDetailedState = true;
recordingDetailedState = true;
channelForceAlarm = false;
break;
case RECORDING:
alarmedDetailedState = false;
recordingDetailedState = true;
channelForceAlarm = false;
break;
case UNKNOWN:
}
logger.debug(
"{}: Recalculate channel states based on Detailed State: DetailedState='{}' -> alarmState='{}', recordingState='{}'",
getLogIdentifier(), channelMonitorStatus.name(), alarmedDetailedState, recordingDetailedState);
// Check if Force alarm was initialed from openHAB
if (forceAlarmManualState == 0) {
if (channelForceAlarm) {
channelForceAlarm = false;
} else {
forceAlarmManualState = -1;
}
} else if (forceAlarmManualState == 1) {
if (!channelForceAlarm) {
channelForceAlarm = true;
} else {
forceAlarmManualState = -1;
}
}
// Now we can conclude on the Alarmed and Recording channel state
channelRecordingState = (recordingFunction && recordingDetailedState && channelEnabled);
channelAlarmedState = (alarmedFunction && alarmedDetailedState && channelEnabled);
}
@Override
protected void onFetchData() {
IZoneMinderSession session = null;
session = aquireSession();
try {
IZoneMinderMonitor monitorProxy = ZoneMinderFactory.getMonitorProxy(session, getZoneMinderId());
IZoneMinderMonitorData data = null;
IZoneMinderDaemonStatus captureDaemon = null;
IZoneMinderDaemonStatus analysisDaemon = null;
IZoneMinderDaemonStatus frameDaemon = null;
data = monitorProxy.getMonitorData();
logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
monitorProxy.getHttpUrl(), monitorProxy.getHttpResponseCode(),
monitorProxy.getHttpResponseMessage());
captureDaemon = monitorProxy.getCaptureDaemonStatus();
logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
monitorProxy.getHttpUrl(), monitorProxy.getHttpResponseCode(),
monitorProxy.getHttpResponseMessage());
analysisDaemon = monitorProxy.getAnalysisDaemonStatus();
logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
monitorProxy.getHttpUrl(), monitorProxy.getHttpResponseCode(),
monitorProxy.getHttpResponseMessage());
frameDaemon = monitorProxy.getFrameDaemonStatus();
logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
monitorProxy.getHttpUrl(), monitorProxy.getHttpResponseCode(),
monitorProxy.getHttpResponseMessage());
if ((data.getHttpResponseCode() != 200) || (captureDaemon.getHttpResponseCode() != 200)
|| (analysisDaemon.getHttpResponseCode() != 200) || (frameDaemon.getHttpResponseCode() != 200)) {
if (data.getHttpResponseCode() != 200) {
logger.warn("{}: HTTP Response MonitorData: Code='{}', Message'{}'", getLogIdentifier(),
data.getHttpResponseCode(), data.getHttpResponseMessage());
channelMonitorStatus = ZoneMinderMonitorStatusEnum.UNKNOWN;
channelFunction = ZoneMinderMonitorFunctionEnum.NONE;
channelEnabled = false;
channelEventCause = "";
}
if (captureDaemon.getHttpResponseCode() != 200) {
channelDaemonCapture = false;
logger.warn("{}: HTTP Response CaptureDaemon: Code='{}', Message'{}'", getLogIdentifier(),
captureDaemon.getHttpResponseCode(), captureDaemon.getHttpResponseMessage());
}
if (analysisDaemon.getHttpResponseCode() != 200) {
channelDaemonAnalysis = false;
logger.warn("{}: HTTP Response AnalysisDaemon: Code='{}', Message='{}'", getLogIdentifier(),
analysisDaemon.getHttpResponseCode(), analysisDaemon.getHttpResponseMessage());
}
if (frameDaemon.getHttpResponseCode() != 200) {
channelDaemonFrame = false;
logger.warn("{}: HTTP Response MonitorData: Code='{}', Message'{}'", getLogIdentifier(),
frameDaemon.getHttpResponseCode(), frameDaemon.getHttpResponseMessage());
}
} else {
if (isConnected()) {
channelMonitorStatus = monitorProxy.getMonitorDetailedStatus();
logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
monitorProxy.getHttpUrl(), monitorProxy.getHttpResponseCode(),
monitorProxy.getHttpResponseMessage());
channelFunction = data.getFunction();
channelEnabled = data.getEnabled();
IZoneMinderEventData event = monitorProxy.getLastEvent();
if (event != null) {
channelEventCause = event.getCause();
} else {
channelEventCause = "";
}
channelDaemonCapture = captureDaemon.getStatus();
channelDaemonAnalysis = analysisDaemon.getStatus();
channelDaemonFrame = frameDaemon.getStatus();
} else {
channelMonitorStatus = ZoneMinderMonitorStatusEnum.UNKNOWN;
channelFunction = ZoneMinderMonitorFunctionEnum.NONE;
channelEnabled = false;
channelEventCause = "";
channelDaemonCapture = false;
channelDaemonAnalysis = false;
channelDaemonFrame = false;
}
}
} finally {
releaseSession();
}
recalculateChannelStates();
if (!channelForceAlarm && !channelAlarmedState
&& (DataRefreshPriorityEnum.HIGH_PRIORITY == getRefreshPriority())) {
stopPriorityRefresh();
}
}
protected State getDetailedStatus() {
State state = UnDefType.UNDEF;
try {
if (channelMonitorStatus == ZoneMinderMonitorStatusEnum.UNKNOWN) {
state = getChannelStringAsStringState("");
} else {
state = getChannelStringAsStringState(channelMonitorStatus.toString());
}
} catch (Exception ex) {
logger.debug("{}", ex.getMessage());
}
return state;
}
/*
* This is experimental
* Try to add different properties
*/
private void updateMonitorProperties(IZoneMinderSession session) {
logger.debug("{}: Update Monitor Properties", getLogIdentifier());
// Update property information about this device
Map<String, String> properties = editProperties();
IZoneMinderMonitor monitorProxy = ZoneMinderFactory.getMonitorProxy(session, getZoneMinderId());
IZoneMinderMonitorData monitorData = monitorProxy.getMonitorData();
logger.debug("{}: URL='{}' ResponseCode='{}' ResponseMessage='{}'", getLogIdentifier(),
monitorProxy.getHttpUrl(), monitorProxy.getHttpResponseCode(), monitorProxy.getHttpResponseMessage());
properties.put(ZoneMinderProperties.PROPERTY_ID, getLogIdentifier());
properties.put(ZoneMinderProperties.PROPERTY_MONITOR_NAME, monitorData.getName());
properties.put(ZoneMinderProperties.PROPERTY_MONITOR_SOURCETYPE, monitorData.getSourceType().name());
properties.put(ZoneMinderProperties.PROPERTY_MONITOR_ANALYSIS_FPS, monitorData.getAnalysisFPS());
properties.put(ZoneMinderProperties.PROPERTY_MONITOR_MAXIMUM_FPS, monitorData.getMaxFPS());
properties.put(ZoneMinderProperties.PROPERTY_MONITOR_ALARM_MAXIMUM, monitorData.getAlarmMaxFPS());
properties.put(ZoneMinderProperties.PROPERTY_MONITOR_IMAGE_WIDTH, monitorData.getWidth());
properties.put(ZoneMinderProperties.PROPERTY_MONITOR_IMAGE_HEIGHT, monitorData.getHeight());
// Must loop over the new properties since we might have added data
boolean update = false;
Map<String, String> originalProperties = editProperties();
for (String property : properties.keySet()) {
if ((originalProperties.get(property) == null
|| !originalProperties.get(property).equals(properties.get(property)))) {
update = true;
break;
}
}
if (update) {
logger.debug("{}: Properties synchronised", getLogIdentifier());
updateProperties(properties);
}
}
@Override
public String getLogIdentifier() {
String result = "[MONITOR]";
try {
if (config != null) {
result = String.format("[MONITOR-%s]", config.getZoneMinderId().toString());
}
} catch (Exception ex) {
result = "[MONITOR]";
}
return result;
}
}

View File

@@ -1,23 +0,0 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zoneminder.internal.handler;
/**
* Enumerator for each Bridge and Thing
*
* @author Martin S. Eskildsen - Initial contribution
*/
public enum ZoneMinderThingType {
ZoneMinderServerBridge,
ZoneMinderMonitorThing
}

View File

@@ -4,7 +4,7 @@
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>ZoneMinder Binding</name>
<description>This binding interfaces a ZoneMinder Server</description>
<author>Martin S. Eskildsen</author>
<description>Binding for ZoneMinder video surveillance system</description>
<author>Mark Hilbush</author>
</binding:binding>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:zoneminder:server">
<parameter-group name="url-info">
<label>ZoneMinder URL Information</label>
</parameter-group>
<parameter-group name="config-info">
<label>Bridge Configuration</label>
</parameter-group>
<parameter-group name="auth-info">
<label>Authentication Information</label>
</parameter-group>
<parameter name="refreshInterval" type="integer" min="2" unit="s" required="true" groupName="config-info">
<label>Refresh Interval</label>
<description>Interval in seconds at which monitor status is updated</description>
<default>5</default>
</parameter>
<parameter name="discoveryEnabled" type="boolean" required="true" groupName="config-info">
<label>Discovery Enabled</label>
<description>Enable/disable automatic discovery</description>
<default>true</default>
</parameter>
<parameter name="discoveryInterval" type="integer" min="60" unit="s" required="true" groupName="config-info">
<label>Monitor Discovery Interval</label>
<description>Specifies time in seconds in which the binding will attempt to discover monitors</description>
<default>300</default>
</parameter>
<parameter name="defaultAlarmDuration" type="integer" unit="s" required="false" groupName="config-info">
<label>Default Alarm Duration</label>
<description>Duration in seconds after which the alarm will be turned off</description>
<default>60</default>
</parameter>
<parameter name="defaultImageRefreshInterval" type="integer" unit="s" required="false"
groupName="config-info">
<label>Default Image Refresh Interval</label>
<description>Interval in seconds at which monitor image snapshot will be updated</description>
</parameter>
<parameter name="host" type="text" required="true" groupName="url-info">
<label>Server</label>
<description>ZoneMinder server name or IP address</description>
<context>network-address</context>
</parameter>
<parameter name="useSSL" type="boolean" required="true" groupName="url-info">
<label>Use https</label>
<description>Enables use of https for connection to ZoneMinder</description>
<default>false</default>
</parameter>
<parameter name="portNumber" type="integer" min="1" max="65535" required="false" groupName="url-info">
<label>Port Number</label>
<description>Port Number (leave blank if Zoneminder installed on default port)</description>
</parameter>
<parameter name="urlPath" type="text" required="true" groupName="url-info">
<label>URL Path</label>
<description>URL path (Default is /zm. Use / if Zoneminder installed under the root directory)</description>
<default>/zm</default>
</parameter>
<parameter name="user" type="text" required="false" groupName="auth-info">
<label>User Name</label>
<description>User name (if authentication enabled in ZoneMinder)</description>
</parameter>
<parameter name="pass" type="text" required="false" groupName="auth-info">
<label>Password</label>
<description>Password (if authentication enabled in ZoneMinder)</description>
<context>password</context>
</parameter>
</config-description>
<config-description uri="thing-type:zoneminder:monitor">
<parameter name="monitorId" type="text" required="true">
<label>Monitor Id</label>
<description>Id of the monitor</description>
</parameter>
<parameter name="imageRefreshInterval" type="integer" unit="s" required="false" min="1">
<label>Image Refresh Interval</label>
<description>Interval in seconds with which monitor image is refreshed</description>
</parameter>
<parameter name="alarmDuration" type="integer" unit="s" required="false">
<label>Alarm Duration</label>
<description>Duration in seconds after which the alarm will be turned off</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:monitor-channels:config">
<parameter name="monitorId" type="integer" required="true">
<label>Monitor ID</label>
<description>The ID of the monitor in ZoneMinder</description>
</parameter>
<parameter name="monitorTriggerTimeout" type="integer" required="false" min="0" max="65535">
<label>ForceAlarm Timeout</label>
<description>Timeout in seconds when activating alarm. Default is 60 seconds</description>
<default>60</default>
</parameter>
<parameter name="monitorEventText" type="text" required="false">
<label>Event Text</label>
<description>Event text in ZoneMinder</description>
<default>Triggered from openHAB</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -1,90 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:zoneminderserver:config">
<parameter-group name="basic">
<context>basic</context>
<label>Basic</label>
</parameter-group>
<parameter-group name="credentials">
<context>credentials</context>
<label>Credentials</label>
</parameter-group>
<parameter-group name="network">
<context>network</context>
<label>Port Configuration</label>
</parameter-group>
<parameter-group name="refreshConfig">
<context>refreshConfig</context>
<label>Refresh Settings</label>
</parameter-group>
<parameter-group name="advancedSettings">
<context>advancedSettings</context>
<label>Advanced</label>
</parameter-group>
<parameter name="hostname" type="text" required="true" groupName="basic">
<context>network-address</context>
<label>Host</label>
<description>The IP address or hostname of the ZoneMinder Server</description>
</parameter>
<parameter name="protocol" type="text" required="false" groupName="basic">
<label>Protocol</label>
<description>Protocol to connect to the ZoneMinder Server API (http or https)</description>
<default>http</default>
<options>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</options>
</parameter>
<parameter name="urlpath" type="text" required="false" groupName="basic">
<label>Additional Path On ZoneMinder Server to Access API</label>
<description>Additional path on ZoneMinder Server to access API. In a standard installation this is' /zm'</description>
<default>/zm</default>
</parameter>
<parameter name="user" type="text" required="false" groupName="credentials">
<label>Username</label>
<description>User to access the ZoneMinder Server API</description>
</parameter>
<parameter name="password" type="text" required="false" groupName="credentials">
<context>password</context>
<label>Password</label>
<description>Password to access the ZoneMinder Server API</description>
</parameter>
<parameter name="http_port" type="integer" required="false" min="0" max="65535" groupName="network">
<label>Port</label>
<description>Port of the ZoneMinder Server API. If '0', then the port will be determined from the protocol</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
<parameter name="telnet_port" type="integer" required="false" min="1" max="65535" groupName="network">
<label>Telnet Port</label>
<description>Port to listen for events in (Telnet)</description>
<default>6802</default>
<advanced>true</advanced>
</parameter>
<parameter name="refresh_interval" type="integer" required="false" min="1" max="65535"
groupName="refreshConfig">
<label>API Polling Interval</label>
<description>Seconds between each call to ZoneMinder Server API to refresh values in openHAB</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="refresh_interval_disk_usage" type="integer" required="false" min="0" max="65335"
groupName="refreshConfig">
<label>Refresh Interval for Disk Usage</label>
<description>Minutes between each call to ZoneMinder Server to refresh Server DiskUsage in ZoneMinder. Default value
is '0' (Disabled)</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
<parameter name="autodiscover_things" type="boolean" required="false" groupName="advanced">
<label>Background Discovery</label>
<description>If enabled new monitors on the ZoneMinder Server will automatically be added to the Inbox in openHAB</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -1,140 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="zoneminder"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Supported ZoneMinder devices and features -->
<thing-type id="monitor">
<supported-bridge-type-refs>
<bridge-type-ref id="server"/>
</supported-bridge-type-refs>
<label>ZoneMinder Monitor</label>
<description>Camera in ZoneMinder</description>
<channels>
<channel id="online" typeId="monitor_online"/>
<channel id="enabled" typeId="monitor_enabled"/>
<channel id="force-alarm" typeId="monitor_force_alarm"/>
<channel id="alarm" typeId="monitor_alarm"/>
<channel id="recording" typeId="monitor_recording"/>
<channel id="detailed-status" typeId="monitor_detailed_status"/>
<channel id="function" typeId="monitor_function"/>
<channel id="event-cause" typeId="monitor_event_cause"/>
<channel id="capture-daemon" typeId="monitor_zmc_daemon"/>
<channel id="analysis-daemon" typeId="monitor_zma_daemon"/>
<channel id="frame-daemon" typeId="monitor_zmf_daemon"/>
</channels>
<config-description-ref uri="thing-type:monitor-channels:config"/>
</thing-type>
<!-- Channel definitions of ZoneMinder Server -->
<channel-type id="monitor_online">
<item-type>Switch</item-type>
<label>Online</label>
<description>Switch telling if the monitor is online</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="monitor_enabled">
<item-type>Switch</item-type>
<label>Enabled</label>
<description>Showing the value of the checkbox 'enabled' in ZoneMinder for the monitor</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="monitor_force_alarm">
<item-type>Switch</item-type>
<label>Force Alarm</label>
<description>Will force an alarm from openHAB in ZoneMinder</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="monitor_alarm">
<item-type>Switch</item-type>
<label>Alarm Status</label>
<description>set to 'ON' when one of the following is true: Motion detected, Signal lost, Force Alarm pressed,
External Alarm. Else set to 'OFF'</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="monitor_recording">
<item-type>Switch</item-type>
<label>Recording Status</label>
<description>set to 'ON' when either channel monitor-alarm set to 'ON', or montior function is 'Mocord' or 'Record'.
Else set to 'OFF'</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="monitor_detailed_status" advanced="true">
<item-type>String</item-type>
<label>Detailed Status</label>
<description>Current Monitor Status: 0=Idle, 1=Pre-alarm, 2=Alarm, 3=Alert, 4=Recording</description>
<state pattern="%s" readOnly="true">
<options>
<option value="Idle">Idle</option>
<option value="Pre-alarm">Pre-alarm</option>
<option value="Alarm">Alarm</option>
<option value="Alert">Alert</option>
<option value="Recording">Recording</option>
</options>
</state>
</channel-type>
<channel-type id="monitor_function" advanced="true">
<item-type>String</item-type>
<label>Operating Mode</label>
<description>Current Monitor Function: None, Monitor, Modect, Record, Mocord, Nodect</description>
<state pattern="%s" readOnly="false">
<options>
<option value="None">None</option>
<option value="Monitor">Monitor</option>
<option value="Modect">Modect</option>
<option value="Record">Record</option>
<option value="Mocord">Mocord</option>
<option value="Nodect">Nodect</option>
</options>
</state>
</channel-type>
<channel-type id="monitor_event_cause" advanced="true">
<item-type>String</item-type>
<label>Event Cause</label>
<description>Cause of event: None, Signal, Motion, Forced Web, openHAB, Other</description>
<state pattern="%s" readOnly="true">
<options>
<option value="none">None</option>
<option value="signal">Signal</option>
<option value="motion">Motion</option>
<option value="forced_web">Forced Web</option>
<option value="openhab">openHAB</option>
<option value="other">Other</option>
</options>
</state>
</channel-type>
<channel-type id="monitor_zmc_daemon" advanced="true">
<item-type>Switch</item-type>
<label>Capture Daemon Status</label>
<description>State of ZoneMinder Capture daemon for this monitor</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="monitor_zma_daemon" advanced="true">
<item-type>Switch</item-type>
<label>Analysis Daemon Status</label>
<description>State of ZoneMinder Analysis daemon for this monitor</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="monitor_zmf_daemon" advanced="true">
<item-type>Switch</item-type>
<label>Frame Daemon Status</label>
<description>State of ZoneMinder Frame daemon for this monitor</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="zoneminder"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="server">
<label>ZoneMinder Server</label>
<description>ZoneMinder Server</description>
<channels>
<channel id="online" typeId="server_online"/>
<channel id="cpu-load" typeId="server_cpu_load"/>
<channel id="disk-usage" typeId="server_disk_usage"/>
</channels>
<config-description-ref uri="thing-type:zoneminderserver:config"/>
</bridge-type>
<channel-type id="server_online">
<item-type>Switch</item-type>
<label>Online</label>
<description>ZoneMinder Server Online Status</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="server_cpu_load">
<item-type>Number</item-type>
<label>CPU Load</label>
<description>ZoneMinder Server CPU Load</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="server_disk_usage">
<item-type>Number</item-type>
<label>Diskusage</label>
<description>ZoneMinder Server Disk Usage</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,217 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="zoneminder"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="server">
<label>ZoneMinder Server</label>
<description>Represents a ZoneMinder server</description>
<channels>
<channel id="imageMonitorId" typeId="id">
<label>Image Monitor Id</label>
<description>Monitor ID for Image URL Channel</description>
</channel>
<channel id="imageUrl" typeId="url">
<label>Image URL</label>
</channel>
<channel id="videoMonitorId" typeId="id">
<label>Video Monitor Id</label>
<description>Monitor ID for Video URL Channel</description>
</channel>
<channel id="videoUrl" typeId="url">
<label>Video URL</label>
</channel>
</channels>
<config-description-ref uri="thing-type:zoneminder:server"/>
</bridge-type>
<thing-type id="monitor">
<supported-bridge-type-refs>
<bridge-type-ref id="server"/>
</supported-bridge-type-refs>
<label>ZoneMinder Monitor</label>
<description>Represents a ZoneMinder monitor</description>
<channels>
<channel id="id" typeId="id"/>
<channel id="name" typeId="name"/>
<channel id="image" typeId="image"/>
<channel id="enable" typeId="enable"/>
<channel id="function" typeId="function"/>
<channel id="alarm" typeId="alarm"/>
<channel id="state" typeId="state"/>
<channel id="triggerAlarm" typeId="triggerAlarm"/>
<channel id="hourEvents" typeId="events">
<label>Hour Events</label>
</channel>
<channel id="dayEvents" typeId="events">
<label>Day Events</label>
</channel>
<channel id="weekEvents" typeId="events">
<label>Week Events</label>
</channel>
<channel id="monthEvents" typeId="events">
<label>Month Events</label>
</channel>
<channel id="totalEvents" typeId="events">
<label>Total Events</label>
</channel>
<channel id="imageUrl" typeId="url">
<label>Image URL</label>
</channel>
<channel id="videoUrl" typeId="url">
<label>Video URL</label>
</channel>
<channel id="eventId" typeId="eventId"/>
<channel id="eventName" typeId="eventName"/>
<channel id="eventCause" typeId="eventCause"/>
<channel id="eventNotes" typeId="eventNotes"/>
<channel id="eventStart" typeId="eventStart"/>
<channel id="eventEnd" typeId="eventEnd"/>
<channel id="eventFrames" typeId="eventFrames"/>
<channel id="eventAlarmFrames" typeId="eventAlarmFrames"/>
<channel id="eventLength" typeId="eventLength"/>
</channels>
<config-description-ref uri="thing-type:zoneminder:monitor"/>
</thing-type>
<channel-type id="id">
<item-type>String</item-type>
<label>ID</label>
<description>Monitor ID</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="name">
<item-type>String</item-type>
<label>Name</label>
<description>Monitor name</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="image">
<item-type>Image</item-type>
<label>Image</label>
<description>A single snapshot image</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="enable">
<item-type>Switch</item-type>
<label>Enabled</label>
<description>Enable or disable monitor</description>
<state pattern="%s">
<options>
<option value="ON">Enable</option>
<option value="OFF">Disable</option>
</options>
</state>
</channel-type>
<channel-type id="function">
<item-type>String</item-type>
<label>Function</label>
<description>State of the monitor (e.g. Nodect, Record)</description>
<state pattern="%s">
<options>
<option value="None">None</option>
<option value="Monitor">Monitor</option>
<option value="Modect">Modect</option>
<option value="Record">Record</option>
<option value="Mocord">Mocord</option>
<option value="Nodect">Nodect</option>
</options>
</state>
</channel-type>
<channel-type id="alarm">
<item-type>Switch</item-type>
<label>Alarm</label>
<description>Monitor alarm status</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="state">
<item-type>String</item-type>
<label>State</label>
<description>Current monitor state</description>
<state readOnly="true" pattern="%s">
<options>
<option value="UNKNOWN">UNKNOWN</option>
<option value="IDLE">IDLE</option>
<option value="PREALARM">PREALARM</option>
<option value="ALARM">ALARM</option>
<option value="ALERT">ALERT</option>
<option value="TAPE">TAPE</option>
</options>
</state>
</channel-type>
<channel-type id="triggerAlarm">
<item-type>Switch</item-type>
<label>Trigger Alarm</label>
<description>Triggers an alarm</description>
<state pattern="%s"></state>
</channel-type>
<channel-type id="events">
<item-type>Number</item-type>
<label>Number of Events</label>
<description>Number of events in time period</description>
<state readOnly="true" pattern="%.0f"></state>
</channel-type>
<channel-type id="url">
<item-type>String</item-type>
<label>URL</label>
<description>URL of image or stream</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="eventId">
<item-type>String</item-type>
<label>Event Id</label>
<description>Id of the event</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="eventName">
<item-type>String</item-type>
<label>Event Name</label>
<description>Name of the event</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="eventCause">
<item-type>String</item-type>
<label>Event Cause</label>
<description>Cause of the event</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="eventNotes">
<item-type>String</item-type>
<label>Event Notes</label>
<description>Notes for the event</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="eventStart">
<item-type>DateTime</item-type>
<label>Event Start</label>
<description>Start date/time of the event</description>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"></state>
</channel-type>
<channel-type id="eventEnd">
<item-type>DateTime</item-type>
<label>Event End</label>
<description>End date/time of the event</description>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"></state>
</channel-type>
<channel-type id="eventFrames">
<item-type>Number</item-type>
<label>Event Frames</label>
<description>Number of frames in the event</description>
<state readOnly="true" pattern="%d"></state>
</channel-type>
<channel-type id="eventAlarmFrames">
<item-type>Number</item-type>
<label>Event Alarm Frames</label>
<description>Number of alarm frames in the event</description>
<state readOnly="true" pattern="%d"></state>
</channel-type>
<channel-type id="eventLength">
<item-type>Number:Time</item-type>
<label>Event Length</label>
<description>Length of the event in seconds</description>
<state readOnly="true" pattern="%.2f %unit%"></state>
</channel-type>
</thing:thing-descriptions>