quiet bee.The ESP32 lab ↗THE SECONDARY SCHOOL LABFR / EN

LESSON 05 / Year 13 · Synthesis project

The connected beehive.

Measure, process, communicate and validate.

The starting situation

A beekeeper wants to check a temperature without routinely opening the hive. The team first builds a model: DS18B20, UNO R4 WiFi and a browser on the same network.

A probe reports on its location, not the health of the colony. This model controls neither heating nor ventilation. On a real hive, choose and position equipment with the beekeeper without disturbing the bees.

ACQUIREDS18B20→PROCESSUNO R4 WiFi→COMMUNICATEHTTP / JSON

01 / Define verifiable requirements

Suggested requirementVerification criterion
Acquire periodicallyA cycle approximately every 5 s
Identify unavailable dataAn error rather than a false value
Make the measurement accessibleLocal page and /mesures JSON endpoint
Identify stale dataInvalid after 15 s
Bound the HTTP waitRequest limited to 1024 bytes and 1.5 s
Measure even without a networkSerial readings continue; Wi-Fi attempts are spaced out

Adapt this classroom specification. “View from anywhere” would be an additional requirement: the code provides local access.

Wiring diagram: The connected beehive
Click to enlarge · SVG · PNG. Connectors are spread out for readability: follow the pin names on your board.

02 / Commission the setup

  1. Use the wiring and libraries from the temperature lesson with a UNO R4 WiFi.
  2. Open the project 05_ruche_wifi. WiFiS3 is supplied with Arduino UNO R4 Boards; do not use another board's WiFi library.
  3. In arduino_secrets.h, enter the authorised network and its password in your own copy only.
  4. Upload; open the monitor at 115200 baud and record the address displayed after connection.
  5. On the same network, open http://adresse-affichée/ then http://adresse-affichée/mesures.

A school or guest network may isolate its devices; check with its administrator. No router port should be opened to the Internet for this lesson.

03 / The complete program

05_ruche_wifi.ino

Complete project ↓
// 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(" &deg;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 tab: arduino_secrets.h

#pragma once
// Renseigner uniquement dans votre copie locale. Ne pas publier ce fichier rempli.
#define SECRET_SSID ""
#define SECRET_PASS ""

Enter the SSID and password in your local copy. The ZIP contains this tab empty; never share the project with your real credentials.

04 / Understand the operation

PartRole
gererMesure()Conversion and validity as in the previous lesson.
gererReseau()Checks the connection, starts the server and spaces attempts 30 s apart.
gererHTTP()Handles up to 64 characters per pass; closes clients that are too slow or send too much data.
donneeFraiche()Checks validity and age.
/mesuresReturns temperature, validity and age; null if unavailable.

The page reloads every 10 s and the probe is queried every 5 s: two separate periods. No history is retained after power-off.

Conversion and HTTP reading are organised without long waits in the loop. WiFiS3 calls, particularly WiFi.begin() and network writes, remain synchronous and may pause other tasks. This prototype is not a strict real-time system.

The HTTP server, without encryption or authentication, is intended for a trusted local network. Public exposure requires a separate secure architecture.

05 / Validate before going to the apiary

TestExpected resultEvidence
Stabilised probeConsistent serial and page readingsScreenshot and comparison with a reference
Sensor absent at startupSerial error; null on the JSON pageLog and screenshot
Wi-Fi outageSerial measurements continue, attempts spaced outChronological record
Network returnsAddress displayed and page accessibleScreenshot after reconnection
Slow client or oversized requestConnection closed; loop remains availableProtocol and duration
Power-off / restartNo silent reuse of an old measurementStartup test

The programs have been compiled for the specified boards; these physical tests still need to be performed with classroom hardware. Report failures too, and explain their causes.

06 / From prototype to field system

Year 12: represent

Draw the blocks, name the information and energy flows, and explain the data's path to the browser.

Year 13: justify

Compare Wi-Fi, local storage and cellular networks in terms of range, availability, cost, consumption and maintenance.

Outside Wi-Fi range, this sketch is not enough. The ESP32 + SHT85 + LTE-M learning path presents another architecture; it is not a matter of copying its radio onto the UNO schematic.

Humidity, weight and outdoor temperature are possible extensions. Choose sensors, check voltages and logic levels, design the circuit and adapt the code. This model does not produce these measurements.

Study the enclosure, condensation, cable routing and disturbance to the colony. Measure average current: P = U × I, then battery life ≈ usable energy / average power, including losses and radio cycles. No battery life is claimed without measurement.

YOUR TURN / YEAR 12

Why does a probe in one corner of a hive not represent every temperature?

GOING FURTHER / YEAR 13

Propose three requirements for a remote apiary, test methods and two communication architectures.

Suggested answers

Spatial gradients and thermal inertia limit representativeness. Connection availability, battery life and environmental resistance each need a measurable criterion and a protocol.

The team's deliverable

Present the need and limitations, annotated schematic, commented program, test table, energy estimate based on measurements and a justified improvement. Validation quality matters as much as the demonstration.

Back to the learning path →

SOURCES AND FURTHER READING

Check the documentation.

Original Quiet Bee content. A suggested progression for working on the information chain, prototyping and validation; it does not replace the curriculum or your teacher's instructions.