2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-05-04 08:03:40 +00:00

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
This commit is contained in:
Otto123 2022-01-22 21:12:58 +00:00
parent 2a19d496d1
commit 4026a7f919
2 changed files with 144 additions and 47 deletions

View File

@ -4567,52 +4567,58 @@ attr DEVICE event-on-change-reading .*
attr DEVICE model roborockRE attr DEVICE model roborockRE
setreading DEVICE attrTemplateVersion 20210510 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 name:valetudo
filter:TYPE=MQTT2_DEVICE filter:TYPE=MQTT2_DEVICE
desc:use this to control a rooted vacuum with valetudo. For further details visit https://github.com/Hypfer/Valetudo<br><br>NOTE: Initial version 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>
order:X_03b order:X_03b
par:BASE_TOPIC;BASE_TOPIC typically is valetudo;{ AttrVal("DEVICE","readingList","") =~ m,(valetudo)[/].*:, ? $1 : undef } 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","readingList","") =~ m,valetudo[/]([^/]+)[/].*:, ? $1 : undef } par:DEV_ID;DEV_ID is random by Firmware;{ AttrVal("DEVICE","devicetopic",AttrVal("DEVICE","readingList","")) =~ m,[^\/]+\/([^\/:]+).*, ? $1 : undef }
{ Svn_GetFile("contrib/AttrTemplate/99_valetudoUtils.pm", "FHEM/99_valetudoUtils.pm", sub(){CommandReload(undef, "99_valetudoUtils")}) } { $data{f}='99_valetudoUtils.pm';\
deletereading -q DEVICE (?!associatedWith|IODev).* $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 alias DEV_ID
attr DEVICE devicetopic BASE_TOPIC/DEV_ID attr DEVICE devicetopic BASE_TOPIC/DEV_ID
attr DEVICE icon vacuum_top attr DEVICE icon vacuum_top
attr DEVICE readingList \ attr DEVICE readingList \
$\DEVICETOPIC/\x24state:.* _state\ $\DEVICETOPIC/\x24state:.* _state\
$\DEVICETOPIC/AttachmentStateAttribute/dustbin:.* dustbin\ $\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/AttachmentStateAttribute/.* { $TOPIC =~ m,.*\/(.*),; {"$1"=>$EVENT} }\
$\DEVICETOPIC/BatteryStateAttribute/level:.* batteryPercent\ $\DEVICETOPIC/BatteryStateAttribute/level:.* batteryPercent\
$\DEVICETOPIC/BatteryStateAttribute/status:.* batteryState\ $\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/FanSpeedControlCapability/preset:.* fanSpeed\
$\DEVICETOPIC/LocateCapability/locate:.* locate\ $\DEVICETOPIC/GoToLocationCapability/presets:.* .locationsPresets\
$\DEVICETOPIC/GoToLocationCapability/go:.* {}\
$\DEVICETOPIC/MapData/map-data:.* {}\ $\DEVICETOPIC/MapData/map-data:.* {}\
$\DEVICETOPIC/MapData/segments:.* segments\ $\DEVICETOPIC/MapData/segments:.* .segments\
$\DEVICETOPIC/StatusStateAttribute/status:.* state\ $\DEVICETOPIC/StatusStateAttribute/status:.* { {"state"=>$EVENT,"cleanerState"=>$EVENT} }\
$\DEVICETOPIC/StatusStateAttribute/detail:.* stateDetail\ $\DEVICETOPIC/StatusStateAttribute/detail:.* stateDetail\
$\DEVICETOPIC/StatusStateAttribute/error:.* stateError\ $\DEVICETOPIC/StatusStateAttribute/error:.* stateError\
$\DEVICETOPIC/WaterUsageControlCapability/preset:.* waterUsage\ $\DEVICETOPIC/WaterUsageControlCapability/preset:.* waterUsage\
$\DEVICETOPIC/WifiConfigurationCapability/.* { $TOPIC =~ m,.*\/(.*),; {"$1"=>$EVENT} }\ $\DEVICETOPIC/ZoneCleaningCapability/presets:.* .zonesPresets\
$\DEVICETOPIC/ZoneCleaningCapability/presets:.* presets\ $\DEVICETOPIC/ZoneCleaningCapability/start:.* {}
$\DEVICETOPIC/ZoneCleaningCapability/start:.* start
attr DEVICE setList \ attr DEVICE setList \
operation:PAUSE,START,STOP,HOME $\DEVICETOPIC/BasicControlCapability/operation/set $EVTPART1\ pause:noArg $\DEVICETOPIC/BasicControlCapability/operation/set PAUSE\
clean_segment:{"multiple-strict,".valetudo_w($name,"segments")} { valetudo_c($NAME,$EVENT) }\ start:noArg $\DEVICETOPIC/BasicControlCapability/operation/set START\
clean_zone:{valetudo_w($name,"presets")} { valetudo_c($NAME,$EVENT) }\ stop:noArg $\DEVICETOPIC/BasicControlCapability/operation/set STOP\
fanSpeed:off,min,low,medium,high,turbo,max $\DEVICETOPIC/FanSpeedControlCapability/preset/set $EVTPART1\ charge:noArg $\DEVICETOPIC/BasicControlCapability/operation/set HOME\
waterUsage:off,min,low,medium,high,turbo,max $\DEVICETOPIC/WaterUsageControlCapability/preset/set $EVTPART1\ 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\ locate:PERFORM $\DEVICETOPIC/LocateCapability/locate/set $EVTPART1\
x_raw_payload:textField { valetudo_c($NAME,$EVENT) } 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 event-on-change-reading .*
attr DEVICE setStateList operation clean_segment clean_zone fanSpeed waterUsage x_raw_payload attr DEVICE timestamp-on-change-reading .*
attr DEVICE model valetudo attr DEVICE setStateList operation clean_segment clean_zone goto fanSpeed waterUsage locate x_raw_payload
setreading DEVICE attrTemplateVersion 20220109 or prior 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) }) ) }
option:{1}
deletereading -q DEVICE (?!associatedWith|IODev).*
# contributed by Otto123, source: https://forum.fhem.de/index.php/topic,94495.msg1062312.html#msg1062312 # contributed by Otto123, source: https://forum.fhem.de/index.php/topic,94495.msg1062312.html#msg1062312
name:worx_landroid name:worx_landroid

