Aller au contenu

Fabriquer un thermomètre avec un ESP8266

·1540 mots·8 mins
 Author

Featured image
Le résultat final

J’ai trouvé récemment un capteur DHT11 qui traînais dans un tiroir, et je me suis dit que c’était l’occasion de faire un petit projet sympa avec. Je vais vous montrer comment fabriquer un thermomètre avec un ESP8266 et un capteur DHT11. Ce projet est parfait pour les débutants qui souhaitent se familiariser avec l’ESP8266 et les capteurs de température et d’humidité.

Pré-requis
#

Nécessaire:

  • Un ESP8266 (J’utiliserais un Wemos D1 Mini mais n’importe quel modèle fait l’affaire)
  • Un capteur DHT11
  • Un écran OLED 128x32
  • Des fils de connexion (des Dupont font l’affaire)
  • De quoi souder (fer, étain, etc.) ou une plaque d’essai (breadboard)
  • Un PC avec VSCode et PlatformIO installé, ou tout autre IDE de votre choix
  • Un peu de patience

Facultatif:

  • Une photorésistance pour éteindre l’écran la nuit (sinon je ne dors pas)
  • Un capteur capacitif pour basculer l’affichage entre la température et l’humidité (c’est un peu gadget mais je voulais jouer avec)

Montage
#

Tous les capteurs sont alimentés en 3.3V, donc pas de soucis d’alimentation. Chaque capteur a sa propre pin pour les données, seul exception pour l’écran OLED qui utilise le bus I2C (donc deux pins seulement) et la photorésistance qui est branchée sur une pin analogique avec une résistance pull-down (10k Ohm).

Montage
Schéma du montage réalisé avec Excalidraw

Si c’est la première fois que vous réaliser ce genre de montage, je vous conseille de commencer sur une plaque d’essai (breadboard) avant de souder le tout et d’utiliser une carte de développement complète comme la Wemos D1 Arduino, qui connecte à l’ESP8266 l’intégralité des I/O avec des prises Dupont.

Montage sur breadboard
Montage sur breadboard

Code
#

Pour le code, j’utilise PlatformIO, qui est un IDE basé sur VSCode et qui permet de gérer facilement les bibliothèques et les dépendances pour son projet, et ceux quelque soit la carte utilisée. Pour installer PlatformIO, il suffit d’installer l’extension PlatformIO IDE sur VSCode. Une fois installé, vous pouvez créer un nouveau projet en sélectionnant la carte ESP8266 et en ajoutant les bibliothèques nécessaires.

Pour ce projet, nous aurons besoin des bibliothèques suivantes :

  • Adafruit GFX
  • Adafruit SSD1306
  • DHT sensor library
  • DHT11

Vous pouvez les installer directement depuis les paramètres du projet ou en ajoutant les lignes suivantes dans le fichier platformio.ini :

[env:d1]
platform = espressif8266
board = d1_mini_lite # adapter selon votre carte
framework = arduino
lib_deps = 
 adafruit/Adafruit SSD1306@^2.5.13
 adafruit/Adafruit GFX Library@^1.12.0
 adafruit/DHT sensor library@^1.4.6
 adafruit/Adafruit Unified Sensor@^1.1.15

Une fois les bibliothèques installées, vous pouvez (enfin) jouer un peu avec le code. Voici le code que j’ai réalisé :

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#include <Wire.h>

// Définition de la taille de l'écran
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_ADDR 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Capteur DHT
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

// Bouton
#define BUTTON_PIN D7

// Photorésistance
#define PHOTORESISTOR_PIN A0 // Broche de la photorésistance
#define LIGHT_THRESHOLD 50  // Seuil de luminosité pour éteindre l'écran

bool isScreenOff = false; // État de l'écran

// États
volatile bool showTemperature = true;
volatile bool needsRefresh = true;
volatile bool triggerAnimation = false;

// Paramètres d'animation
#define ANIMATION_SPEED 6 // Vitesse de l'animation (1-10)

