diff --git a/CODEOWNERS b/CODEOWNERS index eb7c0136140..5ef155aa77d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -267,7 +267,7 @@ /bundles/org.openhab.binding.yamahareceiver/ @davidgraeff @zarusz /bundles/org.openhab.binding.yeelight/ @claell /bundles/org.openhab.binding.yioremote/ @miloit -/bundles/org.openhab.binding.zoneminder/ @Mr-Eskildsen +/bundles/org.openhab.binding.zoneminder/ @mhilbush /bundles/org.openhab.binding.zway/ @pathec /bundles/org.openhab.io.homekit/ @beowulfe @yfre /bundles/org.openhab.io.hueemulation/ @davidgraeff @digitaldan diff --git a/bundles/org.openhab.binding.zoneminder/NOTICE b/bundles/org.openhab.binding.zoneminder/NOTICE index e28de81b808..38d625e3492 100644 --- a/bundles/org.openhab.binding.zoneminder/NOTICE +++ b/bundles/org.openhab.binding.zoneminder/NOTICE @@ -11,15 +11,3 @@ https://www.eclipse.org/legal/epl-2.0/. == Source Code https://github.com/openhab/openhab-addons - -== Third-party Content - -jsoup -* License: MIT License -* Project: https://jsoup.org/ -* Source: https://github.com/jhy/jsoup - -zoneminder4j -* License: MIT License -* Project: https://github.com/Mr-Eskildsen/zoneminder4j -* Source: https://github.com/Mr-Eskildsen/zoneminder4j diff --git a/bundles/org.openhab.binding.zoneminder/README.md b/bundles/org.openhab.binding.zoneminder/README.md index 8fc79231c01..3d1ddc19263 100644 --- a/bundles/org.openhab.binding.zoneminder/README.md +++ b/bundles/org.openhab.binding.zoneminder/README.md @@ -1,184 +1,273 @@ -# Zoneminder Binding +# ZoneMinder Binding -This binding offers integration to a ZoneMinder Server. It currently only offers to integrate to monitors (eg. cameras in ZoneMinder). -It also only offers access to a limited set of values, as well as an even more limited option to update values in ZoneMinder. -It requires at least ZoneMinder 1.29 with API enabled (option 'OPT_USE_API' in ZoneMinder must be enabled). -The option 'OPT_TRIGGERS' must be anabled to allow openHAB to trip the ForceAlarm in ZoneMinder. +Supports the ZoneMinder video surveillance system. ## Supported Things -This binding supports the following thing types +The following thing types are supported: - - - - -
ThingThing TypeDiscoveryDescription
ZoneMinder ServerBridgeManualA ZoneMinder Server. Required version is minimum 1.29
ZoneMinder MonitorThingAutomaticMonitor as defined in ZoneMinder Server
+| Thing | ID | Discovery | Description | +|----------|----------|-----------|-------------| +| Server | server | Manual | Server bridge manages all communication with ZoneMinder server | +| Monitor | monitor | Automatic | Monitor represents a ZoneMinder camera monitor | -## Getting started / Discovery +## Installation -The binding consists of a Bridge (the ZoneMinder Server it self), and a number of Things, which relates to the induvidual monitors in ZoneMinder. -ZoneMinder things can be configured either through the online configuration utility via discovery, or manually through the 'zoneminder.things' configuration file. -The Bridge will not be autodiscovered, this behavior is by design. -That is because the ZoneMinder API can be configured to communicate on custom ports, you can even change the url from the default /zm/ to something userdefined. -That makes it meaningless to scan for a ZoneMinder Server. -The Bridge must therefore be added manually, this can be done from Paper UI. -After adding the Bridge it will go ONLINE, and after a short while and the discovery process for monitors will start. -When a new monitor is discovered it will appear in the Inbox. +The binding requires ZoneMinder version 1.34.0 or greater and API version 2.0 or greater. +It also requires that you enable the **OPT_USE_API** parameter in the ZoneMinder configuration. -### Bridge +If your ZoneMinder is installed using a non-standard URL path or port number, that must be specified when you add the ZoneMinder server thing. -| Channel | Type | Description | -|------------|--------|----------------------------------------------| -| online | Switch | Parameter indicating if the server is online | -| CPU load | Text | Current CPU Load of server | -| Disk Usage | text | Current Disk Usage on server | +There are two different styles of operation, depending on whether or not you have ZoneMinder configured to use authentication. -### Thing +### Non-Authenticated -| Channel | Type | Description | -|-----------------|--------|--------------------------------------------------------------------------------------------| -| online | Switch | Parameter indicating if the monitor is online | -| enabled | Switch | Parameter indicating if the monitor is enabled | -| force-alarm | Switch | Parameter indicating if Force Alarm for the the monitor is active | -| alarm | Switch | true if monitor has an active alarm | -| recording | Text | true if monitor is recording | -| detailed-status | Text | Detailed status of monitor (Idle, Pre-alarm, Alarm, Alert, Recording) | -| event-cause | Text | Empty when there is no active event, else it contains the text with the cause of the event | -| function | Text | Text corresponding the value in ZoneMinder: None, Monitor, Modect, Record, Mocord, Nodect | -| capture-daemon | Switch | Run state of ZMC Daemon | -| analysis-daemon | Switch | Run state of ZMA Daemon | -| frame-daemon | Switch | Run state of ZMF Daemon | +If ZoneMinder authentication is not used, the User and Password parameters should be empty in the *ZoneMinder Server* thing configuration. +No other configuration is required. -## Manual configuration +### Authenticated -### Things configuration +The binding can access ZoneMinder with or without authentication. +If ZoneMinder authentication is used, first make sure the ZoneMinder user has the **API Enabled** permission set in the ZoneMinder Users configuration. +Then, enter the user name and password into the ZoneMinder Server thing configuration. -``` -Bridge zoneminder:server:ZoneMinderSample [ hostname="192.168.1.55", user="", password="", telnet_port=6802, refresh_interval_disk_usage=1 ] -{ - Thing monitor monitor_1 [ monitorId=1, monitorTriggerTimeout=120, monitorEventText="Trigger activated from openHAB" ] -} +## Discovery +The server bridge must be added manually. +Once the server bridge is configured with a valid ZoneMinder host name or IP address, +all monitors associated with the ZoneMinder server will be discovered. + +## Thing Configuration + +### Server Thing + +The following configuration parameters are available on the Server thing: + +| Parameter | Parameter ID | Required/Optional | Description | +|-----------|--------------|-------------------|-------------| +| Host | host | Required | Host name or IP address of the ZoneMinder server. | +| Use secure connection | useSSL | Required | Use http or https for connection to ZoneMinder. Default is http. | +| Port Number | portNumber | Optional | Port number if not on ZoneMinder default port 80. | +| Url Path | urlPath | Required | Path where Zoneminder is installed. Default is /zm. | +| Refresh Interval | refreshInterval | Required | Frequency in seconds at which monitor status will be updated. | +| Default Alarm Duration | defaultAlarmDuration | Required | Can be used to set the default alarm duration on discovered monitors. | +| Default Image Refresh Interval | defaultImageRefreshInterval | Optional | Can be used to set the image refresh interval in seconds on discovered monitors. Leave empty to not set an image refresh interval. | +| Monitor Discovery Enabled | discoveryEnabled | Required | Enable/disable the automatic discovery of monitors. Default is enabled. | +| Monitor Discovery Interval | discoveryInterval | Required | Frequency in seconds at which the binding will try to discover monitors. Default is 300 seconds. | +| User ID | user | Optional | User ID of ZoneMinder user when using authentication. | +| Password | pass | Optional | Password of ZoneMinder user when using authentication. | + +### Monitor Thing + +The following configuration parameters are available on the Monitor thing: + +| Parameter | Parameter ID | Required/Optional | Description | +|-----------|--------------|-------------------|-------------| +| Monitor ID | monitorId | Required | Id of monitor defined in ZoneMinder. | +| Image Refresh Interval | imageRefreshInterval | Optional | Interval in seconds in which snapshot image channel will be updated. | +| Alarm Duration | alarmDuration | Required | How long the alarm will run once triggered by the triggerAlarm channel. | + +## Channels + +### Server Thing + +| Channel | Type | Description | +|----------|--------|--------------| +| imageMonitorId | String | Monitor ID to use for selecting an image URL. Also, sending an OFF command to this channel will reset the monitor id and url to UNDEF. | +| imageUrl | String | Image URL for monitor id specified by imageMonitorId. Channel is UNDEF if the monitor id is not set, or if an OFF command is sent to the imageMonitorId channel. | +| videoMonitorId | String | Monitor ID to use for selecting a video URL. Also, sending an OFF command to this channel will reset the monitor id and url to UNDEF. | +| videoUrl | String | Video URL for monitor id specified by videoMonitorId. Channel is UNDEF if the monitor id is not set, or if an OFF command is sent to the videoMonitorId channel. | + +### Monitor Thing + +| Channel | Type | Description | +|----------|--------|--------------| +| id | String | Monitor ID | +| name | String | Monitor name | +| image | Image | Snapshot image | +| enable | Switch | Enable/disable monitor | +| function | String | Monitor function (e.g. Nodect, Mocord) | +| alarm | Switch | Monitor is alarming | +| state | String | Monitor state (e.g. IDLE, ALARM, TAPE) | +| triggerAlarm | Switch | Turn alarm on/off | +| hourEvents | Number | Number of events in last hour | +| dayEvents | Number | Number of events in last day | +| weekEvents | Number | Number of events in last week | +| monthEvents | Number | Number of events in last month | +| yearEvents | Number | Number of events in last year | +| totalEvents | Number | Total number of events | +| imageUrl | String | URL for image snapshot | +| videoUrl | String | URL for JPEG video stream | +| eventId | String | Event ID | +| eventName | String | Event name | +| eventCause | String | Event cause | +| eventNotes | String | Event notes | +| eventStart | DateTime | Event start date/time | +| eventEnd | DateTime | Event end date/time | +| eventFrames | Number | Event frames | +| eventAlarmFrames | Number | Event alarm frames | +| eventLength | Number:Time | Event length in seconds | + +## Thing Actions + +### triggerAlarm + +The `triggerAlarm` action triggers an alarm that runs for the number of seconds specified by the parameter `duration`. + +##### triggerAlarm - trigger an alarm + +```java +void triggerAlarm(Number duration) ``` -### Items configuration - ``` -/* ***************************************** - * SERVER - * *****************************************/ -Switch zmServer_Online "Zoneminder Online [%s]" {channel="zoneminder:server:ZoneMinderSample:online"} -Number zmServer_CpuLoad "ZoneMinder Server Load [%s]" {channel="zoneminder:server:ZoneMinderSample:cpu-load"} - -Number zmServer_DiskUsage "ZoneMinder Disk Usage [%s]" {channel="zoneminder:server:ZoneMinderSample:disk-usage"} - -/* ***************************************** - * MONITOR 1 - * *****************************************/ -Switch zmMonitor1_Online "Online [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:online"} -Switch zmMonitor1_Enabled "Enabled [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:enabled"} -Switch zmMonitor1_ForceAlarm "Force Alarm [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:force-alarm"} -Switch zmMonitor1_EventState "Alarm [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:alarm"} -Switch zmMonitor1_Recording "Recording [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:recording"} -String zmMonitor1_DetailedStatus "Detailed Status [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:detailed-status"} -String zmMonitor1_EventCause "Event Cause [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:event-cause"} -String zmMonitor1_Function "Function [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:function"} -Switch zmMonitor1_CaptureState "Capture Daemon [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:capture-daemon"} -Switch zmMonitor1_AnalysisState "Analysis Daemon [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:analysis-daemon"} -Switch zmMonitor1_FrameState "Frame Daemon [%s]" {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:frame-daemon"} - - -// Helpers -Switch zmMonitor1_Mode "Monitor active [%s]" +Parameters: +duration - The number of seconds for which the alarm should run. ``` -### Sample Rule +### triggerAlarm + +The `triggerAlarm` action triggers an alarm that runs for the number of seconds specified +in the Monitor thing configuration. + +##### triggerAlarm - trigger an alarm + +```java +void triggerAlarm() +``` + +### triggerAlarmOff + +The `triggerAlarmOff` action cancels a running alarm. + +##### triggerAlarmOff - cancel an alarm + +```java +void triggerAlarmOff() +``` + +### Requirements + +The binding requires ZoneMinder version 1.34.0 or greater, and API version 2.0 or greater. +The API must be enabled in the ZoneMinder configuration using the **OPT_USE_API** parameter. + +## Full Example + +### Things ``` -rule "Monitor1 Alarm State" +Bridge zoneminder:server:server [ host="192.168.1.100", refreshInterval=5, defaultAlarmDuration=120, discoveryEnabled=true, useDefaultUrlPath=true ] + +Thing zoneminder:monitor:1 "Monitor 1" (zm:server:server) [ monitorId="1", imageRefreshInterval=10, alarmDuration=180 ] + +Thing zoneminder:monitor:2 "Monitor 2" (zm:server:server) [ monitorId="2", imageRefreshInterval=10, alarmDuration=180 ] +``` + +### Items + +``` +// Server +String ZmServer_ImageMonitorId "Image Monitor Id [%s]" { channel="zoneminder:server:server:imageMonitorId" } +String ZmServer_ImageUrl "Image Url [%s]" { channel="zoneminder:server:server:imageUrl" } +String ZmServer_VideoMonitorId "Video Monitor Id [%s]" { channel="zm:server:server:videoMonitorId" } +String ZmServer_VideoUrl "Video Url [%s]" { channel="zoneminder:server:server:videoUrl" } + +// Monitor +String ZM_Monitor1_Id "Monitor Id [%s]" { channel="zoneminder:monitor:1:id" } +String ZM_Monitor1_Name "Monitor Name [%s]" { channel="zoneminder:monitor:1:name" } +Image ZM_Monitor1_Image "Image [%s]" { channel="zoneminder:monitor:1:image" } +Switch ZM_Monitor1_Enable "Enable [%s]" { channel="zoneminder:monitor:1:enable" } +String ZM_Monitor1_Function "Function [%s]" { channel="zoneminder:monitor:1:function" } +Switch ZM_Monitor1_Alarm "Alarm Status [%s]" { channel="zoneminder:monitor:1:alarm" } +String ZM_Monitor1_State "Alarm State [%s]" { channel="zoneminder:monitor:1:state" } +Switch ZM_Monitor1_TriggerAlarm "Trigger Alarm [%s]" { channel="zoneminder:monitor:1:triggerAlarm" } +Number ZM_Monitor1_HourEvents "Hour Events [%.0f]" { channel="zoneminder:monitor:1:hourEvents" } +Number ZM_Monitor1_DayEvents "Day Events [%.0f]" { channel="zoneminder:monitor:1:dayEvents" } +Number ZM_Monitor1_WeekEvents "Week Events [%.0f]" { channel="zoneminder:monitor:1:weekEvents" } +Number ZM_Monitor1_MonthEvents "Month Events [%.0f]" { channel="zoneminder:monitor:1:monthEvents" } +Number ZM_Monitor1_TotalEvents "Total Events [%.0f]" { channel="zoneminder:monitor:1:totalEvents" } +String ZM_Monitor1_ImageUrl "Image URL [%s]" { channel="zoneminder:monitor:1:imageUrl" } +String ZM_Monitor1_VideoUrl "Video URL [%s]" { channel="zoneminder:monitor:1:videoUrl" } +String ZM_Monitor1_EventId "Event Id [%s]" { channel="zoneminder:monitor:1:eventId" } +String ZM_Monitor1_Event_Name "Event Name [%s]" { channel="zoneminder:monitor:1:eventName" } +String ZM_Monitor1_EventCause "Event Cause [%s]" { channel="zoneminder:monitor:1:eventCause" } +DateTime ZM_Monitor1_EventStart "Event Start [%s]" { channel="zoneminder:monitor:1:eventStart" } +DateTime ZM_Monitor1_EventEnd "Event End [%s]" { channel="zoneminder:monitor:1:eventEnd" } +Number ZM_Monitor1_Frames "Event Frames [%.0f]" { channel="zoneminder:monitor:1:eventFrames" } +Number ZM_Monitor1_AlarmFrames "Event Alarm Frames [%.0f]" { channel="zoneminder:monitor:1:eventAlarmFrames" } +Number:Time ZM_Monitor1_Length "Event Length [%.2f]" { channel="zoneminder:monitor:1:eventLength" } +``` + +### Sitemap + + +``` +Selection item=ZmServer_ImageMonitorId +Image item=ZmServer_ImageUrl +Selection item=ZmServer_VideoMonitorId +Video item=ZmServer_VideoUrl url="" encoding="mjpeg" + +Selection item=ZM_Monitor1_Function +Selection item=ZM_Monitor1_Enable +Image item=ZM_Monitor1_Image +``` + +### Rules + +The following examples assume you have a motion sensor that is linked to an item called *MotionSensorAlarm*. + +``` +rule "Record When Motion Detected Using Channel" when - Item zmMonitor1_EventState changed + Item MotionSensorAlarm changed to ON then - if (zmMonitor1_EventState.state == ON) { - logInfo("zoneminder.rules", "ZoneMinder Alarm started") - } - else if (zmMonitor1_EventState.state == OFF) { - logInfo("zoneminder.rules", "ZoneMinder Alarm stopped") - } -end - -rule "Monitor1 Recording State" -when - Item zmMonitor1_Recording changed -then - if (zmMonitor1_Recording.state == ON) { - logInfo("zoneminder.rules", "ZoneMinder recording started") - } - else if (zmMonitor1_Recording.state == OFF) { - logInfo("zoneminder.rules", "ZoneMinder recording stopped") - } -end - - -rule "Change Monitor1 Mode" -when - Item zmMonitor1_Mode changed -then - if (zmMonitor1_Mode.state==ON) { - zmMonitor1_Function.sendCommand("Modect") - zmMonitor1_Enabled.sendCommand(ON) - } - else { - zmMonitor1_Function.sendCommand("Monitor") - zmMonitor1_Enabled.sendCommand(OFF) - } + ZM_TriggerAlarm.sendComand(ON) end ``` -### Sitemap configuration - ``` -sitemap zoneminder label="Zoneminder" -{ - Frame { - Text item=zmServer_Online label="ZoneMinder Server [%s]" { - Frame { - Text item=zmServer_Online - Text item=zmServer_CpuLoad - Text item=zmServer_DiskUsage - } - } - - Text item=zmMonitor1_Function label="(Monitor-1) [%s]" { - Frame { - Switch item=zmMonitor1_Enabled - Switch item=zmMonitor1_ForceAlarm - Text item=zmMonitor1_Online - Selection item=zmMonitor1_Function mappings=["None"=None, "Modect"=Modect, "Monitor"=Monitor, "Record"=Record, "Mocord"=Mocord, "Nodect"=Nodect] - Text item=zmMonitor1_EventState - Text item=zmMonitor1_Recording - Text item=zmMonitor1_DetailedStatus - Text item=zmMonitor1_EventCause - Text item=zmMonitor1_CaptureState - Text item=zmMonitor1_AnalysisState - Text item=zmMonitor1_FrameState - } - } - Frame label="Monitor Helpers" { - Switch item=zmMonitor1_Mode - } - } -} +rule "Record for 120 Seconds When Motion Detected" +when + Item MotionSensorAlarm changed to ON +then + val zmActions = getActions("zoneminder", "zoneminder:monitor:1") + zmActions.triggerAlarmOn(120) +end ``` -## Troubleshooting +``` +rule "Record When Motion Detected" +when + Item MotionSensorAlarm changed to ON +then + val zmActions = getActions("zoneminder", "zoneminder:monitor:1") + zmActions.triggerAlarmOn() +end +``` - - - - - - - -
ProblemSolution
Cannot connect to ZoneMinder BridgeCheck if you can logon to ZoneMinder from your openHAB server (with http).
Check that it is possible to establish a Telnet connection from openHAB server to Zoneminder Server
ZoneMinder Bridge is not comming ONLINE. It says: 'OFFLINE - COMMUNICATION_ERROR Cannot access ZoneMinder Server. Check provided usercredentials'Check that the hostname is valid, if using a DNS name, make sure name is correct resolved. Also check that the given host can be accessed from a browser. Finally make sure to change the additional path from '/zm', if not using standard setup.
Cannot connect to ZoneMinder Bridge via HTTPS, using Letsencrypt certificateVerify your Java version, if Java is below build 101, letsencrypt certificate isn't known by Java. Either use HTTP or upgrade Java to newest build. Please be aware that https support is provided as an experimental feature.
I have tried all of the above, it still doesn't workTry to execute this from a commandline (on your openHAB server): curl -d "=XXXX&=YYYY&action=login&view=console" -c cookies.txt http:///zm/index.php. Change , and to the actual values. This will check if your server is accessible from the openHAB server.
+``` +rule "Record When Motion Detection Cleared" +when + Item MotionSensorAlarm changed to OFF +then + val zmActions = getActions("zoneminder", "zoneminder:monitor:1") + zmActions.triggerAlarmOff() +end +``` + +``` +val int NUM_MONITORS = 6 +var int monitorId = 1 + +rule "Rotate Through All Monitor Images Every 10 Seconds" +when + Time cron "0/10 * * ? * * *" +then + var String id = String::format("%d", monitorId) + ZmServer_ImageMonitorId.sendCommand(id) + monitorId = monitorId + 1 + if (monitorId > NUM_MONITORS) { + monitorId = 1 + } +end +``` diff --git a/bundles/org.openhab.binding.zoneminder/pom.xml b/bundles/org.openhab.binding.zoneminder/pom.xml index e64421c6705..c2eb9d12813 100644 --- a/bundles/org.openhab.binding.zoneminder/pom.xml +++ b/bundles/org.openhab.binding.zoneminder/pom.xml @@ -1,5 +1,5 @@ - - + 4.0.0 @@ -12,21 +12,6 @@ org.openhab.binding.zoneminder - openHAB Add-ons :: Bundles :: Zoneminder Binding - - - - org.jsoup - jsoup - 1.10.1 - compile - - - name.eskildsen - zoneminder4j - 0.9.7 - compile - - + openHAB Add-ons :: Bundles :: ZoneMinder Binding diff --git a/bundles/org.openhab.binding.zoneminder/src/main/feature/feature.xml b/bundles/org.openhab.binding.zoneminder/src/main/feature/feature.xml index f786d487a16..bdc6430dad7 100644 --- a/bundles/org.openhab.binding.zoneminder/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.zoneminder/src/main/feature/feature.xml @@ -1,6 +1,7 @@ - mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + openhab-runtime-base diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/action/IZmActions.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/action/IZmActions.java new file mode 100644 index 00000000000..f0ed5208a0b --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/action/IZmActions.java @@ -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(); +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/action/ZmActions.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/action/ZmActions.java new file mode 100644 index 00000000000..05752c5a074 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/action/ZmActions.java @@ -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. + * + * Note:The static method invokeMethodOf handles the case where + * the test actions instanceof ZmActions 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 actions 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(); + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmBindingConstants.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmBindingConstants.java new file mode 100644 index 00000000000..71234d6c2f7 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmBindingConstants.java @@ -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 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 SUPPORTED_MONITOR_THING_TYPES_UIDS = Collections + .unmodifiableSet(Stream.of(UID_MONITOR).collect(Collectors.toSet())); + + // Collection of all supported thing types + public static final Set 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"; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmHandlerFactory.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmHandlerFactory.java new file mode 100644 index 00000000000..26704f29040 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmHandlerFactory.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmStateDescriptionOptionsProvider.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmStateDescriptionOptionsProvider.java new file mode 100644 index 00000000000..a919db0e51c --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZmStateDescriptionOptionsProvider.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderConstants.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderConstants.java deleted file mode 100644 index bff859793d7..00000000000 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderConstants.java +++ /dev/null @@ -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"; -} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderHandlerFactory.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderHandlerFactory.java deleted file mode 100644 index 6d616609434..00000000000 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderHandlerFactory.java +++ /dev/null @@ -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 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; - } -} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderProperties.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderProperties.java deleted file mode 100644 index f50ad42daf3..00000000000 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/ZoneMinderProperties.java +++ /dev/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"; -} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZmBridgeConfig.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZmBridgeConfig.java new file mode 100644 index 00000000000..b1b170b114b --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZmBridgeConfig.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZmMonitorConfig.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZmMonitorConfig.java new file mode 100644 index 00000000000..592394d2d54 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZmMonitorConfig.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderBridgeServerConfig.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderBridgeServerConfig.java deleted file mode 100644 index 3604966c0cc..00000000000 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderBridgeServerConfig.java +++ /dev/null @@ -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; - } -} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderConfig.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderConfig.java deleted file mode 100644 index a1f66f73385..00000000000 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderConfig.java +++ /dev/null @@ -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(); -} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderThingConfig.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderThingConfig.java deleted file mode 100644 index 025f01bf2ec..00000000000 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderThingConfig.java +++ /dev/null @@ -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; - } -} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderThingMonitorConfig.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderThingMonitorConfig.java deleted file mode 100644 index fc609ae4c5f..00000000000 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/config/ZoneMinderThingMonitorConfig.java +++ /dev/null @@ -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(); - } -} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/discovery/MonitorDiscoveryService.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/discovery/MonitorDiscoveryService.java new file mode 100644 index 00000000000..a7429c16b3c --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/discovery/MonitorDiscoveryService.java @@ -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 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 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 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); + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/discovery/ZoneMinderDiscoveryService.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/discovery/ZoneMinderDiscoveryService.java deleted file mode 100644 index 2009353fff6..00000000000 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/discovery/ZoneMinderDiscoveryService.java +++ /dev/null @@ -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 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 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()); - } - } -} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/AbstractResponseDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/AbstractResponseDTO.java new file mode 100644 index 00000000000..57e260a0be6 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/AbstractResponseDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/AuthResponseDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/AuthResponseDTO.java new file mode 100644 index 00000000000..09cbd3dc7e9 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/AuthResponseDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/DataRefreshPriorityEnum.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventContainerDTO.java similarity index 52% rename from bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/DataRefreshPriorityEnum.java rename to bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventContainerDTO.java index c7e2cee3635..0bd0671fe51 100644 --- a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/DataRefreshPriorityEnum.java +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventContainerDTO.java @@ -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; } diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventDTO.java new file mode 100644 index 00000000000..794ef86328d --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventsDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventsDTO.java new file mode 100644 index 00000000000..f3b97582703 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/EventsDTO.java @@ -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 eventsList; + + /** + * Pagination information (currently not used) + */ + @SerializedName("pagination") + public Object pagination; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/ExceptionDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/ExceptionDTO.java new file mode 100644 index 00000000000..a2e00f1efc4 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/ExceptionDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorDTO.java new file mode 100644 index 00000000000..4e746b45b2e --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorItemDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorItemDTO.java new file mode 100644 index 00000000000..833eb3e26ff --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorItemDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorStateDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorStateDTO.java new file mode 100644 index 00000000000..cbbeed89891 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorStateDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorStatusDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorStatusDTO.java new file mode 100644 index 00000000000..ab2d5dd85a1 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorStatusDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorsDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorsDTO.java new file mode 100644 index 00000000000..b6253c11be9 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/MonitorsDTO.java @@ -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 monitorItems; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/VersionDTO.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/VersionDTO.java new file mode 100644 index 00000000000..8a409b23f2d --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/dto/VersionDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/Event.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/Event.java new file mode 100644 index 00000000000..753e55d19c3 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/Event.java @@ -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(); + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/Monitor.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/Monitor.java new file mode 100644 index 00000000000..eed32b066c8 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/Monitor.java @@ -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(); + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/MonitorFunction.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/MonitorFunction.java new file mode 100644 index 00000000000..0a281e240ec --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/MonitorFunction.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/MonitorState.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/MonitorState.java new file mode 100644 index 00000000000..b416a3267c2 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/MonitorState.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmAuth.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmAuth.java new file mode 100644 index 00000000000..361ed3e1beb --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmAuth.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmBridgeHandler.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmBridgeHandler.java new file mode 100644 index 00000000000..d5e8bbc3f7d --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmBridgeHandler.java @@ -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 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 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 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> 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 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 getMonitors() { + List 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 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 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 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 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 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 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"); + } + } +} diff --git a/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmMonitorHandler.java b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmMonitorHandler.java new file mode 100644 index 00000000000..a27df713037 --- /dev/null +++ b/bundles/org.openhab.binding.zoneminder/src/main/java/org/openhab/binding/zoneminder/internal/handler/ZmMonitorHandler.java @@ -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 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> 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