From 4026a7f9196cae944da3e53e82e8a4590eafbd75 Mon Sep 17 00:00:00 2001
From: Otto123 <>
Date: Sat, 22 Jan 2022 21:12:58 +0000
Subject: [PATCH] mqtt2.template contrib/99_valetudoUtils.pm:new valetudo
Template version
git-svn-id: https://svn.fhem.de/fhem/trunk@25545 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/FHEM/lib/AttrTemplate/mqtt2.template | 60 ++++----
fhem/contrib/AttrTemplate/99_valetudoUtils.pm | 131 +++++++++++++++---
2 files changed, 144 insertions(+), 47 deletions(-)
diff --git a/fhem/FHEM/lib/AttrTemplate/mqtt2.template b/fhem/FHEM/lib/AttrTemplate/mqtt2.template
index 549ffd046..4669c6f2d 100644
--- a/fhem/FHEM/lib/AttrTemplate/mqtt2.template
+++ b/fhem/FHEM/lib/AttrTemplate/mqtt2.template
@@ -4567,52 +4567,58 @@ attr DEVICE event-on-change-reading .*
attr DEVICE model roborockRE
setreading DEVICE attrTemplateVersion 20210510
-# new valtudo Template contributed by Otto123, should replace the old valetudo/rockrobo Template
+# 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. For further details visit https://github.com/Hypfer/Valetudo
NOTE: Initial version
+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","readingList","") =~ m,(valetudo)[/].*:, ? $1 : undef }
-par:DEV_ID;DEV_ID is random by Firmware;{ AttrVal("DEVICE","readingList","") =~ m,valetudo[/]([^/]+)[/].*:, ? $1 : undef }
-{ Svn_GetFile("contrib/AttrTemplate/99_valetudoUtils.pm", "FHEM/99_valetudoUtils.pm", sub(){CommandReload(undef, "99_valetudoUtils")}) }
-deletereading -q DEVICE (?!associatedWith|IODev).*
+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 }
+{ $data{f}='99_valetudoUtils.pm';\
+$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
attr DEVICE devicetopic BASE_TOPIC/DEV_ID
attr DEVICE icon vacuum_top
attr DEVICE readingList \
$\DEVICETOPIC/\x24state:.* _state\
- $\DEVICETOPIC/AttachmentStateAttribute/dustbin:.* dustbin\
- $\DEVICETOPIC/AttachmentStateAttribute/.* { $TOPIC =~ m,.*\/(.*),; {"$1"=>$EVENT} }\
+ $\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/BasicControlCapability/operation:.* operation\
- $\DEVICETOPIC/CurrentStatisticsCapability/area:.* area\
- $\DEVICETOPIC/CurrentStatisticsCapability/time:.* time\
- $\DEVICETOPIC/ConsumableMonitoringCapability/.* { $TOPIC =~ m,.*\/(.*),; {"$1"=>$EVENT} }\
$\DEVICETOPIC/FanSpeedControlCapability/preset:.* fanSpeed\
- $\DEVICETOPIC/LocateCapability/locate:.* locate\
+ $\DEVICETOPIC/GoToLocationCapability/presets:.* .locationsPresets\
+ $\DEVICETOPIC/GoToLocationCapability/go:.* {}\
$\DEVICETOPIC/MapData/map-data:.* {}\
- $\DEVICETOPIC/MapData/segments:.* segments\
- $\DEVICETOPIC/StatusStateAttribute/status:.* state\
+ $\DEVICETOPIC/MapData/segments:.* .segments\
+ $\DEVICETOPIC/StatusStateAttribute/status:.* { {"state"=>$EVENT,"cleanerState"=>$EVENT} }\
$\DEVICETOPIC/StatusStateAttribute/detail:.* stateDetail\
$\DEVICETOPIC/StatusStateAttribute/error:.* stateError\
$\DEVICETOPIC/WaterUsageControlCapability/preset:.* waterUsage\
- $\DEVICETOPIC/WifiConfigurationCapability/.* { $TOPIC =~ m,.*\/(.*),; {"$1"=>$EVENT} }\
- $\DEVICETOPIC/ZoneCleaningCapability/presets:.* presets\
- $\DEVICETOPIC/ZoneCleaningCapability/start:.* start
+ $\DEVICETOPIC/ZoneCleaningCapability/presets:.* .zonesPresets\
+ $\DEVICETOPIC/ZoneCleaningCapability/start:.* {}
attr DEVICE setList \
- operation:PAUSE,START,STOP,HOME $\DEVICETOPIC/BasicControlCapability/operation/set $EVTPART1\
- clean_segment:{"multiple-strict,".valetudo_w($name,"segments")} { valetudo_c($NAME,$EVENT) }\
- clean_zone:{valetudo_w($name,"presets")} { 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\
+ 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\
locate:PERFORM $\DEVICETOPIC/LocateCapability/locate/set $EVTPART1\
x_raw_payload:textField { valetudo_c($NAME,$EVENT) }
-attr DEVICE userReadings ip4:ips:.* {(split ',',ReadingsVal($name,'ips','error'))[0]}
attr DEVICE event-on-change-reading .*
-attr DEVICE setStateList operation clean_segment clean_zone fanSpeed waterUsage x_raw_payload
-attr DEVICE model valetudo
-setreading DEVICE attrTemplateVersion 20220109 or prior
+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) }) ) }
+option:{1}
+deletereading -q DEVICE (?!associatedWith|IODev).*
# contributed by Otto123, source: https://forum.fhem.de/index.php/topic,94495.msg1062312.html#msg1062312
name:worx_landroid
diff --git a/fhem/contrib/AttrTemplate/99_valetudoUtils.pm b/fhem/contrib/AttrTemplate/99_valetudoUtils.pm
index 7b2d2bac2..044faec6a 100644
--- a/fhem/contrib/AttrTemplate/99_valetudoUtils.pm
+++ b/fhem/contrib/AttrTemplate/99_valetudoUtils.pm
@@ -8,7 +8,7 @@ package main;
use strict;
use warnings;
-use JSON;
+use JSON;Dumper;
sub
valetudoUtils_Initialize {
@@ -17,23 +17,25 @@ valetudoUtils_Initialize {
}
# Enter you functions below _this_ line.
-# valetudo_w return a string for dynamic selection setList
+#######
+# return a string for dynamic selection setList (widgets)
sub valetudo_w {
my $NAME = shift;
my $setter = shift;
# this part reads segments, it's only filled if Provide map data is enabled in connectivity
if ($setter eq 'segments') {
- my $json = ReadingsVal($NAME,'segments','select');
+ my $json = ReadingsVal($NAME,'.segments','{}');
+ if ($json eq '{}') {$json = '{"1":"no_Segment_or_not_supported"}'};
my $decoded = decode_json($json);
return join ',', sort values %$decoded
}
-# this part read preset which contains a full json for preset zones
-if ($setter eq 'presets') {
- my $json = ReadingsVal($NAME,'presets','select');
+# this part read presets which contains a full json for preset zones or locations
+if ($setter eq 'zones' or $setter eq 'locations') {
+ my $json = ReadingsVal($NAME,'.'.$setter.'Presets','');
my $decoded = decode_json($json);
- my %t;
- for (keys %$decoded) { $t{$decoded->{$_}->{'name'}} = $_ } # build a new hash only with zone id and zone name
- return join ',', sort keys %t
+ my @array;
+ for (keys %$decoded) { push @array, $decoded->{$_}->{'name'} }
+ return join',',@array
}
# 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))}
@@ -46,8 +48,8 @@ if ($setter eq 'json_segments') {
return join ',', sort keys %t
}
}
-##############################################################
-# valetudo_w return a complete string for setList right part
+#######
+# valetudo_c return a complete string for setList right part
sub valetudo_c {
my $NAME = shift;
my $EVENT = shift;
@@ -62,22 +64,28 @@ if ($cmd eq 'x_raw_payload') {$ret=$devicetopic.$load}
# 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','');
+ my $json = ReadingsVal($NAME,'.segments','');
my $decoded = decode_json($json);
- my %t = reverse %$decoded;
my @ids;
- for ( @rooms ) {push @ids, $t{$_}}
+ for ( @rooms ) { push @ids,{reverse %$decoded}->{$_} }
my %Hcmd = ( clean_segment => {segment_ids => \@ids,iterations => 1,customOrder => 'true' } );
$ret = $devicetopic.'/MapSegmentationCapability/clean/set '.toJSON $Hcmd{$cmd}
}
-# this part return the zone id according to the selected Name from presets (zones) (more complex json)
+# this part return the zone/location id according to the selected Name from presets (zones/locations) (more complex json)
if ($cmd eq 'clean_zone') {
- my $json = ReadingsVal($NAME,'presets','');
+ my $json = ReadingsVal($NAME,'.zonesPresets','');
my $decoded = decode_json($json);
- my %t;
- for (keys %$decoded) { $t{$decoded->{$_}->{'name'}} = $_ }
- $ret = $devicetopic.'/ZoneCleaningCapability/start/set '.$t{$load}
+ for (keys %$decoded) {
+ if ( $decoded->{$_}->{'name'} eq $load ) {$ret = $devicetopic.'/ZoneCleaningCapability/start/set '.$_ }
+ }
+ }
+if ($cmd eq 'goto') {
+ my $json = ReadingsVal($NAME,'.locationsPresets','');
+ my $decoded = decode_json($json);
+ for (keys %$decoded) {
+ if ( $decoded->{$_}->{'name'} eq $load ) {$ret = $devicetopic.'/GoToLocationCapability/go/set '.$_ }
+ }
}
# this part is for study purpose to read the full json segments with the REST API
@@ -95,9 +103,92 @@ if ($cmd eq 'clean_segment_j') {
my %Hcmd = ( clean_segment => {segment_ids => \@ids,iterations => 1,customOrder => 'true' } );
$ret = $devicetopic.'/MapSegmentationCapability/clean/set '.toJSON $Hcmd{$cmd}
}
-
return $ret
}
+#######
+# 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 $string = GetHttpFile($ip, '/api/v2/robot/capabilities');
+index($string, $substr) == -1 ? '0':'1';
+}
+#######
+# 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) }) )
+sub CommandAttr_multiline {
+my $NAME = shift;
+my $attr = shift;
+my $item = shift;
+if ($attr ne 'setList' and $attr ne 'readingList') {return 'use only for multiline attrib'}
+my $val = AttrVal($NAME,$attr,'')."\n".$item;
+CommandAttr(undef, "$NAME $attr $val");
+}
+
+################
+# is never used, was in a first version used to preread the json in valetudo_c
+# return simpel json pairs from presets format of valetudo
+sub valetudo_r {
+my $setter = shift;
+my $payload = shift;
+my $ret = 'error';
+my %t;
+if ($setter eq 'presets') {
+ my $decoded = decode_json($payload);
+ for (keys %$decoded) { $t{$decoded->{$_}->{'name'}} = $_ } # build a new hash only with names and ids pairs
+ $ret = toJSON(\%t); # result is sorted
+ }
+ return $ret
+}
+
+
+####### Aus dem Forum funktioniert aber nicht
+# Zeigt aber wie man Readings zurück gibt
+sub
+valetudo2svg($$$)
+{
+ my ($reading, $d, $filename) = @_;
+ my %ret;
+
+ if(!open FD,">$filename") {
+ $ret{$reading} = "ERROR: $filename: $!";
+ return \%ret;
+ }
+ print FD $d;
+ close(FD);
+ $ret{$reading} = "Wrote $filename";
+ return \%ret;
+
+ if($d !~ m/height":(\d+),"width":(\d+).*?floor":\[(.*\])\]/) {
+ $ret{$reading} = "ERROR: Unknown format";
+ return \%ret;
+ }
+ my ($w,$h,$nums) = ($1, $2, $3);
+
+ my $svg=<<"EOD";
+
+
+";
+
+ if(!open FD,">$filename") {
+ $ret{$reading} = "ERROR: $filename: $!";
+ return \%ret;
+ }
+ print FD $svg;
+ close(FD);
+ $ret{$reading} = "Wrote $filename";
+ return \%ret;
+}
1;
=pod