mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-21 07:56:03 +00:00
70_ZoneMinder:Initial version.\n71_ZM_Monitor:Initial version.
git-svn-id: https://svn.fhem.de/fhem/trunk@17479 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
c1470dbb29
commit
8bb8ee0dba
724
fhem/FHEM/70_ZoneMinder.pm
Executable file
724
fhem/FHEM/70_ZoneMinder.pm
Executable file
@ -0,0 +1,724 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 70_ZoneMinder.pm
|
||||
#
|
||||
# This file is part of Fhem.
|
||||
#
|
||||
# Fhem is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fhem is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fhem. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
#
|
||||
# ZoneMinder (c) Martin Gutenbrunner / https://github.com/delmar43/FHEM
|
||||
#
|
||||
# This module enables FHEM to interact with ZoneMinder surveillance system (see https://zoneminder.com)
|
||||
#
|
||||
# Discussed in FHEM Forum: https://forum.fhem.de/index.php/topic,91847.0.html
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use HttpUtils;
|
||||
use Crypt::MySQL qw(password41);
|
||||
use DevIo;
|
||||
use Digest::MD5 qw(md5 md5_hex md5_base64);
|
||||
|
||||
sub ZoneMinder_Initialize {
|
||||
my ($hash) = @_;
|
||||
$hash->{NotifyOrderPrefix} = "70-";
|
||||
$hash->{Clients} = "ZM_Monitor";
|
||||
|
||||
$hash->{GetFn} = "ZoneMinder_Get";
|
||||
$hash->{SetFn} = "ZoneMinder_Set";
|
||||
$hash->{DefFn} = "ZoneMinder_Define";
|
||||
$hash->{UndefFn} = "ZoneMinder_Undef";
|
||||
$hash->{ReadFn} = "ZoneMinder_Read";
|
||||
$hash->{ShutdownFn}= "ZoneMinder_Shutdown";
|
||||
$hash->{FW_detailFn} = "ZoneMinder_DetailFn";
|
||||
$hash->{WriteFn} = "ZoneMinder_Write";
|
||||
$hash->{ReadyFn} = "ZoneMinder_Ready";
|
||||
|
||||
$hash->{AttrList} = "interval publicAddress webConsoleContext " . $readingFnAttributes;
|
||||
$hash->{MatchList} = { "1:ZM_Monitor" => "^.*" };
|
||||
|
||||
Log3 '', 3, "ZoneMinder - Initialize done ...";
|
||||
}
|
||||
|
||||
sub ZoneMinder_Define {
|
||||
my ( $hash, $def ) = @_;
|
||||
my @a = split( "[ \t][ \t]*", $def );
|
||||
$hash->{NOTIFYDEV} = "global";
|
||||
|
||||
my $name = $a[0];
|
||||
$hash->{NAME} = $name;
|
||||
|
||||
my $nrArgs = scalar @a;
|
||||
if ($nrArgs < 3) {
|
||||
my $msg = "ZoneMinder ($name) - Wrong syntax: define <name> ZoneMinder <ZM_URL>";
|
||||
Log3 $name, 2, $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
my $module = $a[1];
|
||||
my $zmHost = $a[2];
|
||||
$hash->{helper}{ZM_HOST} = $zmHost;
|
||||
$zmHost .= ':6802' if (not $zmHost =~ m/:\d+$/);
|
||||
$hash->{DeviceName} = $zmHost;
|
||||
|
||||
if ($nrArgs == 4 || $nrArgs > 6) {
|
||||
my $msg = "ZoneMinder ($name) - Wrong syntax: define <name> ZoneMinder <ZM_URL> [<ZM_USERNAME> <ZM_PASSWORD>]";
|
||||
Log3 $name, 2, $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
if ($nrArgs == 5 || $nrArgs == 6) {
|
||||
$hash->{helper}{ZM_USERNAME} = $a[3];
|
||||
$hash->{helper}{ZM_PASSWORD} = $a[4];
|
||||
}
|
||||
|
||||
# Log3 $name, 3, "ZoneMinder ($name) - Define done ... module=$module, zmHost=$zmHost";
|
||||
|
||||
DevIo_CloseDev($hash) if (DevIo_IsOpen($hash));
|
||||
DevIo_OpenDev($hash, 0, undef);
|
||||
|
||||
ZoneMinder_afterInitialized($hash);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_afterInitialized {
|
||||
my ($hash) = @_;
|
||||
|
||||
ZoneMinder_API_Login($hash);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
# so far only used for generating the link to the ZM Web console
|
||||
# usePublic 0: zmHost, usePublic 1: publicAddress, usePublic undef: use public if publicAddress defined
|
||||
sub ZoneMinder_getZmWebUrl {
|
||||
my ($hash, $usePublic) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
#use private or public LAN for Web access?
|
||||
my $publicAddress = ZoneMinder_getPublicAddress($hash);
|
||||
my $zmHost = '';
|
||||
# Log3 $name, 0, "ZoneMinder ($name) - publicAddress: $publicAddress, usePublic: $usePublic";
|
||||
if ($publicAddress and $usePublic) {
|
||||
$zmHost = $publicAddress;
|
||||
} else {
|
||||
$zmHost = $hash->{helper}{ZM_HOST};
|
||||
}
|
||||
$zmHost = "http://$zmHost";
|
||||
$zmHost .= '/' if (not $zmHost =~ m/\/$/);
|
||||
|
||||
my $zmWebContext = $attr{$name}{webConsoleContext};
|
||||
if (not $zmWebContext) {
|
||||
$zmWebContext = 'zm';
|
||||
}
|
||||
$zmHost .= $zmWebContext;
|
||||
|
||||
return $zmHost;
|
||||
}
|
||||
|
||||
sub ZoneMinder_getPublicAddress {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return $attr{$name}{publicAddress};
|
||||
}
|
||||
|
||||
# is built by using web-url, and adding /api
|
||||
sub ZoneMinder_getZmApiUrl {
|
||||
my ($hash) = @_;
|
||||
|
||||
#use private LAN for API access for a start
|
||||
my $zmWebUrl = ZoneMinder_getZmWebUrl($hash, 0);
|
||||
return "$zmWebUrl/api";
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_Login {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $username = urlEncode($hash->{helper}{ZM_USERNAME});
|
||||
my $password = urlEncode($hash->{helper}{ZM_PASSWORD});
|
||||
|
||||
my $zmWebUrl = ZoneMinder_getZmWebUrl($hash);
|
||||
my $loginUrl = "$zmWebUrl/index.php?username=$username&password=$password&action=login&view=console";
|
||||
|
||||
Log3 $name, 4, "ZoneMinder ($name) - loginUrl: $loginUrl";
|
||||
my $apiParam = {
|
||||
url => $loginUrl,
|
||||
method => "POST",
|
||||
callback => \&ZoneMinder_API_Login_Callback,
|
||||
hash => $hash
|
||||
};
|
||||
HttpUtils_NonblockingGet($apiParam);
|
||||
|
||||
# Log3 $name, 3, "ZoneMinder ($name) - ZoneMinder_API_Login err: $apiErr, data: $apiParam->{httpheader}";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_Login_Callback {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
$hash->{APILoginStatus} = $param->{code};
|
||||
|
||||
if($err ne "") {
|
||||
Log3 $name, 0, "error while requesting ".$param->{url}." - $err";
|
||||
$hash->{APILoginError} = $err;
|
||||
} elsif($data ne "") {
|
||||
if ($data =~ m/Invalid username or password/) {
|
||||
$hash->{APILoginError} = "Invalid username or password.";
|
||||
} else {
|
||||
delete($defs{$name}{APILoginError});
|
||||
|
||||
ZoneMinder_GetCookies($hash, $param->{httpheader});
|
||||
|
||||
my $isFirst = !$hash->{helper}{apiInitialized};
|
||||
if ($isFirst) {
|
||||
$hash->{helper}{apiInitialized} = 1;
|
||||
my $zmApiUrl = ZoneMinder_getZmApiUrl($hash);
|
||||
ZoneMinder_SimpleGet($hash, "$zmApiUrl/host/getVersion.json", \&ZoneMinder_API_ReadHostInfo_Callback);
|
||||
ZoneMinder_SimpleGet($hash, "$zmApiUrl/configs.json", \&ZoneMinder_API_ReadConfig_Callback);
|
||||
ZoneMinder_API_getLoad($hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InternalTimer(gettimeofday() + 3600, "ZoneMinder_API_Login", $hash);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_getLoad {
|
||||
my ($hash) = @_;
|
||||
|
||||
my $zmApiUrl = ZoneMinder_getZmApiUrl($hash);
|
||||
ZoneMinder_SimpleGet($hash, "$zmApiUrl/host/getLoad.json", \&ZoneMinder_API_ReadHostLoad_Callback);
|
||||
}
|
||||
|
||||
sub ZoneMinder_SimpleGet {
|
||||
my ($hash, $url, $callback) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $apiParam = {
|
||||
url => $url,
|
||||
method => "GET",
|
||||
callback => $callback,
|
||||
hash => $hash
|
||||
};
|
||||
|
||||
if ($hash->{HTTPCookies}) {
|
||||
$apiParam->{header} .= "\r\n" if ($apiParam->{header});
|
||||
$apiParam->{header} .= "Cookie: " . $hash->{HTTPCookies};
|
||||
}
|
||||
|
||||
HttpUtils_NonblockingGet($apiParam);
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_ReadHostInfo_Callback {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if($err ne "") {
|
||||
Log3 $name, 0, "error while requesting ".$param->{url}." - $err";
|
||||
$hash->{ZM_VERSION} = 'error';
|
||||
$hash->{ZM_API_VERSION} = 'error';
|
||||
} elsif($data ne "") {
|
||||
|
||||
my $zmVersion = ZoneMinder_GetConfigValueByKey($hash, $data, 'version');
|
||||
if (not $zmVersion) {
|
||||
$zmVersion = 'unknown';
|
||||
}
|
||||
$hash->{ZM_VERSION} = $zmVersion;
|
||||
|
||||
my $zmApiVersion = ZoneMinder_GetConfigValueByKey($hash, $data, 'apiversion');
|
||||
if (not $zmApiVersion) {
|
||||
$zmApiVersion = 'unknown';
|
||||
}
|
||||
$hash->{ZM_API_VERSION} = $zmApiVersion;
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_ReadHostLoad_Callback {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if($err ne "") {
|
||||
Log3 $name, 0, "error while requesting ".$param->{url}." - $err";
|
||||
readingsSingleUpdate($hash, 'CPU_Load', 'error', 0);
|
||||
} elsif($data ne "") {
|
||||
my $load = ZoneMinder_GetConfigArrayByKey($hash, $data, 'load');
|
||||
readingsSingleUpdate($hash, 'CPU_Load', $load, 1);
|
||||
|
||||
InternalTimer(gettimeofday() + 60, "ZoneMinder_API_getLoad", $hash);
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#this extracts ZM_PATH_ZMS and ZM_AUTH_HASH_SECRET from the ZoneMinder config
|
||||
sub ZoneMinder_API_ReadConfig_Callback {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if($err ne "") {
|
||||
Log3 $name, 0, "error while requesting ".$param->{url}." - $err";
|
||||
} elsif($data ne "") {
|
||||
my $zmPathZms = ZoneMinder_GetConfigValueByName($hash, $data, 'ZM_PATH_ZMS');
|
||||
if ($zmPathZms) {
|
||||
$zmPathZms =~ s/\\//g;
|
||||
$hash->{helper}{ZM_PATH_ZMS} = $zmPathZms;
|
||||
}
|
||||
|
||||
my $authHashSecret = ZoneMinder_GetConfigValueByName($hash, $data, 'ZM_AUTH_HASH_SECRET');
|
||||
if ($authHashSecret) {
|
||||
$hash->{helper}{ZM_AUTH_HASH_SECRET} = $authHashSecret;
|
||||
ZoneMinder_calcAuthHash($hash);
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_GetConfigValueByKey {
|
||||
my ($hash, $config, $key) = @_;
|
||||
my $searchString = '"'.$key.'":"';
|
||||
return ZoneMinder_GetFromJson($hash, $config, $searchString, '"');
|
||||
}
|
||||
|
||||
sub ZoneMinder_GetConfigArrayByKey {
|
||||
my ($hash, $config, $key) = @_;
|
||||
my $searchString = '"'.$key.'":[';
|
||||
return ZoneMinder_GetFromJson($hash, $config, $searchString, ']');
|
||||
}
|
||||
|
||||
sub ZoneMinder_GetConfigValueByName {
|
||||
my ($hash, $config, $key) = @_;
|
||||
my $searchString = '"Name":"'.$key.'","Value":"';
|
||||
return ZoneMinder_GetFromJson($hash, $config, $searchString, '"');
|
||||
}
|
||||
|
||||
sub ZoneMinder_GetFromJson {
|
||||
my ($hash, $config, $searchString, $endChar) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
# Log3 $name, 5, "json: $config";
|
||||
my $searchLength = length($searchString);
|
||||
my $startIdx = index($config, $searchString);
|
||||
Log3 $name, 5, "$searchString found at $startIdx";
|
||||
$startIdx += $searchLength;
|
||||
my $endIdx = index($config, $endChar, $startIdx);
|
||||
my $frame = $endIdx - $startIdx;
|
||||
my $searchResult = substr $config, $startIdx, $frame;
|
||||
|
||||
Log3 $name, 5, "looking for $searchString - length: $searchLength. start: $startIdx. end: $endIdx. result: $searchResult";
|
||||
|
||||
return $searchResult;
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_UpdateMonitors_Callback {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my @monitors = split(/\{"Monitor"\:\{/, $data);
|
||||
|
||||
foreach my $monitorData (@monitors) {
|
||||
my $monitorId = ZoneMinder_GetConfigValueByKey($hash, $monitorData, 'Id');
|
||||
|
||||
if ( $monitorId =~ /^[0-9]+$/ ) {
|
||||
ZoneMinder_UpdateMonitorAttributes($hash, $monitorData, $monitorId);
|
||||
} else {
|
||||
Log3 $name, 0, "Invalid monitorId: $monitorId" unless ('itors' eq $monitorId);
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_UpdateMonitorAttributes {
|
||||
my ( $hash, $monitorData, $monitorId ) = @_;
|
||||
|
||||
my $function = ZoneMinder_GetConfigValueByKey($hash, $monitorData, 'Function');
|
||||
my $enabled = ZoneMinder_GetConfigValueByKey($hash, $monitorData, 'Enabled');
|
||||
my $streamReplayBuffer = ZoneMinder_GetConfigValueByKey($hash, $monitorData, 'StreamReplayBuffer');
|
||||
|
||||
my $msg = "monitor:$monitorId|$function|$enabled|$streamReplayBuffer";
|
||||
|
||||
my $dispatchResult = Dispatch($hash, $msg, undef);
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_CreateMonitors_Callback {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my @monitors = split(/\{"Monitor"\:\{/, $data);
|
||||
|
||||
foreach my $monitorData (@monitors) {
|
||||
my $monitorId = ZoneMinder_GetConfigValueByKey($hash, $monitorData, 'Id');
|
||||
|
||||
if ( $monitorId =~ /^[0-9]+$/ ) {
|
||||
my $dispatchResult = Dispatch($hash, "createMonitor:$monitorId", undef);
|
||||
}
|
||||
}
|
||||
my $zmApiUrl = ZoneMinder_getZmApiUrl($hash);
|
||||
ZoneMinder_SimpleGet($hash, "$zmApiUrl/monitors.json", \&ZoneMinder_API_UpdateMonitors_Callback);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_GetCookies {
|
||||
my ($hash, $header) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
foreach my $cookie ($header =~ m/set-cookie: ?(.*)/gi) {
|
||||
$cookie =~ /([^,; ]+)=([^,; ]+)[;, ]*(.*)/;
|
||||
$hash->{HTTPCookieHash}{$1}{Value} = $2;
|
||||
$hash->{HTTPCookieHash}{$1}{Options} = ($3 ? $3 : "");
|
||||
}
|
||||
$hash->{HTTPCookies} = join ("; ", map ($_ . "=".$hash->{HTTPCookieHash}{$_}{Value},
|
||||
sort keys %{$hash->{HTTPCookieHash}}));
|
||||
}
|
||||
|
||||
sub ZoneMinder_Write {
|
||||
my ( $hash, $arguments) = @_;
|
||||
my $method = $arguments->{method};
|
||||
|
||||
if ($method eq 'changeMonitorFunction') {
|
||||
|
||||
my $zmMonitorId = $arguments->{zmMonitorId};
|
||||
my $zmFunction = $arguments->{zmFunction};
|
||||
Log3 $hash->{NAME}, 4, "method: $method, monitorId:$zmMonitorId, Function:$zmFunction";
|
||||
return ZoneMinder_API_ChangeMonitorState($hash, $zmMonitorId, $zmFunction, undef);
|
||||
|
||||
} elsif ($method eq 'changeMonitorEnabled') {
|
||||
|
||||
my $zmMonitorId = $arguments->{zmMonitorId};
|
||||
my $zmEnabled = $arguments->{zmEnabled};
|
||||
Log3 $hash->{NAME}, 4, "method: $method, monitorId:$zmMonitorId, Enabled:$zmEnabled";
|
||||
return ZoneMinder_API_ChangeMonitorState($hash, $zmMonitorId, undef, $zmEnabled);
|
||||
|
||||
} elsif ($method eq 'changeMonitorAlarm') {
|
||||
|
||||
my $zmMonitorId = $arguments->{zmMonitorId};
|
||||
my $zmAlarm = $arguments->{zmAlarm};
|
||||
Log3 $hash->{NAME}, 4, "method: $method, monitorId:$zmMonitorId, Alarm:$zmAlarm";
|
||||
return ZoneMinder_Trigger_ChangeAlarmState($hash, $zmMonitorId, $zmAlarm);
|
||||
|
||||
} elsif ($method eq 'changeMonitorText') {
|
||||
|
||||
my $zmMonitorId = $arguments->{zmMonitorId};
|
||||
my $zmText = $arguments->{text};
|
||||
Log3 $hash->{NAME}, 4, "method: $method, monitorId:$zmMonitorId, Text:$zmText";
|
||||
return ZoneMinder_Trigger_ChangeText($hash, $zmMonitorId, $zmText);
|
||||
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_ChangeMonitorState {
|
||||
my ( $hash, $zmMonitorId, $zmFunction, $zmEnabled ) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $zmApiUrl = ZoneMinder_getZmApiUrl($hash);
|
||||
my $apiParam = {
|
||||
url => "$zmApiUrl/monitors/$zmMonitorId.json",
|
||||
method => "POST",
|
||||
callback => \&ZoneMinder_API_ChangeMonitorState_Callback,
|
||||
hash => $hash,
|
||||
zmMonitorId => $zmMonitorId,
|
||||
zmFunction => $zmFunction,
|
||||
zmEnabled => $zmEnabled
|
||||
};
|
||||
|
||||
if ( $zmFunction ) {
|
||||
$apiParam->{data} = "Monitor[Function]=$zmFunction";
|
||||
} elsif ( $zmEnabled || $zmEnabled eq '0' ) {
|
||||
$apiParam->{data} = "Monitor[Enabled]=$zmEnabled";
|
||||
}
|
||||
|
||||
if ($hash->{HTTPCookies}) {
|
||||
$apiParam->{header} .= "\r\n" if ($apiParam->{header});
|
||||
$apiParam->{header} .= "Cookie: " . $hash->{HTTPCookies};
|
||||
}
|
||||
|
||||
HttpUtils_NonblockingGet($apiParam);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_API_ChangeMonitorState_Callback {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
if ($data) {
|
||||
my $monitorId = $param->{zmMonitorId};
|
||||
my $logDevHash = $modules{ZM_Monitor}{defptr}{$name.'_'.$monitorId};
|
||||
my $function = $param->{zmFunction};
|
||||
my $enabled = $param->{zmEnabled};
|
||||
Log3 $name, 4, "ZM_Monitor ($name) - ChangeMonitorState callback data: $data, enabled: $enabled";
|
||||
|
||||
if ($function) {
|
||||
readingsSingleUpdate($logDevHash, 'monitorFunction', $function, 1);
|
||||
} elsif ($enabled || $enabled eq '0') {
|
||||
readingsSingleUpdate($logDevHash, 'motionDetectionEnabled', $enabled, 1);
|
||||
}
|
||||
|
||||
} else {
|
||||
Log3 $name, 2, "ZoneMinder ($name) - ChangeMonitorState callback err: $err";
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_Trigger_ChangeAlarmState {
|
||||
my ( $hash, $zmMonitorId, $zmAlarm ) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $msg = "$zmMonitorId|";
|
||||
if ( 'on' eq $zmAlarm ) {
|
||||
DevIo_SimpleWrite( $hash, $msg.'on|1|fhem', 2 );
|
||||
} elsif ( 'off' eq $zmAlarm ) {
|
||||
DevIo_SimpleWrite( $hash, $msg.'off|1|fhem', 2);
|
||||
} elsif ( $zmAlarm =~ /^on\-for\-timer/ ) {
|
||||
my $duration = $zmAlarm =~ s/on\-for\-timer\ /on\ /r;
|
||||
DevIo_SimpleWrite( $hash, $msg.$duration.'|1|fhem', 2);
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_Trigger_ChangeText {
|
||||
my ( $hash, $zmMonitorId, $zmText ) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $msg = "$zmMonitorId|show||||$zmText";
|
||||
Log3 $name, 4, "ZoneMinder ($name) - Change Text $msg";
|
||||
DevIo_SimpleWrite( $hash, $msg, 2 );
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_calcAuthHash {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
Log3 $name, 4, "ZoneMinder ($name) - calling calcAuthHash";
|
||||
|
||||
my ($sec,$min,$curHour,$dayOfMonth,$curMonth,$curYear,$wday,$yday,$isdst) = localtime();
|
||||
|
||||
my $zmAuthHashSecret = $hash->{helper}{ZM_AUTH_HASH_SECRET};
|
||||
if (not $zmAuthHashSecret) {
|
||||
Log3 $name, 0, "ZoneMinder ($name) - calcAuthHash was called, but no hash secret was found. This shouldn't happen. Please contact the module maintainer.";
|
||||
return undef;
|
||||
}
|
||||
my $username = $hash->{helper}{ZM_USERNAME};
|
||||
my $password = $hash->{helper}{ZM_PASSWORD};
|
||||
my $hashedPassword = password41($password);
|
||||
|
||||
my $authHash = $zmAuthHashSecret . $username . $hashedPassword . $curHour . $dayOfMonth . $curMonth . $curYear;
|
||||
my $authKey = md5_hex($authHash);
|
||||
|
||||
readingsSingleUpdate($hash, 'authHash', $authKey, 1);
|
||||
InternalTimer(gettimeofday() + 3600, "ZoneMinder_calcAuthHash", $hash);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_Shutdown {
|
||||
ZoneMinder_Undef(@_);
|
||||
}
|
||||
|
||||
sub ZoneMinder_Undef {
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
DevIo_CloseDev($hash) if (DevIo_IsOpen($hash));
|
||||
RemoveInternalTimer($hash);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_Read {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $data = DevIo_SimpleRead($hash);
|
||||
return if (!defined($data)); # connection lost
|
||||
|
||||
my $buffer = $hash->{PARTIAL};
|
||||
$buffer .= $data;
|
||||
#as long as the buffer contains newlines
|
||||
while ($buffer =~ m/\n/) {
|
||||
my $msg;
|
||||
($msg, $buffer) = split("\n", $buffer, 2);
|
||||
chomp $msg;
|
||||
$msg = "event:$msg";
|
||||
# Log3 $name, 3, "ZoneMinder ($name) incoming message $msg.";
|
||||
my $dispatchResult = Dispatch($hash, $msg, undef);
|
||||
}
|
||||
$hash->{PARTIAL} = $buffer;
|
||||
}
|
||||
|
||||
sub ZoneMinder_DetailFn {
|
||||
my ( $FW_wname, $deviceName, $FW_room ) = @_;
|
||||
|
||||
my $hash = $defs{$deviceName};
|
||||
|
||||
my $zmWebUrl = ZoneMinder_getZmWebUrl($hash, 1);
|
||||
my $zmUsername = urlEncode($hash->{helper}{ZM_USERNAME});
|
||||
my $zmPassword = urlEncode($hash->{helper}{ZM_PASSWORD});
|
||||
my $zmConsoleUrl = "$zmWebUrl/index.php?username=$zmUsername&password=$zmPassword&action=login&view=console";
|
||||
|
||||
if ($zmConsoleUrl) {
|
||||
return "<div><a href='$zmConsoleUrl' target='_blank'>Go to ZoneMinder console</a></div>";
|
||||
} else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub ZoneMinder_Get {
|
||||
my ( $hash, $name, $opt, $args ) = @_;
|
||||
|
||||
my $zmApiUrl = ZoneMinder_getZmApiUrl($hash);
|
||||
if ("autocreateMonitors" eq $opt) {
|
||||
ZoneMinder_SimpleGet($hash, "$zmApiUrl/monitors.json", \&ZoneMinder_API_CreateMonitors_Callback);
|
||||
return undef;
|
||||
} elsif ("updateMonitorConfig" eq $opt) {
|
||||
ZoneMinder_SimpleGet($hash, "$zmApiUrl/monitors.json", \&ZoneMinder_API_UpdateMonitors_Callback);
|
||||
return undef;
|
||||
} elsif ("calcAuthHash" eq $opt) {
|
||||
ZoneMinder_calcAuthHash($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
# Log3 $name, 3, "ZoneMinder ($name) - Get done ...";
|
||||
return "Unknown argument $opt, choose one of autocreateMonitors updateMonitorConfig calcAuthHash";
|
||||
}
|
||||
|
||||
sub ZoneMinder_Set {
|
||||
my ( $hash, $param ) = @_;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
# Log3 $name, 3, "ZoneMinder ($name) - Set done ...";
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZoneMinder_Ready {
|
||||
my ( $hash ) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if ( $hash->{STATE} eq "disconnected" ) {
|
||||
my $err = DevIo_OpenDev($hash, 1, undef ); #if success, $err is undef
|
||||
if (not $err) {
|
||||
Log3 $name, 3, "ZoneMinder ($name) - reconnect to ZoneMinder successful";
|
||||
return 1;
|
||||
} else {
|
||||
Log3 $name, 0, "ZoneMinder ($name) - reconnect to ZoneMinder failed: $err";
|
||||
return $err;
|
||||
}
|
||||
}
|
||||
|
||||
# This is relevant for Windows/USB only
|
||||
if(defined($hash->{USBDev})) {
|
||||
my $po = $hash->{USBDev};
|
||||
my ( $BlockingFlags, $InBytes, $OutBytes, $ErrorFlags ) = $po->status;
|
||||
return ( $InBytes > 0 );
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
||||
# Beginn der Commandref
|
||||
|
||||
=pod
|
||||
=item device
|
||||
=item summary Maintain ZoneMinder events and monitor operation modes in FHEM
|
||||
=item summary_DE ZoneMinder events und Monitor Konfiguration in FHEM warten
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="ZoneMinder"></a>
|
||||
<h3>ZoneMinder</h3>
|
||||
|
||||
<a name="ZoneMinderdefine"></a>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> ZoneMinder <ZM-Host> [<username> <password>]</code>
|
||||
<br><br>
|
||||
Defines a ZoneMinder device at the given host address. This allows you to exchange events between ZoneMinder and FHEM.
|
||||
Also providing <code>username</code> and <code>password</code> provides access to ZoneMinder API and more functionality.
|
||||
<br>
|
||||
Example:
|
||||
<ul>
|
||||
<code>define zm ZoneMinder 10.0.0.100</code><br>
|
||||
<code>define zm ZoneMinder 10.0.0.100 fhemApiUser fhemApiPass</code>
|
||||
</ul>
|
||||
<br>
|
||||
</ul>
|
||||
<br><br>
|
||||
|
||||
<a name="ZoneMinderget"></a>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<li><code>autocreateMonitors</code><br>Queries the ZoneMinder API and autocreates all ZM_Monitor devices that belong to that installation.
|
||||
</li>
|
||||
<li><code>updateMonitorConfig</code><br>Queries the ZoneMinder API and updates the Readings of ZM_Monitor devices (monitorFunction, motionDetectionEnabled, ...)
|
||||
</li>
|
||||
<li><code>calcAuthHash</code><br>Calculates a fresh auth hash. Please note that the hash only changes with every full hour. So, calling this doesn't necessarily change any Readings, depending on the age of the current hash.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<br><br>
|
||||
<a name="ZoneMinderattr"></a>
|
||||
<b>Attributes</b>
|
||||
<br><br>
|
||||
<ul>
|
||||
<li><code>publicAddress <address></code><br>This configures public accessibility of your LAN (eg your ddns address). Define a valid URL here, eg <code>https://my.own.domain:2344</code></li>
|
||||
<li><code>webConsoleContext <path></code><br>If not set, this defaults to <code>/zm</code>. This is used for building the URL to the ZoneMinder web console.</li>
|
||||
</ul>
|
||||
|
||||
<br><br>
|
||||
|
||||
<a name="ZoneMinderreadings"></a>
|
||||
<b>Readings</b>
|
||||
<br><br>
|
||||
<ul>
|
||||
<li>CPU_Load<br/>The CPU load of the ZoneMinder host. Provides 1, 5 and 15 minutes interval.</li>
|
||||
<li>authHash<br/>The auth hash that allows access to Stream URLs without requiring username or password.</li>
|
||||
<li>state<br/>The current connection state to the ZoneMinder Trigger Port (6802 per default)</li>
|
||||
</ul>
|
||||
|
||||
|
||||
=end html
|
||||
|
||||
# Ende der Commandref
|
||||
=cut
|
504
fhem/FHEM/71_ZM_Monitor.pm
Executable file
504
fhem/FHEM/71_ZM_Monitor.pm
Executable file
@ -0,0 +1,504 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 71_ZM_Monitor.pm
|
||||
#
|
||||
# This file is part of Fhem.
|
||||
#
|
||||
# Fhem is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fhem is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fhem. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
#
|
||||
# ZoneMinder (c) Martin Gutenbrunner / https://github.com/delmar43/FHEM
|
||||
#
|
||||
# This module is designed to work as a logical device in connection with 70_ZoneMinder
|
||||
# as a physical device.
|
||||
#
|
||||
# Discussed in FHEM Forum: https://forum.fhem.de/index.php/topic,91847.0.html
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use HttpUtils;
|
||||
|
||||
my @ZM_Functions = qw( None Monitor Modect Record Mocord Nodect );
|
||||
my @ZM_Alarms = qw( on off on-for-timer );
|
||||
|
||||
sub ZM_Monitor_Initialize {
|
||||
my ($hash) = @_;
|
||||
$hash->{NotifyOrderPrefix} = "71-";
|
||||
|
||||
$hash->{GetFn} = "ZM_Monitor_Get";
|
||||
$hash->{SetFn} = "ZM_Monitor_Set";
|
||||
$hash->{DefFn} = "ZM_Monitor_Define";
|
||||
$hash->{UndefFn} = "ZM_Monitor_Undef";
|
||||
$hash->{FW_detailFn} = "ZM_Monitor_DetailFn";
|
||||
$hash->{ParseFn} = "ZM_Monitor_Parse";
|
||||
$hash->{NotifyFn} = "ZM_Monitor_Notify";
|
||||
|
||||
$hash->{AttrList} = 'showLiveStreamInDetail:0,1 '.$readingFnAttributes;
|
||||
$hash->{Match} = "^.*";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZM_Monitor_Define {
|
||||
my ( $hash, $def ) = @_;
|
||||
$hash->{NOTIFYDEV} = "TYPE=ZoneMinder";
|
||||
|
||||
my @a = split( "[ \t][ \t]*", $def );
|
||||
|
||||
my $name = $a[0];
|
||||
my $module = $a[1];
|
||||
my $zmMonitorId = $a[2];
|
||||
|
||||
if(@a < 3 || @a > 3) {
|
||||
my $msg = "ZM_Monitor ($name) - Wrong syntax: define <name> ZM_Monitor <ZM_MONITOR_ID>";
|
||||
Log3 $name, 2, $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
$hash->{NAME} = $name;
|
||||
readingsSingleUpdate($hash, "state", "idle", 1);
|
||||
|
||||
AssignIoPort($hash);
|
||||
|
||||
my $ioDevName = $hash->{IODev}{NAME};
|
||||
my $logDevAddress = $ioDevName.'_'.$zmMonitorId;
|
||||
# Adresse rückwärts dem Hash zuordnen (für ParseFn)
|
||||
# Log3 $name, 3, "ZM_Monitor ($name) - Logical device address: $logDevAddress";
|
||||
$modules{ZM_Monitor}{defptr}{$logDevAddress} = $hash;
|
||||
|
||||
# Log3 $name, 3, "ZM_Monitor ($name) - Define done ... module=$module, zmHost=$zmHost, zmMonitorId=$zmMonitorId";
|
||||
|
||||
$hash->{helper}{ZM_MONITOR_ID} = $zmMonitorId;
|
||||
|
||||
ZM_Monitor_UpdateStreamUrls($hash);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZM_Monitor_UpdateStreamUrls {
|
||||
my ( $hash ) = @_;
|
||||
my $ioDevName = $hash->{IODev}{NAME};
|
||||
|
||||
my $zmPathZms = $hash->{IODev}{helper}{ZM_PATH_ZMS};
|
||||
if (not $zmPathZms) {
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $zmHost = $hash->{IODev}{helper}{ZM_HOST};
|
||||
my $streamUrl = "http://$zmHost";
|
||||
my $zmUsername = urlEncode($hash->{IODev}{helper}{ZM_USERNAME});
|
||||
my $zmPassword = urlEncode($hash->{IODev}{helper}{ZM_PASSWORD});
|
||||
my $authPart = "&user=$zmUsername&pass=$zmPassword";
|
||||
|
||||
readingsBeginUpdate($hash);
|
||||
ZM_Monitor_WriteStreamUrlToReading($hash, $streamUrl, 'streamUrl', $authPart);
|
||||
|
||||
my $pubStreamUrl = $attr{$ioDevName}{publicAddress};
|
||||
if ($pubStreamUrl) {
|
||||
my $authHash = ReadingsVal($ioDevName, 'authHash', '');
|
||||
if ($authHash) { #if ZM_AUTH_KEY is defined, use the auth-hash. otherwise, use the previously defined username/pwd
|
||||
$authPart = "&auth=$authHash";
|
||||
}
|
||||
ZM_Monitor_WriteStreamUrlToReading($hash, $pubStreamUrl, 'pubStreamUrl', $authPart);
|
||||
}
|
||||
readingsEndUpdate($hash, 1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
# is build by using hosname, NPH_ZMS, monitorId, streamBufferSize, and auth
|
||||
sub ZM_Monitor_getZmStreamUrl {
|
||||
my ($hash) = @_;
|
||||
|
||||
#use private or public LAN for streaming access?
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZM_Monitor_WriteStreamUrlToReading {
|
||||
my ( $hash, $streamUrl, $readingName, $authPart ) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $zmPathZms = $hash->{IODev}{helper}{ZM_PATH_ZMS};
|
||||
my $zmMonitorId = $hash->{helper}{ZM_MONITOR_ID};
|
||||
my $buffer = ReadingsVal($name, 'streamReplayBuffer', '1000');
|
||||
|
||||
my $imageUrl = $streamUrl."$zmPathZms?mode=single&scale=100&monitor=$zmMonitorId".$authPart;
|
||||
my $imageReadingName = $readingName;
|
||||
$imageReadingName =~ s/Stream/Image/g;
|
||||
readingsBulkUpdate($hash, $imageReadingName, $imageUrl, 1);
|
||||
|
||||
$streamUrl = $streamUrl."$zmPathZms?mode=jpeg&scale=100&maxfps=30&buffer=$buffer&monitor=$zmMonitorId".$authPart;
|
||||
readingsBulkUpdate($hash, $readingName, "$streamUrl", 1);
|
||||
}
|
||||
|
||||
sub ZM_Monitor_DetailFn {
|
||||
my ( $FW_wname, $deviceName, $FW_room ) = @_;
|
||||
|
||||
my $hash = $defs{$deviceName};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $showLiveStream = $attr{$name}{showLiveStreamInDetail};
|
||||
return "<div>To view a live stream here, execute: attr $name showLiveStreamInDetail 1</div>" if (not $showLiveStream);
|
||||
|
||||
my $streamDisabled = (ReadingsVal($deviceName, 'monitorFunction', 'None') eq 'None');
|
||||
if ($streamDisabled) {
|
||||
return '<div>Streaming disabled</div>';
|
||||
}
|
||||
|
||||
my $streamUrl = ReadingsVal($deviceName, 'pubStreamUrl', undef);
|
||||
if (not $streamUrl) {
|
||||
$streamUrl = ReadingsVal($deviceName, 'streamUrl', undef);
|
||||
}
|
||||
if ($streamUrl) {
|
||||
return "<div><img src='$streamUrl'></img></div>";
|
||||
} else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub ZM_Monitor_Undef {
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZM_Monitor_Get {
|
||||
my ( $hash, $name, $opt, @args ) = @_;
|
||||
|
||||
# return "Unknown argument $opt, choose one of config";
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZM_Monitor_Set {
|
||||
my ( $hash, $name, $cmd, @args ) = @_;
|
||||
|
||||
if ( "monitorFunction" eq $cmd ) {
|
||||
my $arg = $args[0];
|
||||
if (grep { $_ eq $arg } @ZM_Functions) {
|
||||
my $arguments = {
|
||||
method => 'changeMonitorFunction',
|
||||
zmMonitorId => $hash->{helper}{ZM_MONITOR_ID},
|
||||
zmFunction => $arg
|
||||
};
|
||||
my $result = IOWrite($hash, $arguments);
|
||||
return $result;
|
||||
}
|
||||
return "Unknown value $arg for $cmd, choose one of ".join(' ', @ZM_Functions);
|
||||
} elsif ("motionDetectionEnabled" eq $cmd ) {
|
||||
my $arg = $args[0];
|
||||
if ($arg eq '1' || $arg eq '0') {
|
||||
my $arguments = {
|
||||
method => 'changeMonitorEnabled',
|
||||
zmMonitorId => $hash->{helper}{ZM_MONITOR_ID},
|
||||
zmEnabled => $arg
|
||||
};
|
||||
my $result = IOWrite($hash, $arguments);
|
||||
return $result;
|
||||
}
|
||||
return "Unknown value $arg for $cmd, choose one of 0 1";
|
||||
} elsif ("alarmState" eq $cmd) {
|
||||
my $arg = $args[0];
|
||||
if (grep { $_ eq $arg } @ZM_Alarms) {
|
||||
|
||||
$arg .= ' '.$args[1] if ( 'on-for-timer' eq $arg );
|
||||
my $arguments = {
|
||||
method => 'changeMonitorAlarm',
|
||||
zmMonitorId => $hash->{helper}{ZM_MONITOR_ID},
|
||||
zmAlarm => $arg
|
||||
};
|
||||
my $result = IOWrite($hash, $arguments);
|
||||
return $result;
|
||||
}
|
||||
return "Unknown value $arg for $cmd, chose one of ".join(' '. @ZM_Alarms);
|
||||
} elsif ("text" eq $cmd) {
|
||||
my $arg = join ' ', @args;
|
||||
if (not $arg) {
|
||||
$arg = '';
|
||||
}
|
||||
|
||||
my $arguments = {
|
||||
method => 'changeMonitorText',
|
||||
zmMonitorId => $hash->{helper}{ZM_MONITOR_ID},
|
||||
text => $arg
|
||||
};
|
||||
my $result = IOWrite($hash, $arguments);
|
||||
return $result;
|
||||
}
|
||||
|
||||
return 'monitorFunction:'.join(',', @ZM_Functions).' motionDetectionEnabled:0,1 alarmState:on,off,on-for-timer text';
|
||||
}
|
||||
|
||||
# incoming messages from physical device module (70_ZoneMinder in this case).
|
||||
sub ZM_Monitor_Parse {
|
||||
my ( $io_hash, $message) = @_;
|
||||
|
||||
my @msg = split(/\:/, $message, 2);
|
||||
my $msgType = $msg[0];
|
||||
if ($msgType eq 'event') {
|
||||
return ZM_Monitor_handleEvent($io_hash, $msg[1]);
|
||||
} elsif ($msgType eq 'createMonitor') {
|
||||
return ZM_Monitor_handleMonitorCreation($io_hash, $msg[1]);
|
||||
} elsif ($msgType eq 'monitor') {
|
||||
return ZM_Monitor_handleMonitorUpdate($io_hash, $msg[1]);
|
||||
} else {
|
||||
Log3 $io_hash, 0, "Unknown message type: $msgType";
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZM_Monitor_handleEvent {
|
||||
my ( $io_hash, $message ) = @_;
|
||||
|
||||
my $ioName = $io_hash->{NAME};
|
||||
my @msgTokens = split(/\|/, $message);
|
||||
my $zmMonitorId = $msgTokens[0];
|
||||
my $alertState = $msgTokens[1];
|
||||
my $eventTs = $msgTokens[2];
|
||||
my $eventId = $msgTokens[3];
|
||||
|
||||
my $logDevAddress = $ioName.'_'.$zmMonitorId;
|
||||
Log3 $io_hash, 5, "Handling event for logical device $logDevAddress";
|
||||
# wenn bereits eine Gerätedefinition existiert (via Definition Pointer aus Define-Funktion)
|
||||
if(my $hash = $modules{ZM_Monitor}{defptr}{$logDevAddress}) {
|
||||
Log3 $hash, 5, "Logical device $logDevAddress found. Writing readings";
|
||||
|
||||
readingsBeginUpdate($hash);
|
||||
ZM_Monitor_createEventStreamUrl($hash, $eventId);
|
||||
my $state;
|
||||
if ($alertState eq "on") {
|
||||
$state = "alert";
|
||||
} elsif ($alertState eq "off") {
|
||||
$state = "idle";
|
||||
}
|
||||
readingsBulkUpdate($hash, "state", $state, 1);
|
||||
readingsBulkUpdate($hash, "alert", $alertState, 1);
|
||||
readingsBulkUpdate($hash, "lastEventTimestamp", $eventTs);
|
||||
readingsBulkUpdate($hash, "lastEventId", $eventId);
|
||||
readingsEndUpdate($hash, 1);
|
||||
|
||||
Log3 $hash, 5, "Writing readings done. Now returning log dev name: $hash->{NAME}";
|
||||
# Rückgabe des Gerätenamens, für welches die Nachricht bestimmt ist.
|
||||
return $hash->{NAME};
|
||||
} else {
|
||||
# Keine Gerätedefinition verfügbar. Daher Vorschlag define-Befehl: <NAME> <MODULNAME> <ADDRESSE>
|
||||
my $autocreate = "UNDEFINED ZM_Monitor_$logDevAddress ZM_Monitor $zmMonitorId";
|
||||
Log3 $io_hash, 5, "logical device with address $logDevAddress not found. returning autocreate: $autocreate";
|
||||
return $autocreate;
|
||||
}
|
||||
}
|
||||
|
||||
#for now, this is nearly a duplicate of writing the streamUrl reading.
|
||||
#will need some love to make better use of existing code.
|
||||
sub ZM_Monitor_createEventStreamUrl {
|
||||
my ( $hash, $eventId ) = @_;
|
||||
my $ioDevName = $hash->{IODev}{NAME};
|
||||
|
||||
my $zmPathZms = $hash->{IODev}{helper}{ZM_PATH_ZMS};
|
||||
if (not $zmPathZms) {
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $zmHost = $hash->{IODev}{helper}{ZM_HOST};
|
||||
my $streamUrl = "http://$zmHost";
|
||||
my $zmUsername = urlEncode($hash->{IODev}{helper}{ZM_USERNAME});
|
||||
my $zmPassword = urlEncode($hash->{IODev}{helper}{ZM_PASSWORD});
|
||||
my $authPart = "&user=$zmUsername&pass=$zmPassword";
|
||||
ZM_Monitor_WriteEventStreamUrlToReading($hash, $streamUrl, 'eventStreamUrl', $authPart, $eventId);
|
||||
|
||||
my $pubStreamUrl = $attr{$ioDevName}{publicAddress};
|
||||
if ($pubStreamUrl) {
|
||||
my $authHash = ReadingsVal($ioDevName, 'authHash', '');
|
||||
if ($authHash) { #if ZM_AUTH_KEY is defined, use the auth-hash. otherwise, use the previously defined username/pwd
|
||||
$authPart = "&auth=$authHash";
|
||||
}
|
||||
ZM_Monitor_WriteEventStreamUrlToReading($hash, $pubStreamUrl, 'pubEventStreamUrl', $authPart, $eventId);
|
||||
}
|
||||
}
|
||||
|
||||
sub ZM_Monitor_handleMonitorUpdate {
|
||||
my ( $io_hash, $message ) = @_;
|
||||
|
||||
my $ioName = $io_hash->{NAME};
|
||||
my @msgTokens = split(/\|/, $message); #$message = "$monitorId|$function|$enabled|$streamReplayBuffer";
|
||||
my $zmMonitorId = $msgTokens[0];
|
||||
my $function = $msgTokens[1];
|
||||
my $enabled = $msgTokens[2];
|
||||
my $streamReplayBuffer = $msgTokens[3];
|
||||
my $logDevAddress = $ioName.'_'.$zmMonitorId;
|
||||
|
||||
if ( my $hash = $modules{ZM_Monitor}{defptr}{$logDevAddress} ) {
|
||||
readingsBeginUpdate($hash);
|
||||
readingsBulkUpdateIfChanged($hash, 'monitorFunction', $function);
|
||||
readingsBulkUpdateIfChanged($hash, 'motionDetectionEnabled', $enabled);
|
||||
my $bufferChanged = readingsBulkUpdateIfChanged($hash, 'streamReplayBuffer', $streamReplayBuffer);
|
||||
readingsEndUpdate($hash, 1);
|
||||
|
||||
ZM_Monitor_UpdateStreamUrls($hash);
|
||||
|
||||
return $hash->{NAME};
|
||||
# } else {
|
||||
# my $autocreate = "UNDEFINED ZM_Monitor_$logDevAddress ZM_Monitor $zmMonitorId";
|
||||
# Log3 $io_hash, 5, "logical device with address $logDevAddress not found. returning autocreate: $autocreate";
|
||||
# return $autocreate;
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZM_Monitor_handleMonitorCreation {
|
||||
my ( $io_hash, $message ) = @_;
|
||||
|
||||
my $ioName = $io_hash->{NAME};
|
||||
my @msgTokens = split(/\|/, $message); #$message = "$monitorId";
|
||||
my $zmMonitorId = $msgTokens[0];
|
||||
my $logDevAddress = $ioName.'_'.$zmMonitorId;
|
||||
|
||||
if ( my $hash = $modules{ZM_Monitor}{defptr}{$logDevAddress} ) {
|
||||
return $hash->{NAME};
|
||||
} else {
|
||||
my $autocreate = "UNDEFINED ZM_Monitor_$logDevAddress ZM_Monitor $zmMonitorId";
|
||||
Log3 $io_hash, 5, "logical device with address $logDevAddress not found. returning autocreate: $autocreate";
|
||||
return $autocreate;
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub ZM_Monitor_WriteEventStreamUrlToReading {
|
||||
my ( $hash, $streamUrl, $readingName, $authPart, $eventId ) = @_;
|
||||
|
||||
my $zmPathZms = $hash->{IODev}{helper}{ZM_PATH_ZMS};
|
||||
$streamUrl = $streamUrl."/" if (not $streamUrl =~ m/\/$/);
|
||||
|
||||
my $zmMonitorId = $hash->{helper}{ZM_MONITOR_ID};
|
||||
my $imageUrl = $streamUrl."$zmPathZms?mode=single&scale=100&monitor=$zmMonitorId".$authPart;
|
||||
my $imageReadingName = $readingName;
|
||||
$imageReadingName =~ s/Stream/Image/g;
|
||||
readingsBulkUpdate($hash, $imageReadingName, $imageUrl, 1);
|
||||
|
||||
$streamUrl = $streamUrl."$zmPathZms?source=event&mode=jpeg&event=$eventId&frame=1&scale=100&rate=100&maxfps=30".$authPart;
|
||||
readingsBulkUpdate($hash, $readingName, $streamUrl, 1);
|
||||
|
||||
}
|
||||
|
||||
sub ZM_Monitor_Notify {
|
||||
my ($own_hash, $dev_hash) = @_;
|
||||
my $name = $own_hash->{NAME}; # own name / hash
|
||||
|
||||
return "" if(IsDisabled($name)); # Return without any further action if the module is disabled
|
||||
|
||||
my $devName = $dev_hash->{NAME}; # Device that created the events
|
||||
|
||||
my $events = deviceEvents($dev_hash,1);
|
||||
return if( !$events );
|
||||
|
||||
foreach my $event (@{$events}) {
|
||||
$event = "" if(!defined($event));
|
||||
Log3 $name, 4, "ZM_Monitor ($name) - Incoming event: $event";
|
||||
|
||||
my @msg = split(/\:/, $event, 2);
|
||||
if ($msg[0] eq 'authHash') {
|
||||
ZM_Monitor_UpdateStreamUrls($own_hash);
|
||||
} else {
|
||||
Log3 $name, 4, "ZM_Monitor ($name) - ignoring";
|
||||
}
|
||||
|
||||
# Examples:
|
||||
# $event = "readingname: value"
|
||||
# or
|
||||
# $event = "INITIALIZED" (for $devName equal "global")
|
||||
#
|
||||
# processing $event with further code
|
||||
}
|
||||
}
|
||||
|
||||
# Eval-Rückgabewert für erfolgreiches
|
||||
# Laden des Moduls
|
||||
1;
|
||||
|
||||
|
||||
# Beginn der Commandref
|
||||
|
||||
=pod
|
||||
=item device
|
||||
=item summary Logical device to change Monitor operation modes in ZoneMinder
|
||||
=item summary_DE Logisches Modul zum Verändern der Kameraeinstellungen in ZoneMinder
|
||||
|
||||
=begin html
|
||||
|
||||
<a name="ZM_Monitor"></a>
|
||||
<h3>ZM_Monitor</h3>
|
||||
|
||||
<a name="ZM_Monitordefine"></a>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> ZM_Monitor <ZM-Monitor ID></code>
|
||||
<br><br>
|
||||
This is usually called by autocreate and triggered by the ZoneMinder IODevice.
|
||||
<br>
|
||||
</ul>
|
||||
<br><br>
|
||||
|
||||
<a name="ZM_Monitorset"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<li><code>alarmState</code><br>Puts a monitor into alarm state or out of alarm state via the ZoneMinder trigger port.</li>
|
||||
<li><code>monitorFunction</code><br>Sets the operating mode of a Monitor in ZoneMinder via the ZoneMinder API.</li>
|
||||
<li><code>motionDetectionEnabled</code><br>Enables or disables monitor detection of a monitor via ZoneMinder API.</li>
|
||||
<li><code>text</code><br/>Allows you to set a text for a Timestamp's <code>%Q</code> portion in ZoneMinder via the ZoneMinder trigger port.</li>
|
||||
</ul>
|
||||
|
||||
<br><br>
|
||||
<a name="ZM_Monitorattr"></a>
|
||||
<b>Attributes</b>
|
||||
<br><br>
|
||||
<ul>
|
||||
<li><code>showLiveStreamInDetail</code><br/>If set to <code>1</code>, a live-stream of the current monitor will be shown on top of the FHEMWEB detail page.</li>
|
||||
</ul>
|
||||
|
||||
<br><br>
|
||||
|
||||
<a name="ZM_Monitorreadings"></a>
|
||||
<b>Readings</b>
|
||||
<br><br>
|
||||
<ul>
|
||||
<li><code>alert</code><br/>The alert state.</li>
|
||||
<li><code>eventImageUrl</code><br/>Link to the first image of the latest event recording, based on the ZM-Host parameter used in the device definition.</li>
|
||||
<li><code>eventStreamUrl</code><br/>Link to the latest event recording, based on the ZM-Host parameter used in the device definition.</li>
|
||||
<li><code>lastEventId</code><br/>ID of the latest event in ZoneMinder.</li>
|
||||
<li><code>lastEventTimestamp</code><br/>Timestamp of the latest event from ZoneMinder.</li>
|
||||
<li><code>monitorFunction</code><br/>Current operation mode of the monitor.</li>
|
||||
<li><code>motionDetectionEnabled</code><br/>Equals the 'enabled' setting in ZoneMinder. Allows you to put the monitor into a more passive state (according to ZoneMinder documentation).</li>
|
||||
<li><code>pubEventImageUrl</code><br/>Link to the first image of the latest event recording, based on the <code>publicAddress</code> attribute used in the ZoneMinder device.</li>
|
||||
<li><code>pubEventStreamUrl</code><br/>Link to the latest event recording, based on the <code>publicAddress</code> attribute used in the ZoneMinder device.</li>
|
||||
<li><code>pubImageUrl</code><br/>Link to the current live image, based on the <code>publicAddress</code> attribute used in the ZoneMinder device.</li>
|
||||
<li><code>pubStreamUrl</code>Link to the live-stream, based on the <code>publicAddress</code> attribute used in the ZoneMinder device.<br/></li>
|
||||
<li><code>streamReplayBuffer</code><br/>Taken from the ZoneMinder configuration. Used for the <code>buffer</code> parameter of stream URLs.</li>
|
||||
<li><code>streamUrl</code><br/>Link to the live-stream, based on the ZM-Host parameter used in the device definition.</li>
|
||||
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
# Ende der Commandref
|
||||
=cut
|
Loading…
x
Reference in New Issue
Block a user