Sommaire
Introduction
Seeed Studio a dévoilé de nouveaux écrans reTerminal E composés du E1001 (monochrome) et du E1002 (couleur). Des terminaux HMI (Human Machine Interface) utilisant des écrans e-paper (ou "paper ink") basse consommation, conçus pour des applications comme les dashboards, la signalétique numérique ou les installations toujours sous tension. Ils sont équipés d'un ESP32-S3 avec écran E-paper.
L’ESP32-S3 est un microcontrôleur puissant de la famille ESP32 d’Espressif, conçu pour des applications embarquées nécessitant à la fois connectivité et performances. Il se distingue par son double cœur Xtensa LX7 cadencé jusqu’à 240 MHz, son support natif de l’IA et du traitement vectoriel, ainsi que ses capacités avancées de gestion de la mémoire et du multimédia. Grâce à sa connectivité Wi-Fi et Bluetooth Low Energy (BLE 5.0), il est particulièrement adapté pour des projets connectés et interactifs.
Couplé à un écran e-paper (ou paper ink), l’ESP32-S3 ouvre la voie à des solutions d’affichage basse consommation, idéales pour des appareils nécessitant une autonomie prolongée. L’e-paper présente en effet deux atouts majeurs :
- une excellente lisibilité même en plein soleil,
- une consommation énergétique quasi nulle lorsqu’il n’y a pas de rafraîchissement de l’écran.
Cette combinaison permet de concevoir des projets tels que des étiquettes électroniques intelligentes, des dashboards IoT basse consommation, des lecteurs de données environnementales ou encore des interfaces utilisateur minimalistes et élégantes.
Unboxing
reTerminal E1001 (Monochrome)







Inclus le manuel, un tournevis et un support avec ça vis de serrage.
reTerminal E1002 (Couleur)







Inclus le manuel, un tournevis et un support avec ça vis de serrage.
Spécification
reTerminal E1001 (Monochrome)
- Processeur : ESP32-S3R8
- Mémoire : 8MB PSRAM
- Écran : 7.5 pouces Monochrome (noir et blanc)
- Résolution : 800 x 480 Pixels
- Stockage : 32MB Flash, Carte Micro SD/TF (Jusqu'à 32GB, optionnelle)
- Connexion Sans fil : 2.4GHz 802.11 b/g/n Wi-Fi, Bluetooth 5.0
- Capteur de température et d'humidité : Capteur SHT40 intégré situé dans le coin inférieur gauche à l'arrière de l'appareil pour la surveillance de l'environnement.
- Microphone
- Buzzer
- Batterie : 2000 mHA
- Tension d'entrée : DC 5V 1A
- Dimension : 175mm x 120mm x 16.5mm
reTerminal E1002 (Couleur)
- Processeur : ESP32-S3R8
- Mémoire : 8MB PSRAM
- Écran : 7.3 pouces Couleur (6 couleurs , inclus noir et blanc) avec technologie ACeP (Advanced Color ePaper)
- Résolution : 800 x 480 Pixels
- Stockage : 32MB Flash, Carte Micro SD/TF (Jusqu'à 32GB, optionnelle)
- Connexion Sans fil : 2.4GHz 802.11 b/g/n Wi-Fi, Bluetooth 5.0
- Capteur de température et d'humidité : Capteur SHT40 intégré situé dans le coin inférieur gauche à l'arrière de l'appareil pour la surveillance de l'environnement.
- Microphone
- Buzzer
- Batterie : 2000 mHA
- Tension d'entrée : DC 5V 1A
- Dimension : 175mm x 120mm x 16.5mm
Description du produit

1 - Écran 7.5" Monochrome / 7.3 Couleur"
2 - Bouton
3 - Microphone
4 - Port carte MicroSD
5 - Interrupteur d'alimentation
6 - LED d'état (verte)
7 - LED d'alimentation (rouge)
8 - Port USB-C
9 - Port d'extension
Pin du port d'extension
Pin (from top to bottom) | Label | ESP32-S3 Pin | Function | Description |
---|---|---|---|---|
1 | HEADER_3V3 | - | Power | 3.3V power supply for external devices |
2 | GND | - | Ground | Common ground reference |
3 | ESP_IO46 | GPIO46 | GPIO/ADC | General purpose I/O with analog input capability |
4 | ESP_IO2/ADC1_CH4 | GPIO2 | GPIO/ADC | General purpose I/O with analog input capability (ADC1 channel 4) |
5 | ESP_IO17/TX1 | GPIO17 | GPIO/UART TX | GPIO or UART transmit (TX) signal |
6 | ESP_IO18/RX1 | GPIO18 | GPIO/UART RX | GPIO or UART receive (RX) signal |
7 | ESP_IO20/I2C0_SCL | GPIO20 | GPIO/I2C SCL | GPIO or I2C clock signal |
8 | ESP_IO19/I2C0_SDA | GPIO19 | GPIO/I2C SDA | GPIO or I2C data signal |
Utilisation
Préparation de l'appareil
Maintenant, nous allons préparer le firmware de l'appareil sous ESPHome.
Vous pouvez consulter ici le tutoriel pour installer et configurer ESPHome.
Allez dans le menu de ESPHome et créer un nouvel appareil en cliquant sur New device.

Vous indiquez un nom à l'appareil.

Sélectionner en type d'appareil ESP32-S3.

À la fenêtre suivante, faite skip.

Votre appareil est prêt à être flashé.

Démo


Page 1 et 2
Je vous propose une démo des deux écrans e-paper, avec quasiment toutes les fonctionnalités de l'appareil (Sauf le Microphone).
Dans un premier temps, on va créer des modèles de capteurs, pour pouvoir récupérer la météo.
Préparation des modèles de capteurs
Installer l'intégration Météo-France, pour avoir les donnes météo.
Vous allez ensuite créer deux triggers template pour récupérer les prévisions météo par jour et heure, puis deux sensors template pour mettre ces informations dans des attributs. Puis une sensor template pour la direction du vent.
Pour cela, ajouter ce code dans votre fichier configuration.yaml ou votre template.yaml suivant comment vous avez configuré vos template.
#########################
# TEMPLATE #
#########################
template:
- sensor:
- name: Direction Vent Ma Ville
unique_id: direction_vent_ma_ville
icon: mdi:weather-windy
state: >-
{% set direction = ['N','NNE','NE','ENE','E','ESE','SE','SSE','S','SSO','SO','OSO','O','ONO','NO','NNO','N'] %}
{% set degree = state_attr('weather.ma_ville', 'wind_bearing')|int(0) %}
{% if degree > 1 %}
{{ direction[((degree+11.25)/22.5)|int(0)] }}
{% else %}
{{ 'X' }}
{% endif %}
- name: Météo Ma Ville Jour epaper
unique_id: meteo_ma_ville_jour_epaper
state: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[0].condition | default(0) }}"
attributes:
conditiona: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[0].condition | default(0) }}"
temperaturea: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[0].temperature | float(0) }}"
templowa: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[0].templow | float(0) }}"
humiditea: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[0].humidity | int(0) }}"
precipitationa: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[0].precipitation | float(0) }}"
conditiond: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[1].condition | default(0) }}"
temperatured: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[1].temperature | float(0) }}"
templowd: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[1].templow | float(0) }}"
humidited: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[1].humidity | int(0) }}"
precipitationd: "{{ state_attr('sensor.weather_forecast_jour_ma_ville','forecast')[1].precipitation | float(0) }}"
availability: "{{ states('sensor.weather_forecast_jour_ma_ville') not in ['unknown', 'unavailable', 'none'] }}"
- name: Météo Ma Ville Heure Epaper
unique_id: meteo_ma_ville_heure_epaper
state: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[0].condition | default(0) }}"
attributes:
condition0: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[0].condition | default(0) }}"
heure0: "{{ as_timestamp(state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[0].datetime) | int(0) | timestamp_custom('%H') }}"
temperature0: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[0].temperature | float(0) }}"
humidite0: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[0].humidity | int(0) }}"
wind0: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[0].wind_speed | int(0) }}"
precipitation0: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[0].precipitation | float(0) }}"
condition1: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[1].condition | default(0) }}"
heure1: "{{ as_timestamp(state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[1].datetime) | int(0) | timestamp_custom('%H') }}"
temperature1: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[1].temperature | float(0) }}"
humidite1: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[1].humidity | int(0) }}"
wind1: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[1].wind_speed | int(0) }}"
precipitation1: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[1].precipitation | float(0) }}"
condition2: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[2].condition | default(0) }}"
heure2: "{{ as_timestamp(state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[2].datetime) | int(0) | timestamp_custom('%H') }}"
temperature2: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[2].temperature | float(0) }}"
humidite2: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[2].humidity | int(0) }}"
wind2: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[2].wind_speed | int(0) }}"
precipitation2: "{{ state_attr('sensor.weather_forecast_heure_ma_ville','forecast')[2].precipitation | float(0) }}"
availability: "{{ states('sensor.weather_forecast_heure_ma_ville') not in ['unknown', 'unavailable', 'none'] }}"
- trigger:
- platform: time_pattern
hours: /1
- platform: homeassistant
event: start
action:
- action: weather.get_forecasts
data:
type: daily
target:
entity_id: weather.ma_ville
response_variable: daily
sensor:
- name: Weather Forecast Jour Ma Ville
unique_id: weather_forecast_jour_ma_ville
state: "{{ daily['weather.ma_ville'].forecast[0].condition }}"
attributes:
forecast: "{{ daily['weather.ma_ville'].forecast }}"
availability: "{{ states('weather.ma_ville') not in ['unknown', 'unavailable', 'none'] }}"
- trigger:
- platform: time_pattern
hours: /1
- platform: homeassistant
event: start
action:
- action: weather.get_forecasts
data:
type: hourly
target:
entity_id: weather.ma_ville
response_variable: hourly
sensor:
- name: Weather Forecast Heure Ma Ville
unique_id: weather_forecast_heure_ma_ville
state: "{{ hourly['weather.ma_ville'].forecast[0].condition }}"
attributes:
forecast: "{{ hourly['weather.ma_ville'].forecast }}"
availability: "{{ states('weather.ma_ville') not in ['unknown', 'unavailable', 'none'] }}"
Puis enregistrer et recharger la configuration des entités basées sur modèle. Aller dans outils de développement, onglet YAML et cliquer sur Entités basées sur modèle.

