Aller au contenu

TP IoT

Durant ces séances de TP, nous allons prendre un main une version d'un microcontrôleur très connu : l'ESP32. Le but ici sera de récupérer des données de capteurs, les afficher, les transmettre et récupérer d'autres données pour effectuer certaines actions. Nous utiliserons différents moyens de communications, du Wi-Fi avec MQTT, du BLE et le protocole pair-à-pair basse consommation d'Espressif : ESP-NOW.

Ressources

Support de cours

Matérielles

  • Kit de développement ESP32-C3
  • Platine d'expérimentation et câbles de connexion
  • Capteur de température et d'humidité I2C AHT20
  • Écran OLED SSD1306 I2C
  • Câble USB-C-USB-A

Logicielles

Prise en main

  • Installer Platformio avec l'IDE que vous préférez ou en ligne de commande.
  • Créer un projet avec la configuration adaptée à la carte de développement.
  • Raccorder les composants en utilisant les broches 2 pour SDA et 3 pour SCL.

Entrées/sorties

  • Afficher et lire des messages sur le port série.
  • Faire clignoter la LED RGB intégrée de différentes couleurs.
  • Lire les valeurs du capteur.
  • Afficher les données sur l'écran.

Wi-Fi, MQTT et Web

  • Se connecter au point d'accès Wi-Fi et afficher l'adresse IP attribuée.
  • Se connecter au broker MQTT.
  • Publier des données capteurs sur un topic.
  • S'abonner à un topic MQTT.
  • Afficher les messages MQTT sur l'écran.
  • Afficher sur une page web les données des topics MQTT.

Attendus finaux

  • Publier les valeurs du capteurs sur les topics prenom/{humidite, temperature}
  • Afficher sur le serveur Web :
    • Les données du capteur interne (température et humidité).
    • Les données du capteur de co2 (loris/co2 sur MQTT).
  • Afficher sur l'écran l'IP locale.
  • Ajouter un controle de la led :
    • sur le serveur web (on/off simple ou rgb).
    • sur MQTT prenom/led, valeur 1 ou 0 ou RGB.

Exemple de publication MQTT et serveur Web avec un capteur de CO2 SCD40

[env:custom]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200
; build_flags = -D MQTTWEBCO2
lib_deps = 
    adafruit/Adafruit SSD1306
    adafruit/Adafruit NeoPixel
    sensirion/Sensirion I2C SCD4x
#include <WiFi.h>
#include <MQTT.h>
#include <SensirionI2CScd4x.h>
#include <Wire.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "secrets.h"

// defined in secrets.h
const char *ssid = SSID;
const char *pass = PASSWORD;

WiFiClient net;
MQTTClient client;
SensirionI2CScd4x scd4x;

AsyncWebServer server(80);

struct scd40_measure
{
    float temperature = 0.f;
    float humidity = 0.f;
    uint16_t co2 = 0.f;
} scd40_measure_t;

void getSensorData()
{
    scd4x.readMeasurement(scd40_measure_t.co2, scd40_measure_t.temperature,
                        scd40_measure_t.humidity);
}

void notFound(AsyncWebServerRequest *request)
{
    request->send(404, "text/plain", "Not found");
}

void connect()
{
    Serial.print("checking wifi...");
    while (WiFi.status() != WL_CONNECTED)
    {
        Serial.print(".");
        delay(1000);
    }

    Serial.print("\nconnecting...");
    while (!client.connect("loris"))
    {
        Serial.print(".");
        delay(1000);
    }

    Serial.println("\nconnected!");

    client.subscribe("loris/#");
}

void messageReceived(String &topic, String &payload)
{
    Serial.println("incoming: " + topic + " - " + payload);
}

void setup()
{
    Serial.begin(115200);
    WiFi.begin(ssid, pass);
    delay(1000);

    Wire.setPins(2, 3);
    Wire.begin();
    scd4x.begin(Wire);
    scd4x.stopPeriodicMeasurement();
    scd4x.startPeriodicMeasurement();

    Serial.println(WiFi.localIP());

    client.begin("172.20.63.156", net);
    client.onMessage(messageReceived);

    connect();

    server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request)
            { 
                char buf[12];
                sprintf(buf, "%f", scd40_measure_t.temperature);
                request->send(200, "text/plain", buf); });
    server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request)
            { 
                char buf[12];
                sprintf(buf, "%f", scd40_measure_t.humidity);
                request->send(200, "text/plain", buf); });
    server.on("/co2", HTTP_GET, [](AsyncWebServerRequest *request)
            { 
                char buf[12];
                sprintf(buf, "%d", scd40_measure_t.co2);
                request->send(200, "text/plain", buf); });

    server.begin();

    delay(5000);
}

