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
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
- IDE Platformio
- Framework Arduino
- Adafruit AHTX0
- Adafruit SSD1306
- Adafruit NeoPixel
- ESP-NOW
- NimBLE
- Wi-Fi
- arduino-mqtt
- ESPAsyncWebServer
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
, valeur1
ou0
ou RGB.
Exemple de publication MQTT et serveur Web avec un capteur de CO2 SCD40
#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 :
- Les messages leds pourront changer la couleur de la led RGB. la structure à respecter pour l'envoi est :
- (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
#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.