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.