void loop()
{
    client.loop();
    delay(10); // <- fixes some issues with WiFi stability

    if (!client.connected())
    {
        connect();
    }

    getSensorData();

    delay(100);

    char co2_buf[12];
    sprintf(co2_buf, "%d", scd40_measure_t.co2);
    client.publish("loris/co2", co2_buf);
    char temp_buf[12];
    sprintf(temp_buf, "%f", scd40_measure_t.temperature);
    client.publish("loris/temperature", temp_buf);
    char hum_buf[12];
    sprintf(hum_buf, "%f", scd40_measure_t.humidity);
    client.publish("loris/humidity", hum_buf);
    delay(5000);
}

ESP-NOW

  • Envoyer des données capteurs via ESP-NOW
  • Recevoir et afficher des données capteurs via ESP-NOW.

Attendus finaux

  • Afficher l'adresse MAC de l'ESP32 sur l'écran.
  • Envoyer un message dans une structure contenant la température et l'humidité du capteur.
  • Envoyer un message contenant une couleur RGB en appuyant sur le bouton BOOT de l'ESP32.
  • Intercepter les messages entrants :
    • Les messages leds pourront changer la couleur de la led RGB. la structure à respecter pour l'envoi est : {uint8_t red, uint8_t green, uint8_t blue}.
    • Les messages capteurs avec la structure {float temperature, float humidity} seront affichés sur l'écran ainsi que l'adresse MAC de l'expediteur, par exemple :
      MAC:DE:AD:BE:EF:FE:ED
      FRM:AB:CD:EF:AB:CD:EF
      24.6C 57.3%
      
  • (Bonus) Changer le canal Wi-Fi, afficher le RSSI.

On peu envoyer en broadcast ou spécifier une addresse MAC de destinataire en paramètre.

Proposition de correction

[env:custom]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200
; build_flags = -D ESPNOW
lib_deps = 
    adafruit/Adafruit SSD1306
    adafruit/Adafruit NeoPixel
    adafruit/Adafruit AHTX0
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <esp_wifi_types.h>
#include <Wire.h>
#include <Adafruit_AHTX0.h>
#include <Adafruit_GFX.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

#define OLED_RESET -1       // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32

#define SSDA 2
#define SSCL 3

#define NUMPIXELS 1
#define LED_PIN 8

#define ADDR_LEN 6
#define CHANNEL 1

