Intégration de la balance Withings grâce à Xpath

Capteurs et actionneurs HTTP

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 14 Déc 2017 20:59

:-)
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 26 Déc 2017 17:11

Bonjour,

J'ai donc bien reçu ma balance Nokia Body Cardio.
Effectivement, le script eedomus n'intègre que 3 relevés, ce qui est dommage par rapport à toutes les possibilités et remontées de la balance.
J'ai modifié le script PHP en remplaçant la ligne 92 :
$measure_type = array(1 => 'weight', 6 => 'fat_percent', 11 => 'heart_rate');

avec cette ligne :
$measure_type = array(1 => 'weight', 4 => 'height', 5 => 'fat_free_mass', 6 => 'fat_percent', 8 => 'fat_mass', 9 => 'DBP', 10 => 'SBP', 11 => 'heart_rate', 54 => 'SP02', 91 => 'pulse_wave_velocity');

Il faut que je teste avec plus d'infos, car visiblement la balance peut en remonter d'autres, mais je suis en aveugle sur comment les écrire pour respecter le format attendu.
Je suis en train de chercher un peu partout sur Internet.

J'ai trouvé ça, mais il faut déchiffrer et tester voir si c'est compatible, au moins pour certaines valeurs, avec la balance...
%measure_types = ( 1 => { name => "Weight (kg)", reading => "weight", },
4 => { name => "Height (meter)", reading => "height", },
5 => { name => "Lean Mass (kg)", reading => "fatFreeMass", },
6 => { name => "Fat Mass (%)", reading => "fatRatio", },
7 => { name => "Lean Mass (%)", reading => "fatFreeRatio", },
8 => { name => "Fat Mass (kg)", reading => "fatMassWeight", },
9 => { name => "Diastolic Blood Pressure (mmHg)", reading => "diastolicBloodPressure", },
10 => { name => "Systolic Blood Pressure (mmHg)", reading => "systolicBloodPressure", },
11 => { name => "Heart Rate (bpm)", reading => "heartPulse", },
12 => { name => "Temperature (°C)", reading => "temperature", },
13 => { name => "Humidity (%)", reading => "humidity", },
14 => { name => "unknown 14", reading => "unknown14", }, #device? event home - peak sound level?
15 => { name => "Noise (dB)", reading => "noise", },
18 => { name => "Weight Objective Speed", reading => "weightObjectiveSpeed", },
19 => { name => "Breastfeeding (s)", reading => "breastfeeding", }, #baby
20 => { name => "Bottle (ml)", reading => "bottle", }, #baby
22 => { name => "BMI", reading => "bmi", }, #user? goals
35 => { name => "CO2 (ppm)", reading => "co2", },
36 => { name => "Steps", reading => "steps", dailyreading => "dailySteps", }, #aggregate
37 => { name => "Elevation (m)", reading => "elevation", dailyreading => "dailyElevation", }, #aggregate
38 => { name => "Active Calories (kcal)", reading => "calories", dailyreading => "dailyCalories", }, #aggregate
39 => { name => "Intensity", reading => "intensity", }, #intraday only
40 => { name => "Distance (m)", reading => "distance", dailyreading => "dailyDistance", }, #aggregate #measure
41 => { name => "Descent (m)", reading => "descent", dailyreading => "dailyDescent", }, #descent #aggregate #measure ??sleepreading!
42 => { name => "Activity Type", reading => "activityType", }, #intraday only 1:walk 2:run
43 => { name => "Duration (s)", reading => "duration", }, #intraday only
44 => { name => "Sleep State", reading => "sleepstate", }, #intraday #aura mat
47 => { name => "MyFitnessPal Calories (kcal)", reading => "caloriesMFP", },
48 => { name => "Active Calories (kcal)", reading => "caloriesActive", dailyreading => "dailyCaloriesActive", }, #day summary
49 => { name => "Idle Calories (kcal)", reading => "caloriesPassive", dailyreading => "dailyCaloriesPassive", }, #aggregate
50 => { name => "unknown 50", reading => "unknown50", dailyreading => "dailyUnknown50", }, #day summary pulse 60k-80k #aggregate
51 => { name => "Light Activity (s)", reading => "durationLight", dailyreading => "dailyDurationLight", }, #aggregate
52 => { name => "Moderate Activity (s)", reading => "durationModerate", dailyreading => "dailyDurationModerate", }, #aggregate
53 => { name => "Intense Activity (s)", reading => "durationIntense", dailyreading => "dailyDurationIntense", }, #aggregate
54 => { name => "SpO2 (%)", reading => "spo2", },
56 => { name => "Ambient light (lux)", reading => "light", }, # aura device
57 => { name => "Respiratory rate", reading => "breathing", }, # aura mat #measure vasistas
58 => { name => "Air Quality (ppm)", reading => "voc", }, # Home Air Quality
59 => { name => "unknown 59", reading => "unknown59", }, #
60 => { name => "unknown 60", reading => "unknown60", }, # aura mat #measure vasistas 20-200 peak 800
61 => { name => "unknown 61", reading => "unknown61", }, # aura mat #measure vasistas 10-60 peak 600
62 => { name => "unknown 62", reading => "unknown62", }, # aura mat #measure vasistas 20-100
63 => { name => "unknown 63", reading => "unknown63", }, # aura mat #measure vasistas 0-100
64 => { name => "unknown 64", reading => "unknown64", }, # aura mat #measure vasistas 800-1300
65 => { name => "unknown 65", reading => "unknown65", }, # aura mat #measure vasistas 3000-4500 peak 5000
66 => { name => "unknown 66", reading => "unknown66", }, # aura mat #measure vasistas 4000-7000
67 => { name => "unknown 67", reading => "unknown67", }, # aura mat #measure vasistas 0-500 peak 1500
68 => { name => "unknown 68", reading => "unknown68", }, # aura mat #measure vasistas 0-1500
69 => { name => "unknown 69", reading => "unknown69", }, # aura mat #measure vasistas 0-6000 peak 10000
70 => { name => "unknown 70", reading => "unknown70", }, #?
71 => { name => "Body Temperature (°C)", reading => "bodyTemperature", }, #thermo
73 => { name => "Skin Temperature (°C)", reading => "skinTemperature", }, #thermo
76 => { name => "Muscle Mass (kg)", reading => "muscleMass", }, # cardio scale
77 => { name => "Water Mass (kg)", reading => "waterMass", }, # cardio scale
78 => { name => "unknown 78", reading => "unknown78", }, # cardio scale
79 => { name => "unknown 79", reading => "unknown79", }, # body scale
80 => { name => "unknown 80", reading => "unknown80", }, # body scale
86 => { name => "unknown 86", reading => "unknown86", }, # body scale
87 => { name => "Active Calories (kcal)", reading => "caloriesActive", dailyreading => "dailyCaloriesActive", }, # measures list sleepreading!
88 => { name => "Bone Mass (kg)", reading => "boneMassWeight", },
89 => { name => "unknown 89", reading => "unknown89", },
90 => { name => "unknown 90", reading => "unknown90", }, #pulse
91 => { name => "Pulse Wave Velocity (m/s)", reading => "pulseWave", }, # new weight
93 => { name => "Muscle Mass (%)", reading => "muscleRatio", }, # cardio scale
94 => { name => "Bone Mass (%)", reading => "boneRatio", }, # cardio scale
95 => { name => "Hydration (%)", reading => "hydration", }, # body water
#-10 => { name => "Speed", reading => "speed", },
#-11 => { name => "Pace", reading => "pace", },
#-12 => { name => "Altitude", reading => "altitude", }

Fred.
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 03 Jan 2018 15:54

Bonjour,

Bon, je n'arrive pas à trouver les bonnes valeurs dans le script pour faire remonter les valeurs suivantes :
Masse osseuse
Tension
IMC
Masse musculaire
Masse hydrique

Les seules valeurs qui remontent sont :
Poids
Masse grasse saine
Rythme cardiaque
Vitesse d'onde de pouls

Ca fait quand même peu vues les possibilités de la balance.
Quelqu'un connait t'il les lignes à rajouter dans le script pour remonter ces 5 valeurs ?

Merci.

Fred.
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar galaksy2001 » 06 Jan 2018 19:02

Quand tu fais un appel à l'API en utilisant la syntaxe suivante :

Code : Tout sélectionner
https://api.health.nokia.com/measure?action=getmeas&userid=xxxxxxx


tu n'as que les paramètres 1, 5, 6, 8 et 11 qui sont renseignés ( weight, fatFreeMass, fatRatio, fatMassWeight et heartPulse ).

En épluchant l'API dispo sur le site https://developer.health.nokia.com/api/doc, au chapitre "Measure - Get Body Measures" il y a pourtant beucoup plus de types dispos. J'en déduis que la limitation vient du modèle de la balance et/ou de l'user ID.
galaksy2001
 
Messages : 727
Inscription : 06 Jan 2014

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 06 Jan 2018 19:08

Bonjour,

Bizarre,

Quand je fais :
https://api.health.nokia.com/measure?action=getmeas&userid=xxxx

avec comme userid, l'id indiqué dans VAR 1 du périphérique créé par 'eedomus, ça me retourne :
{"status":2554}

et rien d'autre :-(
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar galaksy2001 » 06 Jan 2018 19:19

2554 : Wrong action or wrong webservice.

Tu as refait une inscription sur le site https://static.health.nokia.com/content/dashboard/fr/ ?
galaksy2001
 
Messages : 727
Inscription : 06 Jan 2014

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 06 Jan 2018 19:30

Je recommence et ça fonctionne maintenant ?!
La logique floue...
Bref, va falloir que j'étudie la page que j'ai en retour, car elle est pleine de valeurs..
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 06 Jan 2018 19:46

Bon, après étude de la page, j'ai pas mal de données qui remontent.
Ce sont les 12 valeurs suivantes :
1
5
6
8
11
76
77
88
91
93
94
95

Je ne sais pas pour toi si ces valeurs remontent de la même façon.

Ne sachant pas trop comment aller chercher et comment écrire les valeurs à chercher, j'avais modifié la ligne 92 du script withings_oauth.php hébergé sur l'eedomus comme ceci :
$measure_type = array(1 => 'weight', 4 => 'height', 5 => 'fat_free_mass', 6 => 'fat_percent', 8 => 'fat_mass', 7 => 'lean_mass', 7 => 'fat_free_ratio', 7 => 'fat_free_percent', 9 => 'diastolic_blood_pressure', 9 => 'dbp', 9 => 'DBP', 10 => 'systolic_blood_pressure', 10 => 'sbp', 10 => 'SBP', 11 => 'heart_rate', 22 => 'bmi', 54 => 'spo2', 76 => 'muscle_mass', 77 => 'water_mass', 88 => 'bone_mass', 88 => 'bone_mass_weight', 91 => 'pulse_wave_velocity', 93 => 'muscle_ratio', 93 => 'muscle_percent', 93 => 'muscle_mass', 95 => 'hydration');

J'ai donc 12 valeurs qui remontent sur 17 que je tente d'aller chercher, c'est déjà pas mal.
Par contre, je n'en ai que 6 qui remontent dans l'eedomus après pourtant avoir créé les périphériques avec l'Xpath qui va bien.

Par exemple, pour obtenir le résultat du 95 (hydratation), mon Xpath est :
/root/measure[1]/hydration
Mais je n'ai absolument rien en retour sur le xml généré par le script eedomus.

Ce xml contient juste les infos suivantes :
<root><measure><date>2018-01-05 20:38:32</date><pulse_wave_velocity>6.821</pulse_wave_velocity><heart_rate>82</heart_rate><weight>72.511</weight><fat_mass>12.39</fat_mass><fat_free_mass>60.121</fat_free_mass><fat_percent>17.087</fat_percent></measure></root>

A force de "gratter", on va bien trouver...
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar galaksy2001 » 06 Jan 2018 20:05

Ma balance est une Withings WS 50 : les seules données qui remontent via l'API sont celles que j'ai communiquées ( type 1, 5, 6, 8 et 11 ).
J'ai pourtant le taux de CO2 et la température qui apparaissent sur ma balance mais pas communiqués par L'API via cette syntaxe.
Quelles sont tes 6 valeurs qui remontent ?
galaksy2001
 
Messages : 727
Inscription : 06 Jan 2014

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 06 Jan 2018 20:13

Ma balance est un Nokia Cardio C8

Les valeurs qui remontent dans l'eedomus après avoir mofifié la ligne 92 du script sont les suivantes :

Le poids en Kg => 1
La Fat Free Mass en Kg => 5
La masse grasse saine en % => 6
La Fat Mass Weight en Kg => 8
Le rythme cardiaque en bps => 11
La vitesse d'onde de pouls => 91
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar galaksy2001 » 06 Jan 2018 20:26

C'est quand même déjà pas mal !

Pour les autres types (76, 77, 88, 93, 94, 95), je comprends pas pourquoi ça ne fonctionne pas car à priori c'est toi qui fixe, à la ligne 92 du script, le nom des types par rapport à leur numéro , il ne peut donc pas y avoir d'erreurs de syntaxes par rapport à ton chemin Xpath :?

Edith : pour le poids, la valeur "1", c'est de la coquetterie ou c'est un bug ?
galaksy2001
 
Messages : 727
Inscription : 06 Jan 2014

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 07 Jan 2018 11:06

galaksy2001 a écrit:C'est quand même déjà pas mal !

Pour les autres types (76, 77, 88, 93, 94, 95), je comprends pas pourquoi ça ne fonctionne pas car à priori c'est toi qui fixe, à la ligne 92 du script, le nom des types par rapport à leur numéro , il ne peut donc pas y avoir d'erreurs de syntaxes par rapport à ton chemin Xpath :?

Edith : pour le poids, la valeur "1", c'est de la coquetterie ou c'est un bug ?


C'est bien là le problème.
C'est à se demander s'il n'y a pas une restriction dans le script eedomus :-(

Concernant la valeur "1" pour le poids, c'est le codage imposé par Nokia ;-)

Fred.
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar dommarion » 16 Juin 2024 18:21

Bonjour à toutes et à tous,
J'ai adapté le plugin Withings pour lire la montre connectée ScanWatch.
Cela fonctionne plutôt bien et je récupère plein d'infos y compris des infos qui passent par mon iPhone avec l'application Health.
L'API Withings est à cette adresse: https://developer.withings.com/api-reference
J'ai amélioré ou développé:
[MODE]=measure et [ACTION]=getmeas ou getactivity
[MODE]=heart et [ACTION]=list
Ne pas hésitez pas à commenter.
bon weekend
dommarion
OpenWeather|Phases soleil|HeatzyV2|Concaténateur|HP Yamaha|SomfyV3|Epson|Seuils n étages|Baie 2 vantaux|Deezer|Intégrale|HTTP Auth. Digest|TV Philips|SmartThings|fonctions PHP|Tuya Smartlife|CozyTouch2|mySMS|TV Sony
dommarion
 
Messages : 636
Inscription : 28 Déc 2020

Re: Intégration de la balance Withings grâce à Xpath

Messagepar DomotiFred » 16 Juin 2024 18:33

Bonjour Dommarion,

Bravo :-)
3 Eedomus+ 96 Zwave 6 EnOcean 3 Xee 14 cam IP 1 Connexoon 8 SONOS 8 Google Home 7 ChromeCast 1 Synology 1513+ 1 Smartcoffee 1 robot tondeuse 1 Lametric Time 1 Nokia Cardio SARAH Infra LAN/WIFI en full Ubiquiti Serrure NUKI V1 et V2
DomotiFred
 
