421 lines
13 KiB
PHP
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";
|
|
}
|
|
}
|
|
|