Station météo Netatmo
| |||
---|---|---|---|
Nom | WS NETATMO | ||
Famille | Objets connectés | ||
Wiki créé le | 11/03/2018 | ||
Wiki mis à jour le | 11/03/2018 | ||
Auteur | fgtoul |
Présentation
La station Netatmo peut être consultée à distance à l'aide de l'API. Nous allons voir comment récupérer les données de la station météo afin d'interagir avec notre IPX800 V4.
Il sera nécessaire d'écrire un script qui aura pour tâche de
- se connecter à l'API de Netatmo,
- récupérer les données des différents modules (extérieur, intérieurs, pluviomètre, ...),
- envoyer les données à l'IPX800 V4.
J'ai choisi d'écrire un script en Php que j'hébergerai sur mon NAS Synology, mais vous pouvez tout à fait héberger ce script chez votre FAI si vous ne disposez ni de NAS, ni de serveur web.
Il y a deux méthodes possibles pour envoyer les données de la station vers l'IPX800.
- Créer une source de données Json ou Xml sur son serveur à partir du script.
Une datasource peut alors être ajoutée sur l'IPX800, les widget affichent les données.
Cette méthode présente plusieurs inconvénients :
- les données sont inutilisables dans les scenarii
- il est possible que l'IPX800 ne puisse pas récupérer les données à cause du "Cross-domain policy". C'est tout à fait contournable en utilisant un reverse proxy et en installant les entêtes CORS sur le serveur, mais pas à la portée de tout le monde. Pour information, le Synology ne dispose pas des entêtes CORS par défaut. Il faut les installer si vous souhaitez générer une source de données locale. - Utiliser l'API de l'IPX800 pour envoyer les données dans les entrées analogiques virtuelles. De cette manière, les données peuvent être utilisées dans les scènes, les notifications, etc.
J'ai donc choisi d'envoyer les données Netatmo, via l'API.
Accès à l'API Netatmo
Nous allons créer une application sur le site de développement Netatmo afin d'obtenir nos identifiants.
Connectez vous avec vos identifiants Netamo
cliquez sur le bouton de création d'une application
renseignez le nom et la description puis validez
notez les identifiants générés pour votre application
- Client ID
- Client secret
Ces informations seront utilisées dans le script Php
PHP sur le Synology
Le script
Voici le code que vous devez copier et coller dans l'environnement Web de votre synology.
Pour ma part, j'ai créé un dossier Netatmo dans mon dossier Web, j'y ai sauvegardé le script sous le nom "netatmo2push.php".
Les données Json récupéres pourraient être apparentées à des tableaux de valeurs. Il aurait donc été logique de récupérer les données sous la forme de tableaux (Array) également. Le code aurait été plus léger, mais moins lisible.
Pour la compréhension et pour rendre le code plus facilement adaptable par un grand nombre d'entre vous, j'ai vonlontairement récupéré les données dans des variables au nom explicite, sans utiliser les tableaux.
<?php
//renseignez vos identifiants ci-dessous
$password="Votre_mot_de_passe_Netatmo";
$username="Votre_identifiant_Netatmo";
$app_id = "Votre_Client_ID";
$app_secret ="Votre_Client_Secret";
$IP_IPX800="Adresse_IP_IPX800"
$API_key="Votre_Key_IPX800"
//---fin de paramétrage--
$token_url = "https://api.netatmo.net/oauth2/token";
$postdata = http_build_query(
array(
'grant_type' => "password",
'client_id' => $app_id,
'client_secret' => $app_secret,
'username' => $username,
'password' => $password
)
);
$options = array('http' =>
array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
)
);
$context = stream_context_create($options);
$response = file_get_contents($token_url, false, $context);
$params = null;
$params = json_decode($response, true);
$api_url = "https://api.netatmo.net/api/getuser?access_token=" . $params['access_token'];
$requete = file_get_contents($api_url);
$url_devices = "https://api.netatmo.net/api/devicelist?access_token=" . $params['access_token'];
$resultat_device = file_get_contents($url_devices);
//décommentez les 2 lignes suivantes pour voir les données JSON renvoyées par l'API Netatmo
//echo $resultat_device;
//exit();
$json_devices = json_decode($resultat_device,true);
function Batterie1($valeur){
$T1=array(0,10,25,50,75,100);
$valeur=intval(0.002*$valeur-6);
if ($valeur>=5){
$valeur= 5;
}else if ($valeur<1){
$valeur=1;
}
return $T1[$valeur];
}
function Batterie2($valeur){
$T2=array(0,10,25,50,75,100);
$valeur=intval(0.0024*$valeur-8.6341);
if ($valeur>=5){
$valeur= 5;
}else if ($valeur<1){
$valeur=1;
}
return $T2[$valeur];
}
//Module extérieur
$ext_temp = floatval($json_devices["body"]["modules"][0]["dashboard_data"]["Temperature"]) * 10;
$ext_temp_min = floatval($json_devices["body"]["modules"][0]["dashboard_data"]["min_temp"]) * 10;
$ext_temp_max = floatval($json_devices["body"]["modules"][0]["dashboard_data"]["max_temp"]) * 10;
$ext_humidite = floatval($json_devices["body"]["modules"][0]["dashboard_data"]["Humidity"]) * 10;
$ext_battery = Batterie1(floatval($json_devices["body"]["modules"][0]["battery_vp"]));
//module 1
$M1_temp = floatval($json_devices["body"]["modules"][1]["dashboard_data"]["Temperature"]) * 10;
$M1_temp_min = floatval($json_devices["body"]["modules"][1]["dashboard_data"]["min_temp"]) * 10;
$M1_temp_max = floatval($json_devices["body"]["modules"][1]["dashboard_data"]["max_temp"]) * 10;
$M1_humidite = floatval($json_devices["body"]["modules"][1]["dashboard_data"]["Humidity"]) * 10;
$M1_battery = Batterie2(floatval($json_devices["body"]["modules"][1]["battery_vp"]));
$M1_CO2=floatval($json_devices["body"]["modules"][1]["dashboard_data"]["CO2"]);
//module 2
$M2_temp = floatval($json_devices["body"]["modules"][2]["dashboard_data"]["Temperature"]) * 10;
$M2_temp_min = floatval($json_devices["body"]["modules"][2]["dashboard_data"]["min_temp"]) * 10;
$M2_temp_max = floatval($json_devices["body"]["modules"][2]["dashboard_data"]["max_temp"]) * 10;
$M2_humidite = floatval($json_devices["body"]["modules"][2]["dashboard_data"]["Humidity"]) * 10;
$M2_battery = Batterie2(floatval($json_devices["body"]["modules"][2]["battery_vp"]));
$M2_CO2=floatval($json_devices["body"]["modules"][2]["dashboard_data"]["CO2"]);
//module 3 : Pluviomètre
$M3_Rain = floatval($json_devices["body"]["modules"][3]["dashboard_data"]["Rain"]) * 1000;
$M3_Rain_24 = floatval($json_devices["body"]["modules"][3]["dashboard_data"]["sum_rain_24"]) * 1000;
$M3_Rain_1 = floatval($json_devices["body"]["modules"][3]["dashboard_data"]["sum_rain_1"]) * 1000;
$M3_battery = Batterie1(floatval($json_devices["body"]["modules"][3]["battery_vp"]));
//device Station
$MS_temp = floatval($json_devices["body"]["devices"][0]["dashboard_data"]["Temperature"]) * 10;
$MS_temp_min = floatval($json_devices["body"]["devices"][0]["dashboard_data"]["min_temp"]) * 10;
$MS_temp_max = floatval($json_devices["body"]["devices"][0]["dashboard_data"]["max_temp"]) * 10;
$MS_humidite = floatval($json_devices["body"]["devices"][0]["dashboard_data"]["Humidity"]) * 10;
$MS_CO2=floatval($json_devices["body"]["devices"][0]["dashboard_data"]["CO2"]);
$MS_Pressure=floatval($json_devices["body"]["devices"][0]["dashboard_data"]["Pressure"]) * 10;
$MS_Noise=floatval($json_devices["body"]["devices"][0]["dashboard_data"]["Noise"]) * 10;
$URL_Push="http://" . $IP_IPX800 . "/api/xdevices.json?key=" . $API_key . "&SetVA01=" . $ext_temp . "&SetVA02=" . $ext_temp_min . "&SetVA03=" . $ext_temp_max . "&SetVA04=" . $ext_humidite . "&SetVA05=" . $ext_battery . "&SetVA06=" . $M1_temp . "&SetVA07=" . $M1_temp_min . "&SetVA08=" . $M1_temp_max . "&SetVA09=" . $M1_humidite . "&SetVA10=" . $M1_battery . "&SetVA11=" . $M1_CO2 . "&SetVA12=" . $M2_temp ."&SetVA13=" . $M2_temp_min . "&SetVA14=" . $M2_temp_max . "&SetVA15=" . $M2_humidite . "&SetVA16=" . $M2_battery . "&SetVA17=" . $M2_CO2 . "&SetVA18=0" . $M3_rain ."&SetVA19=0" . $M3_rain_24 . "&SetVA20=0" . $M3_rain_1 . "&SetVA21=" . $M3_battery . "&SetVA22=" . $MS_temp . "&SetVA23=" . $MS_temp_min . "&SetVA24=" . $MS_temp_max . "&SetVA25=" . $MS_humidite . "&SetVA26=" . $MS_CO2 . "&SetVA27=" . $MS_Pressure . "&SetVA28=" . $MS_Noise ;
echo $URL_Push . "<br>";
$ch = curl_init();
//Set the URL that you want to GET by using the CURLOPT_URL option.
curl_setopt($ch, CURLOPT_URL, $URL_Push);
//Set CURLOPT_RETURNTRANSFER so that the content is returned as a variable.
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//Set CURLOPT_FOLLOWLOCATION to true to follow redirects.
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
//Execute the request.
$data = curl_exec($ch);
//Close the cURL handle.
curl_close($ch);
?>
Les identifiants
Renseignez vos identifiants dans les huit premières lignes du code, ainsi que l'adresse IP de votre IPX800 et la clé API.
Si le script est hébergé chez vous, l'adresse locale de l'IPX800 doit être renseignée.
Si vous hébergez le script chez votre FAI, il faudra renseigner l'adresse publique et le port externe permettant l'accès à l'IPX800. Au préalable, vous aurez créé une redirection sur votre box ou routeur.
Récupération des données
Le script se charge de récupérer les données de vos différents modules. Dans mon cas, les modules sont les suivant
- ["modules"][0] correspond au module extérieur.
- ["modules"][1] correspond au 1er module intérieur additionnel.
- ["modules"][2] correspond au 2nd module intérieur additionnel.
- ["modules"][3] correspond au Pluviomètre.
- ["devices"][0] correspond au module principal de la station.
Pour voir à quoi correspondent les données chez vous, faites comme suit
- décommentez les lignes 41 et 42 du code
- Exécutez le script dans votre navigateur. Vous devriez obtenir l'ensemble des données JSON servies par l'API.
Sélectionnez la totalité de ces données, copiez et collez dans un visualiseur de données Json.
Cliquez sur le bouton "Tree viewer" Vous devriez alors obtenir une arborescence bien lisible de vos données. Vous devriez y retouver les différents modules avec leur index ainsi que les relevés météorologiques (dashboard_data) pour chacun d'entre eux. Le module principal de la station se trouve en bas de l'arborescence, c'est un device qui en général est identifié ["devices"][0]
Pensez à rétablir les commentaires après la manip.
Préparation des données
- Les fonctions Batterie1 et Batterie2 permettent de convertir la valeur du niveau de charge en un taux de charge (10%, 25%, 50%, 75%, 100%).
Cela permettra l'affichage de l'icône adéquate sur l'interface de l'IPX800 tout en mettant le moins de code possible sur l'IPX800 afin de le charger au munimum.
Les formules ont été déterminées grâce aux seuils fournis par Netatmo sur la page référence de l'API.
- Les relevés de températures et de pression sont effectués sous forme de nombre décimal.
Si nous envoyons ces données dans une analogique virtuelle de l'IPX800, cette dernière ignorera la partie décimale.
Nous perdrons en précision.
Afin de ne pas perdre nos décimales tout en envoyant des nombres entiers, nous multiplierons ces valeurs par 10.
Sur l'IPX800, nous mettrons alors une formule en place (x / 10) sur les entrées analogiques respectives afin de rétablir la valeur d'origine.
Les données concernant le pluviomètre ayant 3 décimales, elles sont multipliées par 1000.
Envoi des données à l'IPX800
Pour envoyer les données à l'IPX800, nous utilisons son API Http.
Toutes les données sont envoyées dans des entrées analogiques virtuelles, afin d'être affichables et utilisables dans les scenarii.
Nous utilisons la commande SetVAxx que nous assemblons sous forme d'URL. Celle-ci est alors exécutée par la fonction curl().
Dans notre script, nous récupérons toutes les valeurs importantes. Vous pouvez restreindre le nombre des valeurs afin d'utiliser moins d'entrées analogiques.
Test et planification du script
Après adaptation du code, vous pouvez le tester.
Vous devriez alors obtenir l'affichage de l'URL qui a été générée afin d'envoyer les données à l'IPX800 grâce à son API.
Si le fonctionnement est correct, vous pouvez planifier une tâche sur votre Synology afin que le script soit exécuté à intervalle régulier.
La station Netatmo envoie ses données toutes les 10 minutes. Il est donc inutile de lancer le script toutes les minutes.
Vous occuperiez inutilement
- votre réseau
- votre synology
- votre IPX800
Personnellement, j'exécute le script toutes les 10 minutes.
Remarque : La station envoie ses mesures toutes les 10 minutes au serveur Netatmo. Si vous êtes équipés d'un anémomètre Netatmo, ne l'utilisez donc pas comme source de mesure afin que l'IPX800 rentre le store en fonction de la vitesse du vent. 10 minutes c'est long, votre store pourrait être détérioré en cas de vent fort.
Dans le panneau de configuration du Synology, créons une nouvelle tâche dans le planificateur :
Renseignez le chemin de /bin/php suivi de l'option -f puis le chemin absolu vers votre page php.
Exécutez la tâche planifiée pour en vérifier le bon fonctionnement.
L'IPX800 V4 recevra les mises à jour des données toutes les 10 minutes.
Sur IPX800
Les sorties analogiques virtuelles
Voici la configuration et l'utilisation des entrées analogiques virtuelles sur votre IPX800 V4.
Vous pouvez les utiliser avec des widgets standards ainsi que dans vos scènes et vos notifications.
La source de données
Dans le menu administrateur de l'interface IPX800 V4, ajoutez une source de données afin d'accèder au fichier Status.xml.
Remarque : Notez son nom en majuscules. La casse devra être respectée lors de son appel dans le code JavaScript.
Les widgets HTML
Pour chaque module de la station Netatmo, vous pouvez ajouter un widget de type HTML sur l'interface de l'IPX800 V4.
La hauteur de chaque widget est de 2 blocs.
Vous trouverez le code Javascript à coller dans chaque widget.
remarque : Vous noterez que l'analogique virtuelle n°1 est nommée analogV0 dans Status.xml, et ainsi de suite jusqu'à analogV31 pour l'entrée analogique virtuelle 32.
Le Module Extérieur
//MODULE EXTERIEUR
let T0=datasources["STATUS"]["response"]["analogV0"]/10;
let Tmin0=datasources["STATUS"]["response"]["analogV1"]/10;
let Tmax0=datasources["STATUS"]["response"]["analogV2"]/10;
let H0=datasources["STATUS"]["response"]["analogV3"]/10;
let B0=datasources["STATUS"]["response"]["analogV4"];
let NB0=B0;
if (B0==100){NB0='full'};
return `
<br>
<span style="float:left;margin-left:20px;font-weight: bold; font-size: 2.5em;">${T0}°</span>
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 2em;">${H0}%</span>
<br><br><br>
<span style="float:left; margin-left:10px;">⇩</span><span style="font-weight: bold;">${Tmin0}°</span> <span>⇧</span><span style="font-weight: bold;">${Tmax0}°</span>
<br><br>
<span style="float:left; margin-left:15px;" class="glyphicons glyphicons-battery-${NB0}"></span>
`;
Le Module additionnel 1
//MODULE INTERIEUR ADDITIONNEL 1
let T1=datasources["STATUS"]["response"]["analogV5"]/10;
let Tmin1=datasources["STATUS"]["response"]["analogV6"]/10;
let Tmax1=datasources["STATUS"]["response"]["analogV7"]/10;
let H1=datasources["STATUS"]["response"]["analogV8"]/10;
let B1=datasources["STATUS"]["response"]["analogV9"];
let CO2_1=datasources["STATUS"]["response"]["analogV10"];
let NB1=B1;
if (B1==100){NB1='full'};
return `
<br>
<span style="float:left;margin-left:20px;font-weight: bold; font-size: 2.5em;">${T1}°</span>
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 2em;">${H1}%</span>
<br><br><br>
<span style="float:left; margin-left:10px;">⇩</span><span style="font-weight: bold;">${Tmin1}°</span> <span>⇧</span><span style="font-weight: bold;">${Tmax1}°</span>
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 1.5em;">${CO2_1}ppm</span>
<br><br>
<span style="float:left; margin-left:15px;" class="glyphicons glyphicons-battery-${NB1}"></span>
`;
Le Module additionnel 2
//MODULE INTERIEUR ADDITIONNEL 2
let T2=datasources["STATUS"]["response"]["analogV11"]/10;
let Tmin2=datasources["STATUS"]["response"]["analogV12"]/10;
let Tmax2=datasources["STATUS"]["response"]["analogV13"]/10;
let H2=datasources["STATUS"]["response"]["analogV14"]/10;
let B2=datasources["STATUS"]["response"]["analogV15"];
let CO2_2=datasources["STATUS"]["response"]["analogV16"];
let NB2=B2;
if (B2==100){NB2='full'};
return `
<br>
<span style="float:left;margin-left:20px;font-weight: bold; font-size: 2.5em;">${T2}°</span>
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 2em;">${H2}%</span>
<br><br><br>
<span style="float:left; margin-left:10px;">⇩</span><span style="font-weight: bold;">${Tmin2}°</span> <span>⇧</span><span style="font-weight: bold;">${Tmax2}°</span>
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 1.5em;">${CO2_2}ppm</span>
<br><br>
<span style="float:left; margin-left:15px;" class="glyphicons glyphicons-battery-${NB2}"></span>
`;
Le Module Pluviomètre
//MODULE PLUVIOMETRE
let P0=datasources["STATUS"]["response"]["analogV19"]/1000;
let P0_24=datasources["STATUS"]["response"]["analogV18"]/1000;
//let P0_1=datasources["STATUS"]["response"]["analogV19"]/1000;
let BP=datasources["STATUS"]["response"]["analogV20"];
let NBP=BP;
if (BP==100){NBP='full'};
return `
<br>
<span style="float:left;margin-left:20px;font-weight: bold; font-size: 2.5em;">${P0.toFixed(1)}</span> mm/h
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 2.5em;">${P0_24.toFixed(1)}</span>
<br><br><br>
<span style="float:left;margin-left:20px;font-weight: bold;font-size:10px;">dernière heure passée</span>
<span style="float:right;margin-right:10px;font-weight: bold;font-size:10px;">cumulé jour</span>
<br><br>
<span style="float:left; margin-left:15px;" class="glyphicons glyphicons-battery-${NBP}"></span>
`;
Le Module Principal
//MODULE PRINCIPAL
let TS=datasources["STATUS"]["response"]["analogV21"]/10;
let TminS=datasources["STATUS"]["response"]["analogV22"]/10;
let TmaxS=datasources["STATUS"]["response"]["analogV23"]/10;
let HS=datasources["STATUS"]["response"]["analogV24"]/10;
let PS=datasources["STATUS"]["response"]["analogV26"]/10;
let CO2_S=datasources["STATUS"]["response"]["analogV25"];
let NOISE=datasources["STATUS"]["response"]["analogV27"]/10;
return `
<span style="float:left;margin-left:20px;font-weight: bold; font-size: 2.5em;">${TS}°</span>
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 2em;">${HS}%</span>
<br><br><br>
<span style="float:left; margin-left:10px;">⇩</span><span style="font-weight: bold;">${TminS}°</span> <span>⇧</span><span style="font-weight: bold;">${TmaxS}°</span>
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 1.5em;">${CO2_S} ppm</span>
<br><br>
<span style="float:left;margin-left:10px;font-weight: bold; font-size: 1.5em;">${PS.toFixed(0)} mbar</span>
<span style="float:right;margin-right:10px;font-weight: bold; font-size: 1.5em;">${NOISE} dB</span>
`;