Intégrer un reTerminal E1002 dans Home Assistant avec ESPHome

5
(2)

Dans un tutoriel précédent, je vous expliquais comment créer un capteur de température Zigbee à base d’ESP32C6 et comment l’intégrer dans Home Assistant.

Dans ce nouveau tutoriel, je vous guide pour intégrer un écran à encre électronique (e-paper) Seeed Studio reTerminal E1002 dans Home Assistant, afin d’y afficher les données issues de ce capteur.

Nous utiliserons le framework ESPHome, qui facilite grandement l’intégration de l’écran reTerminal dans Home Assistant via une configuration simple en YAML. Cette configuration permet de décrire précisément les périphériques connectés au microcontrôleur ESP32-S3 (écran, boutons, buzzer, LED, etc.), ainsi que les actions à exécuter localement. ESPHome compile ensuite ce fichier YAML pour générer un firmware qui sera téléversé sur le reTerminal.

Présentation du reTerminal

Le reTerminal E1002 est un appareil prêt à l’emploi doté d’un boîtier métallique robuste abritant un écran e-paper couleur de 7.3″ d’une résolution de 800×480. Il est équipé d’un microcontrôleur ESP32-S3 et il intègre 3 boutons, un buzzer, une LED de status (en plus de la LED d’alimentation), un capteur de température et de pression, un microphone et un lecteur de carte MicroSD. Il est alimenté par une batterie interne de 2000 mAh offrant jusqu’à 3 mois d’autonomie.

Les caractéristiques détaillées du reTerminal E1002 ainsi que son guide de démarrage rapide sont disponibles sur le wiki de Seeed Studio et la version avancée du wiki.

Mise à jour du firmware du reTerminal

Il est recommandé d’installer le dernier firmware disponible dès la première mise en service de votre appareil. Après avoir créé votre compte utilisateur sur le site sensecraft.seeed.cc, connectez votre reTerminal à un port USB de votre ordinateur, puis utilisez l’outil Device Flasher pour flasher le firmware le plus récent.

Dans ce tutoriel, SenseCraft HMI ne sera pas utilisé : il n’est donc pas nécessaire de suivre la procédure affichée sur l’écran du reTerminal, ni de se connecter au point d’accès Wi-Fi qu’il diffuse. L’objectif ici est de faire fonctionner le reTerminal avec Home Assistant, en le configurant directement via le framework ESPHome.

Configuration du reTerminal avec ESPHome

Installer le module complémentaire ESPHome Device Builder sur votre serveur Home Assistant puis le démarrer.

Dans le module ESPHome Builder, ajoutez le reTerminal comme nouvel appareil de type ESP32-S3. Étant donné que le reTerminal n’est pas encore configuré pour rejoindre le réseau Wi-Fi et que Home Assistant fonctionne en HTTP (et non HTTPS), le téléversement direct du firmware n’est pas possible depuis ESPHome Builder. Utilisez alors l’option Manual Download pour récupérer le firmware compilé au format Factory format (Previously Modern).

Note: l’installation des outils et la compilation du firmware peuvent être relativement longues si Home Assistant est hébergé sur un Raspberry Pi, notamment sur les modèles moins puissants. Il n’est pas rare d’attendre plusieurs minutes pour chaque compilation.

Téléversez ensuite ce fichier .bin sur le reTerminal à l’aide de l’outil en ligne ESPHome. Après ce premier flashage, le reTerminal rejoindra votre réseau Wi-Fi. Par la suite, il sera possible d’effectuer les futures mises à jour du firmware directement depuis Home Assistant via le Wi-Fi, sans nécessiter de connexion filaire.

Hello World!

Nous allons maintenant créer un exemple pour vous permettre de comprendre le principe de ESPHome et la configuration du reTerminal. Dans cet exemple, nous allons simplement afficher le message Hello World! sur l’écran du reTerminal.

Dans le module ESPHome Builder, cliquer sur Edit afin de pouvoir modifier la configuration YAML pour que le reTerminal affiche notre message.

L’écran du reTerminal E1002 de Seeed Studio est une dalle E Ink® Spectra™ 6 couleur de 7,3 pouces avec une résolution de 800×480 pixels. Comme vous pouvez le voir sur le schéma du reTerminal, l’ESP32-S3 communique avec l’écran au travers d’un bus SPI via les broches suivantes :

  • SPI Clock (CLK) : GPIO 7
  • Master In Slave Out (MISO) : GPIO8 (non utilisé par l’écran mais uniquement par le lecteur de carte microSD qui est sur le même bus SPI)
  • Master Out Slave In (MOSI) : GPIO9
  • Chip Select (CS) : GPIO10
  • Data/Command (DC) : GPIO11
  • Reset (RST) : GPIO12

