Publié le

Afficher les données Netatmo avec un Arduino

L’inconvénient des stations météo Netatmo c’est qu’il faut avoir un smartphone/pc/tablette à disposition pour pouvoir consulter les données de la station (ou avoir un Karotz comme Mathieu).

Du coup je me suis mis à la recherche de pistes pour faire un afficheur LCD avec un Arduino pour avoir les données en temps réel !

Du coup il faut pas mal de choses :

  • Un NAS Synology (ou un pc sous Unix ou un Raspberry Pi),
  • un Arduino avec un lcd,
  • de la patience quand on est pas doué comme moi.

Et il faut aussi quelques connaissances :

  • Savoir programmer sur Arduino,
  • maitriser le PHP,
  • savoir bidouiller dans son NAS (merci encore une fois à Mathieu).

 

1. Créer une application Netatmo :

La première étape est d’aller créer un application sur dev.netatmo.com.

On clique en haut sur « CREATE AN APP » et on se connecte avec nos identifiants Netatmo.

On remplit les champs avec astérisque, dans mon cas :

 

Netatmo1

On descend en bas de la page, on pose la case et on valide.

On arrive ensuite sur la page OAUTH SETTINGS d’ou nous allons récupérer les identifiants de l’application :

Sans titre

On récupère le « Client id » et le « Client secret » qui vont nous servir dans la partie PHP.

2. Partie PHP / Synology

2.1 Partie PHP :

On a donc les « Client id » et « Client secret » de notre application Netatmo, on va maintenant mettre tout ça dans un fichier PHP pour récupérer les informations de la station météo Netatmo.

Voici le code de mon fichier netatmo.php (qui à l’origine vient de Domotique Info mais que j’ai modifié).

<?php
$password="Mot de passe du compte Netatmo";
$username="Identifiant du compte Netatmo";
$app_id = "Client id de l'app";
$app_secret ="client secret de l'app";

$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
    )
);

$opts = array('http' =>
    array(
        'method'  => 'POST',
        'header'  => 'Content-type: application/x-www-form-urlencoded',
        'content' => $postdata
    )
);

$context  = stream_context_create($opts);
$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'];
$resulat_device = file_get_contents($url_devices);
$json_devices = json_decode($resulat_device,true);
$module_interne = $json_devices["body"]["devices"][0]["_id"];
$module_externe = $json_devices["body"]["modules"][0]["_id"];

$url_mesures_internes = "https://api.netatmo.net/api/getmeasure?access_token=" .  $params['access_token'] . "&device_id=" . $module_interne . "&scale=max&type=Temperature,CO2,Humidity,Pressure,Noise&date_end=last";
$mesures_internes = file_get_contents($url_mesures_internes);

$url_mesures_externes = "https://api.netatmo.net/api/getmeasure?access_token=" .  $params['access_token'] . "&device_id=" . $module_interne . "&module_id=" . $module_externe . "&scale=max&type=Temperature,Humidity&date_end=last";
$mesures_externes = file_get_contents($url_mesures_externes);

$int = substr($mesures_internes,42,25);
$ext = substr($mesures_externes,42,25);

$interieur=explode(",", $int);
$exterieur=explode(",",$ext);
$h_ext=explode("]",$exterieur["1"]);
$decibel=explode("]", $interieur["4"]);
$message = "[".$interieur["0"]."n".$interieur["1"]."n".$interieur["2"]."n".$interieur["3"]."n".$decibel["0"]."n".$exterieur["0"]."n".$h_ext["0"]."[";
echo $message;


?>

Petites précision sur la fin du script php :

Quand le script exécute

$url_mesures_internes = "https://api.netatmo.net/api/getmeasure?access_token=" .  $params['access_token'] . "&device_id=" . $module_interne . "&scale=max&type=Temperature,CO2,Humidity,Pressure,Noise&date_end=last";
$mesures_internes = file_get_contents($url_mesures_internes);

il nous renvoie un résultat du type :

{"body":[{"beg_time":1444133653,"value":[[24.1,433,60,1007.7,50]]}],"status":"ok","time_exec":0.020297050476074,"time_server":1444134057}

sachant que les valeurs dont nous avons besoin sont : 24.1,433,60,1007.7,50 (Température, CO2, Humidité, Pression, Décibels).

Du coup pour ne récupérer que ça (et pas toute la chaine $mesures_internes) je vais me placer sur le premier chiffre pour la température, j’extrait 25 caractères et ensuite je découpe entre avec mon séparateur « , » puis pour la dernière mesure je découpe avec le séparateur « ] ». (Je ne suis pas en pro, on doit bien pouvoir faire plus propre, mais je fais avec mes connaissances !).

