fhem-mirror/FHEM/96_SIP.pm
Wzut eeb59b168d 96_SIP: change ps to kill 0
git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@21469 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2020-03-21 19:30:58 +00:00

2363 lines
78 KiB
Perl
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

###############################################################################
#
# $Id$
# 96_SIP.pm
# Based on FB_SIP from werner.meines@web.de
#
# Forum : https://forum.fhem.de/index.php/topic,67443.0.html
#
###############################################################################
#
# (c) 2017 Copyright: Wzut & plin
# All rights reserved
#
# This script 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.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
# A copy is found in the textfile GPL.txt and imPORTant notices to the license
# from the author is found in LICENSE.txt distributed with these scripts.
#
# This script 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.
#
##################################################################################
#######################################################################
# need: Net::SIP (cpan install Net::SIP)
#
#
# convert audio to PCM 8000 :
# sox <file>.wav -t raw -r 8000 -c 1 -e a-law <file>.alaw
# oder
# sox <file> -r 8000 -c 1 -e a-law <file>.wav
#
########################################################################
package main;
use strict;
use warnings;
use POSIX qw( strftime );
use Net::SIP qw//;
use Net::SIP::Packet;
use IO::Socket;
use Socket;
use Net::Domain qw(hostname hostfqdn);
use Blocking; # http://www.fhemwiki.de/wiki/Blocking_Call
#use Data::Dumper;
my $sip_version ="V1.92 / 21.03.2020";
my $ua; # SIP user agent
my @fifo;
my %sets = (
"call" => "",
"listen:noArg" => "",
"reject:noArg" => "",
"reset:noArg" => "",
"fetch:noArg" => "",
"password" => ""
);
#my %gets = (
#"search_phonebook" => "",
#"show_phonebook" => ""
# );
sub SIP_Initialize($$)
{
my ($hash) = @_;
$hash->{DefFn} = "SIP_Define";
$hash->{UndefFn} = "SIP_Undef";
$hash->{ShutdownFn} = "SIP_Undef";
$hash->{SetFn} = "SIP_Set";
#$hash->{GetFn} = "SIP_Get";
$hash->{NotifyFn} = "SIP_Notify";
$hash->{AttrFn} = "SIP_Attr";
$hash->{AttrList} = "sip_watch_listen ". #
"sip_ringtime ". #
"sip_waittime ". #
"sip_ip ". #
"sip_port ". #
"sip_user ". #
"sip_registrar ". #
"sip_from ". #
"sip_call_audio_delay:0,0.25,0.5,0.75,1,1.25,1.5,1.75,2,2.25,2.5,2.75,3 ". #
"sip_audiofile_call ". #
"sip_audiofile_dtmf ". #
"sip_audiofile_ok ". #
"sip_audiofile_wfp ". # CE
"sip_dtmf_size:1,2,3,4 ". #
"sip_dtmf_send:audio,rfc2833 ". #
"sip_dtmf_loop:once,loop ". #
"sip_listen:none,dtmf,wfp,echo ". #
"sip_filter ". #
"sip_blocking ". #
"sip_elbc:yes,no ". #
"sip_force_interval ". #
"sip_force_max ". #
"T2S_Device ". #
"T2S_Timeout ". #
"audio_converter:sox,ffmpeg ". #
"history_file ".
"history_size ".
"phonebook ".
"disabled:0,1 ".$readingFnAttributes;
}
sub SIP_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
my $name = shift @a;
my $addr = "0.0.0.0";
$hash->{STATE} = "defined";
$hash->{VERSION} = $sip_version;
$hash->{".reset"} = 0;
$attr{$name}{sip_ringtime} = '3' unless (exists($attr{$name}{sip_ringtime}));
$attr{$name}{sip_user} = '620' unless (exists($attr{$name}{sip_user}));
$attr{$name}{sip_registrar} = 'fritz.box' unless (exists($attr{$name}{sip_registrar}));
$attr{$name}{sip_listen} = 'none' unless (exists($attr{$name}{sip_listen}));
$attr{$name}{sip_dtmf_size} = '2' unless (exists($attr{$name}{sip_dtmf_size}));
$attr{$name}{sip_dtmf_loop} = 'once' unless (exists($attr{$name}{sip_dtmf_loop}));
$attr{$name}{sip_dtmf_send} = 'audio' unless (exists($attr{$name}{sip_dtmf_send}));
$attr{$name}{sip_elbc} = 'yes' unless (exists($attr{$name}{sip_elbc}));
$attr{$name}{sip_from} = 'sip:'.$attr{$name}{sip_user}.'@'.$attr{$name}{sip_registrar} unless (exists($attr{$name}{sip_from}));
$attr{$name}{history_size} = 0 unless (exists($attr{$name}{history_size}));
$attr{$name}{history_file} = "./log/$name.sip" unless (exists($attr{$name}{history_file}));
unless (exists($attr{$name}{sip_ip}))
{
eval { $addr = inet_ntoa(scalar(gethostbyname(hostfqdn()))); };
if ($@)
{
Log3 $name,2,"$name, please check your FQDN hostname -> $@";
eval { $addr = inet_ntoa(scalar(gethostbyname(hostname()))); };
Log3 $name,2,"$name, please check your hostname -> ".$@ if ($@);
}
$attr{$name}{sip_ip} = $addr;
}
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+5, "SIP_updateConfig", $hash);
notifyRegexpChanged($hash,"global");
return undef;
}
sub SIP_Notify($$)
{
# $hash is my hash, $dev_hash is the hash of the changed device
my ($hash, $dev_hash) = @_;
my $events = deviceEvents($dev_hash,0);
return undef if ($dev_hash->{NAME} ne AttrVal($hash->{NAME},"T2S_Device",""));
my $val = ReadingsVal($dev_hash->{NAME},"lastFilename","");
return undef if ((!$val) || (index(@{$events}[0],"lastFilename") == -1));
if (defined($hash->{audio1}) ||
defined($hash->{audio2}) ||
defined($hash->{audio3}) ||
(defined($hash->{callnr}) && defined($hash->{ringtime})))
{ SIP_wait_for_t2s($hash);}
return undef;
}
sub SIP_Attr (@)
{
my ($cmd, $name, $attrName, $attrVal) = @_;
my $hash = $defs{$name};
if ($cmd eq "set")
{
if (substr($attrName ,0,4) eq "sip_")
{
$_[3] = $attrVal;
$hash->{".reset"} = 1 if defined($hash->{LPID});
}
elsif (($attrName eq "disable") && ($attrVal == 1))
{
readingsSingleUpdate($hash,"state","disabled",1);
$_[3] = $attrVal;
$hash->{".reset"} = 1 if defined($hash->{LPID});
}
elsif ($attrName eq "audio_converter")
{
my $res = qx(which $attrVal);
$res =~ s/\n//;
$hash->{AC} = ($res) ? $res : undef;
}
elsif ($attrName eq "T2S_Device")
{
$_[3] = $attrVal;
#$hash->{NOTIFYDEV} = $attrVal;
notifyRegexpChanged($hash,$attrVal.":lastFilename");
}
}
elsif ($cmd eq "del")
{
if (substr($attrName,0,4) eq "sip_")
{
$_[3] = $attrVal;
$hash->{".reset"} = 1 if defined($hash->{LPID});
}
elsif ($attrName eq "audio_converter")
{
$_[3] = $attrVal;
delete $hash->{AC};
}
elsif ($attrName eq "T2S_Device")
{
$_[3] = $attrVal;
#delete $hash->{NOTIFYDEV};
notifyRegexpChanged($hash,"global");
}
}
if ($hash->{".reset"})
{
Log3 $name,5,"$name , SIP_Attr : reset";
InternalTimer(gettimeofday()+1,"SIP_updateConfig",$hash);
}
return undef;
}
sub SIP_updateConfig($)
{
# this routine is called 5 sec after the last define of a restart
# this gives FHEM sufficient time to fill in attributes
my ($hash) = @_;
my $name = $hash->{NAME};
my $error;
if (!$init_done)
{
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+5,"SIP_updateConfig", $hash);
return;
}
## kommen wir via reset Kommando ?
if ($hash->{".reset"})
{
$hash->{".reset"} = 0;
RemoveInternalTimer($hash);
if(defined($hash->{LPID}))
{
Log3 $name,4, "$name, Listen Kill PID : ".$hash->{LPID};
BlockingKill($hash->{helper}{LISTEN_PID});
delete $hash->{helper}{LISTEN_PID};
delete $hash->{LPID};
readingsSingleUpdate($hash,"listen_alive","no",1);
Log3 $name,4,"$name, Reset Listen done";
}
if(defined($hash->{CPID}))
{
Log3 $name,4, "$name, CALL Kill PID : ".$hash->{CPID};
BlockingKill($hash->{helper}{CALL_PID});
delete $hash->{helper}{CALL_PID};
delete $hash->{CPID};
Log3 $name,4,"$name, Reset Call done";
}
}
if (IsDisabled($name))
{
readingsSingleUpdate($hash,"state","disabled",1);
return undef;
}
my $t2s = AttrVal($name,"T2S_Device",undef);
#$hash->{NOTIFYDEV} = $t2s if defined($t2s);
notifyRegexpChanged($hash, $t2s.":lastFilename") if defined($t2s);
if (AttrVal($name,"audio_converter","") && defined($t2s))
{
my $converter = AttrVal($name,"audio_converter","");
my $res = qx(which $converter);
$res =~ s/\n//;
$hash->{AC} = ($res) ? $res : undef;
}
if (AttrVal($name,"sip_listen", "none") ne "none")
{
$error = SIP_try_listen($hash);
if ($error)
{
Log3 $name, 1, $name.", listen -> $error";
readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"state","error");
readingsBulkUpdate($hash,"last_error",$error);
readingsBulkUpdate($hash,"listen_alive","no");
readingsEndUpdate($hash, 1 );
return undef;
}
}
else
{
readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"state","initialized");
readingsBulkUpdate($hash,"listen_alive","no");
readingsEndUpdate($hash, 1 );
}
return undef;
}
sub SIP_Register($$)
{
my ($hash,$type) = @_;
my $name = $hash->{NAME};
$hash->{LPID} = $$;
my $logname = $name."[".$hash->{LPID}."]";
my $ip = AttrVal($name,"sip_ip","");
my $port = int(AttrVal($name,"sip_port",0));
my $leg;
return "missing attribute sip_ip" if (!$ip);
return "this is the IP address of your registrar , not your FHEM !" if ($ip eq AttrVal($name,"sip_registrar",""));
return "invalid IP address $ip" if (($ip eq "0.0.0.0") || ($ip eq "127.0.0.1"));
if ($port)
{
$port +=10 if ($type eq "calling");
Log3 $name,4,"$logname, trying to use port $port";
$leg = IO::Socket::INET->new(Proto => 'udp', LocalHost => $ip, LocalPort => $port);
# if port is already used try another one
if (!$leg)
{
Log3 $name,1,"$logname, cannot open port $port at $ip : ".$!;
$port += 10;
$leg = IO::Socket::INET->new(Proto => 'udp', LocalHost => $ip, LocalPort => $port) || return "can't open port $port at $ip : ".$!;
Log3 $name,2,"$logname, using secundary port $port with IP $ip";
}
close($leg);
$leg = $ip.":".$port;
}
else
{
$leg = $ip;
Log3 $name,4,"$logname, using Leg.pm to find a free port";
}
my $registrar = AttrVal($name,"sip_registrar","fritz.box");
my $user = AttrVal($name,"sip_user","620");
my $from = AttrVal($name,"sip_from","sip:".$user."@".$registrar);
# create new agent
$ua = Net::SIP::Simple->new(
registrar => $registrar,
domain => $registrar,
leg => $leg,
from => $from,
auth => [ $user , SIP_readPassword($name) ]);
# Register agent
# optional registration
my $sub_register;
$sub_register = sub
{
my $expire = $ua->register(registrar => $registrar ) || return "registration failed: ".$ua->error;
#my $cmd = "ps -e | grep '".$hash->{parent}." '";
#my $result = qx($cmd);
#if (index($result,"perl") == -1)
unless (kill 0, $hash->{parent})
{
Log3 $name,1,"$logname, can´t find my parent ".$hash->{parent}." in process list !";
die;
}
Log3 $name,4,"$logname, register new expire : ".FmtDateTime(time()+$expire);
if (AttrVal($name,"sip_listen","none") ne "none")
{
BlockingInformParent("SIP_rBU", [$name,"state;$type|listen_alive;".$hash->{LPID}."|expire;$expire"],0);
}
else
{
BlockingInformParent("SIP_rSU", [$name, "state;$type"], 0);
}
# need to refresh registration periodically
$ua->add_timer( $expire/2, $sub_register );
};
$sub_register->();
if($ua->register) # returned expires time or undef if failed
{
#Log3 $name,4,Dumper($ua);
return 0;
}
my $ret = ($ua->error) ? $ua->error : "registration error";
return $ret;
}
sub SIP_CALLStart($)
{
my ($arg) = @_;
return unless(defined($arg));
my ($name,$nr,$ringtime,$msg,$repeat) = split("\\|",$arg);
my $hash = $defs{$name};
my $logname = $name."[".$$."]";
$ua = undef;
my $rtp_done = 0;
my $dtmf = 'ABCD*#123--4567890';
my $delay = AttrVal($name,"sip_call_audio_delay",0); # Verzoegerung in 1/4 Sekunden Schritten
my $w = ($delay) ? -1 : 0;
my $fi = 0;
#$repeat = 0 if (!$repeat);
my $packets = int($delay*50);
my $timeout = 0;
my $final;
my $peer_hangup;
my $peer_hangup2;
my $stopvar;
my $state;
my $no_answer;
my $call;
my $codec;
my $call_established = 0;
my $calltime =0;
my @files;
my $anz;
my $stat;
my $ph_ok = 0;
my $sound_of_silence = sub
{
return unless $packets-- > 0;
return chr(0) x 160; # 160 bytes for PCMU/8000 = 1/50 Sekunde Sound
};
$hash->{parent} = getppid();
Log3 $name,4,"$logname, my parent is ".$hash->{parent};
my $error = SIP_Register($hash,"calling");
return $name."|0|CallRegister: $error|0" if ($error);
$hash->{helper}{LALL_EST} = 0;
if ((substr($msg,0,1) ne "-") && $msg)
{
$codec = "PCMA/8000" if ($msg =~ /\.al(.+)$/);
$codec = "PCMU/8000" if ($msg =~ /\.ul(.+)$/);
return $name."|0|CallStart: please use filetype .alaw (for a-law) or .ulaw (for u-law)|0" if !defined($codec);
push @files,$sound_of_silence if ($delay);
if ($repeat < 0) { $repeat = $repeat * -1; $ph_ok= 1; }
for(my $i=0; $i<=$repeat; $i++) { push @files,$msg; }
$anz = @files;
Log3 $name,4,"$logname, CallStart with $anz files - first file : $files[0] - $codec , repeat $repeat";
$call = $ua->invite( $nr,
init_media => $ua->rtp('send_recv', $files[0]),
cb_rtp_done => \$rtp_done,
cb_final => sub { my ($status,$self,%info) = @_;
$final = $info{code};
$stat = $status;
Log3 $name,4,"$logname, cb_final - status : $status" if (!defined($final));
Log3 $name,4,"$logname, cb_final - status : $status - final : $final" if (defined($final));
if (($status eq "FAIL") && defined($final))
{
if (int($final) == 481) { BlockingInformParent("SIP_rSU", [$name, "call_state;ringing"], 0);} # bis Net::SIP 0.808
elsif (int($final) == 486) { $fi=1; } # canceled
elsif (int($final) == 603) { $fi=1; } # declined - ab Net::SIP 0.812
}
elsif (($status eq "OK") && !defined($final) && !$call_established) # der Angrufene hat abgenommen
{
Log3 $name,4, $logname.", call established";
$hash->{helper}{CALL_EST} = time();
BlockingInformParent("SIP_rSU", [$name, "call_state;established"], 0);
$call_established++; # nur 1x , bei mehr als einem File kommen wir ofters hier vorbei
}
},
recv_bye => \$peer_hangup,
#ring_time => 5,
#cb_noanswer => \$no_answer, klappt hier nicht wir gehen ueber add_timer
rtp_param => [8, 160, 160/8000, $codec]) || return $name."|0|invite failed: ".$ua->error;
}
else
{
$dtmf = (substr($msg,0,1) eq "-") ? substr($msg,1) : $dtmf;
Log3 $name,4,"$logname, CallStart DTMF : $dtmf";
$delay = 0; # wenn delay sein muss dann ueber DTMF ----
$repeat = 0; # keine Wiederholungen
$call = $ua->invite($nr,
init_media => $ua->rtp( 'recv_echo',undef,0 ),
rtp_param => [0, 160, 160/8000, 'PCMU/8000'],
cb_final => sub { my ($status,$self,%info) = @_;
$final = $info{code};
Log3 $name,4,"$logname, cb_final - Status : $status" if (!defined($final));
Log3 $name,4,"$logname, cb_final - status : $status - final : $final" if (defined($final));
if (($status eq "FAIL") && defined($final))
{
if (int($final) == 481) { BlockingInformParent("SIP_rSU", [$name, "call_state;ringing"], 0); } # bis Net::SIP 0.808
elsif (int($final) == 486) { $fi=1; } # canceled
elsif (int($final) == 603) { $fi=1; } # declined - ab Net::SIP 0.812
}
elsif (($status eq "OK") && !defined($final)) # der Angrufene hat abgenommen
{
Log3 $name,4, $logname.", call established";
$hash->{helper}{CALL_EST} = time();
BlockingInformParent("SIP_rSU", [$name, "call_state;established"], 0);
$call_established = 1; # setzen für die spätere Entscheidung bye oder cancel
}
},
#cb_noanswer => \$no_answer,
#ring_time => 5, siehe oben -> add_timer
cb_cleanup => sub {0},
recv_bye => \$peer_hangup) || return $name."|0|invite failed ".$ua->error."|0";
if (AttrVal($name,"sip_dtmf_send","audio") eq "audio")
{ $call->dtmf( $dtmf, methods => 'audio', duration => 500, cb_final => \$rtp_done); }
else { $call->dtmf( $dtmf, cb_final => \$rtp_done); }
}
return "$name|0|invite call failed |0".$call->error if ($call->error);
Log3 $name,4,"$logname, calling : $nr";
BlockingInformParent("SIP_rSU", [$name, "call_state;calling $nr"], 0);
#return "$name|1|no answer" if ($no_answer);
$ua->add_timer($ringtime,\$stopvar);
$ua->loop( \$stopvar,\$peer_hangup,\$rtp_done,\$fi );
$timeout = 1 if defined($stopvar); # hat der bereits zugeschlagen ?
Log3 $name,5,"$logname, 0. Ende des ersten Loops";
Log3 $name,5,"$logname, 1. rtp_done : $rtp_done" if defined($rtp_done);
Log3 $name,5,"$logname, 2. fi : $fi" if defined($fi);
Log3 $name,5,"$logname, 3. Final : $final" if defined($final);
Log3 $name,5,"$logname, 4. timeout : ".$timeout;
Log3 $name,5,"$logname, 5. peer_hangup : $peer_hangup" if defined($peer_hangup);
Log3 $name,5,"$logname, 6. call_established : ".$call_established;
Log3 $name,5,"$logname, 7. no_answer : $no_answer" if defined($no_answer);
# Lebt der Call noch und gibt es ueberhaupt etwas zum wiederholen ?
while ( !$peer_hangup && !$peer_hangup2 && !$fi && !$stopvar && $msg && ($anz > 1))
{
shift(@files); # done with file
@files || last; # raus hier sobald kein File mehr da ist
Log3 $name,4,"$logname, next file : $files[0]" if defined($files[0]);
Log3 $name,3,"$logname, opps no file" if !defined($files[0]);
# re-invite on current call for next file
$rtp_done = undef; # wichtig ! u.U. haengen wir hier fest wenn der Anrufer jetzt auflegt
select(undef, undef, undef, 0.1); # minimale pause
$call->reinvite(
init_media => $ua->rtp('send_recv', $files[0]),
#rtp_param => [0, 160, 160/8000, 'PCMU/8000'], unbedingt weglassen ! fuehrt zu Verzerrungen bei der Wiedergabe
cb_rtp_done => \$rtp_done,
recv_bye => \$peer_hangup2, # FIXME: do we need to repeat this? Wzut : I think so ...
) || return $name."|0|reinvite failed: ".$ua->error."|0";
$ua->loop( \$rtp_done,\$peer_hangup2,\$peer_hangup,\$stopvar );
Log3 $name,4,"$logname, loop rtp_done : $rtp_done" if defined($rtp_done);
$w++;
}
$timeout = 1 if defined($stopvar); # nach eventuellen reinvte nochmal testen
# timeout or dtmf done, hang up
if ( $timeout || $rtp_done)
{
$stopvar = undef;
if ($timeout && !$call_established)
{
$hash->{helper}{CALL_STATUS} = "cancel";
Log3 $name,5,"$logname, call->cancel";
$call->cancel( cb_final => \$stopvar );
}
else
{
$hash->{helper}{CALL_STATUS} = "bye";
Log3 $name,5,"$logname, call->bye";
$call->bye( cb_final => \$stopvar );
}
$ua->loop( \$stopvar );
}
$calltime = ($hash->{helper}{CALL_EST}) ? int(time()-$hash->{helper}{CALL_EST}) : 0;
Log3 $name,5,"$logname, RTP done : $rtp_done" if defined($rtp_done);
Log3 $name,5,"$logname, Hangup : $peer_hangup" if defined($peer_hangup);
Log3 $name,5,"$logname, Hangup2 : $peer_hangup2" if defined($peer_hangup2);
Log3 $name,5,"$logname, Timeout : $timeout";
Log3 $name,5,"$logname, Final : $final" if defined($final);
Log3 $name,5,"$logname, while : $w" if defined($w);
Log3 $name,5,"$logname, Status : $stat" if defined($stat);
Log3 $name,4,"$logname, Calltime : $calltime" if defined($calltime);
if (defined($rtp_done))
{
if ($rtp_done eq "OK") {return $name."|1|ok|$calltime";} # kein Audio
else
{
if (defined($final))
{
my $txt;
$txt = "canceled" if (int($final) == 486);
$txt = "no answer" if (int($final) == 487);
$txt = "declined" if (int($final) == 603);
return $name."|1|$txt|$calltime" if ($txt);
}
else {return $name."|1|ok|$calltime" if ($rtp_done !=0);}
}
}
# immer noch kein richtiger Text zur Rueckgabe ?
$final = "unknown" if (!defined($final) && !$timeout);
$final = "timeout" if (!defined($final) && $timeout);
$final = "peer hangup" if defined($peer_hangup);
$final = "peer_hangup" if defined($peer_hangup2); # ts,ts hat der doch glatt im reinvite noch abgebochen
# geben wir doch ok zurueck wenn er sich die Nachricht min 1x angehört hat
return $name."|1|ok peer hangup|$calltime" if ($ph_ok && defined($peer_hangup2) && ($stat eq "OK") && ($w>0)); # bei delay 1x mehr !
return $name."|1|$final|$calltime";
}
sub SIP_CALLDone($)
{
my ($string) = @_;
return unless(defined($string));
my @r = split("\\|",$string);
my $hash = $defs{$r[0]};
my $error = (defined($r[1])) ? $r[1] : "0";
my $final = (defined($r[2])) ? $r[2] : "???";
my $calltime = (defined($r[3])) ? $r[3] : 0;
my $name = $hash->{NAME};
my $success = (substr($final,0,2) eq "ok") ? 1 : 0;
my @a;
my @fo = (300,0,0);
my (undef,$nr,$ringtime,$msg,$repeat,$force) = split("\\|",$hash->{helper}{CALL}); # zerlegen wir den Original Call
Log3 $name, 4,"$name, CALLDone -> $string";
$hash->{helper}{CALL_TIME} = $calltime;
$hash->{helper}{CALL_BYE} = $final;
$hash->{helper}{CALL_ERROR} = $error;
$hash->{helper}{CALL_NAME} = SIP_search_phonebook($hash,$name,$nr);
SIP_write_history($hash,$name);
delete($hash->{helper}{CALL_PID}) if (defined($hash->{helper}{CALL_PID}));
delete($hash->{CPID}) if (defined($hash->{CPID}));
delete $hash->{lastnr} if (defined($hash->{lastnr}));
if ($force)
{
$force =~ s/^\&//;
@fo = split(",",$force);
$fo[2]++ if(!$success); # Anzahl bisheriger Durchläufe
}
if ($error ne "1")
{
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "call","done");
readingsBulkUpdate($hash, "call_time",int($calltime)) if defined($calltime);
readingsBulkUpdate($hash, "last_error",$final);
readingsBulkUpdate($hash, "call_state","fail");
readingsBulkUpdate($hash, "call_success","0");
readingsBulkUpdate($hash, "call_attempt",$fo[2]) if ($force);
readingsBulkUpdate($hash, "call_attempt","0") if (!$force);
readingsBulkUpdate($hash, "state",$hash->{'.oldstate'}) if defined($hash->{'.oldstate'});
readingsEndUpdate($hash, 1);
}
else
{
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "call","done");
readingsBulkUpdate($hash, "call_state",lc($final));
readingsBulkUpdate($hash, "call_success",$success);
readingsBulkUpdate($hash, "call_time",int($calltime)) if defined($calltime);
readingsBulkUpdate($hash, "state",$hash->{'.oldstate'}) if defined($hash->{'.oldstate'});
readingsBulkUpdate($hash, "call_attempt",$fo[2]) if ($force);
readingsBulkUpdate($hash, "call_attempt","0") if (!$force);
readingsEndUpdate($hash, 1);
}
if ($force && !$success)
{
$repeat++; $repeat--;
my $nr2 = $nr;
$nr2 =~ tr/0-9//cd;
$nr2 .= "_";
$nr2 .= unpack ("%16C*",$msg);
if ($fo[2] < $fo[1]) # bisherige Anzahl kleiner max Wiederholungen ?
{
$force = "&".join(",", @fo);
my $time_s = strftime("\%H:\%M:\%S", gmtime($fo[0]));
$error = CommandDefine(undef, "at_forcecall_".$nr2." at +".$time_s." set $name call $nr $ringtime $msg *".$repeat." ".$force);
if (!$error) { $attr{"at_forcecall_".$nr2}{room} = AttrVal($name,"room","Unsorted"); }
else { Log3 $name,2,"$name, $error"; }
Log3 $name,4,"$name, at_forcecall_".$nr2." at +".$time_s." set $name call $nr $ringtime $msg *".$repeat." ".$force;
}
else
{
Log3 $name,3,"$name, at_forcecall_".$nr2." max count $fo[1] reached giving up !";
}
} ### end force and !$success
my $nextcall = shift @fifo; # sind da noch Calls in der Queue ?
if ($nextcall)
{
@a = split(" ",$nextcall);
$error = SIP_Set($hash,@a);
Log3 $name,3,"$name, error setting nextcall $nextcall -> $error" if ($error);
return undef;
} else { Log3 $name,5,"$name, fifo is empty"; }
if (exists($hash->{'.elbc'}))
{
@a = (undef,"listen");
Log3 $name,4,"$name, try restarting listen process after call ends";
$error = SIP_Set($hash,@a);
Log3 $name,3,"$name, error restarting listen -> $error" if ($error);
delete $hash->{'.elbc'};
} else { Log3 $name,5,"$name, no elbc"; }
delete $hash->{helper}{CALL};
return undef;
}
#####################################
sub SIP_Set($@)
{
my ($hash, @a) = @_;
my $name = $hash->{NAME};
my $cmd = (defined($a[1])) ? $a[1] : "?";
my $subcmd;
my $error;
return join(" ", sort keys %sets) if ($cmd eq "?");
if (($cmd eq "call") || ($cmd eq "listen"))
{
my $pwd = SIP_readPassword($name);
unless (defined $pwd)
{
$error = "Error: no SIP user password set. Please define it with 'set $name password Your_SIP_User_Password'";
Log3 $name,2,"$name, $error";
return $error;
}
}
if ($cmd eq "call")
{
my $nr = (defined($a[2])) ? $a[2] : "";
my $ringtime = (defined($a[3])) ? $a[3] : 30;
my $msg = (defined($a[4])) ? $a[4] : AttrVal($name, "sip_audiofile_call", "");
return "missing target call number" if (!$nr);
return "invalid max time : $ringtime" unless $ringtime =~ m/^\d+$/;
if (exists($hash->{CPID}))
{
return "there is already a call activ for target $nr" if (defined($hash->{lastnr}) && ($hash->{lastnr} eq $nr));
my $call = join(" ",@a);
push (@fifo,$call);
Log3 $name ,4,"$name, add call $call to fifo so we can do it later !";
return undef;
}
my $anz = @a;
$anz--; # letztes Element
my $force = (substr($a[$anz],0,1) eq "&") ? $a[$anz] : 0;
if ($force)
{
Log3 $name,3,"$name, force call $force";
$force =~ s/^\&//;
my @fo = split(",", $force);
$fo[0] = int(AttrVal($name,"sip_force_interval",300)) if (!$fo[0]);
$fo[1] = int(AttrVal($name,"sip_force_max",99)) if (!$fo[1]);
$fo[2] = 0 if (!$fo[2]);
$force = "&".join("," , @fo);
$anz--; # checken wir dann noch auf repeat
}
my $repeat = 0;
if ((substr($a[$anz],0,1) eq "*") && ($anz > 3))
{
$repeat = $a[$anz];
$repeat =~ s/^\*//;
$repeat ++; $repeat --;
} # * weg , Rest als Int
Log3 $name,4,"$name, msg will be repeat $repeat times" if ($repeat);
if (exists($hash->{LPID}) && (AttrVal($name,"sip_elbc","no") eq "yes"))
{
Log3 $name,4,"$name, listen process ".$hash->{LPID}." must be killed befor we start a new call !";
BlockingKill($hash->{helper}{LISTEN_PID});
delete $hash->{helper}{LISTEN_PID};
delete $hash->{LPID};
readingsSingleUpdate($hash,"listen_alive","no",1);
$hash->{'.elbc'} = 1; # haben wir gerade einen listen Prozess abgeschossen ?
}
if ($msg)
{
if (substr($msg,0,1) eq "-")
{
Log3 $name, 4, $name.", message DTMF = $msg";
}
elsif (substr($msg,0,1) eq "!") # Text2Speech Text ?
{
if ($msg eq AttrVal($name,"sip_audiofile_call", ""))
{
@a = split(" ",AttrVal($name,"sip_audiofile_call", ""));
unshift (@a, ('t2s_name','tts')); # zwei Platzhalter einfügen , Text beginnt jetzt in $a[2]
}
else
{
shift @a;
shift @a;
pop @a if ($force); # das & muss ggf. auch noch weg
pop @a if ($repeat); # das * muss ggf. auch weg
$a[0] = "t2s_name";
$a[1] = "tts"; # Kommando des Set Befehls
}
$a[2] =~ s/^\!//; # das ! muss weg
if (!$a[2]) # ist denn jetzt noch etwas übrig geblieben ?
{
Log3 $name,4,"name, no valid text found in message : $msg";
return "No message text after [!] found";
}
# gibt es denn Text schon als mp3 ?
my $filename = SIP_check_T2S_File($hash,@a);
if($filename)
{
$cmd = "$name call $nr $ringtime $filename";
$cmd .= " *".$repeat if ($repeat);
$cmd .= " ".$force if ($force);
Log3 $name,5,"$name, set call new -> $cmd";
return CommandSet(undef,$cmd);
}
# die nächsten vier brauchen wir unbedingt fuer T2S
$hash->{callnr} = $nr;
$hash->{ringtime} = $ringtime;
$hash->{forcecall} = $force;
$hash->{repeat} = $repeat;
$error = SIP_create_T2S_File($hash,@a); # na dann lege schon mal los
return $error if defined($error); # Das ging leider schief
readingsSingleUpdate($hash,"call_state","waiting T2S",1);
RemoveInternalTimer($hash);
# geben wir T2S mal ein paar Sekunden
InternalTimer(gettimeofday()+int(AttrVal($name,"T2S_Timeout",5)), "SIP_wait_for_t2s", $hash);
return undef;
}
elsif (-e $msg)
{
Log3 $name, 4, $name.", audio file $msg found";
$error = SIP_MP3_conv($hash,$msg,$name) if ($msg =~/\.mp3$/);
if (!$error)
{
$msg =~ s/mp3/alaw/;
$error = "unknown audio type, please use only .alaw , .ulaw or .mp3" if (($msg !~ /\.al(.+)$/) && ($msg !~ /\.ul(.+)$/));
$error = "audio file $msg not found" if(!-e $msg);
}
}
else
{
$error = "audio file $msg not found";
}
if ($error)
{
readingsSingleUpdate($hash, "last_error",$error,1);
Log3 $name, 3, "$name, $error !";
$hash->{repeat} = 0;
$hash->{forcecall} = 0;
return $error;
}
}
else { Log3 $name, 4, $name.", calling $nr, ringtime: $ringtime , no message"; }
$hash->{lastnr} = $nr;
my $arg = "$name|$nr|$ringtime|$msg|$repeat"; # da muss force nicht mit
Log3 $name, 4, "$name, $arg";
#BlockingCall($blockingFn, $arg, $finishFn, $timeout, $abortFn, $abortArg);
$hash->{helper}{CALL_PID} = BlockingCall("SIP_CALLStart",$arg, "SIP_CALLDone") unless(exists($hash->{helper}{CALL_PID}));
if($hash->{helper}{CALL_PID})
{
$hash->{CPID} = $hash->{helper}{CALL_PID}{pid};
$hash->{helper}{CALL} = $arg."|$force"; # hier retten wir aber force
Log3 $name, 4, "$name, call -> ".$hash->{helper}{CALL};
Log3 $name, 5, "$name, call has pid ".$hash->{CPID};
$hash->{helper}{CALL_START} = time();
$hash->{helper}{CALL_TYPE} = "out";
$hash->{helper}{CALL_NR} = $nr;
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "call_state","invite");
readingsBulkUpdate($hash, "call",$nr);
readingsEndUpdate($hash, 1);
$hash->{'.oldstate'} = ReadingsVal($name,"state",undef);
return undef;
}
else
{ # das war wohl nix :(
Log3 $name, 3, "$name, CALL process start failed, arg : $arg";
$error = "can't execute call number $nr as NonBlockingCall";
readingsBeginUpdate($hash);
readingsBulkUpdate($hash, "last_error",$error);
readingsBulkUpdate($hash, "call_state","fail");
readingsEndUpdate($hash, 1);
delete $hash->{lastnr} if (defined($hash->{lastnr}));
return $error;
}
}
elsif ($cmd eq "listen")
{
my $type = AttrVal($name,"sip_listen","none");
return "there is already a listen process running with pid ".$hash->{LPID} if exists($hash->{LPID});
return "please set attr sip_listen to dtmf or wfp or echo first" if (AttrVal($name,"sip_listen","none") eq "none");
$error = SIP_try_listen($hash);
if ($error)
{
Log3 $name, 1, $name.", listen -> $error";
readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"state","error");
readingsBulkUpdate($hash,"last_error",$error);
readingsBulkUpdate($hash,"listen_alive","no");
readingsEndUpdate($hash, 1 );
return $error;
}
return undef;
}
elsif (($cmd eq "dtmf_event") && defined($a[2]))
{
readingsSingleUpdate($hash, "dtmf",$a[2],1);
return undef;
}
elsif ($cmd eq "fetch")
{
readingsSingleUpdate($hash, "caller","fetch",1);
return undef;
}
elsif ($cmd eq "reject")
{
readingsSingleUpdate($hash, "caller","reject",1);
return undef;
}
elsif ($cmd eq "reset")
{
$hash->{".reset"} = 1;
SIP_updateConfig($hash);
return undef;
}
# die ersten beiden brauchen wir nicht mehr
shift @a;
shift @a;
# den Rest als ein String
$subcmd = join(" ",@a);
if ($cmd eq "password")
{
return SIP_storePassword($name,$subcmd);
}
return "Unknown argument: $cmd, choose one of ".join(" ", sort keys %sets);
}
sub SIP_Get($@)
{
my ($hash, @a) = @_;
my $name = $hash->{NAME};
my $cmd = $a[1];
return "get $name needs at least one argument" if(int(@a) < 2);
shift @a;
shift @a;
# den Rest als ein String
my $subcmd = join(" ",@a);
if ($cmd eq "search_phonebook")
{
return SIP_search_phonebook($hash,$name,$subcmd);
}
# return "Unknown argument $cmd, choose one of " . join(" ", sort keys %gets);
return undef;
}
sub SIP_Undef($$)
{
my ($hash, $name) = @_;
$ua->cleanup if (defined($ua));
BlockingKill($hash->{helper}{LISTEN_PID}) if (defined($hash->{helper}{LISTEN_PID}));
#RemoveInternalTimer($hash);
#RemoveInternalTimer($name);
return undef;
}
sub SIP_ListenStart($)
{
my ($name) = @_;
return unless(defined($name));
my $logname = $name."[".$$."]";
my $hash = $defs{$name}; # $hash / $name gueltig in diesem Block
$hash->{parent} = getppid();
Log3 $name,4,"$logname, my parent is ".$hash->{parent};
my $dtmfloop; # Ende-Flag für die DTMF-Schleife
my $okloop; # Ende-Flag für die OK-Ansage
my $okloopbye = 0; # Ende-Flag für recv_bye währne der OK-Ansage
my $byebye = 0; # Anrufer hat aufgelegt
my $packets = 50;
my $block_it;
my $calltime = 0;
my $sub_create;
my $sub_invite_wfp;
my $sub_filter;
my $sub_bye;
my $sub_dtmf;
my $send_something;
$hash->{helper}{CALL_EST} = 0;
$ua = undef;
my $error = SIP_Register($hash,"listen_".AttrVal($name,"sip_listen",""));
return $name."|ListenRegister: $error" if ($error);
my $msg1 = AttrVal($name, "sip_audiofile_dtmf", "");
my $msg2 = AttrVal($name, "sip_audiofile_ok", "");
my $msg3 = AttrVal($name, "sip_audiofile_wfp", "");
$msg1 = SIP_check_file($hash,$hash->{audio1}) if (defined($hash->{audio1}));
$msg1 = SIP_check_file($hash,$msg1) if (!defined($hash->{audio1}) && $msg1);
$msg2 = SIP_check_file($hash,$hash->{audio2}) if (defined($hash->{audio2}));
$msg2 = SIP_check_file($hash,$msg2) if (!defined($hash->{audio2}) && $msg2);
$msg3 = SIP_check_file($hash,$hash->{audio3}) if (defined($hash->{audio3}));
$msg3 = SIP_check_file($hash,$msg3) if (!defined($hash->{audio3}) && $msg3);
Log3 $name,4,"$logname, using $msg1 for audio_dtmf" if ($msg1);
Log3 $name,4,"$logname, using $msg2 for audio_ok" if ($msg2);
Log3 $name,4,"$logname, using $msg3 for audio_wfp" if ($msg3);
$hash->{dtmf} = 0;
$hash->{dtmf_event} = "";
$hash->{old} ="-";
$send_something = sub
{
return unless $packets-- > 0;
my $buf = sprintf "%010d",$packets;
$buf .= "1234567890" x 15;
return $buf; # 160 bytes for PCMU/8000
};
$sub_dtmf = sub
{
my ($event,$dur) = @_;
Log3 $name,5,"$logname, DTMF Event: $event - $dur ms";
return if (int($dur) < 90);
if (($event eq "#") || ($event eq "*"))
{
$hash->{dtmf} = 1;
$hash->{dtmf_event} = "";
$hash->{old} = $event;
return;
}
if (($event ne $hash->{old}) && $hash->{dtmf})
{
$hash->{dtmf} ++;
$hash->{old} = $event;
$hash->{dtmf_event} .= $event;
Log3 $name,5,"$logname, DTMF: ".$hash->{dtmf_event}." , Anz: ".$hash->{dtmf};
if ($hash->{dtmf} > int(AttrVal($name,"sip_dtmf_size",2)))
{
BlockingInformParent("SIP_rSU", [$name, "dtmf_event;".$hash->{dtmf_event}], 0);
$hash->{dtmf} = 0;
$hash->{dtmf_event} = "";
$hash->{old} = "-";
$dtmfloop = 1;
}
}
return;
};
$sub_create = sub
{
my ($call,$request,$leg,$from) = @_;
$hash->{helper}{call} = $call;
$hash->{request} = $request;
$hash->{leg} = $leg;
$hash->{from} = $from;
Log3 $name,4,"$logname, cb_create : ".$request->method;
my $response = ($block_it) ? $request->create_response('487','Request Terminated') : $request->create_response('180','Ringing');
$call->{endpoint}->new_response( $call->{ctx},$response,$leg,$from );
1;
};
$sub_invite_wfp = sub
{
my ($a,$b,$c,$d) = @_;
my $waittime = int(AttrVal($name, "sip_waittime", 10));
my $i;
$packets = 50;
$hash->{helper}{CALL_EST} = 0;
Log3 $name, 5,"$logname, cb_invite_wfp";
for($i=1; $i<=$waittime; $i++)
{
if ($block_it) #und gleich wieder weg
{
sleep int(AttrVal($name, "sip_waittime", 2)); # kleine Pause
last;
}
Log3 $name, 4,"$logname, SIP_invite -> ringing $i";
select(undef, undef, undef, 1); # 1 Sekunde Pause
my $action = BlockingInformParent("SIP_rSU", [$name, "caller_state;ringing $i"], 1);
if(defined($action))
{
Log3 $name, 5,"$logname, cb_invite_wfp action $action";
if ( $action eq "fetch" )
{
$hash->{helper}{CALL_EST} = time();
$hash->{helper}{CALL_BYE} = "fetch";
Log3 $name, 4,"$logname, cb_invite_wfp fetch";
BlockingInformParent("SIP_rSU", [$name, "caller_state;fetching"], 0);
last;
}
elsif ( $action eq "reject" )
{
Log3 $name, 4,"$logname, cp_invite_wfp reject";
BlockingInformParent("SIP_rSU", [$name, "caller_state;rejected"], 0);
my $call = $hash->{helper}{call};
my $request = $hash->{request};
my $leg = $hash->{leg};
my $from = $hash->{from};
my $response = $request->create_response('603','Declined');
$call->{endpoint}->new_response( $call->{ctx},$response,$leg,$from );
$hash->{helper}{CALL_BYE} ="reject";
$hash->{helper}{CALL_TIME}= 0;
SIP_write_history($hash,$logname); # wir kommen nicht zu Bye!
BlockingInformParent("SIP_rBU", [$name, "caller;none|caller_state;waiting|caller_nr;---|caller_time;0|caller_name;---"], 0);
last;
}
}
}
if (($i>$waittime) || $block_it)
{
$calltime = ($hash->{helper}{CALL_EST}) ? int(time()-$hash->{helper}{CALL_EST}) : 0;
BlockingInformParent("SIP_rBU", [$name, "caller;none|caller_state;waiting|caller_nr;---|caller_time;$calltime|caller_name;---"], 0);
}
return 0;
};
$sub_filter = sub
{
my ($a,undef) = @_;
Log3 $name, 5, "$logname, SIP_filter : $a";
$block_it = 0;
$hash->{helper}{CALL_START} = time(); # nochmal prüfen !
my ($caller,undef) = split("\;", $a);
my @callers;
my $caller_nr;
my $caller_name;
$caller =~ s/\"|\>|\<|^\s+|\s+$//g; # fhem mag keine <> in ReadingsVal :(
($caller_nr,undef) = split("\@", $caller);
($caller_name,$caller_nr) = split("\:", $caller_nr);
$caller_name =~ s/sip//g;
$caller_name =~ s/\s+$//g; # Leerzeichen am Anfang und Ende nochmal entfernen
$caller_nr = "0000" if(!$caller_nr);
Log3 $name, 4, "$logname, SIP_filter: caller $caller, caller_nr $caller_nr, caller_name $caller_name";
$caller_name = SIP_search_phonebook($hash,$logname,$caller_nr) if (!$caller_name || ($caller_name eq $caller_nr));
BlockingInformParent("SIP_rBU", [$name, "caller;$caller|caller_nr;$caller_nr|caller_name;$caller_name|caller_time;0|caller_state;calling"], 0);
$hash->{helper}{CALL_NAME} = $caller_name;
$hash->{helper}{CALL_NR} = $caller_nr;
my $block = AttrVal($name,"sip_blocking",undef);
if (defined($block))
{
my @blockers = split (/,/,$block);
foreach (@blockers)
{
if ((index($caller_nr, $_) > -1) || ($_ eq ".*"))
{
$hash->{helper}{CALL_BYE} = "block";
$hash->{helper}{CALL_TIME} = 0;
SIP_write_history($hash,$logname); # wir kommen nicht zu Bye!
BlockingInformParent("SIP_rSU", [$name, "caller_state;blocking"], 0);
Log3 $name, 4, "$logname, blocking $caller_nr found on $block";
$block_it = 1;
#$byebye = 1;
return 1;
}
}
}
my $filter = AttrVal($name,"sip_filter",undef);
if (defined($filter))
{
@callers = split (/,/,$filter);
foreach (@callers) { return 1 if (index($caller_nr, $_) > -1); }
$hash->{helper}{CALL_BYE} = "ignore";
$hash->{helper}{CALL_TIME} = 0;
SIP_write_history($hash,$logname); # wir kommen nicht zu Bye!
BlockingInformParent("SIP_rSU", [$name, "caller_state;ignoring"], 0);
Log3 $name, 4, "$logname, ignoring $caller_nr number not found in $filter";
return 0;
}
return 1;
};
$sub_bye = sub
{
my ($event) = @_;
Log3 $name, 5, "$logname, SIP_bye : $event";
$calltime = ($hash->{helper}{CALL_EST}) ? int(time()-$hash->{helper}{CALL_EST}) : 0;
BlockingInformParent("SIP_rBU", [$name, "caller;none|caller_state;hangup|caller_time;$calltime|caller_nr;---|caller_name;---"], 0);
$hash->{helper}{CALL_BYE}="ok" if (!defined($hash->{helper}{CALL_BYE})); #wfp oder filter kann es schon vorbesetzt haben !
$hash->{helper}{CALL_TIME}= $calltime;
SIP_write_history($hash,$logname);
$hash->{helper}{CALL_EST} = 0;
$byebye = 1;
return 1;
};
################
if (AttrVal($name,"sip_listen", "none") eq "dtmf")
{
$dtmfloop = 0; # Ende-Flag für die DTMF-Schleife
$okloop = 0; # Ende-Flag für die OK-Ansage
$okloopbye = 0; # Ende-Flag für recv_bye während der OK-Ansage
# ToDo : was kann davon noch nach while(1) ?
#$byebye = 0; # Anrufer hat aufgelegt . sthet nun in while(1)
#BlockingInformParent("SIP_rBU", [$name, "caller;none|caller_state;waiting"], 0);
while(1)
{
my $call;
$byebye = 0; # eingefügt mit V1.82 Fehler gefunden von tmp88 ,Forum : https://forum.fhem.de/index.php/topic,67443.msg819729.html#msg819729
$hash->{dtmf} = 0;
$hash->{dtmf_event} = "";
$hash->{old} ="-";
$hash->{helper}{CALL_TYPE} = "dtmf";
$ua->listen (cb_create => \&$sub_create,
cb_invite => sub {
Log3 $name, 5, "$logname, cb_invite_dtmf";
$hash->{helper}{CALL_EST} = 0;
if (!$block_it)
{
BlockingInformParent("SIP_rSU", [$name,"caller_state;ringing"],0);
sleep int(AttrVal($name, "sip_ringtime", 3)); #Anrufer hört das typische Klingeln wenn die Gegenseite nicht abnimmt
}
},
filter => \&$sub_filter,
cb_established => sub {
(my $status,$call) = @_;
Log3 $name, 5, "$logname, cb_est_dtmf";
if (!$block_it)
{
$hash->{helper}{CALL_EST} = time();
BlockingInformParent("SIP_rSU", [$name,"caller_state;established"],0);
}
else {
sleep 1;
return 0;
}
} # sobald invite verlassen wird, wird in cb_established verzweigt
);
$ua->loop(\$call);
# Der SIP-Client ist jetzt im echo-Modus und zwar so lange, bis der Anrufer auflegt,
# das bekommen wir durch recv_bye mit
my $dtmf_loop = 1; # für jeden Anruf neu setzen
while ($dtmf_loop) # Schleife für Code-Ansage, DTMF-Erkennung, okay-Ansage
{
$dtmfloop = 0;
$okloop = 0;
$okloopbye = 0;
Log3 $name, 5, "$logname, while dtmf_loop : start reinvite1";
$call->reinvite(
init_media => $ua->rtp('send_recv',($msg1) ? $msg1 : $send_something),
rtp_param => [8, 160, 160/8000, 'PCMA/8000'],
cb_rtp_done => sub { $packets = 25; },
cb_dtmf => \&$sub_dtmf,
recv_bye => \&$sub_bye);
$ua->loop(\$dtmfloop, \$byebye);
Log3 $name, 5, "$logname, while dtmf_loop : dtmfloop : $dtmfloop , byebye : $byebye";
if (!$byebye)
{ # Anrufer hat nicht aufgelegt
Log3 $name, 5, "$logname, while dtmf_loop : reinvite2";
$call->reinvite(
init_media => $ua->rtp('send_recv',($msg2) ? $msg2 : $send_something),
rtp_param => [8, 160, 160/8000, 'PCMA/8000'],
cb_rtp_done => sub { select(undef, undef, undef, 0.1); $okloop = 1; $packets = 50;},
recv_bye => sub { $okloopbye = 1; },
cb_cleanup => sub {0},
);
Log3 $name, 5, "$logname, while dtmf_loop : after reinvite2 $okloop , $okloopbye";
$ua->loop(\$okloop,\$okloopbye); # ohne diese loop endet der Anruf sofort
}
else { $dtmf_loop = 0; $byebye = 1; Log3 $name, 5, "$logname, aufgelegt";} # Schleife beenden, Anrufer hat aufgelegt
Log3 $name, 5, "$logname, while dtmf_loop, okloopbye : $okloopbye , byebye : $byebye";
if ( $okloopbye || $byebye )
{
# wenn jemand mitten im "okay" auflegt
$dtmf_loop = 0; # beende die innere Loop
$byebye = 1;
}
else {
$dtmf_loop = ((AttrVal($name,"sip_dtmf_loop","once") eq 'once')) ? 0 : 1;
$calltime = (defined($hash->{helper}{CALL_EST})) ? int(time()-$hash->{helper}{CALL_EST}) : 0;
if(!$dtmf_loop)
{
BlockingInformParent("SIP_rBU", [$name, "caller;none|caller_state;hangup|caller_time;$calltime|caller_nr;---|caller_name;---"], 0);
$hash->{helper}{CALL_BYE} = "ok";
$hash->{helper}{CALL_TIME} = $calltime;
SIP_write_history($hash,$logname);
}
} # führt ggf. zum Schleifenende
} # end inner loop
Log3 $name, 5, "$logname, end while dtmf_loop, byebye : $byebye";
if (!$byebye)
{ # Anrufer hat nicht aufgelegt und nur ein DTMF angefordert
my $hanguploop;
$call->bye( cb_final => \$hanguploop );
$ua->loop( \$hanguploop );
}
Log3 $name, 5, "$logname, while(1)";
} # while(1)
}
elsif (AttrVal($name,"sip_listen", "none") eq "wfp")
{
$hash->{helper}{CALL_TYPE} = "wfp";
$ua->listen(
cb_create => \&$sub_create,
cb_invite => \&$sub_invite_wfp,
cb_established => sub { $hash->{helper}{CALL_EST} = time(); Log3 $name, 5, "$logname, cb_est_wfp";},
filter => \&$sub_filter,
recv_bye => \&$sub_bye,
init_media => $ua->rtp('send_recv',($msg3) ? $msg3 : $send_something),
#cb_rtp_done => sub {Log3 $name, 5, "$logname, wfp cb_rtp_done";}, legt nicht mehr auf wenn aktiv !
rtp_param => [8, 160, 160/8000, 'PCMA/8000']
); # options are invite and hangup
}
elsif (AttrVal($name,"sip_listen", "none") eq "echo")
{
$hash->{helper}{CALL_TYPE} = "echo";
$ua->listen(
filter => \&$sub_filter,
cb_create => \&$sub_create,
cb_invite => sub {
Log3 $name, 5, "$logname, cb_invite_echo";
$hash->{helper}{CALL_EST} = 0;
if (!$block_it)
{
Log3 $name, 5, "$logname, cb_invite";
BlockingInformParent("SIP_rSU", [$name,"caller_state;ringing"],0);
sleep int(AttrVal($name, "sip_ringtime", 3)); #Anrufer hört das typische Klingeln wenn die Gegenseite nicht abnimmt
}
},
cb_established => sub {
Log3 $name, 5, "$logname, cb_estab_echo";
if (!$block_it)
{
Log3 $name, 5, "$logname, cb_est";
$hash->{helper}{CALL_EST} = time();
BlockingInformParent("SIP_rSU", [$name,"caller_state;established"],0);
}
else
{
sleep 1;
return 0;
}
},
init_media => $ua->rtp( 'recv_echo',undef,0 ),
rtp_param => [8, 160, 160/8000, 'PCMA/8000'],
recv_bye => \&$sub_bye,
);
}
else { return $name."|end"; }
$ua->loop;
return $name."|end"; # hier sollten wir eigentlich nie himkommen !
}
sub SIP_ListenDone($)
{
my ($string) = @_;
return unless(defined($string));
my @r = split("\\|",$string);
my $hash = $defs{$r[0]};
my $ret = (defined($r[1])) ? $r[1] : "unknown error";
my $name = $hash->{NAME};
Log3 $name, 5,"$name, ListenDone -> $string";
delete($hash->{helper}{LISTEN_PID});
delete $hash->{LPID};
RemoveInternalTimer($name);
if ($ret ne "end")
{
readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"state","error");
readingsBulkUpdate($hash,"last_error",$ret);
readingsBulkUpdate($hash,"listen_alive","no");
readingsEndUpdate($hash, 1 );
Log3 $name, 3 , "$name, listen error -> $ret";
return if(IsDisabled($name));
InternalTimer(gettimeofday()+AttrVal($name, "sip_watch_listen", 60), "SIP_try_listen", $hash);
}
else
{
readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"state","ListenDone");
readingsBulkUpdate($hash,"listen_alive","no");
readingsEndUpdate($hash, 1 );
return if(IsDisabled($name));
return if(!AttrVal($name, "sip_dtmf", 0));
SIP_try_listen($hash);
}
return;
}
sub SIP_try_listen($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
my $waits = AttrVal($name, "sip_watch_listen", 60);
my $audio1 = AttrVal($name, "sip_audiofile_dtmf","-");
my $audio2 = AttrVal($name, "sip_audiofile_ok", "-");
my $audio3 = AttrVal($name, "sip_audiofile_wfp", "-");
my @a = ("tts","tts", "-");;
if (AttrVal($name,"sip_listen","none") eq "dtmf")
{
if ((substr($audio1,0,1) eq "!") && !defined($hash->{audio1})) # muss erst T2S gefragt werden ?
{
$audio1 =~ s/^\!//;
$hash->{audio1} = $audio1;
$a[2] = $audio1;
Log3 $name ,4,"$name, hole $audio1";
}
if ((substr($audio2,0,1) eq "!") && !defined($hash->{audio2})) # muss erst T2S gefragt werden ?
{
$audio2 =~ s/^\!//;
$hash->{audio2} = $audio2;
$a[2] = $audio2;
Log3 $name ,4,"$name, hole $audio2";
}
}
elsif ((substr($audio3,0,1) eq "!") && !defined($hash->{audio3}) && (AttrVal($name,"sip_listen","none") eq "wfp")) # muss erst T2S gefragt werden ?
{
$audio3 =~ s/^\!//;
$hash->{audio3} = $audio3;
$a[2] = $audio3;
Log3 $name ,4,"$name, hole $audio3";
}
if ($a[2] ne "-")
{
# prüfen ob es schon eine passende mp3 Datei gibt
my $filename = SIP_check_T2S_File($hash,@a);
if (!$filename)
{
my $ret = SIP_create_T2S_File($hash,@a);
if ($ret)
{
delete $hash->{audio1} if defined($hash->{audio1});
delete $hash->{audio2} if defined($hash->{audio2});
delete $hash->{audio3} if defined($hash->{audio3});
return $ret;
}
#starte die Überwachung von T2S
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+int(AttrVal($name,"T2S_Timeout",5)), "SIP_watchdog_T2S", $hash);
return undef;
}
else
{
Log3 $name, 4 , "$name, T2S not used $filename exits";
$hash->{audio1} = $filename if defined($hash->{audio1});
$hash->{audio2} = $filename if defined($hash->{audio2});
$hash->{audio3} = $filename if defined($hash->{audio3});
}
}
$hash->{helper}{LISTEN_PID} = BlockingCall("SIP_ListenStart",$name, "SIP_ListenDone") unless(exists($hash->{helper}{LISTEN_PID}));
if ($hash->{helper}{LISTEN_PID})
{
$hash->{LPID} = $hash->{helper}{LISTEN_PID}{pid};
Log3 $name, 4 , $name.", Listen new PID : ".$hash->{LPID};
RemoveInternalTimer($name);
InternalTimer(gettimeofday()+$waits, "SIP_watch_listen", $name); # starte die Überwachung
delete $hash->{audio1};
delete $hash->{audio2};
delete $hash->{audio3};
return 0;
}
else
{
Log3 $name, 2 , $name.", Listen Start failed, waiting $waits seconds for next try";
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$waits, "SIP_try_listen", $hash);
return "Listen Start failed";
}
}
sub SIP_watch_listen($)
{
# Lebt denn der Listen Prozess überhaupt noch ?
my ($name) = @_;
my $hash = $defs{$name};
my $listen_dead = 0;
RemoveInternalTimer($name);
return if (IsDisabled($name));
return if (!defined($hash->{LPID}));
my $cmd = "ps -e | grep '".$hash->{LPID}." '";
my $result = qx($cmd);
my $age = int(ReadingsAge($name, "expire", 0));
my $maxage = int(ReadingsNum($name,"expire",300)*0.7);
my $alive = ReadingsVal($name,"listen_alive","no");
my $waits = AttrVal($name, "sip_watch_listen", 60);
if (($age > $maxage) && ($alive ne "no")) # nach expire/2 Sekunden sollte sich der listen Prozess erneut melden
{
Log3 $name, 2 , "$name, expire timestamp is $age seconds old, restarting listen process";
readingsSingleUpdate($hash,"listen_alive","no",1);
$alive = "no";
}
elsif (index($result,"perl") == -1)
{
Log3 $name, 2 , $name.", cant find listen process ".$hash->{LPID}." in process list !";
$alive = "no";;
}
else { Log3 $name, 5 , $name.", listen process ".$hash->{LPID}." found"; }
if ($alive eq "no")
{
BlockingKill($hash->{helper}{LISTEN_PID});
delete $hash->{helper}{LISTEN_PID};
delete $hash->{LPID};
InternalTimer(gettimeofday()+2, "SIP_try_listen", $hash, 0);
}
InternalTimer(gettimeofday()+$waits, "SIP_watch_listen", $name, 0);
return;
}
sub SIP_wait_for_t2s($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash);
my $t2s_name = AttrVal($name,"T2S_Device",undef);
my $file = ReadingsVal($t2s_name,"lastFilename","");
my $msg = "";
Log3 $name,4,"$name, wait_for_t2s file : $file";
if (-e $file)
{
Log3 $name,4,"$name, new T2S file $file";
my $out = $file;
$out =~ s/mp3/alaw/;
my $error = SIP_MP3_conv($hash,$file,$name);
$msg = $out if (!$error && (-e $out));
}
else
{
Log3 $name,3,"$name, timeout waiting for T2S";
if ($hash->{callnr})
{
readingsSingleUpdate($hash,"call_state","T2S timeout",1);
return undef;
}
}
if (!$hash->{callnr})
{
if (defined($hash->{audio3}))
{
$hash->{audio3} = $msg;
SIP_try_listen($hash);
return undef;
}
elsif (defined($hash->{audio2}))
{
$hash->{audio2} = $msg;
SIP_try_listen($hash);
return undef;
}
elsif (defined($hash->{audio1}))
{
$hash->{audio1} = $msg;
SIP_try_listen($hash);
return undef;
}
}
# nun aber calling
my $repeat = "*".$hash->{repeat};
my @a;
if ($hash->{forcecall})
{ @a = ($name,"call",$hash->{callnr}, $hash->{ringtime},$msg,$repeat,$hash->{forcecall}) ; }
else
{ @a = ($name,"call",$hash->{callnr}, $hash->{ringtime},$msg,$repeat) ; }
delete($hash->{callnr});
delete($hash->{ringtime});
delete($hash->{forcecall});
delete($hash->{repeat});
my $ret = SIP_Set($hash , @a);
Log3 $name,3,"$name, error T2S Call : $ret" if defined($ret);
return undef;
}
######################################################
# storePW & readPW Code geklaut aus 72_FRITZBOX.pm :)
######################################################
sub SIP_storePassword($$)
{
my ($name, $password) = @_;
my $index = "SIP_".$name."_passwd";
my $key = getUniqueId().$index;
my $e_pwd = "";
if (eval "use Digest::MD5;1")
{
$key = Digest::MD5::md5_hex(unpack "H*", $key);
$key .= Digest::MD5::md5_hex($key);
}
for my $char (split //, $password)
{
my $encode=chop($key);
$e_pwd.=sprintf("%.2x",ord($char)^ord($encode));
$key=$encode.$key;
}
my $error = setKeyValue($index, $e_pwd);
return "error while saving SIP user password : $error" if(defined($error));
return "SIP user password successfully saved in FhemUtils/uniqueID Key $index";
}
sub SIP_readPassword($)
{
my ($name) = @_;
my $index = "SIP_".$name."_passwd";
my $key = getUniqueId().$index;
my ($password, $error);
#Log3 $name,5,"$name, read SIP user password from FhemUtils/uniqueID Key $key";
($error, $password) = getKeyValue($index);
if ( defined($error) )
{
Log3 $name,3, "$name, cant't read SIP user password from FhemUtils/uniqueID: $error";
return undef;
}
if ( defined($password) )
{
if (eval "use Digest::MD5;1")
{
$key = Digest::MD5::md5_hex(unpack "H*", $key);
$key .= Digest::MD5::md5_hex($key);
}
my $dec_pwd = '';
for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g))
{
my $decode=chop($key);
$dec_pwd.=chr(ord($char)^ord($decode));
$key=$decode.$key;
}
return $dec_pwd;
}
else
{
Log3 $name,3,"$name, no SIP user password found in FhemUtils/uniqueID";
return undef;
}
}
#####################################
sub SIP_check_file($$)
{
my ($hash,$file) = @_;
my $name = $hash->{NAME};
my $logname = $name."[".$$."]";
if (substr($file,0,1) eq "!")
{
Log3 $name,3,"$logname, Text : $file found, ignoring it";
return "";
}
if ($file =~/\.mp3$/)
{
my $ret = SIP_MP3_conv($hash,$file,$logname);
if ($ret)
{
Log3 $name,3,"$logname, $ret";
return "";
}
$file =~ s/mp3/alaw/;
}
if (($file !~ /\.al(.+)$/) && ($file !~ /\.ul(.+)$/))
{
Log3 $name,3,"$logname, audio file $file not type .alaw or .ulaw, ignoring it";
return "";
}
if (!-e $file)
{
Log3 $name,3,"$logname, audio file $file not found, ignoring it";
return "";
}
Log3 $name,5,"$logname, audio file $file found";
return $file;
}
sub SIP_create_T2S_File($@)
{
my ($hash,@a) = @_;
my $name = $hash->{NAME};
my $t2s_name = AttrVal($name,"T2S_Device",undef);
return "attr T2S_Device not set !" if !defined($t2s_name);
my $t2s_hash = (defined($defs{$t2s_name})) ? $defs{$t2s_name} : undef;
return "T2S_Device $t2s_name not found" if !defined($t2s_hash);
return "attr audio_converter not set" if !AttrVal($name,"audio_converter","");
return "external sox or ffmpeg programm not found, please install sox or ffmpeg first and set attr audio_converter" if !defined($hash->{AC});
my $t2s_file = ReadingsVal($t2s_name,"lastFilename",undef);
Log3 $name,3,"$name, Reading lastFilename not found at device $t2s_name, are you using a old version ?" if !defined($t2s_file);
readingsSingleUpdate($t2s_hash,"lastFilename","",0);
my $ret = Text2Speech_Set($t2s_hash, @a); # na dann lege schon mal los
if (defined($ret))
{
Log3 $name,3,"$name, T2S error : $ret";
readingsSingleUpdate($hash,"last_error",$ret,0);
return $ret; # Das ging leider schief
}
return undef; # alles klar
}
sub SIP_check_T2S_File($@)
{
my ($hash,@a) = @_;
my $name = $hash->{NAME};
my $t2s_name = AttrVal($hash->{NAME},"T2S_Device","");
return 0 if (!$t2s_name);
shift @a;
shift @a;
my $txt = join(" ",@a);
my $filename = (eval "use Digest::MD5;1") ? md5_hex("de|".$txt).".mp3" : "";
if ($filename)
{
my $file = AttrVal($t2s_name,"TTS_CacheFileDir", "cache"). "/".$filename;
Log3 $name,5,"$name, MD5: $txt -> $filename";
return $file if (-e $file);
}
Log3 $name,5,"$name, mp3 File file not found in cache";
return 0;
}
sub SIP_watchdog_T2S($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
Log3 $name,3,"$name, Timeout waiting for T2S";
if (defined($hash->{audio1}))
{
$hash->{audio1}="!T2S Timeout";
SIP_try_listen($hash);
return undef;
}
if (defined($hash->{audio2}))
{
$hash->{audio2}="!T2S Timeout";
SIP_try_listen($hash);
return undef;
}
if (defined($hash->{audio3}))
{
$hash->{audio3}="!T2S Timeout";
SIP_try_listen($hash);
return undef;
}
}
#####################################
# Benutzt um Infos aus dem Blockingprozess in die Readings zu schreiben
#####################################
sub SIP_rSU($$) {
my ($name, $line) = @_;
my $hash = $defs{$name};
my ($reading,$val) = split("\;",$line);
Log3 $hash, 5, "$name, readingS:$reading Val:$val";
readingsSingleUpdate($hash, $reading, $val, 1);
# Sonderfall bei wfp , Abfrage des Readings caller auf fetch oder reject
#my $action = ReadingsVal($name,"caller","");
#return $action if (($reading eq "caller_state") && (substr($val,0,7) eq "ringing") && (($action eq "fetch") || ($action eq "reject")));
return ReadingsVal($name,"caller","") if (($reading eq "caller_state") && (substr($val,0,7) eq "ringing"));
return undef;
}
sub SIP_rBU($$) {
my ($name, $line) = @_;
my $hash = $defs{$name};
readingsBeginUpdate($hash);
my @pair = split("\\|",$line);
foreach (@pair)
{
my ($reading,$val) = split("\;",$_);
{
Log3 $hash, 5, "$name, readingB:$reading Val:$val";
readingsBulkUpdate($hash, $reading, $val);
}
}
readingsEndUpdate($hash, 1 );
return undef;
}
sub SIP_MP3_conv($$$)
{
my ($hash,$file,$logname) = @_;
my $name = $hash->{NAME};
my $ret;
my $status;
my $cmd;
my $out = $file;
$out =~ s/mp3/alaw/;
if (-e $out)
{
Log3 $name,5,"$logname, not converted - using $out from cache";
return undef;
}
else
{
return "external sox or ffmpeg programm not found, please install sox or ffmpeg first and set attr audio_converter" if (!defined($hash->{AC}));
my $converter = AttrVal($name,"audio_converter","");
return "attr audio_converter not set" if(!$converter);
if ($converter eq "sox")
{
$cmd = $hash->{AC}." ".$file." -t raw -r 8000 -c 1 -e a-law ".$out." 2>&1";
Log3 $name,5,"$logname, $cmd";
$ret = qx($cmd);
if ($ret)
{
unlink $out;
$ret =~ s/\n//g;
Log3 $name,5,"$logname, sox output : $ret";
}
}
elsif ($converter eq "ffmpeg")
{
$cmd = $hash->{AC}." -v quiet -y -i ".$file." -f alaw -ar 8000 ".$out;
Log3 $name,5,"$logname, $cmd";
$ret = qx($cmd);
}
else { return "unknow audio_converter"; }
return "$converter : $ret" if ($ret);
return "converted file $out not found" if (!-e $out);
return undef;
}
}
sub SIP_search_phonebook($$$)
{
my ($hash,$logname,$number) = @_;
my $name = $hash->{NAME};
my $file = AttrVal($name,"phonebook",undef);
return "unknown" if (!$file);
Log3 $name,5,"$logname, Phonebook: $file, $number, ".int(AttrVal($name,"history_size",0));
my ($error, @lines) = FileRead($file);
if ($error)
{
Log3 $name,2,"$logname, phonebook : $error";
return "error";
}
my $i = @lines;
if (!@lines)
{
Log3 $name,2,"$logname, phonebook is empty";
return "empty";
}
Log3 $name,5,"$logname, read $i lines from phonebook";
foreach(@lines)
{
my ($nr,$na) = split(",",$_);
($nr,$na) = split("\\|",$_) if (!$na); # Liste vllt doch durch | getrennt ?
if ($na && ($nr eq $number))
{
Log3 $name,4,"$logname, found $na for number $number in phonebook";
return $na;
}
}
Log3 $name,3,"$logname, no entry found in phonebook for number $number";
return $number;
}
sub SIP_write_history($$)
{
my ($hash,$logname) = @_;
my $name = $hash->{NAME};
return if (!int(AttrVal($name,"history_size",0)));
$hash->{helper}{CALL_START} = time() if (!$hash->{helper}{CALL_START});
$hash->{helper}{CALL_TYPE} = "out" if (!$hash->{helper}{CALL_TYPE});
$hash->{helper}{CALL_TIME} = 0 if (!$hash->{helper}{CALL_TIME});
$hash->{helper}{CALL_NAME} = "????" if (!$hash->{helper}{CALL_NAME});
$hash->{helper}{CALL_NR} = "0000" if (!$hash->{helper}{CALL_NR});
$hash->{helper}{CALL_BYE} = "-" if (!$hash->{helper}{CALL_BYE});
my $file = AttrVal($name,"history_file","./log/$name.sip");
my ($error, @lines) = FileRead($file);
my $anz = @lines;
if ($error)
{
Log3 $name,2,"$logname, history file $file, $error";
return undef;
}
Log3 $name,4,"$logname, read $anz lines from history file $file";
while ($anz >= int(AttrVal($name,"history_size", 10))) { shift @lines; $anz--;}
my $line = FmtDateTime(int($hash->{helper}{CALL_START}))."|";
$line .= $hash->{helper}{CALL_TYPE}."|";
$line .= $hash->{helper}{CALL_NAME}."|";
$line .= $hash->{helper}{CALL_NR}."|";
$line .= $hash->{helper}{CALL_BYE}."|";
$line .= int($hash->{helper}{CALL_TIME})."|";
push @lines,$line;
$error = FileWrite($file, @lines);
if ($error)
{
Log3 $name,2,"$logname, history file $file, $error";
return undef;
}
$anz++;
delete $hash->{helper}{CALL_TIME};
delete($hash->{helper}{CALL_START});
delete($hash->{helper}{CALL_BYE});
delete($hash->{helper}{CALL_NAME});
delete($hash->{helper}{CALL_NR});
$hash->{helper}{CALL_EST} = 0;
if ($name ne $logname)
{ BlockingInformParent("SIP_rSU", [$name,"history_lines;$anz"],0); }
else
{ readingsSingleUpdate($hash,"history_lines",$anz,0);}
return undef;
}
sub SIP_html($;$)
{
my ($name,$header) = @_;
$name = "<none>" if(!$name);
$header = "SIP Call List" if(!$header);
my $error = (!$defs{$name} || $defs{$name}{TYPE} ne "SIP") ? "$name is not a SIP device" : "";
my $html = '<table class="roomoverview"><tr class="devTypeTr"><td><div class="devType">'.$header.'</div></td></tr><tr><td>';
$html .='<table class="block fbcalllist">
<tr align="center" class="fbcalllist header">
<td>State</td>
<td>Time</td>
<td>Name</td>
<td>Number</td>
<td>IO</td>
<td>Duration</td>
</tr>'."\n";
my $ehtml = "<tr align='center' number='1' class='odd'><td colspan='6'>";
my $end = "</table></td></tr></table>";
return $html.$ehtml.$error."</td></tr>".$end if($error);
my $hash = $defs{$name};
my @lines;
if (!int(AttrVal($name,"history_size",0)))
{
$html .= $ehtml."please set attr $name history_size first</td></tr>".$end;
return $html;
}
my $file = AttrVal($name,"history_file","./log/$name.sip");
($error, @lines) = FileRead($file);
my $anz = @lines;
if ($error)
{
$html .= $ehtml."error reading $file, $error</td></tr>".$end;
return $html;
}
if (!$anz)
{
$html .= $ehtml."file $file is empty !</td></tr>".$end;
return $html;
}
my $i = 1;
my $style = "style='padding-left:5px;padding-right:5px;'";
foreach(@lines)
{
my $oe = ($i %2) ? 'odd' : 'even';
my @a = split("\\|",$_);
my($d,$h,$m,$s,$dur,$sec);
if (int($a[5])>0)
{
$sec = $a[5];
$d=int($sec/(24*60*60));
$h=($sec/(60*60))%24;
$m=($sec/60)%60;
$s=$sec%60;
$dur = sprintf("%02s:%02s:%02s", $h, $m, $s);
} else { $dur = "-"; }
$html .= "<tr align='center' number='$i' class='$oe'>";
$html .= "<td $style>".$a[4]."</td>";
$html .= "<td $style>".$a[0]."</td>";
$html .= "<td $style>".$a[2]."</td>";
$html .= "<td $style>".$a[3]."</td>";
$html .= "<td $style>".$a[1]."</td>";
$html .= "<td $style>".$dur."</td></tr>\n";
$i++;
}
$html .= $end;
return $html;
}
1;
=pod
=item helper
=item summary SIP device
=item summary_DE SIP Ger&auml;t
=begin html
<a name="SIP"></a>
<h3>SIP</h3>
<ul>
Define a SIP-Client device.<br>
Wiki : <a href="https://wiki.fhem.de/wiki/SIP-Client">https://wiki.fhem.de/wiki/SIP-Client</a>
<br>
Forum : <a href="https://forum.fhem.de/index.php/topic,67443.0.html">https://forum.fhem.de/index.php/topic,67443.0.html</a>
<br><br>
<a name="SIPdefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; SIP</code>
<br><br>
Example:
<ul>
<code>define MySipClient SIP</code><br>
</ul>
</ul>
<br>
<a name="SIPset"></a>
<b>Set</b>
<ul>
<li>
<code>set &lt;name&gt; &lt;SIP password&gt;</code><br>
Stores the password for the SIP users. Without stored password the functions set call and set listen are blocked !<br>
IMPORTANT : if you rename the fhem Device you must set the password again!
</li>
<li>
<code>set &lt;name&gt; reset</code><br>
Stop any listen process and initialize device.<br>
</li>
<li>
<code>set &lt;name&gt; call &lt;number&gt [&lt;maxtime&gt;] [&lt;message&gt;]</code><br>
Start a call to the given number.<br>
Optionally you can supply a max time. Default is 30.
Optionally you can supply a message which is either a full path to an audio file or a relativ path starting from the home directory of the fhem.pl.
</li>
<li>
<code>set &lt;name&gt; listen</code><br>
attr sip_listen = dtmf :<br>
Start a listening process that receives calls. The device goes into an echo mode when a call comes in. If you press # on the keypad followed by 2 numbers and hang up the reading <b>dtmf</b> will reflect that number.<br>
attr sip_listen = wfp :<br>
Start a listening process that waits for incoming calls. If a call comes in for the SIP-Client the state will change to <b>ringing</b>. If you manually set the state to <b>fetch</b> the call will be picked up and the sound file given in attribute sip_audiofile will be played to the caller. After that the devive will go gack into state <b>listenwfp</b>.<br>
</li>
</ul>
<br>
<a name="SIPattr"></a>
<b>Attributes</b>
<ul>
<a name="sip_audiofile_wfp"></a><li>sip_audiofile_wfp<br>Audio file that will be played after <b>fetch</b> command. The audio file has to be generated via <br>
sox &lt;file&gt;.wav -t raw -r 8000 -c 1 -e a-law &lt;file&gt;.al<br>since only raw audio format is supported.</li><br>
<a name="sip_audiofile_call"></a><li>sip_audiofile_call</li>
<a name="sip_audiofile_dtmf"></a><li>sip_audiofile_dtmf</li>
<a name="sip_audiofile_ok"></a><li>sip_audiofile_ok</li>
<a name="sip_listen"></a><li>sip_listen (none , dtmf , wfp)</li>
<a name="sip_from"></a><li>sip_from<br>My sip client info, defaults to sip:620@fritz.box</li><br>
<a name="sip_ip"></a><li>sip_ip<br>external IP address of the FHEM server</li><br>
<a name="sip_port"></a><li>sip_port<br>Optionally portnumber used for sip client<br>If attribute is not set a random port number between 44000 and 45000 will be used</li><br>
<a name="sip_registrar"></a><li>sip_registrar<br>Hostname or IP address of the SIP server you are connecting to, defaults to fritz.box</li><br>
<a name="sip_ringtime"></a><li>sip_ringtime<br>Ringtime for incomming calls (dtmf &wfp)</li><br>
<a name="sip_user"></a><li>sip_user<br>User name of the SIP client, defaults to 620</li><br>
<a name="sip_waittime"></a><li>sip_waittime<br>Maximum waiting time in state listen_for_wfp it will wait to pick up the call</li><br>
<a name="sip_dtmf_size"></a><li>sip_dtmf_size 1 to 4 , default is 2</li><br>
<a name="sip_dtmf_loop"></a><li>sip_dtmf_loop once or loop , default once</li><br>
<a name="sip_force_interval"></a><li>sip_force_interval default 300</li><br>
<a name="sip_force_max"></a><li>sip_force_max default 99</li><br>
<a name="phonebook"></a><li>phonebook default none , filename of own phonebook. each row : number,name</li><br>
<a name="history_size"></a><li>history_size default 0 , max rows in history list</li><br>
<a name="history_file"></a><li>history_file default none, filename of history list</li><br>
</ul>
<br>
</ul>
=end html
=begin html_DE
<a name="SIP"></a>
<h3>SIP</h3>
<ul>
Definiert ein SIP-Client Device.<br>
Wiki : <a href="https://wiki.fhem.de/wiki/SIP-Client">https://wiki.fhem.de/wiki/SIP-Client</a>
<br>
Forum : <a href="https://forum.fhem.de/index.php/topic,67443.0.html">https://forum.fhem.de/index.php/topic,67443.0.html</a>
<br><br>
<a name="SIPdefine"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; SIP</code>
<br><br>
Beispiel:
<ul>
<code>define MySipClient SIP</code><br>
</ul>
</ul>
<br>
<a name="SIPset"></a>
<b>Set</b>
<ul>
<li>
<code>set &lt;name&gt; &lt;SIP Passwort&gt;</code><br>
Speichert das Passwort des SIP Users. Ohne gespeichertes Passwort sind die set call und set listen Funktionen gesperrt !<br>
WICHTIG : wird das SIP Device umbenannt muss dieser Befehl unbedingt wiederholt werden !
</li>
<li>
<code>set &lt;name&gt; reset</code><br>
Stoppt laufende listen-Prozess und initalisiert das Device.<br>
</li>
<li>
<code>set &lt;name&gt; call &lt;nummer&gt; [&lt;maxtime&gt;] [&lt;nachricht&gt;]</code><br>
Startet einen Anruf an die angegebene Nummer.<br>
Optional kann die maximale Zeit angegeben werden. Default ist 30.<br>
Optional kann eine Nachricht in Form eines Audiofiles angegeben werden.<br>
Das File ist mit dem vollen Pfad oder dem relativen ab dem Verzeichnis mit fhem.pl anzugeben.
</li>
<li>
<code>set &lt;name&gt; listen</code><br>
Attribut sip_listen = dtmf :
Der SIP-Client wird in einen Status versetzt in dem er Anrufe annimmt. Der Ton wird als Echo zur&uuml;ckgespielt. Über die Eingabe von # gefolgt von 2 unterschiedlichen Zahlen und anschlie&szlig;endem Auflegen kann eine Zahl in das Reading <b>dtmf</b> &uuml;bergeben werden.<br>
Attribut sip_listen = wfp :
Der SIP-Client wird in einen Status versetzt in dem er auf Anrufe wartet. Erfolgt ein Anruf an den Client, wechselt der Status zu <b>ringing</b>.<br>
Nun kann das Gespr&auml;ch via set-Command <b>fetch</b> angenommen werden. Das als sip_audiofile angegebene File wird abgespielt.<br>
Anschlie&szlig;end wechselt der Status wieder zu <b>listenwfp</b>.<br>
</li>
</ul>
<br>
<a name="SIPattr"></a>
<b>Attributes</b>
<ul>
<a name="sip_user"></a><li>sip_user<br>User Name des SIP-Clients. Default ist 620 (Fritzbox erstes SIP Telefon)</li></br>
<a name="sip_registrar"></a><li>sip_registrar<br>Hostname oder IP-Addresse des SIP-Servers mit dem sich das Modul verbinden soll. (Default fritz.box)</li><br>
<a name="sip_from"></a><li>sip_from<br>SIP-Client-Info. Syntax : sip:sip_user@sip_registrar Default ist sip:620@fritz.box</li></br>
<a name="sip_from"></a><li>sip_ip<br>Die IP-Addresse von FHEM im Heimnetz. Solange das Attribut nicht gesetzt ist versucht das Modul diese beim Start zu ermitteln.</li><br>
<a name="sip_port"></a><li>sip_port<br>Optinale Portnummer die vom Modul benutzt wird.<br>
Wenn dem Attribut kein Wert zugewiesen wurde verwendet das Modul eine zuf&auml;llige Portnummer zwichen 44000 und 45000</li><br>
<li>Audiofiles
Audiofiles k&ouml;nnen einfach mit dem externen Programm sox erzeugt werden :<br>
sox &lt;file&gt;.wav -t raw -r 8000 -c 1 -e a-law &lt;file&gt;.al<br>
Unterst&uuml;tzt werden nur die beiden RAW Audio Formate a-law und u-law !<br>
Statt eines echten Audiofiles kann auch eine Text2Speech Nachricht eingetragen werden.<br>
Bsp : attr mySIP sip_audiofile_call !Hier ist dein FHEM Server </li><br>
<a name="sip_audiofile_wfp"></a><li>sip_audiofile_wfp<br>Audiofile das nach dem Command <b>fetch</b> abgespielt wird</li><br>
<a name="sip_audiofile_call"></a><li>sip_audiofile_call</br>Audiofile das dem Angerufenen bei set call vorgespielt wird</li><br>
<a name="sip_audiofile_dtmf"></a><li>sip_audiofile_dtmf<br>Audiofile das dem Anrufer bei listen_for_dtmf abgespielt wird</li><br>
<a name="sip_audiofile_ok"></a><li>sip_audiofile_ok<br>Audiofile das bei erkannter DTMF Sequenz abgespielt wird</li><br>
<a name="sip_listen"></a><li>sip_listen (none , dtmf, wfp)</li><br>
<a name="sip_ringtime"></a><li>sip_ringtime<br>Klingelzeit für eingehende Anrufe bei listen_for_dtmf</li><br>
<a name="sip_dtmf_size"></a><li>sip_dtmf_size</a><br>1 bis 4 , default 2 Legt die L&auml;ge des erwartenden DTMF Events fest</li><br>
<a name="sip_dtmf_loop"></a><li>sip_dtmf_loop<br> once oder loop , default once</li><br>
<a name="sip_waittime"></a><li>sip_waittime<br>Maximale Wartezeit im Status listen_for_wfp bis das Gespr&auml;ch automatisch angenommen wird</li><br>
<a name="T2S_Device"></a><li>T2S_Device<br>Name des Text2Speech Devices (Wird nur ben&ouml;tigt wenn Sprachnachrichten statt Audiofiles verwendet werden)</li><br>
<a name="T2S_Timeout"></a><li>T2S_Timeout<br>Wartezeit in Sekunden wie lange maximal auf Text2Speech gewartet wird</li><br>
<a name="audio_converter"><li>audo_converter<br>sox oder ffmpeg, default sox<br>
Ist f&uuml;r Text2Speech unbedingt erforderlich um die mp3 Dateien in Raw Audio umzuwandeln.<br>
Installation z.B. mit sudo apt-get install sox und die mp3 Unterst&uuml;tzung mit sudo apt-get install libsox-fmt-mp3</li><br>
<a name="sip_force_interval"></a><li>sip_force_interval default 300 </li><br>
<a name="sip_force_max"></a><li>sip_force_max default 99</li><br>
<a name="phonebook"></a><li>phonebook default none<br>Dateiname des eigenen Telefonbuchs. Inhalt: zeilenweise Nr,Name</li><br>
<a name="history_size"></a><li>history_size default 0<br>max Anzahl von Zeilen in der Ruf/Anrufer Liste</li><br>
<a name="history_file"></a><li>history_file default none<br>Dateiname der Ruf/Anrufer Liste</li><br>
</ul>
<br>
</ul>
=end html_DE
=cut