Voici la configuration qui affiche le message « Hello Worlds! » en bleu, avec la police Google Inter 700 et une taille de 36 points. Vous pouvez utiliser cet exemple en copiant le code ci-dessous et en le collant juste après la ligne captive_portal dans votre fichier YAML.

YAML
captive_portal:
    
# define font to display words
font:
  - file: "gfonts://Inter@700"
    id: myFont
    size: 36

# define SPI interface
spi:
  clk_pin: GPIO7
  mosi_pin: GPIO9

display:
  - platform: epaper_spi
    id: epaper_display
    model: 7.3in-spectra-e6
    cs_pin: GPIO10
    dc_pin: GPIO11
    reset_pin:
      number: GPIO12
      inverted: false
    busy_pin:
      number: GPIO13
      inverted: true
    update_interval: 300s
    lambda: |-
      const auto BLUE    = Color(0,   0,   255, 0);
      it.print(0, 0, id(myFont), BLUE, "Hello World!");     

Sur un écran e-paper, il est essentiel de rafraîchir régulièrement (au moins une fois par jour) l’affichage afin d’éviter les effets de rémanence caractéristiques de cette technologie. Dans la configuration ci-dessus, l’affichage est rafraîchi toutes les 300 secondes (5 minutes) via la configuration update_interval: 300s

Il suffit ensuite de cliquer sur Install et de choisir l’option Wirelessly pour téléverser le firmware correspondant à cette configuration YAML dans le reTerminal via votre réseau Wi-Fi (qui a été configuré lors du premier téléversement via ESPHome).

Après compilation et téléversement, le reTerminal affiche bien le message Hello World! en bleu sur son écran.

Première configuration du reTerminal E1002 affichant le célèbre “Hello World!”

Capteur de température et d’humidité interne au reTerminal

Le reTerminal possède un capteur interne de température et d’humidité STH40.

L’ESP32-S3 communique avec ce capteur au travers d’un bus I2C. Son adresse I2C est 0x44, et le bus I2C utilise les broches suivantes :

  • Serial Data (SDA) : GPIO19
  • Serial Clock (SCL) : GPIO20

Voici la configuration YAML qui permet de récupérer la température et l’humidité depuis ce capteur et de l’afficher sur l’écran, à copier juste après la ligne captive_portal dans votre fichier YAML :

YAML
captive_portal:
    
# define I2C interface
i2c:
  sda: GPIO19
  scl: GPIO20
  scan: false

# temperature and humidity sensor
sensor:
  - platform: sht4x
    temperature:
      name: "Temperature"
      id: sht4x_temperature
    humidity:
      name: "Humidity"
      id: sht4x_humidity
      on_value:
        then:
          - component.update: epaper_display      
    address: 0x44
    update_interval: 60s

# define font to display words
font:
  - file: "gfonts://Inter@700"
    id: myFont
    size: 36

# define SPI interface
spi:
  clk_pin: GPIO7
  mosi_pin: GPIO9

display:
  - platform: epaper_spi
    id: epaper_display
    model: 7.3in-spectra-e6
    cs_pin: GPIO10
    dc_pin: GPIO11
    reset_pin:
      number: GPIO12
      inverted: false
    busy_pin:
      number: GPIO13
      inverted: true
    update_interval: never
    lambda: |-
      const auto BLUE    = Color(0,   0,   255, 0);
      it.printf(10, 10, id(myFont), BLUE, "Temperature: %.1f°C", id(sht4x_temperature).state);  
      it.printf(10, 40, id(myFont), BLUE, "Humidity: %.1f%%", id(sht4x_humidity).state);  

Il est préférable de rafraîchir l’écran qu’une fois la lecture du capteur finalisée. Pour cela, j’utilise le paramètre on_value dans la configuration du capteur SHT4X. Ce paramètre permet de déclencher le rafraîchissement de l’écran à chaque nouvelle valeur reçue du capteur. Par conséquent, le paramètre update_interval de l’écran est configuré sur never afin d’éviter un rafraîchissement automatique indépendant des données du capteur.

