[zoneminder] Replacement for ZoneMinder binding (#8530)
Signed-off-by: Mark Hilbush <mark@hilbush.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user