Mesurer l'humidité avec une sonde DHT22 et la température avec une sonde DS18B20 sur Arduino, transférer l'information à un Raspberry Pi

L'objectif de ce billet est d'illustrer l'appariement et le travail synchronisé d'un Arduino et d'un Raspberry Pi. Concrètement, l'Arduino mesure l'humidité avec une sonde DHT22 et la température avec une sonde DS18B20 et l'objectif est de transférer cette information à un Raspberry Pi qui traite l'information et l'envoie sur Internet.

Certains se demanderont pourquoi ne pas tout faire directement avec le Raspberry Pi :

  1. on peut imaginer que tous les ports du Pi sont déjà utilisés
  2. ou que le montage se complique et qu'il n'est plus possible de tout gérer depuis le Pi mais qu'il est plus aisé de lui adjoindre une (ou des) planche(s) Arduino

Principe de communication entre Raspberry Pi et Arduino

Nous allons utiliser un lien série entre les 2 composants au travers d'une connexion USB qui relie les deux équipements. Concrètement, le Raspberry Pi enverra des ordres à l'Arduino sur le port série et écoutera attentivement la réponse qui viendra également sur le port série.

Code Arduino

Mesurer la température avec une sonde DS18B20

Pour faciliter les choses, on utilisera la bibliothèque DallasTemperature.h (disponible ici sur GitHub). Le code est développé ci-dessous et ne présente aucune difficulté particulière :

#include <OneWire.h> // Inclusion de la librairie OneWire
#include "DallasTemperature.h" //Librairie du capteur

// Initialize 1-wire
OneWire oneWire(10); //Bus 1-Wire sur la pin 10 de l'arduino
DallasTemperature sensors(&oneWire); //Utilistion du bus Onewire pour les capteurs
DeviceAddress sensorDeviceAddress; //Vérifie la compatibilité des capteurs

void doMeasures() {
  sensors.requestTemperatures(); //Demande la température aux capteurs
  Serial.print("temperature=");
  Serial.println(sensors.getTempCByIndex(0)); //Récupération de la température en degrés celsius du capteur n°0
}

// setup()
void setup() {
  sensors.begin(); //Activation des capteurs
  sensors.getAddress(sensorDeviceAddress, 0); //Demande l'adresse du capteur à l'index 0 du bus
  sensors.setResolution(sensorDeviceAddress, 11); //Résolutions possibles: 9,10,11,12
}

Pour mesurer la température, on appellera la fonction doMeasures().

Mesurer l'humidité avec une sonde DHT22

Là encore, le code n'est pas bien compliqué tant que l'on utilise la librairie DHT.h (par exemple celle d'Adafruit).

#include "DHT.h"

// Initialisation du capteur DHT
#define DHTPIN 12    // Pin sur lequel la sonde DHT 22 est attachée
#define DHTTYPE DHT22   // Type de sonde DHT22 : AM2302, AM2321
DHT dht(DHTPIN, DHTTYPE);

void doMeasures() {
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
  }

  // Compute heat index in Celsius (isFahreheit = false)
  float hic = dht.computeHeatIndex(t, h, false);

  Serial.print("humidity=");
  Serial.println(h);
  Serial.print("temperature="); //Oui, un DHT22 ça mesure aussi la température :-) 
  Serial.println(t);
  Serial.print("heat_index=");
  Serial.println(hic);
}

// setup()
void setup() {
  dht.begin();
}
Écoute sur le port série et actions

Là encore, pas de surprise. La boucle loop() est utilisée pour réagir quand des données sont présentes sur l'interface série. On détecte la nature de l'appel et on peut imaginer différents comportements selon le message reçu. Par exemple, un caractère pourra déclencher le relevé de l'humidité sur DHT22 tandis qu'un autre caractère reçu déclenchera la prise de température sur la sonde DS18B20.

//setup()
void setup() {
  Serial.begin(9600); // Initialisation du port série
}