float h = -1; // Humidité
float t = -99; // Température

unsigned long lastSensorUpdate = 0;
unsigned long lastCheckTime = 0;
unsigned long lastAutoScrollTime =
    0; // Temps de la dernière transition automatique
const unsigned long autoScrollInterval = 10000; // Intervalle de 10 secondes

// Icônes
const unsigned char epd_bitmap_humidity_mid_28dp[] PROGMEM = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00,
    0x00, 0x3f, 0xc0, 0x00, 0x00, 0x79, 0xe0, 0x00, 0x00, 0xf0, 0xf0, 0x00,
    0x01, 0xe0, 0x78, 0x00, 0x03, 0xc0, 0x3c, 0x00, 0x03, 0x80, 0x1c, 0x00,
    0x07, 0x00, 0x0e, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00,
    0x06, 0x00, 0x06, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xfe, 0x00,
    0x07, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x03, 0xff, 0xfc, 0x00,
    0x01, 0xff, 0xf8, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xe0, 0x00,
    0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00};

const unsigned char epd_bitmap_thermostat_28dp[] PROGMEM = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00,
    0x03, 0x39, 0xff, 0x00, 0x03, 0x19, 0xff, 0x00, 0x03, 0x18, 0x00, 0x00,
    0x03, 0x18, 0x00, 0x00, 0x03, 0x18, 0x00, 0x00, 0x03, 0x19, 0xf8, 0x00,
    0x03, 0x19, 0xf8, 0x00, 0x03, 0x18, 0x00, 0x00, 0x07, 0x18, 0x00, 0x00,
    0x0f, 0x1c, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00, 0x0c, 0x06, 0x00, 0x00,
    0x0c, 0x06, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00,
    0x0f, 0xfc, 0x00, 0x00, 0x07, 0xfc, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

// Fonction pour dessiner un écran avec un décalage horizontal
void drawScreenContent(int16_t xOffset, bool temp) {
  if (temp) {
    display.drawBitmap(xOffset + 0, 4, epd_bitmap_thermostat_28dp, 28, 28,
                       SSD1306_WHITE);
    display.setTextSize(3);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(xOffset + 30, 8);
    display.print(F(" "));
    if (isnan(t)) {
      display.print(F("Er"));
    } else {
      display.print(int(t));
    }
    display.write(248); // °
    display.print(F("C"));
    // display.print(F(" C"));
  } else {
    display.drawBitmap(xOffset + 0, 4, epd_bitmap_humidity_mid_28dp, 28, 28,
                       SSD1306_WHITE);
    display.setTextSize(3);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(xOffset + 30, 8);
    display.print(F(" "));
    if (isnan(h)) {
      display.print(F("Er"));
    } else {
      display.print(int(h));
    }
    display.print(F(" %"));
  }
}

float easeInOutQuad(float t) {
  return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}

float easeOutCubic(float t) { return (--t) * t * t + 1; }

// Animation de transition avec easing (défilement vers la gauche)
void animateTransition(bool fromTemp, bool toTemp) {
  bool scrollLeft = fromTemp && !toTemp;  // Temp vers humidité → gauche
  bool scrollRight = !fromTemp && toTemp; // Humidité vers temp → droite

  for (int step = 0; step <= 100; step += ANIMATION_SPEED) {
    float progress = step / 100.0;
    float easedProgress = easeOutCubic(progress);
    int16_t offset = easedProgress * SCREEN_WIDTH;

    display.clearDisplay();

    if (scrollLeft) {
      drawScreenContent(-offset, fromTemp);
      drawScreenContent(SCREEN_WIDTH - offset, toTemp);
    } else if (scrollRight) {
      drawScreenContent(offset, fromTemp);
      drawScreenContent(-SCREEN_WIDTH + offset, toTemp);
    }

    display.display();
  }
}

void refreshScreen() {
  display.clearDisplay();
  drawScreenContent(0, showTemperature);
  display.display();
  needsRefresh = false;
}