Messages : 1334
Inscription : 15 Mars 2013
Localisation : 78

Re: Intégration de la balance Withings grâce à Xpath

Messagepar dommarion » 05 Août 2024 15:21

Bonjour à toutes et tous,
Voilà le script modifé:
Code : Tout sélectionner
<?php
// script créé par Connected Object pour eedomus, basé sur la documentation API Withings: https://developer.withings.com/api-reference
// encodage iso-8859-1 pour les accents
// Modifié dommarion le 16/06/2024 Ajout des mesures d'une montre connectée
// l'appel url est le suivant:
// http://localhost/script/?exec=withings_oauth.php&mode=[MODE]&action=[ACTION]&history=true&userid=[VAR1]&debug=1
// [MODE]=user et [ACTION]=activate ou get ou getdevice ou getgoals ou link ou unlink
// [MODE]=measure et [ACTION]=getmeas ou getactivity ou getintradayactivity ou getworkouts
// [MODE]=heart et [ACTION]=get ou list
// [MODE]=stetho et [ACTION]=get ou list
// [MODE]=sleep et [ACTION]=get ou getsummary
// [MODE]=notify et [ACTION]=get ou list ou revoke ou subscribe ou update
// [MODE]=signature et [ACTION]=getnonce
// [MODE]=rawdata et [ACTION]=activate ou desactivate ou get
// [MODE]=device et [ACTION]=disablefeature ou enablefeature ou updatesimstatus
// Optionnels:
// history=true (pour récupérer l'intégralité de l'historique
// debug=1 (pour activer le mode debug)
//

