mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-01 20:20:10 +00:00
DoorPi.pm: Aktualisierung auf Version 1.1
git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@11847 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
0b926d5eda
commit
b49e381ab9
@ -41,7 +41,7 @@ use vars qw{%attr %defs};
|
||||
sub Log($$);
|
||||
|
||||
#-- globals on start
|
||||
my $version = "1.0beta9";
|
||||
my $version = "1.1";
|
||||
|
||||
#-- these we may get on request
|
||||
my %gets = (
|
||||
@ -51,7 +51,7 @@ my %gets = (
|
||||
);
|
||||
|
||||
#-- capabilities of doorpi instance for light and target
|
||||
my ($lon,$loff,$lonft,$don,$doff,$gtt,$son,$soff,$snon) = (0,0,0,0,0,0,0,0,0);
|
||||
my ($lon,$loff,$don,$doff,$gtt,$son,$soff,$snon) = (0,0,0,0,0,0,0,0,0);
|
||||
|
||||
########################################################################################
|
||||
#
|
||||
@ -222,7 +222,12 @@ sub DoorPi_Get ($@) {
|
||||
|
||||
sub DoorPi_Set ($@) {
|
||||
my ($hash, @a) = @_;
|
||||
#-- only hash as parameter when acting as timer callback
|
||||
if( !@a ){
|
||||
@a=($hash->{NAME},"light","off");
|
||||
}
|
||||
my $name = shift @a;
|
||||
|
||||
my ($newkeys,$key,$value,$v);
|
||||
|
||||
#-- commands
|
||||
@ -268,22 +273,37 @@ sub DoorPi_Set ($@) {
|
||||
return "[DoorPi_Set] With unknown argument $key, choose one of " . join(" ", @{$hash->{HELPER}->{CMDS}})
|
||||
if ( !grep( /$key/, @{$hash->{HELPER}->{CMDS}} ) && ($key ne "call") && ($key ne "door") );
|
||||
|
||||
#-- hidden command to be used by DoorPi for communicating
|
||||
#-- hidden command "call" to be used by DoorPi for communicating with this module
|
||||
if( $key eq "call" ){
|
||||
if( $value eq "start" ){
|
||||
if( $value =~ "start.*" ){
|
||||
readingsSingleUpdate($hash,"call","started",1);
|
||||
my ($sec, $min, $hour, $day,$month,$year,$wday) = (localtime())[0,1,2,3,4,5,6];
|
||||
$year += 1900;
|
||||
my $monthn = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$month];
|
||||
$wday = ("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")[$wday];
|
||||
my $timestamp = sprintf("%s, %2d %s %d %02d:%02d:%02d", $wday,$day,$monthn,$year,$hour, $min, $sec);
|
||||
unshift(@{ $hash->{DATA}}, ["",$timestamp,AttrVal($name, "target$value","unknown"),"active","--","xx","yy"] );
|
||||
#-- update web interface immediately
|
||||
DoorPi_inform($hash);
|
||||
|
||||
}elsif( $value eq "end" ){
|
||||
readingsSingleUpdate($hash,"call","ended",1);
|
||||
readingsSingleUpdate($hash,"call","ended",1);
|
||||
DoorPi_GetHistory($hash);
|
||||
#-- update web interface in 5 seconds
|
||||
InternalTimer(gettimeofday()+5, "DoorPi_inform", $hash,0);
|
||||
|
||||
}elsif( $value eq "rejected" ){
|
||||
readingsSingleUpdate($hash,"call","rejected",1);
|
||||
DoorPi_GetHistory($hash);
|
||||
|
||||
}elsif( $value eq "dismissed" ){
|
||||
readingsSingleUpdate($hash,"call","dismissed",1);
|
||||
DoorPi_GetHistory($hash);
|
||||
|
||||
}elsif( $value eq "startup" ){
|
||||
DoorPi_GetConfig($hash);
|
||||
DoorPi_GetHistory($hash);
|
||||
|
||||
}elsif( $value eq "snapshot" ){
|
||||
# TODO
|
||||
}else{
|
||||
@ -364,6 +384,7 @@ sub DoorPi_Set ($@) {
|
||||
Log 1,"[DoorPi_Set] received softlock command from DoorPi, but uncertain lockstate";
|
||||
return;
|
||||
}
|
||||
|
||||
#-- from FHEM: unlocking the door
|
||||
}elsif( $value eq "unlocked" ){
|
||||
#-- careful here -
|
||||
@ -378,6 +399,7 @@ sub DoorPi_Set ($@) {
|
||||
readingsSingleUpdate($hash,"lockstate","unlocked",1);
|
||||
readingsSingleUpdate($hash,$door,"unlocked",1);
|
||||
$v=DoorPi_Cmd($hash,"doorunlocked");
|
||||
|
||||
#-- from FHEM: locking the door
|
||||
}elsif( $value eq "locked" ){
|
||||
#-- careful here -
|
||||
@ -410,7 +432,6 @@ sub DoorPi_Set ($@) {
|
||||
#my $light = AttrVal($name, "lightbutton", "light");
|
||||
if( $value eq "on" ){
|
||||
$v=DoorPi_Cmd($hash,"lighton");
|
||||
readingsSingleUpdate($hash,$light,"on",1);
|
||||
if(AttrVal($name, "lightoncmd",undef)){
|
||||
fhem(AttrVal($name, "lightoncmd",undef));
|
||||
}
|
||||
@ -422,12 +443,13 @@ sub DoorPi_Set ($@) {
|
||||
}
|
||||
readingsSingleUpdate($hash,$light,"off",1);
|
||||
}elsif( $value eq "on-for-timer" ){
|
||||
$v=DoorPi_Cmd($hash,"lightonfortimer");
|
||||
if(AttrVal($name, "lighttimercmd",undef)){
|
||||
fhem(AttrVal($name, "lighttimercmd",undef));
|
||||
$v=DoorPi_Cmd($hash,"lighton");
|
||||
if(AttrVal($name, "lightoncmd",undef)){
|
||||
fhem(AttrVal($name, "lightoncmd",undef));
|
||||
}
|
||||
readingsSingleUpdate($hash,$light,"on-for-timer",1);
|
||||
#-- TODO: reset after time
|
||||
readingsSingleUpdate($hash,$light,"on",1);
|
||||
#-- Intiate turning off light
|
||||
InternalTimer(gettimeofday() + 60, "DoorPi_Set", $hash,1);
|
||||
}
|
||||
#-- dashboard lighting
|
||||
}elsif( $key eq "$dashlight" ){
|
||||
@ -442,9 +464,12 @@ sub DoorPi_Set ($@) {
|
||||
}elsif( $key =~ /button(\d\d?)/){
|
||||
$v=DoorPi_Cmd($hash,$key);
|
||||
}elsif( $key eq "purge"){
|
||||
$v=DoorPi_Cmd($hash,"purge");
|
||||
}elsif( $key eq "clear"){
|
||||
$v=DoorPi_Cmd($hash,"clear");
|
||||
#-- command purge to Doorpi
|
||||
DoorPi_Cmd($hash,"purge");
|
||||
#-- clearing of DB
|
||||
InternalTimer(gettimeofday()+5, "DoorPi_PurgeDB", $hash,0);
|
||||
#-- get new history
|
||||
InternalTimer(gettimeofday()+10, "DoorPi_GetHistory",$hash,0);
|
||||
}
|
||||
|
||||
if(defined($v)) {
|
||||
@ -551,8 +576,6 @@ sub DoorPi_GetConfig {
|
||||
$doff = 1;
|
||||
|
||||
#-- check for scene lighting buttons
|
||||
}elsif($key =~ /$light(on)fortimer/){
|
||||
$lonft = 1;
|
||||
}elsif($key =~ /$light(on)/){
|
||||
push(@{ $hash->{HELPER}->{CMDS}},"$light");
|
||||
$lon = 1;
|
||||
@ -585,8 +608,6 @@ sub DoorPi_GetConfig {
|
||||
if( $lon==0 );
|
||||
Log 1,"[DoorPi_GetConfig] Warning: No DoorPi InputPin named \"".$light."off\" defined"
|
||||
if( $loff==0 );
|
||||
Log 1,"[DoorPi_GetConfig] Warning: No DoorPi InputPin named \"".$light."onfortimer\" defined"
|
||||
if( $lonft==0 );
|
||||
Log 1,"[DoorPi_GetConfig] Warning: No DoorPi InputPin named \"".$dashlight."on\" defined"
|
||||
if( $don==0 );
|
||||
Log 1,"[DoorPi_GetConfig] Warning: No DoorPi InputPin named \"".$dashlight."off\" defined"
|
||||
@ -603,6 +624,64 @@ sub DoorPi_GetConfig {
|
||||
readingsSingleUpdate($hash,"config","ok",1);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#######################################################################################
|
||||
#
|
||||
# DoorPi_LastSnapshot - acts as callable program DoorPi_GetLastSnapshot($hash)
|
||||
# and as callback program DoorPi_GetLastSnapshot($hash,$err,$status)
|
||||
#
|
||||
# Parameter hash, err, status
|
||||
#
|
||||
#######################################################################################
|
||||
|
||||
sub DoorPi_GetLastSnapshot {
|
||||
my ($hash,$err,$status) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $url;
|
||||
|
||||
#-- get configuration from doorpi
|
||||
if ( !$hash ){
|
||||
Log 1,"[DoorPi_GetLastSnapshot] called without hash";
|
||||
return undef;
|
||||
}elsif ( $hash && !$err && !$status ){
|
||||
$url = "http://".$hash->{TCPIP}."/status?module=config";
|
||||
#Log 1,"[DoorPi_GetLastSnapshot] called with only hash => Issue a non-blocking call to $url";
|
||||
HttpUtils_NonblockingGet({
|
||||
url => $url,
|
||||
callback => sub($$$){ DoorPi_GetLastSnapshot($hash,$_[1],$_[2]) }
|
||||
});
|
||||
return undef;
|
||||
}elsif ( $hash && $err ){
|
||||
Log 1,"[DoorPi_GetLastSnapshot] has error $err";
|
||||
readingsSingleUpdate($hash,"snapshot",$err,0);
|
||||
readingsSingleUpdate($hash,"state","Error",1);
|
||||
return;
|
||||
}
|
||||
Log 1,"[DoorPi_GetLastSnapshot] has obtained data";
|
||||
|
||||
#-- test if this is valid JSON
|
||||
if( !is_valid_json($status) ){
|
||||
Log 1,"[DoorPi_GetLastSnapshot] but data is invalid";
|
||||
readingsSingleUpdate($hash,"snapshot","invalid data",0);
|
||||
readingsSingleUpdate($hash,"state","Error",1);
|
||||
return;
|
||||
}
|
||||
|
||||
my $json = JSON->new->utf8;
|
||||
my $jhash0 = $json->decode( $status );
|
||||
|
||||
#-- decode config
|
||||
my $DoorPi = $jhash0->{"config"}->{"DoorPi"};
|
||||
my $lastsnap = $jhash0->{"config"}->{"DoorPi"}->{"last_snapshot"};
|
||||
|
||||
#push(@{ $hash->{DATA}}, ["",$state,$timestamp,$number,"started","--",$snapshot,$record] );
|
||||
|
||||
Log 1,"[DoorPi_GetLastSnapshot] returns $lastsnap";
|
||||
|
||||
#-- put into READINGS
|
||||
readingsSingleUpdate($hash,"snapshot",$lastsnap,1);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#######################################################################################
|
||||
#
|
||||
@ -629,7 +708,7 @@ sub DoorPi_GetHistory {
|
||||
Log 1,"[DoorPi_GetHistory] called without hash";
|
||||
return undef;
|
||||
}elsif ( $hash && !$err1 && !$status1 && !$err2 && !$status2 ){
|
||||
$url = "http://".$hash->{TCPIP}."/status?module=history_event";
|
||||
$url = "http://".$hash->{TCPIP}."/status?module=history_event&name=OnCallStateChange&value=1000";
|
||||
#Log 1,"[DoorPi_GetHistory] called with only hash => Issue a non-blocking call to $url";
|
||||
HttpUtils_NonblockingGet({
|
||||
url => $url,
|
||||
@ -638,7 +717,7 @@ sub DoorPi_GetHistory {
|
||||
return undef;
|
||||
}elsif ( $hash && $err1 && !$status1 && !$err2 && !$status2 ){
|
||||
Log 1,"[DoorPi_GetHistory] has error $err1";
|
||||
readingsSingleUpdate($hash,"history",$err1,0);
|
||||
readingsSingleUpdate($hash,"call_history",$err1,0);
|
||||
readingsSingleUpdate($hash,"state","Error",1);
|
||||
return undef;
|
||||
}elsif ( $hash && !$err1 && $status1 && !$err2 && !$status2 ){
|
||||
@ -651,7 +730,7 @@ sub DoorPi_GetHistory {
|
||||
return undef;
|
||||
}elsif ( $hash && !$err1 && $status1 && $err2){
|
||||
Log 1,"[DoorPi_GetHistory] has error2 $err2";
|
||||
readingsSingleUpdate($hash,"history",$err2,0);
|
||||
readingsSingleUpdate($hash,"call_history",$err2,0);
|
||||
readingsSingleUpdate($hash,"state","Error",1);
|
||||
return undef;
|
||||
}
|
||||
@ -660,13 +739,13 @@ sub DoorPi_GetHistory {
|
||||
#-- test if this is valid JSON
|
||||
if( !is_valid_json($status1) ){
|
||||
Log 1,"[DoorPi_GetHistory] but data from first call is invalid";
|
||||
readingsSingleUpdate($hash,"history","invalid data 1st call",0);
|
||||
readingsSingleUpdate($hash,"call_history","invalid data 1st call",0);
|
||||
readingsSingleUpdate($hash,"state","Error",1);
|
||||
return;
|
||||
}
|
||||
if( !is_valid_json($status2) ){
|
||||
Log 1,"[DoorPi_GetHistory] but data from second call is invalid";
|
||||
readingsSingleUpdate($hash,"history","invalid data 2nd call",0);
|
||||
readingsSingleUpdate($hash,"call_history","invalid data 2nd call",0);
|
||||
readingsSingleUpdate($hash,"state","Error",1);
|
||||
return;
|
||||
}
|
||||
@ -676,152 +755,171 @@ sub DoorPi_GetHistory {
|
||||
my $khash0 = $json->decode( $status2 );
|
||||
|
||||
#-- decode call history
|
||||
if(ref($jhash0->{"history_event"}) ne 'ARRAY'){
|
||||
Log 1,"[DoorPi_GetHistory] Error - has found an empty event history";
|
||||
return
|
||||
}
|
||||
if(ref($khash0->{"history_snapshot"}) ne 'ARRAY'){
|
||||
Log 1,"[DoorPi_GetHistory] Warning - has found an empty snapshot history";
|
||||
}
|
||||
my @history_event = ($jhash0)?@{$jhash0->{"history_event"}}:();
|
||||
my @history_snapshot = ($khash0)?@{$khash0->{"history_snapshot"}}:();
|
||||
my $call = "";
|
||||
|
||||
#-- clear list of calls
|
||||
@{$hash->{DATA}} = ();
|
||||
my ($event,$jhash1,$jhash2,$call_state,$call_state2,$callstart,$callend,$calletime,$calletarget,$callstime,$callstarget,$callsnap,$callrecord,$callstring);
|
||||
|
||||
#-- going backward through the calls
|
||||
my ($callend,$calletime,$calletarget,$callstime,$callstarget,$callsnap,$callrecord,$callstring);
|
||||
for (my $i=0; $i<@history_event; $i++) {
|
||||
my $event = $history_event[$i];
|
||||
|
||||
if( $event->{"event_name"} eq "OnCallStateChange" ){
|
||||
my $status1 = $event->{"additional_infos"};
|
||||
#-- workaround for bug in DoorPi
|
||||
$status1 =~ tr/'/"/;
|
||||
my $jhash1 = from_json( $status1 );
|
||||
my $call_state = $jhash1->{"call_state"};
|
||||
#-- end of call
|
||||
if( ($call eq "") && (($call_state == 18) || ($call_state == 13)) ){
|
||||
$call = "active";
|
||||
$callrecord = "";
|
||||
$callend = $jhash1->{"state"};
|
||||
$callend =~ s/Call //;
|
||||
if( $callend eq "released" ){
|
||||
#-- check previous 4 events
|
||||
for( my $j=1; $j<5; $j++ ){
|
||||
if( $history_event[$i+$j]->{"event_name"} eq "OnCallStateChange"){
|
||||
my $status2 = $history_event[$i+$j]->{"additional_infos"};
|
||||
#-- workaround for bug in DoorPi
|
||||
$status2 =~ tr/'/"/;
|
||||
my $jhash2 = from_json( $status2 );
|
||||
if( $jhash2->{"state"} eq "Busy Here" ){
|
||||
$callend = "busy";
|
||||
last;
|
||||
}elsif( $jhash2->{"state"} eq "Call ended" ){
|
||||
$callend = "ok";
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}elsif( $callend eq "terminated" ){
|
||||
if( $history_event[$i-1]->{"event_name"} eq "OnSipPhoneCallTimeoutNoResponse"){
|
||||
$callend = "no response";
|
||||
}
|
||||
}
|
||||
$calletime = $event->{"start_time"};
|
||||
$calletarget = $jhash1->{"remote_uri"};
|
||||
}elsif( ($call eq "active") && ($call_state == 2) ){
|
||||
$call = "";
|
||||
$callstime = $event->{"start_time"};
|
||||
$callstarget = $jhash1->{"remote_uri"};
|
||||
#--
|
||||
if( $calletarget ne $callstarget){
|
||||
Log 1,"[DoorPi_GetHistory] Found error in call history of target $calletarget";
|
||||
}else{
|
||||
#-- Format values
|
||||
my $state = "";
|
||||
my ($sec, $min, $hour, $day,$month,$year,$wday) = (localtime($callstime))[0,1,2,3,4,5,6];
|
||||
$year += 1900;
|
||||
my $monthn = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$month];
|
||||
$wday = ("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")[$wday];
|
||||
my $timestamp = sprintf("%s, %2d %s %d %02d:%02d:%02d", $wday,$day,$monthn,$year,$hour, $min, $sec);
|
||||
my $number = $callstarget;
|
||||
$number =~ s/sip://;
|
||||
$number =~ s/\@.*//;
|
||||
my $result = $callend;
|
||||
my $duration = int(($calletime - $callstime)*10+0.5)/10;
|
||||
|
||||
my $record = $callrecord;
|
||||
$record =~ s/^.*records\///;
|
||||
#-- workaround for buggy DoorPi
|
||||
$record = sprintf("%d-%02d-%02d_%02d-%02d-%02d.wav", $year,($month+1),$day,$hour, $min, $sec);
|
||||
# if( $callend eq "ok");
|
||||
|
||||
#-- this is the snapshot file if taken at the same time
|
||||
my $snapshot = sprintf("%d-%02d-%02d_%02d-%02d-%02d.jpg", $year,($month+1),$day,$hour, $min, $sec);
|
||||
#-- check if it is present in the list of snapshots
|
||||
my $found = 0;
|
||||
for( my $i=0; $i<@history_snapshot; $i++){
|
||||
if( index($history_snapshot[$i],$snapshot) > -1){
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
Log 1,"[DoorPi_GetHistory] found ".int(@history_event)." events";
|
||||
|
||||
#-- going backward through the calls
|
||||
my $i=0;
|
||||
if( int(@history_event) > 0 ){
|
||||
do{
|
||||
$event = $history_event[$i];
|
||||
$calletime = $event->{"start_time"};
|
||||
$status1 = $event->{"additional_infos"};
|
||||
#-- workaround for bug in DoorPi
|
||||
$status1 =~ tr/'/"/;
|
||||
$jhash1 = from_json( $status1 );
|
||||
$call_state = $jhash1->{"call_state"};
|
||||
$calletarget = $jhash1->{"remote_uri"};
|
||||
my @call_states = ();
|
||||
push(@call_states,$call_state);
|
||||
|
||||
#-- no active call processed and state of call = 18 - or ended = 13
|
||||
if( ($call eq "") && (($call_state == 18)||($call_state == 13)) ){
|
||||
$call = "active";
|
||||
my $j = 1;
|
||||
#-- check previous max. 5 events
|
||||
do {
|
||||
$status2 = $history_event[$i+$j]->{"additional_infos"};
|
||||
if( $status2 ){
|
||||
#-- workaround for bug in DoorPi
|
||||
$status2 =~ tr/'/"/;
|
||||
$jhash2 = from_json( $status2 );
|
||||
$call_state2 = $jhash2->{"call_state"};
|
||||
if( $call_state2 < 18 ){
|
||||
push( @call_states,$call_state2);
|
||||
$callstime = $history_event[$i+$j]->{"start_time"};
|
||||
$callstarget = $jhash2->{"remote_uri"};
|
||||
}
|
||||
#-- if not, look for a file made a second later
|
||||
if( $found == 0 ){
|
||||
($sec, $min, $hour, $day,$month,$year,$wday) = (localtime($callstime+1))[0,1,2,3,4,5,6];
|
||||
$year += 1900;
|
||||
$monthn = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$month];
|
||||
$wday = ("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")[$wday];
|
||||
|
||||
#-- this is the snapshot file if taken at the same time
|
||||
$snapshot = sprintf("%d-%02d-%02d_%02d-%02d-%02d.jpg", $year,($month+1),$day,$hour, $min, $sec);
|
||||
#-- check if it is present in the list of snapshots
|
||||
$found = 0;
|
||||
for( my $i=0; $i<@history_snapshot; $i++){
|
||||
if( index($history_snapshot[$i],$snapshot) > -1){
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if( $found == 0 ){
|
||||
Log 1,"[DoorPi_GetHistory] No snapshot found with $snapshot";
|
||||
}
|
||||
}
|
||||
|
||||
#-- store this
|
||||
push(@{ $hash->{DATA}}, [$state,$timestamp,$number,$result,$duration,$snapshot,$record] );
|
||||
}
|
||||
}
|
||||
$j++;
|
||||
} until( ($j > 5) || ($call_state2 == 18) || ($i+$j >= int(@history_event)) );
|
||||
|
||||
my $call_pattern = join("-",@call_states);
|
||||
#Log 1,"[DoorPi_GetHistory] Pattern for call is $call_pattern, proceeding with event no. ".($i+$j);
|
||||
|
||||
if( $call_pattern =~ /1(3|8)\-.*\-2/ ){
|
||||
$callend = "ok(2)";
|
||||
}elsif( $call_pattern =~ /1(3|8)\-.*\-3/ ){
|
||||
$callend = "ok(3)";
|
||||
}elsif( $call_pattern =~ /1(3|8)\-.*\-5/ ){
|
||||
$callend = "nok(5)";
|
||||
}else{
|
||||
$callend = "unknown";
|
||||
}
|
||||
|
||||
if( $calletarget ne $callstarget){
|
||||
Log 1,"[DoorPi_GetHistory] Found error in call history of target $calletarget";
|
||||
}
|
||||
|
||||
#-- Format values
|
||||
my $state = "";
|
||||
my ($sec, $min, $hour, $day,$month,$year,$wday) = (localtime($callstime))[0,1,2,3,4,5,6];
|
||||
$year += 1900;
|
||||
my $monthn = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$month];
|
||||
$wday = ("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")[$wday];
|
||||
my $timestamp = sprintf("%s, %2d %s %d %02d:%02d:%02d", $wday,$day,$monthn,$year,$hour, $min, $sec);
|
||||
my $number = $callstarget;
|
||||
$number =~ s/sip://;
|
||||
$number =~ s/\@.*//;
|
||||
my $result = $callend;
|
||||
my $duration = int(($calletime - $callstime)*10+0.5)/10;
|
||||
|
||||
#-- workaround for buggy DoorPi
|
||||
my $record = sprintf("%d-%02d-%02d_%02d-%02d-%02d.wav", $year,($month+1),$day,$hour, $min, $sec);
|
||||
|
||||
#-- this is the snapshot file if taken at the same time
|
||||
my $snapshot = sprintf("%d-%02d-%02d_%02d-%02d-%02d.jpg", $year,($month+1),$day,$hour, $min, $sec);
|
||||
|
||||
#-- maybe we have to look at a second later ?
|
||||
($sec, $min, $hour, $day,$month,$year,$wday) = (localtime($callstime+1))[0,1,2,3,4,5,6];
|
||||
$year += 1900;
|
||||
$monthn = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$month];
|
||||
$wday = ("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")[$wday];
|
||||
|
||||
#-- this is the filename without extension if taken a second later
|
||||
my $later = sprintf("%d-%02d-%02d_%02d-%02d-%02d", $year,($month+1),$day,$hour, $min, $sec);
|
||||
|
||||
my $found = 0;
|
||||
for( my $i=0; $i<@history_snapshot; $i++){
|
||||
if( index($history_snapshot[$i],$snapshot) > -1){
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
#-- if not, look for a file made a second later
|
||||
if( $found == 0 ){
|
||||
#-- this is the snapshot file if taken a second later
|
||||
$snapshot = sprintf("%s.jpg", $later);
|
||||
#-- check if it is present in the list of snapshots
|
||||
for( my $i=0; $i<@history_snapshot; $i++){
|
||||
if( index($history_snapshot[$i],$snapshot) > -1){
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if( $found == 0 ){
|
||||
Log 1,"[DoorPi_GetHistory] No snapshot found with $snapshot";
|
||||
}
|
||||
}
|
||||
|
||||
$found = 0;
|
||||
for( my $i=0; $i<@history_snapshot; $i++){
|
||||
if( index($history_snapshot[$i],$record) > -1){
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
#-- if not, look for a file made a second later
|
||||
if( $found == 0 ){
|
||||
#-- this is the record file if taken a second later
|
||||
$record = sprintf("%s.wav", $later);
|
||||
#-- check if it is present in the list of snapshots
|
||||
for( my $i=0; $i<@history_snapshot; $i++){
|
||||
if( index($history_snapshot[$i],$record) > -1){
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if( $found == 0 ){
|
||||
Log 1,"[DoorPi_GetHistory] No record found with $record";
|
||||
}
|
||||
}
|
||||
|
||||
#Log 1,"$snapshot $record";
|
||||
|
||||
#-- store this
|
||||
push(@{ $hash->{DATA}}, [$state,$timestamp,$number,$result,$duration,$snapshot,$record] );
|
||||
|
||||
$i += $j-1;
|
||||
$i--
|
||||
if( $call_state2 == 18 );
|
||||
$call = "";
|
||||
}
|
||||
#-- other events during call active
|
||||
if( ($call eq "active") && ($event->{"event_name"} eq "OnRecorderStarted") ){
|
||||
my $status3 = $event->{"additional_infos"};
|
||||
$status3 =~ tr/'/"/;
|
||||
my $jhash1 = from_json( $status3 );
|
||||
$callrecord = $jhash1->{"last_record_filename"};
|
||||
}
|
||||
}
|
||||
$i++;
|
||||
} until ($i >= int(@history_event));
|
||||
|
||||
#-- going backward through the events to find last action for dashlight and light
|
||||
my $dashlightstate = "off";
|
||||
my $dashlight = AttrVal($name, "dashlightbutton", "dashlight");
|
||||
for (my $i=0; $i<@history_event; $i++) {
|
||||
if( $history_event[$i]->{"event_name"} =~ /OnKeyPressed_webservice\.dashlight(.*)/ ){
|
||||
$dashlightstate=$1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
my $lightstate = "off";
|
||||
my $light = AttrVal($name, "lightbutton", "light");
|
||||
for (my $i=0; $i<@history_event; $i++) {
|
||||
if( $history_event[$i]->{"event_name"} =~ /OnKeyPressed_webservice\.light(.*)/ ){
|
||||
$lightstate=$1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#--put into READINGS
|
||||
readingsBeginUpdate($hash);
|
||||
readingsBulkUpdate($hash,"call_listed",int(@{ $hash->{DATA}}));
|
||||
readingsBulkUpdate($hash,"call_history","ok");
|
||||
readingsBulkUpdate($hash,$dashlight,$dashlightstate);
|
||||
readingsBulkUpdate($hash,$light,$lightstate);
|
||||
#readingsBulkUpdate($hash,$dashlight,$dashlightstate);
|
||||
#readingsBulkUpdate($hash,$light,$lightstate);
|
||||
readingsEndUpdate($hash,1);
|
||||
return undef;
|
||||
}
|
||||
@ -881,6 +979,54 @@ sub DoorPi_GetHistory {
|
||||
return undef;
|
||||
}
|
||||
|
||||
######################################################################################
|
||||
#
|
||||
# DoorPi_PurgeDB - acts as callable program DoorPi_PurgeDB($hash)
|
||||
# and as callback program DoorPi_PurgeDB($hash,$err,$status)
|
||||
#
|
||||
# Parameter hash, err, status
|
||||
#
|
||||
#######################################################################################
|
||||
|
||||
sub DoorPi_PurgeDB {
|
||||
my ($hash,$err,$data) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $url;
|
||||
|
||||
#-- purge doorpi database
|
||||
if ( !$hash ){
|
||||
Log 1,"[DoorPi_PurgeDB] called without hash";
|
||||
return undef;
|
||||
}elsif ( $hash && !$err ){
|
||||
$url = "http://".$hash->{TCPIP}."/status?module=history_event&name=purge&value=1.0";
|
||||
HttpUtils_NonblockingGet({
|
||||
url => $url,
|
||||
callback => sub($$$){ DoorPi_PurgeDB($hash,$_[1],$_[2]) }
|
||||
});
|
||||
return undef;
|
||||
}elsif ( $hash && $err ){
|
||||
Log 1,"[DoorPi_PurgeDB] has error $err";
|
||||
readingsSingleUpdate($hash,"config",$err,0);
|
||||
readingsSingleUpdate($hash,"state","Error",1);
|
||||
return;
|
||||
}
|
||||
#-- test if this is valid JSON
|
||||
if( !is_valid_json($data) ){
|
||||
Log 1,"[DoorPi_PurgeDB] invalid data";
|
||||
readingsSingleUpdate($hash,"state","Error",1);
|
||||
return;
|
||||
}
|
||||
|
||||
my $json = JSON->new->utf8;
|
||||
my $jhash = $json->decode( $data );
|
||||
my $msg = $jhash->{'message'};
|
||||
my $suc = $jhash->{'success'};
|
||||
if( $suc ){
|
||||
return $msg;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
#######################################################################################
|
||||
#
|
||||
# DoorPi_makeShort
|
||||
@ -921,7 +1067,7 @@ sub DoorPi_makeShort($$$$){
|
||||
$ret .= "</td><td>".int(@{ $hash->{DATA}})." calls";
|
||||
}
|
||||
|
||||
$ret .= "</td><td><a href=\"/fhem?cmd.A.Haus.T=set A.Haus.T open\">open</a>";
|
||||
$ret .= "</td><td><a href=\"/fhem?cmd.$devname=set $devname door open\">open</a>";
|
||||
|
||||
setlocale(LC_ALL, $old_locale);
|
||||
|
||||
@ -939,15 +1085,58 @@ sub DoorPi_makeShort($$$$){
|
||||
sub DoorPi_makeTable($$$$){
|
||||
my ($FW_wname, $devname, $room, $extPage) = @_;
|
||||
my $hash = $defs{$devname};
|
||||
|
||||
return DoorPi_list($hash)
|
||||
}
|
||||
|
||||
#######################################################################################
|
||||
#
|
||||
# DoorPi_inform
|
||||
#
|
||||
# Inform FHEMWEB
|
||||
#
|
||||
#######################################################################################
|
||||
|
||||
sub DoorPi_inform($){
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
Log3 $name, 5, "[Doorpi_inform]- inform all FHEMWEB clients";
|
||||
my $count = 0;
|
||||
|
||||
foreach my $line (DoorPi_list($hash,1)){
|
||||
#Log 1,"[Doorpi_Set] - informing $name with $line";
|
||||
FW_directNotify($name, $line, 1);
|
||||
$count++;
|
||||
}
|
||||
|
||||
# send the current row count to ensure all other rows are deleted via JS
|
||||
# FW_directNotify($name,"max-lines,$count", 1);
|
||||
}
|
||||
|
||||
#######################################################################################
|
||||
#
|
||||
# DoorPi_list
|
||||
#
|
||||
# Do the work for makeTable
|
||||
#
|
||||
#######################################################################################
|
||||
|
||||
sub DoorPi_list($;$){
|
||||
my ($hash, $to_json) = @_;
|
||||
|
||||
return undef if( !$hash );
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $wwwpath = $hash->{HELPER}->{wwwpath};
|
||||
my $alias = AttrVal($hash->{NAME}, "alias", $hash->{NAME});
|
||||
my ($state,$timestamp,$number,$result,$duration,$snapshot,$record,$nrecord);
|
||||
|
||||
my $create_readings = AttrVal($hash->{NAME}, "create-readings","0");
|
||||
|
||||
my $td_style = 'style="padding-left:6px;padding-right:6px;"';
|
||||
#my @json_output = ();
|
||||
#my $line;
|
||||
my @json_output = ();
|
||||
my $line;
|
||||
|
||||
my $old_locale = setlocale(LC_ALL);
|
||||
|
||||
@ -976,9 +1165,11 @@ sub DoorPi_makeTable($$$$){
|
||||
|
||||
$ret .= "<tr><td></td><td>";
|
||||
#-- div tag to support inform updates
|
||||
$ret .= '<div class="fhemWidget" informId="'.$name.'" cmd="" arg="fbcalllist" dev="'.$name.'">';
|
||||
if( exists($hash->{DATA}) && (int(@{$hash->{DATA}}) > 0) ){
|
||||
$ret .= '<div class="fhemWidget" informId="'.$name.'" cmd="" arg="doorpicalllist" dev="'.$name.'">';
|
||||
if( exists($hash->{DATA}) ){
|
||||
$ret .= '<table class="block doorpicalllist">';
|
||||
|
||||
my @order=("state","timestamp","number","result","duration","record");
|
||||
|
||||
if(AttrVal($name, "language", "en") eq "de"){
|
||||
$state = "Wer";
|
||||
@ -1004,56 +1195,85 @@ sub DoorPi_makeTable($$$$){
|
||||
$ret .= '<td name="record" class="doorpicalllist" '.$td_style.'>'.$record.'</td>';
|
||||
$ret .= '</tr>';
|
||||
|
||||
my @list = @{$hash->{DATA}};
|
||||
for(my $index=0; $index<(@list); $index++){
|
||||
my @data = @{$list[$index]};
|
||||
$state = $data[0];
|
||||
$timestamp = $data[1];
|
||||
$number = $data[2];
|
||||
$result = $data[3];
|
||||
$duration = $data[4];
|
||||
$snapshot = $data[5];
|
||||
$record = $data[6];
|
||||
#-- Loop through all entries in the list
|
||||
if( int(@{$hash->{DATA}}) > 0){
|
||||
my @list = @{$hash->{DATA}};
|
||||
for(my $index=0; $index<(@list); $index++){
|
||||
my @data = @{$list[$index]};
|
||||
$state = $data[0];
|
||||
$timestamp = $data[1];
|
||||
$number = $data[2];
|
||||
$result = $data[3];
|
||||
$duration = $data[4];
|
||||
$snapshot = $data[5];
|
||||
$record = $data[6];
|
||||
|
||||
if(AttrVal($name, "language", "en") eq "de"){
|
||||
$result =~ s/busy/besetzt/;
|
||||
$result =~ s/no\sresponse/ohne Antw./;
|
||||
if(AttrVal($name, "language", "en") eq "de"){
|
||||
$result =~ s/busy/besetzt/;
|
||||
$result =~ s/no\sresponse/ohne Antw./;
|
||||
}
|
||||
|
||||
if( $record ne ""){
|
||||
my $rs = $record;
|
||||
$rs =~ s/.*$wwwpath\///;
|
||||
$record = '<a href="http://'.$hash->{TCPIP}.'/'.$record.'">';
|
||||
$record .= ($iconaudio) ? $iconaudio : $rs;
|
||||
$record .= '</a>';
|
||||
}
|
||||
|
||||
if( $snapshot ne ""){
|
||||
$state = '<a href="http://'.$hash->{TCPIP}.'/'.$snapshot.'">';
|
||||
$state .= ($iconpic) ? $iconpic : '<img src="http://'.$hash->{TCPIP}.'/'.$snapshot.'" width="40" height="30">';
|
||||
$state .= '</a>';
|
||||
}
|
||||
#-- assemble line
|
||||
my $line = {
|
||||
index => $index,
|
||||
line => $index+1,
|
||||
state => $state,
|
||||
timestamp => $timestamp,
|
||||
number => $number,
|
||||
result => $result,
|
||||
duration => $duration,
|
||||
snapshot => $snapshot,
|
||||
record => $record
|
||||
};
|
||||
|
||||
#-- assemble HTML output
|
||||
my @htmlret = ();
|
||||
push @htmlret, '<tr align="center" number="'.$index.'" class="doorpicalllist '.($index % 2 == 1 ? "odd" : "even").'">';
|
||||
foreach my $col (@order){
|
||||
push @htmlret, '<td name="'.$col.'" class="doorpicalllist" '.$td_style.'>'.$line->{$col}.'</td>';
|
||||
}
|
||||
|
||||
$ret .= join("",@htmlret)."</tr>";
|
||||
|
||||
#-- assemble JSON output
|
||||
my @jsonret = ();
|
||||
push @jsonret, '"line":"'.$line->{index}.'"';
|
||||
foreach my $col (@order){
|
||||
my $val = $line->{$col};
|
||||
$val =~ s,",\\",g;
|
||||
push @jsonret, '"'.$col.'":"'.$val.'"';
|
||||
}
|
||||
push @json_output, "{".join(",",@jsonret)."}";
|
||||
#--- end loop through the list
|
||||
}
|
||||
|
||||
if( $record ne ""){
|
||||
my $rs = $record;
|
||||
$rs =~ s/.*$wwwpath\///;
|
||||
$rs = ($iconaudio) ? $iconaudio : $rs;
|
||||
$record = '<a href="http://'.$hash->{TCPIP}.'/'.$record.'">'.$rs.'</a>';
|
||||
}
|
||||
|
||||
if( $snapshot ne ""){
|
||||
$state = '<a href="http://'.$hash->{TCPIP}.'/'.$snapshot.'">';
|
||||
$state .= ($iconpic) ? $iconpic : '<img src="http://'.$hash->{TCPIP}.'/'.$snapshot.'" width="40" height="30"></a>';
|
||||
}
|
||||
|
||||
$ret .= '<tr align="center" class="doorpicalllist '.($index % 2 == 1 ? "odd" : "even").'">';
|
||||
$ret .= '<td name="state" class="doorpicalllist" '.$td_style.'>'.$state.'</td>';
|
||||
$ret .= '<td name="timestamp" class="doorpicalllist" '.$td_style.'>'.$timestamp.'</td>';
|
||||
$ret .= '<td name="number" class="doorpicalllist" '.$td_style.'>'.$number.'</td>';
|
||||
$ret .= '<td name="result" class="doorpicalllist" '.$td_style.'>'.$result.'</td>';
|
||||
$ret .= '<td name="duration" class="doorpicalllist" '.$td_style.'>'.$duration.'</td>';
|
||||
$ret .= '<td name="record" class="doorpicalllist" '.$td_style.'>'.$record.'</td>';
|
||||
$ret .= '</tr>';
|
||||
}
|
||||
$ret .= "</table></div>";
|
||||
}else{
|
||||
if(AttrVal($name, "language", "en") eq "de"){
|
||||
$ret .= "</td><td>Rufliste leer";
|
||||
}else{
|
||||
$ret .= "</td><td>Calllist empty";
|
||||
}
|
||||
if(AttrVal($name, "language", "en") eq "de"){
|
||||
$ret .= "</td><td>Rufliste leer";
|
||||
}else{
|
||||
$ret .= "</td><td>Calllist empty";
|
||||
}
|
||||
}
|
||||
$ret .= "</table></div>";
|
||||
}
|
||||
|
||||
$ret .= "</td></tr></table>";
|
||||
setlocale(LC_ALL, $old_locale);
|
||||
|
||||
return ($ret);
|
||||
return ($to_json ? @json_output : $ret);
|
||||
#return ($ret);
|
||||
}
|
||||
|
||||
|
||||
@ -1124,10 +1344,7 @@ sub DoorPi_makeTable($$$$){
|
||||
Activate one of the virtual buttons specified in DoorPi.</li>
|
||||
<li><a name="doorpi_purge">
|
||||
<code>set <DoorPi-Device> purge </code></a><br />
|
||||
Clean all recordings and snapshots which are older than the current process </li>
|
||||
<li><a name="doorpi_clear">
|
||||
<code>set <DoorPi-Device> clear </code></a><br />
|
||||
Clear all recordings and snapshots </li>
|
||||
Clear all recordings and snapshots which are older than a day</li>
|
||||
</ul>
|
||||
<br />
|
||||
<a name="DoorPi_Get"></a>
|
||||
@ -1175,9 +1392,6 @@ sub DoorPi_makeTable($$$$){
|
||||
<li><a name="doorpi_lightoffcmd"><code>attr <DoorPi-Device> lightoffcmd
|
||||
<string></code></a>
|
||||
<br />FHEM command additionally executed for "light off" action (no default)</li>
|
||||
<li><a name="doorpi_lighttimercmd"><code>attr <DoorPi-Device> lighttimercmd
|
||||
<string></code></a>
|
||||
<br />FHEM command additionally executed for "light off" action (no default)</li>
|
||||
<li><a name="doorpi_dashlightbutton"><code>attr <DoorPi-Device> dashlightbutton
|
||||
<string></code></a>
|
||||
<br />DoorPi name for dashlight action (default: dashlight)</li>
|
||||
@ -1222,14 +1436,14 @@ base_path_output = <some directory>
|
||||
dooropen = <doorpi action opening the door>
|
||||
doorlocked = <doorpi action if the door is locked by FHEM>
|
||||
doorunlocked = <doorpi action if the door is unlocked by FHEM>
|
||||
streamon = <doorpi action to switch on video stream>
|
||||
streamoff = <doorpi action to switch off video stream>
|
||||
lighton = <doorpi action to switch on scene light>
|
||||
lightonfortimer = <doorpi action to switch on scene light for some time>
|
||||
lightoff = <doorpi action to switch off scene light>
|
||||
dashlighton = <doorpi action to switch on dashlight>
|
||||
dashlightoff = <doorpi action to switch off dashlight>
|
||||
gettarget = <doorpi action to acquire call target number>
|
||||
purge = <doorpi action to purge old files>
|
||||
clear = <doorpi action>
|
||||
purge = <doorpi action to purge files and entries older than a day>
|
||||
... (optional buttons)
|
||||
button1 = <some doorpi action>
|
||||
... (further button definitions)
|
||||
|
@ -11,10 +11,10 @@ checkstream() {
|
||||
}
|
||||
|
||||
FHEMDP="A.Door.Pi"
|
||||
FHEMIP="xxx"
|
||||
FHEM="http://$FHEMIP:8083/fhem?XHR=1&cmd.$FHEMDP"
|
||||
FHEMIP="192.168.xxx"
|
||||
FHEM="http://192.168xx:8083/fhem?XHR=1&cmd.$FHEMDP"
|
||||
HOME="/home/doorpi"
|
||||
default_target=""
|
||||
default_target="722xxxx"
|
||||
|
||||
case $1 in
|
||||
|
||||
@ -52,10 +52,15 @@ case $1 in
|
||||
;;
|
||||
|
||||
purge)
|
||||
find $HOME/records/ -type f ! -newer /var/run/doorpi.pid -delete
|
||||
find $HOME/records/ -type f -ctime 1 -delete
|
||||
;;
|
||||
|
||||
clear)
|
||||
movement)
|
||||
curl "$FHEM=set%20$FHEMDP%20door%20movement" &
|
||||
;;
|
||||
|
||||
sabotage)
|
||||
curl "$FHEM=set%20$FHEMDP%20door%20sabotage" &
|
||||
;;
|
||||
|
||||
esac
|
||||
|
@ -61,10 +61,10 @@ local_port = 5060
|
||||
firewallpolicy = PolicyNoFirewall
|
||||
#
|
||||
sipphonetyp = linphone
|
||||
sipserver_password = xxxx
|
||||
sipserver_password = xxxxxxxxx
|
||||
sipserver_realm = fritz.box
|
||||
sipserver_server = xxxx
|
||||
sipserver_username = 620
|
||||
sipserver_server = xxxxxxx
|
||||
sipserver_username = xxxxxx
|
||||
stun_server =
|
||||
#
|
||||
max_call_time = 300
|
||||
@ -93,8 +93,10 @@ video_size = vga
|
||||
20 = os_execute:/home/doorpi/FHEMHelper.sh call init
|
||||
|
||||
[EVENT_BeforeSipPhoneMakeCall]
|
||||
10 = os_execute:/home/doorpi/FHEMHelper.sh call startup
|
||||
20 = take_snapshot
|
||||
10 = out:irlight,1
|
||||
20 = os_execute:/home/doorpi/FHEMHelper.sh call startup
|
||||
30 = take_snapshot
|
||||
40 = out:irlight,0
|
||||
#30 = mailto:haus271828@henning-weingarten.de,DoorPi,DoorPi initiating call
|
||||
|
||||
[EVENT_OnCallStateDisconnect]
|
||||
@ -118,6 +120,7 @@ onboardpins = piface
|
||||
[webservice_keyboard]
|
||||
base_path_input = /home/doorpi/keyboard/inputs/
|
||||
base_path_output = /home/doorpi/keyboard/outputs/
|
||||
reset_input=false
|
||||
|
||||
[webservice_InputPins]
|
||||
dooropen = out:door,1,0,3
|
||||
@ -127,7 +130,7 @@ snapshot = sleep:0
|
||||
streamon = sleep:0
|
||||
streamoff = sleep:0
|
||||
lighton = out:light,1
|
||||
lightonfortimer = out:light,1,0,60
|
||||
#lightonfortimer = out:light,1,0,60
|
||||
lightoff = out:light,0
|
||||
dashlighton = out:dashlight,1
|
||||
dashlightoff = out:dashlight,0
|
||||
@ -139,8 +142,10 @@ button2 = sleep:0
|
||||
|
||||
#-- communicate to FHEM that a snapshot has been taken
|
||||
[EVENT_OnKeyPressed_webservice.snapshot]
|
||||
10 = take_snapshot
|
||||
10 = out:irlight,1
|
||||
20 = os_execute:/home/doorpi/FHEMHelper.sh call snapshot
|
||||
30 = take_snapshot
|
||||
40 = out:irlight,0
|
||||
|
||||
#-- start video stream
|
||||
[EVENT_OnKeyPressed_webservice.streamon]
|
||||
@ -176,7 +181,8 @@ pull_up_down = PUD_UP
|
||||
0 = door
|
||||
1 = light
|
||||
2 = dashlight
|
||||
3 = hardlock
|
||||
3 = irlight
|
||||
4 = hardlock
|
||||
7 = blinking_led
|
||||
|
||||
[onboardpins_InputPins]
|
||||
@ -187,19 +193,18 @@ pull_up_down = PUD_UP
|
||||
6 = sleep:0
|
||||
7 = sleep:0
|
||||
|
||||
#-- DoorOpen pin from Arduino
|
||||
#-- DoorOpen pin from Arduino
|
||||
[EVENT_OnKeyPressed_onboardpins.1]
|
||||
10 = out:dashlight,1
|
||||
20 = os_execute:/home/doorpi/FHEMHelper.sh doorunlockandopen
|
||||
30 = os_execute:aplay -D plughw:1,0 /home/doorpi/sounds/067_willkommen.wav
|
||||
40 = out:dashlight,0
|
||||
10 = os_execute:/home/doorpi/FHEMHelper.sh doorunlockandopen
|
||||
20 = os_execute:aplay -D plughw:1,0 /home/doorpi/sounds/067_willkommen.wav
|
||||
|
||||
|
||||
#-- WrongID pin from Arduino
|
||||
[EVENT_OnKeyPressed_onboardpins.4]
|
||||
10 = out:dashlight,1
|
||||
10 = out:irlight,1
|
||||
20 = os_execute:/home/doorpi/FHEMHelper.sh wrongid
|
||||
30 = take_snapshot
|
||||
40 = out:dashlight,0
|
||||
40 = out:irlight,0
|
||||
|
||||
#-- LockState pin from Arduino - FHEM will transform softlock into hardlock
|
||||
[EVENT_OnKeyPressed_onboardpins.5]
|
||||
@ -207,8 +212,9 @@ pull_up_down = PUD_UP
|
||||
|
||||
#-- Movement detection
|
||||
[EVENT_OnKeyPressed_onboardpins.6]
|
||||
10 = out:dashlight,1,0,60
|
||||
10 = out:dashlight,1,0,1
|
||||
20 = os_execute:/home/doorpi/FHEMHelper.sh movement
|
||||
30 = sleep:60
|
||||
|
||||
#-- Sabotage detection
|
||||
[EVENT_OnKeyPressed_onboardpins.7]
|
||||
|
385
contrib/DoorPi/handler.py
Normal file
385
contrib/DoorPi/handler.py
Normal file
@ -0,0 +1,385 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.debug("%s loaded", __name__)
|
||||
|
||||
import threading
|
||||
import time # used by: fire_event_synchron
|
||||
from inspect import isfunction, ismethod # used by: register_action
|
||||
import string, random # used by event_id
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
from base import SingleAction
|
||||
import doorpi
|
||||
|
||||
class EnumWaitSignalsClass():
|
||||
WaitToFinish = True
|
||||
WaitToEnd = True
|
||||
sync = True
|
||||
syncron = True
|
||||
|
||||
DontWaitToFinish = False
|
||||
DontWaitToEnd = False
|
||||
async = False
|
||||
asyncron = False
|
||||
EnumWaitSignals = EnumWaitSignalsClass()
|
||||
|
||||
ONTIME = 'OnTime'
|
||||
|
||||
def id_generator(size = 6, chars = string.ascii_uppercase + string.digits):
|
||||
return ''.join(random.choice(chars) for _ in range(size))
|
||||
|
||||
class EventLog(object):
|
||||
|
||||
_db = False
|
||||
|
||||
#doorpi.DoorPi().conf.get_string_parsed('DoorPi', 'eventlog', '!BASEPATH!/conf/eventlog.db')
|
||||
def __init__(self, file_name):
|
||||
|
||||
if not file_name: return
|
||||
try:
|
||||
if not os.path.exists(os.path.dirname(file_name)):
|
||||
logger.info('Path %s does not exist - creating it now', os.path.dirname(file_name))
|
||||
os.makedirs(os.path.dirname(file_name))
|
||||
#https://docs.python.org/2/library/sqlite3.html#sqlite3.connect
|
||||
self._db = sqlite3.connect(
|
||||
database = file_name,
|
||||
timeout = 1,
|
||||
check_same_thread = False
|
||||
)
|
||||
|
||||
self.execute_sql('''
|
||||
CREATE TABLE IF NOT EXISTS event_log (
|
||||
event_id TEXT,
|
||||
fired_by TEXT,
|
||||
event_name TEXT,
|
||||
start_time REAL,
|
||||
additional_infos TEXT
|
||||
);'''
|
||||
)
|
||||
self.execute_sql('''
|
||||
CREATE TABLE IF NOT EXISTS action_log (
|
||||
event_id TEXT,
|
||||
action_name TEXT,
|
||||
start_time REAL,
|
||||
action_result TEXT
|
||||
);'''
|
||||
)
|
||||
except:
|
||||
logger.error('error to create event_db')
|
||||
|
||||
#-- CODE by PAH
|
||||
def purge_logs(self, period = 1):
|
||||
try:
|
||||
limit=int(time.time()-period*86400)
|
||||
logger.info('purge event and action log older than %s days => limit = %s',period,limit)
|
||||
sql_statement = '''
|
||||
DELETE FROM event_log
|
||||
WHERE (start_time < '{limit}');'''.format(limit = limit)
|
||||
self.execute_sql(sql_statement)
|
||||
sql_statement = '''
|
||||
DELETE FROM action_log
|
||||
WHERE (start_time < '{limit}');'''.format(limit = limit)
|
||||
self.execute_sql(sql_statement)
|
||||
self._db.commit()
|
||||
return "0"
|
||||
except Exception as exp:
|
||||
logger.exception(exp)
|
||||
return "-1"
|
||||
#-- END CODE by PAH
|
||||
|
||||
def get_event_log_entries_count(self, filter = ''):
|
||||
logger.debug('request event logs count with filter %s', filter)
|
||||
try:
|
||||
return self.execute_sql('''
|
||||
SELECT COUNT(*)
|
||||
FROM event_log
|
||||
WHERE event_id LIKE '%{filter}%'
|
||||
OR fired_by LIKE '%{filter}%'
|
||||
OR event_name LIKE '%{filter}%'
|
||||
OR start_time LIKE '%{filter}%'
|
||||
'''.format(filter = filter)).fetchone()[0]
|
||||
except Exception as exp:
|
||||
logger.exception(exp)
|
||||
return "-1"
|
||||
|
||||
def get_event_log_entries(self, max_count = 100, filter = ''):
|
||||
logger.debug('request last %s event logs with filter %s', max_count, filter)
|
||||
return_object = []
|
||||
sql_statement = '''
|
||||
SELECT
|
||||
event_id,
|
||||
fired_by,
|
||||
event_name,
|
||||
start_time,
|
||||
additional_infos
|
||||
FROM event_log
|
||||
WHERE event_id LIKE '%{filter}%'
|
||||
OR fired_by LIKE '%{filter}%'
|
||||
OR event_name LIKE '%{filter}%'
|
||||
OR start_time LIKE '%{filter}%'
|
||||
ORDER BY start_time DESC
|
||||
LIMIT {max_count}'''.format(max_count = max_count, filter = filter)
|
||||
|
||||
for single_row in self.execute_sql(sql_statement):
|
||||
return_object.append({
|
||||
'event_id': single_row[0],
|
||||
'fired_by': single_row[1],
|
||||
'event_name': single_row[2],
|
||||
'start_time': single_row[3],
|
||||
'additional_infos': single_row[4]
|
||||
})
|
||||
return return_object
|
||||
|
||||
def execute_sql(self, sql):
|
||||
if not self._db: return
|
||||
#logger.trace('fire sql: %s', sql)
|
||||
return self._db.execute(sql)
|
||||
|
||||
def insert_event_log(self, event_id, fired_by, event_name, start_time, additional_infos):
|
||||
sql_statement = '''
|
||||
INSERT INTO event_log VALUES (
|
||||
"{event_id}","{fired_by}","{event_name}",{start_time},"{additional_infos}"
|
||||
);
|
||||
'''.format(
|
||||
event_id = event_id,
|
||||
fired_by = fired_by.replace('"', "'"),
|
||||
event_name = event_name.replace('"', "'"),
|
||||
start_time = start_time,
|
||||
additional_infos = str(additional_infos).replace('"', "'")
|
||||
)
|
||||
self.execute_sql(sql_statement)
|
||||
#-- CODE by PAH
|
||||
try: self._db.commit()
|
||||
except: pass
|
||||
|
||||
def insert_action_log(self, event_id, action_name, start_time, action_result):
|
||||
sql_statement = '''
|
||||
INSERT INTO action_log VALUES (
|
||||
"{event_id}","{action_name}",{start_time},"{action_result}"
|
||||
);
|
||||
'''.format(
|
||||
event_id = event_id,
|
||||
action_name = action_name.replace('"', "'"),
|
||||
start_time = start_time,
|
||||
action_result = str(action_result).replace('"', "'")
|
||||
)
|
||||
self.execute_sql(sql_statement)
|
||||
#-- CODE by PAH
|
||||
#try: self._db.commit()
|
||||
#except: pass
|
||||
|
||||
def update_event_log(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
try: self._db.close()
|
||||
except: pass
|
||||
|
||||
__del__ = destroy
|
||||
|
||||
class EventHandler:
|
||||
|
||||
__Sources = [] # Auflistung Sources
|
||||
__Events = {} # Zuordnung Event zu Sources (1 : n)
|
||||
__Actions = {} # Zuordnung Event zu Actions (1: n)
|
||||
|
||||
__additional_informations = {}
|
||||
|
||||
@property
|
||||
def event_history(self): return self.db.get_event_log_entries()
|
||||
|
||||
@property
|
||||
def sources(self): return self.__Sources
|
||||
@property
|
||||
def events(self): return self.__Events
|
||||
@property
|
||||
def events_by_source(self):
|
||||
events_by_source = {}
|
||||
for event in self.events:
|
||||
for source in self.events[event]:
|
||||
if source in events_by_source:
|
||||
events_by_source[source].append(event)
|
||||
else:
|
||||
events_by_source[source] = [event]
|
||||
return events_by_source
|
||||
@property
|
||||
def actions(self): return self.__Actions
|
||||
@property
|
||||
def threads(self): return threading.enumerate()
|
||||
@property
|
||||
def idle(self): return len(self.threads) - 1 is 0
|
||||
@property
|
||||
def additional_informations(self): return self.__additional_informations
|
||||
|
||||
def __init__(self):
|
||||
db_path = doorpi.DoorPi().config.get_string_parsed('DoorPi', 'eventlog', '!BASEPATH!/conf/eventlog.db')
|
||||
self.db = EventLog(db_path)
|
||||
|
||||
__destroy = False
|
||||
|
||||
def destroy(self, force_destroy = False):
|
||||
self.__destroy = True
|
||||
self.db.destroy()
|
||||
|
||||
def register_source(self, event_source):
|
||||
if event_source not in self.__Sources:
|
||||
self.__Sources.append(event_source)
|
||||
logger.debug("event_source %s was added", event_source)
|
||||
|
||||
def register_event(self, event_name, event_source):
|
||||
silent = ONTIME in event_name
|
||||
if not silent: logger.trace("register Event %s from %s ", event_name, event_source)
|
||||
self.register_source(event_source)
|
||||
if event_name not in self.__Events:
|
||||
self.__Events[event_name] = [event_source]
|
||||
if not silent: logger.trace("added event_name %s and registered source %s", event_name, event_source)
|
||||
elif event_source not in self.__Events[event_name]:
|
||||
self.__Events[event_name].append(event_source)
|
||||
if not silent: logger.trace("added event_source %s to existing event %s", event_source, event_name)
|
||||
else:
|
||||
if not silent: logger.trace("nothing to do - event %s from source %s is already known", event_name, event_source)
|
||||
|
||||
def fire_event(self, event_name, event_source, syncron = False, kwargs = None):
|
||||
if syncron is False: return self.fire_event_asynchron(event_name, event_source, kwargs)
|
||||
else: return self.fire_event_synchron(event_name, event_source, kwargs)
|
||||
|
||||
def fire_event_asynchron(self, event_name, event_source, kwargs = None):
|
||||
silent = ONTIME in event_name
|
||||
if self.__destroy and not silent: return False
|
||||
if not silent: logger.trace("fire Event %s from %s asyncron", event_name, event_source)
|
||||
return threading.Thread(
|
||||
target = self.fire_event_synchron,
|
||||
args = (event_name, event_source, kwargs),
|
||||
name = "%s from %s" % (event_name, event_source)
|
||||
).start()
|
||||
|
||||
def fire_event_asynchron_daemon(self, event_name, event_source, kwargs = None):
|
||||
logger.trace("fire Event %s from %s asyncron and as daemons", event_name, event_source)
|
||||
t = threading.Thread(
|
||||
target = self.fire_event_synchron,
|
||||
args = (event_name, event_source, kwargs),
|
||||
name = "daemon %s from %s" % (event_name, event_source)
|
||||
)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
def fire_event_synchron(self, event_name, event_source, kwargs = None):
|
||||
silent = ONTIME in event_name
|
||||
if self.__destroy and not silent: return False
|
||||
|
||||
event_fire_id = id_generator()
|
||||
start_time = time.time()
|
||||
if not silent: self.db.insert_event_log(event_fire_id, event_source, event_name, start_time, kwargs)
|
||||
|
||||
if event_source not in self.__Sources:
|
||||
logger.warning('source %s unknown - skip fire_event %s', event_source, event_name)
|
||||
return "source unknown"
|
||||
if event_name not in self.__Events:
|
||||
logger.warning('event %s unknown - skip fire_event %s from %s', event_name, event_name, event_source)
|
||||
return "event unknown"
|
||||
if event_source not in self.__Events[event_name]:
|
||||
logger.warning('source %s unknown for this event - skip fire_event %s from %s', event_name, event_name, event_source)
|
||||
return "source unknown for this event"
|
||||
if event_name not in self.__Actions:
|
||||
if not silent: logger.debug('no actions for event %s - skip fire_event %s from %s', event_name, event_name, event_source)
|
||||
return "no actions for this event"
|
||||
|
||||
if kwargs is None: kwargs = {}
|
||||
kwargs.update({
|
||||
'last_fired': str(start_time),
|
||||
'last_fired_from': event_source,
|
||||
'event_fire_id': event_fire_id
|
||||
})
|
||||
|
||||
self.__additional_informations[event_name] = kwargs
|
||||
if 'last_finished' not in self.__additional_informations[event_name]:
|
||||
self.__additional_informations[event_name]['last_finished'] = None
|
||||
|
||||
if 'last_duration' not in self.__additional_informations[event_name]:
|
||||
self.__additional_informations[event_name]['last_duration'] = None
|
||||
|
||||
if not silent: logger.debug("[%s] fire for event %s this actions %s ", event_fire_id, event_name, self.__Actions[event_name])
|
||||
for action in self.__Actions[event_name]:
|
||||
if not silent: logger.trace("[%s] try to fire action %s", event_fire_id, action)
|
||||
try:
|
||||
result = action.run(silent)
|
||||
if not silent: self.db.insert_action_log(event_fire_id, action.name, start_time, result)
|
||||
if action.single_fire_action is True: del action
|
||||
except SystemExit as exp:
|
||||
logger.info('[%s] Detected SystemExit and shutdown DoorPi (Message: %s)', event_fire_id, exp)
|
||||
doorpi.DoorPi().destroy()
|
||||
except KeyboardInterrupt as exp:
|
||||
logger.info("[%s] Detected KeyboardInterrupt and shutdown DoorPi (Message: %s)", event_fire_id, exp)
|
||||
doorpi.DoorPi().destroy()
|
||||
except:
|
||||
logger.exception("[%s] error while fire action %s for event_name %s", event_fire_id, action, event_name)
|
||||
if not silent: logger.trace("[%s] finished fire_event for event_name %s", event_fire_id, event_name)
|
||||
self.__additional_informations[event_name]['last_finished'] = str(time.time())
|
||||
self.__additional_informations[event_name]['last_duration'] = str(time.time() - start_time)
|
||||
return True
|
||||
|
||||
def unregister_event(self, event_name, event_source, delete_source_when_empty = True):
|
||||
try:
|
||||
logger.trace("unregister Event %s from %s ", event_name, event_source)
|
||||
if event_name not in self.__Events: return "event unknown"
|
||||
if event_source not in self.__Events[event_name]: return "source not know for this event"
|
||||
self.__Events[event_name].remove(event_source)
|
||||
if len(self.__Events[event_name]) is 0:
|
||||
del self.__Events[event_name]
|
||||
logger.debug("no more sources for event %s - remove event too", event_name)
|
||||
if delete_source_when_empty: self.unregister_source(event_source)
|
||||
logger.trace("event_source %s was removed for event %s", event_source, event_name)
|
||||
return True
|
||||
except Exception as exp:
|
||||
logger.error('failed to unregister event %s with error message %s', event_name, exp)
|
||||
return False
|
||||
|
||||
def unregister_source(self, event_source, force_unregister = False):
|
||||
try:
|
||||
logger.trace("unregister Eventsource %s and force_unregister is %s", event_source, force_unregister)
|
||||
if event_source not in self.__Sources: return "event_source %s unknown" % (event_source)
|
||||
for event_name in self.__Events.keys():
|
||||
if event_source in self.__Events[event_name] and force_unregister:
|
||||
self.unregister_event(event_name, event_source, False)
|
||||
elif event_source in self.__Events[event_name] and not force_unregister:
|
||||
return "couldn't unregister event_source %s because it is used for event %s" % (event_source, event_name)
|
||||
if event_source in self.__Sources:
|
||||
# sollte nicht nötig sein, da es entfernt wird, wenn das letzte Event dafür gelöscht wird
|
||||
self.__Sources.remove(event_source)
|
||||
logger.trace("event_source %s was removed", event_source)
|
||||
return True
|
||||
except Exception as exp:
|
||||
logger.exception('failed to unregister source %s with error message %s', event_source, exp)
|
||||
return False
|
||||
|
||||
def register_action(self, event_name, action_object, *args, **kwargs):
|
||||
if ismethod(action_object) and callable(action_object):
|
||||
action_object = SingleAction(action_object, *args, **kwargs)
|
||||
elif isfunction(action_object) and callable(action_object):
|
||||
action_object = SingleAction(action_object, *args, **kwargs)
|
||||
elif not isinstance(action_object, SingleAction):
|
||||
action_object = SingleAction.from_string(action_object)
|
||||
|
||||
if action_object is None:
|
||||
logger.error('action_object is None')
|
||||
return False
|
||||
|
||||
if 'single_fire_action' in kwargs.keys() and kwargs['single_fire_action'] is True:
|
||||
action_object.single_fire_action = True
|
||||
del kwargs['single_fire_action']
|
||||
|
||||
if event_name in self.__Actions:
|
||||
self.__Actions[event_name].append(action_object)
|
||||
logger.trace("action %s was added to event %s", action_object, event_name)
|
||||
else:
|
||||
self.__Actions[event_name] = [action_object]
|
||||
logger.trace("action %s was added to new evententry %s", action_object, event_name)
|
||||
|
||||
return action_object
|
||||
|
||||
__call__ = fire_event_asynchron
|
33
contrib/DoorPi/history_event.py
Normal file
33
contrib/DoorPi/history_event.py
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.debug("%s loaded", __name__)
|
||||
|
||||
def get(*args, **kwargs):
|
||||
try:
|
||||
if len(kwargs['name']) == 0: kwargs['name'] = ['']
|
||||
if len(kwargs['value']) == 0: kwargs['value'] = ['']
|
||||
|
||||
#-- CODE by PAH
|
||||
if kwargs['name'][0] == 'purge':
|
||||
try:period = float(kwargs['value'][0])
|
||||
except: period = 1.0
|
||||
return kwargs['DoorPiObject'].event_handler.db.purge_logs(period)
|
||||
else:
|
||||
filter = kwargs['name'][0]
|
||||
try: max_count = int(kwargs['value'][0])
|
||||
except: max_count = 1000
|
||||
return kwargs['DoorPiObject'].event_handler.db.get_event_log_entries(max_count, filter)
|
||||
#-- END CODE by PAH
|
||||
|
||||
except Exception as exp:
|
||||
logger.exception(exp)
|
||||
return {'Error': 'could not create '+str(__name__)+' object - '+str(exp)}
|
||||
|
||||
def is_active(doorpi_object):
|
||||
if len(doorpi_object.event_handler.db.get_event_log_entries(1, '')):
|
||||
return True
|
||||
else:
|
||||
return False
|
Loading…
x
Reference in New Issue
Block a user