32_withings.pm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. # $Id: 32_withings.pm 7757 2015-01-28 13:13:00Z justme1968 $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use Encode qw(encode);
  6. use JSON;
  7. use LWP::Simple;
  8. use HTTP::Request;
  9. use HTTP::Cookies;
  10. use Digest::MD5 qw(md5 md5_hex md5_base64);
  11. use POSIX qw( strftime );
  12. my %device_types = ( 0 => "User related",
  13. 1 => "Body scale",
  14. 4 => "Blood pressure monitor",
  15. 16 => "Withings Pulse", );
  16. my %device_models = ( 1 => { 1 => "Smart scale", 4 => "Body analyzer", }, );
  17. my %measure_types = ( 1 => { name => "Weight (kg)", reading => "weight", },
  18. 4 => { name => "Height (meter)", reading => "height", },
  19. 5 => { name => "Fat Free Mass (kg)", reading => "fatFreeMass", },
  20. 6 => { name => "Fat Ratio (%)", reading => "fatRatio", },
  21. 8 => { name => "Fat Mass Weight (kg)", reading => "fatMassWeight", },
  22. 9 => { name => "Diastolic Blood Pressure (mmHg)", reading => "diastolicBloodPressure", },
  23. 10 => { name => "Systolic Blood Pressure (mmHg)", reading => "systolicBloodPressure", },
  24. 11 => { name => "Heart Pulse (bpm)", reading => "heartPulse", },
  25. 12 => { name => "Temperature (°C)", reading => "temperature", },
  26. 35 => { name => "CO2 (ppm)", reading => "co2", },
  27. 54 => { name => "SPo2 (%)", reading => "spo2", }, );
  28. sub
  29. withings_Initialize($)
  30. {
  31. my ($hash) = @_;
  32. $hash->{DefFn} = "withings_Define";
  33. $hash->{NOTIFYDEV} = "global";
  34. $hash->{NotifyFn} = "withings_Notify";
  35. $hash->{UndefFn} = "withings_Undefine";
  36. #$hash->{SetFn} = "withings_Set";
  37. $hash->{GetFn} = "withings_Get";
  38. $hash->{AttrFn} = "withings_Attr";
  39. $hash->{AttrList} = "IODev ".
  40. "disable:1 ".
  41. "interval ".
  42. "logfile ".
  43. "nossl:1 ";
  44. $hash->{AttrList} .= $readingFnAttributes;
  45. }
  46. #####################################
  47. sub
  48. withings_Define($$)
  49. {
  50. my ($hash, $def) = @_;
  51. my @a = split("[ \t][ \t]*", $def);
  52. my $subtype;
  53. my $name = $a[0];
  54. if( @a == 3 ) {
  55. $subtype = "DEVICE";
  56. my $device = $a[2];
  57. $hash->{Device} = $device;
  58. $hash->{INTERVAL} = 3600;
  59. my $d = $modules{$hash->{TYPE}}{defptr}{"D$device"};
  60. return "device $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  61. $modules{$hash->{TYPE}}{defptr}{"D$device"} = $hash;
  62. } elsif( @a == 4 && $a[2] =~ m/\d+/ && $a[3] =~ m/[\da-f]+/ ) {
  63. $subtype = "USER";
  64. my $user = $a[2];
  65. my $key = $a[3];
  66. $hash->{User} = $user;
  67. $hash->{Key} = $key;
  68. $hash->{INTERVAL} = 3600;
  69. my $d = $modules{$hash->{TYPE}}{defptr}{"U$user"};
  70. return "device $user already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  71. $modules{$hash->{TYPE}}{defptr}{"U$user"} = $hash;
  72. } elsif( @a == 4 || ($a[2] eq "ACCOUNT" && @a == 5 ) ) {
  73. $subtype = "ACCOUNT";
  74. my $login = $a[@a-2];
  75. my $password = $a[@a-1];
  76. $hash->{Clients} = ":withings:";
  77. $hash->{Login} = $login;
  78. $hash->{Password} = $password;
  79. } else {
  80. return "Usage: define <name> withings device\
  81. define <name> withings userid publickey\
  82. define <name> withings [ACCOUNT] login password" if(@a < 3 || @a > 5);
  83. }
  84. $hash->{NAME} = $name;
  85. $hash->{SUBTYPE} = $subtype;
  86. $hash->{STATE} = "Initialized";
  87. if( $init_done ) {
  88. withings_initUser($hash) if( $hash->{SUBTYPE} eq "USER" );
  89. withings_connect($hash) if( $hash->{SUBTYPE} eq "ACCOUNT" );
  90. withings_initDevice($hash) if( $hash->{SUBTYPE} eq "DEVICE" );
  91. }
  92. return undef;
  93. }
  94. sub
  95. withings_Notify($$)
  96. {
  97. my ($hash,$dev) = @_;
  98. return if($dev->{NAME} ne "global");
  99. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  100. withings_initUser($hash) if( $hash->{SUBTYPE} eq "USER" );
  101. withings_connect($hash) if( $hash->{SUBTYPE} eq "ACCOUNT" );
  102. withings_initDevice($hash) if( $hash->{SUBTYPE} eq "DEVICE" );
  103. return undef;
  104. }
  105. sub
  106. withings_Undefine($$)
  107. {
  108. my ($hash, $arg) = @_;
  109. delete( $modules{$hash->{TYPE}}{defptr}{"U$hash->{User}"} ) if( $hash->{SUBTYPE} eq "USER" );
  110. delete( $modules{$hash->{TYPE}}{defptr}{"D$hash->{Device}"} ) if( $hash->{SUBTYPE} eq "DEVICE" );
  111. return undef;
  112. }
  113. sub
  114. withings_Set($$@)
  115. {
  116. my ($hash, $name, $cmd) = @_;
  117. my $list = "";
  118. return "Unknown argument $cmd, choose one of $list";
  119. }
  120. sub
  121. withings_getToken($)
  122. {
  123. my ($hash) = @_;
  124. my $URL = 'http://auth.withings.com/index/service/once?action=get';
  125. my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
  126. my $header = HTTP::Request->new(GET => $URL);
  127. my $request = HTTP::Request->new('GET', $URL, $header);
  128. my $response = $agent->request($request);
  129. my $json = ();
  130. $json = JSON->new->utf8(0)->decode($response->content) if( $response->content =~ m/^{.*}$/ );
  131. my $once = $json->{body}{once};
  132. $hash->{Token} = $once;
  133. my $hashstring = $hash->{Login}.':'.md5_hex($hash->{Password}).':'.$once;
  134. $hash->{Hash} = md5_hex($hashstring);
  135. }
  136. sub
  137. withings_getSessionKey($)
  138. {
  139. my ($hash) = @_;
  140. return if( $hash->{SessionTimestamp} && gettimeofday() - $hash->{SessionTimestamp} < 300 );
  141. withings_getToken($hash);
  142. my $URL='http://auth.withings.com/en/';
  143. my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
  144. my $response = $agent->post($URL, [email => $hash->{Login}, password => $hash->{Password}, rememberme => 'on', hash => $hash->{Hash}, once => $hash->{Token}, passClear => $hash->{Password}]);
  145. my $authcookies=$response->header('Set-Cookie');
  146. $authcookies =~ /session_key=([\s\S]+?);/;
  147. $hash->{SessionKey} = $1;
  148. $hash->{SessionTimestamp} = (gettimeofday())[0];
  149. $hash->{STATE} = "Connected" if( $hash->{SessionKey} );
  150. $hash->{STATE} = "Error" if( !$hash->{SessionKey} );
  151. if( !$hash->{AccountID} || length($hash->{AccountID} < 2 ) ) {
  152. my $URL = 'http://healthmate.withings.com/index/service/account?applitype=20&action=get&sessionid='.$hash->{SessionKey};
  153. my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
  154. my $header = HTTP::Request->new(GET => $URL);
  155. my $request = HTTP::Request->new('GET', $URL, $header);
  156. my $response = $agent->request($request);
  157. my $json = ();
  158. $json = JSON->new->utf8(0)->decode($response->content) if( $response->content =~ m/^{.*}$/ );
  159. foreach my $account (@{$json->{body}{account}}) {
  160. next if( !defined($account->{id}) );
  161. $hash->{AccountID} = $account->{id} if($account->{email} eq $hash->{Login});
  162. }
  163. }
  164. }
  165. sub
  166. withings_connect($)
  167. {
  168. my ($hash) = @_;
  169. my $name = $hash->{NAME};
  170. withings_getSessionKey( $hash );
  171. foreach my $d (keys %defs) {
  172. next if($defs{$d}{TYPE} ne "autocreate");
  173. return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
  174. }
  175. my $autocreated = 0;
  176. my $users = withings_getUsers($hash);
  177. foreach my $user (@{$users}) {
  178. if( defined($modules{$hash->{TYPE}}{defptr}{"U$user->{id}"}) ) {
  179. Log3 $name, 4, "$name: user '$user->{id}' already defined";
  180. next;
  181. }
  182. my $id = $user->{id};
  183. my $devname = "withings_U". $id;
  184. my $define= "$devname withings $id $user->{publickey}";
  185. Log3 $name, 3, "$name: create new device '$devname' for user '$id'";
  186. my $cmdret= CommandDefine(undef,$define);
  187. if($cmdret) {
  188. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  189. } else {
  190. $cmdret= CommandAttr(undef,"$devname alias ".$user->{shortname});
  191. $cmdret= CommandAttr(undef,"$devname room withings");
  192. $cmdret= CommandAttr(undef,"$devname IODev $name");
  193. $autocreated++;
  194. }
  195. }
  196. my $devices = withings_getDevices($hash);
  197. foreach my $device (@{$devices}) {
  198. if( defined($modules{$hash->{TYPE}}{defptr}{"D$device->{deviceid}"}) ) {
  199. Log3 $name, 4, "$name: device '$device->{deviceid}' already defined";
  200. next;
  201. }
  202. my $detail = withings_getDeviceDetail( $hash, $device->{deviceid} );
  203. my $id = $detail->{id};
  204. my $devname = "withings_D". $id;
  205. my $define= "$devname withings $id";
  206. Log3 $name, 3, "$name: create new device '$devname' for device '$id'";
  207. my $cmdret= CommandDefine(undef,$define);
  208. if($cmdret) {
  209. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  210. } else {
  211. $cmdret= CommandAttr(undef,"$devname alias ".$device_types{$detail->{type}}) if( defined($device_types{$detail->{type}}) );
  212. $cmdret= CommandAttr(undef,"$devname room withings");
  213. $cmdret= CommandAttr(undef,"$devname IODev $name");
  214. $autocreated++;
  215. }
  216. }
  217. CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) );
  218. }
  219. sub
  220. withings_initDevice($)
  221. {
  222. my ($hash) = @_;
  223. my $name = $hash->{NAME};
  224. AssignIoPort($hash);
  225. if(defined($hash->{IODev}->{NAME})) {
  226. Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
  227. } else {
  228. Log3 $name, 1, "$name: no I/O device";
  229. }
  230. my $device = withings_getDeviceDetail( $hash, $hash->{Device} );
  231. $hash->{DeviceType} = "UNKNOWN";
  232. $hash->{sn} = $device->{sn};
  233. $hash->{fw} = $device->{fw};
  234. $hash->{DeviceType} = $device->{type};
  235. $hash->{DeviceType} = $device_types{$device->{type}} if( defined($device_types{$device->{type}}) );
  236. $hash->{model} = $device->{model};
  237. $hash->{model} = $device_models{$device->{type}}->{$device->{model}}
  238. if( defined($device_models{$device->{type}}) && defined($device_models{$device->{type}}->{$device->{model}}) );
  239. if( !defined( $attr{$name}{stateFormat} ) ) {
  240. $attr{$name}{stateFormat} = "batteryLevel %";
  241. $attr{$name}{stateFormat} = "co2 ppm" if( $device->{type} == 1 && $device->{model} == 4 );
  242. }
  243. withings_poll($hash);
  244. }
  245. sub
  246. withings_initUser($)
  247. {
  248. my ($hash) = @_;
  249. my $name = $hash->{NAME};
  250. AssignIoPort($hash);
  251. if(defined($hash->{IODev}->{NAME})) {
  252. Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
  253. } else {
  254. Log3 $name, 1, "$name: no I/O device";
  255. }
  256. my $user = withings_getUserDetail( $hash, $hash->{User} );
  257. $hash->{shortName} = $user->{shortname};
  258. $hash->{gender} = ($user->{gender}==0)?"male":"female" if( defined($hash->{gender}) );
  259. $hash->{userName} = ($user->{firstname}?$user->{firstname}:"") ." ". ($user->{lastname}?$user->{lastname}:"");
  260. $hash->{birthdate} = strftime("%Y-%m-%d", localtime($user->{birthdate})) if( defined($user->{birthdate}) );
  261. $attr{$name}{stateFormat} = "weight kg" if( !defined( $attr{$name}{stateFormat} ) );
  262. withings_poll($hash);
  263. }
  264. sub
  265. withings_getUsers($)
  266. {
  267. my ($hash) = @_;
  268. withings_getSessionKey($hash);
  269. my $URL = 'http://healthmate.withings.com/index/service/account?action=getuserslist&sessionid='.$hash->{SessionKey};
  270. my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
  271. my $header = HTTP::Request->new(GET => $URL);
  272. my $request = HTTP::Request->new('GET', $URL, $header);
  273. my $response = $agent->request($request);
  274. my $json = ();
  275. $json = JSON->new->utf8(0)->decode($response->content) if( $response->content =~ m/^{.*}$/ );
  276. my @users = ();
  277. foreach my $user (@{$json->{body}{users}}) {
  278. next if( !defined($user->{id}) );
  279. push( @users, $user );
  280. }
  281. return \@users;
  282. }
  283. sub
  284. withings_getDevices($)
  285. {
  286. my ($hash) = @_;
  287. withings_getSessionKey($hash);
  288. my $URL = 'http://healthmate.withings.com/index/service/association?action=getbyaccountid&sessionid='.$hash->{SessionKey}.'&accountid='.$hash->{AccountID};
  289. my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
  290. my $header = HTTP::Request->new(GET => $URL);
  291. my $request = HTTP::Request->new('GET', $URL, $header);
  292. my $response = $agent->request($request);
  293. my $json = ();
  294. $json = JSON->new->utf8(0)->decode($response->content) if( $response->content =~ m/^{.*}$/ );;
  295. my @devices = ();
  296. foreach my $association (@{$json->{body}{associations}}) {
  297. next if( !defined($association->{deviceid}) );
  298. push( @devices, $association );
  299. }
  300. return \@devices;
  301. }
  302. sub
  303. withings_getDeviceDetail($$)
  304. {
  305. my ($hash,$id) = @_;
  306. $hash = $hash->{IODev} if( defined($hash->{IODev}) );
  307. withings_getSessionKey( $hash );
  308. my $URL = 'http://healthmate.withings.com/index/service/device?action=getproperties&sessionid='.$hash->{SessionKey}.'&deviceid='.$id;
  309. my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
  310. my $header = HTTP::Request->new(GET => $URL);
  311. my $request = HTTP::Request->new('GET', $URL, $header);
  312. my $response = $agent->request($request);
  313. my $json = ();
  314. $json = JSON->new->utf8(0)->decode($response->content) if( $response->content =~ m/^{.*}$/ );
  315. return $json->{body};
  316. }
  317. sub
  318. withings_getDeviceReadings($$)
  319. {
  320. my ($hash,$id) = @_;
  321. my $name = $hash->{NAME};
  322. return undef if( !defined($hash->{IODev}) );
  323. $hash = $hash->{IODev} if( defined($hash->{IODev}) );
  324. withings_getSessionKey( $hash );
  325. my $lastupdate = ReadingsVal( $name, ".lastupdate", undef );
  326. my $URL = 'http://healthmate.withings.com/index/service/v2/measure?action=getmeashf&meastype=12%2C35&sessionid='.$hash->{SessionKey}.'&deviceid='.$id;
  327. $URL .= "&lastupdate=$lastupdate" if( $lastupdate );
  328. my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
  329. my $header = HTTP::Request->new(GET => $URL);
  330. my $request = HTTP::Request->new('GET', $URL, $header);
  331. my $response = $agent->request($request);
  332. my $json = ();
  333. $json = JSON->new->utf8(0)->decode($response->content) if( $response->content =~ m/^{.*}$/ );
  334. return $json;
  335. }
  336. sub
  337. withings_getUserDetail($$)
  338. {
  339. my ($hash,$id) = @_;
  340. return undef if( $hash->{SUBTYPE} ne "USER" );
  341. my $URL = "http://wbsapi.withings.net/user?action=getbyuserid&userid=$hash->{User}&publickey=$hash->{Key}";
  342. my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30);
  343. my $header = HTTP::Request->new(GET => $URL);
  344. my $request = HTTP::Request->new('GET', $URL, $header);
  345. my $response = $agent->request($request);
  346. my $json = ();
  347. $json = JSON->new->utf8(0)->decode($response->content) if( $response->content =~ m/^{.*}$/ );
  348. return $json->{body}{users}[0];
  349. }
  350. sub
  351. withings_poll($)
  352. {
  353. my ($hash) = @_;
  354. my $name = $hash->{NAME};
  355. RemoveInternalTimer($hash);
  356. if( $hash->{SUBTYPE} eq "DEVICE" ) {
  357. withings_pollDevice($hash);
  358. } elsif( $hash->{SUBTYPE} eq "USER" ) {
  359. withings_pollUser($hash);
  360. }
  361. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "withings_poll", $hash, 0);
  362. }
  363. sub
  364. withings_pollDevice($)
  365. {
  366. my ($hash) = @_;
  367. my $name = $hash->{NAME};
  368. my $json = withings_getDeviceReadings( $hash, $hash->{Device} );
  369. if( $json ) {
  370. $hash->{status} = $json->{status};
  371. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  372. my @readings = ();
  373. if( $hash->{status} == 0 ) {
  374. foreach my $series ( @{$json->{body}{series}}) {
  375. my $reading = $measure_types{$series->{type}}->{reading};
  376. if( !defined($reading) ) {
  377. Log3 $name, 3, "$name: unknown measure type: $series->{type}";
  378. next;
  379. }
  380. foreach my $measure (@{$series->{data}}) {
  381. next if( $measure->{date} < $lastupdate );
  382. my $value = $measure->{value};
  383. push(@readings, [$measure->{date}, $reading, $value]);
  384. }
  385. }
  386. if( @readings ) {
  387. readingsBeginUpdate($hash);
  388. my $i = 0;
  389. foreach my $reading (sort { $a->[0] <=> $b->[0] } @readings) {
  390. $hash->{".updateTimestamp"} = FmtDateTime($reading->[0]);
  391. $hash->{CHANGETIME}[$i++] = FmtDateTime($reading->[0]);
  392. readingsBulkUpdate( $hash, $reading->[1], $reading->[2], 1 );
  393. }
  394. my ($seconds) = gettimeofday();
  395. $hash->{LAST_POLL} = FmtDateTime( $seconds );
  396. readingsBulkUpdate( $hash, ".lastupdate", $seconds, 0 );
  397. readingsEndUpdate($hash,1);
  398. delete $hash->{CHANGETIME};
  399. }
  400. }
  401. }
  402. readingsBeginUpdate($hash);
  403. my $detail = withings_getDeviceDetail( $hash, $hash->{Device} );
  404. if( defined($detail->{batterylvl}) ) {
  405. readingsBulkUpdate( $hash, "batteryLevel", $detail->{batterylvl}, 1 );
  406. readingsBulkUpdate( $hash, "battery", ($detail->{batterylvl}>20?"ok":"low"), 1 );
  407. }
  408. readingsBulkUpdate( $hash, "lastWeighinDate", FmtDateTime($detail->{lastweighindate}), 1 ) if( defined($detail->{lastweighindate}) );
  409. readingsEndUpdate($hash,1);
  410. }
  411. sub
  412. withings_pollUser($)
  413. {
  414. my ($hash) = @_;
  415. my $name = $hash->{NAME};
  416. my $lastupdate = ReadingsVal( $name, ".lastupdate", undef );
  417. my $url = "http://wbsapi.withings.net/measure?action=getmeas";
  418. $url .= "&userid=$hash->{User}&publickey=$hash->{Key}";
  419. $url .= "&lastupdate=$lastupdate" if( $lastupdate );
  420. my $ret = get($url);
  421. return if( !$ret );
  422. #my $json = JSON->new->utf8(0)->decode($ret);
  423. my $json = ();
  424. $json = JSON->new->utf8->decode(encode('UTF-8', $ret)) if( $ret =~ m/^{.*}$/ );
  425. return undef if( !$json );
  426. $hash->{status} = $json->{status};
  427. if( $hash->{status} == 0 ) {
  428. my $i = 0;
  429. readingsBeginUpdate($hash);
  430. foreach my $measuregrp ( sort { $a->{date} <=> $b->{date} } @{$json->{body}{measuregrps}}) {
  431. foreach my $measure (@{$measuregrp->{measures}}) {
  432. my $reading = $measure_types{$measure->{type}}->{reading};
  433. if( !defined($reading) ) {
  434. Log3 $name, 3, "$name: unknown measure type: $measure->{type}";
  435. next;
  436. }
  437. my $value = $measure->{value} * 10 ** $measure->{unit};
  438. $hash->{".updateTimestamp"} = FmtDateTime($measuregrp->{date});
  439. $hash->{CHANGETIME}[$i++] = FmtDateTime($measuregrp->{date});
  440. readingsBulkUpdate( $hash, $reading, $value, 1 );
  441. }
  442. }
  443. my ($seconds) = gettimeofday();
  444. $hash->{LAST_POLL} = FmtDateTime( $seconds );
  445. readingsBulkUpdate( $hash, ".lastupdate", $seconds, 0 );
  446. readingsEndUpdate($hash,1);
  447. delete $hash->{CHANGETIME};
  448. }
  449. }
  450. sub
  451. withings_Get($$@)
  452. {
  453. my ($hash, $name, $cmd) = @_;
  454. my $list;
  455. if( $hash->{SUBTYPE} eq "USER" ) {
  456. $list = "update:noArg updateAll:noArg";
  457. if( $cmd eq "updateAll" ) {
  458. $cmd = "update";
  459. CommandDeleteReading( undef, "$name .*" );
  460. }
  461. if( $cmd eq "update" ) {
  462. withings_poll($hash);
  463. return undef;
  464. }
  465. } elsif( $hash->{SUBTYPE} eq "DEVICE" ) {
  466. $list = "update:noArg updateAll:noArg";
  467. if( $cmd eq "updateAll" ) {
  468. $cmd = "update";
  469. CommandDeleteReading( undef, "$name .*" );
  470. }
  471. if( $cmd eq "update" ) {
  472. withings_poll($hash);
  473. return undef;
  474. }
  475. } elsif( $hash->{SUBTYPE} eq "ACCOUNT" ) {
  476. $list = "users:noArg devices:noArg";
  477. if( $cmd eq "users" ) {
  478. my $users = withings_getUsers($hash);
  479. my $ret;
  480. foreach my $user (@{$users}) {
  481. $ret .= "$user->{id}\t\[$user->{shortname}\]\t$user->{publickey}\t$user->{firstname} $user->{lastname}\n";
  482. }
  483. $ret = "id\tshort\tpublickey\t\tname\n" . $ret if( $ret );;
  484. $ret = "no users found" if( !$ret );
  485. return $ret;
  486. } elsif( $cmd eq "devices" ) {
  487. my $devices = withings_getDevices($hash);
  488. my $ret;
  489. foreach my $device (@{$devices}) {
  490. my $detail = withings_getDeviceDetail($hash,$device->{deviceid});
  491. $ret .= "$detail->{id}\t$device_types{$detail->{type}}\t$detail->{batterylvl}\t$detail->{sn}\n";
  492. }
  493. $ret = "id\ttype\t\tbattery\tSN\n" . $ret if( $ret );;
  494. $ret = "no devices found" if( !$ret );
  495. return $ret;
  496. }
  497. }
  498. return "Unknown argument $cmd, choose one of $list";
  499. }
  500. sub
  501. withings_Attr($$$)
  502. {
  503. my ($cmd, $name, $attrName, $attrVal) = @_;
  504. my $orig = $attrVal;
  505. $attrVal = int($attrVal) if($attrName eq "interval");
  506. $attrVal = 3600 if($attrName eq "interval" && $attrVal < 3600 && $attrVal != 0);
  507. if( $attrName eq "interval" ) {
  508. my $hash = $defs{$name};
  509. $hash->{INTERVAL} = $attrVal;
  510. $hash->{INTERVAL} = 3600 if( !$attrVal );
  511. } elsif( $attrName eq "disable" ) {
  512. my $hash = $defs{$name};
  513. RemoveInternalTimer($hash);
  514. if( $cmd eq "set" && $attrVal ne "0" ) {
  515. } else {
  516. $attr{$name}{$attrName} = 0;
  517. withings_poll($hash);
  518. }
  519. }
  520. if( $cmd eq "set" ) {
  521. if( $orig ne $attrVal ) {
  522. $attr{$name}{$attrName} = $attrVal;
  523. return $attrName ." set to ". $attrVal;
  524. }
  525. }
  526. return;
  527. }
  528. 1;
  529. =pod
  530. =begin html
  531. <a name="withings"></a>
  532. <h3>withings</h3>
  533. <ul>
  534. xxx<br><br>
  535. Notes:
  536. <ul>
  537. <li>JSON, LWP::Simple and Digest::MD5 have to be installed on the FHEM host.</li>
  538. </ul><br>
  539. <a name="withings_Define"></a>
  540. <b>Define</b>
  541. <ul>
  542. <code>define &lt;name&gt; withings &lt;device&gt;</code><br>
  543. <code>define &lt;name&gt; withings &lt;userid&gt; &lt;publickey&gt;</code><br>
  544. <code>define &lt;name&gt; withings [ACCOUNT] &lt;login&gt; &lt;password&gt;</code><br>
  545. <br>
  546. Defines a withings device.<br><br>
  547. If a withings device of the account type is created all fhem devices for users and devices are automaticaly created.
  548. <br>
  549. Examples:
  550. <ul>
  551. <code>define withings withings abc@test.com myPassword</code><br>
  552. <code>define withings withings 642123 2a42f132b9312311</code><br>
  553. </ul>
  554. </ul><br>
  555. <a name="withings_Readings"></a>
  556. <b>Readings</b>
  557. <ul>
  558. <li>weight</li>
  559. <li>height</li>
  560. <li>fatFreeMass</li>
  561. <li>fatRatio</li>
  562. <li>fatMass</li>
  563. <li>diastolicBloodPressure</li>
  564. <li>systolicBloodPressure</li>
  565. <li>heartPulse</li>
  566. <br>
  567. <li>co2</li>
  568. <li>battery</li>
  569. <li>batteryLevel</li>
  570. </ul><br>
  571. <a name="withings_Get"></a>
  572. <b>Get</b>
  573. <ul>
  574. <li>update<br>
  575. trigger an update</li>
  576. </ul><br>
  577. <a name="withings_Attr"></a>
  578. <b>Attributes</b>
  579. <ul>
  580. <li>interval<br>
  581. the interval in seconds used to check for new values.</li>
  582. <li>disable<br>
  583. 1 -> stop polling</li>
  584. </ul>
  585. </ul>
  586. =end html
  587. =cut