Vous obtiendrez deux entités météo par jour et heure, ainsi qu'une entité pour la direction du vent, qui serviront pour les données météo a utilisé dans le code ESPHome.


Installation du fichier icône
Maintenant, vous devrez copier le fichier materialdesignicons-webfont.ttf pour utiliser les icônes du site Material Design Icons dans le code ESPHome.
Il faudra le placer dans votre dossier esphome, puis créer à l'intérieur un dossier fonts et y coller le fichier.

Configuration du fichier secrets.yaml
Ensuite, vous devrez ajouter dans votre fichier secrets.yaml qui se trouve dans le dossier esphome ce code. Modifier par votre SSID de la box internet et sa clé Wifi, l'ip de votre box internet dans wifi_gtw et wifi_dns1. Puis les coordonnées GPS de votre maison dans latitude et longitude (pour les données du soleil).
wifi_ssid: "xxxxxxx"
wifi_password: "xxxxxxxxxxxxxxxxxxxxxxx"
wifi_gtw: "192.168.1.x"
wifi_sub: "255.255.255.0"
wifi_dns1: "192.168.1.x"
latitude: xx.xxxx°
longitude: x.xxxx°
À présent, votre ESPHome est prêt pour flasher le firmware.
Code ESPHome reTerminal E1001 (Monochrome)
substitutions:
name: reterminal-e1001
friendly_name: reTerminal E1001
esphome:
name: ${name}
name_add_mac_suffix: false
friendly_name: ${friendly_name}
on_boot:
- priority: 600
then:
- output.turn_on: bsp_sd_enable
- output.turn_on: bsp_battery_enable
- delay: 200ms
- component.update: battery_voltage
- component.update: battery_level
- delay: 15s
- component.update: sun_sunrise
- component.update: sun_sunset
- delay: 45s
- component.update: epaper_display
- delay: 30s
- lambda: |-
auto time = id(ha_time).now();
if (!time.is_valid()) {
ESP_LOGI("custom", "Heure invalide (pas de synchro NTP). Deep sleep 60 min par défaut.");
id(deep_sleep_1).set_sleep_duration(60 * 60 * 1000ULL);
id(deep_sleep_1).begin_sleep(true);
return;
}
int now_seconds = time.hour * 3600 + time.minute * 60 + time.second;
int h = time.hour;
int m = time.minute;
int interval = id(sleep_interval_hours);
int target_min = id(target_minute);
//Calcul de la prochaine heure cible
int next_target_hour = (h / interval) * interval;
if (m >= target_min) {
next_target_hour += interval;
}
if (next_target_hour >= 24) next_target_hour -= 24;
int target_seconds = next_target_hour * 3600 + target_min * 60;
if (target_seconds <= now_seconds) {
target_seconds += 24 * 3600; // bascule au jour suivant
}
int sleep_seconds = target_seconds - now_seconds;
ESP_LOGI("custom", "Il est %02d:%02d:%02d", h, m, time.second);
ESP_LOGI("custom", "Prochain réveil prévu à %02d:%02d (dans %d sec)",
next_target_hour, target_min, sleep_seconds);
id(deep_sleep_1).set_sleep_duration((uint64_t)sleep_seconds * 1000ULL);
id(deep_sleep_1).begin_sleep(true);
esp32:
#board: esp32-s3-devkitc-1
variant: esp32s3
framework:
type: esp-idf
flash_size: 32MB
psram:
mode: octal
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "LYgjtfzoelYYwciqzqr1Z6dsZxxxxxxxxxxxxxxxxxxx"
ota:
- platform: esphome
password: "ce1cbb03ba47ca50xxxxxxxxxxxxxxxxxx"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: true
manual_ip:
static_ip: 192.168.1.111
gateway: !secret wifi_gtw
subnet: !secret wifi_sub
dns1: !secret wifi_dns1
deep_sleep:
id: deep_sleep_1
run_duration: 2min
#sleep_duration: 58min
wakeup_pin: GPIO3 # Bouton vert
wakeup_pin_mode: INVERT_WAKEUP
# SPI / I²C
spi:
clk_pin: GPIO7
mosi_pin: GPIO9
i2c:
scl: GPIO20
sda: GPIO19
globals:
- id: page_index
type: int
restore_value: true
initial_value: '0'
- id: battery_glyph
type: std::string
restore_value: no
initial_value: "\"\\U000F0079\"" # default full battery
- id: sleep_interval_hours
type: int
restore_value: no
initial_value: '1' # Toutes les 1 heure
- id: target_minute
type: int
restore_value: no
initial_value: '2' # À la minute 02
script:
- id: buzzer_cycle
mode: single
then:
- light.turn_on:
id: buzzer
brightness: "50%"
- delay: 400ms
- light.turn_off: buzzer
- delay: 400ms
- light.turn_on:
id: buzzer
brightness: "60%"
- delay: 400ms
- light.turn_off: buzzer
- delay: 400ms
- light.turn_on:
id: buzzer
brightness: "70%"
- delay: 400ms
- light.turn_off: buzzer
- delay: 400ms
- light.turn_on:
id: buzzer
brightness: "80%"
- delay: 400ms
- light.turn_off: buzzer
- delay: 400ms
- light.turn_on:
id: buzzer
brightness: "80%"
- delay: 400ms
- light.turn_off: buzzer
sensor:
- platform: sht4x
temperature:
name: Température
id: temp_sensor
humidity:
name: Humidité
id: hum_sensor
- platform: adc
pin: GPIO1
name: Tension Batterie
id: battery_voltage
update_interval: 60s
attenuation: 12db
filters:
- multiply: 2.0
on_value_range:
- below: 3.41
then:
- script.execute: buzzer_cycle
- platform: template
name: Niveau Batterie
id: battery_level
unit_of_measurement: "%"
icon: "mdi:battery"
device_class: battery
state_class: measurement
lambda: 'return id(battery_voltage).state;'
update_interval: 60s
on_value:
then:
- lambda: |-
int pct = int(x);
if (pct <= 10) id(battery_glyph) = "\U000F007A";
else if (pct <= 20) id(battery_glyph) = "\U000F007B";
else if (pct <= 30) id(battery_glyph) = "\U000F007C";
else if (pct <= 40) id(battery_glyph) = "\U000F007D";
else if (pct <= 50) id(battery_glyph) = "\U000F007E";
else if (pct <= 60) id(battery_glyph) = "\U000F007F";
else if (pct <= 70) id(battery_glyph) = "\U000F0080";
else if (pct <= 80) id(battery_glyph) = "\U000F0081";
else if (pct <= 90) id(battery_glyph) = "\U000F0082";
else id(battery_glyph) = "\U000F0079";
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
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "temperaturea"
id: today_temperaturea
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "templowa"
id: today_templowa
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "humiditea"
id: today_humiditea
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "precipitationa"
id: today_precipitationa
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "temperatured"
id: tomorrow_temperatured
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "templowd"
id: tomorrow_templowd
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "humidited"
id: tomorrow_humidited
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "precipitationd"
id: tomorrow_precipitationd
- platform: homeassistant
entity_id: weather.ma_ville
attribute: "temperature"
id: now_weather_temperature
- platform: homeassistant
entity_id: weather.ma_ville
attribute: "humidity"
id: now_weather_humidity
- platform: homeassistant
entity_id: weather.ma_ville
attribute: "wind_speed"
id: now_weather_wind_speed
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "heure0"
id: h1_weather_heure
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "temperature0"
id: h1_weather_temperature
- platform: homeassistant
entity_id: sensor.meteo_ma_villex_heure_epaper
attribute: "humidite0"
id: h1_weather_humidite
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "wind0"
id: h1_weather_wind
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "precipitation0"
id: h1_weather_precipitation
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "heure1"
id: h2_weather_heure
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "temperature1"
id: h2_weather_temperature
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "humidite1"
id: h2_weather_humidite
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "wind1"
id: h2_weather_wind
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "precipitation1"
id: h2_weather_precipitation
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "heure2"
id: h3_weather_heure
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "temperature2"
id: h3_weather_temperature
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "humidite2"
id: h3_weather_humidite
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "wind2"
id: h3_weather_wind
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "precipitation2"
id: h3_weather_precipitation
output:
- platform: gpio
pin: GPIO6
id: bsp_led
inverted: true
- platform: gpio
pin: GPIO16
id: bsp_sd_enable
- platform: gpio
pin: GPIO21
id: bsp_battery_enable
- platform: ledc # CORRECTED: 'ledc' is the correct platform for ESP32 PWM.
pin: GPIO45
id: buzzer_pwm
# The frequency determines the pitch of the buzzer's sound. 1000Hz is a mid-range tone.
frequency: 1000Hz
# Onboard LED verte
light:
- platform: binary
name: Onboard LED
output: bsp_led
id: onboard_led
- platform: monochromatic
output: buzzer_pwm
name: Buzzer
id: buzzer
# Setting transition length to 0s makes the buzzer turn on and off instantly.
default_transition_length: 0s
binary_sensor:
- platform: gpio # Bouton page suivante
pin:
number: GPIO4
mode: INPUT_PULLUP
inverted: true
id: key1
name: "Bouton suivant"
on_press:
then:
- lambda: |-
id(page_index) = (id(page_index) + 1) % 2;
id(epaper_display).update();
- platform: gpio # Bouton page précédente
pin:
number: GPIO5
mode: INPUT_PULLUP
inverted: true
id: key2
name: "Bouton précédent"
on_press:
then:
- lambda: |-
id(page_index) = (id(page_index) - 1 + 2) % 2;
id(epaper_display).update();
# - platform: gpio # Bouton vert
# pin:
# number: GPIO3
# mode: INPUT_PULLUP
# inverted: true
# id: key_green
# name: "Bouton vert"
# on_multi_click:
# - timing:
# - ON for 40ms to 400ms
# - OFF for 40ms to 300ms
# - ON for 40ms to 400ms
# - OFF for at least 330ms
# then:
# - component.update: epaper_display
# Home Assistant time
time:
- platform: homeassistant
id: ha_time
sun:
latitude: !secret latitude
longitude: !secret longitude
text_sensor:
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "conditiona"
id: today_weather
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "conditiond"
id: tomorrow_weather
- platform: homeassistant
entity_id: weather.ma_ville
id: now_weather
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "condition0"
id: h1_weather_condition
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "condition1"
id: h2_weather_condition
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "condition2"
id: h3_weather_condition
- platform: sun
name: Sun Next Sunrise
type: sunrise
format: "%H:%M"
id: sun_sunrise
internal: true
- platform: sun
name: Sun Next Sunset
type: sunset
format: "%H:%M"
id: sun_sunset
internal: true
- platform: homeassistant
entity_id: sensor.direction_vent_ma_ville
id: now_weather_wind_bearing
# Fonts
font:
- file: "gfonts://Inter@700"
id: small_font
size: 24
- file: "gfonts://Inter@700"
id: mid_font
size: 36
glyphs: "<>!'%()/+,-_.:;*=°?#0123456789AÀBCDEÉÈÊFGHIJKLMNOPQRSTUVWXYZ aàbcdeéèêfghijklmnopqrstuvwxyzôöç"
- file: "gfonts://Inter@700"
id: title_font
size: 42
glyphs: "<>!'%()/+,-_.:;*=°?#0123456789AÀBCDEÉÈÊFGHIJKLMNOPQRSTUVWXYZ aàbcdeéèêfghijklmnopqrstuvwxyzôöç"
- file: "gfonts://Inter@700"
id: big_font
size: 180
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_mdi_large
size: 70
glyphs:
- "\U000F050F" # thermometer
- "\U000F058E" # humidity
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_bat_icon
size: 24
glyphs:
- "\U000F007A" # mdi-battery-10
- "\U000F007B" # mdi-battery-20
- "\U000F007C" # mdi-battery-30
- "\U000F007D" # mdi-battery-40
- "\U000F007E" # mdi-battery-50
- "\U000F007F" # mdi-battery-60
- "\U000F0080" # mdi-battery-70
- "\U000F0081" # mdi-battery-80
- "\U000F0082" # mdi-battery-90
- "\U000F0079" # mdi-battery
- file: 'fonts/materialdesignicons-webfont.ttf'
id: icon_today
size: 110
glyphs: &mdi-weather-glyphs
- "\U000F0590" # mdi-weather-cloudy
- "\U000F0F2F" # mdi-weather-cloudy-alert
- "\U000F0593" # mdi-weather-lightning
- "\U000F067E" # mdi-weather-lightning-rainy
- "\U000F0594" # mdi-weather-night
- "\U000F0F31" # mdi-weather-night-partly-cloudy
- "\U000F0595" # mdi-weather-partly-cloudy
- "\U000F0F32" # mdi-weather-partly-lightning
- "\U000F0F33" # mdi-weather-partly-rainy
- "\U000F0596" # mdi-weather-pouring
- "\U000F0597" # mdi-weather-rainy
- "\U000F0599" # mdi-weather-sunny
- "\U000F059B" # mdi-weather-sunset-down
- "\U000F059C" # mdi-weather-sunset-up
- "\U000F0F37" # mdi-weather-sunny-alert
- "\U000F14E4" # mdi-weather-sunny-off
- "\U000F059A" # mdi-weather-sunset
- "\U000F059D" # mdi-weather-windy
- "\U000F059E" # mdi-weather-windy-variant
- "\U000F0591" # mdi-weather-fog
- "\U000F0592" # mdi-weather-hail
- "\U000F0F30" # mdi-weather-hazy
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_weather_icon
size: 24
glyphs:
- "\U000F10C2" # mdi-thermometer-high
- "\U000F10C3" # mdi-thermometer-low
- "\U000F058E" # mdi-water-percent
- "\U000F058C" # mdi-water
- "\U000F059D" # mdi-weather-windy
- "\U000F15FA" # mdi-windsock
- file: 'fonts/materialdesignicons-webfont.ttf'
id: icon_sun
size: 36
glyphs:
- "\U000F059B" # mdi-weather-sunset-down
- "\U000F059C" # mdi-weather-sunset-up
# e-paper
display:
- platform: waveshare_epaper
id: epaper_display
model: 7.50inv2
cs_pin: GPIO10
dc_pin: GPIO11
reset_pin:
number: GPIO12
inverted: false
busy_pin:
number: GPIO13
inverted: true
update_interval: never
lambda: |-
// Map weather states to MDI characters.
std::map<std::string, std::string> weather_icon_map
{
{"cloudy", "\U000F0590"},
{"cloudy-alert", "\U000F0F2F"},
{"fog", "\U000F0591"},
{"hail", "\U000F0592"},
{"hazy", "\U000F0F30"},
{"lightning", "\U000F0593"},
{"lightning-rainy", "\U000F067E"},
{"clear-night", "\U000F0594"},
{"night-partly-cloudy", "\U000F0F31"},
{"partlycloudy", "\U000F0595"},
{"partly-lightning", "\U000F0F32"},
{"partly-rainy", "\U000F0F33"},
{"pouring", "\U000F0596"},
{"rainy", "\U000F0597"},
{"sunny", "\U000F0599"},
{"sunny-alert", "\U000F0F37"},
{"sunny-off", "\U000F14E4"},
{"sunset", "\U000F059A"},
{"sunset-down", "\U000F059B"},
{"sunset-up", "\U000F059C"},
{"windy", "\U000F059D"},
{"windy-variant", "\U000F059E"},
};
// ---------- PAGE 0 ----------
if (id(page_index) == 0) {
const int scr_w = 800;
const int scr_h = 480;
// Batterie dans le coin supérieur droit
it.printf(700, 7, id(font_bat_icon), "%s", id(battery_glyph).c_str());
it.printf(730, 5, id(small_font), "%.0f%%", id(battery_level).state);
//ligne verticale
it.filled_rectangle(400, 100, 2, 280);
// ---------------------------------------------------------
// Horizontal split: two 400 px columns
const int col_w = scr_w / 2;
const int icon_y = 100; // Icon baseline
const int value_y = 220; // Number baseline
const int unit_y = 300; // Unit baseline
const int label_y = 380; // Text label baseline
const int icon_size = 70; // icon font size
const int val_size = 120; // number font size
const int unit_size = 44; // unit font size
const int label_size= 36; // label font size
// --- colonne gauche : Température -----------------------------
const int left_mid = col_w / 2 - 30; // 200 px
// Icon
it.printf(left_mid, icon_y, id(font_mdi_large), TextAlign::CENTER, "\U000F050F");
// Value
it.printf(left_mid, value_y, id(big_font), TextAlign::CENTER, "%.0f", id(temp_sensor).state);
// Unit
it.printf(left_mid + 150, unit_y, id(mid_font), TextAlign::CENTER, "°C");
// Label
it.printf(left_mid, label_y, id(mid_font), TextAlign::CENTER, "Température");
// --- colonne droite : Humidité -------------------------------
const int right_mid = col_w + col_w / 2; // 600 px
// Icon
it.printf(right_mid, icon_y, id(font_mdi_large), TextAlign::CENTER, "\U000F058E");
// Value
it.printf(right_mid, value_y, id(big_font), TextAlign::CENTER, "%.0f", id(hum_sensor).state);
// Unit
it.printf(right_mid + 150, unit_y, id(mid_font), TextAlign::CENTER, "%%");
// Label
it.printf(right_mid, label_y, id(mid_font), TextAlign::CENTER, "Humidité");
}
// ---------- PAGE 1 ----------
else{
// Batterie dans le coin supérieur droit
it.printf(700, 7, id(font_bat_icon), "%s", id(battery_glyph).c_str());
it.printf(730, 5, id(small_font), "%.0f%%", id(battery_level).state);
// heure date
const char* jours[] = {"Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"};
const char* mois[] = {"Janv", "Févr", "Mars", "Avr", "Mai", "Juin", "Juill", "Août", "Sept", "Oct", "Nov", "Déc"};
auto t = id(ha_time).now();
if (t.is_valid()) {
// Heure
it.strftime(10, 5, id(mid_font), "%H:%M", t);
// Affiche la date en français "Jeu 04 sept 2025"
it.printf(10, 40, id(mid_font), "%s %d %s %d",
jours[t.day_of_week -1],
t.day_of_month,
mois[t.month - 1],
t.year);
}
// Soleil lever/coucher
it.printf(379, 8, id(icon_sun), TextAlign::TOP_CENTER, "\U000F059C");
it.printf(404, 5, id(mid_font), TextAlign::TOP_LEFT, "à %s", id(sun_sunrise).state.c_str());
it.printf(379, 43, id(icon_sun), TextAlign::TOP_CENTER, "\U000F059B");
it.printf(404, 40, id(mid_font), TextAlign::TOP_LEFT, "à %s", id(sun_sunset).state.c_str());
//ligne verticale
it.filled_rectangle(280, 90, 4, 375, BLACK);
// --- colonne gauche : Météo -----------------------------
// Titre météo
it.printf(135, 90, id(title_font), TextAlign::TOP_CENTER, "MÉTÉO");
// Titre aujourd'hui
it.printf(135, 150, id(small_font), TextAlign::TOP_CENTER, "Aujourd'hui");
// icône aujourd'hui
it.printf(70, 190, id(icon_today), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(today_weather).state.c_str()].c_str());
// températurea
it.printf(145, 192, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(160, 190, id(small_font), TextAlign::TOP_LEFT, "%.1f°C", id(today_temperaturea).state);
// templowa
it.printf(145, 222, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F10C3");
it.printf(160, 220, id(small_font), TextAlign::TOP_LEFT, "%.1f°C", id(today_templowa).state);
// humiditéa
it.printf(145, 252, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058E");
it.printf(160, 250, id(small_font), TextAlign::TOP_LEFT, "%.0f%%", id(today_humiditea).state);
// précipitationa
it.printf(145, 282, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058C");
it.printf(160, 280, id(small_font), TextAlign::TOP_LEFT, "%.1fmm", id(today_precipitationa).state);
// Titre demain
it.printf(130, 310, id(small_font), TextAlign::TOP_CENTER, "Demain");
// icône demain
it.printf(70, 350, id(icon_today), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(tomorrow_weather).state.c_str()].c_str());
// températured
it.printf(145, 352, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(160, 350, id(small_font), TextAlign::TOP_LEFT, "%.1f°C", id(tomorrow_temperatured).state);
// templowd
it.printf(145, 382, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F10C3");
it.printf(160, 380, id(small_font), TextAlign::TOP_LEFT, "%.1f°C", id(tomorrow_templowd).state);
// humiditéd
it.printf(145, 412, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058E");
it.printf(160, 410, id(small_font), TextAlign::TOP_LEFT, "%.0f%%", id(tomorrow_humidited).state);
// précipitationd
it.printf(145, 442, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058C");
it.printf(160, 440, id(small_font), TextAlign::TOP_LEFT, "%.1fmm", id(tomorrow_precipitationd).state);
// --- colonne droite : Prévision -----------------------------
// Titre prévision
it.printf(540, 90, id(title_font), TextAlign::TOP_CENTER, "PRÉVISION");
// Titre maintenant
it.printf(430, 150, id(small_font), TextAlign::TOP_CENTER, "Maintenant");
// icône maintenant
it.printf(360, 190, id(icon_today), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(now_weather).state.c_str()].c_str());
// now_weather_temperature
it.printf(435, 192, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(450, 190, id(small_font), TextAlign::TOP_LEFT, "%.1f°C", id(now_weather_temperature).state);
// now_weather_humidity
it.printf(435, 222, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058E");
it.printf(450, 220, id(small_font), TextAlign::TOP_LEFT, "%.0f%%", id(now_weather_humidity).state);
// now_weather_wind_speed
it.printf(435, 252, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F059D");
it.printf(450, 250, id(small_font), TextAlign::TOP_LEFT, "%.0fkm/h", id(now_weather_wind_speed).state);
// now_weather_wind_bearing
it.printf(435, 282, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F15FA");
it.printf(450, 280, id(small_font), TextAlign::TOP_LEFT, "%s", id(now_weather_wind_bearing).state.c_str());
// Titre heure +1
it.printf(670, 150, id(small_font), TextAlign::TOP_CENTER, "%.0f:00", id(h1_weather_heure).state);
// icône heure +1
it.printf(600, 190, id(icon_today), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(h1_weather_condition).state.c_str()].c_str());
// h1_weather_temperature
it.printf(675, 192, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(690, 190, id(small_font), TextAlign::TOP_LEFT, "%.1f°C", id(h1_weather_temperature).state);
// h1_weather_humidite
it.printf(675, 222, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058E");
it.printf(690, 220, id(small_font), TextAlign::TOP_LEFT, "%.0f%%", id(h1_weather_humidite).state);
// h1_weather_wind
it.printf(675, 252, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F059D");
it.printf(690, 250, id(small_font), TextAlign::TOP_LEFT, "%.0fkm/h", id(h1_weather_wind).state);
// h1_weather_precipitation
it.printf(675, 282, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058C");
it.printf(690, 280, id(small_font), TextAlign::TOP_LEFT, "%.1fmm", id(h1_weather_precipitation).state);
// Titre heure +2
it.printf(430, 310, id(small_font), TextAlign::TOP_CENTER, "%.0f:00", id(h2_weather_heure).state);
// icône heure +2
it.printf(360, 350, id(icon_today), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(h2_weather_condition).state.c_str()].c_str());
// h2_weather_temperature
it.printf(435, 352, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(450, 350, id(small_font), TextAlign::TOP_LEFT, "%.1f°C", id(h2_weather_temperature).state);
// h2_weather_humidite
it.printf(435, 382, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058E");
it.printf(450, 380, id(small_font), TextAlign::TOP_LEFT, "%.0f%%", id(h2_weather_humidite).state);
// h2_weather_wind
it.printf(435, 412, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F059D");
it.printf(450, 410, id(small_font), TextAlign::TOP_LEFT, "%.0fkm/h", id(h2_weather_wind).state);
// h2_weather_precipitation
it.printf(435, 442, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058C");
it.printf(450, 440, id(small_font), TextAlign::TOP_LEFT, "%.1fmm", id(h2_weather_precipitation).state);
// Titre heure +3
it.printf(670, 310, id(small_font), TextAlign::TOP_CENTER, "%.0f:00", id(h3_weather_heure).state);
// icône heure +3
it.printf(600, 350, id(icon_today), TextAlign::TOP_CENTER, "%s", weather_icon_map[id(h3_weather_condition).state.c_str()].c_str());
// h3_weather_temperature
it.printf(675, 352, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(690, 350, id(small_font), TextAlign::TOP_LEFT, "%.1f°C", id(h3_weather_temperature).state);
// h3_weather_humidite
it.printf(675, 382, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058E");
it.printf(690, 380, id(small_font), TextAlign::TOP_LEFT, "%.0f%%", id(h3_weather_humidite).state);
// h3_weather_wind
it.printf(675, 412, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F059D");
it.printf(690, 410, id(small_font), TextAlign::TOP_LEFT, "%.0fkm/h", id(h3_weather_wind).state);
// h3_weather_precipitation
it.printf(675, 442, id(font_weather_icon), TextAlign::TOP_CENTER, "\U000F058C");
it.printf(690, 440, id(small_font), TextAlign::TOP_LEFT, "%.1fmm", id(h3_weather_precipitation).state);
}
Code ESPHome reTerminal E1002 (Couleur)
substitutions:
name: reterminal-e1002
friendly_name: reTerminal E1002
esphome:
name: ${name}
name_add_mac_suffix: false
friendly_name: ${friendly_name}
on_boot:
- priority: 600
then:
- output.turn_on: bsp_sd_enable
- output.turn_on: bsp_battery_enable
- delay: 200ms
- component.update: battery_voltage
- component.update: battery_level
- delay: 15s
- component.update: sun_sunrise
- component.update: sun_sunset
- delay: 45s
- component.update: epaper_display
- delay: 30s
- lambda: |-
auto time = id(ha_time).now();
if (!time.is_valid()) {
ESP_LOGI("custom", "Heure invalide (pas de synchro NTP). Deep sleep 60 min par défaut.");
id(deep_sleep_1).set_sleep_duration(60 * 60 * 1000ULL);
id(deep_sleep_1).begin_sleep(true);
return;
}
int now_seconds = time.hour * 3600 + time.minute * 60 + time.second;
int h = time.hour;
int m = time.minute;
int interval = id(sleep_interval_hours);
int target_min = id(target_minute);
//Calcul de la prochaine heure cible
int next_target_hour = (h / interval) * interval;
if (m >= target_min) {
next_target_hour += interval;
}
if (next_target_hour >= 24) next_target_hour -= 24;
int target_seconds = next_target_hour * 3600 + target_min * 60;
if (target_seconds <= now_seconds) {
target_seconds += 24 * 3600; // bascule au jour suivant
}
int sleep_seconds = target_seconds - now_seconds;
ESP_LOGI("custom", "Il est %02d:%02d:%02d", h, m, time.second);
ESP_LOGI("custom", "Prochain réveil prévu à %02d:%02d (dans %d sec)",
next_target_hour, target_min, sleep_seconds);
id(deep_sleep_1).set_sleep_duration((uint64_t)sleep_seconds * 1000ULL);
id(deep_sleep_1).begin_sleep(true);
external_components:
# - source:
# type: git
# url: https://github.com/lublak/esphome
# ref: dev
# components: [ waveshare_epaper ]
- source: github://pr#8416
components: [waveshare_epaper]
refresh: 1h
esp32:
#board: esp32-s3-devkitc-1
variant: esp32s3
framework:
type: esp-idf
flash_size: 32MB
psram:
mode: octal
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "LYgjtfzoelYYwciqzqr1Z6dsZjwccpxxxxxxxxxxx"
ota:
- platform: esphome
password: "ce1cbb03ba47ca50d022xxxxxxxxx"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: true
manual_ip:
static_ip: 192.168.1.110
gateway: !secret wifi_gtw
subnet: !secret wifi_sub
dns1: !secret wifi_dns1
deep_sleep:
id: deep_sleep_1
run_duration: 2min
#sleep_duration: 58min
wakeup_pin: GPIO3 # Bouton vert
wakeup_pin_mode: INVERT_WAKEUP
# SPI / I²C
spi:
clk_pin: GPIO7
mosi_pin: GPIO9
i2c:
scl: GPIO20
sda: GPIO19
globals:
- id: page_index
type: int
restore_value: true
initial_value: '0'
- id: battery_glyph
type: std::string
restore_value: no
initial_value: "\"\\U000F0079\"" # default full battery
- id: sleep_interval_hours
type: int
restore_value: no
initial_value: '1' # Toutes les 1 heure
- id: target_minute
type: int
restore_value: no
initial_value: '2' # À la minute 02
script:
- id: buzzer_cycle
mode: single
then:
- light.turn_on:
id: buzzer
brightness: "50%"
- delay: 400ms
- light.turn_off: buzzer
- delay: 400ms
- light.turn_on:
id: buzzer
brightness: "60%"
- delay: 400ms
- light.turn_off: buzzer
- delay: 400ms
- light.turn_on:
id: buzzer
brightness: "70%"
- delay: 400ms
- light.turn_off: buzzer
- delay: 400ms
- light.turn_on:
id: buzzer
brightness: "80%"
- delay: 400ms
- light.turn_off: buzzer
- delay: 400ms
- light.turn_on:
id: buzzer
brightness: "80%"
- delay: 400ms
- light.turn_off: buzzer
sensor:
- platform: sht4x
temperature:
name: Température
id: temp_sensor
humidity:
name: Humidité
id: hum_sensor
- platform: adc
pin: GPIO1
name: Tension Batterie
id: battery_voltage
update_interval: 60s
attenuation: 12db
filters:
- multiply: 2.0
on_value_range:
- below: 3.41
then:
- script.execute: buzzer_cycle
- platform: template
name: Niveau Batterie
id: battery_level
unit_of_measurement: "%"
icon: "mdi:battery"
device_class: battery
state_class: measurement
lambda: 'return id(battery_voltage).state;'
update_interval: 60s
on_value:
then:
- lambda: |-
int pct = int(x);
if (pct <= 10) id(battery_glyph) = "\U000F007A";
else if (pct <= 20) id(battery_glyph) = "\U000F007B";
else if (pct <= 30) id(battery_glyph) = "\U000F007C";
else if (pct <= 40) id(battery_glyph) = "\U000F007D";
else if (pct <= 50) id(battery_glyph) = "\U000F007E";
else if (pct <= 60) id(battery_glyph) = "\U000F007F";
else if (pct <= 70) id(battery_glyph) = "\U000F0080";
else if (pct <= 80) id(battery_glyph) = "\U000F0081";
else if (pct <= 90) id(battery_glyph) = "\U000F0082";
else id(battery_glyph) = "\U000F0079";
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
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "temperaturea"
id: today_temperaturea
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "templowa"
id: today_templowa
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "humiditea"
id: today_humiditea
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "precipitationa"
id: today_precipitationa
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "temperatured"
id: tomorrow_temperatured
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "templowd"
id: tomorrow_templowd
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "humidited"
id: tomorrow_humidited
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "precipitationd"
id: tomorrow_precipitationd
- platform: homeassistant
entity_id: weather.ma_ville
attribute: "temperature"
id: now_weather_temperature
- platform: homeassistant
entity_id: weather.ma_ville
attribute: "humidity"
id: now_weather_humidity
- platform: homeassistant
entity_id: weather.ma_ville
attribute: "wind_speed"
id: now_weather_wind_speed
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "heure0"
id: h1_weather_heure
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "temperature0"
id: h1_weather_temperature
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "humidite0"
id: h1_weather_humidite
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "wind0"
id: h1_weather_wind
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "precipitation0"
id: h1_weather_precipitation
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "heure1"
id: h2_weather_heure
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "temperature1"
id: h2_weather_temperature
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "humidite1"
id: h2_weather_humidite
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "wind1"
id: h2_weather_wind
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "precipitation1"
id: h2_weather_precipitation
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "heure2"
id: h3_weather_heure
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "temperature2"
id: h3_weather_temperature
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "humidite2"
id: h3_weather_humidite
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "wind2"
id: h3_weather_wind
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "precipitation2"
id: h3_weather_precipitation
output:
- platform: gpio
pin: GPIO6
id: bsp_led
inverted: true
- platform: gpio
pin: GPIO16
id: bsp_sd_enable
- platform: gpio
pin: GPIO21
id: bsp_battery_enable
- platform: ledc # CORRECTED: 'ledc' is the correct platform for ESP32 PWM.
pin: GPIO45
id: buzzer_pwm
# The frequency determines the pitch of the buzzer's sound. 1000Hz is a mid-range tone.
frequency: 1000Hz
# Onboard LED verte
light:
- platform: binary
name: Onboard LED
output: bsp_led
id: onboard_led
- platform: monochromatic
output: buzzer_pwm
name: Buzzer
id: buzzer
# Setting transition length to 0s makes the buzzer turn on and off instantly.
default_transition_length: 0s
binary_sensor:
- platform: gpio # Bouton page suivante
pin:
number: GPIO4
mode: INPUT_PULLUP
inverted: true
id: key1
name: "Bouton suivant"
on_press:
then:
- lambda: |-
id(page_index) = (id(page_index) + 1) % 2;
id(epaper_display).update();
- platform: gpio # Bouton page précédente
pin:
number: GPIO5
mode: INPUT_PULLUP
inverted: true
id: key2
name: "Bouton précédent"
on_press:
then:
- lambda: |-
id(page_index) = (id(page_index) - 1 + 2) % 2;
id(epaper_display).update();
# - platform: gpio # Bouton vert
# pin:
# number: GPIO3
# mode: INPUT_PULLUP
# inverted: true
# id: key_green
# name: "Bouton vert"
# on_multi_click:
# - timing:
# - ON for 40ms to 400ms
# - OFF for 40ms to 300ms
# - ON for 40ms to 400ms
# - OFF for at least 330ms
# then:
# - component.update: epaper_display
# Home Assistant time
time:
- platform: homeassistant
id: ha_time
sun:
latitude: !secret latitude
longitude: !secret longitude
text_sensor:
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "conditiona"
id: today_weather
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "conditiond"
id: tomorrow_weather
- platform: homeassistant
entity_id: weather.ma_ville
id: now_weather
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "condition0"
id: h1_weather_condition
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "condition1"
id: h2_weather_condition
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "condition2"
id: h3_weather_condition
- platform: sun
name: Sun Next Sunrise
type: sunrise
format: "%H:%M"
id: sun_sunrise
internal: true
- platform: sun
name: Sun Next Sunset
type: sunset
format: "%H:%M"
id: sun_sunset
internal: true
- platform: homeassistant
entity_id: sensor.direction_vent_ma_ville
id: now_weather_wind_bearing
# Fonts
font:
- file: "gfonts://Inter@700"
id: small_font
size: 24
- file: "gfonts://Inter@700"
id: mid_font
size: 36
glyphs: "<>!'%()/+,-_.:;*=°?#0123456789AÀBCDEÉÈÊFGHIJKLMNOPQRSTUVWXYZ aàbcdeéèêfghijklmnopqrstuvwxyzôöç"
- file: "gfonts://Inter@700"
id: title_font
size: 42
glyphs: "<>!'%()/+,-_.:;*=°?#0123456789AÀBCDEÉÈÊFGHIJKLMNOPQRSTUVWXYZ aàbcdeéèêfghijklmnopqrstuvwxyzôöç"
- file: "gfonts://Inter@700"
id: big_font
size: 180
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_mdi_large
size: 70
glyphs:
- "\U000F050F" # thermometer
- "\U000F058E" # humidity
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_bat_icon
size: 24
glyphs:
- "\U000F007A" # mdi-battery-10
- "\U000F007B" # mdi-battery-20
- "\U000F007C" # mdi-battery-30
- "\U000F007D" # mdi-battery-40
- "\U000F007E" # mdi-battery-50
- "\U000F007F" # mdi-battery-60
- "\U000F0080" # mdi-battery-70
- "\U000F0081" # mdi-battery-80
- "\U000F0082" # mdi-battery-90
- "\U000F0079" # mdi-battery
- file: 'fonts/materialdesignicons-webfont.ttf'
id: icon_today
size: 110
glyphs: &mdi-weather-glyphs
- "\U000F0590" # mdi-weather-cloudy
- "\U000F0F2F" # mdi-weather-cloudy-alert
- "\U000F0593" # mdi-weather-lightning
- "\U000F067E" # mdi-weather-lightning-rainy
- "\U000F0594" # mdi-weather-night
- "\U000F0F31" # mdi-weather-night-partly-cloudy
- "\U000F0595" # mdi-weather-partly-cloudy
- "\U000F0F32" # mdi-weather-partly-lightning
- "\U000F0F33" # mdi-weather-partly-rainy
- "\U000F0596" # mdi-weather-pouring
- "\U000F0597" # mdi-weather-rainy
- "\U000F0599" # mdi-weather-sunny
- "\U000F059B" # mdi-weather-sunset-down
- "\U000F059C" # mdi-weather-sunset-up
- "\U000F0F37" # mdi-weather-sunny-alert
- "\U000F14E4" # mdi-weather-sunny-off
- "\U000F059A" # mdi-weather-sunset
- "\U000F059D" # mdi-weather-windy
- "\U000F059E" # mdi-weather-windy-variant
- "\U000F0591" # mdi-weather-fog
- "\U000F0592" # mdi-weather-hail
- "\U000F0F30" # mdi-weather-hazy
- file: 'fonts/materialdesignicons-webfont.ttf'
id: font_weather_icon
size: 24
glyphs:
- "\U000F10C2" # mdi-thermometer-high
- "\U000F10C3" # mdi-thermometer-low
- "\U000F058E" # mdi-water-percent
- "\U000F058C" # mdi-water
- "\U000F059D" # mdi-weather-windy
- "\U000F15FA" # mdi-windsock
- file: 'fonts/materialdesignicons-webfont.ttf'
id: icon_sun
size: 36
glyphs:
- "\U000F059B" # mdi-weather-sunset-down
- "\U000F059C" # mdi-weather-sunset-up
# e-paper
display:
- platform: waveshare_epaper
id: epaper_display
model: 7.30in-e
cs_pin: GPIO10
dc_pin: GPIO11
reset_pin:
number: GPIO12
inverted: false
busy_pin:
number: GPIO13
inverted: true
update_interval: never
lambda: |-
const auto BLACK = Color(0, 0, 0, 0);
const auto RED = Color(255, 0, 0, 0);
const auto GREEN = Color(0, 255, 0, 0);
const auto BLUE = Color(0, 0, 255, 0);
const auto YELLOW = Color(255, 255, 0, 0);
const auto WHITE = Color(255, 255, 255);
// Map weather states to MDI characters.
std::map<std::string, std::string> weather_icon_map
{
{"cloudy", "\U000F0590"},
{"cloudy-alert", "\U000F0F2F"},
{"fog", "\U000F0591"},
{"hail", "\U000F0592"},
{"hazy", "\U000F0F30"},
{"lightning", "\U000F0593"},
{"lightning-rainy", "\U000F067E"},
{"clear-night", "\U000F0594"},
{"night-partly-cloudy", "\U000F0F31"},
{"partlycloudy", "\U000F0595"},
{"partly-lightning", "\U000F0F32"},
{"partly-rainy", "\U000F0F33"},
{"pouring", "\U000F0596"},
{"rainy", "\U000F0597"},
{"sunny", "\U000F0599"},
{"sunny-alert", "\U000F0F37"},
{"sunny-off", "\U000F14E4"},
{"sunset", "\U000F059A"},
{"sunset-down", "\U000F059B"},
{"sunset-up", "\U000F059C"},
{"windy", "\U000F059D"},
{"windy-variant", "\U000F059E"},
};
// ---------- PAGE 0 ----------
if (id(page_index) == 0) {
const int scr_w = 800;
const int scr_h = 480;
// Batterie dans le coin supérieur droit
it.printf(700, 7, id(font_bat_icon), BLACK, "%s", id(battery_glyph).c_str());
it.printf(730, 5, id(small_font), BLUE, "%.0f%%", id(battery_level).state);
//ligne verticale
it.filled_rectangle(400, 100, 2, 280, BLACK);
// ---------------------------------------------------------
// Horizontal split: two 400 px columns
const int col_w = scr_w / 2;
const int icon_y = 100; // Icon baseline
const int value_y = 220; // Number baseline
const int unit_y = 300; // Unit baseline
const int label_y = 380; // Text label baseline
const int icon_size = 70; // icon font size
const int val_size = 120; // number font size
const int unit_size = 44; // unit font size
const int label_size= 36; // label font size
// --- colonne gauche : Température -----------------------------
const int left_mid = col_w / 2 - 30; // 200 px
// Icon
it.printf(left_mid, icon_y, id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F050F");
// Value
it.printf(left_mid, value_y, id(big_font), GREEN, TextAlign::CENTER, "%.0f", id(temp_sensor).state);
// Unit
it.printf(left_mid + 150, unit_y, id(mid_font), GREEN, TextAlign::CENTER, "°C");
// Label
it.printf(left_mid, label_y, id(mid_font), RED, TextAlign::CENTER, "Température");
// --- colonne droite : Humidité -------------------------------
const int right_mid = col_w + col_w / 2; // 600 px
// Icon
it.printf(right_mid, icon_y, id(font_mdi_large), BLACK, TextAlign::CENTER, "\U000F058E");
// Value
it.printf(right_mid, value_y, id(big_font), BLUE, TextAlign::CENTER, "%.0f", id(hum_sensor).state);
// Unit
it.printf(right_mid + 150, unit_y, id(mid_font), BLUE, TextAlign::CENTER, "%%");
// Label
it.printf(right_mid, label_y, id(mid_font), RED, TextAlign::CENTER, "Humidité");
}
// ---------- PAGE 1 ----------
else{
// Batterie dans le coin supérieur droit
it.printf(700, 7, id(font_bat_icon), BLACK, "%s", id(battery_glyph).c_str());
it.printf(730, 5, id(small_font), BLUE, "%.0f%%", id(battery_level).state);
// heure date
const char* jours[] = {"Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"};
const char* mois[] = {"Janv", "Févr", "Mars", "Avr", "Mai", "Juin", "Juill", "Août", "Sept", "Oct", "Nov", "Déc"};
auto t = id(ha_time).now();
if (t.is_valid()) {
// Heure
it.strftime(10, 5, id(mid_font), BLUE, "%H:%M", t);
// Affiche la date en français "Jeu 04 sept 2025"
it.printf(10, 40, id(mid_font), BLUE, "%s %d %s %d",
jours[t.day_of_week -1],
t.day_of_month,
mois[t.month - 1],
t.year);
}
// Soleil lever/coucher
it.printf(379, 8, id(icon_sun), BLUE, TextAlign::TOP_CENTER, "\U000F059C");
it.printf(404, 5, id(mid_font), BLUE, TextAlign::TOP_LEFT, "à %s", id(sun_sunrise).state.c_str());
it.printf(379, 43, id(icon_sun), BLUE, TextAlign::TOP_CENTER, "\U000F059B");
it.printf(404, 40, id(mid_font), BLUE, TextAlign::TOP_LEFT, "à %s", id(sun_sunset).state.c_str());
//ligne verticale
it.filled_rectangle(280, 90, 4, 375, BLACK);
// --- colonne gauche : Météo -----------------------------
// Titre météo
it.printf(135, 90, id(title_font), BLACK, TextAlign::TOP_CENTER, "MÉTÉO");
// Titre aujourd'hui
it.printf(135, 150, id(small_font), RED, TextAlign::TOP_CENTER, "Aujourd'hui");
// icône aujourd'hui
it.printf(70, 190, id(icon_today), BLACK, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(today_weather).state.c_str()].c_str());
// températurea
it.printf(145, 192, id(font_weather_icon), RED, TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(160, 190, id(small_font), RED, TextAlign::TOP_LEFT, "%.1f°C", id(today_temperaturea).state);
// templowa
it.printf(145, 222, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F10C3");
it.printf(160, 220, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.1f°C", id(today_templowa).state);
// humiditéa
it.printf(145, 252, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F058E");
it.printf(160, 250, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.0f%%", id(today_humiditea).state);
// précipitationa
it.printf(145, 282, id(font_weather_icon), BLACK, TextAlign::TOP_CENTER, "\U000F058C");
it.printf(160, 280, id(small_font), BLACK, TextAlign::TOP_LEFT, "%.1fmm", id(today_precipitationa).state);
// Titre demain
it.printf(130, 310, id(small_font), RED, TextAlign::TOP_CENTER, "Demain");
// icône demain
it.printf(70, 350, id(icon_today), BLACK, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(tomorrow_weather).state.c_str()].c_str());
// températured
it.printf(145, 352, id(font_weather_icon), RED, TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(160, 350, id(small_font), RED, TextAlign::TOP_LEFT, "%.1f°C", id(tomorrow_temperatured).state);
// templowd
it.printf(145, 382, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F10C3");
it.printf(160, 380, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.1f°C", id(tomorrow_templowd).state);
// humiditéd
it.printf(145, 412, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F058E");
it.printf(160, 410, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.0f%%", id(tomorrow_humidited).state);
// précipitationd
it.printf(145, 442, id(font_weather_icon), BLACK, TextAlign::TOP_CENTER, "\U000F058C");
it.printf(160, 440, id(small_font), BLACK, TextAlign::TOP_LEFT, "%.1fmm", id(tomorrow_precipitationd).state);
// --- colonne droite : Prévision -----------------------------
// Titre prévision
it.printf(540, 90, id(title_font), BLACK, TextAlign::TOP_CENTER, "PRÉVISION");
// Titre maintenant
it.printf(430, 150, id(small_font), RED, TextAlign::TOP_CENTER, "Maintenant");
// icône maintenant
it.printf(360, 190, id(icon_today), BLACK, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(now_weather).state.c_str()].c_str());
// now_weather_temperature
it.printf(435, 192, id(font_weather_icon), RED, TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(450, 190, id(small_font), RED, TextAlign::TOP_LEFT, "%.1f°C", id(now_weather_temperature).state);
// now_weather_humidity
it.printf(435, 222, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F058E");
it.printf(450, 220, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.0f%%", id(now_weather_humidity).state);
// now_weather_wind_speed
it.printf(435, 252, id(font_weather_icon), GREEN, TextAlign::TOP_CENTER, "\U000F059D");
it.printf(450, 250, id(small_font), GREEN, TextAlign::TOP_LEFT, "%.0fkm/h", id(now_weather_wind_speed).state);
// now_weather_wind_bearing
it.printf(435, 282, id(font_weather_icon), GREEN, TextAlign::TOP_CENTER, "\U000F15FA");
it.printf(450, 280, id(small_font), GREEN, TextAlign::TOP_LEFT, "%s", id(now_weather_wind_bearing).state.c_str());
// Titre heure +1
it.printf(670, 150, id(small_font), RED, TextAlign::TOP_CENTER, "%.0f:00", id(h1_weather_heure).state);
// icône heure +1
it.printf(600, 190, id(icon_today), BLACK, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(h1_weather_condition).state.c_str()].c_str());
// h1_weather_temperature
it.printf(675, 192, id(font_weather_icon), RED, TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(690, 190, id(small_font), RED, TextAlign::TOP_LEFT, "%.1f°C", id(h1_weather_temperature).state);
// h1_weather_humidite
it.printf(675, 222, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F058E");
it.printf(690, 220, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.0f%%", id(h1_weather_humidite).state);
// h1_weather_wind
it.printf(675, 252, id(font_weather_icon), GREEN, TextAlign::TOP_CENTER, "\U000F059D");
it.printf(690, 250, id(small_font), GREEN, TextAlign::TOP_LEFT, "%.0fkm/h", id(h1_weather_wind).state);
// h1_weather_precipitation
it.printf(675, 282, id(font_weather_icon), BLACK, TextAlign::TOP_CENTER, "\U000F058C");
it.printf(690, 280, id(small_font), BLACK, TextAlign::TOP_LEFT, "%.1fmm", id(h1_weather_precipitation).state);
// Titre heure +2
it.printf(430, 310, id(small_font), RED, TextAlign::TOP_CENTER, "%.0f:00", id(h2_weather_heure).state);
// icône heure +2
it.printf(360, 350, id(icon_today), BLACK, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(h2_weather_condition).state.c_str()].c_str());
// h2_weather_temperature
it.printf(435, 352, id(font_weather_icon), RED, TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(450, 350, id(small_font), RED, TextAlign::TOP_LEFT, "%.1f°C", id(h2_weather_temperature).state);
// h2_weather_humidite
it.printf(435, 382, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F058E");
it.printf(450, 380, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.0f%%", id(h2_weather_humidite).state);
// h2_weather_wind
it.printf(435, 412, id(font_weather_icon), GREEN, TextAlign::TOP_CENTER, "\U000F059D");
it.printf(450, 410, id(small_font), GREEN, TextAlign::TOP_LEFT, "%.0fkm/h", id(h2_weather_wind).state);
// h2_weather_precipitation
it.printf(435, 442, id(font_weather_icon), BLACK, TextAlign::TOP_CENTER, "\U000F058C");
it.printf(450, 440, id(small_font), BLACK, TextAlign::TOP_LEFT, "%.1fmm", id(h2_weather_precipitation).state);
// Titre heure +3
it.printf(670, 310, id(small_font), RED, TextAlign::TOP_CENTER, "%.0f:00", id(h3_weather_heure).state);
// icône heure +3
it.printf(600, 350, id(icon_today), BLACK, TextAlign::TOP_CENTER, "%s", weather_icon_map[id(h3_weather_condition).state.c_str()].c_str());
// h3_weather_temperature
it.printf(675, 352, id(font_weather_icon), RED, TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(690, 350, id(small_font), RED, TextAlign::TOP_LEFT, "%.1f°C", id(h3_weather_temperature).state);
// h3_weather_humidite
it.printf(675, 382, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F058E");
it.printf(690, 380, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.0f%%", id(h3_weather_humidite).state);
// h3_weather_wind
it.printf(675, 412, id(font_weather_icon), GREEN, TextAlign::TOP_CENTER, "\U000F059D");
it.printf(690, 410, id(small_font), GREEN, TextAlign::TOP_LEFT, "%.0fkm/h", id(h3_weather_wind).state);
// h3_weather_precipitation
it.printf(675, 442, id(font_weather_icon), BLACK, TextAlign::TOP_CENTER, "\U000F058C");
it.printf(690, 440, id(small_font), BLACK, TextAlign::TOP_LEFT, "%.1fmm", id(h3_weather_precipitation).state);
}
Utilisation de la démo


Après avoir flashé votre appareil, voici comment fonctionne le firmware de la démo.
- Le bouton vert sert à réveiller l'appareil et rafraichira l'écran.
- les boutons blanc page précédent ou suivant change de page. À utiliser après avoir appuyé sur le bouton vert. Ça changera de page et rafraichira la page.
- l'appareil se réveille toutes les xxH02 (chaque heure +2 minute. Exemple : 14h02, 15h02…) pour actualiser les données et rafraichir l'écran. Puis repasse en sommeil pendant une heure.
- Affichage du niveau de la batterie avec icône dynamique sur les deux pages
- Le buzzer doit sonner cinq fois quand le niveau de batterie passe à 10%.
- Affichage de l'heure, la date, le lever/coucher du soleil, de la météo de la journée sur deux jours et la prévision météo de la journée pour 4 heures sur la page 1.
- Affichage de la température et humidité du SHT40 sur la page 2
Vous aurez ces entités disponibles dans l'intégration ESPHome sur Home assistant.

Avec cette démo, le ReTerminal devrait tenir plus de trois semaines sur la batterie (approximatif).

Explication du code de la démo
Deep sleep
Le composant deep_sleep permet de mettre automatiquement l'ESP32 en veille prolongée après un certain temps. Ceci est particulièrement utile pour les appareils fonctionnant sur batterie et nécessitant une économie d'énergie maximale.
- run_duration est le temps que l'ESP reste allumé.
- sleep_duration est le temps qui reste en sommeil.
- wakeup_pin permet de choisir un pin GPIO pour réveiller l'appareil. Dans le code, j'utilise le bouton vert (GPIO03).
- wakeup_pin_mode uniquement sur ESP32. Spécifie comment gérer le réveil de wakeup_pin.
deep_sleep:
id: deep_sleep_1
run_duration: 2min
#sleep_duration: 58min
wakeup_pin: GPIO3 # Bouton vert
wakeup_pin_mode: INVERT_WAKEUP
Pour réveiller l'ESP32 toutes les heures, j'ai opté pour un lambda qui permettra de choisir à quel intervalle de temps il doit se réveille. Dans la démo, je l'ai programmé toutes les xxH02 (chaque heure +2 minute. Exemple : 14h02, 15h02…), pour se réveiller et actualisé les informations des capteurs et repasser en mode sommeil après l'actualisation de l'écran.
- lambda: |-
auto time = id(ha_time).now();
if (!time.is_valid()) {
ESP_LOGI("custom", "Heure invalide (pas de synchro NTP). Deep sleep 60 min par défaut.");
id(deep_sleep_1).set_sleep_duration(60 * 60 * 1000ULL);
id(deep_sleep_1).begin_sleep(true);
return;
}
int now_seconds = time.hour * 3600 + time.minute * 60 + time.second;
int h = time.hour;
int m = time.minute;
int interval = id(sleep_interval_hours);
int target_min = id(target_minute);
//Calcul de la prochaine heure cible
int next_target_hour = (h / interval) * interval;
if (m >= target_min) {
next_target_hour += interval;
}
if (next_target_hour >= 24) next_target_hour -= 24;
int target_seconds = next_target_hour * 3600 + target_min * 60;
if (target_seconds <= now_seconds) {
target_seconds += 24 * 3600; // bascule au jour suivant
}
int sleep_seconds = target_seconds - now_seconds;
ESP_LOGI("custom", "Il est %02d:%02d:%02d", h, m, time.second);
ESP_LOGI("custom", "Prochain réveil prévu à %02d:%02d (dans %d sec)",
next_target_hour, target_min, sleep_seconds);
id(deep_sleep_1).set_sleep_duration((uint64_t)sleep_seconds * 1000ULL);
id(deep_sleep_1).begin_sleep(true);
Pour modifier les heures et minutes, c'est dans la partie globals.
globals:
- id: sleep_interval_hours
type: int
restore_value: no
initial_value: '1' # Toutes les 1 heure
- id: target_minute
type: int
restore_value: no
initial_value: '2' # À la minute 02
Sensor Home Assistant
Le Sensor Home assistant vous permet de créer des capteurs qui importent des états en numérique depuis votre instance Home Assistant à l'aide de l'API.
- entity_id est pour le nom de l'entité numérique a importé de Home assistant.
- attribute ( optionel ) est si vous voulez utiliser l'attribut d'une entité numérique.
- id sert à donner un nom d'appel pour être utilisé dans le code.
sensor:
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_heure_epaper
attribute: "precipitation2"
id: h3_weather_precipitation
Text sensor home assistant
Le composant text sensor home assistant vous permet de créer des capteurs qui importent des états en texte (ON/OFF) depuis votre instance Home Assistant à l'aide de l'API.
- entity_id est pour le nom de l'entité texte a importé de Home assistant.
- attribute ( optionel ) est si vous voulez utiliser l'attribut d'une entité texte.
- id sert à donner un nom d'appel pour être utilisé dans le code.
text_sensor:
- platform: homeassistant
entity_id: sensor.meteo_ma_ville_jour_epaper
attribute: "conditiona"
id: today_weather
Sun
Le composant sun permet de suivre la position du soleil dans le ciel. Les calculs sont effectués toutes les 60 secondes.
sun
il faut indiquer la latitude et longitude de votre maison.
- latitude pour la coordonnéé GPS de la latitude de votre maison.
- longitude pour la coordonnéé GPS de la longitude de votre maison.
time
Une source de temps est requise, on va utiliser la source de notre Home assistant.
text_sensor
Pour créer des capteurs texte pour le prochaine lever/coucher du soleil.
- name est pour le nom de l'entité afficher sur Home assistant.
- type est pour le type de valeur a suivre. Lever (sunrise) ou coucher (sunset) du soleil.
- format format d'heure à afficher.
- internal pour ne pas importer le sensor dans Home assistant.
sun:
latitude: 48.8584°
longitude: 2.2945°
time:
- platform: homeassistant
text_sensor:
- platform: sun
name: Sun Next Sunrise
type: sunrise
format: "%H:%M"
id: sun_sunrise
internal: true
- platform: sun
name: Sun Next Sunset
type: sunset
format: "%H:%M"
id: sun_sunset
internal: true
Time
La meilleure façon de connaître l'heure dans ESPHome est d'utiliser Home Assistant. Avec la plateforme Time home assistant, la connexion à l'API de Home Assistant permet de synchroniser périodiquement l'heure actuelle.
- id sert à donner un nom d'appel pour être utilisé dans le code.
time:
- platform: homeassistant
id: ha_time
Display
Le display composant, permet l'affichage de donnée ou forme sur un écran.
Pour cela, vous devez utiliser une police d'écriture a déclaré dans la partie font.
Police
ESPHome dispose d'un puissant gestionnaire de polices (police) qui s'intègre parfaitement au système.
- file permet de choisir une police ou icone.
L'option gfonts:// permet d'utiliser des polices google. Dans l'exemple, j'utilise la police google Inter en gras 700. - id sert à donner un nom d'appel pour être utilisé dans le code.
- Size permet de choisir la taille de la police.
- glyphs permet une liste de caractère à utiliser pour la police.
font:
- file: "gfonts://Inter@700"
id: mid_font
size: 36
glyphs: "<>!'%()/+,-_.:;*=°?#0123456789AÀBCDEÉÈÊFGHIJKLMNOPQRSTUVWXYZ aàbcdeéèêfghijklmnopqrstuvwxyzôöç"
Pour afficher une icône du site Material design icons, il faut utiliser le fichier materialdesignicons-webfont.ttf et déclaré les icônes à utiliser dans glyphs.
Pour choisir l'icône, allez sur le site Material design icon, choisir une icône et cliquer dessus.

Puis copier le codepoint de l'icône, à utiliser dans la partie glyphs. Exemple "\U000F059C".

font:
- file: 'fonts/materialdesignicons-webfont.ttf'
id: icon_sun
size: 36
glyphs:
- "\U000F059B" # mdi-weather-sunset-down
- "\U000F059C" # mdi-weather-sunset-up
Pour écrire sur l'écran, on va passer par un lambda.
Afficher une icône
lambda: |-
it.printf(379, 8, id(icon_sun), BLUE, TextAlign::TOP_CENTER, "\U000F059C");
- ip.printf = pour écrire une valeur
- 379 = position X
- 8 = positon Y
- id(icon_sun) = la police a utilisé, mais dans notre cas, ça sera une icône.
- BLUE = la couleur de l'icône
- TextAlign = l'alignement du texte
- "\U000F059C" = l'icône a affiché
Afficher un sensor et le formater
lambda: |-
it.printf(160, 190, id(small_font), RED, TextAlign::TOP_LEFT, "%.1f°C", id(today_temperaturea).state);
- ip.printf = pour écrire une valeur
- 160 = position X
- 190 = positon Y
- id(small_font) = la police a utilisé
- RED = la couleur de la valeur
- TextAlign = l'alignement du texte
- "%.1f°C" = permet d'afficher la valeur avec 1 chiffre après la virgule et ajoute l'unité °C à la valeur.
Avec "%.0f" affiche la valeur sans décimal
Avec "%.0f%%" afficher l'unité % et sans décimal
Avec "%.1f%%" afficher l'unité % et un chiffre après la virgule - id(today_temperaturea).state = le ID d'une entité sensor (chiffre) à utiliser pour la valeur a affiché
Afficher un rectangle rempli
lambda: |-
it.filled_rectangle(280, 90, 10, 375, BLACK);
- it.filled_rectangle = afficher un rectangle rempli
- 280 = position X
- 90 = position Y
- 10 = largeur
- 375 = hauteur
- BLACK = couleur
Afficher un rectangle vide
lambda: |-
it.rectangle(5, 20, 30, 42);
- it.rectangle = affiche un rectangle vide
- 5 = position X
- 20 = position Y
- 30 = largeur
- 42 = hauteur
Vous trouverez d'autre d'exemple de forme (cercle, triangle) dans la documentation de ESPHome.
Exemple de code pour afficher :

display:
- platform: waveshare_epaper
id: epaper_display
model: 7.30in-e
cs_pin: GPIO10
dc_pin: GPIO11
reset_pin:
number: GPIO12
inverted: false
busy_pin:
number: GPIO13
inverted: true
update_interval: never
lambda: |-
const auto BLACK = Color(0, 0, 0, 0);
const auto RED = Color(255, 0, 0, 0);
const auto GREEN = Color(0, 255, 0, 0);
const auto BLUE = Color(0, 0, 255, 0);
const auto YELLOW = Color(255, 255, 0, 0);
const auto WHITE = Color(255, 255, 255);
// now_weather_temperature
it.printf(435, 192, id(font_weather_icon), RED, TextAlign::TOP_CENTER, "\U000F10C2");
it.printf(450, 190, id(small_font), RED, TextAlign::TOP_LEFT, "%.1f°C", id(now_weather_temperature).state);
// now_weather_humidity
it.printf(435, 222, id(font_weather_icon), BLUE, TextAlign::TOP_CENTER, "\U000F058E");
it.printf(450, 220, id(small_font), BLUE, TextAlign::TOP_LEFT, "%.0f%%", id(now_weather_humidity).state);
// now_weather_wind_speed
it.printf(435, 252, id(font_weather_icon), GREEN, TextAlign::TOP_CENTER, "\U000F059D");
it.printf(450, 250, id(small_font), GREEN, TextAlign::TOP_LEFT, "%.0fkm/h", id(now_weather_wind_speed).state);
// now_weather_wind_bearing
it.printf(435, 282, id(font_weather_icon), GREEN, TextAlign::TOP_CENTER, "\U000F15FA");
it.printf(450, 280, id(small_font), GREEN, TextAlign::TOP_LEFT, "%s", id(now_weather_wind_bearing).state.c_str());
Afficher un text_sensor
lambda: |-
it.printf(404, 40, id(mid_font), BLUE, TextAlign::TOP_LEFT, "à %s", id(sun_sunset).state.c_str());
- ip.printf = pour écrire une valeur
- 404 = position X
- 40 = positon Y
- id(mid_font) = la police a utilisé
- BLUE = la couleur du texte
- TextAlign = l'alignement du texte
- "à %s" = permet l'ajout d'un texte avant la valeur d'un text_sensor
%s est pour afficher une valeur en text - id(sun_sunset).state.c_str() = le ID d'une entité text_sensor à utiliser pour la valeur a affiché
Exemple de code pour afficher :

display:
- platform: waveshare_epaper
id: epaper_display
model: 7.30in-e
cs_pin: GPIO10
dc_pin: GPIO11
reset_pin:
number: GPIO12
inverted: false
busy_pin:
number: GPIO13
inverted: true
update_interval: never
lambda: |-
const auto BLACK = Color(0, 0, 0, 0);
const auto RED = Color(255, 0, 0, 0);
const auto GREEN = Color(0, 255, 0, 0);
const auto BLUE = Color(0, 0, 255, 0);
const auto YELLOW = Color(255, 255, 0, 0);
const auto WHITE = Color(255, 255, 255);
// Soleil
it.printf(379, 8, id(icon_sun), BLUE, TextAlign::TOP_CENTER, "\U000F059C");
it.printf(404, 5, id(mid_font), BLUE, TextAlign::TOP_LEFT, "à %s", id(sun_sunrise).state.c_str());
Temps de rafraichissement des écrans
reTerminal E1001

reTerminal E1002

Conclusion
Ces deux modèles apportent une excellente combinaison entre longue autonomie, flexibilité logicielle et qualité matérielle. Le E1001 est parfait pour les contenus sobres, tandis que le E1002 délivre un rendu esthétique plus attrayant grâce à la couleur. Mais qui sera dépourvu d'un rafraîchissement d'écran lent.
Les plus :
✔️ Bonne qualité du produit
✔️ Tout en un (ESP32, Écran e-paper, Batterie, Boitier)
✔️ Capteur de température et Humidité SHT40
✔️ Autonomie de la batterie pouvant aller jusqu'à 3 mois d'utilisation (avec 4 rafraichissements par jour)
✔️ Compatible ESPHome
✔️ Accroche murale ou avec support à l'horizontal.
Les moins :
❌ Rafraîchissement de l'écran lent sur le E1002 couleur, mais plus rapide sur le E1001 monochrome.
❌ Pas d'accroche murale ou avec support à la verticale.
Merci à @hackdiy et @Nico.g2, pour leur aide sur le code du firmware ESPHome.
Nous tenons à remercier Seeed Studio, qui a gracieusement offert ce matériel.
Notre unique volonté est de partager, après test et intégration avec HA, nos avis sur du matériel nous semblant intéressant pour la communauté. Il est important de souligner que HACF reste totalement libre de sa ligne éditoriale et les auteurs de leurs propos.
Les médias HACF ont cependant des liens affiliés nous permettant de compléter nos revenus liés aux adhésions et dons, ce qui nous permet de mener nos différentes actions (voir https://www.hacf.fr/association-hacf/#financement)