Lire les données d’un capteur de temperature Xiaomi avec un Arduino Nano ESP32 via BLE

4.5
(4)

Dans ce tutoriel nous allons utiliser un Arduino Nano ESP32 pour récupérer via BLE les données d’un capteur de température et d’humidité Xiaomi Mi Temperature and Humidity Monitor 2. Les données seront ensuite remontée vers Arduino Cloud par l’Arduino Nano ESP32 via votre connexion Wifi.

Lecture et décodage des données du capteur Xiaomi

La première étape est d’identifier la caractéristique permettant d’avoir accès à la température et à l’humidité mesurée par le capteur. Je vous invite à lire mon précédent article Découvrir Bluetooth® Low Energy (BLE) avec un Arduino Nano ESP32.

Le plus simple pour identifier les périphériques BLE, leurs services et caractéristiques, est d’utiliser l’application LightBlue (Android, iOS) sur votre smartphone. L’application découvre mes 2 capteurs Xiaomi. Les 2 périphériques BLE sont identifiés par leur nom LYWSD03MMC et leur adresse (A4:C1:38:D4:FD:17 et A4:C1:38:F1:EA:3F).

Après la connexion au périphérique, on observe que le service avec l’UUID “ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6” expose la caractéristique dont l’UUID “ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6” nommée “Temperature and Humidity“.

La lecture de cette caractéristique au format hexadécimal remonte les 5 octets “7C 07 36 5F 0A“.

Les octets sont au format little endian, les deux premiers octets correspondent à la température (en 1/100 °C), le troisième correspond à l’humidité (en %) et les deux derniers octets correspondent à la tension de la pile (en 1/1000 V).

0x7C07 -> 0x077C = 1916 -> Température = 19,16 °C

0x36 = 54 -> Humidité = 54 %

0x5F0A -> 0x0A5F = 2655 -> Tension = 2,655 V

Le programme

Afin d’économiser la pile du capteur Xiaomi, j’ai décidé de ne pas lui imposer une connexion BLE permanente. L’Arduino se connecte aux capteurs toutes les heures, lit leurs données et s’en déconnecte.

Lors du setup, l’Arduino scanne les périphériques BLE et recherche les périphériques Xiaomi Mi Temperature and Humidity Monitor 2, qui ont pour nom LYWSD03MMC. L’Arduino recherche autant de périphérique Xiaomi qu’indiqué dans le define NUMBER_OF_SENSORS. Dans le croquis ci-dessous j’utilise 2 capteurs Xiaomi, et cette constante est donc définie à 2. Le programme boucle tant qu’il n’a pas trouvé tous les capteurs, donc mettez bien cette constante avec le nombre de capteurs que vous avez.

Lors de la première connexion au capteur, le programme vérifie la disponibilité du service dont l’UUID est “ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6” et qu’il contient bien la caractéristique dont l’UUID est “ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6“, correspondant à Temperature and Humidity.

Ensuite, le programme va stocker tous les périphériques Xiaomi identifiés dans le tableau peripheral[]. Il réutilisera ces informations ultérieurement pour se reconnecter directement aux périphériques et lire leurs données.

Une fois par heure (durée définie dans la variable period exprimée en secondes), l’Arduino va alors se connecter aux périphériques et lire leurs données, puis s’en déconnecter et attendre l’heure suivante.

Les données sont remontées dans Arduino Cloud grâce à la fonction ArduinoCloud.update() qui est appelée à chaque boucle loop. Dans ce tutoriel je ne recherche pas à optimiser cette partie car l’Arduino est alimenté par un transformateur 220V/5V.