$mode=array(
   'user',
   'measure',
   'heart',
   'stetho',
   'sleep',
   'notify',
   'signature',
   'rawdata',
   'device',
   'verify'
   );

$action=array(
   'activate',
   'desactivate',
   'disablefeature',
   'enablefeature',
   'get',
   'getactivity',
   'getdevice',
   'getgoals',
   'getintradayactivity',
   'getmeas',
   'getnonce',
   'getsummary',
   'getworkouts',
   'link',
   'list',
   'revoke',
   'subscribe',
   'unlink',
   'update',
   'updatesimstatus'
   );

$measure_type = array(
   1 => 'Weight',
   4 => 'Height',
   5 => 'Fat_Free_Mass',
   6 => 'Fat_Ratio',
   8 => 'Fat_Mass_Weight',
   9 => 'Diastolic_Blood_Pressure',
   10 => 'Systolic_Blood_Pressure',
   11 => 'Heart_Pulse',
   12 => 'Temperature',
   54 => 'SP02',
   71 => 'Body_Temperature',
   73 => 'Skin_Temperature',
   76 => 'Muscle_Mass',
   77 => 'Hydration',
   88 => 'Bone_Mass',
   91 => 'Pulse_Wave_Velocity',
   123 => 'VO2_Max',
   130 => 'Atrial_Fibrillation',
   135 => 'QRS_ECG_Signal',
   136 => 'PR_ECG_Signal',
   137 => 'QT_ECG_Signal',
   138 => 'Corrected_QT_ECG_Signal',
   139 => 'Atrial_Fibrillation_PPG',
   155 => 'Vascular_Age',
   167 => 'Nerve_Health_Score',
   168 => 'Extracellular_Water',
   169 => 'Intracellular_Water',
   170 => 'Visceral_Fat',
   174 => 'Fat_Mass',
   175 => 'Muscle_Mass_Segments',
   196 => 'Electrodermal_activity',
   226 => 'Basal_Metabolic_Rate'
   );

  $api_url = 'https://wbsapi.withings.net/v2/oauth2';

  $code = $_GET['oauth_code'];
 
  $userid = $_GET['userid']; // peut être vide
  $prev_code = loadVariable('code'.$userid);

  $mode_selection = getArg('mode', false, 'measure');

  if (!in_array($mode_selection, $mode, false)) {$mode_selection = 'measure';}

  $action_selection = getArg('action', false, 'getmeas');

  if (!in_array($action_selection, $action, false)) {$action_selection = 'getmeas';}

  $history = getArg('history', false, NULL);

  $date=date("Y-m-d",time());
  $startdate=strtotime(date($date.' 00:00:00'));
  $startdateymd=date("Y-m-d",$startdate);
  $enddate=time();
  $enddateymd=date("Y-m-d",$enddate);

  $debug = getArg('debug', false, NULL);

  if ($code == '')
  {
    $code = loadVariable('code'.$userid);
  }

  if (strlen($prev_code) > 1 && $code == $prev_code)
  {
    // on reprend le dernier refresh_token seulement s'il correspond au même code
    $refresh_token = loadVariable('refresh_token'.$userid);
    $expire_time = loadVariable('expire_time'.$userid);
    // s'il n'a pas expiré, on peut reprendre l'access_token
    if (time() < $expire_time)
    {
      $access_token = loadVariable('access_token'.$userid);
    }
  }

  // on a déjà un token d'accés non expiré pour le code demandé
  if ($access_token == '')
  {
    if (strlen($refresh_token) > 1)
    {
      // on peut juste rafraichir le token
      $grant_type = 'refresh_token';
      $postdata = 'action=requesttoken&grant_type='.$grant_type.'&refresh_token='.$refresh_token;
    }
    else
    {
      // 1ère utilisation aprés obtention du code
      $grant_type = 'authorization_code';
         $redirect_uri = 'https://secure.eedomus.com/sdk/plugins/withings/callback.php';
      $postdata = 'action=requesttoken&grant_type='.$grant_type.'&code='.$code.'&redirect_uri='.$redirect_uri;
    }
     
    $response = httpQuery($api_url, 'POST', $postdata, 'withings_oauth');
   
    if ($debug == 1) {var_dump($url, $response);}

    $json = sdk_json_decode($response);
    $params = $json['body'];
    if ($params['error'] != '')
    {
      die("Erreur lors de l'authentification".": [".$params['error'].'] (grant_type='.$grant_type.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
    }
    // on sauvegarde l'access_token et le refresh_token pour les authentifications suivantes
   
    if (isset($params['access_token']) && !isset($params['refresh_token']))
    {
      $access_token = $params['access_token'];
      saveVariable('access_token'.$userid, $access_token);
      saveVariable('expire_time'.$userid, time()+$params['expires_in']);
      saveVariable('code'.$userid, $code);
    }
    else if (isset($params['access_token']) && isset($params['refresh_token']))
    {
         $access_token = $params['access_token'];
         saveVariable('access_token'.$userid, $access_token);
         saveVariable('refresh_token'.$userid, $params['refresh_token']);
         saveVariable('expire_time'.$userid, time()+$params['expires_in']);
         if ($code != '')
         {
            saveVariable('code'.$userid, $code);
         }
    }
    else if ($access_token == '')
    {
      die("Erreur lors de l'authentification, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique\n\n".$response);
    }


   if ($mode_selection == 'verify')
   {
      ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <title>eedomus</title>
      <style type="text/css">

      body,td,th {
         font-family: Arial, Helvetica, sans-serif;
         font-size: 14px;
      }
      </style>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
      </head><?

      $userid = $params['userid'];

      // sauvegarde pour l'utilisateur donné
      saveVariable('access_token'.$userid, $access_token);
      if ($params['refresh_token'] != '')
      {
         saveVariable('refresh_token'.$userid, $params['refresh_token']);
      }
      saveVariable('expire_time'.$userid, time()+$params['expires_in']);
      if ($code != '')
      {
         saveVariable('code'.$userid, $code);
      }

      echo "<img width='64' src='https://secure.eedomus.com/img/mdm/01/check.png' align='absmiddle'> Votre box eedomus est maintenant autorisée à consulter vos données Withings !";
      echo '<br><br>';
      echo "Votre <b>Code utilisateur</b> Withings est : ".'<input onclick="this.select();" type="text" size="10" readonly="readonly" value="'.$userid .'" />, vous pouvez le copier-coller dans votre périphérique Withings eedomus.';
      die();
  }
}

$userid = getArg('userid');

$lastupdate = loadVariable('lastupdate'.$userid);
if ($lastupdate == '') { $lastupdate = 0; } // tout l'historique

   
if (($mode_selection == 'user') && ($action_selection == 'getdevice'))
{   
   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token";
   $response = httpQuery($url, 'GET');

   if ($debug == 1) {var_dump($url, $response);}

   $json = sdk_json_decode($response);
   
   if ($json['error'] != '')
   {
      if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
      {
         // force the use of the refresh token
         saveVariable('expire_time'.$userid, 0);
      }
   
      die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
   }

   sdk_header('text/xml');
   $new_xml = "<root>";
   $has_devices = false;
   for($i = 0; $i < sizeof($json['body']['devices']); $i++)
   {
      if ($i == 0 || $json['body']['devices'][$i]['deviceid'] != $json['body']['devices'][$i-1]['deviceid'])
      {
         $new_xml .= "<device>";
         $new_xml .= "<type>".$json['body']['devices'][$i]['type']."</type>";
         $new_xml .= "<battery>".$json['body']['devices'][$i]['battery']."</battery>";
         $new_xml .= "<model>".$json['body']['devices'][$i]['model']."</model>";
         $new_xml .= "<model_id>".$json['body']['devices'][$i]['model_id']."</model_id>";
         $new_xml .= "<deviceid>".$json['body']['devices'][$i]['deviceid']."</deviceid>";
         $new_xml .= "<hash_deviceid>".$json['body']['devices'][$i]['hash_deviceid']."</hash_deviceid>";
         $new_xml .= "</device>";
         $has_devices = true;
      }
   }
   $new_xml .= "<token>".$access_token."</token>";
   $new_xml .= "</root>";
   
   if (!$has_devices)

   {
      saveVariable('lastupdate'.$userid, 0);
   }
   
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml)
   {
      saveVariable('last_xml_time_'.$userid, time());
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '')
      {
         saveVariable('lastupdate'.$userid, $last_date);
      }
   }
   echo $new_xml;
}

