From c3af66840fde1cf21c04a7f8e4a0e9ea79aaa635 Mon Sep 17 00:00:00 2001 From: vuffiraa <> Date: Sat, 20 Apr 2019 12:16:15 +0000 Subject: [PATCH] 70_BOTVAC.pm: add cleaning statistics git-svn-id: https://svn.fhem.de/fhem/trunk@19229 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/70_BOTVAC.pm | 113 ++++++++++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 17 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 8c3637921..02bc0fc05 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 70_BOTVAC: add cleaning statistics - feature: 98_livetracking: CSV data from OwnTracks - new: 98_Weather: add wundergroundAPI for Weather Underground - feature: 98_backup: add support for FileLog path diff --git a/fhem/FHEM/70_BOTVAC.pm b/fhem/FHEM/70_BOTVAC.pm index 7a1826df0..a147c8c83 100755 --- a/fhem/FHEM/70_BOTVAC.pm +++ b/fhem/FHEM/70_BOTVAC.pm @@ -215,8 +215,54 @@ sub Get($@) { } else { return "no such reading: $what"; } + } elsif ( $what =~ /^(statistics)$/ ) { + if (defined( $hash->{helper}{MAPS} and @{$hash->{helper}{MAPS}} > 0)) { + my $ret = ""; + my $mapcount = @{$hash->{helper}{MAPS}}; + my $cw = 9; #column width + my $cws = $cw * 11 + 4; #column width sum + $ret .= "".sprintf("%-".($cws)."s","Report: ".ReadingsVal($name,"name","name").", ".InternalVal($name,"VENDOR","VENDOR").", ".ReadingsVal($name,"model","model"))."\n\n"; + + $ret .= "".sprintf("%-".($cw-5)."s","Map").sprintf("%-".($cw+1)."s","Expected").sprintf("%-".($cw-4)."s","Map").sprintf("%-".($cw-4)."s","Map").sprintf("%-".($cw-2)."s","Charge").sprintf("%-".($cw+1)."s","Discharge").sprintf("%-".($cw-2)."s","Area").sprintf("%-".($cw+11)."s","Cleaning").sprintf("%-".($cw-2)."s","Charge").sprintf("%-".$cw."s","Status").sprintf("%-".($cw+2)."s","Date").sprintf("%-".($cw+10)."s","Time")."\n"; + + $ret .= "".sprintf("%-".($cw-5)."s","No.").sprintf("%-".($cw-4)."s","Area").sprintf("%-".($cw-4)."s","Time").sprintf("%-".($cw-4)."s","Area").sprintf("%-".($cw-4)."s","Time").sprintf("%-".($cw-2)."s","Delta").sprintf("%-".(($cw+1))."s","Speed").sprintf("%-".($cw-2)."s","Speed").sprintf("%-".($cw-2)."s","Cat.").sprintf("%-".($cw-3)."s","Mode").sprintf("%-".($cw-2)."s","Freq.").sprintf("%-".($cw-2)."s","During").sprintf("%-".$cw."s","").sprintf("%-".($cw+2)."s","").sprintf("%-".($cw+10)."s","")."\n"; + + $ret .= "".sprintf("%-".($cw-5)."s","").sprintf("%-".($cw-4)."s","qm").sprintf("%-".($cw-4)."s","min").sprintf("%-".($cw-4)."s","qm").sprintf("%-".($cw-4)."s","min").sprintf("%-".($cw-2)."s","%").sprintf("%-".($cw+1)."s","%/min").sprintf("%-".($cw-2)."s","qm/min").sprintf("%-".($cw-2)."s","").sprintf("%-".($cw-3)."s","").sprintf("%-".($cw-2)."s","").sprintf("%-".($cw-2)."s","Run").sprintf("%-".$cw."s","").sprintf("%-".($cw+2)."s","YYYY-MM-DD").sprintf("%-".($cw+10)."s","hh:mm:ss")."\n"; + $ret .= sprintf("-"x($cws))."\n"; + + for (my $i=0;$i<$mapcount;$i++) { + my $map = \$hash->{helper}{MAPS}[$i]; + my $t1 = GetSecondsFromString("$$map->{end_at}"); + my $t2 = GetSecondsFromString("$$map->{start_at}"); + my $dt = $t1-$t2-$$map->{time_in_suspended_cleaning}-$$map->{time_in_error}-$$map->{time_in_pause}; + my $dc = $$map->{run_charge_at_start}-$$map->{run_charge_at_end}; + my $expa = int($$map->{cleaned_area}*100/$dc+.5) if ($dc > 0); + my $expt = int($dt*100/$dc/60+.5) if ($dc > 0); + $ret .= sprintf("%-".($cw-5)."s",$i+1); # Map No. + $ret .= sprintf("%-".($cw-4)."s",($expa>0?$expa:0)); # Expected Area + $ret .= sprintf("%-".($cw-4)."s",($expt>0?$expt:0)); # Expected Time + $ret .= sprintf("%-".($cw-4)."s",int($$map->{cleaned_area}+.5)); # Map Area + $ret .= sprintf("%-".($cw-4)."s",($dt>0)?(int($dt/60+.5)):0); # Map Time + $ret .= sprintf("%-".($cw-2)."s",($dc>0?$dc:0)); # Charge Delta + $ret .= sprintf("%-".($cw+1)."s",($dt>0 and $dc>0)?(int($dc*600/$dt+.5)/10):0); # Discharge Speed + $ret .= sprintf("%-".($cw-2)."s",($expt>0 and $expa>0)?(int($expa*10/$expt+.5))/10:0); # Area Speed + $ret .= sprintf("%-".($cw-2)."s",GetCategoryText($$map->{category})); # Cleaning Category + $ret .= sprintf("%-".($cw-3)."s",GetModeText($$map->{mode})); # Cleaning Mode + $ret .= sprintf("%-".($cw-2)."s",GetModifierText($$map->{modifier})); # Cleaning Frequency + $ret .= sprintf("%-".($cw-2)."s",$$map->{suspended_cleaning_charging_count}."x"); # Charge During Run + $ret .= sprintf("%-".$cw."s",$$map->{status}); # Status + $ret .= sprintf("%-".($cws)."s",GetTimeFromString($$map->{generated_at})); # Date + $ret .= "\n"; + $ret .= "
".sprintf("-"x($cws))."
" if ($i%5==4); + } + $ret .= "\nManufacturer Specification\n"; + $ret .= "Vorwerk VR220(VR300), battery 84 Wh, eco (90 min, 120 qm, power 65 W), turbo (60 min, 90 qm, power 85 W)\n"; + return $ret; + } else { + return "maps for $what are not available yet"; + } } else { - return "Unknown argument $what, choose one of batteryPercent:noArg"; + return "Unknown argument $what, choose one of batteryPercent:noArg statistics:noArg"; } } @@ -964,7 +1010,7 @@ sub ReceiveCommand($$$) { for (my $i = 0; $i < @boundaries; $i++) { my $currentBoundary = "{"; $currentBoundary .= "\"id\":\"".$boundaries[$i]->{id}."\"," if ($boundaries[$i]->{type} eq "polygon"); - $currentBoundary .= "\"type\":\"".$boundaries[$i]->{type}."\","; + $currentBoundary .= "\"type\":\"".$boundaries[$i]->{type}."\","; if (ref($boundaries[$i]->{vertices}) eq "ARRAY") { my @vertices = @{$boundaries[$i]->{vertices}}; $currentBoundary .= "\"vertices\":["; @@ -974,8 +1020,8 @@ sub ReceiveCommand($$$) { $currentBoundary .= "[".$xy[0].",".$xy[1]."],"; } } - $tmp = chop($currentBoundary); #remove last "," - $currentBoundary .= "],"; + $tmp = chop($currentBoundary); #remove last "," + $currentBoundary .= "],"; } $currentBoundary .= "\"name\":\"".$boundaries[$i]->{name}."\","; $currentBoundary .= "\"color\":\"".$boundaries[$i]->{color}."\","; @@ -983,18 +1029,18 @@ sub ReceiveCommand($$$) { $currentBoundary .= "\"enabled\":".$tmp.","; $tmp = chop($currentBoundary); #remove last "," $currentBoundary .= "},\n"; - if ($boundaries[$i]->{type} eq "polygon") { - $zonesList .= $currentBoundary; - } else { - $boundariesList .= $currentBoundary; - } + if ($boundaries[$i]->{type} eq "polygon") { + $zonesList .= $currentBoundary; + } else { + $boundariesList .= $currentBoundary; + } } $tmp = chomp($boundariesList); #remove last "\n" - $tmp = chomp($zonesList); #remove last "\n" - $tmp = chop($boundariesList); #remove last "," - $tmp = chop($zonesList); #remove last "," + $tmp = chomp($zonesList); #remove last "\n" + $tmp = chop($boundariesList); #remove last "," + $tmp = chop($zonesList); #remove last "," readingsBulkUpdateIfChanged($hash, "floorplan_".$reqId."_boundaries", $boundariesList); - readingsBulkUpdateIfChanged($hash, "floorplan_".$reqId."_zones", $zonesList); + readingsBulkUpdateIfChanged($hash, "floorplan_".$reqId."_zones", $zonesList); } } } @@ -1135,14 +1181,28 @@ sub ReceiveCommand($$$) { if ( ref($return) eq "HASH" ) { if ( ref($return->{maps} ) eq "ARRAY" ) { my @maps = @{$return->{maps}}; + $hash->{helper}{MAPS} = $return->{maps}; if (@maps) { # take first - newest my $map = $maps[0]; - readingsBulkUpdateIfChanged($hash, "map_status", $map->{status}); - readingsBulkUpdateIfChanged($hash, "map_id", $map->{id}); + foreach my $key (keys $map) { + readingsBulkUpdateIfChanged($hash, "map_".$key, defined($map->{$key})?$map->{$key}:"") + if ($key !~ "url|url_valid_for_seconds|generated_at|start_at|end_at"); + } readingsBulkUpdateIfChanged($hash, "map_date", GetTimeFromString($map->{generated_at})); - readingsBulkUpdateIfChanged($hash, "map_area", $map->{cleaned_area}); readingsBulkUpdateIfChanged($hash, ".map_url", $map->{url}); + my $t1 = GetSecondsFromString($map->{end_at}); + my $t2 = GetSecondsFromString($map->{start_at}); + my $dt = $t1-$t2-$map->{time_in_suspended_cleaning}-$map->{time_in_error}-$map->{time_in_pause}; + my $dc = $map->{run_charge_at_start}-$map->{run_charge_at_end}; + my $expa = int($map->{cleaned_area}*100/$dc+.5) if ($dc > 0); + my $expt = int($dt*100/$dc/60+.5) if ($dc > 0); + readingsBulkUpdateIfChanged($hash, "map_duration", int($dt/6+.5)/10); # min + readingsBulkUpdateIfChanged($hash, "map_expected_area", $expa>0?$expa:0); # qm + readingsBulkUpdateIfChanged($hash, "map_run_discharge", $dc>0?$dc:0); # % + readingsBulkUpdateIfChanged($hash, "map_expected_time", $expt>0?$expt:0); # min + readingsBulkUpdateIfChanged($hash, "map_area_per_time", ($expt>0 and $expa>0)?(int($expa*10/$expt+.5)/10):0); # qm/min + readingsBulkUpdateIfChanged($hash, "map_discharge_per_time", ($dt>0 and $dc>0)?(int($dc*600/$dt+.5)/10):0); # %/min $loadMap = 1; # getPersistentMaps push(@successor , ["robots", "persistent_maps"]); @@ -1226,6 +1286,18 @@ sub GetTimeFromString($) { } } +sub GetSecondsFromString($) { + my ($timeStr) = @_; + + eval { + use Time::Local; + if(defined($timeStr) and $timeStr =~ m/^(\d{4})-(\d{2})-(\d{2})T([0-2]\d):([0-5]\d):([0-5]\d)Z$/) { + my $time = timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900); + return $time; + } + } +} + sub SetRobot($$) { my ( $hash, $robot ) = @_; my $name = $hash->{NAME}; @@ -1905,7 +1977,14 @@ sub wsMasking($$) {
  • get <name> batteryPercent
    requests the state of the battery from Robot -

  • + +
    + +
  • get <name> statistics +
    + display statistical data, extracted from available maps of recent cleanings +
  • +