Le reTerminal affichera sur son écran la température et l’humidité mesurées par son capteur interne toutes les 60 secondes.

Grâce à ESPHome, il est possible d’afficher les données issues du reTerminal dans Home Assistant.

Affichage des données d’un capteur externe sur l’écran du reTerminal

Il est parfaitement possible d’afficher n’importe quelle entité de Home Assistant sur l’écran du reTerminal. Comme mentionné au début de ce tutoriel, nous allons afficher sur l’écran du reTerminal, la température et l’humidité mesurée par le capteur Zigbee externe à base d’ESP32-C6 que j’ai conçu dans mon tutoriel précédent.

La température et l’humidité mesurées par ce capteur externe correspondent aux entité d’ID suivantes dans Home Assistant :

  • sensor.tutoduino_esp32c6tempsensor_temperature
  • sensor.tutoduino_esp32c6tempsensor_humidite
ID des entités correspondant à la température et humidité du capteur externe dans Home Assistant

Voici le code à copier juste après la ligne captive_portal dans votre fichier YAML :

YAML
captive_portal:

# define I2C interface
i2c:
  sda: GPIO19
  scl: GPIO20
  scan: false

sensor:
  # internal temperature and humidity sensor
  - platform: sht4x
    temperature:
      name: "Temperature"
      id: sht4x_temperature
    humidity:
      name: "Humidity"
      id: sht4x_humidity
      on_value:
        then:
          - component.update: epaper_display      
    address: 0x44
    update_interval: 60s

  # external EP32C6 sensor
  - platform: homeassistant
    id: tutoduino_esp32c6tempsensor_temperature
    entity_id: sensor.tutoduino_esp32c6tempsensor_temperature

  - platform: homeassistant
    id: tutoduino_esp32c6tempsensor_humidite
    entity_id: sensor.tutoduino_esp32c6tempsensor_humidite

# define font to display words
font:
  - file: "gfonts://Inter@700"
    id: myFont
    size: 36
  - file: "gfonts://Inter@700"
    id: myFontLarge
    size: 50
  - file: "gfonts://Inter@700"
    id: myFontVeryLarge
    size: 70

# define SPI interface
spi:
  clk_pin: GPIO7
  mosi_pin: GPIO9

display:
  - platform: epaper_spi
    id: epaper_display
    model: 7.3in-spectra-e6
    cs_pin: GPIO10
    dc_pin: GPIO11
    reset_pin:
      number: GPIO12
      inverted: false
    busy_pin:
      number: GPIO13
      inverted: true
    update_interval: never
    lambda: |-
      const auto BLUE = Color(0, 0, 255, 0);
      const auto GREEN = Color(0, 255, 0, 0);
      const auto BLACK = Color(0, 0, 0, 0);

      it.line(400, 0, 400, 480, BLACK);

      it.printf(130, 15, id(myFont), BLACK, "INDOOR");
      it.printf(510, 15, id(myFont), BLACK, "OUTDOOR");

      if (isnan(id(sht4x_temperature).state)) {
        it.printf(90, 120, id(myFontVeryLarge), BLUE, "--.-°C");
      } else {
        it.printf(90, 120, id(myFontVeryLarge), BLUE, "%.1f°C", id(sht4x_temperature).state);
      }

      if (isnan(id(sht4x_humidity).state)) {
        it.printf(120, 250, id(myFontLarge), BLUE, "--.-%%");
      } else {
        it.printf(120, 250, id(myFontLarge), BLUE, "%.1f%%", id(sht4x_humidity).state);
      }

      if (isnan(id(tutoduino_esp32c6tempsensor_temperature).state)) {
        it.printf(500, 120, id(myFontVeryLarge), GREEN, "--.-°C");
      } else {
        it.printf(500, 120, id(myFontVeryLarge), GREEN, "%.1f°C", id(tutoduino_esp32c6tempsensor_temperature).state);
      }

      if (isnan(id(tutoduino_esp32c6tempsensor_humidite).state)) {
        it.printf(540, 250, id(myFontLarge), GREEN, "--.-%%");
      } else {
        it.printf(540, 250, id(myFontLarge), GREEN, "%.1f%%", id(tutoduino_esp32c6tempsensor_humidite).state);
      }

Cette configuration affiche les données du capteur interne au reTerminal dans la partie INDOOR et les données issues du capteur externe dans la partie OUTDOOR.

Affichage des données issues du capteur interne et d’un capteur externe

Affichage de la charge des batteries