if (($mode_selection == 'user') && ($action_selection == 'getgoals'))
{   
   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token";
   $response = httpQuery($url, 'GET');

   if ($debug == 1) {var_dump($url, $response);}

   $json = sdk_json_decode($response);
   
   if ($json['error'] != '')
   {
      if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
      {
         // force the use of the refresh token
         saveVariable('expire_time'.$userid, 0);
      }
   
      die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
   }

   sdk_header('text/xml');
   $new_xml = "<root>";
   $new_xml .= "<goals>";
   $has_goals = false;
   if (sizeof($json['body']['goals'])>0)
   {   
      $new_xml .= "<steps>".$json['body']['goals']['steps']."</steps>";
      $new_xml .= "<sleep>".$json['body']['goals']['sleep']."</sleep>";
      $new_xml .= "<weight>";
      $new_xml .= "<value>".$json['body']['goals']['weight']['value']."</value>";
      $new_xml .= "<unit>".$json['body']['goals']['weight']['unit']."</unit>";
      $new_xml .= "</weight>";
      $has_goals = true;
   }
   $new_xml .= "</goals>";
   $new_xml .= "<token>".$access_token."</token>";
   $new_xml .= "</root>";
   
   if (!$has_devices)

   {
      saveVariable('lastupdate'.$userid, 0);
   }
   
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml)
   {
      saveVariable('last_xml_time_'.$userid, time());
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '')
      {
         saveVariable('lastupdate'.$userid, $last_date);
      }
   }
   echo $new_xml;
}

if (($mode_selection == 'measure') && ($action_selection == 'getactivity'))
{   
   if (isset($history))
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&lastupdate=0";}
   else
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&startdateymd=$startdateymd&enddateymd=$enddateymd";}
   $response = httpQuery($url, 'GET');

   if ($debug == 1) {var_dump($url, $response);}

   $json = sdk_json_decode($response);
   
   if ($json['error'] != '')
   {
      if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
      {
         // force the use of the refresh token
         saveVariable('expire_time'.$userid, 0);
      }
   
      die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
   }

   sdk_header('text/xml');
   $new_xml = "<root>";
   $has_activities = false;
   for($i = 0; $i < sizeof($json['body']['activities']); $i++)
   {
      if ($i == 0 || $json['body']['activities'][$i]['date'] != $json['body']['activities'][$i-1]['date'])
      {
         $new_xml .= "<activity>";
         $new_xml .= "<date>".$json['body']['activities'][$i]['date']."</date>";
         $new_xml .= "<steps>".$json['body']['activities'][$i]['steps']."</steps>";
         $new_xml .= "<distance>".$json['body']['activities'][$i]['distance']."</distance>";
         $new_xml .= "<calories>".$json['body']['activities'][$i]['calories']."</calories>";
         $new_xml .= "<model>".$json['body']['activities'][$i]['model']."</model>";
         $new_xml .= "</activity>";
         $last_date = $json['body']['activities'][$i]['modified'];
         $has_activities = true;
      }
   }
   $new_xml .= "<token>".$access_token."</token>";
   $new_xml .= "</root>";
   
   if (!$has_activities)

   {
      saveVariable('lastupdate'.$userid, 0);
   }
   
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml)
   {
      saveVariable('last_xml_time_'.$userid, time());
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '')
      {
         saveVariable('lastupdate'.$userid, $last_date);
      }
   }
   echo $new_xml;
}

if (($mode_selection == 'measure') && ($action_selection == 'getintradayactivity'))
{   
   if (isset($history))
   {   $startdate=$enddate-3*24*60*60;
      $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&startdate=$startdate&enddate=$enddate";}
   else
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&startdateymd=$startdateymd&enddateymd=$enddateymd";}
   $response = httpQuery($url, 'GET');

   if ($debug == 1) {var_dump($url, $response);}

   $json = sdk_json_decode($response);
   
   if ($json['error'] != '')
   {
      if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
      {
         // force the use of the refresh token
         saveVariable('expire_time'.$userid, 0);
      }
   
      die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
   }

   sdk_header('text/xml');
   $new_xml = "<root>";
   $has_intradayactivities = false;
   if (count($json['body']['series'])>0)
   {
      $new_xml .="<intradayactivity>";
      foreach ($json['body']['series'] as $key=>$value)
      {
         $new_xml .= "<$key>";
         foreach ($value as $key2=>$value2)
         {
            $new_xml .= "<$key2>".$value2."</$key2>";
         }
         $new_xml .= "</$key>";
      }
      $new_xml .="</intradayactivity>";
      $has_intradayactivities = true;
   }
   $new_xml .= "<token>".$access_token."</token>";
   $new_xml .= "</root>";
   
   if (!$has_intradayactivities)

   {
      saveVariable('lastupdate'.$userid, 0);
   }
   
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml)
   {
      saveVariable('last_xml_time_'.$userid, time());
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '')
      {
         saveVariable('lastupdate'.$userid, $last_date);
      }
   }
   echo $new_xml;
}

