diff --git a/fhem/CHANGED b/fhem/CHANGED index c91106e63..13742c5d3 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,6 +1,7 @@ # 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. - SVN + - feature: new module 32_withings.pm added (justme1968) - bugfix: PRESENCE: fixing user detection on FritzBox! - feature: new module 38_CO20.pm added (justme1968) - feature: new module 98_GEOFANCY.pm added (loredo) diff --git a/fhem/FHEM/32_withings.pm b/fhem/FHEM/32_withings.pm new file mode 100644 index 000000000..ad8e332a9 --- /dev/null +++ b/fhem/FHEM/32_withings.pm @@ -0,0 +1,759 @@ + +# $Id$ + +package main; + +use strict; +use warnings; + +use JSON; +use LWP::Simple; +use HTTP::Request; +use HTTP::Cookies; + +use Digest::MD5 qw(md5 md5_hex md5_base64); + +use POSIX qw( strftime ); + +my %device_types = ( 0 => "User related", + 1 => "Body scale", + 4 => "Blood pressure monitor", + 16 => "Withings Pulse", ); + +my %device_models = ( 1 => { 1 => "Smart scale", 4 => "Body analyzer", }, ); + +my %measure_types = ( 1 => { name => "Weight (kg)", reading => "weight", }, + 4 => { name => "Height (meter)", reading => "height", }, + 5 => { name => "Fat Free Mass (kg)", reading => "fatFreeMass", }, + 6 => { name => "Fat Ratio (%)", reading => "fatRatio", }, + 8 => { name => "Fat Mass Weight (kg)", reading => "fatMassWeight", }, + 9 => { name => "Diastolic Blood Pressure (mmHg)", reading => "diastolicBloodPressure", }, + 10 => { name => "Systolic Blood Pressure (mmHg)", reading => "systolicBloodPressure", }, + 11 => { name => "Heart Pulse (bpm)", reading => "heartPulse", }, + 12 => { name => "Temperature (°C)", reading => "temperature", }, + 35 => { name => "CO2 (ppm)", reading => "co2", }, ); + +sub +withings_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "withings_Define"; + $hash->{NotifyFn} = "withings_Notify"; + $hash->{UndefFn} = "withings_Undefine"; + #$hash->{SetFn} = "withings_Set"; + $hash->{GetFn} = "withings_Get"; + $hash->{AttrFn} = "withings_Attr"; + $hash->{AttrList} = "IODev ". + "debug:1 ". + "disable:1 ". + "interval ". + "logfile ". + "nossl:1 "; + $hash->{AttrList} .= $readingFnAttributes; +} + +##################################### + +sub +withings_Define($$) +{ + my ($hash, $def) = @_; + + my @a = split("[ \t][ \t]*", $def); + + my $subtype; + my $name = $a[0]; + if( @a == 3 ) { + $subtype = "DEVICE"; + + my $device = $a[2]; + + $hash->{Device} = $device; + + $hash->{INTERVAL} = 3600; + + my $d = $modules{$hash->{TYPE}}{defptr}{"D$device"}; + return "device $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); + + $modules{$hash->{TYPE}}{defptr}{"D$device"} = $hash; + + } elsif( @a == 4 && $a[2] =~ m/\d+/ && $a[3] =~ m/[\da-f]+/ ) { + $subtype = "USER"; + + my $user = $a[2]; + my $key = $a[3]; + + $hash->{User} = $user; + $hash->{Key} = $key; + + $hash->{INTERVAL} = 3600; + + my $d = $modules{$hash->{TYPE}}{defptr}{"U$user"}; + return "device $user already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name ); + + $modules{$hash->{TYPE}}{defptr}{"U$user"} = $hash; + + } elsif( @a == 4 || ($a[2] eq "ACCOUNT" && @a == 5 ) ) { + $subtype = "ACCOUNT"; + + my $login = $a[@a-2]; + my $password = $a[@a-1]; + + $hash->{Clients} = ":withings:"; + + $hash->{Login} = $login; + $hash->{Password} = $password; + } else { + return "Usage: define withings device\ + define withings userid publickey\ + define withings [ACCOUNT] login password" if(@a < 3 || @a > 5); + } + + $hash->{NAME} = $name; + $hash->{SUBTYPE} = $subtype; + + $hash->{STATE} = "Initialized"; + + if( $init_done ) { + delete $modules{withings}->{NotifyFn}; + withings_initUser($hash) if( $hash->{SUBTYPE} eq "USER" ); + withings_connect($hash) if( $hash->{SUBTYPE} eq "ACCOUNT" ); + withings_initDevice($hash) if( $hash->{SUBTYPE} eq "DEVICE" ); + } + + return undef; +} + +sub +withings_Notify($$) +{ + my ($hash,$dev) = @_; + + return if($dev->{NAME} ne "global"); + return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}})); + + delete $modules{$hash->{TYPE}}->{NotifyFn}; + + foreach my $d (keys %defs) { + next if($defs{$d}{TYPE} ne $hash->{TYPE}); + withings_initUser($defs{$d}) if( $defs{$d}->{SUBTYPE} eq "USER" ); + withings_connect($defs{$d}) if( $defs{$d}->{SUBTYPE} eq "ACCOUNT" ); + withings_initDevice($defs{$d}) if( $defs{$d}->{SUBTYPE} eq "DEVICE" ); + } +} + +sub +withings_Undefine($$) +{ + my ($hash, $arg) = @_; + + delete( $modules{$hash->{TYPE}}{defptr}{"U$hash->{User}"} ) if( $hash->{SUBTYPE} eq "USER" ); + delete( $modules{$hash->{TYPE}}{defptr}{"D$hash->{Device}"} ) if( $hash->{SUBTYPE} eq "DEVICE" ); + + return undef; +} + +sub +withings_Set($$@) +{ + my ($hash, $name, $cmd) = @_; + + my $list = ""; + return "Unknown argument $cmd, choose one of $list"; +} + +sub +withings_getToken($) +{ + my ($hash) = @_; + + my $URL = 'http://auth.withings.com/index/service/once?action=get'; + my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); + my $header = HTTP::Request->new(GET => $URL); + my $request = HTTP::Request->new('GET', $URL, $header); + my $response = $agent->request($request); + + my $json = decode_json($response->content); + my $once = $json->{body}{once}; + + $hash->{Token} = $once; + + my $hashstring = $hash->{Login}.':'.md5_hex($hash->{Password}).':'.$once; + + $hash->{Hash} = md5_hex($hashstring); +} + +sub +withings_getSessionKey($) +{ + my ($hash) = @_; + + return if( $hash->{SessionTimestamp} && gettimeofday() - $hash->{SessionTimestamp} < 300 ); + + withings_getToken($hash); + + my $URL='http://auth.withings.com/en/'; + my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); + my $response = $agent->post($URL, [email => $hash->{Login}, password => $hash->{Password}, rememberme => 'on', hash => $hash->{Hash}, once => $hash->{Token}, passClear => $hash->{Password}]); + + my $authcookies=$response->header('Set-Cookie'); + $authcookies =~ /session_key=([\s\S]+?);/; + + $hash->{SessionKey} = $1; + $hash->{SessionTimestamp} = (gettimeofday())[0]; + + $hash->{STATE} = "Connected" if( $hash->{SessionKey} ); + $hash->{STATE} = "Error" if( !$hash->{SessionKey} ); + + if( !$hash->{AccountID} || length($hash->{AccountID} < 2 ) ) { + my $URL = 'http://healthmate.withings.com/index/service/account?applitype=20&action=get&sessionid='.$hash->{SessionKey}; + my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); + my $header = HTTP::Request->new(GET => $URL); + my $request = HTTP::Request->new('GET', $URL, $header); + my $response = $agent->request($request); + + my $json = decode_json($response->content); + foreach my $account (@{$json->{body}{account}}) { + next if( !defined($account->{id}) ); + $hash->{AccountID} = $account->{id} if($account->{email} eq $hash->{Login}); + } + } +} +sub +withings_connect($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + withings_getSessionKey( $hash ); + + foreach my $d (keys %defs) { + next if($defs{$d}{TYPE} ne "autocreate"); + return undef if(AttrVal($defs{$d}{NAME},"disable",undef)); + } + + my $autocreated = 0; + + my $users = withings_getUsers($hash); + foreach my $user (@{$users}) { + if( defined($modules{$hash->{TYPE}}{defptr}{"U$user->{id}"}) ) { + Log3 $name, 4, "$name: user '$user->{id}' already defined"; + next; + } + + my $id = $user->{id}; + my $devname = "withings_U". $id; + my $define= "$devname withings $id $user->{publickey}"; + + Log3 $name, 3, "$name: create new device '$devname' for user '$id'"; + + my $cmdret= CommandDefine(undef,$define); + if($cmdret) { + Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret"; + } else { + $cmdret= CommandAttr(undef,"$devname alias ".$user->{shortname}); + $cmdret= CommandAttr(undef,"$devname room withings"); + $cmdret= CommandAttr(undef,"$devname IODev $name"); + + $autocreated++; + } + } + + my $devices = withings_getDevices($hash); + foreach my $device (@{$devices}) { + if( defined($modules{$hash->{TYPE}}{defptr}{"D$device->{deviceid}"}) ) { + Log3 $name, 4, "$name: device '$device->{deviceid}' already defined"; + next; + } + + my $detail = withings_getDeviceDetail( $hash, $device->{deviceid} ); + + my $id = $detail->{id}; + my $devname = "withings_D". $id; + my $define= "$devname withings $id"; + + Log3 $name, 3, "$name: create new device '$devname' for device '$id'"; + my $cmdret= CommandDefine(undef,$define); + if($cmdret) { + Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret"; + } else { + $cmdret= CommandAttr(undef,"$devname alias ".$device_types{$detail->{type}}) if( defined($device_types{$detail->{type}}) ); + $cmdret= CommandAttr(undef,"$devname room withings"); + $cmdret= CommandAttr(undef,"$devname IODev $name"); + + $autocreated++; + } + } + + CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) ); +} +sub +withings_initDevice($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + AssignIoPort($hash); + if(defined($hash->{IODev}->{NAME})) { + Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME}; + } else { + Log3 $name, 1, "$name: no I/O device"; + } + + my $device = withings_getDeviceDetail( $hash, $hash->{Device} ); + + $hash->{DeviceType} = "UNKNOWN"; + + $hash->{sn} = $device->{sn}; + $hash->{fw} = $device->{fw}; + $hash->{DeviceType} = $device->{type}; + $hash->{DeviceType} = $device_types{$device->{type}} if( defined($device_types{$device->{type}}) ); + $hash->{Model} = $device->{model}; + $hash->{Model} = $device_models{$device->{type}}->{$device->{model}} + if( defined($device_models{$device->{type}}) && defined($device_models{$device->{type}}->{$device->{model}}) ); + + if( !defined( $attr{$name}{stateFormat} ) ) { + $attr{$name}{stateFormat} = "batteryLevel %"; + + $attr{$name}{stateFormat} = "co2 ppm" if( $device->{type} == 1 && $device->{model} == 4 ); + } + + withings_poll($hash); +} + +sub +withings_initUser($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + AssignIoPort($hash); + if(defined($hash->{IODev}->{NAME})) { + Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME}; + } else { + Log3 $name, 1, "$name: no I/O device"; + } + + my $user = withings_getUserDetail( $hash, $hash->{User} ); + + $hash->{shortName} = $user->{shortname}; + $hash->{gender} = ($user->{gender}==0)?"male":"female"; + $hash->{userName} = $user->{firstname} ." ". $user->{lastname}; + $hash->{birthdate} = strftime("%Y-%m-%d", localtime($user->{birthdate})); + + $attr{$name}{stateFormat} = "weight kg" if( !defined( $attr{$name}{stateFormat} ) ); + + withings_poll($hash); +} + +sub +withings_getUsers($) +{ + my ($hash) = @_; + + withings_getSessionKey($hash); + + my $URL = 'http://healthmate.withings.com/index/service/account?action=getuserslist&sessionid='.$hash->{SessionKey}; + my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); + my $header = HTTP::Request->new(GET => $URL); + my $request = HTTP::Request->new('GET', $URL, $header); + my $response = $agent->request($request); + + my $json = decode_json($response->content); + + my @users = (); + foreach my $user (@{$json->{body}{users}}) { + next if( !defined($user->{id}) ); + + push( @users, $user ); + } + + return \@users; +} +sub +withings_getDevices($) +{ + my ($hash) = @_; + + withings_getSessionKey($hash); + + my $URL = 'http://healthmate.withings.com/index/service/association?action=getbyaccountid&sessionid='.$hash->{SessionKey}.'&accountid='.$hash->{AccountID}; + my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); + my $header = HTTP::Request->new(GET => $URL); + my $request = HTTP::Request->new('GET', $URL, $header); + my $response = $agent->request($request); + + my $json = decode_json($response->content); + + my @devices = (); + foreach my $association (@{$json->{body}{associations}}) { + next if( !defined($association->{deviceid}) ); + + push( @devices, $association ); + } + + return \@devices; +} +sub +withings_getDeviceDetail($$) +{ + my ($hash,$id) = @_; + + $hash = $hash->{IODev} if( defined($hash->{IODev}) ); + + withings_getSessionKey( $hash ); + + my $URL = 'http://healthmate.withings.com/index/service/device?action=getproperties&sessionid='.$hash->{SessionKey}.'&deviceid='.$id; + my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); + my $header = HTTP::Request->new(GET => $URL); + my $request = HTTP::Request->new('GET', $URL, $header); + my $response = $agent->request($request); + + my $json = decode_json($response->content); + + return $json->{body}; +} +sub +withings_getDeviceReadings($$) +{ + my ($hash,$id) = @_; + my $name = $hash->{NAME}; + + return undef if( !defined($hash->{IODev}) ); + + $hash = $hash->{IODev} if( defined($hash->{IODev}) ); + + withings_getSessionKey( $hash ); + + my $lastupdate = ReadingsVal( $name, ".lastupdate", undef ); + + my $URL = 'http://healthmate.withings.com/index/service/v2/measure?action=getmeashf&meastype=12%2C35&sessionid='.$hash->{SessionKey}.'&deviceid='.$id; + $URL .= "&lastupdate=$lastupdate" if( $lastupdate ); + my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); + my $header = HTTP::Request->new(GET => $URL); + my $request = HTTP::Request->new('GET', $URL, $header); + my $response = $agent->request($request); + + my $json = decode_json($response->content); + + if(open(FH, ") { + chomp $line; + next if($line =~ m/^#.*$/); + $content .= $line; + } + close(FH); + + $json = decode_json($content); + } + + return $json; +} +sub +withings_getUserDetail($$) +{ + my ($hash,$id) = @_; + + return undef if( $hash->{SUBTYPE} ne "USER" ); + + my $URL = "http://wbsapi.withings.net/user?action=getbyuserid&userid=$hash->{User}&publickey=$hash->{Key}"; + my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); + my $header = HTTP::Request->new(GET => $URL); + my $request = HTTP::Request->new('GET', $URL, $header); + my $response = $agent->request($request); + + my $json = decode_json($response->content); + + return $json->{body}{users}[0]; +} + +sub +withings_poll($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + RemoveInternalTimer($hash); + + if( $hash->{SUBTYPE} eq "DEVICE" ) { + withings_pollDevice($hash); + } elsif( $hash->{SUBTYPE} eq "USER" ) { + withings_pollUser($hash); + } + + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "withings_poll", $hash, 1); +} + +sub +withings_pollDevice($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $json = withings_getDeviceReadings( $hash, $hash->{Device} ); + if( $json ) { + $hash->{status} = $json->{status}; + my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 ); + my @readings = (); + if( $hash->{status} == 0 ) { + foreach my $series ( @{$json->{body}{series}}) { + my $reading = $measure_types{$series->{type}}->{reading}; + if( !defined($reading) ) { + Log3 $name, 3, "$name: unknown measure type: $series->{type}"; + next; + } + + foreach my $measure (@{$series->{data}}) { + next if( $measure->{date} < $lastupdate ); + + my $value = $measure->{value}; + + push(@readings, [$measure->{date}, $reading, $value]); + } + } + + if( @readings ) { + readingsBeginUpdate($hash); + my $i = 0; + foreach my $reading (sort { $a->[0] <=> $b->[0] } @readings) { + $hash->{".updateTimestamp"} = FmtDateTime($reading->[0]); + $hash->{CHANGETIME}[$i++] = FmtDateTime($reading->[0]); + readingsBulkUpdate( $hash, $reading->[1], $reading->[2], 1 ); + } + + my ($seconds) = gettimeofday(); + $hash->{LAST_POLL} = FmtDateTime( $seconds ); + + readingsBulkUpdate( $hash, ".lastupdate", $seconds, 0 ); + + readingsEndUpdate($hash,1); + + delete $hash->{CHANGETIME}; + } + } + } + + + readingsBeginUpdate($hash); + + my $detail = withings_getDeviceDetail( $hash, $hash->{Device} ); + if( defined($detail->{batterylvl}) ) { + readingsBulkUpdate( $hash, "batteryLevel", $detail->{batterylvl}, 1 ); + readingsBulkUpdate( $hash, "battery", ($detail->{batterylvl}>20?"ok":"low"), 1 ); + } + readingsBulkUpdate( $hash, "lastWeighinDate", FmtDateTime($detail->{lastweighindate}), 1 ) if( defined($detail->{lastweighindate}) ); + + readingsEndUpdate($hash,1); +} + +sub +withings_pollUser($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $lastupdate = ReadingsVal( $name, ".lastupdate", undef ); + + my $url = "http://wbsapi.withings.net/measure?action=getmeas"; + $url .= "&userid=$hash->{User}&publickey=$hash->{Key}"; + $url .= "&lastupdate=$lastupdate" if( $lastupdate ); + my $ret = get($url); + my $json = decode_json($ret); + + $hash->{status} = $json->{status}; + if( $hash->{status} == 0 ) { + my $i = 0; + readingsBeginUpdate($hash); + foreach my $measuregrp ( sort { $a->{date} <=> $b->{date} } @{$json->{body}{measuregrps}}) { + foreach my $measure (@{$measuregrp->{measures}}) { + my $reading = $measure_types{$measure->{type}}->{reading}; + if( !defined($reading) ) { + Log3 $name, 3, "$name: unknown measure type: $measure->{type}"; + next; + } + + my $value = $measure->{value} * 10 ** $measure->{unit}; + + $hash->{".updateTimestamp"} = FmtDateTime($measuregrp->{date}); + $hash->{CHANGETIME}[$i++] = FmtDateTime($measuregrp->{date}); + readingsBulkUpdate( $hash, $reading, $value, 1 ); + } + } + + my ($seconds) = gettimeofday(); + $hash->{LAST_POLL} = FmtDateTime( $seconds ); + + readingsBulkUpdate( $hash, ".lastupdate", $seconds, 0 ); + + readingsEndUpdate($hash,1); + + delete $hash->{CHANGETIME}; + } +} + +sub +withings_Get($$@) +{ + my ($hash, $name, $cmd) = @_; + + my $list; + if( $hash->{SUBTYPE} eq "USER" ) { + $list = "update:noArg updateAll:noArg"; + + if( $cmd eq "updateAll" ) { + $cmd = "update"; + CommandDeleteReading( undef, "$name .*" ); + } + + if( $cmd eq "update" ) { + withings_poll($hash); + return undef; + } + } elsif( $hash->{SUBTYPE} eq "DEVICE" ) { + $list = "update:noArg updateAll:noArg"; + + if( $cmd eq "updateAll" ) { + $cmd = "update"; + CommandDeleteReading( undef, "$name .*" ); + } + + if( $cmd eq "update" ) { + withings_poll($hash); + return undef; + } + } elsif( $hash->{SUBTYPE} eq "ACCOUNT" ) { + $list = "users:noArg devices:noArg"; + + if( $cmd eq "users" ) { + my $users = withings_getUsers($hash); + my $ret; + foreach my $user (@{$users}) { + $ret .= "$user->{id}\t\[$user->{shortname}\]\t$user->{publickey}\t$user->{firstname} $user->{lastname}\n"; + } + + $ret = "id\tshort\tpublickey\t\tname\n" . $ret if( $ret );; + $ret = "no users found" if( !$ret ); + return $ret; + } elsif( $cmd eq "devices" ) { + my $devices = withings_getDevices($hash); + my $ret; + foreach my $device (@{$devices}) { + my $detail = withings_getDeviceDetail($hash,$device->{deviceid}); + $ret .= "$detail->{id}\t$device_types{$detail->{type}}\t$detail->{batterylvl}\t$detail->{sn}\n"; + } + + $ret = "id\ttype\t\tbattery\tSN\n" . $ret if( $ret );; + $ret = "no devices found" if( !$ret ); + return $ret; + } + } + + return "Unknown argument $cmd, choose one of $list"; +} + +sub +withings_Attr($$$) +{ + my ($cmd, $name, $attrName, $attrVal) = @_; + + my $orig = $attrVal; + $attrVal = int($attrVal) if($attrName eq "interval"); + $attrVal = 3600 if($attrName eq "interval" && $attrVal < 3600 && $attrVal != 0); + + if( $attrName eq "interval" ) { + my $hash = $defs{$name}; + $hash->{INTERVAL} = $attrVal; + $hash->{INTERVAL} = 3600 if( !$attrVal ); + } elsif( $attrName eq "disable" ) { + my $hash = $defs{$name}; + RemoveInternalTimer($hash); + if( $cmd eq "set" && $attrVal ne "0" ) { + } else { + $attr{$name}{$attrName} = 0; + withings_poll($hash); + } + } + + if( $cmd eq "set" ) { + if( $orig ne $attrVal ) { + $attr{$name}{$attrName} = $attrVal; + return $attrName ." set to ". $attrVal; + } + } + + return; +} + + +1; + +=pod +=begin html + + +

