diff --git a/fhem/FHEM/73_DoorBird.pm b/fhem/FHEM/73_DoorBird.pm new file mode 100644 index 000000000..8fe8451aa --- /dev/null +++ b/fhem/FHEM/73_DoorBird.pm @@ -0,0 +1,2887 @@ +# $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=41758 +# Fhem Wiki : +# +# 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 . +# +# fhem.cfg: define DoorBird +# +# Example: +# define myDoorBird DoorBird 192.168.178.240 Username SecretPW +# +######################################################################################################################## + +######################################################################################################################## +# List of open Problems: +# +# Check on https://www.doorbird.com/checkonline for firmware - updates +# +# +# +# +# +# +######################################################################################################################## + +package main; + +use strict; +use warnings; +use utf8; +use JSON; +use HttpUtils; +use Encode; +use MIME::Base64 qw(); +use Crypt::NaCl::Sodium qw( :utils ); +use Crypt::Argon2 qw/argon2i_pass argon2i_verify argon2id_pass argon2id_verify/; +use IO::Socket; +use LWP::UserAgent; +use constant false => 0; +use constant true => 1; +use Data::Dumper; +use Alien::Sodium; + +my $cflags = Alien::Sodium->cflags; +my $libs = Alien::Sodium->libs; + +sub DoorBird_Define($$); +sub DoorBird_Undefine($$); + + +# use Inline C => <<'END'; +# int add(int x, int y) { + # return x + y; +# } + +# int subtract(int x, int y) { + # return x - y; +# } +# END + + + +###START###### Initialize module ##############################################################################START#### +sub DoorBird_Initialize($) +{ + my ($hash) = @_; + + $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->{AttrList} = "do_not_notify:1,0 " . + "header " . + "PollingTimeout:slider,1,1,20 " . + "MaxHistory:slider,0,1,50 " . + "KeepAliveTimeout " . + "UdpPort:6524,35344 " . + "SipDevice:" . join(",", devspec2array("TYPE=SIP")) . " " . + "SipNumber " . + "disable:1,0 " . + "debug:1,0 " . + "loglevel:0,1,2,3,4,5 " . + $readingFnAttributes; +} +####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 + fhem( "deletereading $name .*" ); + + ### 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 DoorBird \" instead!\nExamples for :\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 DoorBird \" 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.25"; + $hash->{helper}{SOX} = "/usr/bin/sox"; #On Windows systems use "C:\Programme\sox\sox.exe" + $hash->{helper}{URL} = $url; + $hash->{helper}{SipDevice} = AttrVal($name,"SipDevice",""); + $hash->{helper}{SipNumber} = AttrVal($name, "SipNumber", "**620"); + $hash->{helper}{debug} = 0; + $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}{UdpMessageId} = 0; + @{$hash->{helper}{RelayAdresses}} = (0); + @{$hash->{helper}{Images}{History}{doorbell}} = (); + @{$hash->{helper}{Images}{History}{motionsensor}} = (); + $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##### + + + ###START###### For Debugging purpose only ##################################################################START#### + 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 SipDevice : " . $hash->{helper}{SipDevice}; + ####END####### For Debugging purpose only ###################################################################END##### + + ### 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()+300, "DoorBird_LostConn", $hash, 0); + + return undef; +} +####END####### Activate module after module has been used via fhem command "define" ############################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 + DevIo_CloseDev($hash); + + ### 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}; + + ### 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 + fhem( "deletereading $name .*" ); + + ### 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 debug attribute has been provided + elsif ($a[2] eq "debug") { + ### Check whether debug is on + if ($a[3] == true) { + ### Set helper in hash + $hash->{helper}{debug} = true; + } + ### If debug is off + else { + ### Set helper in hash + $hash->{helper}{debug} = false; + } + } + ### 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]; + } + } + ### Check whether SipDevice attribute has been provided + elsif ($a[2] eq "SipDevice") { + ### Check whether SipDevice is defined as fhem device + if (defined($defs{$a[3]})) { + ### Set helper in hash + $hash->{helper}{SipDevice} = $a[3]; + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Attr - SipDevice set to : " . $hash->{helper}{SipDevice}; + } + else { + ### Set helper in hash + $hash->{helper}{SipDevice} = ""; + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Attr - SipDevice reset to : " . $hash->{helper}{SipDevice}; + } + } + ### Check whether SipNumber attribute has been provided + elsif ($a[2] eq "SipNumber") { + ### Check whether SipNumber is defined + if (defined($a[3])) { + ### Set helper in hash + $hash->{helper}{SipNumber} = $a[3]; + } + else { + ### Set helper in hash + $hash->{helper}{SipNumber} = "**620"; + } + } + ### 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") { + ### Stop Timer + RemoveInternalTimer($hash); + ### 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); + } + ### 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 .= "Image_Request:noArg History_Request:noArg "; + + + ### If debug modus is enabled and allows JSON extract + if ($hash->{helper}{debug} == 1) { + $usage .= " Info_Request:,JSON List_Favorites:,JSON List_Schedules:,JSON"; + } + ### If debug modus is NOT enabled + else { + $usage .= " Info_Request:noArg List_Favorites:noArg List_Schedules:noArg"; + } + 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); + } + ### LIVE IMAGE REQUEST + elsif ($command eq "Image_Request") { + ### Call Subroutine and hand back return value + return DoorBird_Image_Request($hash, $option); + } + ### 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 $optionString; + my @RelayAdresses = @{$hash->{helper}{RelayAdresses}}; + + ### 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 - 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); + + ### Define "set" menu + my $usage = 'Unknown argument, choose one of '; + + ### Create Selection List + $usage .= "Live_Video:on,off Open_Door:" . join(",", @RelayAdresses) . " Light_On:noArg Restart:noArg Live_Audio:on,off Transmit_Audio X_Test"; + + return $usage if $command eq '?'; + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Set - usage : " . $usage; + + ### 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 + return DoorBird_Open_Door($hash, $option) + } + ### 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) + } + + ### LIVE AUDIO TRANSMIT + elsif ($command eq "Transmit_Audio") { + ### Call Subroutine and hand back return value + return DoorBird_Transmit_Audio($hash, $option) + } + ### ADD OR CHANGE FAVORITE + ### DELETE FAVORITE + ### ADD OR UPDATE SCHEDULE ENTRY + ### DELETE SCHEDULE ENTRY + ### Test Decrypt + elsif ($command eq "X_Test") { + ### Call Subroutine and hand back return value + return DoorBird_Decrypt($hash); + } + ### If none of the above have been selected + else { + ### Do nothing + return + } +} +####END####### Manipulate service after "Set" command by fhem ##################################################END##### + +###START###### After return of UDP message ####################################################################START#### +sub DoorBird_Read($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $UdpMessageIdLast = $hash->{helper}{UdpMessageId}; + my $buf; + my $flags; + + ### Get defined PeerHost + my $url = $hash->{helper}{URL}; + + ### Get sending Peerhost + my $PeerHost = $hash->{CD}->peerhost; + + ### Get and unpack UDP Datagramm + $hash->{CD}->recv($buf, 1024, $flags); + + ### Unpack Hex-Package + my $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 all timer + RemoveInternalTimer($hash); + + ### 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 - buf : " . $buf; + Log3 $name, 5, $name. " : DoorBird_Read - data : " . $data; + 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_Decrypt ------------------------------ Encryption Version 1 in accordance to DoorBird API has been used ------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Udp hex : " . $data; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Udp hex : " . $HexFriendlyData; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Ident hex : " . $HexFriendlyIdent; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Version hex : " . $HexFriendlyVersion; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client OpsLimit hex : " . $HexFriendlyOpsLimit; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client MemLimit hex : " . $HexFriendlyMemLimit; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Salt hex : " . $HexFriendlySalt; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Nonce hex : " . $HexFriendlyNonce; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Cipher hex : " . $HexFriendlyCipherText; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + + ### Further encryption methods will be here... + } + } + } + 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###### Define Test-Function for UDP decryption ########################################################START#### +sub DoorBird_Decrypt($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $username = "foobar0001"; + my $password = "QzT3jeK3JY"; + my $msg = "Decryption failed"; + + ########################################################################################################################################################################################################## + ### Define Test UDP Package from the API example + my $buf = pack('C*' , 0xDE, 0xAD, 0xBE, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x77, 0x35, 0x36, 0xDC, + 0xC3, 0x0E, 0x2E, 0x84, 0x7E, 0x0E, 0x75, 0x29, 0xE2, 0x34, 0x60, 0xCF, 0xE3, 0xFF, 0xCC, 0x52, + 0x3F, 0x37, 0xB2, 0xF2, 0xDC, 0x1A, 0x71, 0x80, 0xF2, 0x9B, 0x2E, 0xA0, 0x27, 0xA9, 0x82, 0x41, + 0x9C, 0xCE, 0x45, 0x9D, 0x27, 0x45, 0x2E, 0x42, 0x14, 0xBE, 0x9C, 0x74, 0xE9, 0x33, 0x3A, 0x21, + 0xDB, 0x10, 0x78, 0xB9, 0xF6, 0x7B); + + ### Define the comparisons from the API example + my $TestReferenceUdp = "0xde 0xad 0xbe 0x01 0x00 0x00 0x00 0x04 0x00 0x00 0x20 0x00 0x77 0x35 0x36 0xdc 0xc3 0x0e 0x2e 0x84 0x7e 0x0e 0x75 0x29 0xe2 0x34 0x60 0xcf 0xe3 0xff 0xcc 0x52 0x3f 0x37 0xb2 0xf2 0xdc 0x1a 0x71 0x80 0xf2 0x9b 0x2e 0xa0 0x27 0xa9 0x82 0x41 0x9c 0xce 0x45 0x9d 0x27 0x45 0x2e 0x42 0x14 0xbe 0x9c 0x74 0xe9 0x33 0x3a 0x21 0xdb 0x10 0x78 0xb9 0xf6 0x7b"; + my $TestReferenceIdent = "0xde 0xad 0xbe"; + my $TestReferenceVersion = "0x01"; + my $TestReferenceOpsLimit = "0x00 0x00 0x00 0x04"; + my $TestReferenceMemLimit = "0x00 0x00 0x20 0x00"; + my $TestReferenceSalt = "0x77 0x35 0x36 0xdc 0xc3 0x0e 0x2e 0x84 0x7e 0x0e 0x75 0x29 0xe2 0x34 0x60 0xcf"; + my $TestReferenceNonce = "0xe3 0xff 0xcc 0x52 0x3f 0x37 0xb2 0xf2"; + my $TestReferenceCypherText = "0xdc 0x1a 0x71 0x80 0xf2 0x9b 0x2e 0xa0 0x27 0xa9 0x82 0x41 0x9c 0xce 0x45 0x9d 0x27 0x45 0x2e 0x42 0x14 0xbe 0x9c 0x74 0xe9 0x33 0x3a 0x21 0xdb 0x10 0x78 0xb9 0xf6 0x7b"; + my $TestReferenceStrechedPW = "0x47 0xb8 0xa5 0xbc 0xdd 0xda 0x29 0xeb 0x4c 0x0f 0xf6 0x78 0x46 0x41 0xe7 0x2f 0x7b 0x9c 0x45 0xd5 0x46 0x1a 0x60 0x71 0x2c 0x68 0x1f 0x05 0x23 0x9b 0xcc 0xa2"; + my $TestReferenceDecrypt = "0x66 0x6f 0x6f 0x62 0x61 0x72 0x31 0x30 0x32 0x20 0x20 0x20 0x20 0x20 0x5a 0x12 0xe4 0x13"; + ########################################################################################################################################################################################################## + + ### Unpack Hex-Package + my $data = bin2hex($buf); + + ### Split up in accordance to API 0.24 description in hex values + my $IDENT = substr($data, 0, 6); + my $VERSION = substr($data, 6, 2); + 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_Decrypt -- Part 1 ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP length data in decimal : " . length($data); + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Udp : " . $buf; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Udp hex : " . $data; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Udp hex : " . $HexFriendlyData; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Udp API Ref. : " . $TestReferenceUdp; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Ident hex : " . $HexFriendlyIdent; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Ident API Ref. : " . $TestReferenceIdent; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Version hex : " . $HexFriendlyVersion; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Version API Ref. : " . $TestReferenceVersion; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client OpsLimit hex : " . $HexFriendlyOpsLimit; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client OpsLimit API Ref. : " . $TestReferenceOpsLimit; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client MemLimit hex : " . $HexFriendlyMemLimit; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client MemLimit API Ref. : " . $TestReferenceMemLimit; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Salt hex : " . $HexFriendlySalt; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Salt API Ref. : " . $TestReferenceSalt; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Nonce hex : " . $HexFriendlyNonce; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Nonce API Ref. : " . $TestReferenceNonce; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Cipher hex : " . $HexFriendlyCipherText; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Client Cipher API Ref. : " . $TestReferenceCypherText; + Log3 $name, 5, $name. " : DoorBird_Decrypt -"; + + + Log3 $name, 5, $name. " : DoorBird_Decrypt -- Part 2 ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP IDENT in hexadecimal : " . $IDENT; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP VERSION in hexdecimal : " . $VERSION; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP OPSLIMIT in hexdecimal : " . $OPSLIMIT; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP MEMLIMIT in hexdecimal : " . $MEMLIMIT; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP SALT in hexdecimal : " . $SALT; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP NONCE in hexdecimal : " . $NONCE; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP CIPHERTEXT in hexdecimal : " . $CIPHERTEXT; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP FiveCharPw in character : " . $FiveCharPw; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP length(SALT) : " . length($SALT); + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP length(FiveCharPw) : " . length($FiveCharPw); + Log3 $name, 5, $name. " : DoorBird_Decrypt -"; + + ### Convert in accordance to API 0.24 description + $IDENT = hex($IDENT); + $VERSION = hex($VERSION); + $OPSLIMIT = hex($OPSLIMIT); + $MEMLIMIT = hex($MEMLIMIT); + + $SALT = $SALT; # Seems to be the only one progressing through but different values from the API reference + #$SALT = hex($SALT); # error - crypto_pwhash_scrypt_str : Invalid salt + #$SALT = pack('C', $SALT); # error - crypto_pwhash_scrypt_str : Invalid salt + #$SALT = pack('C*', $SALT); # error - crypto_pwhash_scrypt_str : Invalid salt + #$SALT = pack('H', $SALT); # error - crypto_pwhash_scrypt_str : Invalid salt + #$SALT = pack('H*', $SALT); # error - crypto_pwhash_scrypt_str : Invalid salt + + Log3 $name, 5, $name. " : DoorBird_Decrypt -- Part 3 ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP IDENT decimal : " . $IDENT; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP VERSION decimal : " . $VERSION; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP OPSLIMIT decimal : " . $OPSLIMIT; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP MEMLIMIT decimal : " . $MEMLIMIT; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP FiveCharPw in character : " . $FiveCharPw; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP length(SALT) : " . length($SALT); + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP length(FiveCharPw) : " . length($FiveCharPw); + Log3 $name, 5, $name. " : DoorBird_Decrypt -"; + + + ### Convert string of hex values in string of char values + my $CharSalt; + for (my $i=0; $i < (length($SALT)/2); $i++) { + $CharSalt .= chr(hex(substr($SALT, $i*2, 2))); + } + + my $StrechedPWHex; + + ###BEGIN################# Part 4a: Crypt::Argon2::argon2i Libary ######################################################## + my $hashpass = argon2i_pass($FiveCharPw, $CharSalt, 4, 8192, 1, 5); + + my $StrechedPWChar="x"; + if($hashpass =~ m/p=(.*)/) { + $StrechedPWChar = $1; + } + + $StrechedPWHex = unpack("H*", $StrechedPWChar); + + ### Generate user friendly hex-string + my $StrechedPWHexFriendly4a; + for (my $i=0; $i < (length($StrechedPWHex)/2); $i++) { + $StrechedPWHexFriendly4a .= "0x" . substr($StrechedPWHex, $i*2, 2) . " "; + } + ####END################## Part 4a: Crypt::Argon2::argon2i Libary ######################################################## + + ###BEGIN################# Part 4b: Crypt::Argon2::argon2id Libary ###################################################### + $hashpass = argon2id_pass($FiveCharPw, $CharSalt, 4, 8192, 1, 5); + + $StrechedPWChar="x"; + if($hashpass =~ m/p=(.*)/) { + $StrechedPWChar = $1; + } + + $StrechedPWHex = unpack("H*", $StrechedPWChar); + + ### Generate user friendly hex-string + my $StrechedPWHexFriendly4b; + for (my $i=0; $i < (length($StrechedPWHex)/2); $i++) { + $StrechedPWHexFriendly4b .= "0x" . substr($StrechedPWHex, $i*2, 2) . " "; + } + ####END################## Part 4b: Crypt::Argon2::argon2id Libary ####################################################### + + ###BEGIN################# Part 4c: Crypt::NaCl::Sodium Libary ################################################### + my $key; + my $crypto_pwhash = Crypt::NaCl::Sodium->pwhash(); + + eval + { + ### PW-Hash: https://metacpan.org/pod/distribution/Crypt-NaCl-Sodium/lib/Crypt/NaCl/Sodium/pwhash.pod#key + $key = $crypto_pwhash->key($FiveCharPw, $SALT, bytes => 32, opslimit => $OPSLIMIT, memlimit => $MEMLIMIT); + + 1; + } + or do + { + ### Log Entry + Log3 $name, 3, $name. " : DoorBird_Decrypt - error - crypto_pwhash_scrypt_str : " . $@; + return + }; + + $key->unlock(); + my $StrechedPW = $key->bytes(); + my $StrechedPWHex4c1 = $key->to_hex(); + + my $StrechedPWHex4c2 = unpack("H*", $StrechedPW); + + ### Generate user friendly hex-string + my $StrechedPWHexFriendly4c1; + for (my $i=0; $i < (length($StrechedPWHex)/2); $i++) { + $StrechedPWHexFriendly4c1 .= "0x" . substr($StrechedPWHex4c1, $i*2, 2) . " "; + } + ### Generate user friendly hex-string + my $StrechedPWHexFriendly4c2; + for (my $i=0; $i < (length($StrechedPWHex)/2); $i++) { + $StrechedPWHexFriendly4c2 .= "0x" . substr($StrechedPWHex4c2, $i*2, 2) . " "; + } + + ####END################## Part 4c: Crypt::NaCl::Sodium Libary ################################################### + + + + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Decrypt -- Part 4 ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP CharSalt : " . $CharSalt; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP StrechedPWChar : " . $StrechedPWChar; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP StrechedPWHex : " . $StrechedPWHex; + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP StrechedPW API Reference : " . $TestReferenceStrechedPW; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP StrechedPW hex friendly 4a : " . $StrechedPWHexFriendly4a; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP StrechedPW hex friendly 4b : " . $StrechedPWHexFriendly4b; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP StrechedPW hex friendly 4c1: " . $StrechedPWHexFriendly4c1; + Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP StrechedPW hex friendly 4c2: " . $StrechedPWHexFriendly4c2; + + + Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + Log3 $name, 5, $name. " : DoorBird_Decrypt -"; + + + + + + + + + + # my $crypto_aead = Crypt::NaCl::Sodium->aead(); + + # eval { + # $msg = $crypto_aead->decrypt($CIPHERTEXT, "", $NONCE, $StrechedPWHex); + + # 1; + # }; + # if ( $@ ) { + # Log3 $name, 3, $name. " Message forged!"; + # } + # else { + # Log3 $name, 3, $name. "Decrypted message: " . $msg; + # } + + # ### Unpack Streched Password + # my $StrechedMsgHex = unpack('H*', $msg); + # # my $StrechedMsgHex = $msg->to_hex(); + + + # ### Generate user friendly hex-string + # my $StrechedMsgHexFriendly; + # for (my $i=0; $i < (length($StrechedMsgHex)/2); $i++) { + # $StrechedMsgHexFriendly .= "0x" . substr($StrechedMsgHex, $i*2, 2) . " "; + # } + + # ### Log Entry for debugging purposes + # Log3 $name, 5, $name. " : DoorBird_Decrypt -- Part 5 ------------------------------------------------------------------------------------------------------------------------"; + # Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP crypto_aead : " . Dumper($crypto_aead); + # Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Msg : " . Dumper($msg); + # Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Msg hex : " . $StrechedMsgHex; + # Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP Msg hex friendly : " . $StrechedMsgHexFriendly; + # Log3 $name, 5, $name. " : DoorBird_Decrypt - UDP StrechedPW API Reference : " . $TestReferenceDecrypt; + # Log3 $name, 5, $name. " : DoorBird_Decrypt ------------------------------------------------------------------------------------------------------------------------"; + # Log3 $name, 5, $name. " : DoorBird_Decrypt -"; + return $msg; +} +####END####### Define Test-Function for UDP decryption #########################################################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 $VideoHtmlCode; + my $ImageHtmlCode; + my $ImageHtmlCodeBig; + my $AudioHtmlCode; + my @HistoryDoorbell; + my @HistoryMotion; + + ### 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 = ""; + my $PopupfunctionCode = "onclick=\"FW_okDialog(\'" . $ImageHtmlCodeBig . "\') \" "; + $VideoHtmlCode = ''; + + + + ### Create proper html link + #$VideoHtmlCode = ''; + } + + ### 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 = "
" . $ImageTimeStamp . "
"; + my $PopupfunctionCode = "onclick=\"FW_okDialog(\'" . $ImageHtmlCodeBig . "\') \" "; + $ImageHtmlCode = 'tick'; + } + + ### 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 = ''; + } + #type="audio/wav + + ### Create html Code + my $htmlCode = ' + + + + + + + + + + + + + + + + + + +
Image from ' . $ImageTimeStamp . 'Live Stream
+ ' . $ImageHtmlCode . ' + + ' . $VideoHtmlCode . '
+
' . $AudioHtmlCode . '
+ '; + + ### 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 .= + ' +
+
+ + + + + + + + + + + + + + + + + + + '; + + + ### 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 = "
" . $HistoryDoorbell[$i]{timestamp} . "
"; + my $PopupfunctionCode = "onclick=\"FW_okDialog(\'" . $ImageHtmlCodeBig . "\') \" "; + $ImageHtmlCodeDoorbell = 'tick'; + } + } + 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 = "
" . $HistoryMotion[$i]{timestamp} . "
"; + my $PopupfunctionCode = "onclick=\"FW_okDialog(\'" . $ImageHtmlCodeBig . "\') \" "; + $ImageHtmlCodeMotion = 'tick'; + } + } + else { + $ImageHtmlCodeMotion = 'No image available'; + } + + $htmlCode .= + ' + + + + + + + + '; + ### 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 .= + ' + +
History of events - Last download: ' . $hash->{helper}{HistoryTime} . '
DoorbellMotion-Sensor
PictureTimestamp#PictureTimestamp
' . $ImageHtmlCodeDoorbell . '' . $HistoryDoorbell[$i]{timestamp} . '' . ($i + 1) . '' . $ImageHtmlCodeMotion . '' . $HistoryMotion[$i]{timestamp} . '
+ '; + + } + ### Log Entry for debugging purposes +# Log3 $name, 5, $name. " : DoorBird_FW_detailFn - htmlCode : " . $htmlCode; + +# my $infoBtn = "$info" +#
TestDescription')\">Testtitle
+ + 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; + $ShowData =~ s/[\r]//g; + $ShowData =~ s/[\n]//g; + + + ### 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 + fhem("deletereading $name 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\n", $RelayNumber), ": " . $RelayAddress); + + ### Update Reading + readingsBulkUpdate($hash, "RelayAddr_" . sprintf("%02d\n", $RelayNumber), $RelayAddress); + } + } + ### 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 Reading for Firmware-Status + readingsBulkUpdate($hash, "Firmware-Status", "up-to-date"); + + ### 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); + + ### Download website of changelocks + my $html = GetFileFromURL("https://www.doorbird.com/changelog"); + + ### Get the latest firmware number + my $result; + if ($html =~ /(?<=Firmware version )(.*)(?=\n=====)/) { + $result = $1; + } + + ### 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 $username = DoorBird_credential_decrypt($hash->{helper}{".USER"}); + my $password = DoorBird_credential_decrypt($hash->{helper}{".PASSWORD"}); + my $url = $hash->{helper}{URL}; + + ### Create complete command URL for DoorBird + my $UrlPrefix = "http://" . $url . "/bha-api/"; + my $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 AUDIO REQUEST ######################################################START#### +sub DoorBird_Live_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}; + + ### Create complete command URL for DoorBird + my $UrlPrefix = "http://" . $url . "/bha-api/"; + my $UrlPostfix = "?http-user=". $username . "&http-password=" . $password; + my $AudioURL = $UrlPrefix . "audio-receive.cgi" . $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 = " "; + + ### Create complete command URL for DoorBird + my $UrlPrefix = "http://" . $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 - ImageURL : " . $ImageURL ; + + ### Update Reading + readingsSingleUpdate($hash, ".ImageURL", $ImageURL, 1); + + ### Refresh Browser Window + FW_directNotify("#FHEMWEB:$FW_wname", "location.reload()", "") if defined($FW_wname); + + ### 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); + + ### Save picture and timestamp into hash + $hash->{helper}{Images}{Individual}{Data} = $ImageData; + $hash->{helper}{Images}{Individual}{Timestamp} = $ImageTimeStamp; + + ### 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 OPEN DOOR ###############################################################START#### +sub DoorBird_Open_Door($$) { + my ($hash, $option) = @_; + my $name = $hash->{NAME}; + my $command = "open-door.cgi?r=" . $option; + 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_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 ". $option . " successfully triggered."; + + ### Create popup message + return "Door ". $option . " 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 LIVE AUDIO TRANSMIT #####################################################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 $SipDevice = $hash->{helper}{SipDevice}; + my $SipNumber = $hash->{helper}{SipNumber}; + my $AudioDataPathOrig = $option; + my @ListSipDevices = devspec2array("TYPE=SIP"); + my $err; + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - ---------------------------------------------------------------"; + + ### If device of TYPE = SIP exists + if (@ListSipDevices > 0) { + ### If file exists + if (-e $AudioDataPathOrig) { + ### Create new filepath from old filepath + my $AudioDataNew; + my $AudioDataSizeNew; + my $AudioDataPathNew = $AudioDataPathOrig; + $AudioDataPathNew =~ s/\..*//; + my $AudioDataPathTemp = $AudioDataPathNew . "_tmp.wav"; + $AudioDataPathNew .= ".ulaw"; + + ### Delete future new file and temporary file if exist + unlink($AudioDataPathTemp); + unlink($AudioDataPathNew); + + ### Create Sox - command + my $SoxCmd = $Sox . " -V " . $AudioDataPathOrig . " -r 8000 -b 8 -c 1 -e u-law " . $AudioDataPathTemp; + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - Original Path exists : " . $AudioDataPathOrig; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - Temp Path created : " . $AudioDataPathTemp; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - New Path created : " . $AudioDataPathNew; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - Sox System-Command : " . $SoxCmd; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - SipDeviceAttribute : " . $SipDevice; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - SipNumber : " . $SipNumber; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - ListSipDevices : " . Dumper(@ListSipDevices); + + ### Convert file + system ($SoxCmd); + + ### Rename temporary file in .ulaw + $err = rename($AudioDataPathTemp, $AudioDataPathNew); + + ### Get new filesize + $AudioDataSizeNew = -s $AudioDataPathNew; + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - New Filesize : " . $AudioDataSizeNew; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - rename response message : " . $err; + + ### If the a name for a SIP - TYPE device has been provided as per attribute + if (defined($SipDevice)) { + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - Attribute for SIP device: " . $SipDevice; + + ### If SIP device provided in attribute exists + if (defined($defs{$SipDevice})) { + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - SIP device in Attribute exists"; + } + ### If SIP device provided in attribute does NOT exists + else { + ### Take the first available SIP device + $SipDevice= $ListSipDevices[0]; + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - SIP device in Attribute does NOT exist"; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - SipDevice chosen : " . $SipDevice; + } + } + ### If the a name for a SIP - TYPE device has NOT been provided as per attribute + else { + ### Take the first available SIP device + $SipDevice= $ListSipDevices[0]; + + ### Log Entry for debugging purposes + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - SIP device has not been provided in Attribute"; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - SipDevice chosen : " . $SipDevice; + } + + + ### Use SIP device and transfer filepath + my $FhemCommand = "set " . $SipDevice . " call " . $SipNumber . " 30 " . $AudioDataPathNew; + fhem($FhemCommand); + + return "The audio file: " . $AudioDataPathOrig . " has been passed to the fhem device " . $SipDevice; + } + ### 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!" + } + } + ### If no device TYPE = SIP exists + else { + ### Log Entry + Log3 $name, 3, $name. " : DoorBird_Transmit_Audio - No device with TYPE=SIP exists. Install SIP device first"; + Log3 $name, 5, $name. " : DoorBird_Transmit_Audio - ---------------------------------------------------------------"; + return "No device with TYPE=SIP exists. Install SIP device first" + } +} +####END####### Define Subfunction for LIVE AUDIO TRANSMIT ######################################################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 = "http://" . $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, + incrementalTimout => 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 %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); + } + ### 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 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}; + } + + ### 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 - 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 - 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 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} ); + } + } + ### Update Reading for Firmware-Status + readingsBulkUpdate($hash, "Firmware-Status", "up-to-date"); + + ### Execute Readings Bulk Update + readingsEndUpdate($hash, 1); + + ### 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 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 = "http://" . $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##### +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 + + +

