2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-10 09:16:53 +00:00

72_XiaomiDevice: better handling of definition w/ missing token, 32_withings: add in_bed for sleep trackers, ignore inactive users, 38_netatmo: statistics

git-svn-id: https://svn.fhem.de/fhem/trunk@17431 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
moises 2018-09-29 20:49:21 +00:00
parent fada3b2d5d
commit 9a0a282940
4 changed files with 200 additions and 104 deletions

View File

@ -1,5 +1,7 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it. # Do not insert empty lines here, update check depends on it.
- bugfix: 72_XiaomiDevice: better handling of definition w/ missing token
- change: 32_withings: add in_bed for sleep trackers, ignore inactive users
- change: 57_Calendar: new attribute quirks with ignoreDtStamp value. - change: 57_Calendar: new attribute quirks with ignoreDtStamp value.
- change: 57_Calendar: cutoffOlderThan also removes recurring events when - change: 57_Calendar: cutoffOlderThan also removes recurring events when
the series has ended. the series has ended.

View File

@ -10,7 +10,7 @@
# #
# #
############################################################################## ##############################################################################
# Release 07 / 2018-09-18 # Release 08 / 2018-09-28
package main; package main;
@ -341,14 +341,18 @@ sub withings_Define($$) {
CommandAttr(undef,"$name IODev $a[4]"); CommandAttr(undef,"$name IODev $a[4]");
} elsif( @a == 4 && $a[2] =~ m/^\d+$/ && $a[3] =~ m/^[\w-]+$/i ) { } elsif( @a == 4 && $a[2] =~ m/^\d+$/ && $a[3] =~ m/^[\w:-]+$/i ) {
$subtype = "USER"; $subtype = "USER";
my $user = $a[2]; my $user = $a[2];
my $key = $a[3]; my $key = $a[3];
my $accesskey = withings_encrypt($key);
Log3 $name, 3, "$name: encrypt $key to $accesskey" if($key ne $accesskey);
$hash->{DEF} = "$user $accesskey";
$hash->{User} = $user; $hash->{User} = $user;
$hash->{Key} = $key; #$hash->{Key} = $accesskey; #not needed
my $d = $modules{$hash->{TYPE}}{defptr}{"U$user"}; my $d = $modules{$hash->{TYPE}}{defptr}{"U$user"};
return "device $user already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); return "device $user already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
@ -380,7 +384,7 @@ sub withings_Define($$) {
} }
$hash->{NAME} = $name; $hash->{NAME} = $name;
$hash->{SUBTYPE} = $subtype; $hash->{SUBTYPE} = $subtype if(defined($subtype));
#CommandAttr(undef,"$name DbLogExclude .*"); #CommandAttr(undef,"$name DbLogExclude .*");
@ -527,7 +531,7 @@ sub withings_getSessionKey($) {
if(!defined($resolve)) if(!defined($resolve))
{ {
$hash->{SessionTimestamp} = 0; $hash->{SessionTimestamp} = 0;
Log3 "withings", 1, "$name: DNS error on getSessionData"; Log3 $name, 1, "$name: DNS error on getSessionData";
return undef; return undef;
} }
@ -543,18 +547,18 @@ sub withings_getSessionKey($) {
# }); # });
# if($err0 || !defined($data0)) # if($err0 || !defined($data0))
# { # {
# Log3 "withings", 1, "$name: appliver call failed! ".$err0; # Log3 $name, 1, "$name: appliver call failed! ".$err0;
# return undef; # return undef;
# } # }
# $data1 = $data0; # $data1 = $data0;
# $data0 =~ /appliver=([^.*]+)\&/; # $data0 =~ /appliver=([^.*]+)\&/;
# $hash->{helper}{appliver} = $1; # $hash->{helper}{appliver} = $1;
# if(!defined($hash->{helper}{appliver})) { # if(!defined($hash->{helper}{appliver})) {
# Log3 "withings", 1, "$name: APPLIVER ERROR "; # Log3 $name, 1, "$name: APPLIVER ERROR ";
# $hash->{STATE} = "APPLIVER error"; # $hash->{STATE} = "APPLIVER error";
# return undef; # return undef;
# } # }
# Log3 "withings", 4, "$name: appliver ".$hash->{helper}{appliver}; # Log3 $name, 4, "$name: appliver ".$hash->{helper}{appliver};
# #} # #}
# #
# #
@ -564,11 +568,11 @@ sub withings_getSessionKey($) {
# $hash->{helper}{csrf_token} = $1; # $hash->{helper}{csrf_token} = $1;
# #
# if(!defined($hash->{helper}{csrf_token})) { # if(!defined($hash->{helper}{csrf_token})) {
# Log3 "withings", 1, "$name: CSRF ERROR "; # Log3 $name, 1, "$name: CSRF ERROR ";
# $hash->{STATE} = "CSRF error"; # $hash->{STATE} = "CSRF error";
# return undef; # return undef;
# } # }
# Log3 "withings", 4, "$name: csrf_token ".$hash->{helper}{csrf_token}; # Log3 $name, 4, "$name: csrf_token ".$hash->{helper}{csrf_token};
#} #}
#my $ua = LWP::UserAgent->new; #my $ua = LWP::UserAgent->new;
@ -580,7 +584,7 @@ sub withings_getSessionKey($) {
# $resolve = inet_aton("account.withings.com"); # $resolve = inet_aton("account.withings.com");
# if(!defined($resolve)) # if(!defined($resolve))
# { # {
# Log3 "withings", 1, "$name: DNS error on getSessionKey."; # Log3 $name, 1, "$name: DNS error on getSessionKey.";
# return undef; # return undef;
# } # }
@ -596,7 +600,7 @@ sub withings_getSessionKey($) {
if ($err || !defined($data) || $data =~ /Authentification failed/ || $data =~ /not a valid/) if ($err || !defined($data) || $data =~ /Authentification failed/ || $data =~ /not a valid/)
{ {
Log3 "withings", 1, "$name: LOGIN ERROR "; Log3 $name, 1, "$name: LOGIN ERROR ";
$hash->{STATE} = "Login error"; $hash->{STATE} = "Login error";
return undef; return undef;
} }
@ -608,12 +612,12 @@ sub withings_getSessionKey($) {
$hash->{SessionTimestamp} = (gettimeofday())[0] if( $hash->{SessionKey} ); $hash->{SessionTimestamp} = (gettimeofday())[0] if( $hash->{SessionKey} );
$hash->{STATE} = "Connected" if( $hash->{SessionKey} ); $hash->{STATE} = "Connected" if( $hash->{SessionKey} );
$hash->{STATE} = "Session error" if( !$hash->{SessionKey} ); $hash->{STATE} = "Session error" if( !$hash->{SessionKey} );
Log3 "withings", 4, "$name: sessionkey ".$hash->{SessionKey}; Log3 $name, 4, "$name: sessionkey ".$hash->{SessionKey};
} }
else else
{ {
$hash->{STATE} = "Cookie error"; $hash->{STATE} = "Cookie error";
Log3 "withings", 1, "$name: COOKIE ERROR "; Log3 $name, 1, "$name: COOKIE ERROR ";
$hash->{helper}{appliver} = '9855c478'; $hash->{helper}{appliver} = '9855c478';
$hash->{helper}{csrf_token} = '9855c478'; $hash->{helper}{csrf_token} = '9855c478';
return undef; return undef;
@ -648,15 +652,15 @@ sub withings_getSessionKey($) {
} }
else else
{ {
Log3 "withings", 4, "$name: account email: ".$account->{email}; Log3 $name, 4, "$name: account email: ".$account->{email};
} }
} }
Log3 "withings", 4, "$name: accountid ".$hash->{AccountID}; Log3 $name, 4, "$name: accountid ".$hash->{AccountID};
} }
else else
{ {
$hash->{STATE} = "Account error"; $hash->{STATE} = "Account error";
Log3 "withings", 1, "$name: ACCOUNT ERROR "; Log3 $name, 1, "$name: ACCOUNT ERROR ";
return undef; return undef;
} }
} }
@ -667,16 +671,15 @@ sub withings_getSessionKey($) {
sub withings_connect($) { sub withings_connect($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: connect"; Log3 $name, 5, "$name: connect";
$hash->{'.https'} = "https"; $hash->{'.https'} = "https";
$hash->{'.https'} = "http" if( AttrVal($name, "nossl", 0) ); $hash->{'.https'} = "http" if( AttrVal($name, "nossl", 0) );
#my $cmdret= CommandAttr(undef,"$name room WithingsTest");
#$cmdret= CommandAttr(undef,"$name verbose 5");
withings_getSessionKey( $hash ); withings_getSessionKey( $hash );
return undef; #no more autocreate on start
foreach my $d (keys %defs) { foreach my $d (keys %defs) {
next if(!defined($defs{$d})); next if(!defined($defs{$d}));
next if($defs{$d}{TYPE} ne "autocreate"); next if($defs{$d}{TYPE} ne "autocreate");
@ -691,11 +694,12 @@ sub withings_connect($) {
Log3 $name, 2, "$name: user '$user->{id}' already defined"; Log3 $name, 2, "$name: user '$user->{id}' already defined";
next; next;
} }
next if($user->{firstname} eq "Repository-User"); next if($user->{usertype} ne "1" || $user->{status} ne "0");
my $id = $user->{id}; my $id = $user->{id};
my $devname = "withings_U". $id; my $devname = "withings_U". $id;
my $define= "$devname withings $id $user->{publickey}"; my $publickey = withings_encrypt($user->{publickey});
my $define= "$devname withings $id $publickey";
Log3 $name, 2, "$name: create new device '$devname' for user '$id'"; Log3 $name, 2, "$name: create new device '$devname' for user '$id'";
@ -761,7 +765,7 @@ sub withings_connect($) {
sub withings_autocreate($) { sub withings_autocreate($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: autocreate"; Log3 $name, 5, "$name: autocreate";
$hash->{'.https'} = "https"; $hash->{'.https'} = "https";
$hash->{'.https'} = "http" if( AttrVal($name, "nossl", 0) ); $hash->{'.https'} = "http" if( AttrVal($name, "nossl", 0) );
@ -777,11 +781,12 @@ sub withings_autocreate($) {
Log3 $name, 2, "$name: user '$user->{id}' already defined"; Log3 $name, 2, "$name: user '$user->{id}' already defined";
next; next;
} }
next if($user->{firstname} eq "Repository-User"); next if($user->{usertype} ne "1" || $user->{status} ne "0");
my $id = $user->{id}; my $id = $user->{id};
my $devname = "withings_U". $id; my $devname = "withings_U". $id;
my $define= "$devname withings $id $user->{publickey}"; my $publickey = withings_encrypt($user->{publickey});
my $define= "$devname withings $id $publickey";
Log3 $name, 2, "$name: create new device '$devname' for user '$id'"; Log3 $name, 2, "$name: create new device '$devname' for user '$id'";
@ -844,7 +849,7 @@ sub withings_autocreate($) {
sub withings_initDevice($) { sub withings_initDevice($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: initdevice ".$hash->{Device}; Log3 $name, 5, "$name: initdevice ".$hash->{Device};
AssignIoPort($hash); AssignIoPort($hash);
if(defined($hash->{IODev}->{NAME})) { if(defined($hash->{IODev}->{NAME})) {
@ -904,7 +909,7 @@ sub withings_initDevice($) {
sub withings_initUser($) { sub withings_initUser($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: inituser ".$hash->{User}; Log3 $name, 5, "$name: inituser ".$hash->{User};
AssignIoPort($hash); AssignIoPort($hash);
if(defined($hash->{IODev}->{NAME})) { if(defined($hash->{IODev}->{NAME})) {
@ -936,7 +941,7 @@ sub withings_initUser($) {
sub withings_getUsers($) { sub withings_getUsers($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getusers"; Log3 $name, 5, "$name: getusers";
withings_getSessionKey($hash); withings_getSessionKey($hash);
@ -977,7 +982,7 @@ sub withings_getUsers($) {
sub withings_getDevices($) { sub withings_getDevices($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getdevices"; Log3 $name, 5, "$name: getdevices";
withings_getSessionKey($hash); withings_getSessionKey($hash);
@ -1002,7 +1007,7 @@ sub withings_getDevices($) {
return undef; return undef;
} }
Log3 $name, 1, "withings: getDevices json error ".$json->{error} if(defined($json->{error})); Log3 $name, 1, "withings: getDevices json error ".$json->{error} if(defined($json->{error}));
Log3 "withings", 5, "$name: getdevices ".Dumper($json); Log3 $name, 5, "$name: getdevices ".Dumper($json);
my @devices = (); my @devices = ();
foreach my $association (@{$json->{body}{associations}}) { foreach my $association (@{$json->{body}{associations}}) {
@ -1018,7 +1023,7 @@ sub withings_getDevices($) {
sub withings_getDeviceDetail($) { sub withings_getDeviceDetail($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getdevicedetail ".$hash->{Device}; Log3 $name, 5, "$name: getdevicedetail ".$hash->{Device};
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
withings_getSessionKey( $hash->{IODev} ); withings_getSessionKey( $hash->{IODev} );
@ -1030,7 +1035,7 @@ sub withings_getDeviceDetail($) {
data => {sessionid => $hash->{IODev}->{SessionKey}, deviceid => $hash->{Device} , appname => 'my2', appliver=> $hash->{IODev}->{helper}{appliver}, apppfm => 'web', action => 'getproperties'}, data => {sessionid => $hash->{IODev}->{SessionKey}, deviceid => $hash->{Device} , appname => 'my2', appliver=> $hash->{IODev}->{helper}{appliver}, apppfm => 'web', action => 'getproperties'},
}); });
#Log3 "withings", 5, "$name: getdevicedetaildata ".Dumper($data); #Log3 $name, 5, "$name: getdevicedetaildata ".Dumper($data);
return undef if(!defined($data)); return undef if(!defined($data));
my $json = eval { JSON->new->utf8(0)->decode($data) }; my $json = eval { JSON->new->utf8(0)->decode($data) };
@ -1066,7 +1071,7 @@ sub withings_getDeviceDetail($) {
sub withings_getDeviceLink($) { sub withings_getDeviceLink($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getdevicelink ".$hash->{Device}; Log3 $name, 5, "$name: getdevicelink ".$hash->{Device};
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
withings_getSessionKey( $hash->{IODev} ); withings_getSessionKey( $hash->{IODev} );
@ -1110,7 +1115,7 @@ sub withings_getDeviceProperties($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getdeviceproperties ".$hash->{Device}; Log3 $name, 5, "$name: getdeviceproperties ".$hash->{Device};
return undef if( !defined($hash->{Device}) ); return undef if( !defined($hash->{Device}) );
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
@ -1140,7 +1145,7 @@ sub withings_getDeviceProperties($) {
sub withings_getDeviceReadingsScale($) { sub withings_getDeviceReadingsScale($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getscalereadings ".$hash->{Device}; Log3 $name, 5, "$name: getscalereadings ".$hash->{Device};
return undef if( !defined($hash->{Device}) ); return undef if( !defined($hash->{Device}) );
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
@ -1175,7 +1180,7 @@ sub withings_getDeviceReadingsScale($) {
sub withings_getDeviceReadingsBedside($) { sub withings_getDeviceReadingsBedside($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getaurareadings ".$hash->{Device}; Log3 $name, 5, "$name: getaurareadings ".$hash->{Device};
return undef if( !defined($hash->{Device}) ); return undef if( !defined($hash->{Device}) );
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
@ -1210,7 +1215,7 @@ sub withings_getDeviceReadingsBedside($) {
sub withings_getDeviceReadingsHome($) { sub withings_getDeviceReadingsHome($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: gethomereadings ".$hash->{Device}; Log3 $name, 5, "$name: gethomereadings ".$hash->{Device};
return undef if( !defined($hash->{Device}) ); return undef if( !defined($hash->{Device}) );
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
@ -1245,7 +1250,7 @@ sub withings_getDeviceReadingsHome($) {
sub withings_getDeviceEventsBaby($) { sub withings_getDeviceEventsBaby($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getbabyevents ".$hash->{Device}; Log3 $name, 5, "$name: getbabyevents ".$hash->{Device};
return undef if( !defined($hash->{Device}) ); return undef if( !defined($hash->{Device}) );
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
@ -1280,7 +1285,7 @@ sub withings_getDeviceEventsBaby($) {
sub withings_getDeviceAlertsHome($) { sub withings_getDeviceAlertsHome($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: gethomealerts ".$hash->{Device}; Log3 $name, 5, "$name: gethomealerts ".$hash->{Device};
return undef if( !defined($hash->{Device}) ); return undef if( !defined($hash->{Device}) );
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
@ -1313,7 +1318,7 @@ sub withings_getDeviceAlertsHome($) {
sub withings_getDeviceAlertsBaby($) { sub withings_getDeviceAlertsBaby($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getbabyevents ".$hash->{Device}; Log3 $name, 5, "$name: getbabyevents ".$hash->{Device};
return undef if( !defined($hash->{Device}) ); return undef if( !defined($hash->{Device}) );
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
@ -1345,7 +1350,7 @@ sub withings_getDeviceAlertsBaby($) {
sub withings_getVideoLink($) { sub withings_getVideoLink($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getbabyvideo ".$hash->{Device}; Log3 $name, 5, "$name: getbabyvideo ".$hash->{Device};
return undef if( !defined($hash->{Device}) ); return undef if( !defined($hash->{Device}) );
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
@ -1384,7 +1389,7 @@ sub withings_getS3Credentials($) {
return undef if( $hash->{sts_expiretime} && $hash->{sts_expiretime} > time - 3600 ); # min 1h return undef if( $hash->{sts_expiretime} && $hash->{sts_expiretime} > time - 3600 ); # min 1h
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
Log3 "withings", 5, "$name: gets3credentials ".$hash->{Device}; Log3 $name, 5, "$name: gets3credentials ".$hash->{Device};
withings_getSessionKey( $hash->{IODev} ); withings_getSessionKey( $hash->{IODev} );
@ -1442,7 +1447,7 @@ sub withings_signS3Link($$$;$) {
sub withings_getUserDetail($) { sub withings_getUserDetail($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getuserdetails ".$hash->{User}; Log3 $name, 5, "$name: getuserdetails ".$hash->{User};
return undef if( !defined($hash->{User}) ); return undef if( !defined($hash->{User}) );
return undef if( $hash->{SUBTYPE} ne "USER" ); return undef if( $hash->{SUBTYPE} ne "USER" );
@ -1495,14 +1500,14 @@ sub withings_poll($;$) {
if( $hash->{SUBTYPE} eq "DEVICE" ) { if( $hash->{SUBTYPE} eq "DEVICE" ) {
my $intervalData = AttrVal($name,"intervalData",900); my $intervalData = AttrVal($name,"intervalData",900);
my $intervalDebug = AttrVal($name,"intervalDebug",900); my $intervalDebug = AttrVal($name,"intervalDebug",AttrVal($name,"intervalData",900));
my $intervalProperties = AttrVal($name,"intervalProperties",AttrVal($name,"intervalData",900));
my $lastData = ReadingsVal( $name, ".pollData", 0 ); my $lastData = ReadingsVal( $name, ".pollData", 0 );
my $lastDebug = ReadingsVal( $name, ".pollDebug", 0 ); my $lastDebug = ReadingsVal( $name, ".pollDebug", 0 );
my $intervalProperties = AttrVal($name,"intervalProperties",43200);
my $lastProperties = ReadingsVal( $name, ".pollProperties", 0 ); my $lastProperties = ReadingsVal( $name, ".pollProperties", 0 );
if(defined($hash->{modelID}) && $hash->{modelID} eq '4') { if(defined($hash->{modelID}) && $hash->{modelID} eq '4') {
withings_getDeviceProperties($hash) if($force > 1 || $lastData <= ($now - $intervalData)); withings_getDeviceProperties($hash) if($force > 1 || $lastProperties <= ($now - $intervalProperties));
withings_getDeviceReadingsScale($hash) if($force || $lastData <= ($now - $intervalData)); withings_getDeviceReadingsScale($hash) if($force || $lastData <= ($now - $intervalData));
} }
elsif(defined($hash->{modelID}) && $hash->{modelID} eq '21') { elsif(defined($hash->{modelID}) && $hash->{modelID} eq '21') {
@ -1563,7 +1568,7 @@ sub withings_poll($;$) {
sub withings_getUserReadingsDaily($) { sub withings_getUserReadingsDaily($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getuserdailystats ".$hash->{User}; Log3 $name, 5, "$name: getuserdailystats ".$hash->{User};
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
withings_getSessionKey( $hash->{IODev} ); withings_getSessionKey( $hash->{IODev} );
@ -1630,7 +1635,7 @@ sub withings_getUserReadingsDaily($) {
sub withings_getUserReadingsCommon($) { sub withings_getUserReadingsCommon($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getuserreadings ".$hash->{User}; Log3 $name, 5, "$name: getuserreadings ".$hash->{User};
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
withings_getSessionKey( $hash->{IODev} ); withings_getSessionKey( $hash->{IODev} );
@ -1666,7 +1671,7 @@ sub withings_getUserReadingsCommon($) {
sub withings_getUserReadingsSleep($) { sub withings_getUserReadingsSleep($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getsleepreadings ".$hash->{User}; Log3 $name, 5, "$name: getsleepreadings ".$hash->{User};
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
withings_getSessionKey( $hash->{IODev} ); withings_getSessionKey( $hash->{IODev} );
@ -1701,7 +1706,7 @@ sub withings_getUserReadingsSleep($) {
sub withings_getUserReadingsSleepDebug($) { sub withings_getUserReadingsSleepDebug($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getsleepreadingsdebug ".$hash->{User}; Log3 $name, 5, "$name: getsleepreadingsdebug ".$hash->{User};
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
withings_getSessionKey( $hash->{IODev} ); withings_getSessionKey( $hash->{IODev} );
@ -1737,7 +1742,7 @@ sub withings_getUserReadingsActivity($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: getactivityreadings ".$hash->{User}; Log3 $name, 5, "$name: getactivityreadings ".$hash->{User};
return undef if( !defined($hash->{IODev}) ); return undef if( !defined($hash->{IODev}) );
withings_getSessionKey( $hash->{IODev} ); withings_getSessionKey( $hash->{IODev} );
@ -1748,7 +1753,7 @@ sub withings_getUserReadingsActivity($) {
my $enddate = ($lastupdate+(8*60*60)); my $enddate = ($lastupdate+(8*60*60));
$enddate = $now if ($enddate > $now); $enddate = $now if ($enddate > $now);
Log3 "withings", 5, "$name: getactivityreadings ".$lastupdate." to ".$enddate; Log3 $name, 5, "$name: getactivityreadings ".$lastupdate." to ".$enddate;
HttpUtils_NonblockingGet({ HttpUtils_NonblockingGet({
url => "https://scalews.withings.com/cgi-bin/v2/measure", url => "https://scalews.withings.com/cgi-bin/v2/measure",
@ -1776,7 +1781,7 @@ sub withings_parseProperties($$) {
my ($hash,$json) = @_; my ($hash,$json) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 "withings", 5, "$name: parsedevice"; Log3 $name, 5, "$name: parsedevice";
#parse #parse
my $detail = $json->{body}; my $detail = $json->{body};
@ -1799,7 +1804,7 @@ sub withings_parseMeasureGroups($$) {
my ($hash, $json) = @_; my ($hash, $json) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#parse #parse
Log3 "withings", 5, "$name: parsemeasuregroups"; Log3 $name, 5, "$name: parsemeasuregroups";
my ($now) = int(time); my ($now) = int(time);
my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) ); my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) );
my $newlastupdate = $lastupdate; my $newlastupdate = $lastupdate;
@ -1858,7 +1863,7 @@ sub withings_parseMeasureGroups($$) {
delete $hash->{CHANGETIME}; delete $hash->{CHANGETIME};
Log3 $name, 3, "$name: got ".$i.' entries from MeasureGroups (latest: '.FmtDateTime($newlastupdate).')'; Log3 $name, (($i>0)?3:4), "$name: got ".$i.' entries from MeasureGroups (latest: '.FmtDateTime($newlastupdate).')';
} }
@ -1868,7 +1873,7 @@ sub withings_parseMeasurements($$) {
my ($hash, $json) = @_; my ($hash, $json) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#parse #parse
Log3 "withings", 4, "$name: parsemeasurements"; Log3 $name, 4, "$name: parsemeasurements";
my ($now) = time; my ($now) = time;
my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) ); my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) );
my $newlastupdate = $lastupdate; my $newlastupdate = $lastupdate;
@ -1940,7 +1945,7 @@ sub withings_parseMeasurements($$) {
delete $hash->{CHANGETIME}; delete $hash->{CHANGETIME};
Log3 $name, 3, "$name: got ".$i.' entries from Measurements (latest: '.FmtDateTime($newlastupdate).')'; Log3 $name, (($i>0)?3:4), "$name: got ".$i.' entries from Measurements (latest: '.FmtDateTime($newlastupdate).')';
} }
@ -1956,7 +1961,7 @@ sub withings_parseAggregate($$) {
my ($hash, $json) = @_; my ($hash, $json) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#parse #parse
Log3 "withings", 5, "$name: parseaggregate"; Log3 $name, 5, "$name: parseaggregate";
#return undef; #return undef;
my ($now) = time; my ($now) = time;
@ -2056,7 +2061,7 @@ sub withings_parseAggregate($$) {
delete $hash->{CHANGETIME}; delete $hash->{CHANGETIME};
Log3 $name, 4, "$name: got ".$i.' entries from Aggregate (latest: '.FmtDateTime($newlastupdate).')'; Log3 $name, (($i>0)?3:4), "$name: got ".$i.' entries from Aggregate (latest: '.FmtDateTime($newlastupdate).')';
} }
@ -2071,7 +2076,7 @@ sub withings_parseActivity($$) {
my ($hash, $json) = @_; my ($hash, $json) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#parse #parse
Log3 "withings", 5, "$name: parseactivity"; Log3 $name, 5, "$name: parseactivity";
my ($now) = time; my ($now) = time;
my $lastupdate = ReadingsVal( $name, ".lastActivity", ($now-21*24*60*60) ); my $lastupdate = ReadingsVal( $name, ".lastActivity", ($now-21*24*60*60) );
@ -2152,7 +2157,7 @@ sub withings_parseActivity($$) {
delete $hash->{CHANGETIME}; delete $hash->{CHANGETIME};
Log3 $name, 4, "$name: got ".$i.' entries from Activity (latest: '.FmtDateTime($newlastupdate).')'; Log3 $name, (($i>0)?3:4), "$name: got ".$i.' entries from Activity (latest: '.FmtDateTime($newlastupdate).')';
} }
@ -2167,7 +2172,7 @@ sub withings_parseWorkouts($$) {
my ($hash, $json) = @_; my ($hash, $json) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#parse #parse
Log3 "withings", 1, "$name: parseworkouts\n".Dumper($json); Log3 $name, 1, "$name: parseworkouts\n".Dumper($json);
return undef; return undef;
@ -2180,7 +2185,7 @@ sub withings_parseVasistas($$;$) {
my ($hash, $json, $datatype) = @_; my ($hash, $json, $datatype) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#parse #parse
Log3 "withings", 5, "$name: parsevasistas"; Log3 $name, 5, "$name: parsevasistas";
my ($now) = time; my ($now) = time;
my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) ); my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) );
@ -2196,7 +2201,7 @@ sub withings_parseVasistas($$;$) {
my $readingsdate; my $readingsdate;
my $newlastupdate = $lastupdate; my $newlastupdate = $lastupdate;
my $iscurrent = 0;
foreach my $series ( @{$json->{body}{series}}) { foreach my $series ( @{$json->{body}{series}}) {
$j=0; $j=0;
@ -2252,7 +2257,19 @@ sub withings_parseVasistas($$;$) {
$newlastupdate = $readingsdate if($readingsdate > $newlastupdate); $newlastupdate = $readingsdate if($readingsdate > $newlastupdate);
$i++; $i++;
} }
#start in-bed detection
if($iscurrent == 0 && $datatype =~ /Sleep/){
if($i>40 && $readingsdate > time()-3600){
$iscurrent = 1;
#Log3 $name, 1, "$name: in-bed: ".FmtDateTime($readingsdate) if($i>0);
readingsBeginUpdate($hash);
$hash->{".updateTimestamp"} = FmtDateTime($readingsdate);
readingsBulkUpdate( $hash, "in_bed", 1, 1 );
$hash->{CHANGETIME}[0] = FmtDateTime($readingsdate);
readingsEndUpdate($hash,1);
}
}
#end in-bed detection
} }
} }
} }
@ -2263,6 +2280,15 @@ sub withings_parseVasistas($$;$) {
$newlastupdate = $device->{lastsessiondate} if($device->{lastsessiondate} and $device->{lastsessiondate} < $newlastupdate); $newlastupdate = $device->{lastsessiondate} if($device->{lastsessiondate} and $device->{lastsessiondate} < $newlastupdate);
$newlastupdate = $device->{lastweighindate} if($device->{lastweighindate} and $device->{lastweighindate} < $newlastupdate); $newlastupdate = $device->{lastweighindate} if($device->{lastweighindate} and $device->{lastweighindate} < $newlastupdate);
#start in-bed detection
if($datatype =~ /Sleep/ && $iscurrent == 0){
if($device->{lastweighindate} > (time()-1800)){
readingsSingleUpdate( $hash, "in_bed", 1, 1 );
} else {
readingsSingleUpdate( $hash, "in_bed", 0, 1 );
}
}
#end in-bed detection
} }
$newlastupdate = $now if($newlastupdate > $now); $newlastupdate = $now if($newlastupdate > $now);
if($newlastupdate < ($lastupdate-1)) if($newlastupdate < ($lastupdate-1))
@ -2284,7 +2310,7 @@ sub withings_parseVasistas($$;$) {
readingsSingleUpdate( $hash, ".lastData", $newlastupdate, 0 ); readingsSingleUpdate( $hash, ".lastData", $newlastupdate, 0 );
} }
Log3 $name, 4, "$name: got ".$i.' entries from Vasistas (latest: '.FmtDateTime($newlastupdate).')'; Log3 $name, (($i>0)?3:4), "$name: got ".$i.' entries from Vasistas (latest: '.FmtDateTime($newlastupdate).')';
} }
} }
@ -2297,7 +2323,7 @@ sub withings_parseTimeline($$) {
my ($hash, $json) = @_; my ($hash, $json) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#parse #parse
Log3 "withings", 5, "$name: parsemetimeline "; Log3 $name, 5, "$name: parsemetimeline ";
my ($now) = time; my ($now) = time;
my $lastupdate = ReadingsVal( $name, ".lastAlert", ($now-21*24*60*60) ); my $lastupdate = ReadingsVal( $name, ".lastAlert", ($now-21*24*60*60) );
@ -2321,12 +2347,12 @@ sub withings_parseTimeline($$) {
} }
elsif($event->{class} eq 'deleted') elsif($event->{class} eq 'deleted')
{ {
Log3 "withings", 5, "withings: event " . FmtDateTime($event->{epoch})." Event was deleted"; Log3 $name, 5, "withings: event " . FmtDateTime($event->{epoch})." Event was deleted";
next; next;
} }
elsif($event->{class} ne 'noise_detected' && $event->{class} ne 'movement_detected' && $event->{class} ne 'alert_environment' && $event->{class} ne 'offline' && $event->{class} ne 'online' && $event->{class} ne 'snapshot') elsif($event->{class} ne 'noise_detected' && $event->{class} ne 'movement_detected' && $event->{class} ne 'alert_environment' && $event->{class} ne 'offline' && $event->{class} ne 'online' && $event->{class} ne 'snapshot')
{ {
Log3 "withings", 2, "withings: alert class unknown " . $event->{class}; Log3 $name, 2, "withings: alert class unknown " . $event->{class};
next; next;
} }
@ -2377,7 +2403,7 @@ sub withings_parseTimeline($$) {
delete $hash->{CHANGETIME}; delete $hash->{CHANGETIME};
Log3 $name, 4, "$name: got ".$i.' entries from Timeline (latest: '.FmtDateTime($newlastupdate).')'; Log3 $name, (($i>0)?3:4), "$name: got ".$i.' entries from Timeline (latest: '.FmtDateTime($newlastupdate).')';
} }
@ -2388,7 +2414,7 @@ sub withings_parseEvents($$) {
my ($hash, $json) = @_; my ($hash, $json) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#parse #parse
Log3 "withings", 5, "$name: parseevents"; Log3 $name, 5, "$name: parseevents";
my ($now) = time; my ($now) = time;
my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) ); my $lastupdate = ReadingsVal( $name, ".lastData", ($now-21*24*60*60) );
my $lastalertupdate = ReadingsVal( $name, ".lastAlert", ($now-21*24*60*60) ); my $lastalertupdate = ReadingsVal( $name, ".lastAlert", ($now-21*24*60*60) );
@ -2410,7 +2436,7 @@ sub withings_parseEvents($$) {
$hash->{".updateTimestamp"} = FmtDateTime($event->{date}); $hash->{".updateTimestamp"} = FmtDateTime($event->{date});
my $changeindex = 0; my $changeindex = 0;
#Log3 "withings", 5, "withings: event " . FmtDateTime($event->{date})." ".$event->{type}." ".$event->{activated}."/".$event->{measure}{value}; #Log3 $name, 5, "withings: event " . FmtDateTime($event->{date})." ".$event->{type}." ".$event->{activated}."/".$event->{measure}{value};
my $reading = $event_types{$event->{type}}->{reading}; my $reading = $event_types{$event->{type}}->{reading};
my $value = "notice"; my $value = "notice";
@ -2482,7 +2508,7 @@ sub withings_parseEvents($$) {
delete $hash->{CHANGETIME}; delete $hash->{CHANGETIME};
Log3 $name, 4, "$name: got ".$i.' entries from Events (latest: '.FmtDateTime($newlastupdate).')'; Log3 $name, (($i>0)?3:4), "$name: got ".$i.' entries from Events (latest: '.FmtDateTime($newlastupdate).')';
} }
@ -2550,10 +2576,10 @@ sub withings_Get($$@) {
my $users = withings_getUsers($hash); my $users = withings_getUsers($hash);
my $ret; my $ret;
foreach my $user (@{$users}) { foreach my $user (@{$users}) {
$ret .= "$user->{id}\t\[$user->{shortname}\]\t$user->{publickey}\t$user->{firstname} $user->{lastname}\n"; $ret .= "$user->{id}\t\[$user->{shortname}\]\t$user->{publickey} \t$user->{usertype}/$user->{status}\t$user->{firstname} $user->{lastname}\n";
} }
$ret = "id\tshort\tpublickey\t\tname\n" . $ret if( $ret );; $ret = "id\tshort\tpublickey\tusertype/status\tname\n" . $ret if( $ret );;
$ret = "no users found" if( !$ret ); $ret = "no users found" if( !$ret );
return $ret; return $ret;
} }
@ -3172,7 +3198,7 @@ sub withings_Dispatch($$$) {
Log3 $name, 2, "$name: json evaluation error on dispatch type ".$param->{type}." ".$@; Log3 $name, 2, "$name: json evaluation error on dispatch type ".$param->{type}." ".$@;
return undef; return undef;
} }
Log3 $name, 1, "withings: Dispatch ".$param->{type}." json error ".$json->{error} if(defined($json->{error})); Log3 $name, 1, "$name: Dispatch ".$param->{type}." json error ".$json->{error} if(defined($json->{error}));
Log3 $name, 5, "$name: json returned: ".Dumper($json); Log3 $name, 5, "$name: json returned: ".Dumper($json);

View File

@ -11,7 +11,7 @@
# #
# #
############################################################################## ##############################################################################
# Release 20 / 2018-09-09 # Release 20 / 2018-09-20
package main; package main;
@ -78,6 +78,8 @@ netatmo_Define($$)
my $subtype; my $subtype;
if($a[2] eq "WEBHOOK") { if($a[2] eq "WEBHOOK") {
$subtype = "WEBHOOK"; $subtype = "WEBHOOK";
$hash->{model} = "WEBHOOK";
my $d = $modules{$hash->{TYPE}}{defptr}{"WEBHOOK"}; my $d = $modules{$hash->{TYPE}}{defptr}{"WEBHOOK"};
return "Netatmo webkook already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); return "Netatmo webkook already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
@ -116,6 +118,7 @@ netatmo_Define($$)
if( $a[3] && $a[3] =~ m/[\da-f]{2}(:[\da-f]{2}){5}/ ) if( $a[3] && $a[3] =~ m/[\da-f]{2}(:[\da-f]{2}){5}/ )
{ {
$hash->{model} = "PUBLIC";
my $device = $a[3]; my $device = $a[3];
$hash->{Device} = $device; $hash->{Device} = $device;
@ -200,6 +203,8 @@ netatmo_Define($$)
$hash->{Rad} = $rad; $hash->{Rad} = $rad;
$subtype = "PUBLIC"; $subtype = "PUBLIC";
$hash->{model} = "WEATHERMAP";
$modules{$hash->{TYPE}}{defptr}{$hash->{Lat}.$hash->{Lon}.$hash->{Rad}} = $hash; $modules{$hash->{TYPE}}{defptr}{$hash->{Lat}.$hash->{Lon}.$hash->{Rad}} = $hash;
my $account = $modules{$hash->{TYPE}}{defptr}{"account"}; my $account = $modules{$hash->{TYPE}}{defptr}{"account"};
@ -233,6 +238,7 @@ netatmo_Define($$)
} elsif( ($a[2] eq "FORECAST" && @a == 4 ) ) { } elsif( ($a[2] eq "FORECAST" && @a == 4 ) ) {
$subtype = "FORECAST"; $subtype = "FORECAST";
$hash->{model} = "FORECAST";
my $device = $a[3]; my $device = $a[3];
@ -291,6 +297,7 @@ netatmo_Define($$)
} elsif( ($a[2] eq "HEATINGHOME" && @a == 4 ) ) { } elsif( ($a[2] eq "HEATINGHOME" && @a == 4 ) ) {
$subtype = "HEATINGHOME"; $subtype = "HEATINGHOME";
$hash->{model} = "HEATINGHOME";
my $home = $a[@a-1]; my $home = $a[@a-1];
@ -307,6 +314,7 @@ netatmo_Define($$)
} elsif( ($a[2] eq "HEATINGROOM" && @a == 5 ) ) { } elsif( ($a[2] eq "HEATINGROOM" && @a == 5 ) ) {
$subtype = "HEATINGROOM"; $subtype = "HEATINGROOM";
$hash->{model} = "HEATINGROOM";
my $room = $a[@a-1]; my $room = $a[@a-1];
my $home = $a[@a-2]; my $home = $a[@a-2];
@ -325,6 +333,7 @@ netatmo_Define($$)
} elsif( ($a[2] eq "HOME" && @a == 4 ) ) { } elsif( ($a[2] eq "HOME" && @a == 4 ) ) {
$subtype = "HOME"; $subtype = "HOME";
$hash->{model} = "HOME";
my $home = $a[@a-1]; my $home = $a[@a-1];
@ -341,6 +350,7 @@ netatmo_Define($$)
} elsif( ($a[2] eq "PERSON" && @a == 5 ) ) { } elsif( ($a[2] eq "PERSON" && @a == 5 ) ) {
$subtype = "PERSON"; $subtype = "PERSON";
$hash->{model} = "PERSON";
my $home = $a[@a-2]; my $home = $a[@a-2];
my $person = $a[@a-1]; my $person = $a[@a-1];
@ -389,6 +399,7 @@ netatmo_Define($$)
} elsif( @a == 6 || ($a[2] eq "ACCOUNT" && @a == 7 ) ) { } elsif( @a == 6 || ($a[2] eq "ACCOUNT" && @a == 7 ) ) {
$subtype = "ACCOUNT"; $subtype = "ACCOUNT";
$hash->{model} = "ACCOUNT";
$hash->{network} = "ok"; $hash->{network} = "ok";
delete($hash->{access_token}); delete($hash->{access_token});
@ -3723,14 +3734,14 @@ netatmo_parseGlobal($$)
} }
if(defined($moduledata->{dashboard_data}{sum_rain_24})) if(defined($moduledata->{dashboard_data}{sum_rain_24}))
{ {
my $rain_day = ReadingsVal($module->{NAME},"rain_day",0); # my $rain_day = ReadingsVal($module->{NAME},"rain_day",0);
if($moduledata->{dashboard_data}{sum_rain_24} < $rain_day) # if($moduledata->{dashboard_data}{sum_rain_24} < $rain_day)
{ # {
my $rain_total = ReadingsVal($module->{NAME},"rain_total",0); # my $rain_total = ReadingsVal($module->{NAME},"rain_total",0);
$rain_total += $rain_day; # $rain_total += $rain_day;
readingsSingleUpdate($module,"rain_total",$rain_total,1); # readingsSingleUpdate($module,"rain_total",$rain_total,1);
Log3 $name, 1, $module->{NAME}.":_added rain ".$rain_day." (to ".$rain_total.")"; # Log3 $name, 1, $module->{NAME}.":_added rain ".$rain_day." (to ".$rain_total.")";
} # }
readingsBeginUpdate($module); readingsBeginUpdate($module);
$module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{time_utc}); $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{time_utc});
readingsBulkUpdate( $module, "rain_day", $moduledata->{dashboard_data}{sum_rain_24}, 1 ); readingsBulkUpdate( $module, "rain_day", $moduledata->{dashboard_data}{sum_rain_24}, 1 );

View File

@ -1,4 +1,4 @@
############################################## ##############################################
# $Id$$$ # $Id$$$
# #
# 72_XiaomiDevice.pm # 72_XiaomiDevice.pm
@ -211,7 +211,7 @@ sub XiaomiDevice_Define($$$) {
$hash->{helper}{delay} = 0; $hash->{helper}{delay} = 0;
#my $token = ''; $a[3] = '' if(!defined($a[3]));
if(length($a[3]) == 32) { if(length($a[3]) == 32) {
$hash->{helper}{token} = $a[3]; $hash->{helper}{token} = $a[3];
} elsif(length($a[3]) == 96) { } elsif(length($a[3]) == 96) {
@ -1587,12 +1587,12 @@ sub XiaomiDevice_GetUpdate($)
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "AirPurifier") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "AirPurifier")
{ {
$hash->{helper}{packet}{$packetid} = "air_data"; $hash->{helper}{packet}{$packetid} = "air_data";
XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["power","mode","motor1_speed","temp_dec","humidity","aqi","average_aqi","favorite_level","use_time","purify_volume","filter1_life"]}' ); XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["power","mode","motor1_speed","temp_dec","humidity","aqi","average_aqi","favorite_level","use_time","purify_volume","filter1_life","f1_hour_used","f1_hour","button_pressed","motor2_speed"]}' );
} }
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "Humidifier") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "Humidifier")
{ {
$hash->{helper}{packet}{$packetid} = "hum_data"; $hash->{helper}{packet}{$packetid} = "hum_data";
XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["power","mode","temp_dec","humidity"]}' ); XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["power","mode","temp_dec","humidity","button_pressed"]}' );
} }
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "SmartFan") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "SmartFan")
{ {
@ -1612,7 +1612,7 @@ sub XiaomiDevice_GetUpdate($)
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "WaterPurifier") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "WaterPurifier")
{ {
$hash->{helper}{packet}{$packetid} = "water_data"; $hash->{helper}{packet}{$packetid} = "water_data";
XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["power","mode","tds","filter1_life","filter1_state","filter_life","filter_state","life","state","level","volume","filter","usage","temperature","uv_life","uv_state","elecval_state"]}' ); XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["power","mode","tds","filter1_life","filter1_state","filter_life","filter_state","life","state","level","volume","filter","usage","temperature","uv_life","uv_state","elecval_state","button_pressed"]}' );
} }
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "Camera") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "Camera")
{ {
@ -1622,7 +1622,7 @@ sub XiaomiDevice_GetUpdate($)
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "RiceCooker") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "RiceCooker")
{ {
$hash->{helper}{packet}{$packetid} = "ricecooker_data"; $hash->{helper}{packet}{$packetid} = "ricecooker_data";
XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["func", "menu", "stage", "temp", "t_func", "t_precook", "t_cook", "setting", "delay", "version"]}' ); XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["func", "menu", "stage", "temp", "t_func", "t_precook", "t_cook", "setting", "delay", "version","button_pressed"]}' );
} }
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "PowerPlug") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "PowerPlug")
{ {
@ -1648,7 +1648,7 @@ sub XiaomiDevice_GetSettings($)
my $packetid = $hash->{helper}{packetid}; my $packetid = $hash->{helper}{packetid};
$hash->{helper}{packetid} = $packetid+1; $hash->{helper}{packetid} = $packetid+1;
$hash->{helper}{packet}{$packetid} = "air_settings"; $hash->{helper}{packet}{$packetid} = "air_settings";
return XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["buzzer","led_b","child_lock","app_extra","act_sleep","sleep_time"]}' ); return XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["buzzer","led_b","child_lock","app_extra","act_sleep","sleep_time","volume","rfid_product_id","rfid_tag"]}' );
} }
if( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "Humidifier") if( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "Humidifier")
@ -1733,6 +1733,16 @@ sub XiaomiDevice_GetSettings($)
$hash->{helper}{packet}{$packetid} = "get_carpet_mode"; $hash->{helper}{packet}{$packetid} = "get_carpet_mode";
XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_carpet_mode","params":[""]}' ); XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_carpet_mode","params":[""]}' );
$packetid = $hash->{helper}{packetid};
$hash->{helper}{packetid} = $packetid+1;
$hash->{helper}{packet}{$packetid} = "get_fw_features";
XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_fw_features","params":[""]}' );
$packetid = $hash->{helper}{packetid};
$hash->{helper}{packetid} = $packetid+1;
$hash->{helper}{packet}{$packetid} = "app_get_locale";
XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"app_get_locale","params":[""]}' );
return undef; return undef;
} }
@ -1794,7 +1804,7 @@ sub XiaomiDevice_GetSpeed($)
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "AirPurifier") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "AirPurifier")
{ {
$hash->{helper}{packet}{$packetid} = "air_status"; $hash->{helper}{packet}{$packetid} = "air_status";
XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["power","mode","motor1_speed","favorite_level"]}' ); XiaomiDevice_WriteJSON($hash, '{"id":'.$packetid.',"method":"get_prop","params":["power","mode","motor1_speed","favorite_level","motor2_speed"]}' );
} }
elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "Humidifier") elsif( defined($attr{$name}) && defined($attr{$name}{subType}) && $attr{$name}{subType} eq "Humidifier")
{ {
@ -1961,8 +1971,12 @@ sub XiaomiDevice_ParseJSON($$)
readingsBulkUpdate( $hash, "pm25_average", $json->{result}[6], 1 ) if(defined($json->{result}[6])); readingsBulkUpdate( $hash, "pm25_average", $json->{result}[6], 1 ) if(defined($json->{result}[6]));
readingsBulkUpdate( $hash, "favorite", $json->{result}[7], 1 ) if(defined($json->{result}[7])); readingsBulkUpdate( $hash, "favorite", $json->{result}[7], 1 ) if(defined($json->{result}[7]));
readingsBulkUpdate( $hash, "usage", sprintf( "%.1f", $json->{result}[8]/3600), 1 ) if(defined($json->{result}[8])); readingsBulkUpdate( $hash, "usage", sprintf( "%.1f", $json->{result}[8]/3600), 1 ) if(defined($json->{result}[8]));
readingsBulkUpdate( $hash, "volume", $json->{result}[9], 1 ) if(defined($json->{result}[9])); readingsBulkUpdate( $hash, "filter_volume", $json->{result}[9], 1 ) if(defined($json->{result}[9]));
readingsBulkUpdate( $hash, "filter", $json->{result}[10], 1 ) if(defined($json->{result}[10])); readingsBulkUpdate( $hash, "filter", $json->{result}[10], 1 ) if(defined($json->{result}[10]));
readingsBulkUpdate( $hash, "filter_used", $json->{result}[11], 1 ) if(defined($json->{result}[11]));
readingsBulkUpdate( $hash, "filter_life", $json->{result}[12], 1 ) if(defined($json->{result}[12]));
readingsBulkUpdate( $hash, "button_pressed", $json->{result}[13], 1 ) if(defined($json->{result}[13]));
readingsBulkUpdate( $hash, "speed2", $json->{result}[14], 1 ) if(defined($json->{result}[14]));
readingsBulkUpdate( $hash, "state", $stateval, 1 ) if(defined($stateval)); readingsBulkUpdate( $hash, "state", $stateval, 1 ) if(defined($stateval));
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef; return undef;
@ -1978,6 +1992,9 @@ sub XiaomiDevice_ParseJSON($$)
readingsBulkUpdate( $hash, "turbo", ($json->{result}[3] eq "0" ? 'off' : 'on'), 1 ) if(defined($json->{result}[3])); readingsBulkUpdate( $hash, "turbo", ($json->{result}[3] eq "0" ? 'off' : 'on'), 1 ) if(defined($json->{result}[3]));
readingsBulkUpdate( $hash, "sleep_auto", $json->{result}[4], 1 ) if(defined($json->{result}[4])); readingsBulkUpdate( $hash, "sleep_auto", $json->{result}[4], 1 ) if(defined($json->{result}[4]));
readingsBulkUpdate( $hash, "sleep_time", $json->{result}[6], 1 ) if(defined($json->{result}[6])); readingsBulkUpdate( $hash, "sleep_time", $json->{result}[6], 1 ) if(defined($json->{result}[6]));
readingsBulkUpdate( $hash, "filter_volume", $json->{result}[7], 1 ) if(defined($json->{result}[7]));
readingsBulkUpdate( $hash, "rfid_product_id", $json->{result}[8], 1 ) if(defined($json->{result}[8]));
readingsBulkUpdate( $hash, "rfid_tag", $json->{result}[9], 1 ) if(defined($json->{result}[9]));
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef; return undef;
} }
@ -1992,6 +2009,7 @@ sub XiaomiDevice_ParseJSON($$)
readingsBulkUpdate( $hash, "mode", $json->{result}[1], 1 ) if(defined($json->{result}[1])); readingsBulkUpdate( $hash, "mode", $json->{result}[1], 1 ) if(defined($json->{result}[1]));
readingsBulkUpdate( $hash, "speed", $json->{result}[2], 1 ) if(defined($json->{result}[2])); readingsBulkUpdate( $hash, "speed", $json->{result}[2], 1 ) if(defined($json->{result}[2]));
readingsBulkUpdate( $hash, "favorite", $json->{result}[3], 1 ) if(defined($json->{result}[3])); readingsBulkUpdate( $hash, "favorite", $json->{result}[3], 1 ) if(defined($json->{result}[3]));
readingsBulkUpdate( $hash, "speed2", $json->{result}[4], 1 ) if(defined($json->{result}[4]));
readingsBulkUpdate( $hash, "state", $stateval, 1 ) if(defined($stateval)); readingsBulkUpdate( $hash, "state", $stateval, 1 ) if(defined($stateval));
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef; return undef;
@ -2005,6 +2023,7 @@ sub XiaomiDevice_ParseJSON($$)
readingsBulkUpdate( $hash, "mode", ($json->{result}[0] eq "off") ? "idle" : $json->{result}[1], 1 ) if(defined($json->{result}[1])); readingsBulkUpdate( $hash, "mode", ($json->{result}[0] eq "off") ? "idle" : $json->{result}[1], 1 ) if(defined($json->{result}[1]));
readingsBulkUpdate( $hash, "temperature", ($json->{result}[2]/10), 1 ) if(defined($json->{result}[2])); readingsBulkUpdate( $hash, "temperature", ($json->{result}[2]/10), 1 ) if(defined($json->{result}[2]));
readingsBulkUpdate( $hash, "humidity", $json->{result}[3], 1 ) if(defined($json->{result}[3])); readingsBulkUpdate( $hash, "humidity", $json->{result}[3], 1 ) if(defined($json->{result}[3]));
readingsBulkUpdate( $hash, "button_pressed", $json->{result}[4], 1 ) if(defined($json->{result}[4]));
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef; return undef;
} }
@ -2177,13 +2196,14 @@ sub XiaomiDevice_ParseJSON($$)
readingsBulkUpdate( $hash, "life", $json->{result}[7], 1 ) if(defined($json->{result}[7])); readingsBulkUpdate( $hash, "life", $json->{result}[7], 1 ) if(defined($json->{result}[7]));
readingsBulkUpdate( $hash, "state", $json->{result}[8], 1 ) if(defined($json->{result}[8])); readingsBulkUpdate( $hash, "state", $json->{result}[8], 1 ) if(defined($json->{result}[8]));
readingsBulkUpdate( $hash, "level", $json->{result}[9], 1 ) if(defined($json->{result}[9])); readingsBulkUpdate( $hash, "level", $json->{result}[9], 1 ) if(defined($json->{result}[9]));
readingsBulkUpdate( $hash, "volume", $json->{result}[10], 1 ) if(defined($json->{result}[10])); readingsBulkUpdate( $hash, "water_volume", $json->{result}[10], 1 ) if(defined($json->{result}[10]));
readingsBulkUpdate( $hash, "filter", $json->{result}[11], 1 ) if(defined($json->{result}[11])); readingsBulkUpdate( $hash, "filter", $json->{result}[11], 1 ) if(defined($json->{result}[11]));
readingsBulkUpdate( $hash, "usage", $json->{result}[12], 1 ) if(defined($json->{result}[12])); readingsBulkUpdate( $hash, "usage", $json->{result}[12], 1 ) if(defined($json->{result}[12]));
readingsBulkUpdate( $hash, "temperature", $json->{result}[13], 1 ) if(defined($json->{result}[13])); readingsBulkUpdate( $hash, "temperature", $json->{result}[13], 1 ) if(defined($json->{result}[13]));
readingsBulkUpdate( $hash, "uv_life", $json->{result}[14], 1 ) if(defined($json->{result}[14])); readingsBulkUpdate( $hash, "uv_life", $json->{result}[14], 1 ) if(defined($json->{result}[14]));
readingsBulkUpdate( $hash, "uv_state", $json->{result}[15], 1 ) if(defined($json->{result}[15])); readingsBulkUpdate( $hash, "uv_state", $json->{result}[15], 1 ) if(defined($json->{result}[15]));
readingsBulkUpdate( $hash, "elecval_state", $json->{result}[16], 1 ) if(defined($json->{result}[16])); readingsBulkUpdate( $hash, "elecval_state", $json->{result}[16], 1 ) if(defined($json->{result}[16]));
readingsBulkUpdate( $hash, "button_pressed", $json->{result}[17], 1 ) if(defined($json->{result}[17]));
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef; return undef;
} }
@ -2216,6 +2236,7 @@ sub XiaomiDevice_ParseJSON($$)
readingsBulkUpdate( $hash, "setting", $json->{result}[7], 1 ) if(defined($json->{result}[7])); readingsBulkUpdate( $hash, "setting", $json->{result}[7], 1 ) if(defined($json->{result}[7]));
readingsBulkUpdate( $hash, "delay", $json->{result}[8], 1 ) if(defined($json->{result}[8])); readingsBulkUpdate( $hash, "delay", $json->{result}[8], 1 ) if(defined($json->{result}[8]));
readingsBulkUpdate( $hash, "version", $json->{result}[9], 1 ) if(defined($json->{result}[9])); readingsBulkUpdate( $hash, "version", $json->{result}[9], 1 ) if(defined($json->{result}[9]));
readingsBulkUpdate( $hash, "button_pressed", $json->{result}[10], 1 ) if(defined($json->{result}[10]));
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef; return undef;
} }
@ -2236,6 +2257,7 @@ sub XiaomiDevice_ParseJSON($$)
readingsBulkUpdate( $hash, "voltage", $json->{result}[0]{voltage}, 1 ) if(defined($json->{result}[0]{voltage})); readingsBulkUpdate( $hash, "voltage", $json->{result}[0]{voltage}, 1 ) if(defined($json->{result}[0]{voltage}));
readingsBulkUpdate( $hash, "power_factor", $json->{result}[0]{power_factor}, 1 ) if(defined($json->{result}[0]{power_factor})); readingsBulkUpdate( $hash, "power_factor", $json->{result}[0]{power_factor}, 1 ) if(defined($json->{result}[0]{power_factor}));
readingsBulkUpdate( $hash, "elec_leakage", $json->{result}[0]{elec_leakage}, 1 ) if(defined($json->{result}[0]{elec_leakage})); readingsBulkUpdate( $hash, "elec_leakage", $json->{result}[0]{elec_leakage}, 1 ) if(defined($json->{result}[0]{elec_leakage}));
readingsBulkUpdate( $hash, "button_pressed", $json->{result}[0]{button_pressed}, 1 ) if(defined($json->{result}[0]{button_pressed}));
#readingsBulkUpdate( $hash, "setting", (($json->{result}[0]{setting} eq "1")?"yes":"no"), 1 ) if(defined($json->{result}[0]{setting})); #readingsBulkUpdate( $hash, "setting", (($json->{result}[0]{setting} eq "1")?"yes":"no"), 1 ) if(defined($json->{result}[0]{setting}));
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef; return undef;
@ -2306,6 +2328,14 @@ sub XiaomiDevice_ParseJSON($$)
readingsEndUpdate($hash,1); readingsEndUpdate($hash,1);
return undef; return undef;
} }
if($msgtype eq "get_sound_volume")
{
return undef if(!defined($json->{result}));
readingsSingleUpdate( $hash, "volume", "100", 0) if(($json->{result} eq "unknown_method") || (ref($json->{result}) ne "ARRAY" && $json->{result} eq "0"));
return undef if(ref($json->{result}) ne "ARRAY");
readingsSingleUpdate( $hash, "volume", $json->{result}[0], 1 ) if(defined($json->{result}[0]));
return undef;
}
if($msgtype eq "get_carpet_mode") if($msgtype eq "get_carpet_mode")
{ {
return undef if(!defined($json->{result})); return undef if(!defined($json->{result}));
@ -2319,12 +2349,33 @@ sub XiaomiDevice_ParseJSON($$)
readingsSingleUpdate( $hash, "carpet_integral", $json->{result}[0]{current_integral}, 1 ) if(defined($json->{result}[0]{current_integral})); readingsSingleUpdate( $hash, "carpet_integral", $json->{result}[0]{current_integral}, 1 ) if(defined($json->{result}[0]{current_integral}));
return undef; return undef;
} }
if($msgtype eq "get_sound_volume") if($msgtype eq "app_get_locale")
{ {
return undef if(!defined($json->{result})); return undef if(!defined($json->{result}));
readingsSingleUpdate( $hash, "volume", "100", 0) if(($json->{result} eq "unknown_method") || (ref($json->{result}) ne "ARRAY" && $json->{result} eq "0"));
return undef if(ref($json->{result}) ne "ARRAY"); return undef if(ref($json->{result}) ne "ARRAY");
readingsSingleUpdate( $hash, "volume", $json->{result}[0], 1 ) if(defined($json->{result}[0])); return undef if(ref($json->{result}[0]) ne "HASH");
readingsSingleUpdate( $hash, "app_logserver", $json->{result}[0]{logserver}, 1 ) if(defined($json->{result}[0]{logserver}));
readingsSingleUpdate( $hash, "app_wifiplan", $json->{result}[0]{wifiplan}, 1 ) if(defined($json->{result}[0]{wifiplan}) && $json->{result}[0]{wifiplan} ne "");
readingsSingleUpdate( $hash, "app_timezone", $json->{result}[0]{timezone}, 1 ) if(defined($json->{result}[0]{timezone}));
readingsSingleUpdate( $hash, "app_bom", $json->{result}[0]{bom}, 1 ) if(defined($json->{result}[0]{bom}));
readingsSingleUpdate( $hash, "app_language", $json->{result}[0]{language}, 1 ) if(defined($json->{result}[0]{language}));
readingsSingleUpdate( $hash, "app_name", $json->{result}[0]{name}, 1 ) if(defined($json->{result}[0]{name}));
readingsSingleUpdate( $hash, "app_location", $json->{result}[0]{location}, 1 ) if(defined($json->{result}[0]{location}));
return undef;
}
if($msgtype eq "get_fw_features")
{
return undef if(!defined($json->{result}));
return undef if(ref($json->{result}) ne "ARRAY");
my $featurestring = "";
$featurestring = $json->{result}[0] if(defined($json->{result}[0]));
my $i = 1;
while(defined($json->{result}[$i])){
$featurestring .= ",";
$featurestring .= $json->{result}[$i];
$i++;
}
readingsSingleUpdate( $hash, "device_fw_features", $featurestring, 1 );
return undef; return undef;
} }
if($msgtype eq "get_custom_mode") if($msgtype eq "get_custom_mode")
@ -2812,7 +2863,7 @@ sub XiaomiDevice_Read($) {
if($len == 32) # token return if($len == 32) # token return
{ {
my $token = substr($data,-32,32); my $token = substr($data,-32,32);
if($token eq "ffffffffffffffffffffffffffffffff" && !defined($hash->{helper}{token})) if(($token eq "00000000000000000000000000000000" || $token eq "ffffffffffffffffffffffffffffffff") && !defined($hash->{helper}{token}))
{ {
Log3 $name, 1, "$name: Token could not be retrieved automatically from already cloud-connected device!"; Log3 $name, 1, "$name: Token could not be retrieved automatically from already cloud-connected device!";
$attr{$name}{disable} = "1"; $attr{$name}{disable} = "1";
@ -2822,7 +2873,10 @@ sub XiaomiDevice_Read($) {
RemoveInternalTimer($hash, "XiaomiDevice_connectFail"); RemoveInternalTimer($hash, "XiaomiDevice_connectFail");
$hash->{helper}{delay} = 0; $hash->{helper}{delay} = 0;
$hash->{helper}{token} = $token if(!defined($hash->{helper}{token})); if(!defined($hash->{helper}{token})){
$hash->{helper}{token} = $token;
$hash->{DEF} = $hash->{DEF}." ".$hash->{helper}{token};
}
return undef; return undef;
} }
@ -2953,13 +3007,16 @@ sub XiaomiDevice_DbLog_splitFn($) {
$value = $parts[1]; $value = $parts[1];
$unit = ""; $unit = "";
$unit = "%" if($reading =~ /filter/);; $unit = "%" if($reading eq "filter");;
$unit = "%" if($reading =~ /humidity/);; $unit = "%" if($reading =~ /humidity/);;
$unit = "µg/m³" if($reading =~ /pm25/);; $unit = "µg/m³" if($reading =~ /pm25/);;
$unit = "rpm" if($reading =~ /speed/); $unit = "rpm" if($reading =~ /speed/);
$unit = "˚C" if($reading =~ /temperature/); $unit = "˚C" if($reading =~ /temperature/);
$unit = "h" if($reading =~ /usage/); $unit = "h" if($reading =~ /usage/);
$unit = "m³" if($reading =~ /volume/); $unit = "h" if($reading =~ /_life/);
$unit = "h" if($reading =~ /_used/);
$unit = "m³" if($reading eq "filter_volume");
$unit = "l" if($reading eq "water_volume");
$unit = "%" if($reading =~ /batteryPercent/);; $unit = "%" if($reading =~ /batteryPercent/);;
$unit = "%" if($reading =~ /fan_power/);; $unit = "%" if($reading =~ /fan_power/);;
$unit = "h" if($reading =~ /clean_time/);; $unit = "h" if($reading =~ /clean_time/);;
@ -3219,7 +3276,7 @@ sub XiaomiDevice_DbLog_splitFn($) {
<br> <br>
Usage time in h<br/> Usage time in h<br/>
</li><br> </li><br>
<li><code>volume</code> <i>(AirPurifier)</i> <li><code>filter_volume</code> <i>(AirPurifier)</i>
<br> <br>
Total air volume in m³<br/> Total air volume in m³<br/>
</li><br> </li><br>