2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-11 08:34:18 +00:00

mqtt2.template,99_valetudoUtils.pm:minor add to worx_landroid,new valetudo template, for valetudo version gt 2022.05

git-svn-id: https://svn.fhem.de/fhem/trunk@26636 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Otto123 2022-11-01 13:09:36 +00:00
parent 267d40a50a
commit 6c0e8cff3e
2 changed files with 196 additions and 42 deletions

View File

@ -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 <a href="https://github.com/Hypfer/Valetudo">Hypfer/Valetudo</a><br><br>NOTE: tested with Dreame L10pro, Xiaomi Vacuum Gen1, details: <a href="https://forum.fhem.de/index.php/topic,121017.0.html">Forum Thread</a>
desc:use this to control a rooted vacuum with valetudo V2. For further details visit <a href="https://github.com/Hypfer/Valetudo">Hypfer/Valetudo</a><br>NOTE: tested with Dreame L10pro, Xiaomi Vacuum Gen1, details: <a href="https://forum.fhem.de/index.php/topic,121017.0.html">Forum Thread</a>
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 <a href="http://ip4" target="_blank">state</a>
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\
<a href="http://ip4" target="_blank">cleanerState</a>
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
###########################################

View File

@ -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.
<a id="MQTT2 valetudo"></a>
<h3>MQTT2 valetudoUtils</h3>
@ -149,6 +299,13 @@ Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.
subroutines<br>
<b>valetudo_w</b> return a string for dynamic selection setList<br>
<b>valetudo_c</b> return a complete string for setList right part of setList<br>
<b>valetudo_z</b> handling .zones Reading<br>
<b>valetudo_f</b> ask the robot via REST API for Featurelist and feature and return true false<br>
<b>valetudo_r</b> used for readingList. return readingname -> value pairs<br>
<b>valetudo_g</b> get some Information with Rest Api<br>
<b>valetudo_s</b> doing model spezific setup<br>
<b>CommandAttr_multiline</b> add a line to multiline Attribute setList or regList<br>
CommandAttr_multiline( 'MQTT2_valetudo_xxx','setList',q( clean_segment:{"multiple-strict,".valetudo_w($name,"segments")} { valetudo_c($NAME,$EVENT) }) )<br>
<br>
<a id="MQTT2_DEVICE-setList"></a>
<b>attr setList</b>
@ -159,10 +316,8 @@ Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.
</ul>
<br>
</ul>
=end html
=begin html_DE
Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.
<a id="MQTT2 valetudo"></a>
<h3>MQTT2 valetudoUtils</h3>
@ -170,6 +325,13 @@ Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.
subroutines<br>
<b>valetudo_w</b> return a string for dynamic selection setList<br>
<b>valetudo_c</b> return a complete string for setList right part of setList<br>
<b>valetudo_z</b> handling .zones Reading<br>
<b>valetudo_f</b> ask the robot via REST API for Featurelist and feature and return true false<br>
<b>valetudo_r</b> used for readingList. return readingname -> value pairs<br>
<b>valetudo_g</b> get some Information with Rest Api<br>
<b>valetudo_s</b> doing model spezific setup<br>
<b>CommandAttr_multiline</b> add a line to multiline Attribute setList or regList<br>
CommandAttr_multiline( 'MQTT2_valetudo_xxx','setList',q( clean_segment:{"multiple-strict,".valetudo_w($name,"segments")} { valetudo_c($NAME,$EVENT) }) )<br>
<br>
<a id="MQTT2_DEVICE-setList"></a>
<b>attr setList</b>
@ -180,7 +342,5 @@ Subroutines for generic MQTT2 vacuum cleaner Devices rooted with valetudo.
</ul>
<br>
</ul>
=end html_DE
=cut