// Monitor Xiaomi Mi Temperature and Humidity Monitor 2
// with Arduino Nano ESP32 and Arduino Cloud
// https://tutoduino.fr/
// Copyleft 2023
#include "thingProperties.h"
#include <ArduinoBLE.h>
// Bluetooth Low Energy Mi Temperature and Humidity Service
const char* miServiceUUID = "ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6";
// Bluetooth Low Energy Mi Temperature and Humidity Characteristic
const char* miCharacteristicUUID = "ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6";
// Variables used for periodic reading
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;
unsigned long period = 3600000;
// State of the BLE connexion
enum state_t {
  INIT_STATE,
  SCANNING_STATE,
  ALL_PERIPHERAL_FOUND_STATE,
  RUNNING_STATE
};
state_t state = INIT_STATE;
// Number of Mi Temperature and Humidity Monitor 2 peripherals we want to monitor
#define NUMBER_OF_SENSORS 2
// Number of Mi Temperature and Humidity Monitor 2 peripherals found during BLE scanning
uint8_t nbMiTempPeripheralFound = 0;
// Global variable to store BLE devices found during scanning
BLEDevice peripheral[NUMBER_OF_SENSORS];
// Function to disconnect all peripherals found during BLE scanning
void disconnectAllPeripherals() {
  for (int sensor = 0; sensor < nbMiTempPeripheralFound; sensor++) {
    // Check if peripheral is connected
    if (peripheral[sensor].connected()) {
      // Disconnect peripheral
      peripheral[sensor].disconnect();
    }
  }
}
// Function to stop BLE scanning and disconnect all peripherals
// that are connected
void stopScanMiPeripheral() {
  // Stop BLE scanning
  BLE.stopScan();
  // Disconnect from peripherals
  disconnectAllPeripherals();
  // Leave ALL_PERIPHERAL_FOUND_STATE state to enter RUNNING_STATE
  state = RUNNING_STATE;
}
// BLE scan and connect to Mi Temperature and Humidity Monotor 2 peripherals
// Discover exposed services and check if required temperature and humidity
// characteristics are available on these peripherals
uint8_t scanMiPeripheral() {
  peripheral[nbMiTempPeripheralFound] = BLE.available();
  if (peripheral[nbMiTempPeripheralFound]) {
    // Check if the peripheral is a Mi Temperature and Humidity Monitor 2 peripheral
    if (peripheral[nbMiTempPeripheralFound].hasLocalName()) {
      if (peripheral[nbMiTempPeripheralFound].localName() == "LYWSD03MMC") {
        Serial.print("Discovered a Mi Temperature and Humidity Monitor 2, address : ");
        Serial.println(peripheral[nbMiTempPeripheralFound].address());
        // Connect to the peripheral (try it twice)
        if (peripheral[nbMiTempPeripheralFound].connect()) {
        } else {
          Serial.println("Failed to connect, disconnect and try again!");
          peripheral[nbMiTempPeripheralFound].disconnect();
          if (peripheral[nbMiTempPeripheralFound].connect()) {
            Serial.println("Connected");
          } else {
            Serial.println("Failed to connect!");
            return 1;
          }
        }
        // Peripheral is connected, discover exposed services and check if temperature
        // and humidity characteristic is available
        if (peripheral[nbMiTempPeripheralFound].discoverService(miServiceUUID)) {
          if (!peripheral[nbMiTempPeripheralFound].characteristic(miCharacteristicUUID)) {
            Serial.println("Characteristic not found!");
            peripheral[nbMiTempPeripheralFound].disconnect();
            return 1;
          }
          // Required characteristic has been found for this peripheral
          Serial.println("Sensor ok");
          nbMiTempPeripheralFound++;
        }
      }
    }
  }
  // Change state to ALL_PERIPHERAL_FOUND_STATE if all peripherals have been discovered
  if ((nbMiTempPeripheralFound == NUMBER_OF_SENSORS) && (state == SCANNING_STATE)) {
    state = ALL_PERIPHERAL_FOUND_STATE;
    Serial.println("All sensors found");
  }
  return 0;
}
// Connect to Mi Temperature and Humidity Monotor 2 peripherals
// and read Temperature, Humidity and Battery data
void connectAndReadData() {
  float temp, hum, batt;
  uint8_t value[5];
  // The connection and read is only done in RUNNING_STATE
  if (state == RUNNING_STATE) {
    for (int sensor = 0; sensor < nbMiTempPeripheralFound; sensor++) {
      if (peripheral[sensor].connect()) {
        if (peripheral[sensor].discoverService(miServiceUUID)) {
          peripheral[sensor].characteristic(miCharacteristicUUID).readValue(value, 5);
          temp = ((float)(value[1] << 8) + (float)value[0]) / 100;
          hum = (float)value[2];
          batt = ((float)(value[4] << 8) + (float)value[3]) / 1000;
          Serial.print("Temp = ");
          Serial.print(temp);
          Serial.print(" ");
          Serial.print("Hum = ");
          Serial.print(hum);
          Serial.print(" ");
          Serial.print("Batt = ");
          Serial.print(batt);
          Serial.println();
          if (sensor == 0) {
            temperature = temp;
            batterie = batt;
          }
          if (sensor == 1) {
            temperature2 = temp;
            batterie2 = batt;
          }
          // Once date is read, disconnect from peripheral
          peripheral[sensor].disconnect();
        }
      }
    }
  }
}
void setup() {
  // Start serial port monitor
  Serial.begin(9600);
  delay(1500);
  // BLE initialization
  if (!BLE.begin()) {
    Serial.println("Starting Bluetooth® Low Energy module failed!");
    while (1) {};
  }
  Serial.println("Bluetooth® Low Energy module started");
  // Start scanning for BLE peripheral
  BLE.scan();
  state = SCANNING_STATE;
  // Init Arduino IoT Cloud
  initProperties();
  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}