if (($mode_selection == 'measure') && ($action_selection == 'getworkouts'))
{   
   if (isset($history))
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&lastupdate=0";}
   else
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&startdateymd=$startdateymd&enddateymd=$enddateymd";}
   $response = httpQuery($url, 'GET');

   if ($debug == 1) {var_dump($url, $response);}

   $json = sdk_json_decode($response);
   
   if ($json['error'] != '')
   {
      if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
      {
         // force the use of the refresh token
         saveVariable('expire_time'.$userid, 0);
      }
   
      die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
   }

   sdk_header('text/xml');
   $new_xml = "<root>";
   $has_workouts = false;
   for($i = 0; $i < sizeof($json['body']['series']); $i++)
   {
      if ($i == 0 || $json['body']['series'][$i]['date'] != $json['body']['series'][$i-1]['date'])
      {
         $new_xml .= "<workout>";
         $new_xml .= "<date>".$json['body']['series'][$i]['date']."</date>";
         $new_xml .= "<deviceid>".$json['body']['series'][$i]['deviceid']."</deviceid>";
         $new_xml .= "<calories>".$json['body']['series'][$i]['data']['calories']."</calories>";
         $new_xml .= "<intensity>".$json['body']['series'][$i]['data']['intensity']."</intensity>";
         $new_xml .= "<steps>".$json['body']['series'][$i]['data']['steps']."</steps>";
         $new_xml .= "<distance>".$json['body']['series'][$i]['data']['distance']."</distance>";
         $new_xml .= "</workout>";
         $last_date = $json['body']['series'][$i]['modified'];
         $has_workouts = true;
      }
   }
   $new_xml .= "<token>".$access_token."</token>";
   $new_xml .= "</root>";
   
   if (!$has_workouts)

   {
      saveVariable('lastupdate'.$userid, 0);
   }
   
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml)
   {
      saveVariable('last_xml_time_'.$userid, time());
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '')
      {
         saveVariable('lastupdate'.$userid, $last_date);
      }
   }
   echo $new_xml;
}

if (($mode_selection == 'sleep') && ($action_selection == 'get'))
{   
   if (isset($history))
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&data_fields=hr,rr,snoring,sdnn_1,rmssd,mvt_score&access_token=$access_token&startdate=0&enddate=$enddate";}
   else
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&data_fields=hr,rr,snoring,sdnn_1,rmssd,mvt_score&access_token=$access_token&startdate=$startdate&enddate=$enddate";}
   $response = httpQuery($url, 'GET');

   if ($debug == 1) {var_dump($url, $response);}

   $json = sdk_json_decode($response);
   
   if ($json['error'] != '')
   {
      if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
      {
         // force the use of the refresh token
         saveVariable('expire_time'.$userid, 0);
      }
   
      die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
   }

   sdk_header('text/xml');
   $new_xml = "<root>";
   $has_sleep = false;
   for($i = 0; $i < sizeof($json['body']['series']); $i++)
   {
      if (isset($json['body']['series'][$i]['hr']))
      {   
         if ($i == 0 || $json['body']['series'][$i]['startdate'] != $json['body']['series'][$i-1]['startdate'])
         {
            $new_xml .= "<sleep>";
            $new_xml .= "<startdate>".date('Y-m-d H:i:s',$json['body']['series'][$i]['startdate'])."</startdate>";
            $new_xml .= "<enddate>".date('Y-m-d H:i:s',$json['body']['series'][$i]['enddate'])."</enddate>";
            $new_xml .= "<model>".$json['body']['series'][$i]['model']."</model>";
            $j=0;
            $moyenne=0;
            foreach ($json['body']['series'][$i]['hr'] as $key=>$value)
            {
               $new_xml .= "<hr_time$j>".date("Y-m-d H:i:s",$key)."</hr_time$j>";
               $new_xml .= "<hr_value$j>".$value."</hr_value$j>";
               $j++;
               $moyenne = $moyenne+$value;
            }
            $moyenne = round($moyenne/$j,0);
            $new_xml .= "<hr_average>".$moyenne."</hr_average>";
            $new_xml .= "</sleep>";
            $has_sleep = true;
         }
      }
   }
   $new_xml .= "<token>".$access_token."</token>";
   $new_xml .= "</root>";
   
   if (!$has_sleep)

   {
      saveVariable('lastupdate'.$userid, 0);
   }
   
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml)
   {
      saveVariable('last_xml_time_'.$userid, time());
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '')
      {
         saveVariable('lastupdate'.$userid, $last_date);
      }
   }
   echo $new_xml;
}

if (($mode_selection == 'sleep') && ($action_selection == 'getsummary'))
{   
   if (isset($history))
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&lastupdate=0";}
   else
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&startdateymd=$startdateymd&enddateymd=$enddateymd";}
   $response = httpQuery($url, 'GET');

   if ($debug == 1) {var_dump($url, $response);}

   $json = sdk_json_decode($response);
   
   if ($json['error'] != '')
   {
      if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
      {
         // force the use of the refresh token
         saveVariable('expire_time'.$userid, 0);
      }
   
      die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
   }

   sdk_header('text/xml');
   $new_xml = "<root>";
   $has_sleep = false;
   for($i = 0; $i < sizeof($json['body']['series']); $i++)
   {
      if (isset($json['body']['series'][$i]['data']))
      {   
         if ($i == 0 || $json['body']['series'][$i]['startdate'] != $json['body']['series'][$i-1]['startdate'])
         {
            $new_xml .= "<sleep_summary>";
            $new_xml .= "<startdate>".date('Y-m-d H:i:s',$json['body']['series'][$i]['startdate'])."</startdate>";
            $new_xml .= "<enddate>".date('Y-m-d H:i:s',$json['body']['series'][$i]['enddate'])."</enddate>";
            $new_xml .= "<data>";
            foreach ($json['body']['series'][$i]['data'] as $key=>$value)
            {
               $new_xml .= "<$key>".$value."</$key>";
            }
            $new_xml .= "</data>";
            $new_xml .= "</sleep_summary>";
            $has_sleep = true;
         }
      }
   }
   $new_xml .= "<token>".$access_token."</token>";
   $new_xml .= "</root>";
   
   if (!$has_sleep)

   {
      saveVariable('lastupdate'.$userid, 0);
   }
   
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml)
   {
      saveVariable('last_xml_time_'.$userid, time());
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '')
      {
         saveVariable('lastupdate'.$userid, $last_date);
      }
   }
   echo $new_xml;
}


