diff --git a/fhem/contrib/DoorPi/70_DoorPi.pm b/fhem/contrib/DoorPi/70_DoorPi.pm index ba7ae28f1..7d2876ffa 100644 --- a/fhem/contrib/DoorPi/70_DoorPi.pm +++ b/fhem/contrib/DoorPi/70_DoorPi.pm @@ -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 .= "".int(@{ $hash->{DATA}})." calls"; } - $ret .= "open"; + $ret .= "open"; 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 .= ""; #-- div tag to support inform updates - $ret .= '
'; - if( exists($hash->{DATA}) && (int(@{$hash->{DATA}}) > 0) ){ + $ret .= '
'; + if( exists($hash->{DATA}) ){ $ret .= ''; + + 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 .= ''; $ret .= ''; - 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 = ''; + $record .= ($iconaudio) ? $iconaudio : $rs; + $record .= ''; + } + + if( $snapshot ne ""){ + $state = ''; + $state .= ($iconpic) ? $iconpic : ''; + $state .= ''; + } + #-- 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, ''; + foreach my $col (@order){ + push @htmlret, ''; + } + + $ret .= join("",@htmlret).""; + + #-- 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 = ''.$rs.''; - } - - if( $snapshot ne ""){ - $state = ''; - $state .= ($iconpic) ? $iconpic : ''; - } - - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - } - $ret .= "
'.$record.'
'.$line->{$col}.'
'.$state.''.$timestamp.''.$number.''.$result.''.$duration.''.$record.'
"; - }else{ - if(AttrVal($name, "language", "en") eq "de"){ - $ret .= "Rufliste leer"; }else{ - $ret .= "Calllist empty"; - } + if(AttrVal($name, "language", "en") eq "de"){ + $ret .= "Rufliste leer"; + }else{ + $ret .= "Calllist empty"; + } + } + $ret .= "
"; } $ret .= ""; 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.
  • set <DoorPi-Device> purge
    - Clean all recordings and snapshots which are older than the current process
  • -
  • - set <DoorPi-Device> clear
    - Clear all recordings and snapshots
  • + Clear all recordings and snapshots which are older than a day
    @@ -1175,9 +1392,6 @@ sub DoorPi_makeTable($$$$){
  • attr <DoorPi-Device> lightoffcmd <string>
    FHEM command additionally executed for "light off" action (no default)
  • -
  • attr <DoorPi-Device> lighttimercmd - <string> -
    FHEM command additionally executed for "light off" action (no default)
  • attr <DoorPi-Device> dashlightbutton <string>
    DoorPi name for dashlight action (default: dashlight)
  • @@ -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) diff --git a/fhem/contrib/DoorPi/FHEMHelper.sh.safe b/fhem/contrib/DoorPi/FHEMHelper.sh.safe index 5f71bb9ca..cedafecca 100644 --- a/fhem/contrib/DoorPi/FHEMHelper.sh.safe +++ b/fhem/contrib/DoorPi/FHEMHelper.sh.safe @@ -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 diff --git a/fhem/contrib/DoorPi/doorpi.ini.safe b/fhem/contrib/DoorPi/doorpi.ini.safe index e58160edb..77c504b9c 100644 --- a/fhem/contrib/DoorPi/doorpi.ini.safe +++ b/fhem/contrib/DoorPi/doorpi.ini.safe @@ -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] diff --git a/fhem/contrib/DoorPi/handler.py b/fhem/contrib/DoorPi/handler.py new file mode 100644 index 000000000..8cebad369 --- /dev/null +++ b/fhem/contrib/DoorPi/handler.py @@ -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 diff --git a/fhem/contrib/DoorPi/history_event.py b/fhem/contrib/DoorPi/history_event.py new file mode 100644 index 000000000..afac3edd7 --- /dev/null +++ b/fhem/contrib/DoorPi/history_event.py @@ -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