Mon capteur externe à base d’ESP32-C6 et le reTerminal étant tous les deux alimentés par batterie, il est utile de pouvoir afficher leur niveau sur l’écran.

Pour récupérer le niveau de batterie du reTerminal, il faut activer la mesure de la tension de la batterie en configurant le GPIO21 (VBAT ENABLE). La lecture s’effectue ensuite sur le GPIO1 (VBAT ADC), qui est relié en interne à la batterie via un circuit de mesure. En lisant la valeur analogique (tension) sur ce GPIO1, il est possible d’obtenir une estimation précise du niveau de charge de la batterie.

Concernant le niveau de la batterie du capteur externe, elle est simplement récupérée via l’entité Home Assistant sensor.tutoduino_esp32c6tempsensor_batterie.

Voici la configuration qui permet de récupérer le niveau de la batterie du reTerminal ainsi que celle du capteur de température externe.

YAML
esphome:
  name: reterminal
  friendly_name: reTerminal
  on_boot:
    priority: 600
    then:
      - output.turn_on: bsp_battery_enable
      - delay: 500ms   
      
esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "XXXX"

ota:
  - platform: esphome
    password: "XXXX"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Reterminal Fallback Hotspot"
    password: "XXXX"

captive_portal:

output:
  - platform: gpio
    pin: GPIO21
    id: bsp_battery_enable

# define I2C interface
i2c:
  sda: GPIO19
  scl: GPIO20
  scan: false

sensor:
  # internal temperature and humidity sensor
  - platform: sht4x
    temperature:
      name: "Temperature"
      id: sht4x_temperature
    humidity:
      name: "Humidity"
      id: sht4x_humidity
    address: 0x44
    update_interval: 60s

  # external EP32C6 sensor
  - platform: homeassistant
    id: tutoduino_esp32c6tempsensor_temperature
    entity_id: sensor.tutoduino_esp32c6tempsensor_temperature
  - platform: homeassistant
    id: tutoduino_esp32c6tempsensor_humidite
    entity_id: sensor.tutoduino_esp32c6tempsensor_humidite
  - platform: homeassistant
    id: tutoduino_esp32c6tempsensor_batterie
    entity_id: sensor.tutoduino_esp32c6tempsensor_batterie

  # ADC for battery voltage measurement
  - platform: adc
    pin: GPIO1
    name: "Battery Voltage"
    id: battery_voltage
    on_value:
      then:    
        - component.update: battery_level  
    update_interval: 60s
    attenuation: 12db
    filters:
      - multiply: 2.0  # Voltage divider compensation

# Template to display battery level in %
  - platform: template
    name: "Battery Level"
    id: battery_level      
    unit_of_measurement: "%"
    icon: "mdi:battery"
    device_class: battery
    state_class: measurement
    lambda: 'return id(battery_voltage).state;'
    update_interval: never
    filters:
      - calibrate_linear:
          - 4.15 -> 100.0
          - 3.96 -> 90.0
          - 3.91 -> 80.0
          - 3.85 -> 70.0
          - 3.80 -> 60.0
          - 3.75 -> 50.0
          - 3.68 -> 40.0
          - 3.58 -> 30.0
          - 3.49 -> 20.0
          - 3.41 -> 10.0
          - 3.30 -> 5.0
          - 3.27 -> 0.0
      - clamp:
          min_value: 0
          max_value: 100

# define font to display words
font:
  - file: "gfonts://Inter@700"
    id: mySmallFont
    size: 15
  - file: "gfonts://Inter@700"
    id: myFont
    size: 36
  - file: "gfonts://Inter@700"
    id: myFontLarge
    size: 50
  - file: "gfonts://Inter@700"
    id: myFontVeryLarge
    size: 70
  - file: 'fonts/MaterialDesignIconsDesktop.ttf'
    id: font_mdi_large
    size: 50
    glyphs:
      - "\U000F050F"  # thermometer
      - "\U000F058E"  # humidity
  - file: 'fonts/MaterialDesignIconsDesktop.ttf'
    id: font_bat_icon
    size: 50
    glyphs:
      - "\U000F0079"  # mdi-battery

# define SPI interface
spi:
  clk_pin: GPIO7
  mosi_pin: GPIO9
