diff --git a/fhem/FHEM/lib/AttrTemplate/mqtt2.template b/fhem/FHEM/lib/AttrTemplate/mqtt2.template index a6933694a..45b719349 100644 --- a/fhem/FHEM/lib/AttrTemplate/mqtt2.template +++ b/fhem/FHEM/lib/AttrTemplate/mqtt2.template @@ -4885,12 +4885,14 @@ setreading DEVICE attrTemplateVersion 20210510 # new valetudo Template contributed by Otto123, should replace the old valetudo/rockrobo Template name:valetudo filter:TYPE=MQTT2_DEVICE -desc:use this to control a rooted vacuum with valetudo V2. For further details visit Hypfer/Valetudo

NOTE: tested with Dreame L10pro, Xiaomi Vacuum Gen1, details: Forum Thread +desc:use this to control a rooted vacuum with valetudo V2. For further details visit Hypfer/Valetudo
NOTE: tested with Dreame L10pro, Xiaomi Vacuum Gen1, details: Forum Thread order:X_03b par:BASE_TOPIC;BASE_TOPIC typically is valetudo;{ AttrVal("DEVICE","devicetopic",AttrVal("DEVICE","readingList","")) =~ m,([^\/:]+)[\/].*, ? $1 : undef } par:DEV_ID;DEV_ID is random by Firmware;{ AttrVal("DEVICE","devicetopic",AttrVal("DEVICE","readingList","")) =~ m,[^\/]+\/([^\/:]+).*, ? $1 : undef } +par:IP4;Device IP Adress;{(split q{,}, ReadingsVal("DEVICE",'ips',undef))[0] || (split q{_}, InternalVal("DEVICE",ReadingsVal("DEVICE",'IODev',undef).'_CONN',undef))[1] || undef } { $data{f}='99_valetudoUtils.pm';\ -$data{u}="https://svn.fhem.de/trac/browser/trunk/fhem/contrib/AttrTemplate/$data{f}?format=txt";\ +# Svn_GetFile() works asynch #{ Svn_GetFile("contrib/AttrTemplate/$data{f}", $data{f}, sub(){CommandReload(undef, $data{f})}) }\ +{$data{u}="https://svn.fhem.de/trac/browser/trunk/fhem/contrib/AttrTemplate/$data{f}?format=txt"}\ qx(wget -qO FHEM/$data{f} $data{u}); CommandReload(undef, $data{f});\ delete $data{f};delete $data{u};''} attr DEVICE alias DEV_ID @@ -4898,42 +4900,33 @@ attr DEVICE devicetopic BASE_TOPIC/DEV_ID attr DEVICE icon vacuum_top attr DEVICE readingList \ $\DEVICETOPIC/\x24state:.* _state\ - $\DEVICETOPIC/(Att.*|Basic.*|Consum.*|Curr.*|Loc.*|Wifi.*)/[a-zA-Z\-_]+:.* { $TOPIC =~ m,$\DEVICETOPIC\/.*\/([a-zA-Z\-_]+),; $1 eq 'ips'? {"ip4"=> (split ',',$EVENT)[0]}:{"$1"=>$EVENT} }\ - $\DEVICETOPIC/BatteryStateAttribute/level:.* batteryPercent\ - $\DEVICETOPIC/BatteryStateAttribute/status:.* batteryState\ - $\DEVICETOPIC/FanSpeedControlCapability/preset:.* fanSpeed\ - $\DEVICETOPIC/GoToLocationCapability/presets:.* .locationsPresets\ - $\DEVICETOPIC/GoToLocationCapability/go:.* {}\ - $\DEVICETOPIC/MapData/map-data:.* {}\ - $\DEVICETOPIC/MapData/segments:.* .segments\ - $\DEVICETOPIC/StatusStateAttribute/status:.* { {"state"=>$EVENT,"cleanerState"=>$EVENT} }\ - $\DEVICETOPIC/StatusStateAttribute/detail:.* stateDetail\ - $\DEVICETOPIC/StatusStateAttribute/error:.* stateError\ - $\DEVICETOPIC/WaterUsageControlCapability/preset:.* waterUsage\ - $\DEVICETOPIC/ZoneCleaningCapability/presets:.* .zonesPresets\ - $\DEVICETOPIC/ZoneCleaningCapability/start:.* {} + $\DEVICETOPIC/.*/[a-zA-Z\-_]+:.* { $TOPIC =~ m,$\DEVICETOPIC\/(.*)\/([a-zA-Z\-_]+),; valetudo_r($NAME,$1,$2,$EVENT) } attr DEVICE setList \ pause:noArg $\DEVICETOPIC/BasicControlCapability/operation/set PAUSE\ start:noArg $\DEVICETOPIC/BasicControlCapability/operation/set START\ stop:noArg $\DEVICETOPIC/BasicControlCapability/operation/set STOP\ charge:noArg $\DEVICETOPIC/BasicControlCapability/operation/set HOME\ clean_zone:{valetudo_w($name,'zones')} { valetudo_c($NAME,$EVENT) }\ - fanSpeed:off,min,low,medium,high,turbo,max $\DEVICETOPIC/FanSpeedControlCapability/preset/set $EVTPART1\ - waterUsage:off,min,low,medium,high,turbo,max $\DEVICETOPIC/WaterUsageControlCapability/preset/set $EVTPART1\ + fanSpeed:min,low,medium,high,max $\DEVICETOPIC/FanSpeedControlCapability/preset/set $EVTPART1\ locate:PERFORM $\DEVICETOPIC/LocateCapability/locate/set $EVTPART1\ - x_raw_payload:textField { valetudo_c($NAME,$EVENT) } + x_raw_payload:textField { valetudo_c($NAME,$EVENT) }\ + delete:zones,locations {fhem("deletereading $NAME .$EVTPART1");;return undef}\ + zoneNew:textField { valetudo_z($NAME,$EVENT) }\ + zoneRename:textField { valetudo_z($NAME,$EVENT) }\ + get:segments,release,ip { valetudo_g($NAME,$EVENT) } attr DEVICE event-on-change-reading .* attr DEVICE timestamp-on-change-reading .* attr DEVICE setStateList operation clean_segment clean_zone goto fanSpeed waterUsage locate x_raw_payload attr DEVICE model valetudoV2 -attr DEVICE stateFormat state -setreading DEVICE attrTemplateVersion 20220121 -option:{valetudo_f('DEVICE','MapSegmentation')} -{ CommandAttr_multiline( 'DEVICE','setList',q( clean_segment:{'multiple-strict,'.valetudo_w($name,'segments')} { valetudo_c($NAME,$EVENT) }) ) } -option:{valetudo_f('DEVICE','GoToLocation')} -{ CommandAttr_multiline( 'DEVICE','setList',q( goto:{valetudo_w($name,'locations')} { valetudo_c($NAME,$EVENT) }) ) } +attr DEVICE devStateIcon docked:rc_PLAY:start (moving|cleaning):rc_PLAY@blue:pause (idle|paused):rc_PAUSE@red:start returning:refresh:pause +attr DEVICE stateFormat cleanerState\ +cleanerState +setreading DEVICE attrTemplateVersion 20221101 option:{1} -deletereading -q DEVICE (?!associatedWith|IODev).* +deletereading -q DEVICE \.{0,1}segments|map-data.*|.*_.*_.*|_.* +{valetudo_s('DEVICE')} +set DEVICE get ip IP4 +set DEVICE get release # contributed by Otto123, source: https://forum.fhem.de/index.php/topic,94495.msg1062312.html#msg1062312 name:worx_landroid @@ -4952,7 +4945,7 @@ attr DEVICE event-on-change-reading .* attr DEVICE readingList $\DEVICETOPIC/commandOut:.* { json2nameValue($EVENT,'',$JSONMAP) } attr DEVICE jsonMap dat_rsi:wifiQuality dat_fw:firmware cfg_sn:SerialNumber\ dat_le:mowerErrorIndex dat_ls:mowerStatusIndex\ -cfg_rd:mowerRainDelay cfg_sc_m:mowerActiveIndex cfg_sc_p:mowerTimeCorrection\ +cfg_rd:mowerRainDelay cfg_sc_m:mowerActiveIndex cfg_sc_p:mowerTimeCorrection cfg_tq:torqueSetting\ dat_bt_t:batteryTemperature dat_bt_v:batteryVoltage dat_bt_p:batteryPercent dat_bt_nr:batteryChargeCycle dat_bt_c:batteryCharging\ dat_st_b:totalBladeTime dat_st_d:totalDistance dat_st_wt:totalTime dat_st_bl:borderLength\ dat_dmp_1:directionPitch dat_dmp_2:directionRoll dat_dmp_3:directionYaw @@ -4963,6 +4956,7 @@ attr DEVICE setList mowerRainDelay:slider,0,30,1440 $\DEVICETOPIC/commandIn {"rd startMower:noArg $\DEVICETOPIC/commandIn {"cmd":1}\ pauseMower:noArg $\DEVICETOPIC/commandIn {"cmd":2}\ stopMower:noArg $\DEVICETOPIC/commandIn {"cmd":3}\ + torqueSetting:slider,-50,1,50 $\DEVICETOPIC/commandIn {"tq":$EVTPART1}\ PartyTime:slider,0,10,2880 $\DEVICETOPIC/commandIn {"sc":{"distm":$EVTPART1}}\ PartyMode:on,off { my %hash = ( 'on' => 2, 'off' => 1);qq($\DEVICETOPIC/commandIn {"sc":{"m":$hash{$EVTPART1}}})}\ x_raw_payload:textField { my $payload = $EVENT;$payload =~ s/$EVTPART0 //g; qq($\DEVICETOPIC/commandIn $payload)} @@ -5013,7 +5007,7 @@ mowerErrorTxt:mowerErrorIndex.* { my %errorCodes = (\ 17 => "Battery temp out of range"\ ); $errorCodes{ReadingsVal($name,"mowerErrorIndex","0")}} attr DEVICE model worx_landroid_mower -setreading DEVICE attrTemplateVersion 20210602 +setreading DEVICE attrTemplateVersion 20221101 ########################################### diff --git a/fhem/contrib/AttrTemplate/99_valetudoUtils.pm b/fhem/contrib/AttrTemplate/99_valetudoUtils.pm index a672bd394..eb843cc8f 100644 --- a/fhem/contrib/AttrTemplate/99_valetudoUtils.pm +++ b/fhem/contrib/AttrTemplate/99_valetudoUtils.pm @@ -4,6 +4,9 @@ # utils for valetudo v2 API MQTT Implementation # They are then available in every Perl expression. +# for zone cleaning after release 2022.05 a Reading .zones as json String is needed +# {"Zone1": copy of string in the UI,"Zone2": copy of string in the UI } + package main; use strict; @@ -41,12 +44,23 @@ sub valetudo_w { return join ',', sort values %{$decoded} } # this part read presets which contains a full json for preset zones or locations + # depending on the valetudo release, there was a fundamental change in 2022.05 if ($setter eq 'zones' or $setter eq 'locations') { - my $json = ReadingsVal($NAME,'.'.$setter.'Presets',q{}); + if (ReadingsVal($NAME,'valetudo_release','') lt '2022.05.0') { + # old code + my $json = ReadingsVal($NAME,'.'.$setter.'Presets','{}'); + if ($json eq '{}') {$json = '{"1":"no_Segment_or_not_supported"}'}; my $decoded = decode_j($json); my @array; for ( keys %{$decoded} ) { push @array, $decoded->{$_}->{'name'} } return join ',', sort @array + } else { + my $json = ReadingsVal($NAME,'.'.$setter,'{}'); + if ($json eq '{}') {$json = '{"1":"no_Segment_or_not_supported"}'}; + my $decoded = decode_j($json); + return join ',', sort keys %{$decoded}; + #attr MQTT2_ClumsyQuirkyCattle userReadings zoneRename:.zones.* { join ' ',sort keys %{ decode_j(ReadingsVal($name,'.zones','')) } } + } } # this part is for study purpose to read the full json segments with the REST API like # setreading alias=DreameL10pro json_segments {(qx(wget -qO - http://192.168.90.21/api/v2/robot/capabilities/MapSegmentationCapability))} @@ -74,7 +88,8 @@ sub valetudo_c { # this part return an array of segment id's according to selected Names from segments (simple json) if ($cmd eq 'clean_segment') { my @rooms = split ',', $load; - my $json = ReadingsVal($NAME,'.segments',q{}); + my $json = ReadingsVal($NAME,'.segments','{}'); + if ($json eq '{}') {$json = '{"1":"no_Segment_or_not_supported"}'}; my $decoded = decode_j($json); my @ids; for ( @rooms ) { push @ids, {reverse %{$decoded} }->{$_} } @@ -82,14 +97,25 @@ sub valetudo_c { $ret = $devicetopic.'/MapSegmentationCapability/clean/set '.toJSON $Hcmd{$cmd} } - # this part return the zone/location id according to the selected Name from presets (zones/locations) (more complex json) + # this part return the zone/location id according to the selected Name from presets (zones/locations) + # depending on the valetudo release, there was a fundamental change in 2022.05 if ($cmd eq 'clean_zone') { - my $json = ReadingsVal($NAME,'.zonesPresets',q{}); - my $decoded = decode_j($json); - for (keys %{$decoded}) { + if (ReadingsVal($NAME,'valetudo_release','') lt '2022.05.0') { + # old code + my $json = ReadingsVal($NAME,'.zonesPresets',q{}); + my $decoded = decode_j($json); + for (keys %{$decoded}) { if ( $decoded->{$_}->{'name'} eq $load ) {$ret = $devicetopic.'/ZoneCleaningCapability/start/set '.$_ } - } + } + } else { + my $json = ReadingsVal($NAME,'.zones',q{}); + my $decoded = decode_j($json); + for (keys %{$decoded}) { + if ( $_ eq $load ) {$ret = $devicetopic.'/ZoneCleaningCapability/start/set '.toJSON $decoded->{$_} } + } + } } + if ($cmd eq 'goto') { my $json = ReadingsVal($NAME,'.locationsPresets',q{}); my $decoded = decode_j($json); @@ -115,15 +141,140 @@ sub valetudo_c { } return $ret } +####### +# handling .zones Reading +sub valetudo_z { + my $NAME = shift; + my ($cmd,$load) = split q{ }, shift, 2; + my $ret = 'error'; + my $reading = $cmd =~ m,^zone, ? 'zone':'location'; + my $json = ReadingsVal($NAME,'.'.$reading.'s',''); + if ($cmd =~ m,New$,) { + $load =~ s/[\n\r\s]//g; + if ($json ne '') { + my $decoded = decode_j($json); + my $zone_name = 'Zjson'.((keys %{$decoded} )+ 1); + my ($key, $val) = ($zone_name, decode_j $load); + $decoded->{$key} = $val; + $ret = toJSON ($decoded); + } else {$ret = "{\"Zjson1\":$load}"} + } + if ($cmd =~ m,Rename$,) { + my ($part1,$part2) = split q{ },$load; + my @keys = keys %{ decode_j($json) }; + for (@keys) { if($_ eq $part1){ $json =~ s/$part1/$part2/ } } + $ret = $json; + #fhem("setreading $NAME $cmd ".join ' ',(sort keys %{ decode_j($ret) } ) ); + } + fhem("setreading $NAME ".$reading."Rename ".join ' ',(reverse sort keys %{ decode_j($ret) } ) ); + fhem("setreading $NAME .".$reading."s ".$ret); + return undef +} + ####### # ask the robot via REST API for Featurelist and feature and return true false sub valetudo_f { my $NAME = shift; # Devicename of the robot my $substr = shift; # requested Feature like GoToLocation or MapSegmentation - my $ip = ReadingsVal($NAME,'ip4',(split ',',ReadingsVal($NAME,'ips','error'))[0]); + # my $ip = ReadingsVal($NAME,'ip4',(split ',',ReadingsVal($NAME,'ips','error'))[0]); + my $ip = ( split '_', InternalVal($NAME,ReadingsVal($NAME,'IODev','').'_CONN','error') )[1] ; my $string = GetHttpFile($ip, '/api/v2/robot/capabilities'); - index($string, $substr) == -1 ? '0':'1'; + index($string, $substr) == -1 ? 0:1; } + +####### +# used for readingList. return readingname -> value pairs +# look https://valetudo.cloud/pages/integrations/mqtt.html for details mqtt implementation +sub valetudo_r { + my $NAME = shift; + my $feature = shift; + my $value = shift; + my $EVENT = shift; + #Log3(undef, 1, "Name $NAME, featur $feature, value $value, $EVENT"); + + if ($feature =~ m,(^Att.*|^Basic.*|^Consum.*|^Loc.*),) + {return {"$value"=>$EVENT} } + if ($feature eq 'BatteryStateAttribute') + {return $value eq 'level' ? {"batteryPercent"=>$EVENT}: + $value eq 'status' ? {"batteryState"=>$EVENT}:{"$value"=>$EVENT} } + if ($feature eq 'CurrentStatisticsCapability') + {return $value eq 'area' ? {"area"=>sprintf("%.2f",($EVENT / 10000))." m²"}:{"$value"=>$EVENT} } + if ($feature eq 'FanSpeedControlCapability') + {return $value eq 'preset' ? {"fanSpeed"=>$EVENT}:{"$value"=>$EVENT} } + if ($feature eq 'GoToLocationCapability' or $feature eq 'ZoneCleaningCapability' or $feature eq 'MapSegmentationCapability') + {return {} } + if ($feature eq 'MapData') + {return $value eq 'map-data' ? {}: + $value eq 'segments' ? {".segments"=>$EVENT}:{"$value"=>$EVENT} } + if ($feature eq 'OperationModeControlCapability') + {return $value eq 'preset' ? {"operationMode"=>$EVENT}:{"$value"=>$EVENT} } + if ($feature eq 'StatusStateAttribute') + {return $value eq 'status' ? {"state"=>$EVENT,"cleanerState"=>$EVENT}: + $value eq 'detail' ? {"stateDetail"=>$EVENT}: + $value eq 'error' ? {"stateError"=>$EVENT}:{"$value"=>$EVENT} } + if ($feature eq 'WaterUsageControlCapability') + {return $value eq 'preset' ? {"waterUsage"=>$EVENT}:{"$value"=>$EVENT} } + if ($feature eq 'WifiConfigurationCapability') + { + # two possibilities to return more than one reading + # may be more than two for (split q{,}, $EVENT){} + my $ret = {"ip4"=>(split q{,},$EVENT)[0],"ip6"=>(split q{,},$EVENT)[1]}; + return $value eq 'ips' ? $ret :{"$value"=>$EVENT} } + #my %h; + #$h{"ip4"}=(split q{,},$EVENT)[0]; + #$h{"ip6"}=(split q{,},$EVENT)[1]; + #return $value eq 'ips' ? \%h :{"$value"=>$EVENT} } + #{return $value eq 'ips' ? {"ip4"=>(split q{,},$EVENT)[0]}:{"$value"=>$EVENT} } +} + +####### +# get some Information with Rest Api +sub valetudo_g { + my $NAME = shift; + my ($cmd,$load) = split q{ }, shift, 2; + #Log3(undef, 1, "Name $NAME, cmd $cmd, load $load"); + my $ip = (split q{ },$load)[1] || ReadingsVal($NAME,'ip4',(split q{_}, InternalVal($NAME,ReadingsVal($NAME,'IODev','').'_CONN','') )[1] || return 'error no ip'); + if ($load eq 'segments'){ + my $url = '/api/v2/robot/capabilities/MapSegmentationCapability'; + my $json = GetHttpFile($ip, $url); + my $decoded = decode_j($json); + my @array=@{$decoded}; + my %t; + for (@array) { $t{$_->{'id'}} = $_->{'name'} }; + my $ret = toJSON \%t; + fhem("setreading $NAME .segments $ret"); + return undef; + } + if ($load eq 'release'){ + my $url = '/api/v2/valetudo/version'; + my $json = GetHttpFile($ip, $url); + my $ret = decode_j($json)->{release}; + #Log3(undef, 1, "setreading $NAME valetudo_release $ret" ); + fhem("setreading $NAME valetudo_release $ret"); + return undef; + } + if ($load =~ m,^ip.*,){ fhem("setreading $NAME ip4 $ip") } + return undef; +} + +####### +# setup according supported features +sub valetudo_s { + my $NAME = shift; + if (valetudo_f($NAME,'MapSegmentation') ) { + CommandAttr_multiline($NAME,'setList',q( clean_segment:{'multiple-strict,'.valetudo_w($name,'segments')} { valetudo_c($NAME,$EVENT) }) ); + fhem("set $NAME get segments"); + } + if (valetudo_f($NAME,'GoToLocation') ) { + CommandAttr_multiline($NAME,'setList',q( goto:{valetudo_w($name,'locations')} { valetudo_c($NAME,$EVENT) }) ); + CommandAttr_multiline($NAME,'setList',q( locationNew:textField { valetudo_z($NAME,$EVENT) }) ); + CommandAttr_multiline($NAME,'setList',q( locationRename:textField { valetudo_z($NAME,$EVENT) }) ); + } + if (valetudo_f($NAME,'WaterUsageControl') ) { + CommandAttr_multiline($NAME,'setList',q( waterUsage:low,medium,high $DEVICETOPIC/WaterUsageControlCapability/preset/set $EVTPART1) ); + } +} + ####### # add a line to multiline Attribute setList or regList # CommandAttr_multiline( 'MQTT2_valetudo_xxx','setList',q( clean_segment:{"multiple-strict,".valetudo_w($name,"segments")} { valetudo_c($NAME,$EVENT) }) ) @@ -141,7 +292,6 @@ sub CommandAttr_multiline { =item summary generic MQTT2 vacuum valetudo Device =item summary_DE generische MQTT2 Staubsauger gerootet mit valetudo =begin html - Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.

MQTT2 valetudoUtils

@@ -149,6 +299,13 @@ Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo. subroutines
valetudo_w return a string for dynamic selection setList
valetudo_c return a complete string for setList right part of setList
+ valetudo_z handling .zones Reading
+ valetudo_f ask the robot via REST API for Featurelist and feature and return true false
+ valetudo_r used for readingList. return readingname -> value pairs
+ valetudo_g get some Information with Rest Api
+ valetudo_s doing model spezific setup
+ CommandAttr_multiline add a line to multiline Attribute setList or regList
+ CommandAttr_multiline( 'MQTT2_valetudo_xxx','setList',q( clean_segment:{"multiple-strict,".valetudo_w($name,"segments")} { valetudo_c($NAME,$EVENT) }) )

attr setList @@ -159,10 +316,8 @@ Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.
- =end html =begin html_DE - Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.

MQTT2 valetudoUtils

@@ -170,6 +325,13 @@ Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo. subroutines
valetudo_w return a string for dynamic selection setList
valetudo_c return a complete string for setList right part of setList
+ valetudo_z handling .zones Reading
+ valetudo_f ask the robot via REST API for Featurelist and feature and return true false
+ valetudo_r used for readingList. return readingname -> value pairs
+ valetudo_g get some Information with Rest Api
+ valetudo_s doing model spezific setup
+ CommandAttr_multiline add a line to multiline Attribute setList or regList
+ CommandAttr_multiline( 'MQTT2_valetudo_xxx','setList',q( clean_segment:{"multiple-strict,".valetudo_w($name,"segments")} { valetudo_c($NAME,$EVENT) }) )

attr setList @@ -180,7 +342,5 @@ Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.
- - =end html_DE =cut