void loop() {
  switch (state) {
    case SCANNING_STATE:
      // Scan BLE peripherals to find Mi Temperature and Humidity Monitor 2 peripherals
      scanMiPeripheral();
      break;
    case ALL_PERIPHERAL_FOUND_STATE:
      // Stop BLE scanning once all Mi Temperature and Humidity Monitor 2 peripherals have been found
      stopScanMiPeripheral();
      // Connect to Mi Temperature and Humidity Monitor 2 peripherals
      // and read Temperature, Humidity and Battery data
      connectAndReadData();
      break;
    case RUNNING_STATE:
      // Periodically connect to Mi Temperature and Humidity Monitor 2 peripherals
      // and read Temperature, Humidity and Battery data
      currentMillis = millis();
      if (currentMillis - previousMillis >= period) {
        previousMillis = currentMillis;
        connectAndReadData();
      }
      break;
  }
  // Update Arduino IoT Cloud
  ArduinoCloud.update();
}

Ce croquis est disponible sur mon Github.

Arduino Cloud

Les différentes étapes de la création du projet sous Arduino Cloud est détaillée dans mon tuto Créez votre premier objet connecté (IoT) avec la plateforme Arduino Cloud et un Arduino Nano ESP32.

Pour afficher la température et le niveau de la pile de mes 2 capteurs, j’ai créé 4 variable de type float dans “Things”. Je ne remonte pas l’humidité car mon “Arduino Plan Free” me limite à 5 variables… Je remonte donc uniquement la température et la tension de la pile pour mes 2 capteurs Xiaomi.

Le “Dashboard” affiche les valeurs numériques des températures et des tensions des batteries, ainsi que des graphiques pour afficher les variations de température au cours du temps.

J’espère que cet article vous donnera des idées pour explorer l’Arduino Nano ESP32, le BLE et Arduino Cloud. N’hésitez pas à donner une note à ce tuto (étoiles ci-dessous) et à ajouter un commentaire afin que je puisse connaître votre avis et l’améliorer si besoin.

Votre avis compte !

Note moyenne : 4.5 / 5. Nombre de votes : 4

Pas encore de vote pour ce tutoriel

Désolé si cet article ne vous a pas intéressé

Merci de commenter afin que je puisse l’améliorer.

Dites-moi comment améliorer cette page.