withings

+
    + xxx

    + + Notes: +
      +
    • JSON, LWP::Simple and Digest::MD5 have to be installed on the FHEM host.
    • +

    + + + Define +
      + define <name> withings <device>
      + define <name> withings <userid> <publickey>
      + define <name> withings [ACCOUNT] <login> <password>
      +
      + + Defines a withings device.

      + If a withing device of the account type is created all fhem devices for users and devices are automaticaly created. +
      + + Examples: +
        + define withings withings abc@test.com myPassword
        + define withings withings 642123 2a42f132b9312311
        +
      +

    + + + Readings +
      +
    • weight
    • +
    • height
    • +
    • fatFreeMass
    • +
    • fatRatio
    • +
    • fatMass
    • +
    • diastolicBloodPressure
    • +
    • systolicBloodPressure
    • +
    • heartPulse
    • +
      +
    • co2
    • +
    • battery
    • +
    • batteryLevel
    • +

    + + + Get +
      +
    • update
      + trigger an update
    • +

    + + + Attributes +
      +
    • interval
      + the interval in seconds used to check for new values.
    • +
    • disable
      + 1 -> stop polling
    • +
    +
+ +=end html +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index dad892d67..6c57525fe 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -86,6 +86,7 @@ FHEM/31_HUEDevice.pm justme1968 http://forum.fhem.de Sonstige FHEM/31_LightScene.pm justme1968 http://forum.fhem.de Automatisierung FHEM/32_SYSSTAT.pm justme1968 http://forum.fhem.de Unterstuetzende Dienste FHEM/32_mailcheck.pm justme1968 http://forum.fhem.de Automatisierung +FHEM/32_withings.pm justme1968 http://forum.fhem.de Sonstiges FHEM/33_readingsGroup.pm justme1968 http://forum.fhem.de Frontends FHEM/33_readingsproxy.pm justme1968 http://forum.fhem.de Automatisierung FHEM/32_speedtest.pm justme1968 http://forum.fhem.de Sonstiges