DoorBird

+
    + + + + +
    + 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". + The following packet - installations are pre-requisite if not already installed by other modules (Examples below tested on Raspberry JESSIE):
    +
    + +
  • sudo apt-get install sox
  • +
  • sudo apt-get install libsox-fmt-all
  • +
  • sudo apt-get install libsodium-dev
  • +
  • sudo cpanm HTTP::Request::StreamingUpload
  • +
  • sudo cpanm LWP::UserAgent
  • +
  • sudo cpanm Alien::Base::ModuleBuild
  • +
  • sudo cpanm Alien::Sodium
  • +
  • sudo cpanm Crypt::NaCl::Sodium
  • +
  • sudo cpanm MIME::Base64
  • +
    +
    +
    + + + + + +
    + Define +
    + + + + + +
    +
      + define <name> DoorBird <IPv4-address> <Username> <Password> +
    +
    + +
      +
        + + + + + +
        <name> : The name of the device. Recommendation: "myDoorBird".
        <IPv4-address> : A valid IPv4 address of the KMxxx. You might look into your router which DHCP address has been given to the DoorBird unit.
        <Username> : The username which is required to sign on the DoorBird.
        <Password> : The password which is required to sign on the DoorBird.
        +
      +
    + +
    + + + + +
    Set
      The set function is able to change or activate the following features as follows:
    + + + + + + + + + +
      set Light_On
    : Activates the IR lights of the DoorBird unit. The IR - light deactivates automatically by the default time within the Doorbird unit
      set Live_Audio <on:off>
    : Activate/Deactivate the Live Audio Stream of the DoorBird on or off and toggles the direct link in the hidden Reading .AudioURL
      set Live_Video <on:off>
    : Activate/Deactivate the Live Video Stream of the DoorBird on or off and toggles the direct link in the hidden Reading .VideoURL
      set Open Door <Value>
    : Activates the Relay of the DoorBird unit with the given address. The list of installed relay addresses are imported with the initialization of parameters.
      set Restart
    : Sends the command to restart (reboot) the Doorbird unit
      set Transmit_Audio <Path>
    : 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.
    + + + + + +
    Get
    +
      + The get function is able to obtain the following information from the DoorBird unit:

      +
    +
    + + + + +
      get History_Request
    : Downloads the pictures of the last events of the doorbell and motion sensor. (Refer to attribute MaxHistory)
      get Image_Request
    : Downloads the current Image of the camera of DoorBird unit.
      get Info_Request
    : 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 Open_Door command.
    + + + + + +
    Attributes
    +
      + The following user attributes can be used with the DoorBird module in addition to the global ones e.g. room.
      +
    +
    + +
      + + + + + + + + + + + + + + + + + + + + + + +
      + disable : Stops the device from further reacting on UDP datagrams sent by the DoorBird unit.
      + The default value is 0 = activated
      +
      + KeepAliveTimeout : Timeout in seconds without still-alive UDP datagrams before state of device will be set to "disconnected".
      + The default value is 30s
      +
      + MaxHistory : Number of pictures to be downloaded from history for both - doorbell and motion sensor events.
      + The default value is "50" which is the maximum possible.
      +
      + PollingTimeout : 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.
      + The default value is 10s.
      + +
      + UdpPort : Port number to be used to receice UDP datagrams. Ports are pre-defined by firmware.
      + The default value is port 6524
      + +
      + SipDevice : Name of the fhem SIP device which is registered in the DoorBird unit as those ones who are allowed to call the DoorBird. Refer to SIP.
      + The default value is the first SIP device in fhem.
      + +
      + SipNumber : The telephone number under which the DoorBird unit is registered and can be called.
      + The default value is **620
      +
      +
    +