void IRAM_ATTR handleButtonPress() {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 200) {
    showTemperature = !showTemperature;
    needsRefresh = true;
    triggerAnimation = true;
  }

  lastInterruptTime = interruptTime;
}

void showStartupScreen() {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 10); // Position centrée approximativement
  display.print(F("LUCAGOC.FR"));
  display.display();
  delay(2000); // Affiche pendant 2 secondes
}

void checkLightLevel() {
  int lightLevel = analogRead(PHOTORESISTOR_PIN);
  Serial.println(lightLevel); // Affiche le niveau de lumière sur le moniteur série
  if (lightLevel < LIGHT_THRESHOLD && !isScreenOff) {
    display.ssd1306_command(SSD1306_DISPLAYOFF); // Éteint l'écran
    isScreenOff = true;
  } else if (lightLevel >= LIGHT_THRESHOLD && isScreenOff) {
    display.ssd1306_command(SSD1306_DISPLAYON); // Allume l'écran
    isScreenOff = false;
    needsRefresh = true; // Force une mise à jour de l'écran
  }
}

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

  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println(F("Échec de l'initialisation de l'écran OLED"));
    while (true)
      ;
  }

  showStartupScreen(); // Affiche l'écran de démarrage

  display.clearDisplay();
  dht.begin();
  delay(2000);

  pinMode(BUTTON_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButtonPress, RISING);

  pinMode(PHOTORESISTOR_PIN, INPUT); // Configure la photorésistance

  lastSensorUpdate = millis();
  lastCheckTime = millis();
}

void loop() {
  unsigned long currentTime = millis();

  // Mise à jour des capteurs toutes les 2s
  if (currentTime - lastSensorUpdate >= 2000) {
    h = dht.readHumidity();
    t = dht.readTemperature();
    lastSensorUpdate = currentTime;
    needsRefresh = true;
  }

  // Transition automatique toutes les 10 secondes
  if (currentTime - lastAutoScrollTime >= autoScrollInterval) {
    lastAutoScrollTime = currentTime;
    showTemperature = !showTemperature;
    triggerAnimation = true;
  }

  // Vérifie régulièrement si mise à jour de l'écran nécessaire
  if (currentTime - lastCheckTime >= 100) {
    lastCheckTime = currentTime;

    if (triggerAnimation) {
      bool previousState = !showTemperature; // On vient de l'inverse
      animateTransition(previousState, showTemperature);
      triggerAnimation = false;
    }

    if (needsRefresh) {
      refreshScreen();
    }
  }

  checkLightLevel(); // Vérifie le niveau de luminosité
}

Le code initialise l’écran OLED et le capteur DHT11, puis lit les valeurs de température et d’humidité toutes les 2 secondes. Il utilise également un bouton pour basculer entre l’affichage de la température et de l’humidité. Petit bonus, j’ai ajouté une animation de transition entre les deux affichages pour rendre le tout un peu plus joli.

Le dépôt GitHub contient le projet PlatformIO complet avec le code et les fichiers de configuration (et probablement avec des mises à jour).

Conclusion
#

Voilà, vous avez maintenant un thermomètre fonctionnel avec un ESP8266 et un capteur DHT11 ! Je n’ai pas traité d’une partie très intéressante des ESP8266: le WiFi. Vous pouvez facilement ajouter une connexion WiFi pour envoyer les données sur un serveur HomeAssistant ou un autre service, j’y travaillerais peut-être dans un futur article.

Il pourrait être sympa d’ajouter aussi un capteur de particule pour avoir une idée de la qualité de l’air (ceux qui ont une imprimante 3D savent de quoi je parle). En parlant d’imprimante 3D, je vais probablement réaliser un boîtier pour le montage même si je suis très fan des circuits à nu. Un boîtier transparent pourrait être sympa pour voir le montage et les LED.

Boitier
Cet aspect DIY fait tout le charme de l’objet, par contre je suis vraiment pas fan du rendu avec la colle chaude, je vais devoir trouver un moyen de fixer proprement les composants.