Files
Domoticz/wbs.php
2025-03-06 11:09:58 +01:00

421 lines
13 KiB
PHP

<?php
/**
* Author: Stefan Andersen <stfnandersen@gmail.com>
* File: wbs.php
*
* Withings Bodyscale Services API (WBS API) is a set of webservices allowing developers and third parties limited access to users' data.
*
* http://www.withings.com/en/api/bodyscale
*
* Copyright (c) 2010, Stefan Andersen <stfnandersen@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL STEFAN ANDERSEN BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
class remoteCallWbsException extends Exception {}
class validationErrorWbsException extends Exception {}
abstract class wbs {
const WBSAPIHOST = 'wbsapi.withings.net';
private $responseCodes = array(
'account-2555' => "An unknown error occurred",
'account-264' => "The email address provided is either unknown or invalid",
'account-100' => "The hash is missing, invalid, or does not match the provided email",
'getmeas-2555' => "An unknown error occurred",
'getmeas-250' => "The userid and publickey provided do not match, or the user does not share its data",
'getmeas-247' => "The userid provided is absent, or incorrect",
);
/**
* Can we probe the remote api?
*
* @return boolean
*/
public function probe() {
return ($this->callWbs('once', 'probe') === false) ? false : true;
}
/**
* Fetches magic string for computing password hash
*
* @return string
*/
protected function getMagicString() {
$result = $this->callWbs('once', 'get');
return $result['body']['once'];
}
/**
* Calls remote WBS API
*
* @param string $service
* @param string $action
* @param array $parameters
* @return false | result
*/
protected function callWbs($service, $action, $parameters = array())
{
$url = sprintf('http://%s/%s?action=%s&%s',
self::WBSAPIHOST,
$service,
$action,
implode('&', $parameters));
$ch = curl_init();
curl_setopt($ch, CURLOPT_POST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
if (!is_array($result)) {
throw new remoteCallWbsException("Didn't get a valid array in response");
}
if (!key_exists('status', $result)) {
throw new remoteCallWbsException("No status en response body");
}
if ($result['status'] != 0) {
$statusKey = sprintf('%s-%s', $service, $result['status']);
if (key_exists($statusKey, $this->responseCodes)) {
throw new remoteCallWbsException($this->responseCodes[$statusKey]);
}
throw new remoteCallWbsException(sprintf("WBS returned error code: %s", $result['status']));
}
return $result;
}
}
class wbs_Account extends wbs {
protected $userEmail;
protected $userPassword;
public function getUsersList() {
if (!is_string($this->userEmail)) {
throw new validationErrorWbsException("userEmail not set, call setUserEmail(email) first");
}
if (!is_string($this->userPassword)) {
throw new validationErrorWbsException("userEmail not set, call setUserPassword(password) first");
}
$magic = $this->getMagicString();
$hash = md5(sprintf('%s:%s:%s',
$this->userEmail,
md5($this->userPassword),
$magic));
$params = array("email={$this->userEmail}",
"hash={$hash}");
$result = $this->callWbs('account', 'getuserslist', $params);
foreach($result['body']['users'] as $user) {
$users[] = new wbs_User($user);
}
return (count($users) > 0)? $users : false;
}
public function setUserEmail($email) {
$this->userEmail = $email;
}
public function setUserPassword($password) {
$this->userPassword = $password;
}
}
class wbs_User extends wbs {
protected $id; // The user identifier
protected $firstname; // The user's firstname, as an UTF-8 encoded string
protected $lastname; // The user's lastname, as an UTF-8 encoded string
protected $shortname; // The user's shortname
protected $gender; // The user's gender (0 for male, 1 for female)
protected $fatmethod; // Byte indicating the Body Composition Formula in use
protected $birthdate; // The user's birthdate in Epoch format
protected $ispublic; // Set to 1 if the user curently shares their data
protected $publickey; // The user's current publickey
protected $startdate; // Will prevent retrieval of values dated prior to the supplied parameter.
protected $enddate; // Will prevent retrieval of values dated after the supplied parameter.
protected $meastype; // Will restrict the type of data retrieved
protected $lastupdate; // Only entries which have been added or modified since this time are retrieved.
protected $category; // Can be set to 2 to retrieve objectives or to 1 to retrieve actual measurements.
protected $limit; // Can be used to limit the number of measure groups returned in the result.
protected $offset; // Can be used to skip the 'offset' most recent measure group records of the result set.
protected $measures;
/**
* Constructs a wbs_User obj. Must supply information about user in array.
*
* @param array $fromArray
*/
public function __construct($fromArray) {
$this->id = $fromArray['id'];
$this->firstname = $fromArray['firstname'];
$this->lastname = $fromArray['lastname'];
$this->shortname = $fromArray['shortname'];
$this->gender = $fromArray['gender'];
$this->fatmethod = $fromArray['fatmethod'];
$this->birthdate = $fromArray['birthdate'];
$this->ispublic = $fromArray['ispublic'];
$this->publickey = $fromArray['publickey'];
}
static function loadUser($userid, $publickey) {
$params = array("userid={$userid}",
"publickey={$publickey}");
$result = parent::callWbs('user', 'getbyuserid', $params);
$result = $result['body']['users'][0];
$result['publickey'] = $publickey;
return new wbs_User($result);
}
public function getFullname() {
return $this->lastname . ", " . $this->firstname;
}
public function getMeasures($allowCache = true) {
if ($allowCache && is_array($this->measures)) {
return $this->measures;
}
$params = array("userid={$this->id}",
"publickey={$this->publickey}");
if (is_int($this->startdate)) {
$params[] = "startdate={$this->startdate}";
}
if (is_int($this->enddate)) {
$params[] = "enddate={$this->enddate}";
}
if (is_int($this->meastype)) {
$params[] = "meastype={$this->meastype}";
}
if (is_int($this->lastupdate)) {
$params[] = "lastupdate={$this->lastupdate}";
}
if (is_int($this->category)) {
$params[] = "category={$this->category}";
}
if (is_int($this->limit)) {
$params[] = "limit={$this->limit}";
}
if (is_int($this->offset)) {
$params[] = "offset={$this->offset}";
}
$result = $this->callWbs('measure', 'getmeas', $params);
foreach ($result['body']['measuregrps'] as $measuregroup) {
$this->measures[] = new wbs_MeasureGroup($measuregroup);
}
return $this->measures;
}
/**
* For a user's data to be accessible through this API, a prior authorization has to be given.
*
* @param boolean $public
* @return boolean
*/
public function setPublic($public) {
$params = array("userid={$this->id}",
"publickey={$this->publickey}");
switch ($public) {
case true:
$params[] = "ispublic=1";
break;
case false:
$params[] = "ispublic=0";
break;
default:
throw new validationErrorWbsException("Public should be set to true or false");
break;
}
$this->callWbs('user', 'update', $params);
return true;
}
public function setStartdate($startdate) {
$this->startdate = $startdate;
}
public function setEnddate($enddate) {
$this->enddate = $enddate;
}
public function setMeastype($meastype) {
$this->meastype = $meastype;
}
public function setLastupdate($lastupdate) {
$this->lastupdate = $lastupdate;
}
public function setCategory($category) {
$this->category = $category;
}
public function setLimit($limit) {
$this->limit = $limit;
}
public function setOffset($offest) {
$this->offset = $offset;
}
public function getId() {
return $this->id;
}
}
class wbs_MeasureGroup {
protected $grpid; // A unique grpid (Group Id), useful for performing synchronization tasks.
protected $attrib; // An attrib (Attribute), defining the way the measure was attributed to the user.
protected $date; // The date (EPOCH format) at which the measure was taken or entered.
protected $category; // The category of the group.
protected $measures;
public function __construct($fromArray) {
$this->grpid = $fromArray['grpid'];
$this->attrib = $fromArray['attrib'];
$this->date = $fromArray['date'];
$this->category = $fromArray['category'];
foreach ($fromArray['measures'] as $measure) {
$this->measures[] = new wbs_Measure($measure);
}
}
/**
* Attribution status
*
* Return values:
* 0 The measuregroup has been captured by a device and is known to belong to this user (and is not ambiguous)
* 1 The measuregroup has been captured by a device but may belong to other users as well as this one (it is ambiguous)
* 2 The measuregroup has been entered manually for this particular user
* 4 The measuregroup has been entered manually during user creation (and may not be accurate)
*
* @return int
*/
public function getAttrib() {
return $this->attrib;
}
public function getAttribText() {
switch ($this->attrib) {
case 0:
return "Captured by device, belongs to this user";
case 1:
return "Captured by device, belongs others users as well";
case 2:
return "Entered manually, belongs to this user";
case 4:
return "Entered manually during creating, not accurate";
default:
return "unknown";
}
}
/**
*
* @return int
*/
public function getDate() {
return $this->date;
}
/**
* Retun values:
* 1 Measure
* 2 Target
*
* @return int
*/
public function getCategory() {
return $this->category;
}
/**
* Returns an array of wbs_Meassure's
*
* @return wbs_Measure[]
*/
public function getMeasures() {
return $this->measures;
}
}
class wbs_Measure {
protected $type;
protected $value;
protected $unit;
public function __construct($fromArray) {
$this->type = $fromArray['type'];
$this->value = $fromArray['value'];
$this->unit = $fromArray['unit'];
}
/**
* Return values:
* 1 Weight (kg)
* 4 Height (meter)
* 5 Fat Free Mass (kg)
* 6 Fat Ratio (%)
* 8 Fat Mass Weight (kg)
*
* @return int
*/
public function getType() {
return $this->type;
}
public function getValue() {
return $this->value * pow(10, $this->unit);
}
public function getUnitPrefix() {
switch ($this->type) {
case 1:
return "Weight";
case 4:
return "Height";
case 5:
return "Fat Free Mass";
case 6:
return "Fat Ratio";
case 8:
return "Fat Mass Weight";
default:
return "unknown";
}
}
public function getUnitSuffix() {
if (in_array($this->type, array(1, 5, 8))) {
return "kg";
} elseif ($this->type == 4) {
return "meter";
} elseif ($this->type == 6) {
return "%";
}
return "unknown";
}
}