if (($mode_selection == 'measure') && ($action_selection == 'getmeas'))
{
   if (isset($history))
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&lastupdate=0";}
   else
   {   $url = "https://wbsapi.withings.net/v2/$mode_selection?action=$action_selection&access_token=$access_token&startdate=$startdate&enddate=$enddate";}
         
   $response = httpQuery($url, 'GET');

   if ($debug == 1) {var_dump($url, $response);}

   $json = sdk_json_decode($response);
   
   if ($json['error'] != '')
   {
      if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
      {
         // force the use of the refresh token
         saveVariable('expire_time'.$userid, 0);
      }
   
      die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n\n".$response);
   }

   sdk_header('text/xml');
   $new_xml = "<root>";
   $has_measures = false;
   for($i = 0; $i < sizeof($json['body']['measuregrps']); $i++)
   {
      if ($i == 0 || $json['body']['measuregrps'][$i]['date'] != $json['body']['measuregrps'][$i-1]['date'])
      {
         $new_xml .= "<measure>";
         $new_xml .= "<date>".date('Y-m-d H:i:s', $json['body']['measuregrps'][$i]['date']*1)."</date>";
         $last_date = $json['body']['measuregrps'][$i]['date']*1;
      }
      for($j = 0; $j < sizeof($json['body']['measuregrps'][$i]['measures']); $j++)
      {
         
         $type = $measure_type[$json['body']['measuregrps'][$i]['measures'][$j]['type']*1];
         if ($type == '')
         {
            $type = "unknown_type_".$json['body']['measuregrps'][$i]['measures'][$j]['type'];
         }
         
         if ($type != '')
         {
            $new_xml .= "<$type>".($json['body']['measuregrps'][$i]['measures'][$j]['value']*pow(10, $json['body']['measuregrps'][$i]['measures'][$j]['unit']))."</$type>";
            $has_measures = true;
         }
      }
      if ($i<sizeof($json['body']['measuregrps'])-1)
      {
         if (($json['body']['measuregrps'][$i+1]['date'] != $json['body']['measuregrps'][$i]['date']) && has_measures)
         {
            $new_xml .= "</measure>";
         }
      }
   }
   $new_xml .= "<token>".$access_token."</token>";
   $new_xml .= "<userid>".$userid."</userid>";
   $new_xml .= "</root>";
   
   if (!$has_measures)
   {
      saveVariable('lastupdate'.$userid, 0);
   }
   
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml)
   {
      saveVariable('last_xml_time_'.$userid, time());
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '')
      {
         saveVariable('lastupdate'.$userid, $last_date);
      }
   }
   echo $new_xml;
}
?>

OPA95 travaille le sujet aussi.
Bonne semaine
dommarion
OpenWeather|Phases soleil|HeatzyV2|Concaténateur|HP Yamaha|SomfyV3|Epson|Seuils n étages|Baie 2 vantaux|Deezer|Intégrale|HTTP Auth. Digest|TV Philips|SmartThings|fonctions PHP|Tuya Smartlife|CozyTouch2|mySMS|TV Sony
dommarion
 
Messages : 636
Inscription : 28 Déc 2020

Re: Intégration de la balance Withings grâce à Xpath

Messagepar opa95 » 12 Août 2024 13:48

Bonjour à toutes et à tous
J'ai changé ma balance Withing de base pour une Withing "body smart".
Voici une version de script compatible avec celui de dommarion (merci), mais simplifié (en attendant une version complète de dommarion ou pas) qui ne prend en compte que les valeurs "measures", donc compatible a priori avec les balances et peut-être d'autres objets).
Sur la "body smart", je récupère tout ce qui est récupérable. Pour traiter d'autres balances, il me faudrait le "json" de retour de la balance correspondante.
Pour le faire fonctionner, on peut remplacer l'ancien script withing_oauth.php d'eedomus par ce script (même nom).
Pour les anciens devices (3), il faut remplacer les anciens XPATH (Majuscules compatibles avec dommarion)
Rythme cardiaque : /root/measure[1]/heart_rate par //Pulse_Rate
Poids : /root/measure[1]/weight par //Weight
Masse grasse : /root/measure[1]/fat_percent par //Fat_Ratio
On peut ajouter
Basal_Metabolic_Rate (kCal/jour) : //Basal_Metabolic_Rate
Masse musculaire (kg) : //Muscle_Mass
Pourcentage Muscle (%) : //Muscle_Mass_Ratio
Masse osseuse (kg) : //Bone_Mass
Pourcentage os (%) //Bone_Mass_Ratio
Masse eau (kg) : //Hydration
Pourcentage eau (%) ://Hydration_Ratio
Taux de gras viscéral : //Visceral_Fat
Masse grasse (kg) : //Fat_Mass_Weight
Masse non grasse : //Fat_Free_Mass
Pourcentage de masse grasse (%) : //Fat_Ratio
IMC : //Body_Mass_Index : il faut fournir la taille en mètre (1.83) en [VAR2] et modifier le ligne de commande en
Code : Tout sélectionner
http://localhost/script/?exec=withings_oauth.php&userid=[VAR1]&height=[VAR2]&period=[VAR3]