+=end html + + +=begin html_DE + + +

DoorBird

+
    + + + + +
    + 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. + 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):
    +
    + +
  • sudo apt-get install sox
  • +
  • sudo apt-get install libsox-fmt-all
  • +
  • sudo apt-get install libsodium-dev
  • +
  • sudo cpanm HTTP::Request::StreamingUpload
  • +
  • sudo cpanm LWP::UserAgent
  • +
  • sudo cpanm Alien::Base::ModuleBuild
  • +
  • sudo cpanm Alien::Sodium
  • +
  • sudo cpanm Crypt::NaCl::Sodium
  • +
  • sudo cpanm MIME::Base64
  • +
    +
    +
    + + + + + +
    + Define +
    + + + + + +
    +
      + define <name> DoorBird <IPv4-address> <Username> <Passwort> +
    +
    + +
      +
        + + + + + +
        <name> : Der Name des Device unter fhem. Beispiel: "myDoorBird".
        <IPv4-Addresse> : 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.
        <Username> : Der Username zum einloggen auf der DoorBird Anlage.
        <Passwort> : Das Passwort zum einloggen auf der DoorBird Anlage.
        +
      +
    + +
    + + + + +
    Set
      Die Set - Funktion ist in der lage auf der DoorBird - Anlage die folgenden Einstellungen vorzunehmen bzw. zu de-/aktivieren:

    + + + + + + + + +
      set Light_On
    : 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.
      set Live_Audio <on:off>
    : Aktiviert/Deaktiviert den Live Audio Stream der DoorBird - Anlage Ein oder Aus und wechselt den direkten link in dem versteckten Reading .AudioURL.
      set Live_Video <on:off>
    : Aktiviert/Deaktiviert den Live Video Stream der DoorBird - Anlage Ein oder Aus und wechselt den direkten link in dem versteckten Reading .VideoURL.
      set Open Door <Value>
    : Aktiviert das Relais der DoorBird - Anlage mit dessen Adresse. Die Liste der installierten Relais werden mit der Initialisierung der Parameter importiert.
      set Restart
    : Sendet das Kommando zum rebooten der DoorBird - Anlage.
      set Transmit_Audio <Path>
    : 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).
    + + + + + +
    Get
      Die Get - Funktion ist in der lage von der DoorBird - Anlage die folgenden Informationen und Daten zu laden:

    + + + + +
      get History_Request
    : Lädt die Bilder der letzten Ereignisse durch die Türklingel und dem Bewegungssensor herunter. (Siehe auch Attribut MaxHistory)
      get Image_Request
    : Lädt das gegenwärtige Bild der DoorBird - Kamera herunter.
      get Info_Request
    : Lädt das interne Setup (Firmware Version, Relais Konfiguration etc.) herunter. Die übermittelten Relais-Adressen werden als Option für das Kommando Open_Door verwendet.
    + + + + + +
    Attributes
    +
      + Die folgenden Attribute können mit dem DoorBird Module neben den globalen Attributen wie room verwednet werden.
      +
    +
    + +
      + + + + + + + + + + + + + + + + + + + + + + +
      + disable : Stoppt das Gerät von weiteren Reaktionen auf die von der DoorBird ß Anlage ausgesendeten UDP - Datageramme
      Der Default Wert ist 0 = aktiviert
      +
      + KeepAliveTimeout : Timeout in Sekunden ohne "still-alive" - UDP Datagramme bevor der Status des Gerätes auf "disconnected" gesetzt wird.
      + Der Default Wert ist 30s
      +
      + MaxHistory : Anzahl der herunterzuladenden Bilder aus dem Historien-Archiv sowohl für Ereignisse seitens der Türklingel als auch für den Bewegungssensor.
      + Der Default Wert ist "50" = Maximum.
      +
      + PollingTimeout : 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.
      + Der Default-Wert ist 10s.
      +
      + UdpPort : Port Nummer auf welcher das DoorBird - Modul nach den UDP Datagrammen der DoorBird - Anlage hören soll. Die Ports sind von der Firmware vorgegeben.
      + Der Default Port ist 6524
      +
      + SipDevice : Name des fhem SIP Device mit wessen Nummer in der DoorBird - Anlage hinterlegt wurde die die DoorBird - Anlage anrufen dürfen. Refer to SIP.
      + Der Default Wert ist das erste SIP device in fhem.
      +
      + SipNumber : Die Telefonnummer unter der die DoorBird / Anlage registriert und erreicht werden kann.
      + Der Default Wert ist **620
      +
      +
    +
+=end html_DE \ No newline at end of file