// for incoming serial data
int incomingByte = 0;
String res = "";

// loop()
void loop() {
  // On écoute sur le port série
  if (Serial.available() > 0) {
          // On lit le contenu du message série et selon le caractère reçu on déclenche une action différente
          incomingByte = Serial.read(); 
          if (incomingByte == 103) { //g
            doAction1();
          }
          if (incomingByte == 104) { //h
            doAction2();
          }
  }
}
Il suffit alors de tout combiner !

Il n'est pas difficile de combiner ces trois morceaux de code !

#include "DHT.h"
#include <OneWire.h>
#include "DallasTemperature.h" 

// Initialize DHT sensor.
#define DHTPIN 12
#define DHTTYPE DHT22  
DHT dht(DHTPIN, DHTTYPE);

// Initialize 1-wire
OneWire oneWire(10);
DallasTemperature sensors(&oneWire); //Utilistion du bus Onewire pour les capteurs
DeviceAddress sensorDeviceAddress; //Vérifie la compatibilité des capteurs

// for incoming serial data
int incomingByte = 0;
String res = "";

void doMeasures() {
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  // Read temperature as Celsius (the default)
  float t = dht.readTemperature();

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(t)) {
    Serial.println("Failed to read from DHT sensor!");
  }

  // Compute heat index in Celsius (isFahreheit = false)
  float hic = dht.computeHeatIndex(t, h, false);

  Serial.print("humidity=");
  Serial.println(h);
  Serial.print("temperature=");
  Serial.println(t);
  Serial.print("heat_index=");
  Serial.println(hic);

  sensors.requestTemperatures(); //Demande la température aux capteurs
  Serial.print("temperature=");
  Serial.println(sensors.getTempCByIndex(0)); //Récupération de la température en celsius du capteur n°0
}

// setup()
void setup() {
  Serial.begin(9600); // Initialisation du port série
  dht.begin();
  sensors.begin(); //Activation des capteurs
  sensors.getAddress(sensorDeviceAddress, 0); //Demande l'adresse du capteur à l'index 0 du bus
  sensors.setResolution(sensorDeviceAddress, 11); //Résolutions possibles: 9,10,11,12
}

// loop()
void loop() {
  // send data only when you receive data:
  if (Serial.available() > 0) {
          // read the incoming byte:
          incomingByte = Serial.read();
          if (incomingByte == 103) { //g
            doMeasures();
          }
  }
}

Code sur le Raspberry Pi

Nous allons utiliser un script Python qui se connectera via le port série à l'Arduino et :

  1. émettra le signal déclenchant l'action (dans notre cas le caractère "g")
  2. écoutera pendant 20 secondes l'éventuelle réponse
  3. transmettra la réponse à un serveur distant dans une requête GET
import serial
import os
import glob
import time
import requests

#Timeout de 20 secondes
tf = time.time()+20

#Initialisation du port série /dev/ttyUSB0 (cela peut varier selon la version de Raspbian et le type de planche Arduino, /dev/ttyUSB0 semble le défaut avec Raspbian Jessie et un Arduino Nano328)
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)

#On attend 2 secondes : si l'on écrit trop vite après l'ouverture du bus série, la communication risque d'échouer
time.sleep(2)

#On écrit le caractère "g" (ASCII 103) sur le port série
print("Now, we are sending the command to Arduino!")
ser.write("g".encode())

#Et on boucle désormais en lecture sur le port série
while time.time()<tf:
  line = ser.readline()
  if line:
    result = str(line).rstrip('\r\n')
    print(result)
    try:
      #Dans notre cas, le résultat brut de l'Arduino est passé à un serveur distant qui stocke l'information
      r = requests.post('https://monserveurdecollectedesdonnees', data=result, timeout=5.0)
      print(str(r.status_code)+" "+result)
    except:
      print("Failed, with payload: "+result)

#On ferme le canal de communication
ser.close()

Ce script peut alors être placé dans le crontab du système pour être exécuté périodiquement.