On peut avoir les valeurs moyennes (1 semaine par défaut) en mettant dans XPATH par exemple pour le poids
Code : Tout sélectionner
sum(//Weight)div(count(//Weight))

On peut modifier la durée des moyennes en mettant le nombre de jours en VAR3
Pour une balance, en général une mesure par jour suffit, on peut passer la fréquence à 60 (l'appel à l'API withing ne se fait de toute façon que toutes les "deltat" (59 minutes). On peut ajouter à la ligne de commande "&deltat=x" ou dans VAR3
Code : Tout sélectionner
7&deltat=179
si on veut modifier cette valeur par défaut (x= 0 force la relecture depuis l'API, sinon on relit la dernière copie du XML). Ca ne donne que 3 appels par jours au lieu 24x8 ou 48x8.
Perso, je n'ai mis une fréquence nulle et une règle avec 3 lectures entre 7h et 10h et un bouton pour forcer la lecture si on est pressé. :)
Code : Tout sélectionner
<?php
// script créé par Connected Object pour eedomus
// encodage iso-8859-1 pour les accents
// Modifié dommarion le 16/06/2024 Ajout des mesures d'une montre connectée
// aout 2024 : ajout nouvelles valeurs et nouvelles données balance (opa95)
// possibilite de renseigner la taille pour avoir l'IMC
// Commande http://localhost/script/?exec=withings_oauth.php&mode=measure&action=getmeas&userid=[VAR1]&height=[VAR2]&period=[VAR3]
// ou pour mesure http://localhost/script/?exec=withings_oauth.phpuserid=[VAR1]&height=[VAR2]&period=[VAR3]
// Commandes complémentaires
//   &print=json -> impression json et var_dump, Résultat du XPATH valide
//   &deltat=x -> écart minimal entre 2 lectures (x minutes)
// [VAR1] -> identifiant Withing
// [VAR2] -> vide ou taille en metres 1.80, 0 par défaut
// [VAR3] -> vide ou période de relévés, par défaut 7; si 0 -> illimité si historique pas trop long (balance, 1 mesure/jour -> plante si valeur>environ 700)
// 7 derniers jours
// dernières valeurs XPATH -> //Nom ou /root/measure[1]/Nom
// moyennes XPATH -> sum(//Nom)div(count(//Npm)) moyenne semaine
// Nom parmi la liste ci-dessous
/*
   1 => 'Weight',
   4 => 'Height',
   5 => 'Fat_Free_Mass',
   6 => 'Fat_Ratio',
   8 => 'Fat_Mass_Weight',
   9 => 'Diastolic_Blood_Pressure',
   10 => 'Systolic_Blood_Pressure',
   11 => 'Heart_Pulse',
   12 => 'Temperature',
   54 => 'SP02',
   71 => 'Body_Temperature',
   73 => 'Skin_Temperature',
   76 => 'Muscle_Mass',
   77 => 'Hydration',
   88 => 'Bone_Mass',
   91 => 'Pulse_Wave_Velocity',
   123 => 'VO2_max',
   130 => 'Atrial_fibrillation',
   135 => 'QRS_ECG_Signal',
   136 => 'PR_ECG_Signal',
   137 => 'QT_ECG_Signal',
   138 => 'Corrected_QT_ECG_Signal',
   139 => 'Atrial_Fibrillation_PPG',
   155 => 'Vascular_Age',
   167 => 'Nerve_Health_Score',
   168 => 'Extracellular_Water',
   169 => 'Intracellular_Water',
   170 => 'Visceral_Fat',
   174 => 'Fat_Mass_Segments',
   175 => 'Muscle_Mass_Segments',
   196 => 'Electrodermal_Activity',
   226 => 'Basal_Metabolic_Rate'   
   autres
      Date, Fat_Free_Ratio, Muscle_Mass_Ratio, Hydration_Ratio, Bone_Mass_Ratio, Heart
Depuis la fenêtre de test &print=json renvoie les valeurs brutes et le tableau
*/
/*
mémoires internes :
$code = $_GET['oauth_code'] si vide $code = loadVariable('code'.$userid);
$userid = $_GET['userid']; $userid : contenu de VAR1
'code'.$userid -> $prev_code
'refresh_token'.$userid -> $refresh_token
'expire_time'.$userid -> $expire_time
'access_token'.$userid -> access_token
'lastupdate'.$userid -> lastupdate
'last_xml_'.$userid -> $last_xml
'last_xml_time_'.$userid -> last_xmltime
*/
function sdk_abort($comment) {
   global $params,$access_token,$response;
         die("Erreur lors de l'authentification".": [".$params['error'].'] (access_token = '.$access_token.')<br>, vous pouvez lier à nouveau votre compte en cliquant sur [Lier à nouveau] depuis la configuration de votre périphérique'."\n".$comment."\n".$response);
}

$mode=array(
   'user',
   'measure',
   'heart',
   'stetho',
   'sleep',
   'notify',
   'signature',
   'rawdata',
   'device',
   'verify'
   );

$action=array(
   'activate',
   'desactivate',
   'disablefeature',
   'enablefeature',
   'get',
   'getactivity',
   'getdevice',
   'getgoals',
   'getintradayactivity',
   'getmeas',
   'getnonce',
   'getsummary',
   'getworkouts',
   'link',
   'list',
   'revoke',
   'subscribe',
   'unlink',
   'update',
   'updatesimstatus'
   );

$measure_type = array(
   1 => 'Weight',
   4 => 'Height',
   5 => 'Fat_Free_Mass',
   6 => 'Fat_Ratio',
   8 => 'Fat_Mass_Weight',
   9 => 'Diastolic_Blood_Pressure',
   10 => 'Systolic_Blood_Pressure',
   11 => 'Heart_Pulse',
   12 => 'Temperature',
   54 => 'SP02',
   71 => 'Body_Temperature',
   73 => 'Skin_Temperature',
   76 => 'Muscle_Mass',
   77 => 'Hydration',
   88 => 'Bone_Mass',
   91 => 'Pulse_Wave_Velocity',
   123 => 'VO2_Max',
   130 => 'Atrial_Fibrillation',
   135 => 'QRS_ECG_Signal',
   136 => 'PR_ECG_Signal',
   137 => 'QT_ECG_Signal',
   138 => 'Corrected_QT_ECG_Signal',
   139 => 'Atrial_Fibrillation_PPG',
   155 => 'Vascular_Age',
   167 => 'Nerve_Health_Score',
   168 => 'Extracellular_Water',
   169 => 'Intracellular_Water',
   170 => 'Visceral_Fat',
   174 => 'Fat_Mass_Segments',
   175 => 'Muscle_Mass_Segments',
   196 => 'Electrodermal_Activity',
   226 => 'Basal_Metabolic_Rate'
   );

   $api_url = 'https://wbsapi.withings.net/v2/oauth2';

   $code = $_GET['oauth_code'];
   
   $userid = $_GET['userid']; // peut être vide ssi demande d'association
   $prev_code = loadVariable('code'.$userid);

   $mode_selection = getArg('mode', false, 'measure');

   if (!in_array($mode_selection, $mode, false)) {$mode_selection = 'measure';}

   $action_selection = getArg('action', false, 'getmeas');

   if (!in_array($action_selection, $action, false)) {$action_selection = 'getmeas';}

   $history = getArg('history', false, NULL);

   $date=date("Y-m-d",time());
   $startdate=strtotime(date($date.' 00:00:00'));
   $startdateymd=date("Y-m-d",$startdate);
   $enddate=time();
   $enddateymd=date("Y-m-d",$enddate);

   $debug = getArg('debug', false, NULL);

//******** Lecture du Code d'accès *********
   if ($code == '')   {$code = loadVariable('code'.$userid);}
   if (strlen($prev_code) > 1 && $code == $prev_code) {
      // on reprend le dernier refresh_token seulement s'il correspond au même code
      $refresh_token = loadVariable('refresh_token'.$userid);//code pour rafraichir l'access_token
      $expire_time = loadVariable('expire_time'.$userid);// expiration de l'access_code (3 heures)
      // s'il n'a pas expiré, on peut reprendre l'access_token
      if (time() < $expire_time) {
         // on a déjà un token d'accés non expiré pour le code demandé
         $access_token = loadVariable('access_token'.$userid);
      }
   }
   if ($access_token == '') {//obtention d'un code valide
      if (strlen($refresh_token) > 1) {
         // on peut juste rafraichir le token
         $grant_type = 'refresh_token';
         $postdata = 'action=requesttoken&grant_type='.$grant_type.'&refresh_token='.$refresh_token;
      } else {
         // 1ère utilisation après obtention du code
         $grant_type = 'authorization_code';
         $redirect_uri = 'https://secure.eedomus.com/sdk/plugins/withings/callback.php';
         $postdata = 'action=requesttoken&grant_type='.$grant_type.'&code='.$code.'&redirect_uri='.$redirect_uri;
      }
      $response = httpQuery($api_url, 'POST', $postdata, 'withings_oauth');
      $json = sdk_json_decode($response);
      $params = $json['body'];
      if ($params['error'] != '') {sdk_abort(1);}
      // on sauvegarde l'access_token et le refresh_token pour les authentifications suivantes
   
      if (isset($params['access_token']) && !isset($params['refresh_token'])) {
         $access_token = $params['access_token'];
         saveVariable('access_token'.$userid, $access_token);
         saveVariable('expire_time'.$userid, time()+$params['expires_in']);
         saveVariable('code'.$userid, $code);
      } elseif (isset($params['access_token']) && isset($params['refresh_token'])){
         $access_token = $params['access_token'];
         saveVariable('access_token'.$userid, $access_token);
         saveVariable('refresh_token'.$userid, $params['refresh_token']);
         saveVariable('expire_time'.$userid, time()+$params['expires_in']);
         if ($code != '') {saveVariable('code'.$userid, $code);}
   } elseif ($access_token == '') {sdk_abort(2);}

   if ($_GET['mode'] == 'verify') {
      ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <title>eedomus</title>
      <style type="text/css">

      body,td,th {
         font-family: Arial, Helvetica, sans-serif;
         font-size: 14px;
      }
      </style>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
      </head><?

      $userid = $params['userid'];

      // sauvegarde pour l'utilisateur donné
      saveVariable('access_token'.$userid, $access_token);
      if ($params['refresh_token'] != '') {saveVariable('refresh_token'.$userid, $params['refresh_token']);}
      saveVariable('expire_time'.$userid, time()+$params['expires_in']);
      if ($code != '') {saveVariable('code'.$userid, $code);}

      echo "<img width='64' src='https://secure.eedomus.com/img/mdm/01/check.png' align='absmiddle'> Votre box eedomus est maintenant autorisée à consulter vos données Withings !";
      echo '<br><br>';
      echo "Votre <b>Code utilisateur</b> Withings est : ".'<input onclick="this.select();" type="text" size="10" readonly="readonly" value="'.$userid .'" />, vous pouvez le copier-coller dans votre périphérique Withings eedomus.';
      die();
      }
   }

//******** Programme de lecture des données *********

$userid = getArg('userid');
$lastupdate = loadVariable('lastupdate'.$userid);
sdk_header('text/xml');
echo '<?xml version="1.0" encoding="utf8" ?>'."\n";
if (($mode_selection == 'measure') && ($action_selection == 'getmeas'))
{   
   define('PERIOD',7);//Période par défaut (7 jours)//opa95
   define('DELTATIME',59);//Validité du XML (59 minutes)//opa95
   $period = getArg('period',false,PERIOD);
   $delta_Time = getArg('deltat',false,DELTATIME);
   $use_last_xml = ( ( (loadVariable('last_xml_time_'.$userid)+$delta_Time*60)>time()) || $last_update=0);
   if ($use_last_xml) {$new_xml = $last_xml = loadVariable('last_xml_'.$userid);}
   else {
      if ( ($lastupdate == '') || ($period == 0) ) {
         $lastupdate = 0;// tout l'historique//opa95
         $url = "https://wbsapi.withings.net/measure?action=getmeas&access_token=$access_token&lastupdate=$lastupdate";
      } else {
         $startdate -= 86400*($period-1);
         $url = "https://wbsapi.withings.net/measure?action=getmeas&access_token=$access_token&startdate=$startdate&enddate=$enddate";
      }
   //$info=array();
   //   $response = httpQuery($url, 'GET',NULL,NULL,NULL,false, false, $info);
      $response = httpQuery($url, 'GET');
   //   var_dump($info); die();
      
      if ($debug) {echo "<--\n";var_dump($url, $response);echo "-->\n";}

      $json = sdk_json_decode($response);
      
      if ($json['error'] != '') {
         if ($json['status'] == '401') // XRequestID: Not provided invalid_token: The access token provided is invalid
         {
            // force the use of the refresh token for next access
            saveVariable('expire_time'.$userid, 0);
         }
         sdk_abort("$mode_selection : $action_selection");
      }
      
      if ($_GET['print'] == 'json') {
         echo "<!--\n$response\n";
         var_dump($json);
         echo "-->\n";
      }

      $has_measures = false;
      $new_xml = "<root>";

      for($i = 0; $i < sizeof($json['body']['measuregrps']); $i++) {
         if ($i == 0 || $json['body']['measuregrps'][$i]['date'] != $json['body']['measuregrps'][$i-1]['date']) {
            $has_weight = false;
            $has_heart   = false;
            $height = getArg('height',false,0);
            $has_height = $height>0;
            $new_xml .= "<measure>";
            $last_date = $json['body']['measuregrps'][$i]['date']*1;
            $new_xml .= "<Date>".date('Y-m-d H:i:s', $last_date)."</Date>";
         }
         for($j = 0; $j < sizeof($json['body']['measuregrps'][$i]['measures']); $j++){
            $type = $measure_type[$json['body']['measuregrps'][$i]['measures'][$j]['type']*1];
            if ($type == '') {
               $type = "unknown_type_".$json['body']['measuregrps'][$i]['measures'][$j]['type'];
            }
            if ($type != '') {
               $measures[$type] = $json['body']['measuregrps'][$i]['measures'][$j]['value']*pow(10, $json['body']['measuregrps'][$i]['measures'][$j]['unit']);
               $new_xml .= "<$type>".$measures[$type]."</$type>";
               $has_measures = true;
               switch ($type) {
                  case 'Weight' : $has_weight = ($measures[$type]>0);break;
                  case 'Height' : $has_height = true;break;
                  case 'Diastolic_Blood_Pressure' :
                  case 'Systolic_Blood_Pressure' : $has_heart = true;break;
               }
            }
         }
         if ($has_weight) {
            foreach ($measures as $type => $measure){
               switch ($type) {
                  case 'Fat_Free_Mass'      : $new_xml .= "<Fat_Free_Ratio>".round(100*$measure/$measures['Weight'],2)."</Fat_Free_Ratio>";break;
                  case 'Muscle_Mass'         : $new_xml .= "<Muscle_Mass_Ratio>".round(100*$measure/$measures['Weight'],2)."</Muscle_Mass_Ratio>";break;
                  case 'Hydration'            : $new_xml .= "<Hydration_Ratio>".round(100*$measure/$measures['Weight'],2)."</Hydration_Ratio>";break;
                  case 'Bone_Mass'            : $new_xml .= "<Bone_Mass_Ratio>".round(100*$measure/$measures['Weight'],2)."</Bone_Mass_Ratio>";break;
                  case 'Muscle_Mass_Segments' : $new_xml .= "<Muscle_Mass_Segments_Ratio>".round(100*$measure/$measures['weight'],2)."</Muscle_Mass_Segments_Ratio>";break;
               }
            }
            if ($has_height) $new_xml .= "<Body_Mass_Index>".round($measures['Weight']/$height/$height,2)."</Body_Mass_Index>";
         }
         if ($has_heart) $new_xml .= "<heart>".$measures['Systolic_Blood_Pressure']."/".$measures['Diastolic_Blood_Pressure']." (".$measures['Heart_Pulse'].")"."</heart>";
         if ($json['body']['measuregrps'][$i+1]['date'] != $json['body']['measuregrps'][$i]['date']) {$new_xml .= "</measure>";}
      }
      $new_xml .= "<token>".$access_token."</token>";
      $new_xml .= "<Rtoken>".$refresh_token."</Rtoken>";
      $new_xml .= "<userid>".$userid."</userid>";
      $new_xml .= "<new>new</new>";
      $new_xml .= "<expire>".date('Y-m-d H:i:s',$expire_time)."</expire>";
      $new_xml .= "</root>";
      
      if (!$has_measures) {saveVariable('lastupdate'.$userid, 0);}
   }
   echo $new_xml;
   $last_xml = loadVariable('last_xml_'.$userid);
   $last_xml_time = loadVariable('last_xml_time_'.$userid);
   if ($last_xml != $new_xml) {
      saveVariable('last_xml_time_'.$userid, time());
      $new_xml = str_replace("<new>new</new>","<new>old</new>",$new_xml);
      saveVariable('last_xml_'.$userid, $new_xml);
      if ($last_date != '') {saveVariable('lastupdate'.$userid, $last_date);}
   }
}
?>
eedomus+, Zibase V1, RFP1000, RFXcom, RadioDriver CPL 630 X2D, capteurs puissance OWL, thermometres Oregon, téléinfo (USB Linky), detecteurs ouverture X2D, pilotage chauffage X2D, Ecoflow River PRO, PAC Shogun (Atlantic-Cozytouch)
opa95
 
Messages : 775
Inscription : 04 Fév 2019
Localisation : Val d'Oise

Précédent

Retour vers Requêtes HTTP

Qui est en ligne ?

Utilisateurs parcourant ce forum : Google [Bot] et 4 invité(s)

cron