[zoneminder] Replacement for ZoneMinder binding (#8530)

Signed-off-by: Mark Hilbush <mark@hilbush.com>
This commit is contained in:
Mark Hilbush 2020-10-06 14:11:13 -04:00 committed by GitHub
parent cd16c680eb
commit 7615e5fd09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 3325 additions and 3458 deletions

View File

@ -267,7 +267,7 @@
/bundles/org.openhab.binding.yamahareceiver/ @davidgraeff @zarusz /bundles/org.openhab.binding.yamahareceiver/ @davidgraeff @zarusz
/bundles/org.openhab.binding.yeelight/ @claell /bundles/org.openhab.binding.yeelight/ @claell
/bundles/org.openhab.binding.yioremote/ @miloit /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.binding.zway/ @pathec
/bundles/org.openhab.io.homekit/ @beowulfe @yfre /bundles/org.openhab.io.homekit/ @beowulfe @yfre
/bundles/org.openhab.io.hueemulation/ @davidgraeff @digitaldan /bundles/org.openhab.io.hueemulation/ @davidgraeff @digitaldan

View File

@ -11,15 +11,3 @@ https://www.eclipse.org/legal/epl-2.0/.
== Source Code == Source Code
https://github.com/openhab/openhab-addons 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

View File

@ -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). Supports the ZoneMinder video surveillance system.
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.
## Supported Things ## Supported Things
This binding supports the following thing types The following thing types are supported:
<table> | Thing | ID | Discovery | Description |
<tr><td><b>Thing</b></td><td><b>Thing Type</b></td><td><b>Discovery</b></td><td><b>Description</b></td></tr> |----------|----------|-----------|-------------|
<tr><td>ZoneMinder Server</td><td>Bridge</td><td>Manual</td><td>A ZoneMinder Server. Required version is minimum 1.29</td></tr> | Server | server | Manual | Server bridge manages all communication with ZoneMinder server |
<tr><td>ZoneMinder Monitor</td><td>Thing</td><td>Automatic</td><td>Monitor as defined in ZoneMinder Server</td></tr> | Monitor | monitor | Automatic | Monitor represents a ZoneMinder camera monitor |
</table>
## 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. The binding requires ZoneMinder version 1.34.0 or greater and API version 2.0 or greater.
ZoneMinder things can be configured either through the online configuration utility via discovery, or manually through the 'zoneminder.things' configuration file. It also requires that you enable the **OPT_USE_API** parameter in the ZoneMinder configuration.
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.
### 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 | There are two different styles of operation, depending on whether or not you have ZoneMinder configured to use authentication.
|------------|--------|----------------------------------------------|
| 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 |
### Thing ### Non-Authenticated
| Channel | Type | Description | 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.
| 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 |
## 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.
``` ## Discovery
Bridge zoneminder:server:ZoneMinderSample [ hostname="192.168.1.55", user="<USERNAME>", password="<PASSWORD>", telnet_port=6802, refresh_interval_disk_usage=1 ]
{
Thing monitor monitor_1 [ monitorId=1, monitorTriggerTimeout=120, monitorEventText="Trigger activated from openHAB" ]
}
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
``` ```
/* ***************************************** Parameters:
* SERVER duration - The number of seconds for which the alarm should run.
* *****************************************/
Switch zmServer_Online "Zoneminder Online [%s]" <switch> {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]" <switch> {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:online"}
Switch zmMonitor1_Enabled "Enabled [%s]" <switch> {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:enabled"}
Switch zmMonitor1_ForceAlarm "Force Alarm [%s]" <switch> {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:force-alarm"}
Switch zmMonitor1_EventState "Alarm [%s]" <switch> {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:alarm"}
Switch zmMonitor1_Recording "Recording [%s]" <switch> {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]" <switch> {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]" <switch> {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:capture-daemon"}
Switch zmMonitor1_AnalysisState "Analysis Daemon [%s]" <switch> {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:analysis-daemon"}
Switch zmMonitor1_FrameState "Frame Daemon [%s]" <switch> {channel="zoneminder:monitor:ZoneMinderSample:monitor-1:frame-daemon"}
// Helpers
Switch zmMonitor1_Mode "Monitor active [%s]"
``` ```
### 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 when
Item zmMonitor1_EventState changed Item MotionSensorAlarm changed to ON
then then
if (zmMonitor1_EventState.state == ON) { ZM_TriggerAlarm.sendComand(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)
}
end end
``` ```
### Sitemap configuration
``` ```
sitemap zoneminder label="Zoneminder" rule "Record for 120 Seconds When Motion Detected"
{ when
Frame { Item MotionSensorAlarm changed to ON
Text item=zmServer_Online label="ZoneMinder Server [%s]" { then
Frame { val zmActions = getActions("zoneminder", "zoneminder:monitor:1")
Text item=zmServer_Online zmActions.triggerAlarmOn(120)
Text item=zmServer_CpuLoad end
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
}
}
}
``` ```
## Troubleshooting ```
rule "Record When Motion Detected"
when
Item MotionSensorAlarm changed to ON
then
val zmActions = getActions("zoneminder", "zoneminder:monitor:1")
zmActions.triggerAlarmOn()
end
```
<table> ```
<tr><td><b>Problem</b></td><td><b>Solution</b></td></tr> rule "Record When Motion Detection Cleared"
<tr><td>Cannot connect to ZoneMinder Bridge</td><td>Check if you can logon to ZoneMinder from your openHAB server (with http).</td></tr> when
<tr><td></td><td>Check that it is possible to establish a Telnet connection from openHAB server to Zoneminder Server</td></tr> Item MotionSensorAlarm changed to OFF
<tr><td>ZoneMinder Bridge is not comming ONLINE. It says: 'OFFLINE - COMMUNICATION_ERROR Cannot access ZoneMinder Server. Check provided usercredentials'</td><td>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.</td></tr> then
<tr><td>Cannot connect to ZoneMinder Bridge via HTTPS, using Letsencrypt certificate</td><td>Verify 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.</td></tr> val zmActions = getActions("zoneminder", "zoneminder:monitor:1")
<tr><td>I have tried all of the above, it still doesn't work</td><td>Try to execute this from a commandline (on your openHAB server): curl -d "<username>=XXXX&<password>=YYYY&action=login&view=console" -c cookies.txt http://<yourzmip>/zm/index.php. Change <yourzmip>, <username> and <password> to the actual values. This will check if your server is accessible from the openHAB server.</td></tr> zmActions.triggerAlarmOff()
</table> 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
```

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -12,21 +12,6 @@
<artifactId>org.openhab.binding.zoneminder</artifactId> <artifactId>org.openhab.binding.zoneminder</artifactId>
<name>openHAB Add-ons :: Bundles :: Zoneminder Binding</name> <name>openHAB Add-ons :: Bundles :: ZoneMinder Binding</name>
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>name.eskildsen</groupId>
<artifactId>zoneminder4j</artifactId>
<version>0.9.7</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project> </project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,13 +10,20 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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 { public class EventContainerDTO {
SCHEDULED,
HIGH_PRIORITY /**
* Zoneminder event object
*/
@SerializedName("Event")
public EventDTO event;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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