View File

@ -8,7 +8,7 @@ package main;
use strict; use strict;
use warnings; use warnings;
use JSON; use JSON;Dumper;
sub sub
valetudoUtils_Initialize { valetudoUtils_Initialize {
@ -17,23 +17,25 @@ valetudoUtils_Initialize {
} }
# Enter you functions below _this_ line. # 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 { sub valetudo_w {
my $NAME = shift; my $NAME = shift;
my $setter = shift; my $setter = shift;
# this part reads segments, it's only filled if Provide map data is enabled in connectivity # this part reads segments, it's only filled if Provide map data is enabled in connectivity
if ($setter eq 'segments') { 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); my $decoded = decode_json($json);
return join ',', sort values %$decoded return join ',', sort values %$decoded
} }
# this part read preset which contains a full json for preset zones # this part read presets which contains a full json for preset zones or locations
if ($setter eq 'presets') { if ($setter eq 'zones' or $setter eq 'locations') {
my $json = ReadingsVal($NAME,'presets','select'); my $json = ReadingsVal($NAME,'.'.$setter.'Presets','');
my $decoded = decode_json($json); my $decoded = decode_json($json);
my %t; my @array;
for (keys %$decoded) { $t{$decoded->{$_}->{'name'}} = $_ } # build a new hash only with zone id and zone name for (keys %$decoded) { push @array, $decoded->{$_}->{'name'} }
return join ',', sort keys %t return join',',@array
} }
# this part is for study purpose to read the full json segments with the REST API like # 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))} # 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 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 { sub valetudo_c {
my $NAME = shift; my $NAME = shift;
my $EVENT = 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) # this part return an array of segment id's according to selected Names from segments (simple json)
if ($cmd eq 'clean_segment') { if ($cmd eq 'clean_segment') {
my @rooms = split',', $load; my @rooms = split',', $load;
my $json = ReadingsVal($NAME,'segments',''); my $json = ReadingsVal($NAME,'.segments','');
my $decoded = decode_json($json); my $decoded = decode_json($json);
my %t = reverse %$decoded;
my @ids; my @ids;
for ( @rooms ) {push @ids, $t{$_}} for ( @rooms ) { push @ids,{reverse %$decoded}->{$_} }
my %Hcmd = ( clean_segment => {segment_ids => \@ids,iterations => 1,customOrder => 'true' } ); my %Hcmd = ( clean_segment => {segment_ids => \@ids,iterations => 1,customOrder => 'true' } );
$ret = $devicetopic.'/MapSegmentationCapability/clean/set '.toJSON $Hcmd{$cmd} $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') { if ($cmd eq 'clean_zone') {
my $json = ReadingsVal($NAME,'presets',''); my $json = ReadingsVal($NAME,'.zonesPresets','');
my $decoded = decode_json($json); my $decoded = decode_json($json);
my %t; for (keys %$decoded) {
for (keys %$decoded) { $t{$decoded->{$_}->{'name'}} = $_ } if ( $decoded->{$_}->{'name'} eq $load ) {$ret = $devicetopic.'/ZoneCleaningCapability/start/set '.$_ }
$ret = $devicetopic.'/ZoneCleaningCapability/start/set '.$t{$load} }
}
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 # 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' } ); my %Hcmd = ( clean_segment => {segment_ids => \@ids,iterations => 1,customOrder => 'true' } );
$ret = $devicetopic.'/MapSegmentationCapability/clean/set '.toJSON $Hcmd{$cmd} $ret = $devicetopic.'/MapSegmentationCapability/clean/set '.toJSON $Hcmd{$cmd}
} }
return $ret 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";
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="$w" height="$h" viewBox="0 0 $w $h">
<g fill="#000000" stroke="none">
<rect x="0" y="0" width="$w" height="$h" stroke="black" stroke-width="1" fill="none"/>
EOD
$nums =~ s/\[(\d+),(\d+)\]/
$svg .= "<rect x=\"$1\" y=\"$2\" width=\"1\" height=\"1\"\/>\n";
""
/xge;
$svg .= "</g></svg>";
if(!open FD,">$filename") {
$ret{$reading} = "ERROR: $filename: $!";
return \%ret;
}
print FD $svg;
close(FD);
$ret{$reading} = "Wrote $filename";
return \%ret;
}
1; 1;
=pod =pod