display:
  - platform: epaper_spi
    id: epaper_display
    model: 7.3in-spectra-e6
    cs_pin: GPIO10
    dc_pin: GPIO11
    reset_pin:
      number: GPIO12
      inverted: false
    busy_pin:
      number: GPIO13
      inverted: true
    update_interval: 60s
    lambda: |-
      const auto BLUE = Color(0, 0, 255, 0);
      const auto GREEN = Color(0, 255, 0, 0);
      const auto BLACK = Color(0, 0, 0, 0);
      const auto RED   = Color(255, 0,   0,   0);

      it.line(400, 0, 400, 480, BLACK);

      it.printf(130, 15, id(myFont), BLACK, "INDOOR");
      it.printf(510, 15, id(myFont), BLACK, "OUTDOOR");

      it.printf(60, 165, id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F050F");

      if (isnan(id(sht4x_temperature).state)) {
        it.printf(90, 120, id(myFontVeryLarge), BLUE, "--.-°C");
      } else {
        it.printf(90, 120, id(myFontVeryLarge), BLUE, "%.1f°C", id(sht4x_temperature).state);
      }

      it.printf(60, 283, id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F058E");

      if (isnan(id(sht4x_humidity).state)) {
        it.printf(90, 250, id(myFontLarge), BLUE, "--.-%%");
      } else {
        it.printf(90, 250, id(myFontLarge), BLUE, "%.1f%%", id(sht4x_humidity).state);
      }

      it.printf(60, 390, id(font_bat_icon), BLACK, TextAlign::CENTER, "\U000F0079");

      if (isnan(id(battery_level).state)) {
        it.printf(90, 360, id(myFontLarge), BLUE, "--%%");
      } else {
        it.printf(90, 360, id(myFontLarge), BLUE, "%2.0f%%", id(battery_level).state);
      }

      it.printf(470, 165 , id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F050F");

      if (isnan(id(tutoduino_esp32c6tempsensor_temperature).state)) {
        it.printf(500, 120, id(myFontVeryLarge), BLUE, "--.-°C");
      } else {
        it.printf(500, 120, id(myFontVeryLarge), BLUE, "%.1f°C", id(tutoduino_esp32c6tempsensor_temperature).state);
      }

      it.printf(470, 283, id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F058E");

      if (isnan(id(tutoduino_esp32c6tempsensor_humidite).state)) {
        it.printf(500, 250, id(myFontLarge), BLUE, "--.-%%");
      } else {
        it.printf(500, 250, id(myFontLarge), BLUE, "%.1f%%", id(tutoduino_esp32c6tempsensor_humidite).state);
      }

      it.printf(470, 390, id(font_bat_icon), BLACK, TextAlign::CENTER, "\U000F0079");

      if (isnan(id(tutoduino_esp32c6tempsensor_batterie).state)) {
        it.printf(500, 360, id(myFontLarge), BLUE, "--%%");
      } else {
        it.printf(500, 360, id(myFontLarge), BLUE, "%2.0f%%", id(tutoduino_esp32c6tempsensor_batterie).state);
      }

Ajout des icônes

Pour ajouter les icônes de thermomètre, d’hygromètre et de batterie, il faut créer le répertoire fonts dans le répertoire esphome (avec le module complémentaire File editor) et y ajouter le fichier de font suivant : MaterialDesignIconsDesktop.ttf

Voici le rendu des icônes sur l’écran du reTerminal E1002, mais n’hésitez pas à les adapter à vos besoins.

Affichage de la température, de l’humidité et du niveau de la batterie d’un capteur externe et du capteur interne du reTerminal E1002

Mode veille

Afin d’économiser la batterie du reTerminal entre 2 rafraîchissements de son écran, il est possible de mettre son microcontrôleur ESP32-S3 en veille profonde. C’est très simple avec ESPHome, il suffit de configurer la durée de la veille profonde via le paramètre sleep_duration ainsi que la durée d’activité en sortie de veille via le paramètre run_duration dans la configuration deep_sleep.

Afin de pouvoir réveiller le microcontrôleur lorsqu’il est en veille profonde, il est possible de configurer un événement (temporisation, changement d’état d’une broche…). Ici je configure l’appui sur le bouton vert du reTerminal (GPIO3) comme événement qui le fera sortir du mode veille.

YAML

# Deep sleep configuration to save power
deep_sleep:
  id: deep_sleep_1
  run_duration: 30s     # Active time after wake-up
  sleep_duration: 10min # Sleep duration between wake-ups
  wakeup_pin: GPIO3     # External pin to wake up device
  wakeup_pin_mode: INVERT_WAKEUP  # Inverted wake-up logic

En sortie du mode veille profonde, le microcontrôleur redémarre comme si il venait d’être allumé. C’est un point important car cela impacte fortement la façon dont il faut lire les capteurs et rafraîchir l’écran avant de retourner en mode veille. Il faut être attentif car plusieurs problèmes potentiels se présentent :

  • Le capteur interne SHT4X n’est pas encore prêt au démarrage et ne retourne pas de donnée et ainsi retourne nan (not a number).
  • Le Wi-Fi n’est pas encore initialisé au démarrage et les données du serveur Home Assistant ne sont pas disponibles au démarrage et ainsi retournent nan.

Lorsque la lecture des capteurs est cyclique (update_interval: 60s) cela ne pose pas de problème, car ces données sont bien disponibles lors du second cycle (après 60 secondes). Mais il va falloir gérer le cas d’une lecture unique au démarrage du reTerminal et que l’affichage soit correct dès le démarrage.

Pour cela, nous allons effectuer les actions suivantes :

1/ Lecture du capteur interne STH4X et de la tension de la batterie lors du démarrage (on_boot) :

YAML
  on_boot:
    priority: 600
    then:
      # Turn on the GPIO output that powers the battery measurement circuit
      - output.turn_on: bsp_battery_enable
      # Wait a short delay to allow power stabilization
      - delay: 500ms
      # Manually update battery sensors (voltage and percentage)
      - component.update: battery_voltage
      - component.update: battery_level
      # Manually read the internal temperature/humidity sensor
      - component.update: sht4x_component

2/ Pas de mise à jour automatique de la lecture du capteur interne et de la tension de la batterie, puisque ils sont lus au démarrage, ni de rafraississement automatique de l’écran.

YAML
    update_interval: never            # Do not update automatically (manual read only)

3/ Rafraississement de l’écran uniquement lorque la valeur des capteurs Home Assistant est disponible (et donc le Wi-Fi opérationnel) :

YAML
  # Home Assistant sensors (external data)
  - platform: homeassistant
    id: tutoduino_esp32c6tempsensor_temperature
    entity_id: sensor.tutoduino_esp32c6tempsensor_temperature
    on_value:
      then:
        # Refresh the e-paper display whenever this sensor updates
        - component.update: epaper_display  

Voici le code complet avec une mise en veille profonde pendant 10 minutes du reTerminal entre chaque mise à jour des capteur et du rafraississement de son écran.

YAML
# ESPHome configuration for the "reterminal2" device
esphome:
  name: "reterminal2"          # Device name for ESPHome
  friendly_name: "reterminal2" # User-friendly name for the device
  on_boot:
    priority: 600
    then:
      # Turn on the GPIO output that powers the battery measurement circuit
      - output.turn_on: bsp_battery_enable
      # Wait a short delay to allow power stabilization
      - delay: 500ms
      # Manually update battery sensors (voltage and percentage)
      - component.update: battery_voltage
      - component.update: battery_level
      # Manually read the internal temperature/humidity sensor
      - component.update: sht4x_component
      
# ESP32 board configuration
esp32:
  board: esp32-s3-devkitc-1    # Board model
  framework:                   # Framework settings
    type: esp-idf              # Use ESP-IDF framework

# Enable logging for debugging
logger:

# Home Assistant API configuration
api:
  encryption:                  # Encryption settings for secure communication
    key: "xxx="  # Encryption key

# Over-The-Air (OTA) update configuration
ota:
  - platform: esphome          # Use ESPHome OTA platform
    password: "xxx"  # OTA update password

# Wi-Fi configuration
wifi:
  ssid: !secret wifi_ssid      # Wi-Fi SSID (from secrets)
  password: !secret wifi_password  # Wi-Fi password (from secrets)
  ap:                          # Fallback Access Point (hotspot) if Wi-Fi fails
    ssid: "Re Fallback Hotspot" # Hotspot SSID
    password: "xxx"    # Hotspot password
    
captive_portal:                # Enable captive portal for AP mode

# GPIO output configuration
output:
  - platform: gpio             # GPIO platform
    pin: GPIO21                # GPIO pin number
    id: bsp_battery_enable     # ID for this output


# I2C bus configuration (for SHT4x sensor)
i2c:
  sda: GPIO19                  # I2C SDA pin
  scl: GPIO20                  # I2C SCL pin
  scan: false                  # Disable automatic I2C scanning for faster boot

# Sensor configurations
sensor:
  # Internal SHT4x temperature/humidity sensor
  - platform: sht4x            # Sensor platform
    id: sht4x_component         # ID for the component
    temperature:                # Temperature sensor
      name: "Temperature"       # Sensor name
      id: sht4x_temperature     # ID for temperature sensor
    humidity:                   # Humidity sensor
      name: "Humidity"          # Sensor name
      id: sht4x_humidity        # ID for humidity sensor
    address: 0x44               # I2C address
    update_interval: never      # Disable automatic updates (manual read only)

  # Home Assistant sensors (external data)
  - platform: homeassistant    # Home Assistant sensor platform
    id: tutoduino_esp32c6tempsensor_temperature  # ID for this sensor
    entity_id: sensor.tutoduino_esp32c6tempsensor_temperature  # Home Assistant entity ID
    on_value:                   # Action to perform when value updates
      then:                     # List of actions
        - component.update: epaper_display  # Update e-paper display on value change

  - platform: homeassistant    # Home Assistant sensor platform
    id: tutoduino_esp32c6tempsensor_humidite  # ID for this sensor
    entity_id: sensor.tutoduino_esp32c6tempsensor_humidite  # Home Assistant entity ID

  - platform: homeassistant    # Home Assistant sensor platform
    id: tutoduino_esp32c6tempsensor_batterie  # ID for this sensor
    entity_id: sensor.tutoduino_esp32c6tempsensor_batterie  # Home Assistant entity ID

  # ADC (analog) battery voltage measurement
  - platform: adc              # ADC platform
    pin: GPIO1                  # GPIO pin for ADC
    name: "Battery Voltage"     # Sensor name
    id: battery_voltage         # ID for this sensor
    attenuation: 12db           # Attenuation level
    update_interval: never      # Disable automatic updates (manual read only)
    filters:                    # Data processing filters
      - multiply: 2.0           # Compensate for voltage divider ratio

  # Template battery level (%) derived from measured voltage
  - platform: template         # Template sensor platform
    name: "Battery Level"       # Sensor name
    id: battery_level           # ID for this sensor
    unit_of_measurement: "%"    # Unit of measurement
    icon: "mdi:battery"         # Icon for display
    device_class: battery      # Device class
    state_class: measurement    # State class
    update_interval: never      # Disable automatic updates (manual read only)
    lambda: 'return id(battery_voltage).state;'  # Lambda function to get voltage
    filters:                    # Data processing filters
      # Voltage-to-percentage calibration curve
      - calibrate_linear:       # Linear calibration
          - 4.15 -> 100.0       # Voltage to percentage mapping
          - 3.96 -> 90.0
          - 3.91 -> 80.0
          - 3.85 -> 70.0
          - 3.80 -> 60.0
          - 3.75 -> 50.0
          - 3.68 -> 40.0
          - 3.58 -> 30.0
          - 3.49 -> 20.0
          - 3.41 -> 10.0
          - 3.30 -> 5.0
          - 3.27 -> 0.0
      - clamp:                  # Clamp values to valid range
          min_value: 0          # Minimum value
          max_value: 100         # Maximum value

# Deep sleep configuration to save power
deep_sleep:
  id: deep_sleep_1              # ID for deep sleep component
  run_duration: 30s             # Time to stay awake after wake-up
  sleep_duration: 10min         # Time to sleep between wake-ups
  wakeup_pin: GPIO3             # GPIO pin to wake up device
  wakeup_pin_mode: INVERT_WAKEUP # Inverted wake-up logic

# SPI bus configuration (for the e-paper display)
spi:
  clk_pin: GPIO7                # SPI CLK pin
  mosi_pin: GPIO9                # SPI MOSI pin

# Font definitions for the display
font:
  - file: "gfonts://Inter@700"   # Google Fonts URL
    id: mySmallFont             # ID for this font
    size: 15                    # Font size
  - file: "gfonts://Inter@700"   # Google Fonts URL
    id: myFont                  # ID for this font
    size: 36                    # Font size
  - file: "gfonts://Inter@700"   # Google Fonts URL
    id: myFontLarge             # ID for this font
    size: 50                    # Font size
  - file: "gfonts://Inter@700"   # Google Fonts URL
    id: myFontVeryLarge         # ID for this font
    size: 70                    # Font size
  - file: 'fonts/MaterialDesignIconsDesktop.ttf'  # Icon font file
    id: font_mdi_large          # ID for this font
    size: 50                    # Font size
    glyphs:                     # Specific glyphs to include
      - "\U000F050F"            # Thermometer icon
      - "\U000F058E"            # Humidity icon
  - file: 'fonts/MaterialDesignIconsDesktop.ttf'  # Icon font file
    id: font_bat_icon           # ID for this font
    size: 50                    # Font size
    glyphs:                     # Specific glyphs to include
      - "\U000F0079"            # Battery icon

# E-paper display configuration
display:
  - platform: epaper_spi
    id: epaper_display
    model: 7.3in-spectra-e6
    cs_pin: GPIO10              # SPI CS pin
    dc_pin: GPIO11              # DC pin
    reset_pin:                  # Reset pin configuration
      number: GPIO12            # GPIO pin number
      inverted: false           # Inversion setting
    busy_pin:                   # Busy pin configuration
      number: GPIO13            # GPIO pin number
      inverted: true            # Inversion setting
    update_interval: never      # Disable automatic updates
    # Lambda function for drawing on the display
    lambda: |-
      const auto BLUE = Color(0, 0, 255, 0);  # Define color constants
      const auto GREEN = Color(0, 255, 0, 0);
      const auto BLACK = Color(0, 0, 0, 0);
      const auto RED   = Color(255, 0,   0,   0);
      it.line(400, 0, 400, 480, BLACK);  # Draw a vertical line to separate sections
      it.printf(130, 15, id(myFont), BLACK, "INDOOR");  # Print "INDOOR" label
      it.printf(510, 15, id(myFont), BLACK, "OUTDOOR"); # Print "OUTDOOR" label

      # Draw indoor temperature icon and value
      it.printf(60, 165, id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F050F");
      if (isnan(id(sht4x_temperature).state)) {
        it.printf(90, 120, id(myFontVeryLarge), BLUE, "--.-°C");  # Placeholder if no data
      } else {
        it.printf(90, 120, id(myFontVeryLarge), BLUE, "%.1f°C", id(sht4x_temperature).state);  # Display temperature
      }

      # Draw indoor humidity icon and value
      it.printf(60, 283, id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F058E");
      if (isnan(id(sht4x_humidity).state)) {
        it.printf(90, 250, id(myFontLarge), BLUE, "--.-%%");  # Placeholder if no data
      } else {
        it.printf(90, 250, id(myFontLarge), BLUE, "%.1f%%", id(sht4x_humidity).state);  # Display humidity
      }

      # Draw battery level icon and value
      it.printf(60, 390, id(font_bat_icon), BLACK, TextAlign::CENTER, "\U000F0079");
      if (isnan(id(battery_level).state)) {
        it.printf(90, 360, id(myFontLarge), BLUE, "--%%");  # Placeholder if no data
      } else {
        it.printf(90, 360, id(myFontLarge), BLUE, "%2.0f%%", id(battery_level).state);  # Display battery level
      }

      # Draw outdoor temperature icon and value
      it.printf(470, 165 , id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F050F");
      if (isnan(id(tutoduino_esp32c6tempsensor_temperature).state)) {
        it.printf(500, 120, id(myFontVeryLarge), BLUE, "--.-°C");  # Placeholder if no data
      } else {
        it.printf(500, 120, id(myFontVeryLarge), BLUE, "%.1f°C", id(tutoduino_esp32c6tempsensor_temperature).state);  # Display temperature
      }

      # Draw outdoor humidity icon and value
      it.printf(470, 283, id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F058E");
      if (isnan(id(tutoduino_esp32c6tempsensor_humidite).state)) {
        it.printf(500, 250, id(myFontLarge), BLUE, "--.-%%");  # Placeholder if no data
      } else {
        it.printf(500, 250, id(myFontLarge), BLUE, "%.1f%%", id(tutoduino_esp32c6tempsensor_humidite).state);  # Display humidity
      }

      # Draw outdoor battery level icon and value
      it.printf(470, 390, id(font_bat_icon), BLACK, TextAlign::CENTER, "\U000F0079");
      if (isnan(id(tutoduino_esp32c6tempsensor_batterie).state)) {
        it.printf(500, 360, id(myFontLarge), BLUE, "--%%");  # Placeholder if no data
      } else {
        it.printf(500, 360, id(myFontLarge), BLUE, "%2.0f%%", id(tutoduino_esp32c6tempsensor_batterie).state);  # Display battery level
      }

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 2

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?