const uint8_t broadcast[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

Adafruit_AHTX0 aht;

Adafruit_NeoPixel pixels(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

int buttonPin = 9;
bool buttonState = true;
int lastMillis = 0;

int rssi_display;

typedef struct
{
    unsigned frame_ctrl : 16;
    unsigned duration_id : 16;
    uint8_t addr1[6]; /* receiver address */
    uint8_t addr2[6]; /* sender address */
    uint8_t addr3[6]; /* filtering address */
    unsigned sequence_ctrl : 16;
    uint8_t addr4[6]; /* optional */
} wifi_ieee80211_mac_hdr_t;

typedef struct
{
    wifi_ieee80211_mac_hdr_t hdr;
    uint8_t payload[0]; /* network data ended with 4 bytes csum (CRC32) */
} wifi_ieee80211_packet_t;

// Define data structures for received messages
typedef struct color_data
{
    uint8_t red;
    uint8_t green;
    uint8_t blue;
} color_data_t;

typedef struct sensor_data
{
    float temperature;
    float humidity;
} sensor_data_t;

uint8_t sender_mac[6];

void promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
{
    if (type != WIFI_PKT_MGMT)
        return;

    const wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t *)buf;
    const wifi_ieee80211_packet_t *ipkt = (wifi_ieee80211_packet_t *)ppkt->payload;
    const wifi_ieee80211_mac_hdr_t *hdr = &ipkt->hdr;

    int rssi = ppkt->rx_ctrl.rssi;
    rssi_display = rssi;
}

void initScreen()
{
    Wire.setPins(SSDA, SSCL);

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
    if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
    {
        Serial.println(F("SSD1306 allocation failed"));
        for (;;)
            ; // Don't proceed, loop forever
    }

    display.display();
    delay(1000);

    // Clear the buffer.
    display.clearDisplay();
    display.display();

    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
}

void updateScreen(sensor_data_t sensor, const uint8_t *macAddr)
{
    display.clearDisplay();
    display.setCursor(0, 0);
    display.print("MAC:");
    display.print(WiFi.macAddress());
    display.setCursor(0, 10);
    display.print("FRM:");
    char buf_mac[18];
    sprintf(buf_mac, "%02X:%02X:%02X:%02X:%02X:%02X",
            macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
    display.print(buf_mac);
    display.setCursor(0, 20);
    display.print(String(sensor.temperature, 1));
    display.print("C ");
    display.print(String(sensor.humidity, 1));
    display.print("% ");
    display.print(rssi_display);
    display.print("dBm");
    display.display();
}

// Callback function for received data
void OnDataRecv(const uint8_t *macAddr, const uint8_t *data, int len)
{
    Serial.print("from: ");
    char buf_mac[18];
    sprintf(buf_mac, "%02X:%02X:%02X:%02X:%02X:%02X",
            macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
    Serial.println(buf_mac);
    if (len == sizeof(color_data_t))
    {
        color_data_t color;
        memcpy(&macAddr, sender_mac, sizeof(sender_mac));
        memcpy(&color, data, sizeof(color));
        pixels.setPixelColor(0, color.red, color.green, color.blue);
        pixels.show();
    }
    else if (len == sizeof(sensor_data_t))
    {
        sensor_data_t sensor;
        memcpy(&sensor, data, sizeof(sensor));
        updateScreen(sensor, macAddr);
    }
    else
    {
        Serial.println("Received data with unexpected length!");
    }

    // Send response (replace with your preferred response method)
    esp_now_send(macAddr, (uint8_t *)"OK", 2);
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
{
    Serial.print("\r\nLast Packet Send Status:\t");
    Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

void initESPNOW()
{

    // WiFi.printDiag(Serial); // Uncomment to verify channel number before
    esp_wifi_set_promiscuous(true);
    esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE);
    esp_wifi_set_promiscuous(false);
    // WiFi.printDiag(Serial); // Uncomment to verify channel change after

    // Initialize ESP-NOW
    if (esp_now_init() != ESP_OK)
    {
        Serial.println("Error initializing ESP-NOW");
        while (true)
            ;
    }

    esp_now_peer_info_t peerInfo;

    // Register peer
    memcpy(peerInfo.peer_addr, broadcast, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;

    // Add peer
    if (esp_now_add_peer(&peerInfo) != ESP_OK)
    {
        Serial.println("Failed to add peer");
        return;
    }

    // Register callback function for received data
    esp_now_register_recv_cb(OnDataRecv);
    esp_now_register_send_cb(OnDataSent);
    Serial.println("ESP-NOW receiver started");
}

void sendColor(uint8_t red, uint8_t green, uint8_t blue)
{
    color_data_t msg;
    msg.red = red;
    msg.green = green;
    msg.blue = blue;
    esp_now_send(broadcast, (uint8_t *)&msg, sizeof(msg));
}

void sendSensor()
{
    sensor_data_t msg;
    sensors_event_t humidity, temperature;
    aht.getEvent(&humidity, &temperature);
    msg.humidity = humidity.relative_humidity;
    msg.temperature = temperature.temperature;
    esp_now_send(broadcast, (uint8_t *)&msg, sizeof(msg));
}

void IRAM_ATTR buttonPressed()
{
    buttonState = !buttonState;
}

void setup()
{
    Serial.begin(115200);

    initScreen();
    pixels.begin();
    pixels.setBrightness(10);

    initESPNOW();

    display.setCursor(0, 0);
    display.print("MAC:");
    display.print(WiFi.macAddress());
    yield();
    display.display();

    aht.begin(&Wire);

    pinMode(buttonPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(9), buttonPressed, CHANGE);

    esp_wifi_set_promiscuous(true);
    esp_wifi_set_promiscuous_rx_cb(&promiscuous_rx_cb);
}

void loop()
{
    if (buttonState && (millis() - lastMillis >= 2000))
    {
        sendSensor();
        sendColor(random(250), random(250), random(250));
        lastMillis = millis();
        buttonState = !buttonState;
    }
}

BLE

  • Exposer les données capteurs sur un serveur BLE.
  • Récuperer les données capteurs d'un autre ESP32 avec un client BLE.

Attendus finaux

Utiliser les profils, services et attributs appropriés → Spécifications. Dans les deux cas il faudra utiliser les bons UUIDs prédéfinis dans la spécification. Au choix :

Client

  • Faire un scan et se connecter à un ESP32.
  • Afficher l'adresse MAC de l'ESP32 auquel on s'est connecté sur l'écran et les valeurs de son capteur.

Serveur

  • Afficher l'adresse MAC de son ESP32 sur l'écran et les valeurs de son capteur.
  • Exposer un service GATT, ESS ou CUSTOM contenant :
    • une caractéristique pour la température avec le descripteur associé.
    • une caractéristique pour l'humidité avec le descripteur associé.

Extra

  • Bonus: controller les leds des autres ESP32 via BLE.
  • Mettre l'ESP32 en mode sommeil et sommeil profond et le réveiller.