SÉQUENCE 05 / Terminale · Projet de synthèse
La ruche connectée.
Mesurer, traiter, communiquer et valider.
La situation de départ
Un apiculteur souhaite consulter une température sans ouvrir systématiquement la ruche. L’équipe réalise d’abord une maquette : DS18B20, UNO R4 WiFi et navigateur sur le même réseau.
Une sonde renseigne sur son emplacement, pas sur l’état de santé de la colonie. Cette maquette ne commande ni chauffage, ni ventilation. Sur une vraie ruche, choix et pose se font avec l’apiculteur, sans gêner les abeilles.
01 / Définir des exigences vérifiables
| Exigence proposée | Critère de vérification |
|---|---|
| Acquérir périodiquement | Un cycle toutes les 5 s environ |
| Identifier une donnée indisponible | Erreur plutôt qu’une fausse valeur |
| Rendre la mesure accessible | Page locale et point /mesures JSON |
| Identifier une donnée périmée | Invalide au-delà de 15 s |
| Borner l’attente HTTP | Requête limitée à 1024 octets et 1,5 s |
| Mesurer même sans réseau | Lecture série maintenue ; tentatives Wi-Fi espacées |
Ce cahier des charges de classe est à adapter. « Consulter depuis n’importe où » serait une exigence supplémentaire : le code fournit une consultation locale.