Une fois fait, je compose un message à afficher après avoir exécuté le script PHP sous forme d’un écho qu’on récupèrera avec l’arduino (message avec les séparateurs « [ » et « n » on verra ça dans la partie Arduino).

2.2 Mise en place sur le Synology :

J’ai déjà un serveur Web installé sur mon NAS, donc je suis allé dans le dossier « www », j’ai créé un dossier « Netatmo » et j’ai copié mon fichier « netatmo.php » dedans. Pour automatiser l’exécution de mon script, direction le panneau de configuration et « planificateur de tâche ».

On clique sur créer et on rentre le code suivant :

/usr/bin/php -f /volume1/web/Netatmo/netatmo.php > /volume1/web/Netatmo/out.txt

Voilà ce qu’on obtient :

syno1

 

On va dans l’onglet Programmer :

syno2

On valide et on exécute le script (pour voir si tout se passe bien, le fichier out.txt devrait apparaitre dans le dossier Netatmo.

Le contenu du fichier doit ressembler à ça :

[24.3n450n60n1007.8n51n26.9n45[

Génial c’est la chaine qu’on va récupérer sur l’Arduino !

Note : ne pas faire de bêtises et ne pas effacer le fichier /usr/bin/php (ne me demandez pas comment je l’ai fait, merci à Mathieu de m’avoir envoyé le sien).

 

3. Récupération et affichage sur un LCD : Partie ARDUINO.

3.1 Tests préliminaires (Et il y en a eu…) :

Habituellement, je travaille avec des ESP8266, mais il y a un mois, je me suis commandé un shield CC3000 sur ebay qui ressemble à ça :

$_57

Avantage : il fonctionne avec les librairies ADAFRUIT que l’on trouve ici.

Pour le codage, j’ai fait le flemmard. J’avais déjà testé le code exemple WebClient.ino fournit avec la librairie ADAFRUIT, donc je l’ai bêtement repris.

Il faut juste modifier un peu les premières ligne (je ne montre que les premières lignes) :

#define WLAN_SSID       "LE SSID DE LA BOX"           // cannot be longer than 32 characters!
#define WLAN_PASS       "MOT DE PASSE WIFI"
// Security can be WLAN_SEC_UNSEC, WLAN_SEC_WEP, WLAN_SEC_WPA or WLAN_SEC_WPA2
#define WLAN_SECURITY   WLAN_SEC_WPA2

#define IDLE_TIMEOUT_MS  3000      // Amount of time to wait (in milliseconds) with no data
                                   // received before closing the connection.  If you know the server
                                   // you're accessing is quick to respond, you can reduce this value.

// What page to grab!
#define WEBSITE      "IP DU NAS"
#define WEBPAGE      "/Netatmo/out.php"  // lien vers out.txt

Sur la voie série quand j’exécute mon programme, j’ai la visualisation suivante :

Hello, CC3000!

Free RAM: 5687

Initializing...

Attempting to connect to Livebox-4246
Connected!
Request DHCP

IP Addr: 192.168.1.65
Netmask: 255.255.255.0
Gateway: 192.168.1.1
DHCPsrv: 192.168.1.1
DNSserv: 192.168.1.1
192.168.1.50 -> 192.168.1.50-------------------------------------
HTTP/1.1 200 OK
Date: Tue, 06 Oct 2015 13:02:00 GMT
Server: Apache
Last-Modified: Tue, 06 Oct 2015 13:01:11 GMT
ETag: "1d-5216f395e9d25"
Accept-Ranges: bytes
Content-Length: 29
Vary: Accept-Encoding
Content-Type: text/plain

[24.5n455n60n1007.9n51n27n44[-------------------------------------


Disconnecting

En fait le programme WebClient.ino me renvoie la réponse donnée par le site web, mais la réponse COMPLETE, c’est à dire :

HTTP/1.1 200 OK
Date: Tue, 06 Oct 2015 13:02:00 GMT
Server: Apache
Last-Modified: Tue, 06 Oct 2015 13:01:11 GMT
ETag: "1d-5216f395e9d25"
Accept-Ranges: bytes
Content-Length: 29
Vary: Accept-Encoding
Content-Type: text/plain

[24.5n455n60n1007.9n51n27n44[

Et devinez ce dont on a besoin ? Et oui juste la fin de la réponse…

3.2 Après les tests, le programme fonctionnel :

Donc j’ai passé un peu de temps à voir comment récupérer juste le contenu de mon fichier « out.txt », j’ai retravaillé le fichier « WebClient.ino » car d’une part, tout était dans le void setup, et d’autre part, il me fallait récupérer l’essentiel (c’est lors de cette étape que j’ai rajouté les délimiter « [ » dans le fichier netatmo.php).

Voici mon code :

#include <Adafruit_CC3000.h>
#include <LiquidCrystal.h>
#include <ccspi.h>
#include <SPI.h>
#include <string.h>
#include "utility/debug.h"


#define ADAFRUIT_CC3000_IRQ   3  // MUST be an interrupt pin!
#define ADAFRUIT_CC3000_VBAT  5
#define ADAFRUIT_CC3000_CS    10
Adafruit_CC3000 cc3000 = Adafruit_CC3000(ADAFRUIT_CC3000_CS, ADAFRUIT_CC3000_IRQ, ADAFRUIT_CC3000_VBAT,
                                         SPI_CLOCK_DIVIDER); // you can change this clock speed

#define WLAN_SSID       "SSID Wifi"           // cannot be longer than 32 characters!
#define WLAN_PASS       "Mot de passe Wifi"
// Security can be WLAN_SEC_UNSEC, WLAN_SEC_WEP, WLAN_SEC_WPA or WLAN_SEC_WPA2
#define WLAN_SECURITY   WLAN_SEC_WPA2

#define IDLE_TIMEOUT_MS  3000      // Amount of time to wait (in milliseconds) with no data
                                   // received before closing the connection.  If you know the server
                                   // you're accessing is quick to respond, you can reduce this value.

// What page to grab!
#define WEBSITE      "192.168.1.50"             //ip NAS
#define WEBPAGE      "/Netatmo/out.txt"         //Chemin du fichier Out


LiquidCrystal lcd(42,41,37,38,39,40);

/**************************************************************************/
/*!
    @brief  Sets up the HW and the CC3000 module (called automatically
            on startup)
*/
/**************************************************************************/

uint32_t ip;

void setup(void)
{
  Serial.begin(115200);
  lcd.begin(16,2);
  lcd.clear();
  lcd.write("test");
}

void loop(void)
{
    Serial.println(F("Hello, CC3000!n"));

  Serial.print("Free RAM: "); Serial.println(getFreeRam(), DEC);

  /* Initialise the module */
  Serial.println(F("nInitializing..."));
  if (!cc3000.begin())
  {
    Serial.println(F("Couldn't begin()! Check your wiring?"));
    while(1);
  }

  // Optional SSID scan
  // listSSIDResults();

  Serial.print(F("nAttempting to connect to ")); Serial.println(WLAN_SSID);
  if (!cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY)) {
    Serial.println(F("Failed!"));
    while(1);
  }

  Serial.println(F("Connected!"));

  /* Wait for DHCP to complete */
  Serial.println(F("Request DHCP"));
  while (!cc3000.checkDHCP())
  {
    delay(100); // ToDo: Insert a DHCP timeout!
  }

  /* Display the IP address DNS, Gateway, etc. */
  while (! displayConnectionDetails()) {
    delay(1000);
  }

  ip = 0;
  // Try looking up the website's IP address
  Serial.print(WEBSITE); Serial.print(F(" -> "));
  while (ip == 0) {
    if (! cc3000.getHostByName(WEBSITE, &ip)) {
      Serial.println(F("Couldn't resolve!"));
    }
    delay(500);
  }

  cc3000.printIPdotsRev(ip);

  // Optional: Do a ping test on the website
  /*
  Serial.print(F("nrPinging ")); cc3000.printIPdotsRev(ip); Serial.print("...");
  replies = cc3000.ping(ip, 5);
  Serial.print(replies); Serial.println(F(" replies"));
  */

  /* Try connecting to the website.
     Note: HTTP/1.1 protocol is used to keep the server from closing the connection before all data is read.
  */
  Adafruit_CC3000_Client www = cc3000.connectTCP(ip, 80);
  if (www.connected()) {
    www.fastrprint(F("GET "));
    www.fastrprint(WEBPAGE);
    www.fastrprint(F(" HTTP/1.1rn"));
    www.fastrprint(F("Host: ")); www.fastrprint(WEBSITE); www.fastrprint(F("rn"));
    www.fastrprint(F("rn"));
    www.println();
  } else {
    Serial.println(F("Connection failed"));
    return;
  }

  Serial.println(F("-------------------------------------"));
  int i=0;
  String message ="";
  /* Read data until either the connection is closed, or the idle timeout is reached. */
  unsigned long lastRead = millis();
  while (www.connected() && (millis() - lastRead < IDLE_TIMEOUT_MS)) {
    while (www.available()) {
      char c = www.read();
      message += c;
      Serial.print(c);
      lastRead = millis();
    }
  }

 //Serial.println(message);
  www.close();
  Serial.println(F("-------------------------------------"));

  /* You need to make sure to clean up after yourself or the CC3000 can freak out */
  /* the next time your try to connect ... */
  Serial.println(F("nnDisconnecting"));
  cc3000.disconnect();

  char messageChar[500];
  message.toCharArray(messageChar,500);

  char dlm[] = "[";
            int cnt = 0;
            char* tab[255] = { 0 };
            char *pch = strtok(messageChar, dlm);
            while ( pch != NULL ) {
              if (cnt < 60) {
              tab[cnt++] = pch;
              } else {
              break;
              }
            pch = strtok (NULL, dlm);
            }


  char *chaine = tab[1];
  //Serial.println(chaine);
  char dlm2[] = "n";
            int cnt2 = 0;
            char* tab2[255] = { 0 };
            char *pch2 = strtok(chaine, dlm2);
            while ( pch2 != NULL ) {
              if (cnt2 < 60) {
              tab2[cnt2++] = pch2;
              } else {
              break;
              }
            pch2 = strtok (NULL, dlm2);
            }
 char *temp = tab2[0];
 Serial.print(temp);
 lcd.clear();
 lcd.setCursor(0,0);
 lcd.write("t:");
 lcd.write(temp);
 lcd.write("C CO2:");
 lcd.write(tab2[1]);
 lcd.setCursor(0,1);
 lcd.write("h:");
 lcd.write(tab2[2]);
 lcd.write("% p:");
 lcd.write(tab2[3]);

 delay(120000);
}

/**************************************************************************/
/*!
    @brief  Begins an SSID scan and prints out all the visible networks
*/
/**************************************************************************/

void listSSIDResults(void)
{
  uint32_t index;
  uint8_t valid, rssi, sec;
  char ssidname[33];

  if (!cc3000.startSSIDscan(&index)) {
    Serial.println(F("SSID scan failed!"));
    return;
  }

  Serial.print(F("Networks found: ")); Serial.println(index);
  Serial.println(F("================================================"));

  while (index) {
    index--;

    valid = cc3000.getNextSSID(&rssi, &sec, ssidname);

    Serial.print(F("SSID Name    : ")); Serial.print(ssidname);
    Serial.println();
    Serial.print(F("RSSI         : "));
    Serial.println(rssi);
    Serial.print(F("Security Mode: "));
    Serial.println(sec);
    Serial.println();
  }
  Serial.println(F("================================================"));

  cc3000.stopSSIDscan();
}

/**************************************************************************/
/*!
    @brief  Tries to read the IP address and other connection details
*/
/**************************************************************************/
bool displayConnectionDetails(void)
{
  uint32_t ipAddress, netmask, gateway, dhcpserv, dnsserv;

  if(!cc3000.getIPAddress(&ipAddress, &netmask, &gateway, &dhcpserv, &dnsserv))
  {
    Serial.println(F("Unable to retrieve the IP Address!rn"));
    return false;
  }
  else
  {
    Serial.print(F("nIP Addr: ")); cc3000.printIPdotsRev(ipAddress);
    Serial.print(F("nNetmask: ")); cc3000.printIPdotsRev(netmask);
    Serial.print(F("nGateway: ")); cc3000.printIPdotsRev(gateway);
    Serial.print(F("nDHCPsrv: ")); cc3000.printIPdotsRev(dhcpserv);
    Serial.print(F("nDNSserv: ")); cc3000.printIPdotsRev(dnsserv);
    Serial.println();
    return true;
  }
}

Et voici le résultat :

IMG_2739

3.3 Ce qu’il reste à faire :

  • J’ai commandé un écran LCD 20×4 I2C car sur le 16×2 (qui n’est pas en I2C) je ne peux pas tout afficher.
  • Voir pour récupérer les infos du pluviomètre.

3 réflexions au sujet de « Afficher les données Netatmo avec un Arduino »

  1. Super article, merci pour le partage !

  2. j’ai passé plus de 2heures sur internet pour arrivez votre
    article et vraiment je vous encourage.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *