mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-01 19:30:31 +00:00
870773f6c6
git-svn-id: https://svn.fhem.de/fhem/trunk@26695 2b470e98-0d58-463d-a4d8-8e2adae1ed80
5086 lines
227 KiB
Perl
5086 lines
227 KiB
Perl
# $Id$
|
|
########################################################################################################################
|
|
#
|
|
# 73_DoorBird.pm
|
|
# Creates the possibility to access and control the DoorBird IP door station
|
|
#
|
|
# Author : Matthias Deeke
|
|
# e-mail : matthias.deeke(AT)deeke(DOT)eu
|
|
# Fhem Forum : https://forum.fhem.de/index.php/topic,100758
|
|
# Fhem Wiki : https://wiki.fhem.de/wiki/DoorBird
|
|
#
|
|
# 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/>.
|
|
#
|
|
# fhem.cfg: define <devicename> DoorBird <IPv4-address> <User> <Password>
|
|
#
|
|
# Example:
|
|
# define myDoorBird DoorBird 192.168.178.240 Username SecretPW
|
|
#
|
|
########################################################################################################################
|
|
|
|
########################################################################################################################
|
|
# List of open Problems:
|
|
#
|
|
# Problem with error message after startup: "PERL WARNING: Prototype mismatch: sub main::memcmp: none vs ($$;$) at /usr/local/share/perl/5.24.1/Sub/Exporter.pm line 445."
|
|
# This problem has been addressed to GitHub since its based on problems with sub-libary
|
|
#
|
|
#
|
|
########################################################################################################################
|
|
|
|
package main;
|
|
use constant false => 0;
|
|
use constant true => 1;
|
|
use strict;
|
|
use warnings;
|
|
use utf8;
|
|
###START###### Initialize module ##############################################################################START####
|
|
sub DoorBird_Initialize($) {
|
|
my ($hash) = @_;
|
|
|
|
### Try to load perl libraries if installed or write log in case of unavailability. ###
|
|
my @UseLibraries = split(/[\n,\t]/,"
|
|
JSON
|
|
HttpUtils
|
|
Encode
|
|
FHEM::Meta
|
|
Cwd
|
|
MIME::Base64
|
|
Crypt::NaCl::Sodium qw( :utils )
|
|
Crypt::Argon2 qw/argon2i_raw/
|
|
IO::Socket
|
|
IO::String;
|
|
LWP::UserAgent
|
|
Data::Dumper
|
|
File::Spec::Functions ':ALL'
|
|
");
|
|
|
|
foreach my $Library (grep(/\S/, @UseLibraries)) {
|
|
eval "use " . $Library;
|
|
if (length($@) == 0) {
|
|
Log3 undef, 5, "DoorBird - Successfully Installed Perl Module : " . $Library
|
|
}
|
|
else {
|
|
Log3 undef, 2, "DoorBird - Cannot find " . $Library . " in \@INC. Please install the Perl library first. Initialization of 73_DoorBird.pm aborted!";
|
|
return
|
|
}
|
|
}
|
|
|
|
$hash->{STATE} = "Init";
|
|
$hash->{DefFn} = "DoorBird_Define";
|
|
$hash->{UndefFn} = "DoorBird_Undefine";
|
|
$hash->{SetFn} = "DoorBird_Set";
|
|
$hash->{GetFn} = "DoorBird_Get";
|
|
$hash->{AttrFn} = "DoorBird_Attr";
|
|
$hash->{ReadFn} = "DoorBird_Read";
|
|
$hash->{DbLog_splitFn} = "DoorBird_DbLog_splitFn";
|
|
$hash->{FW_detailFn} = "DoorBird_FW_detailFn";
|
|
$hash->{NotifyFn} = "DoorBird_Notify";
|
|
|
|
$hash->{AttrList} = "do_not_notify:1,0 " .
|
|
"header " .
|
|
"PollingTimeout:slider,1,1,20 " .
|
|
"MaxHistory:slider,0,1,50 " .
|
|
"KeepAliveTimeout " .
|
|
"UdpPort:6524,35344 " .
|
|
"ImageFileDir " .
|
|
"ImageFileDirMaxSize " .
|
|
"AudioFileDir " .
|
|
"AudioFileDirMaxSize " .
|
|
"VideoFileDir " .
|
|
"VideoFileDirMaxSize " .
|
|
"VideoFileFormat:mpeg,mpg,mp4,avi,mov,dvd,vob,ogg,ogv,mkv,flv,webm " .
|
|
"VideoDurationDoorbell " .
|
|
"VideoDurationMotion " .
|
|
"VideoDurationKeypad " .
|
|
"HistoryFilePath:1,0 " .
|
|
"EventReset " .
|
|
"SessionIdSec:slider,0,10,600 " .
|
|
"WaitForHistory " .
|
|
"OpsModeList " .
|
|
"disable:1,0 " .
|
|
"loglevel:slider,0,1,5 " .
|
|
$readingFnAttributes;
|
|
return FHEM::Meta::InitMod( __FILE__, $hash );
|
|
}
|
|
####END####### Initialize module ###############################################################################END#####
|
|
|
|
|
|
###START###### Activate module after module has been used via fhem command "define" ##########################START####
|
|
sub DoorBird_Define($$) {
|
|
my ($hash, $def) = @_;
|
|
my @a = split("[ \t][ \t]*", $def);
|
|
my $name = $a[0];
|
|
#$a[1] just contains the "DoorBird" module name and we already know that! :-)
|
|
my $url = $a[2];
|
|
|
|
### Delete all Readings for DoorBird
|
|
readingsDelete($hash, ".*");
|
|
|
|
### Log Entry and state
|
|
Log3 $name, 4, $name. " : DoorBird - Starting to define device " . $name . " with DoorBird module";
|
|
readingsSingleUpdate($hash, "state", "define", 1);
|
|
|
|
### Stop the current timer if one exists errornous
|
|
RemoveInternalTimer($hash);
|
|
Log3 $name, 4, $name. " : DoorBird - InternalTimer has been removed.";
|
|
|
|
|
|
###START### Check whether all variables are available #####################################################START####
|
|
if (int(@a) == 5)
|
|
{
|
|
###START### Check whether IPv4 address is valid
|
|
if ($url =~ m/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(|:([0-9]{1,4}|[0-6][0-5][0-5][0-3][0-5])){1}$/)
|
|
{
|
|
Log3 $name, 4, $name. " : DoorBird - IPv4-address is valid : " . $url;
|
|
}
|
|
else
|
|
{
|
|
return $name .": Error - IPv4 address is not valid \n Please use \"define <devicename> DoorBird <IPv4-address> <Username> <Password>\" instead!\nExamples for <IPv4-address>:\n192.168.178.240\n192.168.178.240:0 to 192.168.178.240:65535";
|
|
}
|
|
####END#### Check whether IPv4 address is valid
|
|
}
|
|
else
|
|
{
|
|
return $name .": DoorBird - Error - Not enough parameter provided." . "\n" . "DoorBird station IPv4 address, Username and Password must be provided" ."\n". "Please use \"define <devicename> DoorBird <IPv4-address> <Username> <Password>\" instead";
|
|
}
|
|
####END#### Check whether all variables are available ######################################################END#####
|
|
|
|
###START### Check whether username and password are already encrypted #####################################START####
|
|
### If the username does not contain the "crypt" prefix, then it is still bareword
|
|
if($a[3] =~ /^((?!crypt:).)*$/ ) {
|
|
# Encrypt bareword username and password
|
|
my $username = DoorBird_credential_encrypt($a[3]);
|
|
my $password = DoorBird_credential_encrypt($a[4]);
|
|
|
|
### Rewrite definition of device to remove bare passwords
|
|
$hash->{DEF} = "$url $username $password";
|
|
|
|
### Write encrypted credentials into hash
|
|
$hash->{helper}{".USER"} = $username;
|
|
$hash->{helper}{".PASSWORD"} = $password;
|
|
|
|
### Write Log entry
|
|
Log3 $name, 3, $name. " : DoorBird - Credentials have been encrypted for further use.";
|
|
}
|
|
### If the username contains the "crypt" prefix, then it is already encrypted
|
|
else {
|
|
### Write encrypted credentials into hash
|
|
$hash->{helper}{".USER"} = $a[3];
|
|
$hash->{helper}{".PASSWORD"} = $a[4];
|
|
}
|
|
####END#### Check whether username and password are already encrypted ######################################END#####
|
|
|
|
###START###### Writing values to global hash ##############################################################START####
|
|
$hash->{NAME} = $name;
|
|
$hash->{RevisonAPI} = "0.26";
|
|
$hash->{helper}{SOX} = "/usr/bin/sox"; #On Windows systems use "C:\Programme\sox\sox.exe"
|
|
$hash->{helper}{URL} = $url;
|
|
$hash->{helper}{PollingTimeout} = AttrVal($name,"PollingTimeout",5);
|
|
$hash->{helper}{KeepAliveTimeout} = AttrVal($name, "KeepAliveTimeout", 30);
|
|
$hash->{helper}{MaxHistory} = AttrVal($name, "MaxHistory", 50);
|
|
$hash->{helper}{HistoryTime} = "????-??-?? ??:??";
|
|
$hash->{helper}{UdpPort} = AttrVal($name, "UdpPort", 6524);
|
|
$hash->{helper}{SessionIdSec} = AttrVal($name, "SessionIdSec", 540);
|
|
$hash->{helper}{ImageFileDir} = AttrVal($name, "ImageFileDir", "");
|
|
$hash->{helper}{ImageFileDirMaxSize} = AttrVal($name, "ImageFileDirMaxSize", 50);
|
|
$hash->{helper}{AudioFileDir} = AttrVal($name, "AudioFileDir", "");
|
|
$hash->{helper}{AudioFileDirMaxSize} = AttrVal($name, "AudioFileDirMaxSize", 50);
|
|
$hash->{helper}{VideoFileDir} = AttrVal($name, "VideoFileDir", "");
|
|
$hash->{helper}{VideoFileDirMaxSize} = AttrVal($name, "VideoFileDirMaxSize", 50);
|
|
$hash->{helper}{VideoFileFormat} = AttrVal($name, "VideoFileFormat","mpeg");
|
|
$hash->{helper}{VideoDurationDoorbell} = AttrVal($name, "VideoDurationDoorbell", 0);
|
|
$hash->{helper}{VideoDurationMotion} = AttrVal($name, "VideoDurationMotion", 0);
|
|
$hash->{helper}{VideoDurationKeypad} = AttrVal($name, "VideoDurationKeypad", 0);
|
|
$hash->{helper}{EventReset} = AttrVal($name, "EventReset", 5);
|
|
$hash->{helper}{WaitForHistory} = AttrVal($name, "WaitForHistory", 7);
|
|
$hash->{helper}{CameraInstalled} = false;
|
|
$hash->{helper}{HistoryFilePath} = 0;
|
|
$hash->{helper}{SessionId} = 0;
|
|
$hash->{helper}{UdpMessageId} = 0;
|
|
$hash->{helper}{UdpMotionId} = 0;
|
|
$hash->{helper}{UdpDoorbellId} = 0;
|
|
$hash->{helper}{UdpKeypadId} = 0;
|
|
@{$hash->{helper}{RelayAdresses}} = (0);
|
|
@{$hash->{helper}{Images}{History}{doorbell}} = ();
|
|
@{$hash->{helper}{Images}{History}{motionsensor}} = ();
|
|
@{$hash->{helper}{OpsModeList}} = ();
|
|
${$hash->{helper}{OpsModeListBackup}}[0] = "Initial-gJ8990Gl";
|
|
$hash->{helper}{Images}{Individual}{Data} = "";
|
|
$hash->{helper}{Images}{Individual}{Timestamp} = "";
|
|
$hash->{helper}{HistoryDownloadActive} = false;
|
|
$hash->{helper}{HistoryDownloadCount} = 0;
|
|
$hash->{reusePort} = AttrVal($name, 'reusePort', defined(&SO_REUSEPORT)?1:0)?1:0;
|
|
####END####### Writing values to global hash ##############################################################END#####
|
|
|
|
|
|
### For Debugging purpose only
|
|
Log3 $name, 5, $name. " : DoorBird - Define H : " . $hash;
|
|
Log3 $name, 5, $name. " : DoorBird - Define D : " . $def;
|
|
Log3 $name, 5, $name. " : DoorBird - Define A : " . @a;
|
|
Log3 $name, 5, $name. " : DoorBird - Define Name : " . $name;
|
|
Log3 $name, 5, $name. " : DoorBird - Define OpsModeList : " . Dumper(@{$hash->{helper}{OpsModeList}});
|
|
Log3 $name, 5, $name. " : DoorBird - Define OpsModeListBackup[0] : " . ${$hash->{helper}{OpsModeListBackup}}[0];
|
|
|
|
### Notify this device after changes on global variables
|
|
$hash->{NOTIFYDEV} = "global,";
|
|
|
|
### Call intitialization of sub if the initialization is done
|
|
DoorBird_InitializeDevice($hash) if ($init_done);
|
|
|
|
return undef;
|
|
}
|
|
####END####### Activate module after module has been used via fhem command "define" ############################END#####
|
|
|
|
|
|
###START###### Handle Notifications received by this module ##################################################START####
|
|
sub DoorBird_Notify($$) {
|
|
my ($hash, $dev) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $devName = $dev->{NAME}; # Device that created the events
|
|
my $events = deviceEvents($dev, 1);
|
|
|
|
### For Debugging purpose only
|
|
Log3 $name, 5, $name. " : DoorBird - DoorBird_Notify devname : " . $devName;
|
|
Log3 $name, 5, $name. " : DoorBird - DoorBird_Notify events : " . Dumper($events);
|
|
|
|
### Return without any further action if the module is disabled
|
|
return "" if(IsDisabled($name));
|
|
|
|
### If the global variables notified an update and matches
|
|
if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events}))
|
|
{
|
|
### For Debugging purpose only
|
|
Log3 $name, 5, $name. " : DoorBird_Notify : fhem system has been initialized or config has been re-read.";
|
|
|
|
### Call initialization
|
|
DoorBird_InitializeDevice($hash);
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
###END######## Handle Notifications received by this module ####################################################END####
|
|
|
|
|
|
##START###### Initialize the device when all attributes are available ########################################START####
|
|
sub DoorBird_InitializeDevice($) {
|
|
my($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
### Initialize Socket connection
|
|
DoorBird_OpenSocketConn($hash);
|
|
|
|
### Initialize Readings
|
|
DoorBird_Info_Request($hash, "");
|
|
DoorBird_Image_Request($hash, "");
|
|
DoorBird_Live_Video($hash, "off");
|
|
|
|
### Initiate the timer for first time
|
|
InternalTimer(gettimeofday()+ $hash->{helper}{KeepAliveTimeout} , "DoorBird_LostConn", $hash, 0);
|
|
InternalTimer(gettimeofday()+ 10, "DoorBird_RenewSessionID", $hash, 0);
|
|
|
|
### For Debugging purpose only
|
|
Log3 $name, 3, $name. " : DoorBird_InitializeDevice : DoorBird has been initialized";
|
|
|
|
return undef;
|
|
}
|
|
###END######## Initialize the device when all attributes are available #########################################END####
|
|
|
|
###START###### To bind unit of value to DbLog entries #########################################################START####
|
|
# sub DoorBird_DbLog_splitFn($$)
|
|
# {
|
|
# return ();
|
|
# }
|
|
####END####### To bind unit of value to DbLog entries ##########################################################END#####
|
|
|
|
|
|
###START###### Deactivate module module after "undefine" command by fhem ######################################START####
|
|
sub DoorBird_Undefine($$) {
|
|
my ($hash, $def) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $url = $hash->{URL};
|
|
|
|
### Stop the internal timer for this module
|
|
RemoveInternalTimer($hash);
|
|
|
|
### Close UDP scanning
|
|
delete $selectlist{$name};
|
|
if (defined($hash->{CD})) {
|
|
$hash->{CD}->close();
|
|
delete $hash->{CD};
|
|
}
|
|
delete $hash->{FD};
|
|
### Add Log entry
|
|
Log3 $name, 3, $name. " - DoorBird has been undefined. The DoorBird unit will no longer polled.";
|
|
|
|
return undef;
|
|
}
|
|
####END####### Deactivate module module after "undefine" command by fhem #######################################END#####
|
|
|
|
|
|
###START###### Handle attributes after changes via fhem GUI ###################################################START####
|
|
sub DoorBird_Attr(@) {
|
|
my @a = @_;
|
|
my $name = $a[1];
|
|
my $hash = $defs{$name};
|
|
|
|
Log3 $name, 5, $name. " : DoorBird_Attr - Subfunction entered.";
|
|
|
|
### Check whether disable attribute has been provided
|
|
if ($a[2] eq "disable") {
|
|
### Check whether device shall be disabled
|
|
if ($a[3] == 1) {
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "disabled", 1);
|
|
|
|
### Stop the current timer
|
|
RemoveInternalTimer($hash);
|
|
Log3 $name, 4, $name. " : DoorBird - InternalTimer has been removed.";
|
|
|
|
### Delete all Readings
|
|
readingsDelete($hash, ".*");
|
|
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "disconnected", 1);
|
|
|
|
Log3 $name, 3, $name. " : DoorBird - Device disabled as per attribute.";
|
|
}
|
|
else {
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "disconnected", 1);
|
|
Log3 $name, 4, $name. " : DoorBird - Device enabled as per attribute.";
|
|
}
|
|
}
|
|
### Check whether UdpPort attribute has been provided
|
|
elsif ($a[2] eq "UdpPort") {
|
|
### Check whether UdpPort is numeric
|
|
if ($a[3] == int($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{UdpPort} = $a[3];
|
|
|
|
### Call initialization
|
|
DoorBird_InitializeDevice($hash);
|
|
}
|
|
}
|
|
### Check whether PollingTimeout attribute has been provided
|
|
elsif ($a[2] eq "PollingTimeout") {
|
|
### Check whether PollingTimeout is numeric
|
|
if (($a[3] == int($a[3])) && ($a[3] > 0)) {
|
|
### Check whether PollingTimeout is positiv and smaller or equal than 10s
|
|
if (($a[3] > 0) && ($a[3] <= 10)) {
|
|
### Save attribute as internal
|
|
$hash->{helper}{PollingTimeout} = $a[3];
|
|
}
|
|
### If PollingTimeout is NOT positiv and smaller or equal than 10s
|
|
else {
|
|
### Return error message to GUI
|
|
}
|
|
}
|
|
### If PollingTimeout is NOT numeric
|
|
else {
|
|
### Do nothing
|
|
}
|
|
}
|
|
### Check whether MaxHistory attribute has been provided
|
|
elsif ($a[2] eq "MaxHistory") {
|
|
### Check whether MaxHistory is numeric
|
|
if ($a[3] == int($a[3])) {
|
|
### Check whether MaxHistory is positiv and smaller or equal than 50
|
|
if (($a[3] >= 0) && ($a[3] <= 50)) {
|
|
### Save attribute as internal
|
|
$hash->{helper}{MaxHistory} = $a[3];
|
|
}
|
|
### If MaxHistory is NOT positiv and smaller or equal than 50
|
|
else {
|
|
### Save attribute as internal
|
|
$hash->{helper}{MaxHistory} = 50;
|
|
}
|
|
}
|
|
### If MaxHistory is NOT numeric
|
|
else {
|
|
### Save attribute as internal
|
|
$hash->{helper}{MaxHistory} = 50;
|
|
}
|
|
}
|
|
### Check whether KeepAliveTimeout attribute has been provided
|
|
elsif ($a[2] eq "KeepAliveTimeout") {
|
|
### Remove Timer for LostConn
|
|
RemoveInternalTimer($hash, "DoorBird_LostConn");
|
|
|
|
### Check whether KeepAliveTimeout is numeric and greater or equal than 10
|
|
if ($a[3] == int($a[3]) && ($a[3] >= 10)) {
|
|
### Save attribute as internal
|
|
$hash->{helper}{KeepAliveTimeout} = $a[3];
|
|
}
|
|
### If KeepAliveTimeout is NOT numeric or smaller than 10
|
|
else {
|
|
### Save attribute as internal
|
|
$hash->{helper}{KeepAliveTimeout} = 30;
|
|
}
|
|
### Initiate the timer for first time
|
|
InternalTimer(gettimeofday()+$hash->{helper}{KeepAliveTimeout}, "DoorBird_LostConn", $hash, 0);
|
|
}
|
|
### Check whether SessionIdSec attribute has been provided
|
|
elsif ($a[2] eq "SessionIdSec") {
|
|
### Remove Timer for LostConn
|
|
RemoveInternalTimer($hash, "DoorBird_RenewSessionID");
|
|
|
|
### If the attribute has not been deleted entirely
|
|
if (defined $a[3]) {
|
|
|
|
### Check whether SessionIdSec is 0 = disabled
|
|
if ($a[3] == int($a[3]) && ($a[3] == 0)) {
|
|
### Save attribute as internal
|
|
$hash->{helper}{SessionIdSec} = 0;
|
|
$hash->{helper}{SessionId} = 0;
|
|
}
|
|
### If KeepAliveTimeout is numeric and greater than 9s
|
|
elsif ($a[3] == int($a[3]) && ($a[3] > 9)) {
|
|
|
|
### Save attribute as internal
|
|
$hash->{helper}{SessionIdSec} = $a[3];
|
|
|
|
### Obtain SessionId
|
|
DoorBird_RenewSessionID($hash);
|
|
|
|
### Re-Initiate the timer
|
|
InternalTimer(gettimeofday()+$hash->{helper}{SessionIdSec}, "DoorBird_RenewSessionID", $hash, 0);
|
|
}
|
|
### If KeepAliveTimeout is NOT numeric or smaller than 10
|
|
else{
|
|
### Save standard interval as internal
|
|
$hash->{helper}{SessionIdSec} = 540;
|
|
|
|
### Obtain SessionId
|
|
DoorBird_RenewSessionID($hash);
|
|
|
|
### Re-Initiate the timer
|
|
InternalTimer(gettimeofday()+$hash->{helper}{SessionIdSec}, "DoorBird_RenewSessionID", $hash, 0);
|
|
}
|
|
}
|
|
### If the attribute has been deleted entirely
|
|
else{
|
|
### Save standard interval as internal
|
|
$hash->{helper}{SessionIdSec} = 540;
|
|
|
|
### Obtain SessionId
|
|
DoorBird_RenewSessionID($hash);
|
|
|
|
### Re-Initiate the timer
|
|
InternalTimer(gettimeofday()+$hash->{helper}{SessionIdSec}, "DoorBird_RenewSessionID", $hash, 0);
|
|
}
|
|
}
|
|
### Check whether ImageFileDir attribute has been provided
|
|
elsif ($a[2] eq "ImageFileDir") {
|
|
### Check whether ImageFileSave is defined
|
|
if (defined($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{ImageFileDir} = $a[3];
|
|
}
|
|
else {
|
|
### Set helper in hash
|
|
$hash->{helper}{ImageFileDir} = "";
|
|
}
|
|
}
|
|
### Check whether ImageFileDirMaxSize attribute has been provided
|
|
elsif ($a[2] eq "ImageFileDirMaxSize") {
|
|
|
|
### If the attribute has not been deleted entirely
|
|
if (defined $a[3]) {
|
|
### Check whether ImageFileDirMaxSize is 0 = disabled
|
|
if ($a[3] == int($a[3]) && ($a[3] <= 50)) {
|
|
### Save standard value
|
|
$hash->{helper}{ImageFileDirMaxSize} = 50;
|
|
}
|
|
### If ImageFileDirMaxSize is numeric and greater than 50
|
|
elsif ($a[3] == int($a[3]) && ($a[3] > 50)) {
|
|
|
|
### Save attribute as internal
|
|
$hash->{helper}{ImageFileDirMaxSize} = $a[3];
|
|
}
|
|
### If KeepAliveTimeout is NOT numeric or smaller than 50
|
|
else{
|
|
### Save standard interval as internal
|
|
$hash->{helper}{ImageFileDirMaxSize} = 50;
|
|
}
|
|
}
|
|
### If the attribute has been deleted entirely
|
|
else {
|
|
### Save standard interval as internal
|
|
$hash->{helper}{ImageFileDirMaxSize} = 50;
|
|
}
|
|
}
|
|
### Check whether AudioFileDir attribute has been provided
|
|
elsif ($a[2] eq "AudioFileDir") {
|
|
### Check whether AudioFileSave is defined
|
|
if (defined($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{AudioFileDir} = $a[3];
|
|
}
|
|
else {
|
|
### Set helper in hash
|
|
$hash->{helper}{AudioFileDir} = "";
|
|
}
|
|
}
|
|
### Check whether AudioFileDirMaxSize attribute has been provided
|
|
elsif ($a[2] eq "AudioFileDirMaxSize") {
|
|
|
|
### If the attribute has not been deleted entirely
|
|
if (defined $a[3]) {
|
|
### Check whether AudioFileDirMaxSize is 0 = disabled
|
|
if ($a[3] == int($a[3]) && ($a[3] <= 50)) {
|
|
### Save standard value
|
|
$hash->{helper}{AudioFileDirMaxSize} = 50;
|
|
}
|
|
### If AudioFileDirMaxSize is numeric and greater than 50
|
|
elsif ($a[3] == int($a[3]) && ($a[3] > 50)) {
|
|
|
|
### Save attribute as internal
|
|
$hash->{helper}{AudioFileDirMaxSize} = $a[3];
|
|
}
|
|
### If KeepAliveTimeout is NOT numeric or smaller than 50
|
|
else{
|
|
### Save standard interval as internal
|
|
$hash->{helper}{AudioFileDirMaxSize} = 50;
|
|
}
|
|
}
|
|
### If the attribute has been deleted entirely
|
|
else {
|
|
### Save standard interval as internal
|
|
$hash->{helper}{AudioFileDirMaxSize} = 50;
|
|
}
|
|
}
|
|
### Check whether VideoFileDir attribute has been provided
|
|
elsif ($a[2] eq "VideoFileDir") {
|
|
### Check whether VideoFileSave is defined
|
|
if (defined($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoFileDir} = $a[3];
|
|
}
|
|
else {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoFileDir} = "";
|
|
}
|
|
}
|
|
### Check whether VideoFileDirMaxSize attribute has been provided
|
|
elsif ($a[2] eq "VideoFileDirMaxSize") {
|
|
|
|
### If the attribute has not been deleted entirely
|
|
if (defined $a[3]) {
|
|
### Check whether VideoFileDirMaxSize is 0 = disabled
|
|
if ($a[3] == int($a[3]) && ($a[3] <= 50)) {
|
|
### Save standard value
|
|
$hash->{helper}{VideoFileDirMaxSize} = 50;
|
|
}
|
|
### If VideoFileDirMaxSize is numeric and greater than 50
|
|
elsif ($a[3] == int($a[3]) && ($a[3] > 50)) {
|
|
|
|
### Save attribute as internal
|
|
$hash->{helper}{VideoFileDirMaxSize} = $a[3];
|
|
}
|
|
### If KeepAliveTimeout is NOT numeric or smaller than 50
|
|
else{
|
|
### Save standard interval as internal
|
|
$hash->{helper}{VideoFileDirMaxSize} = 50;
|
|
}
|
|
}
|
|
### If the attribute has been deleted entirely
|
|
else {
|
|
### Save standard interval as internal
|
|
$hash->{helper}{VideoFileDirMaxSize} = 50;
|
|
}
|
|
}
|
|
### Check whether VideoDurationDoorbell attribute has been provided
|
|
elsif ($a[2] eq "VideoDurationDoorbell") {
|
|
### Check whether VideoDurationDoorbell is defined
|
|
if (defined($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoDurationDoorbell} = $a[3];
|
|
}
|
|
else {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoDurationDoorbell} = "0";
|
|
}
|
|
}
|
|
### Check whether VideoDurationMotion attribute has been provided
|
|
elsif ($a[2] eq "VideoDurationMotion") {
|
|
### Check whether VideoDurationMotion is defined
|
|
if (defined($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoDurationMotion} = $a[3];
|
|
}
|
|
else {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoDurationMotion} = "0";
|
|
}
|
|
}
|
|
### Check whether VideoDurationKeypad attribute has been provided
|
|
elsif ($a[2] eq "VideoDurationKeypad") {
|
|
### Check whether VideoDurationKeypad is defined
|
|
if (defined($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoDurationKeypad} = $a[3];
|
|
}
|
|
else {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoDurationKeypad} = "0";
|
|
}
|
|
}
|
|
### Check whether HistoryFilePath attribute has been provided
|
|
elsif ($a[2] eq "HistoryFilePath") {
|
|
### Check whether HistoryFilePath is defined
|
|
if (defined($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{HistoryFilePath} = $a[3];
|
|
|
|
if ($a[3] == true) {
|
|
### Update the history list for images and videos
|
|
DoorBird_History_List($hash);
|
|
}
|
|
else {
|
|
### Delete all reading entries to files
|
|
fhem("deleteReading " . $name . " HistoryFilePath.*");
|
|
}
|
|
}
|
|
else {
|
|
### Set helper in hash
|
|
$hash->{helper}{HistoryFilePath} = "0";
|
|
|
|
### Delete all reading entries to files
|
|
fhem("deleteReading " . $name . " HistoryFilePath.*");
|
|
}
|
|
}
|
|
### Check whether VideoFileFormat attribute has been provided
|
|
elsif ($a[2] eq "VideoFileFormat") {
|
|
### Check whether VideoFileFormat is defined
|
|
if (defined($a[3])) {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoFileFormat} = $a[3];
|
|
}
|
|
else {
|
|
### Set helper in hash
|
|
$hash->{helper}{VideoFileFormat} = "mpeg";
|
|
}
|
|
}
|
|
### Check whether EventReset attribute has been provided
|
|
elsif ($a[2] eq "EventReset") {
|
|
### Remove Timer for Event Reset
|
|
RemoveInternalTimer($hash, "DoorBird_EventResetMotion");
|
|
RemoveInternalTimer($hash, "DoorBird_EventResetDoorbell");
|
|
#RemoveInternalTimer($hash, "DoorBird_EventResetKeypad");
|
|
|
|
### Check whether EventReset is numeric and greater than 0
|
|
if ($a[3] == int($a[3]) && ($a[3] > 0)) {
|
|
### Save attribute as internal
|
|
$hash->{helper}{EventReset} = $a[3];
|
|
}
|
|
### If KeepAliveTimeout is NOT numeric or 0
|
|
else {
|
|
### Save attribute as internal
|
|
$hash->{helper}{EventReset} = 5;
|
|
}
|
|
}
|
|
### Check whether WaitForHistory attribute has been provided
|
|
elsif ($a[2] eq "WaitForHistory") {
|
|
### Check whether WaitForHistory is numeric and greater than 5
|
|
if ($a[3] == int($a[3]) && ($a[3] > 5)) {
|
|
### Save attribute as internal
|
|
$hash->{helper}{WaitForHistory} = $a[3];
|
|
}
|
|
### If KeepAliveTimeout is NOT numeric or <5
|
|
else {
|
|
### Save attribute as internal
|
|
$hash->{helper}{WaitForHistory} = 5;
|
|
}
|
|
}
|
|
### Check whether OpsModeList attribute has been provided
|
|
elsif ($a[2] eq "OpsModeList") {
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Attr - OpsModeList entered";
|
|
Log3 $name, 5, $name. " : DoorBird_Attr - {OpsModeListBackup}}[0] : " . ${$hash->{helper}{OpsModeListBackup}}[0];
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Attr - OpsModeListBackup is not initial ";
|
|
|
|
### If the attribute has not been deleted entirely or is empty
|
|
if (defined $a[3]) {
|
|
### Save OpsList and empty string as internal
|
|
@{$hash->{helper}{OpsModeList}} = split(/ /, $a[3]);
|
|
|
|
### Update depending Readings
|
|
DoorBird_OpsModeUpdate($hash);
|
|
}
|
|
### If the attribute has been deleted entirely or is empty
|
|
else {
|
|
### Save OpsList and empty string as internal
|
|
@{$hash->{helper}{OpsModeList}} = "";
|
|
|
|
### Update depending Readings
|
|
DoorBird_OpsModeUpdate($hash);
|
|
}
|
|
}
|
|
### If no attributes of the above known ones have been selected
|
|
else {
|
|
# Do nothing
|
|
}
|
|
return undef;
|
|
}
|
|
####END####### Handle attributes after changes via fhem GUI ####################################################END#####
|
|
|
|
###START###### Obtain value after "get" command by fhem #######################################################START####
|
|
sub DoorBird_Get($@) {
|
|
my ( $hash, @a ) = @_;
|
|
|
|
### If not enough arguments have been provided
|
|
if ( @a < 2 )
|
|
{
|
|
return "\"get DoorBird\" needs at least one argument";
|
|
}
|
|
|
|
my $name = shift @a;
|
|
my $command = shift @a;
|
|
my $option = shift @a;
|
|
my $optionString;
|
|
|
|
### Create String to avoid perl warning if option is empty
|
|
if (defined $option) {
|
|
$optionString = $option;
|
|
}
|
|
else {
|
|
$optionString = " ";
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Get - name : " . $name;
|
|
Log3 $name, 5, $name. " : DoorBird_Get - command : " . $command;
|
|
Log3 $name, 5, $name. " : DoorBird_Get - option : " . $optionString;
|
|
|
|
### Define "get" menu
|
|
my $usage = "Unknown argument, choose one of ";
|
|
$usage .= "Info_Request:noArg List_Favorites:noArg List_Schedules:noArg ";
|
|
|
|
### If DoorBird has a Camera installed
|
|
if ($hash->{helper}{CameraInstalled} == true) {
|
|
$usage .= "Image_Request:noArg History_Request:noArg Video_Request "
|
|
}
|
|
### If DoorBird has NO Camera installed
|
|
else {
|
|
# Do not add anything
|
|
}
|
|
### Return values
|
|
return $usage if $command eq '?';
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Get - usage : " . $usage;
|
|
|
|
### INFO REQUEST
|
|
if ($command eq "Info_Request") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Info_Request($hash, $option);
|
|
}
|
|
### IMAGE REQUEST
|
|
elsif ($command eq "Image_Request") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Image_Request($hash, $option);
|
|
}
|
|
### VIDEO REQUEST
|
|
elsif ($command eq "Video_Request") {
|
|
my $VideoDuration = 10;
|
|
### If the duration has been given use it. Otherwise use 10s
|
|
if ( $optionString == int($optionString) and $optionString eq int($optionString) and $optionString > 0 ) {
|
|
$VideoDuration = $optionString;
|
|
}
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Video_Request($hash, $VideoDuration, "manual", time());
|
|
}
|
|
### HISTORY IMAGE REQUEST
|
|
elsif ($command eq "History_Request") {
|
|
if ($hash->{helper}{HistoryDownloadActive} == false) {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_History_Request($hash, $option);
|
|
}
|
|
else {
|
|
return "History download already in progress.\nPlease wait and try again later."
|
|
}
|
|
}
|
|
### LIST FAVORITES
|
|
elsif ($command eq "List_Favorites") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_List_Favorites($hash, $option);
|
|
}
|
|
### LIST SCHEDULES
|
|
elsif ($command eq "List_Schedules") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_List_Schedules($hash, $option);
|
|
}
|
|
### If none of the known options has been chosen
|
|
else {
|
|
### Do nothing
|
|
return
|
|
}
|
|
### MONITOR REQUEST
|
|
### To be implemented via UDP
|
|
}
|
|
####END####### Obtain value after "get" command by fhem ########################################################END#####
|
|
|
|
|
|
###START###### Manipulate service after "set" command by fhem #################################################START####
|
|
sub DoorBird_Set($@) {
|
|
my ( $hash, @a ) = @_;
|
|
|
|
### If not enough arguments have been provided
|
|
if ( @a < 2 )
|
|
{
|
|
return "\"get DoorBird\" needs at least one argument";
|
|
}
|
|
|
|
my $name = shift @a;
|
|
my $command = shift @a;
|
|
my $option = shift @a;
|
|
my $ErrorMessage = "";
|
|
my $optionString;
|
|
my $AudioFileDir = $hash->{helper}{AudioFileDir};
|
|
my @RelayAdresses = @{$hash->{helper}{RelayAdresses}};
|
|
my @OpsModeList = @{$hash->{helper}{OpsModeList}};
|
|
|
|
### Create String to avoid perl warning if option is empty
|
|
if (defined $option) {
|
|
$optionString = $option;
|
|
}
|
|
else {
|
|
$optionString = " ";
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Set _______________________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_Set - name : " . $name;
|
|
Log3 $name, 5, $name. " : DoorBird_Set - command : " . $command;
|
|
Log3 $name, 5, $name. " : DoorBird_Set - option : " . $optionString;
|
|
Log3 $name, 5, $name. " : DoorBird_Set - RelayAdresses : " . join(",", @RelayAdresses);
|
|
Log3 $name, 5, $name. " : DoorBird_Set - OpsModeList : " . join(",", @OpsModeList);
|
|
|
|
### Define "set" menu
|
|
my $usage = "Unknown argument, choose one of";
|
|
#$usage .= " Test";
|
|
$usage .= " Open_Door:" . join(",", @RelayAdresses) . " OpsMode:" . join(",", @OpsModeList) . " Restart:noArg Transmit_Audio";
|
|
$usage .= " Receive_Audio";
|
|
|
|
### If the OpsModeList is not empty
|
|
if ((defined(${$hash->{helper}{OpsModeList}}[0])) && (${$hash->{helper}{OpsModeList}}[0] ne "")) {
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Set - The OpsModeList is empty";
|
|
Log3 $name, 5, $name. " : DoorBird_Set - OpsModeList : " . Dumper(@{$hash->{helper}{OpsModeList}});
|
|
|
|
### For each item in the list of possible Operation Modes
|
|
foreach (@OpsModeList) {
|
|
|
|
### Set Prefix for ReadingsName
|
|
my $OpsModeReadingPrefix = "OpsMode" . $_;
|
|
|
|
### For each DoorBirdEvent, create setlist for relays to be activated in case of event
|
|
$usage .= " " . $OpsModeReadingPrefix . "DoorbellRelay:" . "Off," . join(",", @RelayAdresses);
|
|
$usage .= " " . $OpsModeReadingPrefix . "MotionRelay:" . "Off," . join(",", @RelayAdresses);
|
|
#$usage .= " " . $OpsModeReadingPrefix . "KeypadRelay:" . "Off," . join(",", @RelayAdresses);
|
|
|
|
### If the Attribute for the directories of the audiofiles have bene provided
|
|
if ($AudioFileDir ne "") {
|
|
|
|
### Get current working directory
|
|
my $cwd = getcwd();
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Set - working directory : " . $cwd;
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Set - file system format : LINUX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($AudioFileDir =~ /^\//) {
|
|
$AudioFileDir = $AudioFileDir;
|
|
}
|
|
else {
|
|
$AudioFileDir = $cwd . "/" . $AudioFileDir;
|
|
}
|
|
|
|
### Remove last / of directory if exists
|
|
$AudioFileDir =~ s/\/\z//;
|
|
|
|
}
|
|
### If the path is given as Windows file system format
|
|
elsif ($cwd =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Set - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($AudioFileDir != /^.:\//) {
|
|
$AudioFileDir = $cwd . $AudioFileDir;
|
|
}
|
|
else {
|
|
$AudioFileDir = $AudioFileDir;
|
|
}
|
|
|
|
### Remove last \ of directory if exists
|
|
$AudioFileDir =~ s/\\\z//;
|
|
|
|
}
|
|
### If nothing matches above
|
|
else {
|
|
### Set directory to nothing
|
|
$AudioFileDir = "";
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Set - AudioFileDir : " . $AudioFileDir;
|
|
|
|
### Get content of subdirectory and eliminate the root directories "." and ".."
|
|
my @AudioFileList;
|
|
eval {
|
|
opendir(ReadOut,$AudioFileDir) or die "Could not open '$AudioFileDir' for reading";
|
|
@AudioFileList = grep(/^([^.]+)./, readdir(ReadOut));
|
|
close ReadOut;
|
|
};
|
|
### If error message appered
|
|
if ( $@ ) {
|
|
$ErrorMessage = $@;
|
|
}
|
|
### If no error message appeared and therefore directory exists
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Set - AudioFileList : " . join(",", @AudioFileList);
|
|
|
|
### For each DoorBirdEvent, create setlist for the file path for the audio messages
|
|
$usage .= " " . $OpsModeReadingPrefix . "DoorbellAudio:Off,". join(",", @AudioFileList);
|
|
$usage .= " " . $OpsModeReadingPrefix . "MotionAudio:Off,". join(",", @AudioFileList);
|
|
#$usage .= " " . $OpsModeReadingPrefix . "KeypadAudio:Off," . join(",", @AudioFileList);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
### If DoorBird has a Camera installed
|
|
if ($hash->{helper}{CameraInstalled} == true) {
|
|
### Create Selection List for camera
|
|
$usage .= " Live_Video:on,off Light_On:noArg Live_Audio:on,off ";
|
|
}
|
|
### If DoorBird has NO Camera installed
|
|
else {
|
|
# Do not add anything
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name . " : DoorBord_Set - " . $ErrorMessage;
|
|
Log3 $name, 2, $name . " : DoorBord_Set - Could not open directory for audiofiles. See commandref for attribute \"AudioFileDir\"." if $ErrorMessage ne "";
|
|
Log3 $name, 5, $name . " : DoorBird_Set - usage : " . $usage;
|
|
|
|
### Return values
|
|
return $usage if $command eq '?';
|
|
|
|
######### Section for response on set-command ##########################################################
|
|
|
|
### LIVE VIDEO REQUEST
|
|
if ($command eq "Live_Video") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Live_Video($hash, $option)
|
|
}
|
|
### OPEN DOOR
|
|
elsif ($command eq "Open_Door") {
|
|
### Call Subroutine and hand back return value
|
|
$hash->{helper}{OpenRelay} = $option;
|
|
return DoorBird_Open_Door($hash);
|
|
}
|
|
### LIGHT ON
|
|
elsif ($command eq "Light_On") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Light_On($hash, $option)
|
|
}
|
|
### RESTART
|
|
elsif ($command eq "Restart") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Restart($hash, $option)
|
|
}
|
|
### LIVE AUDIO RECEIVE
|
|
elsif ($command eq "Live_Audio") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Live_Audio($hash, $option)
|
|
}
|
|
### AUDIO RECEIVE
|
|
elsif ($command eq "Receive_Audio") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Receive_Audio($hash, $option)
|
|
}
|
|
### AUDIO TRANSMIT
|
|
elsif ($command eq "Transmit_Audio") {
|
|
### Call Subroutine and hand back return value
|
|
return DoorBird_Transmit_Audio($hash, $option)
|
|
}
|
|
### TEST
|
|
elsif ($command eq "Test") {
|
|
DoorBird_History_List($hash);
|
|
}
|
|
### ADD OR CHANGE FAVORITE
|
|
### DELETE FAVORITE
|
|
### ADD OR UPDATE SCHEDULE ENTRY
|
|
### DELETE SCHEDULE ENTRY
|
|
### If none of the above have been selected
|
|
else {
|
|
### Update Reading
|
|
readingsSingleUpdate($hash, $command, $option, 1);
|
|
|
|
### Save new Readings in stat file
|
|
WriteStatefile();
|
|
return
|
|
}
|
|
}
|
|
####END####### Manipulate service after "Set" command by fhem ##################################################END#####
|
|
|
|
###START###### Update Readings and variables after update of Operation Mode ###################################START####
|
|
sub DoorBird_OpsModeUpdate($) {
|
|
my ($hash) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my @OpsModeList = @{$hash->{helper}{OpsModeList}};
|
|
my $OpsModeActive = ReadingsVal($name, "OpsMode", "");
|
|
my $AudioFileDir = $hash->{helper}{AudioFileDir};
|
|
|
|
### Extract all names of Readings which start with "OpsMode"
|
|
my @OpsModeReadings = grep(/OpsMode/, keys(%{$hash->{READINGS}}));
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate ____________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - OpsModeList : " . Dumper(@{$hash->{helper}{OpsModeList}});
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - OpsModeListBackup : " . Dumper(@{$hash->{helper}{OpsModeListBackup}});
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - Size of OpsModeList : " . @OpsModeList;
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - OpsModeActive : " . $OpsModeActive;
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - AudioFileDir : " . $AudioFileDir;
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - Readings Current : " . Dumper(@OpsModeReadings);
|
|
|
|
### If the OpsModeList has been changed
|
|
if (join(",", @{$hash->{helper}{OpsModeListBackup}}) ne join(",", @{$hash->{helper}{OpsModeList}})) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - The OpsModeList is different from the Backup!";
|
|
|
|
### If the OpsModeList has not been deleted (is not empty) and is not in initial state
|
|
if ((${$hash->{helper}{OpsModeList}}[0] ne "") && (${$hash->{helper}{OpsModeListBackup}}[0] ne "Initial-gJ8990Gl")) {
|
|
|
|
### Save new list as backup
|
|
@{$hash->{helper}{OpsModeListBackup}} = @{$hash->{helper}{OpsModeList}};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - The OpsModeList is filled but not in initial state!";
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - OpsModeList : " . Dumper(@{$hash->{helper}{OpsModeList}});
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - Readings Old : " . Dumper(@OpsModeReadings);
|
|
|
|
### Delete all Readings which start with "OpsMode"
|
|
foreach (@OpsModeReadings) {
|
|
### Delete all depending Readings
|
|
readingsDelete($hash, $_);
|
|
}
|
|
|
|
### Update Reading for the active Operation Mode with the first item of the list
|
|
readingsSingleUpdate($hash, "OpsMode", ${$hash->{helper}{OpsModeList}}[0], 1);
|
|
|
|
### For each item in the list of possible Operation Modes
|
|
foreach (@OpsModeList) {
|
|
|
|
### Set Prefix for ReadingsName
|
|
my $OpsModeReadingPrefix = "OpsMode" . $_;
|
|
|
|
### For each DoorBirdEvent, create Reading for the file path for the audio messages
|
|
readingsSingleUpdate($hash, $OpsModeReadingPrefix . "DoorbellAudio", "", 1);
|
|
readingsSingleUpdate($hash, $OpsModeReadingPrefix . "MotionAudio", "", 1);
|
|
#readingsSingleUpdate($hash, $OpsModeReadingPrefix . "KeypadAudio", "", 1);
|
|
|
|
### For each DoorBirdEvent, create Reading for relays to be activated in case of event
|
|
readingsSingleUpdate($hash, $OpsModeReadingPrefix . "DoorbellRelay", "", 1);
|
|
readingsSingleUpdate($hash, $OpsModeReadingPrefix . "MotionRelay", "", 1);
|
|
#readingsSingleUpdate($hash, $OpsModeReadingPrefix . "KeypadRelay", "", 1);
|
|
}
|
|
### Save new Readings in stat file
|
|
WriteStatefile();
|
|
|
|
### Log Entry for debugging purposes
|
|
my @OpsModeReadingsNew = grep(/OpsMode/, keys(%{$hash->{READINGS}}));
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - Readings New : " . Dumper(@OpsModeReadingsNew);
|
|
|
|
}
|
|
### If the OpsModeList is empty (is empty) and is not in initial state
|
|
elsif ((${$hash->{helper}{OpsModeList}}[0] eq "") && (${$hash->{helper}{OpsModeListBackup}}[0] ne "Initial-gJ8990Gl")) {
|
|
|
|
### Save new list as backup
|
|
@{$hash->{helper}{OpsModeListBackup}} = @{$hash->{helper}{OpsModeList}};
|
|
|
|
### Extract all names of Readings which start with "OpsMode"
|
|
my @OpsModeReadings = grep(/OpsMode/, keys(%{$hash->{READINGS}}));
|
|
|
|
### Save new list as backup
|
|
@{$hash->{helper}{OpsModeListBackup}} = @{$hash->{helper}{OpsModeList}};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - The OpsModeList is empty but not in initial state!";
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - OpsModeList : " . Dumper(@{$hash->{helper}{OpsModeList}});
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - OpsModeListBackup : " . Dumper(@{$hash->{helper}{OpsModeListBackup}});
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - Readings to be deleted : " . Dumper(@OpsModeReadings);
|
|
|
|
### Delete all Readings which start with "OpsMode"
|
|
foreach (@OpsModeReadings) {
|
|
### Delete all depending Readings
|
|
readingsDelete($hash, $_);
|
|
}
|
|
}
|
|
### If the OpsModeList has not been deleted (is not empty) and is in initial state
|
|
elsif ((${$hash->{helper}{OpsModeList}}[0] ne "") && (${$hash->{helper}{OpsModeListBackup}}[0] eq "Initial-gJ8990Gl")) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - The OpsModeList is NOT empty and in initial state!";
|
|
|
|
### Save new list as backup
|
|
@{$hash->{helper}{OpsModeListBackup}} = @{$hash->{helper}{OpsModeList}};
|
|
}
|
|
### If the OpsModeList has been deleted (is empty) and is in initial state
|
|
elsif ((${$hash->{helper}{OpsModeList}}[0] eq "") && (${$hash->{helper}{OpsModeListBackup}}[0] eq "Initial-gJ8990Gl")) {
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - The OpsModeList is empty and in initial state!";
|
|
|
|
### Save new list as backup
|
|
@{$hash->{helper}{OpsModeListBackup}} = @{$hash->{helper}{OpsModeList}};
|
|
}
|
|
### If the OpsModeList is in unknown state
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - The OpsModeList is in unknown state!";
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - OpsModeList : " . Dumper(@{$hash->{helper}{OpsModeList}});
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - OpsModeListBackup Old : " . Dumper(@{$hash->{helper}{OpsModeListBackup}});
|
|
|
|
### Save new list as backup
|
|
@{$hash->{helper}{OpsModeListBackup}} = @{$hash->{helper}{OpsModeList}};
|
|
|
|
Log3 $name, 3, $name. " : DoorBird_OpsModeUpdate - OpsModeListBackup New : " . Dumper(@{$hash->{helper}{OpsModeListBackup}});
|
|
}
|
|
}
|
|
### If the OpsModeList has not been changed
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeUpdate - The OpsModeList has not been changed.";
|
|
}
|
|
}
|
|
####END####### Update Readings and variables after update of Operation Mode ####################################END#####
|
|
|
|
###START###### Execution of automatic events depending on operation mode ######################################START####
|
|
sub DoorBird_OpsModeExecute($$) {
|
|
my ($hash, $OpsModeEvent) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $AudioFileDir = $hash->{helper}{AudioFileDir};
|
|
my $Sox = $hash->{helper}{SOX};
|
|
my @OpsModeList = @{$hash->{helper}{OpsModeList}};
|
|
my $OpsModeActive = ReadingsVal($name, "OpsMode", "");
|
|
my $OpsModeReadingPrefix = "OpsMode" . $OpsModeActive;
|
|
my $ReadingNameRelay;
|
|
my $ReadingNameAudio;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute ___________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - OpsModeList : " . Dumper(@OpsModeList);
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - OpsModeActive : " . $OpsModeActive;
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - AudioFileDir : " . $AudioFileDir;
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - OpsModeEvent : " . $OpsModeEvent;
|
|
|
|
### Get current working directory
|
|
my $cwd = getcwd();
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - working directory : " . $cwd;
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - file system format : LINUX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($AudioFileDir =~ /^\//) {
|
|
$AudioFileDir = $AudioFileDir;
|
|
}
|
|
else {
|
|
$AudioFileDir = $cwd . "/" . $AudioFileDir;
|
|
}
|
|
|
|
### Remove last / of directory if exists
|
|
$AudioFileDir =~ s/\/\z//;
|
|
|
|
### Add last / for definitiv
|
|
$AudioFileDir .= "/";
|
|
}
|
|
### If the path is given as Windows file system format
|
|
elsif ($cwd =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($AudioFileDir != /^.:\//) {
|
|
$AudioFileDir = $cwd . $AudioFileDir;
|
|
}
|
|
else {
|
|
$AudioFileDir = $AudioFileDir;
|
|
}
|
|
|
|
### Remove last \ of directory if exists
|
|
$AudioFileDir =~ s/\\\z//;
|
|
|
|
### Add last \ for definitiv
|
|
$AudioFileDir .= "\\";
|
|
}
|
|
### If nothing matches above
|
|
else {
|
|
### Set directory to nothing
|
|
$AudioFileDir = "";
|
|
}
|
|
|
|
### If the event has been triggered by a doorbell event
|
|
if ($OpsModeEvent =~ m/doorbell/) {
|
|
### Construct name of reading for the current actions
|
|
$ReadingNameRelay = $OpsModeReadingPrefix . "DoorbellRelay";
|
|
$ReadingNameAudio = $OpsModeReadingPrefix . "DoorbellAudio";
|
|
}
|
|
### If the event has been triggered by a motion event
|
|
elsif ($OpsModeEvent =~ m/motion/) {
|
|
### Construct name of reading for the current actions
|
|
$ReadingNameRelay = $OpsModeReadingPrefix . "MotionRelay";
|
|
$ReadingNameAudio = $OpsModeReadingPrefix . "MotionAudio";
|
|
}
|
|
### If the event has been triggered by a keypad event
|
|
elsif ($OpsModeEvent =~ m/keypad/) {
|
|
### Construct name of reading for the current actions
|
|
$ReadingNameRelay = $OpsModeReadingPrefix . "KeypadRelay";
|
|
$ReadingNameAudio = $OpsModeReadingPrefix . "KeypadAudio";
|
|
}
|
|
### If none of the nown events has been triggering this subroutine
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_OpsModeExecute - Unknown OpsModeEvent has been triggered. Ignoring.";
|
|
|
|
### Do nothing
|
|
}
|
|
|
|
### Get Values of Readings
|
|
my $ReadingValueRelay = ReadingsVal($name, "$ReadingNameRelay","");
|
|
my $ReadingValueAudio = ReadingsVal($name, "$ReadingNameAudio","");
|
|
|
|
### Create full path to audio file
|
|
my $AudioFilePath = $AudioFileDir . $ReadingValueAudio;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - AudioFilePath : " . $AudioFilePath;
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - ReadingValueAudio : " . $ReadingValueAudio;
|
|
|
|
### Create Sox - command
|
|
my $SoxCmd = $Sox . " " . $AudioFilePath . " -n stat stats";
|
|
|
|
### Convert file
|
|
my $AudioLength;
|
|
|
|
### If the value of the Readings for audiofile is not empty or "Off"
|
|
if ($ReadingValueAudio ne "" && $ReadingValueAudio ne "Off") {
|
|
|
|
### Get FileInfo and extract the length of mediafile in seconds
|
|
my @FileInfo = qx($SoxCmd 2>&1);
|
|
$AudioLength = $FileInfo[1];
|
|
$AudioLength =~ s/Length \(seconds\)\://;
|
|
$AudioLength =~ s/\s+//g;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpsModeExecute - AudioLength : " . $AudioLength;
|
|
|
|
|
|
### Transmit Audiofile
|
|
DoorBird_Transmit_Audio($hash, $AudioFilePath);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 4, $name. " : DoorBird_OpsModeExecute - Audiofile transmitted : ". $AudioFilePath;
|
|
|
|
}
|
|
### If the value of the Readings for relay ID is not empty of "Off"
|
|
if ($ReadingValueRelay ne "" && $ReadingValueRelay ne "Off") {
|
|
|
|
### Execute Relay (=Open Door)
|
|
$hash->{helper}{OpenRelay} = $ReadingValueRelay;
|
|
InternalTimer(gettimeofday()+ $AudioLength, "DoorBird_Open_Door", $hash, 0);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 4, $name. " : DoorBird_OpsModeExecute - Relay triggered : ". $ReadingValueRelay;
|
|
}
|
|
}
|
|
####END####### Execution of automatic events depending on operation mode #######################################END#####
|
|
|
|
###START###### After return of UDP message ####################################################################START####
|
|
sub DoorBird_Read($) {
|
|
my ($hash) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $UdpMessageIdLast = $hash->{helper}{UdpMessageId};
|
|
my $UdpMotionIdLast = $hash->{helper}{UdpMotionId};
|
|
my $UdpDoorbellIdLast = $hash->{helper}{UdpDoorbellId};
|
|
my $UdpKeypadIdLast = $hash->{helper}{UdpKeypadId};
|
|
my $Username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $Password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
my $PollingTimeout = $hash->{helper}{PollingTimeout};
|
|
my $url = $hash->{helper}{URL};
|
|
my $Method = "GET";
|
|
my $Header = "Accept: application/json";
|
|
my $UrlPostfix;
|
|
my $CommandURL;
|
|
my $ReadingEvent;
|
|
my $ReadingEventContent;
|
|
my $err;
|
|
my $data;
|
|
my $buf;
|
|
my $flags;
|
|
|
|
### Get sending Peerhost
|
|
my $PeerHost = $hash->{CD}->peerhost;
|
|
|
|
### Get and unpack UDP Datagramm
|
|
$hash->{CD}->recv($buf, 1024, $flags);
|
|
|
|
### Unpack Hex-Package
|
|
$data = bin2hex($buf);
|
|
|
|
### Remove Newlines for better log entries
|
|
$buf =~ s/\n+\z//;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read _____________________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client said PeerHost : " . $PeerHost if defined($PeerHost);
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client said buf : " . $buf if defined($buf);
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client said flags : " . $flags if defined($flags);
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client said data : " . $data if defined($data);
|
|
|
|
### If the UDP datagramm comes from the defined DoorBird
|
|
if ((defined($PeerHost)) && ($PeerHost eq $url)) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP datagram transmitted by valid PeerHost.";
|
|
|
|
### Extract message ID
|
|
my $UdpMessageIdCurrent = $buf;
|
|
$UdpMessageIdCurrent =~ s/:.*//;
|
|
|
|
### If the first part is only numbers and therefore is the message Id of the KeepAlive datagramm
|
|
if ($UdpMessageIdCurrent =~ /^\d+$/) {
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UdpMessage is : Still Alive Message";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UdpMessageIdLast : " . $UdpMessageIdLast;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UdpMessageIdCurrent : " . $UdpMessageIdCurrent;
|
|
|
|
### If the MessageID is integer type has not yet appeared yet
|
|
if ((int($UdpMessageIdCurrent) == $UdpMessageIdCurrent) && ($UdpMessageIdLast != $UdpMessageIdCurrent)) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP datagram transmitted is new - Working on it.";
|
|
|
|
### Remove timer for LostConn
|
|
RemoveInternalTimer($hash, "DoorBird_LostConn");
|
|
|
|
### If Reading for state is not already "connected"
|
|
if (ReadingsVal($name, "state", "") ne "connected") {
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "connected", 1);
|
|
|
|
### Update Reading
|
|
readingsSingleUpdate($hash, "ContactLostSince", "", 1);
|
|
}
|
|
|
|
### Initiate the timer for lost connection handling
|
|
InternalTimer(gettimeofday()+ $hash->{helper}{KeepAliveTimeout}, "DoorBird_LostConn", $hash, 0);
|
|
|
|
### Store Current UdpMessageId in hash
|
|
$hash->{helper}{UdpMessageId} = $UdpMessageIdCurrent;
|
|
}
|
|
### If the UDP datagram is already known
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP datagram transmitted is NOT new - Ignoring it.";
|
|
}
|
|
}
|
|
### If the UDP message is an event message by comparing the first 6 hex-values ignore case sensitivity
|
|
elsif ($data =~ /^deadbe/i) {
|
|
### Decrypt username and password
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
|
|
### Split up in accordance to DoorBird API description in hex values
|
|
my $IDENT = substr($data, 0, 6);
|
|
my $VERSION = substr($data, 6, 2);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UdpMessage is : Event Message";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - version of encryption used : " . $VERSION;
|
|
|
|
### If the version 1 of encryption in accordance to the DoorBird API is used
|
|
if (hex($VERSION) == 1){
|
|
### Split up in hex values in accordance to DoorBird API description for encryption version 1
|
|
my $OPSLIMIT = substr($data, 8, 8);
|
|
my $MEMLIMIT = substr($data, 16, 8);
|
|
my $SALT = substr($data, 24, 32);
|
|
my $NONCE = substr($data, 56, 16);
|
|
my $CIPHERTEXT = substr($data, 72, 68);
|
|
my $FiveCharPw = substr($password, 0, 5);
|
|
|
|
### Generate user friendly hex-string for data
|
|
my $HexFriendlyData;
|
|
for (my $i=0; $i < (length($data)/2); $i++) {
|
|
$HexFriendlyData .= "0x" . substr($data, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Generate user friendly hex-string for Ident
|
|
my $HexFriendlyIdent;
|
|
for (my $i=0; $i < (length($IDENT)/2); $i++) {
|
|
$HexFriendlyIdent .= "0x" . substr($IDENT, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Generate user friendly hex-string for Version
|
|
my $HexFriendlyVersion;
|
|
for (my $i=0; $i < (length($VERSION)/2); $i++) {
|
|
$HexFriendlyVersion .= "0x" . substr($VERSION, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Generate user friendly hex-string for OpsLimit
|
|
my $HexFriendlyOpsLimit;
|
|
for (my $i=0; $i < (length($OPSLIMIT)/2); $i++) {
|
|
$HexFriendlyOpsLimit .= "0x" . substr($OPSLIMIT, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Generate user friendly hex-string for MemLimit
|
|
my $HexFriendlyMemLimit;
|
|
for (my $i=0; $i < (length($MEMLIMIT)/2); $i++) {
|
|
$HexFriendlyMemLimit .= "0x" . substr($MEMLIMIT, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Generate user friendly hex-string for Salt
|
|
my $HexFriendlySalt;
|
|
for (my $i=0; $i < (length($SALT)/2); $i++) {
|
|
$HexFriendlySalt .= "0x" . substr($SALT, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Generate user friendly hex-string for Nonce
|
|
my $HexFriendlyNonce;
|
|
for (my $i=0; $i < (length($NONCE)/2); $i++) {
|
|
$HexFriendlyNonce .= "0x" . substr($NONCE, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Generate user friendly hex-string for CipherText
|
|
my $HexFriendlyCipherText;
|
|
for (my $i=0; $i < (length($CIPHERTEXT)/2); $i++) {
|
|
$HexFriendlyCipherText .= "0x" . substr($CIPHERTEXT, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read ------------------------------ Encryption Version 1 in accordance to DoorBird API has been used ------------------------";
|
|
#Log3 $name, 5, $name. " : DoorBird_Read - UDP Client Udp hex : " . $HexFriendlyData;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client Ident hex : " . $HexFriendlyIdent;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client Version hex : " . $HexFriendlyVersion;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client OpsLimit hex : " . $HexFriendlyOpsLimit;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client MemLimit hex : " . $HexFriendlyMemLimit;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client Salt hex : " . $HexFriendlySalt;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client Nonce hex : " . $HexFriendlyNonce;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Client Cipher hex : " . $HexFriendlyCipherText;
|
|
|
|
### Convert in accordance to API 0.24 description
|
|
$IDENT = hex($IDENT);
|
|
$VERSION = hex($VERSION);
|
|
$OPSLIMIT = hex($OPSLIMIT);
|
|
$MEMLIMIT = hex($MEMLIMIT);
|
|
$SALT = pack("H*", $SALT);
|
|
$NONCE = pack("H*", $NONCE);
|
|
$CIPHERTEXT = pack("H*", $CIPHERTEXT);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read -- Part 2 ------------------------------------------------------------------------------------------------------------------------";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP IDENT decimal : " . $IDENT;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP VERSION decimal : " . $VERSION;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP OPSLIMIT decimal : " . $OPSLIMIT;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP MEMLIMIT decimal : " . $MEMLIMIT;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP FiveCharPw in character : " . $FiveCharPw;
|
|
|
|
### Create Password Hash or return error message if failed
|
|
my $PASSWORDHASH;
|
|
eval {
|
|
$PASSWORDHASH = argon2i_raw($FiveCharPw, $SALT, $OPSLIMIT, $MEMLIMIT, 1, 32);
|
|
1;
|
|
};
|
|
if ( $@ ) {
|
|
Log3 $name, 3, $name . " " . $@;
|
|
return($@);
|
|
}
|
|
|
|
### Unpack Password Hash
|
|
my $StrechedPWHex = unpack("H*",$PASSWORDHASH);
|
|
|
|
### Generate user friendly hex-string
|
|
my $StrechedPWHexFriendly;
|
|
for (my $i=0; $i < (length($StrechedPWHex)/2); $i++) {
|
|
$StrechedPWHexFriendly .= "0x" . substr($StrechedPWHex, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read -- Part 3 ------------------------------------------------------------------------------------------------------------------------";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP StrechedPW hex friendly : " . $StrechedPWHexFriendly;
|
|
|
|
|
|
### Open crypto_aead object
|
|
my $crypto_aead = Crypt::NaCl::Sodium->aead();
|
|
my $msg;
|
|
|
|
### Decrypt message or create error message
|
|
eval {
|
|
$msg = $crypto_aead->decrypt($CIPHERTEXT, "", $NONCE, $PASSWORDHASH);
|
|
|
|
1;
|
|
};
|
|
if ( $@ ) {
|
|
Log3 $name, 3, $name. " : Message forged!";
|
|
return("Messaged forged!");
|
|
}
|
|
|
|
### Unpack message as hex
|
|
my $DecryptedMsgHex = $msg->to_hex();
|
|
|
|
### Generate user friendly hex-string
|
|
my $StrechedMsgHexFriendly;
|
|
for (my $i=0; $i < (length($DecryptedMsgHex)/2); $i++) {
|
|
$StrechedMsgHexFriendly .= "0x" . substr($DecryptedMsgHex, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read -- Part 4 ------------------------------------------------------------------------------------------------------------------------";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Msg hex friendly : " . $StrechedMsgHexFriendly;
|
|
|
|
### Split up in accordance to API 0.24 description in hex values
|
|
my $INTERCOM_ID = substr($DecryptedMsgHex, 0, 12);
|
|
my $EVENT = substr($DecryptedMsgHex, 12, 16);
|
|
my $TIMESTAMP = substr($DecryptedMsgHex, 28, 8);
|
|
|
|
### Generate user friendly hex-string for Intercom_Id
|
|
my $Intercom_IdHexFriendly;
|
|
for (my $i=0; $i < (length($INTERCOM_ID)/2); $i++) {
|
|
$Intercom_IdHexFriendly .= "0x" . substr($INTERCOM_ID, $i*2, 2) . " ";
|
|
}
|
|
### Generate user friendly hex-string for Event
|
|
my $EventHexFriendly;
|
|
for (my $i=0; $i < (length($EVENT)/2); $i++) {
|
|
$EventHexFriendly .= "0x" . substr($EVENT, $i*2, 2) . " ";
|
|
}
|
|
### Generate user friendly hex-string for Timestamp
|
|
my $TimestampHexFriendly;
|
|
for (my $i=0; $i < (length($TIMESTAMP)/2); $i++) {
|
|
$TimestampHexFriendly .= "0x" . substr($TIMESTAMP, $i*2, 2) . " ";
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read -- Part 5 ------------------------------------------------------------------------------------------------------------------------";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Intercom_Id hex friendly : " . $Intercom_IdHexFriendly;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Event hex friendly : " . $EventHexFriendly;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Timestamp hex friendly : " . $TimestampHexFriendly;
|
|
|
|
### Convert in accordance to API 0.24 description in hex values
|
|
$INTERCOM_ID = pack("H*", $INTERCOM_ID);
|
|
$EVENT = pack("H*", $EVENT);
|
|
$TIMESTAMP = hex($TIMESTAMP);
|
|
|
|
### Convert in accordance to API 0.24 description in hex values
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($TIMESTAMP);
|
|
my $TIMESTAMPHR = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d",$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read -- Part 6 ------------------------------------------------------------------------------------------------------------------------";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP Intercom_Id character : " . $INTERCOM_ID;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP EVENT character : " . $EVENT;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP TIMESTAMP UNIX : " . $TIMESTAMP;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP TIMESTAMP human readeable : " . $TIMESTAMPHR;
|
|
|
|
### Remove trailing whitespace
|
|
$EVENT =~ s/\s+$//;
|
|
|
|
### If event belongs to the current user
|
|
if ($username =~ m/$INTERCOM_ID/){
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read -- Part 7 ------------------------------------------------------------------------------------------------------------------------";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - INTERCOM_ID matches username";
|
|
|
|
### Create first part command URL for DoorBird
|
|
my $UrlPrefix = "https://" . $url . "/bha-api/";
|
|
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "Downloading image", 1);
|
|
|
|
|
|
### If event has been triggered by motion sensor
|
|
if ($EVENT =~ m/motion/) {
|
|
### If the MessageID is integer type has not yet appeared yet
|
|
if ((int($TIMESTAMP) == $TIMESTAMP) && ($UdpMotionIdLast != $TIMESTAMP)) {
|
|
### Save Timestamp as new ID
|
|
$hash->{helper}{UdpMotionId} = $TIMESTAMP;
|
|
|
|
### Create name of reading for event
|
|
$ReadingEvent = "motion_sensor";
|
|
$ReadingEventContent = "Motion detected!";
|
|
|
|
### Create Parameter for CommandURL for motionsensor events
|
|
$UrlPostfix = "history.cgi?event=motionsensor&index=1";
|
|
|
|
### Create complete command URL for DoorBird
|
|
$CommandURL = $UrlPrefix . $UrlPostfix;
|
|
|
|
### Define Parameter for Non-BlockingGet
|
|
my $param = {
|
|
url => $CommandURL,
|
|
timeout => $PollingTimeout,
|
|
user => $Username,
|
|
pwd => $Password,
|
|
hash => $hash,
|
|
method => $Method,
|
|
header => $Header,
|
|
timestamp => $TIMESTAMP,
|
|
event => "motionsensor",
|
|
incrementalTimeout => 1,
|
|
callback => \&DoorBird_LastEvent_Image
|
|
};
|
|
|
|
### Initiate Bulk Update
|
|
readingsBeginUpdate($hash);
|
|
|
|
### Update readings of device
|
|
readingsBulkUpdate($hash, "state", $ReadingEventContent, 1);
|
|
readingsBulkUpdate($hash, $ReadingEvent, "triggered", 1);
|
|
|
|
### Execute Readings Bulk Update
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
### Initiate communication
|
|
HttpUtils_NonblockingGet($param);
|
|
|
|
### Wrap up a container and initiate the timer to reset reading "doorbell_button"
|
|
my %Container;
|
|
$Container{"HashReference"} = $hash;
|
|
$Container{"Reading"} = $ReadingEvent;
|
|
InternalTimer(gettimeofday()+ $hash->{helper}{EventReset}, "DoorBird_EventReset", \%Container, 0);
|
|
|
|
### Execute event trigger for Operation Mode
|
|
DoorBird_OpsModeExecute($hash, "motion");
|
|
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : An event has been triggered by the DoorBird unit : " . $EVENT;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - Timer for reset reading in : " . $hash->{helper}{EventReset};
|
|
}
|
|
### If the MessageID is integer type has appeared before
|
|
else {
|
|
### Do nothing
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - Motion sensor message already been sent. Ignoring it!";
|
|
}
|
|
}
|
|
### If event has been triggered by keypad
|
|
elsif ($EVENT =~ m/keypad/) {
|
|
### If the MessageID is integer type has not yet appeared yet
|
|
if ((int($TIMESTAMP) == $TIMESTAMP) && ($UdpKeypadIdLast != $TIMESTAMP)) {
|
|
### Save Timestamp as new ID
|
|
$hash->{helper}{UdpKeypadId} = $TIMESTAMP;
|
|
|
|
### Create name of reading for event
|
|
$ReadingEvent = "keypad_pin";
|
|
$ReadingEventContent = "Access via Keypad!";
|
|
|
|
### Create Parameter for CommandURL for keypad events
|
|
$UrlPostfix = "history.cgi?event=keypad&index=1";
|
|
|
|
### Create complete command URL for DoorBird
|
|
$CommandURL = $UrlPrefix . $UrlPostfix;
|
|
|
|
### Define Parameter for Non-BlockingGet
|
|
my $param = {
|
|
url => $CommandURL,
|
|
timeout => $PollingTimeout,
|
|
user => $Username,
|
|
pwd => $Password,
|
|
hash => $hash,
|
|
method => $Method,
|
|
header => $Header,
|
|
timestamp => $TIMESTAMP,
|
|
event => "keypad",
|
|
incrementalTimeout => 1,
|
|
callback => \&DoorBird_LastEvent_Image
|
|
};
|
|
|
|
### Initiate Bulk Update
|
|
readingsBeginUpdate($hash);
|
|
|
|
### Update readings of device
|
|
readingsBulkUpdate($hash, "state", $ReadingEventContent, 1);
|
|
readingsBulkUpdate($hash, $ReadingEvent, "triggered", 1);
|
|
|
|
### Execute Readings Bulk Update
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
### Initiate communication and close
|
|
HttpUtils_NonblockingGet($param);
|
|
|
|
### Wrap up a container and initiate the timer to reset reading "doorbell_button"
|
|
my %Container;
|
|
$Container{"HashReference"} = $hash;
|
|
$Container{"Reading"} = $ReadingEvent;
|
|
InternalTimer(gettimeofday()+ $hash->{helper}{EventReset}, "DoorBird_EventReset", \%Container, 0);
|
|
|
|
### Execute event trigger for Operation Mode
|
|
DoorBird_OpsModeExecute($hash, "keypad");
|
|
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : An event has been triggered by the DoorBird unit : " . $EVENT;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - Timer for reset reading in : " . $hash->{helper}{EventReset};
|
|
}
|
|
### If the MessageID is integer type has appeared before
|
|
else {
|
|
### Do nothing
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - Keypad message already been sent. Ignoring it!";
|
|
}
|
|
}
|
|
### If event has been triggered by doorbell -> Only a number has been transfered
|
|
elsif (int($EVENT) == $EVENT) {
|
|
### If the MessageID is integer type has not yet appeared yet
|
|
if ((int($TIMESTAMP) == $TIMESTAMP) && ($UdpDoorbellIdLast != $TIMESTAMP)) {
|
|
### Save Timestamp as new ID
|
|
$hash->{helper}{UdpDoorbellId} = $TIMESTAMP;
|
|
|
|
### Create name of reading for event
|
|
$ReadingEvent = "doorbell_button_" . sprintf("%03d", $EVENT);
|
|
$ReadingEventContent = "doorbell pressed!";
|
|
|
|
### Create Parameter for CommandURL for doorbell events
|
|
$UrlPostfix = "history.cgi?event=doorbell&index=1";
|
|
|
|
### Create complete command URL for DoorBird
|
|
$CommandURL = $UrlPrefix . $UrlPostfix;
|
|
|
|
### Define Parameter for Non-BlockingGet
|
|
my $param = {
|
|
url => $CommandURL,
|
|
timeout => $PollingTimeout,
|
|
user => $Username,
|
|
pwd => $Password,
|
|
hash => $hash,
|
|
method => $Method,
|
|
header => $Header,
|
|
timestamp => $TIMESTAMP,
|
|
event => "doorbell",
|
|
doorbellNo => $EVENT,
|
|
incrementalTimeout => 1,
|
|
callback => \&DoorBird_LastEvent_Image
|
|
};
|
|
|
|
### Initiate Bulk Update
|
|
readingsBeginUpdate($hash);
|
|
|
|
### Update readings of device
|
|
readingsBulkUpdate($hash, "state", $ReadingEventContent, 1);
|
|
readingsBulkUpdate($hash, $ReadingEvent, "triggered", 1);
|
|
|
|
### Execute Readings Bulk Update
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
### Initiate communication and close
|
|
HttpUtils_NonblockingGet($param);
|
|
|
|
### Wrap up a container and initiate the timer to reset reading "doorbell_button"
|
|
my %Container;
|
|
$Container{"HashReference"} = $hash;
|
|
$Container{"Reading"} = $ReadingEvent;
|
|
InternalTimer(gettimeofday()+ $hash->{helper}{EventReset}, "DoorBird_EventReset", \%Container, 0);
|
|
|
|
### Execute event trigger for Operation Mode
|
|
DoorBird_OpsModeExecute($hash, "doorbell");
|
|
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : An event has been triggered by the DoorBird unit : " . $EVENT;
|
|
Log3 $name, 5, $name. " : DoorBird_Read - Timer for reset reading in : " . $hash->{helper}{EventReset};
|
|
}
|
|
### If the MessageID is integer type has appeared before
|
|
else {
|
|
### Do nothing
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - Doorbell message already been sent. Ignoring it!";
|
|
}
|
|
}
|
|
### If the event has been triggered by unknown code
|
|
else {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : Unknown event triggered by Doorbird Unit : " . $EVENT;
|
|
}
|
|
}
|
|
### Event does not belong to the current user
|
|
else {
|
|
### Do nothing
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read -- Part 7 ------------------------------------------------------------------------------------------------------------------------";
|
|
Log3 $name, 5, $name. " : DoorBird_Read - INTERCOM_ID does not matches username. Ignoring datagram packet!";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
### Do nothing
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Read - UDP datagram transmitted by invalid PeerHost.";
|
|
}
|
|
}
|
|
####END####### After return of UDP message #####################################################################END#####
|
|
|
|
###START###### Open UDP socket connection #####################################################################START####
|
|
sub DoorBird_OpenSocketConn($) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $conn;
|
|
my $port = $hash->{helper}{UdpPort};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpenSocketConn - port : " . $port;
|
|
|
|
### Check if connection can be opened
|
|
$conn = new IO::Socket::INET (
|
|
ReusePort => $hash->{reusePort},
|
|
LocalPort => $port,
|
|
Proto => 'udp'
|
|
);
|
|
|
|
### Log Entry for debugging purposes
|
|
my $ShowConn = Dumper($conn);
|
|
$ShowConn =~ s/[\t]//g;
|
|
$ShowConn =~ s/[\r]//g;
|
|
$ShowConn =~ s/[\n]//g;
|
|
Log3 $name, 5, $name. " : DoorBird_OpenSocketConn - SocketConnection : " . $ShowConn;
|
|
|
|
|
|
if (defined($conn)) {
|
|
$hash->{FD} = $conn->fileno();
|
|
$hash->{CD} = $conn;
|
|
$selectlist{$name} = $hash;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 4, $name. " : DoorBird_OpenSocketConn - Socket Connection has been established";
|
|
}
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_OpenSocketConn - Socket Connection has NOT been established";
|
|
}
|
|
return
|
|
}
|
|
####END####### Open UDP socket connection ######################################################################END#####
|
|
|
|
###START###### Lost Connection with DorBird unit ##############################################################START####
|
|
sub DoorBird_LostConn($) {
|
|
my ($hash) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
|
|
### Create Timestamp
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
|
|
my $TimeStamp = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d",$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LostConn - Connection with DoorBird Unit lost";
|
|
|
|
### If Reading for state is not already disconnected
|
|
if (ReadingsVal($name, "state", "") ne "disconnected") {
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "disconnected", 1);
|
|
|
|
### Update Reading
|
|
readingsSingleUpdate($hash, "ContactLostSince", $TimeStamp, 1);
|
|
}
|
|
return;
|
|
}
|
|
####END####### Lost Connection with DorBird unit ###############################################################END#####
|
|
|
|
###START###### Reset event reading ############################################################################START####
|
|
sub DoorBird_EventReset($) {
|
|
my ($ContainerRef) = @_;
|
|
|
|
### Transform hash-Reference into hash
|
|
my %Container = %$ContainerRef;
|
|
|
|
### Extract hash and reading to be reset
|
|
my $hash = $Container{"HashReference"};
|
|
my $Reading = $Container{"Reading"};
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_EventReset - Reseting reading to idle : " . $Reading;
|
|
|
|
### Update readings of device
|
|
readingsSingleUpdate($hash, "state", "connected", 1);
|
|
readingsSingleUpdate($hash, $Reading, "idle", 1);
|
|
|
|
return;
|
|
}
|
|
####END####### Reset event reading #############################################################################END#####
|
|
|
|
###START###### Renew Session ID for DorBird unit ##############################################################START####
|
|
sub DoorBird_RenewSessionID($) {
|
|
my ($hash) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $command = "getsession.cgi";
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
my $err = " ";
|
|
my $data = " ";
|
|
my $json;
|
|
|
|
### Obtain data
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Remove Newlines for better log entries
|
|
my $ShowData = $data;
|
|
$ShowData =~ s/[\t]//g if($ShowData ne "");
|
|
$ShowData =~ s/[\r]//g if($ShowData ne "");
|
|
$ShowData =~ s/[\n]//g if($ShowData ne "");
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_RenewSessionID - err : " . $err if(defined($err));
|
|
Log3 $name, 5, $name. " : DoorBird_RenewSessionID - data : " . $ShowData if(defined($ShowData));
|
|
|
|
### If no error has been handed back
|
|
if ($err eq "") {
|
|
### Check if json can be parsed into hash
|
|
eval
|
|
{
|
|
$json = decode_json(encode_utf8($data));
|
|
1;
|
|
}
|
|
or do
|
|
{
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_RenewSessionID - Data cannot parsed JSON : Info_Request";
|
|
return $name. " : DoorBird_RenewSessionID - Data cannot be parsed by JSON for Info_Request";
|
|
};
|
|
|
|
### Extract SessionId from hash
|
|
$hash->{helper}{SessionId} = $json-> {BHA}{SESSIONID};
|
|
|
|
### Remove timer for LostConn
|
|
RemoveInternalTimer($hash, "DoorBird_RenewSessionID");
|
|
|
|
### If a time interval for the Session ID has been provided.
|
|
if ($hash->{helper}{SessionIdSec} > 0) {
|
|
### Initiate the timer for renewing SessionId
|
|
InternalTimer(gettimeofday()+ $hash->{helper}{SessionIdSec}, "DoorBird_RenewSessionID", $hash, 0);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_RenewSessionID - Session ID refreshed : " . $hash->{helper}{SessionId};
|
|
}
|
|
### If a time interval of 0 = disabled has been provided.
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_RenewSessionID - Session ID Security has been disabled - No further renewing of SessionId.";
|
|
}
|
|
|
|
### If the VideoStream has been activated
|
|
if (ReadingsVal($name, ".VideoURL", "") ne "") {
|
|
### Refresh Video URL
|
|
DoorBird_Live_Video($hash, "on");
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_RenewSessionID - VideoUrl refreshed";
|
|
}
|
|
|
|
### If the AudioStream has been activated
|
|
if (ReadingsVal($name, ".AudioURL", "") ne "") {
|
|
### Refresh Video URL
|
|
DoorBird_Live_Audio($hash, "on");
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_RenewSessionID - AudioUrl refreshed";
|
|
}
|
|
return
|
|
}
|
|
### If error has been handed back
|
|
else {
|
|
$err =~ s/^[^ ]*//;
|
|
return "ERROR!\nError Code:" . $err;
|
|
}
|
|
return;
|
|
}
|
|
####END####### Renew Session ID for DorBird unit ###############################################################END#####
|
|
|
|
###START###### Display of html code preceding the "Internals"-section #########################################START####
|
|
sub DoorBird_FW_detailFn($$$$) {
|
|
my ($FW_wname, $devname, $room, $extPage) = @_;
|
|
my $hash = $defs{$devname};
|
|
my $name = $hash->{NAME};
|
|
my $ImageData = $hash->{helper}{Images}{Individual}{Data};
|
|
my $ImageTimeStamp = $hash->{helper}{Images}{Individual}{Timestamp};
|
|
|
|
my $VideoURL = ReadingsVal($name, ".VideoURL", "");
|
|
my $ImageURL = ReadingsVal($name, ".ImageURL", "");
|
|
my $AudioURL = ReadingsVal($name, ".AudioURL", "");
|
|
my $htmlCode;
|
|
my $VideoHtmlCode;
|
|
my $ImageHtmlCode;
|
|
my $ImageHtmlCodeBig;
|
|
my $AudioHtmlCode;
|
|
my @HistoryDoorbell;
|
|
my @HistoryMotion;
|
|
|
|
# ### If Operation Mode(s) have been provided
|
|
# if (@{$hash->{helper}{OpsModeList}} > 0) {
|
|
# ### Log Entry for debugging purposes
|
|
# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - OpsModeList : " . Dumper(@{$hash->{helper}{OpsModeList}});
|
|
# }
|
|
# else {
|
|
# ### Log Entry for debugging purposes
|
|
# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - OpsModeList is empty";
|
|
# }
|
|
|
|
### Only if DoorBird has a Camera installed view the Image and History Part
|
|
if ($hash->{helper}{CameraInstalled} == true) {
|
|
|
|
### Log Entry for debugging purposes
|
|
if (defined $hash->{helper}{Images}{History}{doorbell}) {
|
|
@HistoryDoorbell = @{$hash->{helper}{Images}{History}{doorbell}};
|
|
Log3 $name, 5, $name. " : DoorBird_FW_detailFn - Size ImageData doorbell : " . @HistoryDoorbell;
|
|
}
|
|
### Log Entry for debugging purposes
|
|
if (defined $hash->{helper}{Images}{History}{motionsensor}) {
|
|
@HistoryMotion = @{$hash->{helper}{Images}{History}{motionsensor}};
|
|
Log3 $name, 5, $name. " : DoorBird_FW_detailFn - Size ImageData motion : " . @HistoryMotion;
|
|
}
|
|
|
|
### If VideoURL is empty
|
|
if ($VideoURL eq "") {
|
|
### Create Standard Response
|
|
$VideoHtmlCode = "Video Stream deactivated";
|
|
}
|
|
### If VideoURL is NOT empty
|
|
else {
|
|
|
|
### Create proper html code including popup
|
|
my $ImageHtmlCodeBig = "<img src=\\'" . $VideoURL . "\\'>";
|
|
my $PopupfunctionCode = "onclick=\"FW_okDialog(\'" . $ImageHtmlCodeBig . "\') \" ";
|
|
$VideoHtmlCode = '<img ' . $PopupfunctionCode . ' width="400" height="300" src="' . $VideoURL . '">';
|
|
|
|
### Create proper html link
|
|
#$VideoHtmlCode = '<img src="' . $VideoURL . '" width="400px" height="300px">';
|
|
}
|
|
|
|
### If ImageData is empty
|
|
if ($ImageData eq "") {
|
|
### Create Standard Response
|
|
$ImageHtmlCode = "Image not available";
|
|
}
|
|
### If ImageData is NOT empty
|
|
else {
|
|
### Create proper html code including popup
|
|
my $ImageHtmlCodeBig = "<img src=\\'data:image/jpeg;base64," . $ImageData . "\\'><br><center>" . $ImageTimeStamp . "</center>";
|
|
my $PopupfunctionCode = "onclick=\"FW_okDialog(\'" . $ImageHtmlCodeBig . "\') \" ";
|
|
$ImageHtmlCode = '<img ' . $PopupfunctionCode . ' width="400" height="300" alt="tick" src="data:image/jpeg;base64,' . $ImageData . '">';
|
|
}
|
|
|
|
### If AudioURL is empty
|
|
if ($AudioURL eq "") {
|
|
### Create Standard Response
|
|
$AudioHtmlCode = "Audio Stream deactivated";
|
|
}
|
|
### If AudioURL is NOT empty
|
|
else {
|
|
### Create proper html code
|
|
$AudioHtmlCode = '<audio id="audio_with_controls" controls src="' . $AudioURL . '" ">Your Browser cannot play this audio stream.</audio>';
|
|
}
|
|
#type="audio/wav
|
|
|
|
### Create html Code
|
|
$htmlCode = '
|
|
<table border="1" style="border-collapse:separate;">
|
|
<tbody >
|
|
<tr>
|
|
<td width="400px" align="center"><b>Image from ' . $ImageTimeStamp . '</b></td>
|
|
<td width="400px" align="center"><b>Live Stream</b></td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td id="ImageCell" width="430px" height="300px" align="center">
|
|
' . $ImageHtmlCode . '
|
|
</td>
|
|
<td id="ImageCell" width="435px" height="300px" align="center">
|
|
' . $VideoHtmlCode . '<BR>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td></td>
|
|
<td align="center">' . $AudioHtmlCode . '</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
';
|
|
|
|
### Log Entry for debugging purposes
|
|
# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - ImageHtmlCode : " . $ImageHtmlCode;
|
|
# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - VideoHtmlCode : " . $VideoHtmlCode;
|
|
# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - AudioHtmlCode : " . $AudioHtmlCode;
|
|
|
|
if ((@HistoryDoorbell > 0) || (@HistoryMotion > 0)) {
|
|
$htmlCode .=
|
|
'
|
|
<BR>
|
|
<BR>
|
|
<table border="1" style="border-collapse:separate;">
|
|
<tbody >
|
|
<tr>
|
|
<td align="center" colspan="5"><b>History of events - Last download: ' . $hash->{helper}{HistoryTime} . '</b></td>
|
|
</tr>
|
|
<tr>
|
|
<td align="center" colspan="2"><b>Doorbell</b></td>
|
|
<td align="center"></td>
|
|
<td align="center" colspan="2"><b>Motion-Sensor</b></td>
|
|
</tr>
|
|
<tr>
|
|
<td width="195px" align="center"><b>Picture</b></td>
|
|
<td width="195px" align="center"><b>Timestamp</b></td>
|
|
<td width="20px" align="center">#</td>
|
|
<td width="195px" align="center"><b>Picture</b></td>
|
|
<td width="195px" align="center"><b>Timestamp</b></td>
|
|
</tr>
|
|
';
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FW_detailFn - hash->{helper}{MaxHistory} : " . $hash->{helper}{MaxHistory};
|
|
|
|
### For all entries in Picture-Array do
|
|
for (my $i=0; $i <= ($hash->{helper}{MaxHistory} - 1); $i++) {
|
|
|
|
my $ImageHtmlCodeDoorbell;
|
|
my $ImageHtmlCodeMotion;
|
|
|
|
### Create proper html code for image triggered by doorbell
|
|
if ($HistoryDoorbell[$i]{data} ne "") {
|
|
### If element contains an error message
|
|
if ($HistoryDoorbell[$i]{data} =~ m/Error/) {
|
|
$ImageHtmlCodeDoorbell = $HistoryDoorbell[$i]{data};
|
|
}
|
|
### If element does not contain an error message
|
|
else {
|
|
### Create proper html code including popup
|
|
my $ImageHtmlCodeBig = "<img src=\\'data:image/jpeg;base64," . $HistoryDoorbell[$i]{data} . "\\'><br><center>" . $HistoryDoorbell[$i]{timestamp} . "</center>";
|
|
my $PopupfunctionCode = "onclick=\"FW_okDialog(\'" . $ImageHtmlCodeBig . "\') \" ";
|
|
$ImageHtmlCodeDoorbell = '<img ' . $PopupfunctionCode . ' width="190" height="auto" alt="tick" src="data:image/jpeg;base64,' . $HistoryDoorbell[$i]{data} . '">';
|
|
}
|
|
}
|
|
else {
|
|
$ImageHtmlCodeDoorbell = 'No image available';
|
|
}
|
|
### Create proper html code for image triggered by motionsensor
|
|
if ($HistoryMotion[$i]{data} ne "") {
|
|
### If element contains an error message
|
|
if ($HistoryMotion[$i]{data} =~ m/Error/) {
|
|
$ImageHtmlCodeMotion = $HistoryMotion[$i]{data};
|
|
}
|
|
### If element does not contain an error message
|
|
else {
|
|
### Create proper html code including popup
|
|
my $ImageHtmlCodeBig = "<img src=\\'data:image/jpeg;base64," . $HistoryMotion[$i]{data} . "\\'><br><center>" . $HistoryMotion[$i]{timestamp} . "</center>";
|
|
my $PopupfunctionCode = "onclick=\"FW_okDialog(\'" . $ImageHtmlCodeBig . "\') \" ";
|
|
$ImageHtmlCodeMotion = '<img ' . $PopupfunctionCode . ' width="190" height="auto" alt="tick" src="data:image/jpeg;base64,' . $HistoryMotion[$i]{data} . '">';
|
|
}
|
|
}
|
|
else {
|
|
$ImageHtmlCodeMotion = 'No image available';
|
|
}
|
|
|
|
$htmlCode .=
|
|
'
|
|
<tr>
|
|
<td align="center">' . $ImageHtmlCodeDoorbell . '</td>
|
|
<td align="center">' . $HistoryDoorbell[$i]{timestamp} . '</td>
|
|
<td align="center">' . ($i + 1) . '</td>
|
|
<td align="center">' . $ImageHtmlCodeMotion . '</td>
|
|
<td align="center">' . $HistoryMotion[$i]{timestamp} . '</td>
|
|
</tr>
|
|
';
|
|
### Log Entry for debugging purposes
|
|
# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - ImageHtmlCodeDoorbell : " . $ImageHtmlCodeDoorbell;
|
|
# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - ImageHtmlCodeMotion : " . $ImageHtmlCodeMotion;
|
|
}
|
|
|
|
### Finish table
|
|
$htmlCode .=
|
|
'
|
|
</tbody>
|
|
</table>
|
|
';
|
|
|
|
}
|
|
}
|
|
### Log Entry for debugging purposes
|
|
# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - htmlCode : " . $htmlCode;
|
|
|
|
# my $infoBtn = "</td><td><a onClick='FW_cmd(FW_root+\"?cmd.$name=get $name all&XHR=1\",function(data){FW_okDialog(data)})'\>$info</a>"
|
|
# <a href=\"#!\" onclick=\"FW_okDialog('Testtitle<br><br>TestDescription')\">Testtitle</a>
|
|
|
|
return($htmlCode );
|
|
}
|
|
####END####### Display of html code preceding the "Internals"-section ##########################################END#####
|
|
|
|
###START###### Define Subfunction for INFO REQUEST ############################################################START####
|
|
sub DoorBird_Info_Request($$) {
|
|
my ($hash, $option) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $command = "info.cgi";
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
|
|
my $err = " ";
|
|
my $data = " ";
|
|
my $json;
|
|
|
|
### Obtain data
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Remove Newlines for better log entries
|
|
my $ShowData = $data;
|
|
$ShowData =~ s/[\t]//g if($ShowData ne "");
|
|
$ShowData =~ s/[\r]//g if($ShowData ne "");
|
|
$ShowData =~ s/[\n]//g if($ShowData ne "");
|
|
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - err : " . $err if(defined($err));
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - data : " . $ShowData if(defined($ShowData));
|
|
|
|
### If no error has been handed back
|
|
if ($err eq "") {
|
|
### If the option is asking for the JSON string
|
|
if (defined($option) && ($option =~ /JSON/i)) {
|
|
return $data;
|
|
}
|
|
### If the option is asking for nothing special
|
|
else {
|
|
### Check if json can be parsed into hash
|
|
eval
|
|
{
|
|
$json = decode_json(encode_utf8($data));
|
|
1;
|
|
}
|
|
or do
|
|
{
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_Info_Request - Data cannot parsed JSON : Info_Request";
|
|
return $name. " : DoorBird_Info_Request - Data cannot be parsed by JSON for Info_Request";
|
|
};
|
|
|
|
my $VersionContent = $json-> {BHA}{VERSION}[0];
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - json : " . $json;
|
|
|
|
### Initiate Bulk Update
|
|
readingsBeginUpdate($hash);
|
|
|
|
foreach my $key (keys %{$VersionContent}) {
|
|
|
|
### If the entry are information about connected relays
|
|
if ( $key eq "RELAYS") {
|
|
|
|
### Save adresses of relays into hash
|
|
@{$hash->{helper}{RelayAdresses}} = @{$VersionContent -> {$key}};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - No of connected relays : " . @{$VersionContent -> {$key}};
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - Adresses of relays : " . join(",", @{$VersionContent -> {$key}});
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - {helper}{RelayAdresses} : " . join(",", @{$hash->{helper}{RelayAdresses}});
|
|
|
|
### Delete all Readings for Relay-Addresses
|
|
readingsDelete($hash, "RelayAddr_.*");
|
|
|
|
### For all registred relays do
|
|
my $RelayNumber =0;
|
|
foreach my $RelayAddress (@{$VersionContent -> {$key}}) {
|
|
|
|
$RelayNumber++;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - Adress of " . sprintf("%15s %-s", "Relay_" . sprintf("%02d", $RelayNumber), ": " . $RelayAddress);
|
|
|
|
### Update Reading
|
|
readingsBulkUpdate($hash, "RelayAddr_" . sprintf("%02d", $RelayNumber), $RelayAddress);
|
|
}
|
|
}
|
|
### If the entry has the information about the device type
|
|
elsif ( $key eq "DEVICE-TYPE") {
|
|
|
|
### If the Device Type is not containing type numbers which have no camera installed - Currently only "DoorBird D301A - Door Intercom IP Upgrade"
|
|
if ($VersionContent -> {$key} !~ m/301/) {
|
|
### Set Information about Camera installed to true
|
|
$hash->{helper}{CameraInstalled} = true;
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - Content of" . sprintf("%15s %-s", $key, ": " . $VersionContent -> {$key});
|
|
|
|
### Update Reading
|
|
readingsBulkUpdate($hash, $key, $VersionContent -> {$key} );
|
|
}
|
|
### For all other entries
|
|
else {
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Info_Request - Content of" . sprintf("%15s %-s", $key, ": " . $VersionContent -> {$key});
|
|
|
|
### Update Reading
|
|
readingsBulkUpdate($hash, $key, $VersionContent -> {$key} );
|
|
}
|
|
}
|
|
### Update SessionId
|
|
DoorBird_RenewSessionID($hash);
|
|
|
|
### Execute Readings Bulk Update
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
### Download SIP Status Request
|
|
DoorBird_SipStatus_Request($hash,"");
|
|
|
|
### Check for Firmware-Updates
|
|
DoorBird_FirmwareStatus($hash);
|
|
|
|
return "Readings have been updated!\n";
|
|
}
|
|
}
|
|
### If error has been handed back
|
|
else {
|
|
$err =~ s/^[^ ]*//;
|
|
return "ERROR!\nError Code:" . $err;
|
|
}
|
|
}
|
|
####END####### Define Subfunction for INFO REQUEST #############################################################END#####
|
|
|
|
###START###### Firmware-Update Status for DorBird unit ########################################################START####
|
|
sub DoorBird_FirmwareStatus($) {
|
|
my ($hash) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
|
|
### Create Timestamp
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
|
|
my $TimeStamp = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d",$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FirmwareStatus - Checking firmware status on doorbird page";
|
|
|
|
my $FirmwareVersionUnit = ReadingsVal($name, "FIRMWARE" , 0 );
|
|
my $FirmwareDevice = ReadingsVal($name, "DEVICE-TYPE", "unknown");
|
|
|
|
### Download website of changelocks
|
|
my $html = GetFileFromURL("https://www.doorbird.com/changelog");
|
|
|
|
### Get the latest firmware number for this product
|
|
my $versions = DoorBird_parseChangelog($hash, $html);
|
|
my $result = DoorBird_findNewestFWVersion($hash, $versions, $FirmwareDevice);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FirmwareStatus - result : " . $result;
|
|
|
|
### If the latest Firmware is installed
|
|
if (int($FirmwareVersionUnit) == int($result)) {
|
|
### Update Reading for Firmware-Status
|
|
readingsSingleUpdate($hash, "Firmware-Status", "up-to-date", 1);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FirmwareStatus - Latest firmware is installed!";
|
|
|
|
}
|
|
### If the latest Firmware is NOT installed
|
|
elsif (int($FirmwareVersionUnit) < int($result)) {
|
|
### Update Reading for Firmware-Status
|
|
readingsSingleUpdate($hash, "Firmware-Status", "Firmware update required!", 1);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FirmwareStatus - DoorBird requires firmware update!";
|
|
}
|
|
### Something went wrong
|
|
else {
|
|
### Update Reading for Firmware-Status
|
|
readingsSingleUpdate($hash, "Firmware-Status", "unknown", 1);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FirmwareStatus - An error occured!";
|
|
}
|
|
|
|
return;
|
|
}
|
|
####END####### Firmware-Update Status for DorBird unit #########################################################END#####
|
|
|
|
###START###### Define Subfunction for LIVE VIDEO REQUEST ######################################################START####
|
|
sub DoorBird_Live_Video($$) {
|
|
my ($hash, $option) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
|
|
my $url = $hash->{helper}{URL};
|
|
|
|
|
|
### Create complete command URL for DoorBird depending on whether SessionIdSecurity has been enabled (>0) or disabled (=0)
|
|
my $UrlPrefix = "http://" . $url . "/bha-api/";
|
|
my $UrlPostfix;
|
|
if ($hash->{helper}{SessionIdSec} > 0) {
|
|
$UrlPostfix = "?sessionid=" . $hash->{helper}{SessionId};
|
|
}
|
|
else {
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
$UrlPostfix = "?http-user=". $username . "&http-password=" . $password;
|
|
}
|
|
my $VideoURL = $UrlPrefix . "video.cgi" . $UrlPostfix;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Live_Video - VideoURL : " . $VideoURL ;
|
|
Log3 $name, 5, $name. " : DoorBird_Live_Video - VideoURL : Created";
|
|
|
|
### If VideoStreaming shall be switched ON
|
|
if ($option eq "on") {
|
|
|
|
### Update Reading
|
|
readingsSingleUpdate($hash, ".VideoURL", $VideoURL, 1);
|
|
|
|
### Refresh Browser Window
|
|
FW_directNotify("#FHEMWEB:$FW_wname", "location.reload()", "") if defined($FW_wname);
|
|
}
|
|
### If VideoStreaming shall be switched OFF
|
|
elsif ($option eq "off") {
|
|
### Update Reading
|
|
readingsSingleUpdate($hash, ".VideoURL", "", 1);
|
|
|
|
### Refresh Browser Window
|
|
FW_directNotify("#FHEMWEB:$FW_wname", "location.reload()", "") if defined($FW_wname);
|
|
|
|
}
|
|
### If wrong parameter has been transfered
|
|
else
|
|
{
|
|
### Do nothing - Just return
|
|
return("ERROR!\nWrong Parameter used");
|
|
}
|
|
return
|
|
}
|
|
####END####### Define Subfunction for LIVE VIDEO REQUEST #######################################################END#####
|
|
|
|
###START###### Define Subfunction for LIVE VIDEO REQUEST ######################################################START####
|
|
sub DoorBird_Live_Audio($$) {
|
|
my ($hash, $option) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $url = $hash->{helper}{URL};
|
|
|
|
### Create complete command URL for DoorBird depending on whether SessionIdSecurity has been enabled (>0) or disabled (=0)
|
|
my $UrlPrefix = "http://" . $url . "/bha-api/audio-receive.cgi";
|
|
my $UrlPostfix;
|
|
if ($hash->{helper}{SessionIdSec} > 0) {
|
|
$UrlPostfix = "?sessionid=" . $hash->{helper}{SessionId};
|
|
}
|
|
else {
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
$UrlPostfix = "?http-user=". $username . "&http-password=" . $password;
|
|
}
|
|
my $AudioURL = $UrlPrefix . $UrlPostfix;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Live_Audio - AudioURL : " . $AudioURL ;
|
|
|
|
### If AudioStreaming shall be switched ON
|
|
if ($option eq "on") {
|
|
|
|
### Update Reading
|
|
readingsSingleUpdate($hash, ".AudioURL", $AudioURL, 1);
|
|
|
|
### Refresh Browser Window
|
|
FW_directNotify("#FHEMWEB:$FW_wname", "location.reload()", "") if defined($FW_wname);
|
|
}
|
|
### If AudioStreaming shall be switched OFF
|
|
elsif ($option eq "off") {
|
|
### Update Reading
|
|
readingsSingleUpdate($hash, ".AudioURL", "", 1);
|
|
|
|
### Refresh Browser Window
|
|
FW_directNotify("#FHEMWEB:$FW_wname", "location.reload()", "") if defined($FW_wname);
|
|
}
|
|
### If wrong parameter has been transfered
|
|
else
|
|
{
|
|
### Do nothing - Just return
|
|
return("ERROR!\nWrong Parameter used");
|
|
}
|
|
return
|
|
}
|
|
####END####### Define Subfunction for LIVE VIDEO REQUEST #######################################################END#####
|
|
|
|
|
|
###START###### Define Subfunction for LIVE IMAGE REQUEST ######################################################START####
|
|
sub DoorBird_Image_Request($$) {
|
|
my ($hash, $option) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
my $url = $hash->{helper}{URL};
|
|
my $command = "image.cgi";
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
my $err = " ";
|
|
my $data = " ";
|
|
my $json = " ";
|
|
my $ImageFileName = " ";
|
|
|
|
### Create complete command URL for DoorBird
|
|
my $UrlPrefix = "https://" . $url . "/bha-api/";
|
|
my $UrlPostfix = "?http-user=". $username . "&http-password=" . $password;
|
|
my $ImageURL = $UrlPrefix . $command . $UrlPostfix;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request _____________________________________________________________";
|
|
# Log3 $name, 5, $name. " : DoorBird_Image_Request - ImageURL : " . $ImageURL ;
|
|
|
|
### Update Reading
|
|
readingsSingleUpdate($hash, ".ImageURL", $ImageURL, 1);
|
|
|
|
### Get Image Data
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - err : " . $err;
|
|
# Log3 $name, 5, $name. " : DoorBird_Image_Request - data : " . $data;
|
|
|
|
|
|
### Encode jpeg data into base64 data and remove lose newlines
|
|
my $ImageData = MIME::Base64::encode($data);
|
|
$ImageData =~ s{\n}{}g;
|
|
|
|
### Create Timestamp
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
|
|
my $ImageTimeStamp = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d",$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
my $ImageFileTimeStamp = sprintf ( "%04d%02d%02d-%02d%02d%02d" ,$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
|
|
### Save picture and timestamp into hash
|
|
$hash->{helper}{Images}{Individual}{Data} = $ImageData;
|
|
$hash->{helper}{Images}{Individual}{Timestamp} = $ImageTimeStamp;
|
|
|
|
### Refresh Browser Window
|
|
FW_directNotify("#FHEMWEB:$FW_wname", "location.reload()", "") if defined($FW_wname);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - hash - ImageFileDir : " . $hash->{helper}{ImageFileDir};
|
|
|
|
### If pictures supposed to be saved as files
|
|
if ($hash->{helper}{ImageFileDir} ne "") {
|
|
|
|
### Get current working directory
|
|
my $cwd = getcwd();
|
|
my $ImageFileDir;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - working directory : " . $cwd;
|
|
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - file system format : LINUX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($hash->{helper}{ImageFileDir} =~ /^\//) {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $cwd . "/" . $hash->{helper}{ImageFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{ImageFileDir} =~ /\/\z/) {
|
|
### Save directory
|
|
$ImageFileDir = $ImageFileName;
|
|
|
|
### Create full datapath
|
|
$ImageFileName .= $ImageFileTimeStamp . "_snapshot.jpg";
|
|
}
|
|
else {
|
|
### Save directory
|
|
$ImageFileDir = $ImageFileName . "/";
|
|
|
|
### Create full datapath
|
|
$ImageFileName .= "/" . $ImageFileTimeStamp . "_snapshot.jpg";
|
|
}
|
|
}
|
|
|
|
### If the path is given as Windows file system format
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($hash->{helper}{ImageFileDir} != /^.:\//) {
|
|
$ImageFileName = $cwd . $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
|
|
### Save directory
|
|
$ImageFileDir = $ImageFileName;
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\\z/) {
|
|
### Save directory
|
|
$ImageFileDir = $ImageFileName;
|
|
|
|
### Create full datapath
|
|
$ImageFileName .= $ImageFileTimeStamp . "_snapshot.jpg";
|
|
}
|
|
else {
|
|
### Save directory
|
|
$ImageFileDir = $ImageFileName . "\\";
|
|
|
|
### Create full datapath
|
|
$ImageFileName .= "\\" . $ImageFileTimeStamp . "_snapshot.jpg";
|
|
}
|
|
}
|
|
|
|
### Save filename of last snapshot into hash
|
|
$hash->{helper}{Images}{LastSnapshotPath} = $ImageFileName;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - ImageFileName : " . $ImageFileName;
|
|
|
|
### Open file or write error message in log
|
|
open my $fh, ">", $ImageFileName or do {
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_Image_Request - open file error : " . $! . " - ". $ImageFileName;
|
|
};
|
|
|
|
### Write the base64 decoded data in file
|
|
print $fh decode_base64($ImageData) if defined($fh);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - write file : Successfully written " . $ImageFileName;
|
|
|
|
### Update the history list for images and videos
|
|
DoorBird_History_List($hash);
|
|
|
|
### Close file or write error message in log
|
|
close $fh or do {
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_Image_Request - close file error : " . $! . " - ". $ImageFileName;
|
|
};
|
|
|
|
### Free FileDirSpace if exxeeds maximum
|
|
DoorBird_FileSpace($hash, $ImageFileDir, "jpg", $hash->{helper}{ImageFileDirMaxSize});
|
|
}
|
|
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - ImageData size : " . length($ImageData);
|
|
Log3 $name, 5, $name. " : DoorBird_Image_Request - ImageTimeStamp : " . $ImageTimeStamp;
|
|
|
|
return;
|
|
}
|
|
####END####### Define Subfunction for LIVE IMAGE REQUEST #######################################################END#####
|
|
|
|
###START###### Define Subfunction for LAST EVENT IMAGE REQUEST ################################################START####
|
|
sub DoorBird_LastEvent_Image($$$) {
|
|
my ($param, $err, $data) = @_;
|
|
my $hash = $param->{hash};
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $event = $param->{event};
|
|
my $timestamp = $param->{timestamp};
|
|
my $VideoEvent;
|
|
my $httpHeader;
|
|
my $ReadingImage;
|
|
|
|
if ($event =~ m/doorbell/ ){
|
|
$ReadingImage = "doorbell_snapshot_" . sprintf("%03d", $param->{doorbellNo});
|
|
$VideoEvent = "doorbell_" . sprintf("%03d", $param->{doorbellNo});
|
|
}
|
|
elsif ($event =~ m/motion/ ){
|
|
$ReadingImage = "motion_snapshot";
|
|
$VideoEvent = "motionsensor"
|
|
}
|
|
elsif ($event =~ m/keypad/ ){
|
|
$ReadingImage = "keypad_snapshot";
|
|
$VideoEvent = "keypad"
|
|
}
|
|
else {
|
|
### Create Log entry
|
|
Log3 $name, 2, $name. " : DoorBird_LastEvent_Image - Unknown event. Breaking up";
|
|
|
|
### Exit sub
|
|
return
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image ___________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - err : " . $err if (defined($err ));
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - length data : " . length($data) if (defined($data ));
|
|
#Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - param : " . join("\n", @{[%{$param}]}) if (defined($param));
|
|
|
|
### If error message available
|
|
if ($err ne "") {
|
|
### Create Log entry
|
|
Log3 $name, 3, $name. " : DoorBird_LastEvent_Image - Error : " . $err if (defined($err ));
|
|
|
|
### Write Last Image into reading
|
|
readingsSingleUpdate($hash, $ReadingImage, "No image data", 1);
|
|
}
|
|
### if no error message available
|
|
else {
|
|
### If any image data available
|
|
if (defined $data) {
|
|
|
|
### Predefine Image Data and Image-hash and hash - reference
|
|
my $ImageData;
|
|
my $ImageTimeStamp;
|
|
my $ImageFileTimeStamp;
|
|
my $ImageFileName;
|
|
my %ImageDataHash;
|
|
my $ref_ImageDataHash = \%ImageDataHash;
|
|
|
|
### If http response code is 200 = OK
|
|
if ($param->{code} == 200) {
|
|
### Encode jpeg data into base64 data and remove lose newlines
|
|
$ImageData = MIME::Base64::encode($data);
|
|
$ImageData =~ s{\n}{}g;
|
|
|
|
### Create Timestamp
|
|
$httpHeader = $param->{httpheader};
|
|
$httpHeader =~ s/^[^_]*X-Timestamp: //;
|
|
$httpHeader =~ s/\n.*//g;
|
|
|
|
### If timestamp from history image has NOT been done since the timestamp from the event
|
|
if ((int($timestamp) - int($httpHeader)) > 0){
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - timestamp from history image has NOT been done since the timestamp from the event.";
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Image timestamp : " . $httpHeader;
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Event timestamp : " . $timestamp;
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - dt : " . (int($timestamp) - int($httpHeader));
|
|
|
|
|
|
### If timestamp from the event is NOT older than WaitForHistory from current time => Try again
|
|
if ((time - int($timestamp)) <= $hash->{helper}{WaitForHistory}){
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - timestamp of event is not older than Attribute WaitForHistory: Still time to try again";
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Event timestamp : " . $timestamp;
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - current timestamp : " . time;
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Attr WaitForHistory : " . $hash->{helper}{WaitForHistory};
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - dt : " . int(time - int($timestamp));
|
|
|
|
### Try again: Initiate communication and close
|
|
HttpUtils_NonblockingGet($param);
|
|
|
|
### Exit routine
|
|
return;
|
|
}
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 2, $name. " : DoorBird_LastEvent_Image - timestamp of event is older than than Attribute WaitForHistory: Proceeding without waiting any longer...";
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Event timestamp : " . $timestamp;
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - current timestamp : " . time;
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Attr WaitForHistory : " . $hash->{helper}{WaitForHistory};
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - dt : " . int(time - int($timestamp));
|
|
|
|
### Write Last Image into reading
|
|
readingsSingleUpdate($hash, $ReadingImage, "No image data", 1);
|
|
}
|
|
}
|
|
### If timestamp from history picture has been done since the timestamp from the event
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - timestamp from history image has been done since the timestamp from the event.";
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Image timestamp : " . $httpHeader;
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Event timestamp : " . $timestamp;
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - dt : " . (int($timestamp) - int($httpHeader));
|
|
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($httpHeader);
|
|
$ImageTimeStamp = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d",$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
$ImageFileTimeStamp = sprintf ( "%04d%02d%02d-%02d%02d%02d" ,$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
|
|
### Save picture and timestamp into hash
|
|
$hash->{helper}{Images}{Individual}{Data} = $ImageData;
|
|
$hash->{helper}{Images}{Individual}{Timestamp} = $ImageTimeStamp;
|
|
|
|
### Refresh Browser Window
|
|
FW_directNotify("#FHEMWEB:$FW_wname", "location.reload()", "") if defined($FW_wname);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - hash - ImageFileDir : " . $hash->{helper}{ImageFileDir};
|
|
|
|
### If pictures supposed to be saved as files
|
|
if ($hash->{helper}{ImageFileDir} ne "0") {
|
|
|
|
### Get current working directory
|
|
my $cwd = getcwd();
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - working directory : " . $cwd;
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - file system format : LINUX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($hash->{helper}{ImageFileDir} =~ /^\//) {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $cwd . "/" . $hash->{helper}{ImageFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{ImageFileDir} =~ /\/\z/) {
|
|
$ImageFileName .= $ImageFileTimeStamp . "_" . $event . ".jpg";
|
|
}
|
|
else {
|
|
$ImageFileName .= "/" . $ImageFileTimeStamp . "_" . $event . ".jpg";
|
|
}
|
|
}
|
|
|
|
### If the path is given as Windows file system format
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($hash->{helper}{ImageFileDir} != /^.:\//) {
|
|
$ImageFileName = $cwd . $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\\z/) {
|
|
$ImageFileName .= $ImageFileTimeStamp . "_" . $event . ".jpg";
|
|
}
|
|
else {
|
|
$ImageFileName .= "\\" . $ImageFileTimeStamp . "_" . $event . ".jpg";
|
|
}
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - ImageFileName : " . $ImageFileName;
|
|
|
|
### Open file or write error message in log
|
|
open my $fh, ">", $ImageFileName or do {
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_LastEvent_Image - open file error : " . $! . " - ". $ImageFileName;
|
|
};
|
|
|
|
### Write the base64 decoded data in file
|
|
print $fh decode_base64($ImageData) if defined($fh);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - write file : Successfully written " . $ImageFileName;
|
|
|
|
### Close file or write error message in log
|
|
close $fh or do {
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_LastEvent_Image - close file error : " . $! . " - ". $ImageFileName;
|
|
};
|
|
|
|
### Write Last Image into reading
|
|
readingsSingleUpdate($hash, $ReadingImage, $ImageFileName, 1);
|
|
|
|
### Update the history list for images and videos
|
|
DoorBird_History_List($hash);
|
|
}
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - ImageData - event : " . length($ImageData);
|
|
|
|
}
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Type of event : " . $event;
|
|
}
|
|
### If http response code is 204 = No permission to download the event history
|
|
elsif ($param->{code} == 204) {
|
|
### Create Log entry
|
|
Log3 $name, 3, $name. " : DoorBird_LastEvent_Image - Error 204 : User not authorized to download event history";
|
|
|
|
### Create Error message
|
|
$ImageData = "Error 204: The user has no permission to download the event history.";
|
|
$ImageTimeStamp =" ";
|
|
|
|
### Write Last Image into reading
|
|
readingsSingleUpdate($hash, $ReadingImage, "No image data", 1);
|
|
}
|
|
### If http response code is 404 = No picture available to download the event history
|
|
elsif ($param->{code} == 404) {
|
|
### Create Log entry
|
|
Log3 $name, 5, $name. " : DoorBird_LastEvent_Image - Error 404 : No picture available to download event history. Check settings in DoorBird APP.";
|
|
|
|
### Create Error message
|
|
$ImageData = "Error 404: No picture available to download in the event history.";
|
|
$ImageTimeStamp =" ";
|
|
|
|
### Write Last Image into reading
|
|
readingsSingleUpdate($hash, $ReadingImage, "No image data", 1);
|
|
}
|
|
### If http response code is none of one above
|
|
else {
|
|
### Create Log entry
|
|
Log3 $name, 3, $name. " : DoorBird_LastEvent_Image - Unknown http response code : " . $param->{code};
|
|
|
|
### Create Error message
|
|
$ImageData = "Error : " . $param->{code};
|
|
$ImageTimeStamp =" ";
|
|
|
|
### Write Last Image into reading
|
|
readingsSingleUpdate($hash, $ReadingImage, "No image data", 1);
|
|
}
|
|
}
|
|
else {
|
|
### Write Last Image into reading
|
|
readingsSingleUpdate($hash, $ReadingImage, "No image data", 1);
|
|
}
|
|
}
|
|
|
|
### If the attribute VideoDuration has been set and therefore an video shall be recorded
|
|
if (($event =~ m/doorbell/ ) && ($hash->{helper}{VideoDurationDoorbell} > 0)){
|
|
### Call sub for Videorecording
|
|
DoorBird_Video_Request($hash, $hash->{helper}{VideoDurationDoorbell}, $VideoEvent, $httpHeader);
|
|
}
|
|
elsif (($event =~ m/motion/ ) && ($hash->{helper}{VideoDurationMotion} > 0)){
|
|
### Call sub for Videorecording
|
|
DoorBird_Video_Request($hash, $hash->{helper}{VideoDurationMotion}, $VideoEvent, $httpHeader);
|
|
}
|
|
elsif (($event =~ m/keypad/ ) && ($hash->{helper}{VideoDurationKeypad} > 0)){
|
|
### Call sub for Videorecording
|
|
DoorBird_Video_Request($hash, $hash->{helper}{VideoDurationKeypad}, $VideoEvent, $httpHeader);
|
|
}
|
|
return;
|
|
}
|
|
####END####### Define Subfunction for LAST EVENT IMAGE REQUEST #################################################END#####
|
|
|
|
###START###### Define Subfunction for OPEN DOOR ###############################################################START####
|
|
sub DoorBird_Open_Door($) {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $relay = $hash->{helper}{OpenRelay};
|
|
my $command = "open-door.cgi?r=" . $relay;
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $err;
|
|
my $data;
|
|
my $json;
|
|
|
|
### Delete Helper
|
|
$hash->{helper}{OpenRelay} = "";
|
|
|
|
### Activate Relay
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Open_Door - err : " . $err;
|
|
Log3 $name, 5, $name. " : DoorBird_Open_Door - data : " . $data;
|
|
|
|
### If no error message is available
|
|
if ($err eq "") {
|
|
|
|
### Check if json can be parsed into hash
|
|
eval {
|
|
$json = decode_json(encode_utf8($data));
|
|
1;
|
|
}
|
|
or do {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_Open_Door - Data cannot be parsed by JSON for: Open_Door";
|
|
return $name. " : DoorBird_Open_Door - Data cannot be parsed by JSON for Open_Door";
|
|
};
|
|
|
|
### Create return messages and log entries based on error codes returned
|
|
if ($json->{BHA}{RETURNCODE} eq "1") {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Open_Door - Door ". $relay . " successfully triggered.";
|
|
|
|
### Create popup message
|
|
return "Door ". $relay . " successful triggered.";
|
|
}
|
|
elsif ($json->{BHA}{RETURNCODE} eq "204") {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Open_Door - Error 204: The user " . $username . "has no “watch-always” - permission to open the door.";
|
|
|
|
### Create popup message
|
|
return "Error 204: The user " . $username . "has no “watch-always” - permission to open the door.";
|
|
}
|
|
else {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Light_On - ERROR! - Return Code:" . $json->{BHA}{RETURNCODE};
|
|
return "ERROR!\nReturn Code:" . $json->{BHA}{RETURNCODE};
|
|
}
|
|
}
|
|
### If error message is available
|
|
else {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Light_On - ERROR! - Error Code:" . $err;
|
|
|
|
### Create error message
|
|
$err =~ s/^[^ ]*//;
|
|
return "ERROR!\nError Code:" . $err;
|
|
}
|
|
}
|
|
####END####### Define Subfunction for OPEN DOOR ################################################################END#####
|
|
|
|
###START###### Define Subfunction for LIGHT ON ################################################################START####
|
|
sub DoorBird_Light_On($$) {
|
|
my ($hash, $option) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $command = "light-on.cgi";
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $err;
|
|
my $data;
|
|
my $json;
|
|
|
|
### Activate Relay
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Light_On - err : " . $err;
|
|
Log3 $name, 5, $name. " : DoorBird_Light_On - data : " . $data;
|
|
|
|
### If no error message is available
|
|
if ($err eq "") {
|
|
### Check if json can be parsed into hash
|
|
eval {
|
|
$json = decode_json(encode_utf8($data));
|
|
1;
|
|
}
|
|
or do {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_Light_On - Data cannot be parsed by JSON for: Light_On";
|
|
return $name. " : DoorBird_Light_On - Data cannot be parsed by JSON for Light_On";
|
|
};
|
|
|
|
### Create return messages and log entries based on error codes returned
|
|
if ($json->{BHA}{RETURNCODE} eq "1") {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Light_On - Light successfully triggered.";
|
|
|
|
### Create popup message
|
|
return "Light successful triggered.";
|
|
}
|
|
elsif ($json->{BHA}{RETURNCODE} eq "204") {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Light_On - Error 204: The user " . $username . "has no “watch-always” - permission to switch the light ON.";
|
|
|
|
### Create popup message
|
|
return "Error 204: The user " . $username . "has no “watch-always” - permission to switch the light ON.";
|
|
}
|
|
else {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Light_On - ERROR! - Return Code:" . $json->{BHA}{RETURNCODE};
|
|
return "ERROR!\nReturn Code:" . $json->{BHA}{RETURNCODE};
|
|
}
|
|
}
|
|
### If error message is available
|
|
else {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Light_On - ERROR! - Error Code:" . $err;
|
|
|
|
### Create error message
|
|
$err =~ s/^[^ ]*//;
|
|
return "ERROR!\nError Code:" . $err;
|
|
}
|
|
}
|
|
####END####### Define Subfunction for LIGHT ON #################################################################END#####
|
|
|
|
###START###### Define Subfunction for TRANSMIT AUDIO REQUEST ##################################################START####
|
|
sub DoorBird_Transmit_Audio($$) {
|
|
my ($hash, $option) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $Username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $Password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
my $Url = $hash->{helper}{URL};
|
|
my $Sox = $hash->{helper}{SOX};
|
|
my $AudioDataPathOrig = $option;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - ---------------------------------------------------------------";
|
|
|
|
### If file exists
|
|
if (-e $AudioDataPathOrig) {
|
|
### Create new filepath from old filepath
|
|
my $AudioDataNew;
|
|
my $AudioDataSizeNew;
|
|
my $AudioDataPathNew = $AudioDataPathOrig;
|
|
$AudioDataPathNew =~ s/\..*//;
|
|
$AudioDataPathNew .= ".wav";
|
|
|
|
### If the respective .wav file already exists
|
|
if (-e $AudioDataPathNew) {
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - wav file already exists : " . $AudioDataPathNew;
|
|
|
|
}
|
|
### If the respective .wav file does not exists
|
|
else {
|
|
|
|
### Create Sox - command
|
|
my $SoxCmd = $Sox . " -V " . $AudioDataPathOrig . " " . $AudioDataPathNew;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - Original Path exists : " . $AudioDataPathOrig;
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - New Path created : " . $AudioDataPathNew;
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - Sox System-Command : " . $SoxCmd;
|
|
|
|
### Convert file
|
|
system ($SoxCmd);
|
|
}
|
|
|
|
### Get filesize of wav file
|
|
$AudioDataSizeNew = -s $AudioDataPathNew;
|
|
|
|
### Get FileInfo and extract the length of wav file in seconds
|
|
my $SoxCmd = $Sox . " " . $AudioDataPathNew . " -n stat stats";
|
|
|
|
my @FileInfo = qx($SoxCmd 2>&1);
|
|
my $AudioLength = $FileInfo[1];
|
|
$AudioLength =~ s/Length \(seconds\)\://;
|
|
$AudioLength =~ s/\s+//g;
|
|
$AudioLength = int($AudioLength);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - AudioLength in seconds : " . $AudioLength;
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - New Filesize : " . $AudioDataSizeNew;
|
|
|
|
### Create complete command URL for DoorBird depending on whether SessionIdSecurity has been enabled (>0) or disabled (=0)
|
|
my $UrlPrefix = "http://" . $Url . "/bha-api/audio-transmit.cgi";
|
|
my $UrlPostfix;
|
|
|
|
### If SessionIdSec is enabled
|
|
if ($hash->{helper}{SessionIdSec} != 0) {
|
|
$UrlPostfix = "?sessionid=" . $hash->{helper}{SessionId} . " content-type=\"audio/basic\" use-content-length=true";
|
|
}
|
|
### Id SessionID Security is disabled
|
|
else {
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
$UrlPostfix = " content-type=\"audio/basic\" use-content-length=true user=" . $username . " passwd=" . $password;
|
|
}
|
|
my $CommandURL = $UrlPrefix . $UrlPostfix;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - CommandURL : " . $CommandURL ;
|
|
|
|
### Create the gst-lauch command
|
|
my $GstCommand = "gst-launch-1.0 filesrc location=";
|
|
$GstCommand .= $AudioDataPathNew;
|
|
$GstCommand .= " ! wavparse ! audioconvert ! audioresample ! \"audio/x-raw,format=S16LE,rate=8000,channels=1\" ! mulawenc ! \"audio/x-mulaw,rate=8000,channels=1\" ! curlhttpsink location=";
|
|
$GstCommand .= $CommandURL;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - GstCommand : " . $GstCommand;
|
|
|
|
### Create command for shell
|
|
my $ShellCommand = "timeout " . ($AudioLength + 3) . " " . $GstCommand . " &";
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - ShellCommand : " . $ShellCommand;
|
|
|
|
### Pass shell command to shell and continue with the code below
|
|
eval {
|
|
system($ShellCommand) or die "Could not execute" . $ShellCommand . " ". $@;
|
|
};
|
|
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - File streamed successf. : " . $AudioDataPathOrig;
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - ---------------------------------------------------------------";
|
|
return "The audio file: " . $AudioDataPathOrig . " has been streamed to the DoorBird";
|
|
}
|
|
### If Filepath does not exist
|
|
else {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Transmit_Audio - Path doesn't exist : " . $AudioDataPathOrig;
|
|
Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - ---------------------------------------------------------------";
|
|
return "The audio file: " . $AudioDataPathOrig . " does not exist!"
|
|
}
|
|
}
|
|
####END####### Define Subfunction for TRANSMIT AUDIO REQUEST ###################################################END#####
|
|
|
|
###START###### Define Subfunction for RECEIVE AUDIO REQUEST ###################################################START####
|
|
sub DoorBird_Receive_Audio($$) {
|
|
my ($hash, $option) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $Username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $Password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
my $Url = $hash->{helper}{URL};
|
|
my $Sox = $hash->{helper}{SOX};
|
|
my $AudioDataPathOrig = $option;
|
|
|
|
### For Test only
|
|
my $AudioLength = 7;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Live_Audio - ---------------------------------------------------------------";
|
|
|
|
### If file does not exist already
|
|
if ((-e $AudioDataPathOrig) == false) {
|
|
### Create new filepath from old filepath
|
|
my $AudioDataNew;
|
|
my $AudioDataSizeNew;
|
|
my $AudioDataPathNew = $AudioDataPathOrig;
|
|
$AudioDataPathNew =~ s/\..*//;
|
|
$AudioDataPathNew .= ".mp3";
|
|
|
|
### Create complete command URL for DoorBird depending on whether SessionIdSecurity has been enabled (>0) or disabled (=0)
|
|
my $UrlPrefix = "http://" . $Url . "/bha-api/audio-receive.cgi";
|
|
my $UrlPostfix;
|
|
|
|
### If SessionIdSec is enabled
|
|
if ($hash->{helper}{SessionIdSec} != 0) {
|
|
$UrlPostfix = "?sessionid=" . $hash->{helper}{SessionId};
|
|
}
|
|
### Id SessionID Security is disabled
|
|
else {
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
$UrlPostfix = " user=" . $username . " passwd=" . $password;
|
|
}
|
|
my $CommandURL = $UrlPrefix . $UrlPostfix;
|
|
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 1, $name. " : DoorBird_Live_Audio - CommandURL : " . $CommandURL ;
|
|
|
|
### Create the gst-lauch command
|
|
my $GstCommand = "gst-launch-1.0 filesrc location=<";
|
|
$GstCommand .= $CommandURL;
|
|
$GstCommand .= "> ! wavparse ! audioconvert ! lame ! filesink location=";
|
|
$GstCommand .= $AudioDataPathNew;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 1, $name. " : DoorBird_Live_Audio - GstCommand : " . $GstCommand;
|
|
|
|
### Create command for shell
|
|
my $ShellCommand = "timeout " . ($AudioLength + 3) . " " . $GstCommand . " &";
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 1, $name. " : DoorBird_Live_Audio - ShellCommand : " . $ShellCommand;
|
|
|
|
### Pass shell command to shell and continue with the code below
|
|
eval {
|
|
system($ShellCommand) or die "Could not execute" . $ShellCommand . " ". $@;
|
|
};
|
|
|
|
Log3 $name, 1, $name. " : DoorBird_Live_Audio - File streamed successf. : " . $AudioDataPathOrig;
|
|
Log3 $name, 1, $name. " : DoorBird_Live_Audio - ---------------------------------------------------------------";
|
|
return "The audio file: " . $AudioDataPathOrig . " has been streamed to the DoorBird";
|
|
}
|
|
### If Filepath does not exist
|
|
else {
|
|
### Log Entry
|
|
Log3 $name, 1, $name. " : DoorBird_Live_Audio - Path doesn't exist : " . $AudioDataPathOrig;
|
|
Log3 $name, 1, $name. " : DoorBird_Live_Audio - ---------------------------------------------------------------";
|
|
return "The audio file: " . $AudioDataPathOrig . " does not exist!"
|
|
}
|
|
}
|
|
####END####### Define Subfunction for RECEIVE VIDEO REQUEST ####################################################END#####
|
|
|
|
|
|
###START###### Define Subfunction for HISTORY IMAGE REQUEST ###################################################START####
|
|
### https://wiki.fhem.de/wiki/HttpUtils#HttpUtils_NonblockingGet
|
|
sub DoorBird_History_Request($$) {
|
|
my ($hash, $option) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $Name = $hash->{NAME};
|
|
my $Username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $Password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
my $PollingTimeout = $hash->{helper}{PollingTimeout};
|
|
my $url = $hash->{helper}{URL};
|
|
my $Method = "GET";
|
|
my $Header = "Accept: application/json";
|
|
my $err;
|
|
my $data;
|
|
my $UrlPostfix;
|
|
my $CommandURL;
|
|
|
|
### Create Timestamp
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
|
|
my $ImageTimeStamp = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d",$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
|
|
|
|
### Create first part command URL for DoorBird
|
|
my $UrlPrefix = "https://" . $url . "/bha-api/";
|
|
|
|
### If the Itereation is started for the first time = new polling
|
|
if ($hash->{helper}{HistoryDownloadCount} == 0) {
|
|
### Delete arrays of pictures
|
|
@{$hash->{helper}{Images}{History}{doorbell}} = ();
|
|
@{$hash->{helper}{Images}{History}{motionsensor}} = ();
|
|
$hash->{helper}{HistoryTime} = $ImageTimeStamp;
|
|
$hash->{helper}{HistoryDownloadActive} = true;
|
|
}
|
|
|
|
### Define STATE message
|
|
my $CountDown = $hash->{helper}{MaxHistory}*2 - $hash->{helper}{HistoryDownloadCount};
|
|
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "Downloading history: " . $CountDown, 1);
|
|
|
|
### Create the URL Index which is identical every 2nd: 1 1 2 2 3 3 4 4 5 5 6 6
|
|
my $UrlIndex=int(int($hash->{helper}{HistoryDownloadCount})/int(2))+1;
|
|
|
|
### As long the maximum ammount of Images for history events is not reached
|
|
if ($UrlIndex <= $hash->{helper}{MaxHistory}) {
|
|
### If the counter is even, download an image based on the doorbell event
|
|
if (0 == $hash->{helper}{HistoryDownloadCount} % 2) {
|
|
### Create Parameter for CommandURL for doorbell events
|
|
$UrlPostfix = "history.cgi?event=doorbell&index=" . $UrlIndex;
|
|
}
|
|
### If the counter is odd, download an image based on the motion sensor event
|
|
else {
|
|
### Create Parameter for CommandURL for motionsensor events
|
|
$UrlPostfix = "history.cgi?event=motionsensor&index=" . $UrlIndex;
|
|
}
|
|
}
|
|
### If the requested maximum number of Images for history events is reached
|
|
else {
|
|
### Reset helper
|
|
$hash->{helper}{HistoryDownloadActive} = false;
|
|
$hash->{helper}{HistoryDownloadCount} = 0;
|
|
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "connected", 1);
|
|
|
|
### Refresh Browser Window
|
|
FW_directNotify("#FHEMWEB:$FW_wname", "location.reload()", "") if defined($FW_wname);
|
|
|
|
### Return since Routine is finished or wrong parameter has been transfered.
|
|
return
|
|
}
|
|
|
|
### Create complete command URL for DoorBird
|
|
$CommandURL = $UrlPrefix . $UrlPostfix;
|
|
|
|
### Define Parameter for Non-BlockingGet
|
|
my $param = {
|
|
url => $CommandURL,
|
|
timeout => $PollingTimeout,
|
|
user => $Username,
|
|
pwd => $Password,
|
|
hash => $hash,
|
|
method => $Method,
|
|
header => $Header,
|
|
incrementalTimeout => 1,
|
|
callback => \&DoorBird_History_Request_Parse
|
|
};
|
|
|
|
### Initiate communication and close
|
|
HttpUtils_NonblockingGet($param);
|
|
|
|
return;
|
|
}
|
|
|
|
sub DoorBird_History_Request_Parse($) {
|
|
my ($param, $err, $data) = @_;
|
|
my $hash = $param->{hash};
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request ___________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - Download Index : " . $hash->{helper}{HistoryDownloadCount};
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - err : " . $err if (defined($err ));
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - length data : " . length($data) if (defined($data ));
|
|
# Log3 $name, 5, $name. " : DoorBird_History_Request - param : " . join("\n", @{[%{$param}]}) if (defined($param));
|
|
|
|
|
|
### If error message available
|
|
if ($err ne "") {
|
|
### Create Log entry
|
|
Log3 $name, 3, $name. " : DoorBird_History_Request - Error : " . $err if (defined($err ));
|
|
}
|
|
### if no error message available
|
|
else {
|
|
### If any image data available
|
|
if (defined $data) {
|
|
|
|
### Predefine Image Data and Image-hash and hash - reference
|
|
my $ImageData;
|
|
my $ImageTimeStamp;
|
|
my $ImageFileTimeStamp;
|
|
my $ImageFileName;
|
|
my %ImageDataHash;
|
|
my $ref_ImageDataHash = \%ImageDataHash;
|
|
|
|
### If http response code is 200 = OK
|
|
if ($param->{code} == 200) {
|
|
### Encode jpeg data into base64 data and remove lose newlines
|
|
$ImageData = MIME::Base64::encode($data);
|
|
$ImageData =~ s{\n}{}g;
|
|
|
|
### Create Timestamp
|
|
my $httpHeader = $param->{httpheader};
|
|
$httpHeader =~ s/^[^_]*X-Timestamp: //;
|
|
$httpHeader =~ s/\n.*//g;
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($httpHeader);
|
|
$ImageTimeStamp = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d",$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
$ImageFileTimeStamp = sprintf ( "%04d%02d%02d-%02d%02d%02d" ,$year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
}
|
|
### If http response code is 204 = Nno permission to download the event history
|
|
elsif ($param->{code} == 204) {
|
|
### Create Log entry
|
|
Log3 $name, 3, $name. " : DoorBird_History_Request - Error 204 : User not authorized to download event history";
|
|
|
|
### Create Error message
|
|
$ImageData = "Error 204: The user has no permission to download the event history.";
|
|
$ImageTimeStamp =" ";
|
|
}
|
|
### If http response code is 404 = No picture available to download the event history
|
|
elsif ($param->{code} == 404) {
|
|
### Create Log entry
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - Error 404 : No picture available to download event history. Check settings in DoorBird APP.";
|
|
|
|
### Create Error message
|
|
$ImageData = "Error 404: No picture available to download in the event history.";
|
|
$ImageTimeStamp =" ";
|
|
}
|
|
### If http response code is none of one above
|
|
else {
|
|
### Create Log entry
|
|
Log3 $name, 3, $name. " : DoorBird_History_Request - Unknown http response code : " . $param->{code};
|
|
|
|
### Create Error message
|
|
$ImageData = "Error : " . $param->{code};
|
|
$ImageTimeStamp =" ";
|
|
}
|
|
|
|
### Create the URL Index which is identical every 2nd: 1 1 2 2 3 3 4 4 5 5 6 6
|
|
my $UrlIndex=int(int($hash->{helper}{HistoryDownloadCount})/int(2))+1;
|
|
|
|
### If the counter is even, download an image based on the doorbell event
|
|
if (0 == $hash->{helper}{HistoryDownloadCount} % 2) {
|
|
my $HistoryDownloadCount = $hash->{helper}{HistoryDownloadCount};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - doorbell - HistoryCount: " . $HistoryDownloadCount;
|
|
|
|
### Save Image data and timestamp into hash
|
|
$ref_ImageDataHash->{data} = $ImageData;
|
|
$ref_ImageDataHash->{timestamp} = $ImageTimeStamp;
|
|
|
|
### Save image hash into array of hashes
|
|
push (@{$hash->{helper}{Images}{History}{doorbell}}, $ref_ImageDataHash);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - hash - ImageFileDir : " . $hash->{helper}{ImageFileDir};
|
|
|
|
### If pictures supposed to be saved as files
|
|
if ($hash->{helper}{ImageFileDir} ne "0") {
|
|
|
|
### Get current working directory
|
|
my $cwd = getcwd();
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - working directory : " . $cwd;
|
|
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - file system format : LINUX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($hash->{helper}{ImageFileDir} =~ /^\//) {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $cwd . "/" . $hash->{helper}{ImageFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{ImageFileDir} =~ /\/\z/) {
|
|
$ImageFileName .= $ImageFileTimeStamp . "_doorbell.jpg";
|
|
}
|
|
else {
|
|
$ImageFileName .= "/" . $ImageFileTimeStamp . "_doorbell.jpg";
|
|
}
|
|
}
|
|
|
|
### If the path is given as Windows file system format
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($hash->{helper}{ImageFileDir} != /^.:\//) {
|
|
$ImageFileName = $cwd . $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\\z/) {
|
|
$ImageFileName .= $ImageFileTimeStamp . "_doorbell.jpg";
|
|
}
|
|
else {
|
|
$ImageFileName .= "\\" . $ImageFileTimeStamp . "_doorbell.jpg";
|
|
}
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - ImageFileName : " . $ImageFileName;
|
|
|
|
### Open file or write error message in log
|
|
open my $fh, ">", $ImageFileName or do {
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_History_Request - open file error : " . $! . " - ". $ImageFileName;
|
|
};
|
|
|
|
### Write the base64 decoded data in file
|
|
print $fh decode_base64($ImageData) if defined($fh);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - write file : Successfully written " . $ImageFileName;
|
|
|
|
### Close file or write error message in log
|
|
close $fh or do {
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_History_Request - close file error : " . $! . " - ". $ImageFileName;
|
|
}
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - Index - doorbell : " . $UrlIndex;
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - ImageData - doorbell : " . length($ImageData);
|
|
}
|
|
### If the counter is odd, download an image based on the motion sensor event
|
|
else {
|
|
my $HistoryDownloadCount = $hash->{helper}{HistoryDownloadCount} - 50;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - motion - HistoryCount : " . $HistoryDownloadCount;
|
|
|
|
### Save Image data and timestamp into hash
|
|
$ref_ImageDataHash->{data} = $ImageData;
|
|
$ref_ImageDataHash->{timestamp} = $ImageTimeStamp;
|
|
|
|
### Save image hash into array of hashes
|
|
push (@{$hash->{helper}{Images}{History}{motionsensor}}, $ref_ImageDataHash);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - hash - ImageFileDir : " . $hash->{helper}{ImageFileDir};
|
|
|
|
### If pictures supposed to be saved as files
|
|
if ($hash->{helper}{ImageFileDir} ne "0") {
|
|
|
|
### Get current working directory
|
|
my $cwd = getcwd();
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - working directory : " . $cwd;
|
|
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - file system format : LINUX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($hash->{helper}{ImageFileDir} =~ /^\//) {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $cwd . "/" . $hash->{helper}{ImageFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{ImageFileDir} =~ /\/\z/) {
|
|
$ImageFileName .= $ImageFileTimeStamp . "_motionsensor.jpg";
|
|
}
|
|
else {
|
|
$ImageFileName .= "/" . $ImageFileTimeStamp . "_motionsensor.jpg";
|
|
}
|
|
}
|
|
|
|
### If the path is given as Windows file system format
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($hash->{helper}{ImageFileDir} != /^.:\//) {
|
|
$ImageFileName = $cwd . $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\\z/) {
|
|
$ImageFileName .= $ImageFileTimeStamp . "_motionsensor.jpg";
|
|
}
|
|
else {
|
|
$ImageFileName .= "\\" . $ImageFileTimeStamp . "_motionsensor.jpg";
|
|
}
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - ImageFileName : " . $ImageFileName;
|
|
|
|
### Open file or write error message in log
|
|
open my $fh, ">", $ImageFileName or do {
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_History_Request - open file error : " . $! . " - ". $ImageFileName;
|
|
};
|
|
|
|
### Write the base64 decoded data in file
|
|
print $fh decode_base64($ImageData) if defined($fh);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - write file : Successfully written " . $ImageFileName;
|
|
|
|
### Update the history list for images and videos
|
|
DoorBird_History_List($hash);
|
|
|
|
### Close file or write error message in log
|
|
close $fh or do {
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_History_Request - close file error : " . $! . " - ". $ImageFileName;
|
|
}
|
|
}
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - Index - motionsensor : " . $UrlIndex;
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - ImageData- motionsensor: " . length($ImageData);
|
|
}
|
|
}
|
|
### If no image data available
|
|
else {
|
|
### Create second part command URL for DoorBird based on iteration cycle
|
|
if (($hash->{helper}{HistoryDownloadCount} > 0) && $hash->{helper}{HistoryDownloadCount} <= 50) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - No Image doorbell : " . $hash->{helper}{HistoryDownloadCount};
|
|
}
|
|
elsif (($hash->{helper}{HistoryDownloadCount} > 50) && $hash->{helper}{HistoryDownloadCount} <= 100) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - No Image motionsensor : " . ($hash->{helper}{HistoryDownloadCount} -50);
|
|
}
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_Request - ERROR! Wrong Index b) : " . $hash->{helper}{HistoryDownloadCount};
|
|
}
|
|
}
|
|
}
|
|
|
|
### Increase Download Counter and download the next one
|
|
$hash->{helper}{HistoryDownloadCount}++;
|
|
DoorBird_History_Request($hash, "");
|
|
return
|
|
}
|
|
####END####### Define Subfunction for HISTORY IMAGE REQUEST ####################################################END#####
|
|
|
|
###START###### Define Subfunction for history list update as readings #########################################START####
|
|
sub DoorBird_History_List($) {
|
|
my ($hash) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
|
|
### If the HistoryFile List shall be created
|
|
if ($hash->{helper}{HistoryFilePath} == true) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List ___________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - The HistoryList has been activated. Processing...";
|
|
|
|
### Delete all older reading entries to files
|
|
#fhem("deleteReading " . $name . " HistoryFilePath.*", 1);
|
|
|
|
foreach my $DeleteReading (keys %{$hash->{READINGS}}) {
|
|
if ($DeleteReading =~ m/HistoryFilePath/ ){
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - Delete Reading : " . $DeleteReading;
|
|
|
|
### Delete Reading
|
|
readingsDelete($hash, $DeleteReading);
|
|
}
|
|
}
|
|
|
|
### Get current working directory
|
|
my $cwd = getcwd();
|
|
|
|
### If the ImageFileDir has been defined and images are taken
|
|
if ((defined($hash->{helper}{ImageFileDir})) && (($hash->{helper}{ImageFileDir} =~ m/\/fhem\/www/)) || (($hash->{helper}{ImageFileDir} =~ m/\\fhem\\www/))){
|
|
my $ImageFileName;
|
|
my @ImageFileList;
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - file system format : UNIX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($hash->{helper}{ImageFileDir} =~ /^\//) {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $cwd . "/" . $hash->{helper}{ImageFileDir};
|
|
}
|
|
}
|
|
|
|
### If the path is given as Windows file system format
|
|
if ($hash->{helper}{ImageFileDir} =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($hash->{helper}{ImageFileDir} != /^.:\//) {
|
|
$ImageFileName = $cwd . $hash->{helper}{ImageFileDir};
|
|
}
|
|
else {
|
|
$ImageFileName = $hash->{helper}{ImageFileDir};
|
|
}
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ImageFileName : " . $ImageFileName;
|
|
|
|
### Get list of directory items
|
|
if (opendir(FileSearch,$ImageFileName)) {
|
|
@ImageFileList = readdir(FileSearch);
|
|
close FileSearch;
|
|
|
|
### Define Types to be searched for
|
|
my @FileTypes = ("motionsensor", "doorbell", "keypad", "snapshot", "manual");
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
foreach my $SearchType (@FileTypes) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - SearchType : " . $SearchType;
|
|
|
|
### Extract all Filenames which matches the SearchType
|
|
my @ImageFileListSearch = grep {/$SearchType/} @ImageFileList;
|
|
|
|
### Extract all Filenames which matches the file extension
|
|
my @ImageFileListExt = grep {/jpg/} @ImageFileListSearch;
|
|
|
|
# Sort list
|
|
@ImageFileListExt=sort(@ImageFileListExt);
|
|
|
|
### Get the last n elements (hash->{helper}{MaxHistory})
|
|
@ImageFileListExt = ($hash->{helper}{MaxHistory} >= @ImageFileListExt) ? @ImageFileListExt : @ImageFileListExt[-$hash->{helper}{MaxHistory}..-1];
|
|
|
|
# Sort list
|
|
@ImageFileListExt=sort({$b cmp $a} @ImageFileListExt);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ImageFileListSearch : \n" . Dumper(@ImageFileListExt);
|
|
|
|
### Update Readings
|
|
my $index = 0;
|
|
foreach my $FileName (@ImageFileListExt){
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - FileName : " . $FileName;
|
|
|
|
### Extract timestamp from Filename
|
|
my $TimeStamp = substr($FileName,0,4) . "-" . substr($FileName, 4,2) . "-" . substr($FileName, 6,2) . " ";
|
|
$TimeStamp .= substr($FileName,9,2) . ":" . substr($FileName,11,2) . ":" . substr($FileName,13,2);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - TimeStamp : " . $TimeStamp;
|
|
|
|
### Complete relative path from /opt/fhem/www/tablet to $hash->{helper}{ImageFileDir}
|
|
my $FilePath = abs2rel(rel2abs($ImageFileName), rel2abs("/opt/fhem/www/tablet"));
|
|
my $ReadingsValue = $FilePath . "/" . $FileName;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - FilePath : " . $FilePath;
|
|
|
|
### Create ReadingsName for Imagepath and write reading
|
|
my $ReadingsName = "HistoryFilePath_" . $SearchType . "_Image_" . sprintf("%02d", $index);
|
|
readingsBulkUpdate($hash, $ReadingsName, $ReadingsValue);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ReadingsName-Image : " . $ReadingsName;
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ReadingsValue-Image : " . $ReadingsValue;
|
|
|
|
|
|
### Create ReadingsName for Timestamp and write reading
|
|
$ReadingsName = "HistoryFilePath_" . $SearchType . "_Image_" . sprintf("%02d", $index) . "_Timestamp";
|
|
readingsBulkUpdate($hash, $ReadingsName, $TimeStamp);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ReadingsName-Timestamp : " . $ReadingsName;
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ReadingsValue-Timestamp : " . $TimeStamp;
|
|
|
|
### Increase Index
|
|
$index++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
### Log Entry for warning
|
|
Log3 $name, 2, $name. " : DoorBird_History_List - The Attribute \"ImageFileDir\" leads to an directory which produced an error! - Does it actually exist?";
|
|
}
|
|
}
|
|
else {
|
|
### Log Entry for warning
|
|
Log3 $name, 4, $name. " : DoorBird_History_List - The ImageFileDir has not been provided or has not been created below the web-folder e.g. \"/opt/fhem/www/doorbird-images/\"";
|
|
}
|
|
### If the VideoFileDir has been defined and Videos are taken
|
|
if ((defined($hash->{helper}{VideoFileDir})) && (($hash->{helper}{VideoFileDir} =~ m/\/fhem\/www/) || (($hash->{helper}{ImageFileDir} =~ m/\\fhem\\www/)))){
|
|
my $VideoFileName;
|
|
my @VideoFileList;
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - file system format : UNIX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($hash->{helper}{VideoFileDir} =~ /^\//) {
|
|
$VideoFileName = $hash->{helper}{VideoFileDir};
|
|
}
|
|
else {
|
|
$VideoFileName = $cwd . "/" . $hash->{helper}{VideoFileDir};
|
|
}
|
|
}
|
|
|
|
### If the path is given as Windows file system format
|
|
if ($hash->{helper}{VideoFileDir} =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($hash->{helper}{VideoFileDir} != /^.:\//) {
|
|
$VideoFileName = $cwd . $hash->{helper}{VideoFileDir};
|
|
}
|
|
else {
|
|
$VideoFileName = $hash->{helper}{VideoFileDir};
|
|
}
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - VideoFileName : " . $VideoFileName;
|
|
|
|
### Get list of directory items
|
|
if (opendir(FileSearch,$VideoFileName)) {
|
|
@VideoFileList = readdir(FileSearch);
|
|
close FileSearch;
|
|
|
|
### Define Types to be searched for
|
|
my @FileTypes = ("motionsensor", "doorbell", "keypad", "snapshot", "manual");
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
foreach my $SearchType (@FileTypes) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - SearchType : " . $SearchType;
|
|
|
|
### Extract all Filenames which matches the SearchType
|
|
my @VideoFileListSearch = grep {/$SearchType/} @VideoFileList;
|
|
|
|
### Extract all Filenames which matches the file extension
|
|
my @VideoFileListExt = grep {/$hash->{helper}{VideoFileFormat}/} @VideoFileListSearch;
|
|
|
|
# Sort list
|
|
@VideoFileListExt=sort(@VideoFileListExt);
|
|
|
|
### Get the last n elements (hash->{helper}{MaxHistory})
|
|
@VideoFileListExt = ($hash->{helper}{MaxHistory} >= @VideoFileListExt) ? @VideoFileListExt : @VideoFileListExt[-$hash->{helper}{MaxHistory}..-1];
|
|
|
|
# Sort list
|
|
@VideoFileListExt=sort({$b cmp $a} @VideoFileListExt);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - VideoFileListSearch : \n" . Dumper(@VideoFileListExt);
|
|
|
|
### Update Readings
|
|
my $index = 0;
|
|
foreach my $FileName (@VideoFileListExt){
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - FileName : " . $FileName;
|
|
|
|
### Extract timestamp from Filename
|
|
my $TimeStamp = substr($FileName,0,4) . "-" . substr($FileName, 4,2) . "-" . substr($FileName, 6,2) . " ";
|
|
$TimeStamp .= substr($FileName,9,2) . ":" . substr($FileName,11,2) . ":" . substr($FileName,13,2);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - TimeStamp : " . $TimeStamp;
|
|
|
|
### Complete relative path from /opt/fhem/www/tablet to $hash->{helper}{VideoFileDir}
|
|
my $FilePath = abs2rel(rel2abs($VideoFileName), rel2abs("/opt/fhem/www/tablet"));
|
|
my $ReadingsValue = $FilePath . "/" . $FileName;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - FilePath : " . $FilePath;
|
|
|
|
### Create ReadingsName for Videopath and write reading
|
|
my $ReadingsName = "HistoryFilePath_" . $SearchType . "_Video_" . sprintf("%02d", $index);
|
|
readingsBulkUpdate($hash, $ReadingsName, $ReadingsValue);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ReadingsName-Video : " . $ReadingsName;
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ReadingsValue-Video : " . $ReadingsValue;
|
|
|
|
|
|
### Create ReadingsName for Timestamp and write reading
|
|
$ReadingsName = "HistoryFilePath_" . $SearchType . "_Video_" . sprintf("%02d", $index) . "_Timestamp";
|
|
readingsBulkUpdate($hash, $ReadingsName, $TimeStamp);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ReadingsName-Timestamp : " . $ReadingsName;
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - ReadingsValue-Timestamp : " . $TimeStamp;
|
|
|
|
### Increase Index
|
|
$index++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
### Log Entry for warning
|
|
Log3 $name, 2, $name. " : DoorBird_History_List - The Attribute \"VideoFileDir\" leads to an directory which produced an error! - Does it actually exist?";
|
|
}
|
|
}
|
|
else {
|
|
### Log Entry for warning
|
|
Log3 $name, 4, $name. " : DoorBird_History_List - The VideoFileDir has not been provided or has not been created below the web-folder e.g. \"/opt/fhem/www/doorbird-videos/\"";
|
|
}
|
|
|
|
### Initiate Readings Update
|
|
readingsEndUpdate($hash, 1);
|
|
}
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_History_List - The HistoryList has been disabled. Not Links to pictures are provided.";
|
|
}
|
|
}
|
|
####END####### Define Subfunction for history list update as readings ##########################################END#####
|
|
|
|
###START###### Define Subfunction for VIDEO REQUEST ###########################################################START####
|
|
sub DoorBird_Video_Request($$$$) {
|
|
my ($hash, $duration, $event, $timestamp) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $url = $hash->{helper}{URL};
|
|
my $Method = "GET";
|
|
my $Header = "Accept: application/json";
|
|
my $VideoFileName;
|
|
my $ReadingVideo;
|
|
my $err;
|
|
my $data;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request ___________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - duration : " . $duration;
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - event : " . $event;
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - timestamp : " . $timestamp;
|
|
|
|
### Create name for Reading holding the filename for the event triggered video
|
|
if ($event =~ m/doorbell/ ){
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - doorbell event old : " . $event;
|
|
### Extract doorbell pushbutton number from event
|
|
my $DoorbellNo = $event =~ s/doorbell_//;
|
|
### Reset event back to doorbell without pushbutton number
|
|
$event = "doorbell";
|
|
$ReadingVideo = "doorbell_video_" . sprintf("%03d", $DoorbellNo);
|
|
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - doorbellevent new : " . $event;
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - DoorbellNo : " . $DoorbellNo;
|
|
|
|
}
|
|
elsif ($event =~ m/motionsensor/ ){
|
|
$ReadingVideo = "motion_video";
|
|
}
|
|
elsif ($event =~ m/keypad/ ){
|
|
$ReadingVideo = "keypad_video";
|
|
}
|
|
elsif ($event =~ m/manual/ ){
|
|
$ReadingVideo = "manual_video";
|
|
}
|
|
else {
|
|
### Create Log entry
|
|
Log3 $name, 2, $name. " : DoorBird_LastEvent_Image - Unknown event. Breaking up";
|
|
|
|
### Exit sub
|
|
return
|
|
}
|
|
Log3 $name, 3, $name. " : DoorBird_Video_Request - ReadingVideo : " . $ReadingVideo;
|
|
|
|
### Create complete command URL for DoorBird depending on whether SessionIdSecurity has been enabled (>0) or disabled (=0)
|
|
my $UrlPrefix = "http://" . $url . "/bha-api/";
|
|
my $UrlPostfix;
|
|
if ($hash->{helper}{SessionIdSec} > 0) {
|
|
$UrlPostfix = "?sessionid=" . $hash->{helper}{SessionId};
|
|
}
|
|
else {
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
$UrlPostfix = "?http-user=". $username . "&http-password=" . $password;
|
|
}
|
|
my $CommandURL = $UrlPrefix . "video.cgi" . $UrlPostfix;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - CommandURL : " . $CommandURL ;
|
|
|
|
### Create Timestamp
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($timestamp);
|
|
my $VideoFileTimeStamp = sprintf ( "%04d%02d%02d-%02d%02d%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
|
|
|
|
### Update STATE of device
|
|
readingsSingleUpdate($hash, "state", "Retrieving video", 1);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - hash - VideoFileDir : " . $hash->{helper}{VideoFileDir};
|
|
|
|
### If attribute to video directory has been set
|
|
if ($hash->{helper}{VideoFileDir} ne "") {
|
|
|
|
### Get current working directory
|
|
my $cwd = getcwd();
|
|
my $VideoFileDir;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - working directory : " . $cwd;
|
|
|
|
### If the path is given as UNIX file system format
|
|
if ($cwd =~ /\//) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - file system format : LINUX";
|
|
|
|
### Find out whether it is an absolute path or an relative one (leading "/")
|
|
if ($hash->{helper}{VideoFileDir} =~ /^\//) {
|
|
|
|
$VideoFileName = $hash->{helper}{VideoFileDir};
|
|
}
|
|
else {
|
|
$VideoFileName = $cwd . "/" . $hash->{helper}{VideoFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{VideoFileDir} =~ /\/\z/) {
|
|
### Save directory
|
|
$VideoFileDir = $VideoFileName;
|
|
|
|
### Create complete datapath
|
|
$VideoFileName .= $VideoFileTimeStamp . "_" . $event . "." . $hash->{helper}{VideoFileFormat};
|
|
}
|
|
else {
|
|
### Save directory
|
|
$VideoFileDir = $VideoFileName . "/";
|
|
|
|
### Create complete datapath
|
|
$VideoFileName .= "/" . $VideoFileTimeStamp . "_" . $event . "." . $hash->{helper}{VideoFileFormat};
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - VideoFileName : " . $VideoFileName;
|
|
|
|
### Create command for shell
|
|
my $ShellCommand = "timeout " . $duration . " ffmpeg -hide_banner -loglevel panic -re -i '" . $CommandURL . "' -filter:v setpts=4.0*PTS -y " . $VideoFileName . " &";
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - ShellCommand : " . $ShellCommand;
|
|
|
|
### Pass shell command to shell and continue with the code below
|
|
eval {
|
|
system($ShellCommand) or die "Could not execute" . $ShellCommand . " ". $@;
|
|
};
|
|
### If error message appered
|
|
if ( $@ ) {
|
|
# $ErrorMessage = $@;
|
|
}
|
|
|
|
### Write Last video into reading
|
|
readingsSingleUpdate($hash, $ReadingVideo, $VideoFileName, 1);
|
|
}
|
|
|
|
### If the path is given as Windows file system format
|
|
if ($hash->{helper}{VideoFileDir} =~ /\\/) {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Video_Request - file system format : WINDOWS";
|
|
|
|
### Find out whether it is an absolute path or an relative one (containing ":\")
|
|
if ($hash->{helper}{VideoFileDir} != /^.:\//) {
|
|
$VideoFileName = $cwd . $hash->{helper}{VideoFileDir};
|
|
}
|
|
else {
|
|
$VideoFileName = $hash->{helper}{VideoFileDir};
|
|
}
|
|
|
|
### Check whether the last "/" at the end of the path has been given otherwise add it an create complete path
|
|
if ($hash->{helper}{VideoFileDir} =~ /\\\z/) {
|
|
### Save directory
|
|
$VideoFileDir = $VideoFileName;
|
|
|
|
### Create full datapath
|
|
$VideoFileName .= $VideoFileTimeStamp . "_" . $event . "." . $hash->{helper}{VideoFileFormat};
|
|
}
|
|
else {
|
|
### Save directory
|
|
$VideoFileDir = $VideoFileName . "\\";
|
|
|
|
### Create full datapath
|
|
$VideoFileName .= "\\" . $VideoFileTimeStamp . "_" . $event . "." . $hash->{helper}{VideoFileFormat};
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 2, $name. " : DoorBird_Video_Request - Video-Request ha not been implemented for Windows file system. Contact fhem forum and WIKI.";
|
|
}
|
|
|
|
### Free FileDirSpace if exxeeds maximum
|
|
DoorBird_FileSpace($hash, $VideoFileDir, $hash->{helper}{VideoFileFormat}, $hash->{helper}{VideoFileDirMaxSize});
|
|
|
|
### Update the history list for images and videos after the video has been taken
|
|
InternalTimer(gettimeofday()+$duration+3,"DoorBird_History_List", $hash, 0);
|
|
}
|
|
### If attribute to video directory has NOT been set
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 2, $name . " : DoorBird_Video_Request - Could not open directory for video files. See commandref for attribute \"VideoFileDir\".";
|
|
}
|
|
|
|
return;
|
|
}
|
|
####END####### Define Subfunction for VIDEO REQUEST ############################################################END#####
|
|
|
|
##START###### Define Subfunction for LIST FAVOURITES #########################################################START####
|
|
sub DoorBird_List_Favorites($$) {
|
|
my ($hash, $option) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $command = "favorites.cgi";
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
|
|
my $err = " ";
|
|
my $data = " ";
|
|
my $json;
|
|
|
|
### Obtain data
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Get - List_Favourites - err : " . $err;
|
|
Log3 $name, 5, $name. " : DoorBird_Get - List_Favourites - data : " . $data;
|
|
|
|
### If no error has been handed back
|
|
if ($err eq "") {
|
|
### If the option is asking for the JSON string
|
|
if (defined($option) && ($option =~ /JSON/i)) {
|
|
return $data;
|
|
}
|
|
### If the option is asking for nothing special
|
|
else {
|
|
### Check if json can be parsed into hash
|
|
eval
|
|
{
|
|
$json = decode_json(encode_utf8($data));
|
|
1;
|
|
}
|
|
or do
|
|
{
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_Get - Data cannot be parsed by JSON for : List_Favourites";
|
|
return $name. " : DoorBird_Get - Data cannot be parsed by JSON for List_Favourites";
|
|
};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Get - json : " . $json;
|
|
|
|
### Delete all Readings for Relay-Addresses
|
|
fhem( "deletereading $name Favorite_.*" );
|
|
|
|
### Initiate Bulk Update
|
|
readingsBeginUpdate($hash);
|
|
|
|
### For every chapter in the List of Favourites (e.g. SIP, http)
|
|
foreach my $FavoritChapter (keys %{$json}) {
|
|
### For every item in the List of chapters (e.g. 0, 1, 5 etc.)
|
|
foreach my $FavoritItem (keys %{$json->{$FavoritChapter}}) {
|
|
|
|
### Create first part of Reading
|
|
my $ReadingName = "Favorite_" . $FavoritChapter . "_" . $FavoritItem;
|
|
|
|
### Update Reading
|
|
readingsBulkUpdate($hash, $ReadingName . "_Title", $json->{$FavoritChapter}{$FavoritItem}{title});
|
|
readingsBulkUpdate($hash, $ReadingName . "_Value", $json->{$FavoritChapter}{$FavoritItem}{value});
|
|
|
|
### Log Entry for debugging purpose
|
|
Log3 $name, 5, $name. " : DoorBird_List_Favorites --------------------------------";
|
|
Log3 $name, 5, $name. " : DoorBird_List_Favorites - Reading : " . $ReadingName;
|
|
Log3 $name, 5, $name. " : DoorBird_List_Favorites - _Title : " . $json->{$FavoritChapter}{$FavoritItem}{title};
|
|
Log3 $name, 5, $name. " : DoorBird_List_Favorites - _Value : " . $json->{$FavoritChapter}{$FavoritItem}{title};
|
|
}
|
|
}
|
|
### Execute Readings Bulk Update
|
|
readingsEndUpdate($hash, 1);
|
|
return "Readings have been updated!\nPress F5 to refresh Browser.";
|
|
}
|
|
}
|
|
### If error has been handed back
|
|
else {
|
|
$err =~ s/^[^ ]*//;
|
|
return "ERROR!\nError Code:" . $err;
|
|
}
|
|
}
|
|
####END####### Define Subfunction for LIST FAVOURITES ##########################################################END#####
|
|
|
|
###START###### Define Subfunction for LIST SCHEDULES ##########################################################START####
|
|
sub DoorBird_List_Schedules($$) {
|
|
my ($hash, $option) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $command = "schedule.cgi";
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
|
|
my $err = " ";
|
|
my $data = " ";
|
|
my $json;
|
|
|
|
### Obtain data
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Get - List_Schedules - err : " . $err;
|
|
Log3 $name, 5, $name. " : DoorBird_Get - List_Schedules - data : " . $data;
|
|
|
|
### If no error has been handed back
|
|
if ($err eq "") {
|
|
### If the option is asking for the JSON string
|
|
if (defined($option) && ($option =~ /JSON/i)) {
|
|
return $data;
|
|
}
|
|
### If the option is asking for nothing special
|
|
else {
|
|
### Check if json can be parsed into hash
|
|
eval
|
|
{
|
|
$json = decode_json(encode_utf8($data));
|
|
1;
|
|
}
|
|
or do
|
|
{
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_List_Schedules - Data : " . $data;
|
|
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Get - Data cannot be parsed by JSON for : List_Schedules";
|
|
|
|
return $data;
|
|
};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Get - json : " . $json;
|
|
|
|
### Delete all Readings for Relay-Addresses
|
|
fhem( "deletereading $name Schedule_.*" );
|
|
|
|
### Initiate Bulk Update
|
|
readingsBeginUpdate($hash);
|
|
|
|
### For every chapter in the Array of elements
|
|
foreach my $Schedule (@{$json}) {
|
|
|
|
### Create first part of Reading
|
|
my $ReadingNameA = "Schedule_" . $Schedule->{input} . "_";
|
|
|
|
### If Parameter exists
|
|
if ($Schedule->{param} ne "") {
|
|
### Add Parameter
|
|
$ReadingNameA .= $Schedule->{param} . "_";
|
|
}
|
|
|
|
### For every chapter in the Array of elements
|
|
foreach my $Output (@{$Schedule->{output}}) {
|
|
|
|
my $ReadingNameB = $ReadingNameA . $Output->{event} ."_";
|
|
|
|
### If Parameter exists
|
|
if ($Output->{param} ne "") {
|
|
### Add Parameter
|
|
$ReadingNameB .= $Schedule->{param} . "_";
|
|
}
|
|
else {
|
|
### Add Parameter
|
|
$ReadingNameB .= "x_";
|
|
}
|
|
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Get - Schedules - ReadingName : " . $ReadingNameB;
|
|
|
|
# my $ReadingValue = $Output->($Output);
|
|
# Log3 $name, 5, $name. " : DoorBird_Get - Schedules - ReadingValue : " . $ReadingValue;
|
|
}
|
|
}
|
|
|
|
### Execute Readings Bulk Update
|
|
readingsEndUpdate($hash, 1);
|
|
return "Readings have been updated!\nPress F5 to refresh Browser.";
|
|
}
|
|
}
|
|
### If error has been handed back
|
|
else {
|
|
$err =~ s/^[^ ]*//;
|
|
return "ERROR!\nError Code:" . $err;
|
|
}
|
|
}
|
|
####END####### Define Subfunction for LIST SCHEDULES ###########################################################END#####
|
|
|
|
###START###### Define Subfunction for RESTART #################################################################START####
|
|
sub DoorBird_Restart($$) {
|
|
my ($hash, $option) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $command = "restart.cgi";
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $err;
|
|
my $data;
|
|
my $json;
|
|
|
|
### Activate Relay
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_Restart - err : " . $err;
|
|
Log3 $name, 5, $name. " : DoorBird_Restart - data : " . $data;
|
|
|
|
### If no error has been handed back
|
|
if ($err eq "") {
|
|
### Log Entry
|
|
Log3 $name, 3, $name. " : DoorBird_Restart - Reboot request successfully transmitted to DoorBird";
|
|
|
|
return "Reboot request successfully transmitted to DoorBird\nData: " . $data;
|
|
}
|
|
### If error has been handed back
|
|
else {
|
|
### Cut off url from error message
|
|
$err =~ s/^[^ ]*//;
|
|
|
|
### Log Entry
|
|
Log3 $name, 2, $name. " : DoorBird_Restart - Reboot command failed. ErrorMsg: " . $err;
|
|
|
|
return "ERROR!\nError Code:" . $err . "\nData: " . $data;
|
|
}
|
|
}
|
|
####END####### Define Subfunction for RESTART ##################################################################END#####
|
|
|
|
|
|
###START###### Define Subfunction for SIP Status REQUEST ######################################################START####
|
|
sub DoorBird_SipStatus_Request($$) {
|
|
my ($hash, $option) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $command = "sip.cgi?action=status";
|
|
my $method = "GET";
|
|
my $header = "Accept: application/json";
|
|
|
|
my $err = " ";
|
|
my $data = " ";
|
|
my $json;
|
|
|
|
### Obtain data
|
|
($err, $data) = DoorBird_BlockGet($hash, $command, $method, $header);
|
|
|
|
### Remove Newlines for better log entries
|
|
my $ShowData = $data;
|
|
$ShowData =~ s/[\t]//g;
|
|
$ShowData =~ s/[\r]//g;
|
|
$ShowData =~ s/[\n]//g;
|
|
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- err : " . $err if(defined($err));
|
|
# Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- data : " . $ShowData if(defined($ShowData));
|
|
|
|
### If no error has been handed back
|
|
if ($err eq "") {
|
|
### If the option is asking for the JSON string
|
|
if (defined($option) && ($option =~ /JSON/i)) {
|
|
return $data;
|
|
}
|
|
### If the option is asking for nothing special
|
|
else {
|
|
### Check if json can be parsed into hash
|
|
eval
|
|
{
|
|
$json = decode_json(encode_utf8($data));
|
|
1;
|
|
}
|
|
or do
|
|
{
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_SipStatus_Req- Data cannot parsed JSON : Info_Request";
|
|
return $name. " : DoorBird_SipStatus_Req- Data cannot be parsed by JSON for Info_Request";
|
|
};
|
|
|
|
my $VersionContent = $json-> {BHA}{SIP}[0];
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- json : " . Dumper($json);
|
|
|
|
### Initiate Bulk Update
|
|
readingsBeginUpdate($hash);
|
|
|
|
foreach my $key (keys %{$VersionContent}) {
|
|
|
|
### If the entry are information about connected INCOMING_CALL_USER
|
|
if ( $key eq "INCOMING_CALL_USER") {
|
|
|
|
### Split all Call User in array
|
|
my @CallUserArray = split(";", $VersionContent -> {$key});
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- CallUser : " . Dumper(@CallUserArray);
|
|
|
|
### Count Number of current readings containing call user
|
|
my $CountCurrentCallUserReadings = 0;
|
|
foreach my $CurrentCallUserReading (keys(%{$hash->{READINGS}})) {
|
|
if ($CurrentCallUserReading =~ m/SIP_INCOMING_CALL_USER_/){
|
|
$CountCurrentCallUserReadings++;
|
|
}
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- CountCurrentCallUserReadings : " . $CountCurrentCallUserReadings;
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- CallUserArray : " . @CallUserArray;
|
|
|
|
### If the number of call user in DoorBird unit is smaller than the number of Call user readings then delete all respective readings first
|
|
if (@CallUserArray < $CountCurrentCallUserReadings) {
|
|
fhem("deletereading $name SIP_INCOMING_CALL_USER_.*");
|
|
}
|
|
|
|
### For every Call-User do
|
|
my $CallUserId;
|
|
foreach my $CallUser (@CallUserArray) {
|
|
|
|
### Increment Counter
|
|
$CallUserId++;
|
|
|
|
### Delete "sip:" if exists
|
|
$CallUser =~ s/^[^:]*://;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- Content of" . sprintf("%15s %-s", "SIP_INCOMING_CALL_USER_" . sprintf("%02d",$CallUserId), ": " . "sip:" . $CallUser);
|
|
|
|
### Update Reading
|
|
readingsBulkUpdate($hash, "SIP_INCOMING_CALL_USER_" . sprintf("%02d",$CallUserId), "sip:" . $CallUser);
|
|
}
|
|
}
|
|
### If the entry are information about connected relais
|
|
elsif ( $key =~ m/relais:/) {
|
|
|
|
### Extract number, swap to Uppercase and concat to new Readingsname
|
|
my ($RelaisNumer) = $key =~ /(\d+)/g;
|
|
|
|
my $NewReadingsName = uc($key);
|
|
$NewReadingsName =~ s/:.*//;
|
|
$NewReadingsName = "SIP_" . $NewReadingsName . "_" . sprintf("%02d",$RelaisNumer);
|
|
|
|
### Update Reading
|
|
readingsBulkUpdate($hash, $NewReadingsName, $VersionContent -> {$key});
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- Content of" . sprintf("%15s %-s", $key, ": " . $VersionContent -> {$key});
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- Content of" . sprintf("%15s %-s", "NewReadingsName", ": " . $NewReadingsName);
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- Content of" . sprintf("%15s %-s", "RelaisNumber", ": " . $RelaisNumer);
|
|
}
|
|
### For all other entries
|
|
else {
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_SipStatus_Req- Content of" . sprintf("%15s %-s", $key, ": " . $VersionContent -> {$key});
|
|
|
|
### Update Reading
|
|
readingsBulkUpdate($hash, "SIP_" . $key, $VersionContent -> {$key} );
|
|
}
|
|
}
|
|
|
|
### Execute Readings Bulk Update
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
return "Readings have been updated!\n";
|
|
}
|
|
}
|
|
### If error has been handed back
|
|
else {
|
|
$err =~ s/^[^ ]*//;
|
|
return "ERROR!\nError Code:" . $err;
|
|
}
|
|
}
|
|
####END####### Define Subfunction for SIP Status REQUEST #####################################################END#####
|
|
|
|
|
|
###START###### Encrypt Credential #############################################################################START####
|
|
sub DoorBird_credential_encrypt($) {
|
|
my ($decoded) = @_;
|
|
my $key = getUniqueId();
|
|
my $encoded;
|
|
|
|
return $decoded if( $decoded =~ /\Qcrypt:\E/ );
|
|
|
|
for my $char (split //, $decoded) {
|
|
my $encode = chop($key);
|
|
$encoded .= sprintf("%.2x",ord($char)^ord($encode));
|
|
$key = $encode.$key;
|
|
}
|
|
|
|
return 'crypt:'.$encoded;
|
|
}
|
|
####END####### Encrypt Credential ##############################################################################END#####
|
|
|
|
###START###### Decrypt Credential #############################################################################START####
|
|
sub DoorBird_credential_decrypt($) {
|
|
my ($encoded) = @_;
|
|
my $key = getUniqueId();
|
|
my $decoded;
|
|
|
|
return $encoded if( $encoded !~ /crypt:/ );
|
|
|
|
$encoded = $1 if( $encoded =~ /crypt:(.*)/ );
|
|
|
|
for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) {
|
|
my $decode = chop($key);
|
|
$decoded .= chr(ord($char)^ord($decode));
|
|
$key = $decode.$key;
|
|
}
|
|
|
|
return $decoded;
|
|
}
|
|
####END####### Decrypt Credential ##############################################################################END#####
|
|
|
|
###START###### Blocking Get ###################################################################################START####
|
|
sub DoorBird_BlockGet($$$$) {
|
|
### https://wiki.fhem.de/wiki/HttpUtils#HttpUtils_BlockingGet
|
|
|
|
### Extract subroutine parameter from caller
|
|
my ($hash, $ApiCom, $Method, $Header) = @_;
|
|
|
|
### Obtain values from hash
|
|
my $name = $hash->{NAME};
|
|
my $username = DoorBird_credential_decrypt($hash->{helper}{".USER"});
|
|
my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"});
|
|
my $url = $hash->{helper}{URL};
|
|
my $PollingTimeout = $hash->{helper}{PollingTimeout};
|
|
|
|
### Create complete command URL for DoorBird
|
|
my $UrlPrefix = "https://" . $url . "/bha-api/";
|
|
my $CommandURL = $UrlPrefix . $ApiCom;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_BlockingGet - CommandURL : " . $CommandURL;
|
|
|
|
my $param = {
|
|
url => $CommandURL,
|
|
user => $username,
|
|
pwd => $password,
|
|
timeout => $PollingTimeout,
|
|
hash => $hash,
|
|
method => $Method,
|
|
header => $Header
|
|
};
|
|
|
|
### Initiate communication and close
|
|
my ($err, $data) = HttpUtils_BlockingGet($param);
|
|
|
|
return($err, $data);
|
|
}
|
|
####END####### Blocking Get ####################################################################################END#####
|
|
|
|
###START###### Calculate relative path ########################################################################START####
|
|
sub DoorBird_Rel_Path($$) {
|
|
my ($start,$target) = @_;
|
|
$start =~ s:/[^/]+:/:; # remove trailing filename
|
|
my @spath = grep {$_ ne ''} split(/\//,$start);
|
|
my @tpath = grep {$_ ne ''} split(/\//,$target);
|
|
|
|
# strip common start of the path
|
|
while ( @spath && @tpath && $spath[0] eq $tpath[0]) {
|
|
shift @spath;
|
|
shift @tpath;
|
|
}
|
|
|
|
return ("../"x(@spath)).join('/', @tpath);
|
|
}
|
|
####END####### Calculate relative path #########################################################################END#####
|
|
|
|
###START###### Processing Change Log ##########################################################################START####
|
|
# Changelog parser for DoorBird changelog as of 2020-02-22 (or earlier) containing multiple product lines.
|
|
# Returns a hash ref containing the newest version number for each product name or prefix found.
|
|
#
|
|
# Prefixes are denoted by a trailing 'x', as in the original changelog. Note:
|
|
# this means that still multiple versions matching a single product could be in the hash,
|
|
# e. g. for different prefixes all matching the final product name.
|
|
|
|
sub DoorBird_parseChangelog($$) {
|
|
my ($hash, $data) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $lines = IO::String->new($data);
|
|
my $all_versions;
|
|
my $version;
|
|
|
|
### Log Entry for debugging purposes
|
|
# Log3 $name, 5, $name. " : DoorBird_parseChangelog - data : " . $data;
|
|
|
|
|
|
### For all lines do
|
|
while(my $line = <$lines>) {
|
|
|
|
### If the line contains the keywords "Firmware version " followed by a number then obtain it
|
|
if ($line =~ m/^Firmware version /) {
|
|
( $version ) = $line =~ /(\d+)/;
|
|
}
|
|
|
|
### If the line contains the keywords "Products affected: " then obtain it
|
|
elsif ($line =~ /^Products affected: (.*)$/) {
|
|
|
|
### If version is already obtained from the changelog
|
|
if (defined($version)) {
|
|
|
|
### Split the product names into an array
|
|
my @products = split(/,\s*/, $1);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_parseChangelog - found product : " . $version;
|
|
|
|
### For each product name mentioned in the changelog
|
|
foreach my $product (@products) {
|
|
### Apparently the line of the "Products affected" in current changelog file is not closed with an \r so we ignore this array - entry
|
|
next if $product =~ /Preceding version: /;
|
|
|
|
### If the Product version for the firmware ha snot yet been defined or the already obtaine version number is older than the current value
|
|
if (!defined($all_versions->{$product}) or 0 + $all_versions->{$product} < 0 + $version) {
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_parseChangelog - found firmware version : " . $version . " for " . $product;
|
|
|
|
### Save latest firmware version
|
|
$all_versions->{$product} = $version;
|
|
}
|
|
}
|
|
undef $version;
|
|
}
|
|
### If version cannot be found
|
|
else {
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 3, $name. " : DoorBird_parseChangelog - Products without version found in changelog, ignored.";
|
|
}
|
|
}
|
|
}
|
|
return $all_versions;
|
|
}
|
|
|
|
# Find newest firmware version for this device by name or prefix.
|
|
# The versions hash ref expected as second argument should match the format returned from DoorBird_parseChangelog().
|
|
sub DoorBird_findNewestFWVersion($$$) {
|
|
my ($hash, $versions, $product_name) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $newest = 0;
|
|
|
|
### For all version entries
|
|
foreach my $product (sort keys %$versions)
|
|
{
|
|
### Optional prefix matching
|
|
my $prefix = $product;
|
|
$prefix =~ s/x$//;
|
|
|
|
### If the installed product name is (partial) identical with the product name given in the changelog file entry
|
|
if (length($prefix) <= length($product_name) and $prefix eq substr($product_name, 0, length($prefix)) and 0 + $newest < 0 + $versions->{$product}) {
|
|
$newest = $versions->{$product};
|
|
}
|
|
}
|
|
|
|
return $newest;
|
|
}
|
|
####END####### Processing Change Log ###########################################################################END#####
|
|
|
|
###START###### Limit File Space ###############################################################################START####
|
|
sub DoorBird_FileSpace($$$$) {
|
|
my ($hash, $FileDir, $FileExt, $FileDirMaxSize) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - __________________________________________________________";
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - FileDir : " . $FileDir;
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - FileExt : " . $FileExt;
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - FileDirMaxSize : " . $FileDirMaxSize . " MByte";
|
|
|
|
|
|
|
|
|
|
my $SizeOfFiles = 0;
|
|
my @FileList;
|
|
opendir( my $dh, $FileDir ) or die "Cannot opendir '$FileDir': $!\n";
|
|
|
|
### Search for files of specified extension (FileExt) in specified directory (FileDir)
|
|
for my $i ( readdir( $dh ) ) {
|
|
if ($i =~ m/$FileExt/) {
|
|
|
|
push(@FileList, $FileDir . $i);
|
|
my $s = -s $FileDir . $i;
|
|
|
|
$SizeOfFiles += $s;
|
|
$SizeOfFiles += getdirsize( $FileDir . $i ) if -d $FileDir . $i && $i !~ /^\.\.?$/;
|
|
}
|
|
}
|
|
|
|
### Sort list of files by name => timestamp
|
|
@FileList = sort(@FileList);
|
|
|
|
### Log Entry for debugging purposes
|
|
#Log3 $name, 5, $name. " : DoorBird_FileSpace - FileList sorted : \n" . Dumper(@FileList);
|
|
|
|
### Transform Byte in MByte
|
|
$SizeOfFiles = int($SizeOfFiles / 1024 / 1024);
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - Dirsize : " . $SizeOfFiles . " MByte";
|
|
|
|
if ($SizeOfFiles > $FileDirMaxSize) {
|
|
|
|
### Calculate Delta Volume to be deleted
|
|
my $DeltaVol = $SizeOfFiles - $FileDirMaxSize;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - MaxDirSize exceeded dV : " . $DeltaVol . " MByte";
|
|
|
|
my $CountVol = 0;
|
|
my @FileListToBeDeleted;
|
|
|
|
### If there are files available at all which could be deleted
|
|
if (@FileList > 0) {
|
|
my $i = 0;
|
|
|
|
### As long their need more files to be deleted
|
|
while (($CountVol / 1024 / 1024) < $DeltaVol) {
|
|
|
|
### Add to the list of deleted files
|
|
push (@FileListToBeDeleted, $FileList[$i]);
|
|
|
|
### Sum up the volume freed if file is deleted
|
|
$CountVol += -s $FileList[$i];
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - CountVol collected so far : " . int($CountVol/1024/1024) . " MByte";
|
|
|
|
$i++
|
|
}
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - FileListToBeDeleted : \n" . Dumper(@FileListToBeDeleted);
|
|
}
|
|
|
|
### Delete oldestFile
|
|
my $NoOfDeletedFiles = unlink @FileListToBeDeleted;
|
|
|
|
### Log Entry for debugging purposes
|
|
Log3 $name, 5, $name. " : DoorBird_FileSpace - NumberOfDeletedFiles : " . $NoOfDeletedFiles;
|
|
}
|
|
|
|
### Close directory
|
|
closedir( $dh );
|
|
}
|
|
####END####### Limit File Space ################################################################################END#####
|
|
|
|
1;
|
|
|
|
###START###### Description for fhem commandref ################################################################START####
|
|
=pod
|
|
=item device
|
|
=item summary Connects fhem to the DoorBird IP door station
|
|
=item summary_DE Verbindet fhem mit der DoorBird IP Türstation
|
|
=begin html
|
|
|
|
<a name="DoorBird"></a>
|
|
<h3>DoorBird</h3>
|
|
<ul>
|
|
<table>
|
|
<tr>
|
|
<td>
|
|
The DoorBird module establishes the communication between the DoorBird - door intercommunication unit and the fhem home automation based on the official API, published by the manufacturer.<BR>
|
|
Please make sure, that the user has been enabled the API-Operator button in the DoorBird Android/iPhone APP under "Administration -> User -> Edit -> Permission -> API-Operator".
|
|
The following packet - installations are pre-requisite if not already installed by other modules (Examples below tested on Raspbian):<BR>
|
|
<BR>
|
|
<code>
|
|
<li>sudo apt-get install sox </li>
|
|
<li>sudo apt-get install libsox-fmt-all </li>
|
|
<li>sudo apt-get install libsodium-dev </li>
|
|
<li>sudo apt-get install gstreamer1.0-tools </li>
|
|
<li>sudo cpan Crypt::Argon2 </li>
|
|
<li>sudo cpan Alien::Base::ModuleBuild </li>
|
|
<li>sudo cpan Alien::Sodium </li>
|
|
<li>sudo cpan Crypt::NaCl::Sodium </li>
|
|
</code>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<BR>
|
|
<table>
|
|
<tr><td><a name="DoorBirddefine"></a><b>Define</b></td></tr>
|
|
<tr><td><ul><code>define <name> DoorBird <IPv4-address> <Username> <Password></code> <BR> </ul></td></tr>
|
|
<tr><td><ul><ul><code><name> </code> : The name of the device. Recommendation: "myDoorBird". <BR> </ul></ul></td></tr>
|
|
<tr><td><ul><ul><code><IPv4-address> </code> : A valid IPv4 address of the KMxxx. You might look into your router which DHCP address has been given to the DoorBird unit. <BR> </ul></ul></td></tr>
|
|
<tr><td><ul><ul><code><Username> </code> : The username which is required to sign on the DoorBird. <BR> </ul></ul></td></tr>
|
|
<tr><td><ul><ul><code><Password> </code> : The password which is required to sign on the DoorBird. <BR> </ul></ul></td></tr>
|
|
</table>
|
|
<BR>
|
|
<table>
|
|
<tr><td><a name="DoorBirdSet"></a><b>Set</b></td></tr>
|
|
<tr><td><ul>The set function is able to change or activate the following features as follows: <BR> </ul> </td></tr>
|
|
<tr><td><ul><a name="Light_On" ></a><li><b><u><code>set Light_On </code></u></b> : Activates the IR lights of the DoorBird unit. The IR - light deactivates automatically by the default time within the Doorbird unit <BR></li></ul> </td></tr>
|
|
<tr><td><ul><a name="Live_Audio" ></a><li><b><u><code>set Live_Audio <on:off> </code></u></b> : Activate/Deactivate the Live Audio Stream of the DoorBird on or off and toggles the direct link in the <b>hidden</b> Reading <code>.AudioURL</code> <BR></li></ul> </td></tr>
|
|
<tr><td><ul><a name="Live_Video" ></a><li><b><u><code>set Live_Video <on:off> </code></u></b> : Activate/Deactivate the Live Video Stream of the DoorBird on or off and toggles the direct link in the <b>hidden</b> Reading <code>.VideoURL</code> <BR></li></ul> </td></tr>
|
|
<tr><td><ul><a name="Open_Door" ></a><li><b><u><code>set Open_Door <Value> </code></u></b> : Activates the Relay of the DoorBird unit with the given address. The list of installed relay addresses are imported with the initialization of parameters. <BR></li></ul> </td></tr>
|
|
<tr><td><ul><a name="Restart" ></a><li><b><u><code>set Restart </code></u></b> : Sends the command to restart (reboot) the Doorbird unit <BR></li></ul> </td></tr>
|
|
<tr><td><ul><a name="Transmit_Audio" ></a><li><b><u><code>set Transmit_Audio <Path> </code></u></b> : Converts a given audio file and transmits the stream to the DoorBird speaker. Requires a datapath to audio file to be converted and send. The user "fhem" needs to have write access to this directory. <BR></li></ul> </td></tr>
|
|
</table>
|
|
<BR>
|
|
<table>
|
|
<tr><td><a name="DoorBirdGet"></a><b>Get</b></td></tr>
|
|
<tr><td><ul>The get function is able to obtain the following information from the DoorBird unit:<BR></ul></td></tr>
|
|
<tr><td><ul><a name="History_Request" ></a><li><b><u><code>get History_Request </code></u></b> : Downloads the pictures of the last events of the doorbell and motion sensor. (Refer to attribute <code>MaxHistory</code>) <BR></li></ul> </td></tr>
|
|
<tr><td><ul><a name="Image_Request" ></a><li><b><u><code>get Image_Request </code></u></b> : Downloads the current Image of the camera of DoorBird unit. <BR></li></ul> </td></tr>
|
|
<tr><td><ul><a name="Video_Request" ></a><li><b><u><code>get Video_Request <Value> </code></u></b> : Downloads the current Video of the camera of DoorBird unit for th etime in seconds given. <BR></li></ul> </td></tr>
|
|
<tr><td><ul><a name="Info_Request" ></a><li><b><u><code>get Info_Request </code></u></b> : Downloads the current internal setup such as relay configuration, firmware version etc. of the DoorBird unit. The obtained relay adresses will be used as options for the <code>Open_Door</code> command. <BR></li></ul> </td></tr>
|
|
</table>
|
|
<BR>
|
|
<table>
|
|
<tr><td><a name="DoorBirdAttr"></a><b>Attributes</b></td></tr>
|
|
<tr><td><ul>The following user attributes can be used with the DoorBird module in addition to the global ones e.g. <a href="#room">room</a>.<BR></ul></td></tr>
|
|
</table>
|
|
<table>
|
|
<tr><td><ul><ul><a name="disable" ></a><li><b><u><code>disable </code></u></b> : Stops the device from further reacting on UDP datagrams sent by the DoorBird unit.<BR>The default value is 0 = activated <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="KeepAliveTimeout" ></a><li><b><u><code>KeepAliveTimeout </code></u></b> : Timeout in seconds without still-alive UDP datagrams before state of device will be set to "disconnected".<BR>The default value is 30s <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="MaxHistory" ></a><li><b><u><code>MaxHistory </code></u></b> : Number of pictures to be downloaded from history for both - doorbell and motion sensor events.<BR>The default value is "50" which is the maximum possible. <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="PollingTimeout" ></a><li><b><u><code>PollingTimeout </code></u></b> : Timeout in seconds before download requests are terminated in cause of no reaction by DoorBird unit. Might be required to be adjusted due to network speed.<BR>The default value is 10s. <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="UdpPort" ></a><li><b><u><code>UdpPort </code></u></b> : Port number to be used to receice UDP datagrams. Ports are pre-defined by firmware.<BR>The default value is port 6524 <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="SessionIdSec" ></a><li><b><u><code>SessionIdSec </code></u></b> : Time in seconds for how long the session Id shall be valid, which is required for secure Video and Audio transmission. The DoorBird kills the session Id after 10min = 600s automatically. In case of use with CCTV recording units, this function must be disabled by setting to 0.<BR>The default value is 540s = 9min. <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="AudioFileDir" ></a><li><b><u><code>AudioFileDir </code></u></b> : The relative (e.g. "audio") or absolute (e.g. "/mnt/NAS/audio") with or without trailing "/" directory path to which the audio files supposed to be stored.<BR>The default value is <code>""</code> = disabled <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="AudioFileDirMaxSize" ></a><li><b><u><code>AudioFileDirmaxSize </code></u></b> : The maximum size of the AudioFileDir in Megabyte [MB]. If the maximum Size has been reached with audio files, the oldest files are deleted automatically<BR>The default value is <code>50</code> = 50MB <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="ImageFileDir" ></a><li><b><u><code>ImageFileDir </code></u></b> : The relative (e.g. "images") or absolute (e.g. "/mnt/NAS/images") with or without trailing "/" directory path to which the image files supposed to be stored.<BR>The default value is <code>""</code> = disabled <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="ImageFileDirMaxSize" ></a><li><b><u><code>ImageFileDirmaxSize </code></u></b> : The maximum size of the ImageFileDir in Megabyte [MB]. If the maximum Size has been reached with Image files, the oldest files are deleted automatically<BR>The default value is <code>50</code> = 50MB <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoFileDir" ></a><li><b><u><code>VideoFileDir </code></u></b> : The relative (e.g. "images") or absolute (e.g. "/mnt/NAS/images") with or without trailing "/" directory path to which the video files supposed to be stored.<BR>The default value is <code>""</code> = disabled <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoFileDirMaxSize" ></a><li><b><u><code>VideoFileDirmaxSize </code></u></b> : The maximum size of the VideoFileDir in Megabyte [MB]. If the maximum Size has been reached with Video files, the oldest files are deleted automatically<BR>The default value is <code>50</code> = 50MB <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoFileFormat" ></a><li><b><u><code>VideoFileFormat </code></u></b> : The file format for the video file to be stored<BR>The default value is <code>"mpeg"</code> <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoDurationDoorbell" ></a><li><b><u><code>VideoDurationDoorbell </code></u></b> : Time in seconds for how long the video shall be recorded in case of an doorbbell event.<BR>The default value is <code>0</code> = disabled <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoDurationMotion" ></a><li><b><u><code>VideoDurationMotion </code></u></b> : Time in seconds for how long the video shall be recorded in case of an motion sensor event.<BR>The default value is <code>0</code> = disabled <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="EventReset" ></a><li><b><u><code>EventReset </code></u></b> : Time in seconds after wich the Readings for the Events Events (e.g. "doorbell_button", "motions sensor", "keypad") shal be reset to "idle".<BR>The default value is 5s <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="WaitForHistory" ></a><li><b><u><code>WaitForHistory </code></u></b> : Time in seconds after wich the module shall wait for an history image triggered by an event is ready for download. Might be adjusted if fhem-Server and Doorbird unit have large differences in system time.<BR>The default value is 7s <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="OpsModeList" ></a><li><b><u><code>OpsModeList </code></u></b> : A space separated list of names for operational modes (e.g. "Normal Party Fire") on which the DoorBird reacts automatically on events.<BR>The default value is <code>""</code> = disabled <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="HistoryFilePath" ></a><li><b><u><code>HistoryFilePath </code></u></b> : Creates relative datapaths to the last pictures, and videos in order to indicate them directly (e.g. fhem ftui widget "image")<BR>The default value is <code>"0"</code> = disabled <BR></li></ul></ul></td></tr>
|
|
</table>
|
|
</ul>
|
|
=end html
|
|
=begin html_DE
|
|
|
|
<a name="DoorBird"></a>
|
|
<h3>DoorBird</h3>
|
|
<ul>
|
|
<table>
|
|
<tr>
|
|
<td>
|
|
Das DoorBird Modul ermöglicht die Komminikation zwischen der DoorBird Interkommunikationseinheit und dem fhem Automationssystem basierend auf der API des Herstellers her.<BR>
|
|
Für den vollen Funktionsumfang muss sichergestellt werden, dass das Setting "API-Operator" in der DoorBird Android/iPhone - APP unter "Administration -> User -> Edit -> Permission -> API-Operator" gesetzt ist.
|
|
Die folgenden Software - Pakete müssen noch zusätzlich installiert werden, sofern dies nicht schon durch andere Module erfolgt ist. (Die Beispiele sind auf dem Raspberry JESSIE gestestet):<BR>
|
|
<BR>
|
|
<code>
|
|
<li>sudo apt-get install sox </li>
|
|
<li>sudo apt-get install libsox-fmt-all </li>
|
|
<li>sudo apt-get install libsodium-dev </li>
|
|
<li>sudo apt-get install gstreamer1.0-tools </li>
|
|
<li>sudo cpan Crypt::Argon2 </li>
|
|
<li>sudo cpan Alien::Base::ModuleBuild </li>
|
|
<li>sudo cpan Alien::Sodium </li>
|
|
<li>sudo cpan Crypt::NaCl::Sodium </li>
|
|
</code>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<BR>
|
|
<table>
|
|
<tr><td><a name="DoorBirddefine"></a><b>Define</b></td></tr>
|
|
|
|
<tr><td><ul><code>define <name> DoorBird <IPv4-address> <Username> <Passwort></code> <BR> </ul></td></tr>
|
|
<tr><td><ul><ul><code><name> </code> : </td><td>Der Name des Device unter fhem. Beispiel: "myDoorBird". <BR> </ul></ul></td></tr>
|
|
<tr><td><ul><ul><code><IPv4-Addresse> </code> : </td><td>Eine gültige IPv4 - Addresse der DoorBird-Anlage. Ggf. muss man im Router nach der entsprechenden DHCP Addresse suchen, die der DoorBird Anlage vergeben wurde. <BR> </ul></ul></td></tr>
|
|
<tr><td><ul><ul><code><Username> </code> : </td><td>Der Username zum einloggen auf der DoorBird Anlage. <BR> </ul></ul></td></tr>
|
|
<tr><td><ul><ul><code><Passwort> </code> : </td><td>Das Passwort zum einloggen auf der DoorBird Anlage. <BR> </ul></ul></td></tr>
|
|
</table>
|
|
<BR>
|
|
<table>
|
|
<tr><td><a name="DoorBirdSet"></a><b>Set</b></td></tr>
|
|
<tr><td><ul>Die Set - Funktion ist in der lage auf der DoorBird - Anlage die folgenden Einstellungen vorzunehmen bzw. zu de-/aktivieren: <BR> </ul></td></tr>
|
|
<tr><td><ul><a name="Light_On" ></a><li><b><u><code>set Light_On </code></u></b> : Schaltet das IR lichht der DoorBird Anlage ein. Das IR Licht schaltet sich automatisch nach der in der DoorBird - Anlage vorgegebenen Default Zeit wieder aus. <BR></li> </ul></td></tr>
|
|
<tr><td><ul><a name="Live_Audio" ></a><li><b><u><code>set Live_Audio <on:off> </code></u></b> : Aktiviert/Deaktiviert den Live Audio Stream der DoorBird - Anlage Ein oder Aus und wechselt den direkten link in dem <b>versteckten</b> Reading <code>.AudioURL.</code> <BR></li> </ul></td></tr>
|
|
<tr><td><ul><a name="Live_Video" ></a><li><b><u><code>set Live_Video <on:off> </code></u></b> : Aktiviert/Deaktiviert den Live Video Stream der DoorBird - Anlage Ein oder Aus und wechselt den direkten link in dem <b>versteckten</b> Reading <code>.VideoURL.</code> <BR></li> </ul></td></tr>
|
|
<tr><td><ul><a name="Open_Door" ></a><li><b><u><code>set Open_Door <Value> </code></u></b> : Aktiviert das Relais der DoorBird - Anlage mit dessen Adresse. Die Liste der installierten Relais werden mit der Initialisierung der Parameter importiert. <BR></li> </ul></td></tr>
|
|
<tr><td><ul><a name="Restart" ></a><li><b><u><code>set Restart </code></u></b> : Sendet das Kommando zum rebooten der DoorBird - Anlage. <BR></li> </ul></td></tr>
|
|
<tr><td><ul><a name="Transmit_Audio" ></a><li><b><u><code>set Transmit_Audio <Path> </code></u></b> : Konvertiert die angegebene Audio-Datei und sendet diese zur Ausgabe an die DoorBird - Anlage. Es benötigt einen Dateipfad zu der Audio-Datei zu dem der User "fhem" Schreibrechte braucht (z.B.: /opt/fhem/audio). <BR></li> </ul></td></tr>
|
|
</table>
|
|
<BR>
|
|
<table>
|
|
<tr><td><a name="DoorBirdGet"></a><b>Get</b></td></tr>
|
|
<tr><td><ul>Die Get - Funktion ist in der lage von der DoorBird - Anlage die folgenden Informationen und Daten zu laden: <BR> </ul></td></tr>
|
|
<tr><td><ul><a name="History_Request" ></a><li><b><u><code>get History_Request </code></u></b> : Lädt die Bilder der letzten Ereignisse durch die Türklingel und dem Bewegungssensor herunter. (Siehe auch Attribut <code>MaxHistory</code>) <BR></li> </ul></td></tr>
|
|
<tr><td><ul><a name="Image_Request" ></a><li><b><u><code>get Image_Request </code></u></b> : Lädt das gegenwärtige Bild der DoorBird - Kamera herunter. <BR></li> </ul></td></tr>
|
|
<tr><td><ul><a name="Video_Request" ></a><li><b><u><code>get Video_Request <Value> </code></u></b> : Lädt das gegenwärtige Video der DoorBird - Kamera für die gegebene Zeit in Sekunden herunter. <BR></li> </ul></td></tr>
|
|
<tr><td><ul><a name="Info_Request" ></a><li><b><u><code>get Info_Request </code></u></b> : Lädt das interne Setup (Firmware Version, Relais Konfiguration etc.) herunter. Die übermittelten Relais-Adressen werden als Option für das Kommando <code>Open_Door</code> verwendet. <BR></li> </ul></td></tr>
|
|
</table>
|
|
<BR>
|
|
<table>
|
|
<tr><td><a name="DoorBirdAttr"></a><b>Attributes</b></td></tr>
|
|
<tr><td><ul>Die folgenden Attribute können mit dem DoorBird Module neben den globalen Attributen wie <a href="#room">room</a> verwednet werden.<BR></ul></td></tr>
|
|
</table>
|
|
<table>
|
|
<tr><td><ul><ul><a name="disable" ></a><li><b><u><code>disable </code></u></b> : Stoppt das Gerät von weiteren Reaktionen auf die von der DoorBird ß Anlage ausgesendeten UDP - Datageramme<BR>Der Default Wert ist 0 = aktiviert <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="KeepAliveTimeout" ></a><li><b><u><code>KeepAliveTimeout </code></u></b> : Timeout in Sekunden ohne "still-alive" - UDP Datagramme bevor der Status des Gerätes auf "disconnected" gesetzt wird.<BR>Der Default Wert ist 30s <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="MaxHistory" ></a><li><b><u><code>MaxHistory </code></u></b> : Anzahl der herunterzuladenden Bilder aus dem Historien-Archiv sowohl für Ereignisse seitens der Türklingel als auch für den Bewegungssensor.<BR>Der Default Wert ist "50" = Maximum. <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="PollingTimeout" ></a><li><b><u><code>PollingTimeout </code></u></b> : Timeout in Sekunden before der Download-Versuch aufgrund fehlender Antwort seitens der DoorBird-Anlage terminiert wird. Eine Adjustierung mag notwendig sein, sobald Netzwerk-Latenzen aufteten.<BR>Der Default-Wert ist 10s. <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="UdpPort" ></a><li><b><u><code>UdpPort </code></u></b> : Port Nummer auf welcher das DoorBird - Modul nach den UDP Datagrammen der DoorBird - Anlage hören soll. Die Ports sind von der Firmware vorgegeben.<BR>Der Default Port ist 6524 <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="SessionIdSec" ></a><li><b><u><code>SessionIdSec </code></u></b> : Zeit in Sekunden nach welcher die Session Id erneuert werden soll. Diese ist für die sichere Übertragung der Video und Audio Verbindungsdaten notwendig. Die DoorBird-Unit devalidiert die Session Id automatisch nach 10min. Für den Fall, dass die DoorBird Kamera an ein Überwachungssystem angebunden werden soll, muss diese Funktion ausser Betrieb genommen werden indem man den Wert auf 0 setzt 0.<BR>Der Default Wert ist 540s = 9min. <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="AudioFileDir" ></a><li><b><u><code>AudioFileDir </code></u></b> : Der relative (z.B. "audio") oder absolute (z.B. "/mnt/NAS/audio") Verzeichnispfad mit oder ohne nachfolgendem Pfadzeichen "/" in welchen die Audio-Dateien abgelegt sind.<BR>Der Default Wert ist <code>""</code> = deaktiviert <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="AudioFileDirMaxSize" ></a><li><b><u><code>AudioFileDirMaxSize </code></u></b> : Die maximale Größe des Unterverzeichnisses für die Audio-Dateien in Megabyte (MB). Beim Erreichen dieses Wertes, werden die ältesten Dateien automatisch gelöscht.<BR>Der Default Wert ist <code>50</code> = 50MB <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="ImageFileDir" ></a><li><b><u><code>ImageFileDir </code></u></b> : Der relative (z.B. "images") oderr absolute (z.B. "/mnt/NAS/images") Verzeichnispfad mit oder ohne nachfolgendem Pfadzeichen "/" in welchen die Video-Dateien gespeichert werden sollen.<BR>Der Default Wert ist <code>""</code> = deaktiviert <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="ImageFileDirMaxSize" ></a><li><b><u><code>ImageFileDirMaxSize </code></u></b> : Die maximale Größe des Unterverzeichnisses für die Image-Dateien in Megabyte (MB). Beim Erreichen dieses Wertes, werden die ältesten Dateien automatisch gelöscht.<BR>Der Default Wert ist <code>50</code> = 50MB <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoFileDir" ></a><li><b><u><code>VideoFileDir </code></u></b> : Der relative (z.B. "images") oder absolute (z.B. "/mnt/NAS/images") Verzeichnispfad mit oder ohne nachfolgendem Pfadzeichen "/" in welchen die Bild-Dateien gespeichert werden sollen.<BR>Der Default Wert ist <code>""</code> = deaktiviert <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoFileDirMaxSize" ></a><li><b><u><code>VideoFileDirMaxSize </code></u></b> : Die maximale Größe des Unterverzeichnisses für die Video-Dateien in Megabyte (MB). Beim Erreichen dieses Wertes, werden die ältesten Dateien automatisch gelöscht.<BR>Der Default Wert ist <code>50</code> = 50MB <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoFileFormat" ></a><li><b><u><code>VideoFileFormat </code></u></b> : Das Dateiformat für die Videodatei<BR>Der Default Wert ist <code>"mpeg"</code> <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoDurationDoorbell" ></a><li><b><u><code>VideoDurationDoorbell </code></u></b> : Zeit in Sekunden für wie lange das Video im Falle eines Klingel Events aufgenommen werden soll.<BR>Der Default Wert ist <code>0</code> = deaktiviert <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="VideoDurationMotion" ></a><li><b><u><code>VideoDurationMotion </code></u></b> : Zeit in Sekunden für wie lange das Video im Falle eines Bewegungssensor Events aufgenommen werden soll.<BR>Der Default Wert ist <code>0</code> = deaktiviert <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="EventReset" ></a><li><b><u><code>EventReset </code></u></b> : Zeit in Sekunden nach welcher die Readings für die Events (z.B. "doorbell_button", "motions sensor", "keypad")wieder auf "idle" gesetzt werden sollen.<BR>Der Default Wert ist 5s <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="WaitForHistory" ></a><li><b><u><code>WaitForHistory </code></u></b> : Zeit in Sekunden die das Modul auf das Bereitstellen eines korrespondierenden History Bildes zu einem Event warten soll. Muss ggf. adjustiert werden, sobald deutliche Unterschiede in der Systemzeit zwischen fhemßServer und DoorBird Station vorliegen.<BR>Der Default Wert ist 7s <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="OpsModeList" ></a><li><b><u><code>OpsModeList </code></u></b> : Eine durch Leerzeichen getrennte Liste von Namen für Operationszustände (e.g. "Normal Party Feuer" auf diese der DoorBird automatisch bei Events reagiert.<BR>Der Default Wert ist "" = deaktiviert <BR></li></ul></ul></td></tr>
|
|
<tr><td><ul><ul><a name="HistoryFilePath" ></a><li><b><u><code>HistoryFilePath </code></u></b> : Erstellt Dateipfade zu den letzten Bildern und Videos um sie in den User Interfaces direkt anzuzeigen (e.g. fhem ftui Widget "Image")<BR>Der Default Wert ist <code>"0"</code> = disabled <BR></li></ul></ul></td></tr>
|
|
</table>
|
|
</ul>
|
|
=end html_DE
|
|
=encoding utf8
|
|
=for :application/json;q=META.json 73_DoorBird.pm
|
|
{
|
|
"abstract": "Connects fhem to the DoorBird IP door station",
|
|
"description": "The DoorBird module establishes the communication between the DoorBird - door intercommunication unit and the fhem home automation based on the official API, published by the manufacturer. Please make sure, that the user has been enabled the API-Operator button in the DoorBird Android/iPhone APP under Administration -> User -> Edit -> Permission -> API-Operator.",
|
|
"x_lang": {
|
|
"de": {
|
|
"abstract": "Verbindet fhem mit der DoorBird IP Türstation",
|
|
"description": "Das DoorBird Modul ermöglicht die Komminikation zwischen der DoorBird Interkommunikationseinheit und dem fhem Automationssystem basierend auf der API des Herstellers her. Für den vollen Funktionsumfang muss sichergestellt werden, dass das Setting \"API-Operator\" in der DoorBird Android/iPhone - APP unter Administration -> User -> Edit -> Permission -> API-Operator gesetzt ist."
|
|
}
|
|
},
|
|
"license": [
|
|
"GPL_2"
|
|
],
|
|
"author": [
|
|
"Matthias Deeke <matthias.deeke@deeke.eu>"
|
|
],
|
|
"x_fhem_maintainer": [
|
|
"Sailor"
|
|
],
|
|
"keywords": [
|
|
"Doorbird",
|
|
"Intercom"
|
|
],
|
|
"prereqs": {
|
|
"runtime": {
|
|
"requires": {
|
|
"Alien::Base::ModuleBuild": 0,
|
|
"Alien::Sodium": 0,
|
|
"Crypt::Argon2": 0,
|
|
"Crypt::NaCl::Sodium": 0,
|
|
"IO::String": 0,
|
|
"Cwd": 0,
|
|
"Data::Dumper": 0,
|
|
"Encode": 0,
|
|
"HttpUtils": 0,
|
|
"IO::Socket": 0,
|
|
"JSON": 0,
|
|
"LWP::UserAgent": 0,
|
|
"MIME::Base64": 0,
|
|
"constant": 0,
|
|
"strict": 0,
|
|
"utf8": 0,
|
|
"warnings": 0,
|
|
"perl": 5.014
|
|
},
|
|
"recommends": {
|
|
},
|
|
"suggests": {
|
|
}
|
|
}
|
|
},
|
|
"x_prereqs_os_debian": {
|
|
"runtime": {
|
|
"requires": {
|
|
"sox": 0,
|
|
"libsox-fmt-all": 0,
|
|
"libsodium-dev": 0,
|
|
"gstreamer1.0-tools": 0
|
|
},
|
|
"recommends": {
|
|
},
|
|
"suggests": {
|
|
}
|
|
}
|
|
},
|
|
"resources": {
|
|
"x_support_community": {
|
|
"rss": "https://forum.fhem.de/index.php/topic,100758.msg",
|
|
"web": "https://forum.fhem.de/index.php/topic,100758.msg",
|
|
"subCommunity" : {
|
|
"rss" : "https://forum.fhem.de/index.php/topic,100758.msg",
|
|
"title" : "This sub-board will be first contact point",
|
|
"web" : "https://forum.fhem.de/index.php/topic,100758.msg"
|
|
}
|
|
},
|
|
"x_wiki" : {
|
|
"title" : "FHEM Wiki: DoorBird",
|
|
"web" : "https://wiki.fhem.de/wiki/DoorBird"
|
|
}
|
|
},
|
|
"x_support_status": "supported"
|
|
}
|
|
=end :application/json;q=META.json
|
|
=cut |