02 / Mettre en service
- Reprendre le câblage et les bibliothèques du TP température avec une UNO R4 WiFi.
- Ouvrir le projet
05_ruche_wifi. WiFiS3 est fourni par Arduino UNO R4 Boards ; ne pas utiliser la bibliothèque WiFi d’une autre carte. - Dans
arduino_secrets.h, renseigner le réseau autorisé et son mot de passe, dans votre copie seulement. - Téléverser ; ouvrir le moniteur à 115200 bauds et relever l’adresse affichée après connexion.
- Sur le même réseau, ouvrir
http://adresse-affichée/puishttp://adresse-affichée/mesures.
Un réseau scolaire ou invité peut isoler ses appareils ; vérifier ce point avec son responsable. Aucun port du routeur ne doit être ouvert sur Internet pour ce TP.
03 / Le programme complet
05_ruche_wifi.ino
Projet complet ↓// UNO R4 WiFi seulement. Prototype de classe sur reseau local de confiance.
#include <WiFiS3.h>
#include "arduino_secrets.h"
#include <OneWire.h>
#include <DallasTemperature.h>
#include <math.h>
constexpr uint8_t BROCHE_DQ = 4;
constexpr unsigned long MESURE_MS = 5000;
constexpr unsigned long CONVERSION_MS = 800; // Marge au-dela des 750 ms a 12 bits.
OneWire bus(BROCHE_DQ);
DallasTemperature capteurs(&bus);
DeviceAddress adresse;
bool conversion = false;
bool adresseConnue = false;
bool mesureValide = false;
float temperatureC = 0.0f;
unsigned long debutConversion = 0;
unsigned long dernierCycle = 0;
unsigned long derniereMesure = 0;
void lancerMesure() {
// Recherche du premier capteur : reconnexion possible apres debranchement.
adresseConnue = capteurs.getAddress(adresse, 0);
if (!adresseConnue) {
mesureValide = false;
Serial.println("Erreur : DS18B20 absent");
return;
}
capteurs.setResolution(adresse, 12);
capteurs.requestTemperatures();
debutConversion = millis();
conversion = true;
}
void gererMesure() {
const unsigned long maintenant = millis();
if (conversion && maintenant - debutConversion >= CONVERSION_MS) {
conversion = false;
const float t = capteurs.getTempC(adresse);
mesureValide = isfinite(t) && t != DEVICE_DISCONNECTED_C && t >= -55 && t <= 125;
if (mesureValide) {
temperatureC = t;
derniereMesure = millis();
Serial.print("Temperature : "); Serial.print(t, 2); Serial.println(" deg C");
} else {
Serial.println("Erreur : lecture DS18B20 impossible");
}
}
if (!conversion && maintenant - dernierCycle >= MESURE_MS) {
dernierCycle = maintenant;
lancerMesure();
}
}
WiFiServer serveur(80);
WiFiClient client;
bool serveurPret = false;
bool tentativeFaite = false;
unsigned long dernierEssai = 0;
unsigned long dernierControleReseau = 0;
unsigned long debutClient = 0;
constexpr unsigned long REESSAI_MS = 30000;
constexpr unsigned long FRAICHEUR_MS = 15000;
char requete[96];
size_t tailleLigne = 0;
size_t totalRecu = 0;
bool premiereLigneLue = false;
bool ligneVide = true;
bool donneeFraiche() {
return mesureValide && millis() - derniereMesure <= FRAICHEUR_MS;
}
void fermerClient() {
client.stop();
client = WiFiClient();
}
void repondre() {
const bool accueil = strncmp(requete, "GET / HTTP/1.", 13) == 0;
const bool json = strncmp(requete, "GET /mesures HTTP/1.", 20) == 0;
client.println(accueil || json ? "HTTP/1.1 200 OK" : "HTTP/1.1 404 Not Found");
client.println("Connection: close");
client.println("Cache-Control: no-store");
client.println(json ? "Content-Type: application/json" : "Content-Type: text/html; charset=utf-8");
client.println();
if (json) {
client.print("{\"temperature_c\":");
if (donneeFraiche()) client.print(temperatureC, 2); else client.print("null");
client.print(",\"valide\":"); client.print(donneeFraiche() ? "true" : "false");
client.print(",\"age_ms\":");
if (mesureValide) client.print(millis() - derniereMesure); else client.print("null");
client.println("}");
} else if (accueil) {
client.println("<!doctype html><html lang='fr'><meta charset='utf-8'><meta name='viewport' content='width=device-width'><meta http-equiv='refresh' content='10'><title>Ruche : mesure locale</title><body><h1>Ruche pedagogique</h1>");
client.print("<p>Temperature : <strong>");
if (donneeFraiche()) {client.print(temperatureC, 2); client.print(" °C");}
else client.print("mesure indisponible");
client.println("</strong></p><p>Actualisation de la page : 10 s. Acquisition : 5 s.</p>");
client.println("<p>Une seule sonde ; aucune conclusion sanitaire automatique.</p><a href='/mesures'>Donnees JSON</a></body></html>");
} else client.println("<h1>404</h1><a href='/'>Accueil</a>");
fermerClient();
}
void gererHTTP() {
if (!serveurPret) return;
if (!client) {
client = serveur.available();
if (!client) return;
debutClient = millis();
tailleLigne = 0; totalRecu = 0;
premiereLigneLue = false; ligneVide = true;
requete[0] = '\0';
}
if (!client.connected() || millis() - debutClient > 1500) {fermerClient(); return;}
// Au plus 64 caracteres traites par passage ; taille et duree bornees.
for (uint8_t n = 0; n < 64 && client.available(); ++n) {
const char c = char(client.read());
if (++totalRecu > 1024) {fermerClient(); return;}
if (!premiereLigneLue && c != '\r' && c != '\n') {
if (tailleLigne >= sizeof(requete) - 1) {fermerClient(); return;}
requete[tailleLigne++] = c;
requete[tailleLigne] = '\0';
}
if (c == '\n') {
if (ligneVide) {repondre(); return;}
premiereLigneLue = true;
ligneVide = true;
} else if (c != '\r') ligneVide = false;
}
}
void gererReseau() {
const unsigned long maintenant = millis();
if (maintenant - dernierControleReseau < 1000) return;
dernierControleReseau = maintenant;
const int etat = WiFi.status();
if (etat == WL_CONNECTED) {
if (!serveurPret) {
serveur.begin(); serveurPret = true;
Serial.print("Page locale : http://"); Serial.println(WiFi.localIP());
}
return;
}
if (serveurPret) {serveurPret = false; fermerClient();}
if (!tentativeFaite || maintenant - dernierEssai >= REESSAI_MS) {
tentativeFaite = true; dernierEssai = maintenant;
if (etat == WL_NO_MODULE) {Serial.println("Module WiFi indisponible"); return;}
if (SECRET_SSID[0] == '\0') {Serial.println("Configurer arduino_secrets.h"); return;}
Serial.println("Tentative WiFi (l'appel de bibliotheque peut attendre)");
WiFi.begin(SECRET_SSID, SECRET_PASS);
}
}
void setup() {
Serial.begin(115200);
capteurs.begin();
capteurs.setWaitForConversion(false);
dernierCycle = millis();
lancerMesure();
}
void loop() {
gererMesure();
gererReseau();
gererHTTP();
}
Second onglet : arduino_secrets.h
#pragma once
// Renseigner uniquement dans votre copie locale. Ne pas publier ce fichier rempli.
#define SECRET_SSID ""
#define SECRET_PASS ""
Renseignez le SSID et le mot de passe dans votre copie locale. Le ZIP contient cet onglet vide ; ne partagez jamais le projet avec vos vrais identifiants.
04 / Comprendre le fonctionnement
| Partie | Rôle |
|---|---|
gererMesure() | Conversion et validité comme au TP précédent. |
gererReseau() | Contrôle la connexion, démarre le serveur, espace les tentatives à 30 s. |
gererHTTP() | Traite au plus 64 caractères par passage ; ferme les clients trop lents ou bavards. |
donneeFraiche() | Vérifie validité et âge. |
/mesures | Renvoie température, validité, âge ; null si indisponible. |
La page se recharge toutes les 10 s, la sonde est interrogée toutes les 5 s : deux périodes distinctes. Aucun historique n’est conservé après extinction.
Conversion et lecture HTTP sont organisées sans longue attente dans la boucle. Les appels WiFiS3, notamment WiFi.begin() et les écritures réseau, restent synchrones et peuvent suspendre les autres tâches. Ce prototype n’est pas un système temps réel strict.
Le serveur HTTP, sans chiffrement ni authentification, est prévu pour un réseau local de confiance. Une exposition publique demande une architecture sécurisée distincte.
05 / Valider avant le rucher
| Essai | Résultat attendu | Preuve |
|---|---|---|
| Sonde stabilisée | Série et page cohérentes | Capture et comparaison à une référence |
| Capteur absent au démarrage | Erreur série ; null sur la page JSON | Journal et capture |
| Coupure du Wi-Fi | Mesures série poursuivies, tentatives espacées | Relevé chronologique |
| Retour du réseau | Adresse affichée et page accessible | Capture après reconnexion |
| Client lent ou requête trop longue | Connexion fermée ; boucle disponible | Protocole et durée |
| Extinction / redémarrage | Pas de réutilisation silencieuse d’une ancienne mesure | Essai de démarrage |
Les programmes sont compilés pour les cartes indiquées ; ces essais physiques restent à réaliser avec le matériel de la classe. Rapporter aussi les échecs et expliquer leurs causes.
06 / Du prototype au système de terrain
Première : représenter
Tracer les blocs, nommer les flux d’information et d’énergie, expliquer le trajet de la donnée jusqu’au navigateur.
Terminale : justifier
Comparer Wi-Fi, stockage local et réseau cellulaire selon portée, disponibilité, coût, consommation et maintenance.
Hors de portée Wi-Fi, ce sketch ne suffit pas. Le parcours ESP32 + SHT85 + LTE-M présente une autre architecture ; il ne s’agit pas de recopier sa radio sur le schéma UNO.
Humidité, poids et température extérieure sont des évolutions possibles. Choisir les capteurs, vérifier tensions et niveaux logiques, concevoir le montage et adapter le code. Ces mesures ne sont pas produites par cette maquette.
Étudier le boîtier, la condensation, le passage des câbles et les perturbations pour la colonie. Mesurer le courant moyen : P = U × I, puis autonomie ≈ énergie utile / puissance moyenne, en intégrant pertes et cycles radio. Aucune autonomie n’est annoncée sans mesure.
À VOUS DE JOUER / PREMIÈRE
Pourquoi une sonde dans un coin de ruche ne représente-t-elle pas toutes les températures ?
PLUS LOIN / TERMINALE
Proposer trois exigences pour un rucher isolé, des méthodes d’essai et deux architectures de communication.
Pistes de correction
Gradient spatial et inertie limitent la représentativité. Disponibilité de la liaison, autonomie et tenue à l’environnement demandent chacune un critère mesurable et un protocole.
Le livrable de l’équipe
Présenter besoin et limites, schéma annoté, programme commenté, tableau d’essais, estimation énergétique fondée sur des mesures et amélioration argumentée. La qualité de la validation compte autant que la démonstration.
SOURCES ET PROLONGEMENTS
Vérifier dans la documentation.
- Arduino UNO R4 Minima · UNO R4 WiFi
- Arduino : résolution du convertisseur analogique
- Arduino : exemples WiFiS3
- Analog Devices : DS18B20
- OneWire · DallasTemperature
- Éduscol : programmes et ressources STI2D
Contenus originaux Quiet Bee. Progression proposée pour travailler la chaîne d’information, le prototypage et la validation ; elle ne remplace pas le programme ni les consignes de votre professeur.