Seeed Studio vient de présenter le reTerminal E1002, un appareil prêt à l’emploi doté d’un boîtier métallique robuste et d’un écran e-paper couleur de 7,3″ affichant en 800×480 pixels. Basé sur un microcontrôleur ESP32-S3, il intègre plusieurs fonctionnalités matérielles : trois boutons, un buzzer, une LED de statut (en plus de la LED d’alimentation), un capteur de température et de pression, un microphone ainsi qu’un lecteur de carte MicroSD. L’ensemble est alimenté par une batterie interne de 2000 mAh offrant jusqu’à trois mois d’autonomie.
Cet appareil est particulièrement bien adapté pour l’affichage des données issues du capteur Zigbee que j’ai développé dans mon tutoriel Créer un capteur de température Zigbee sur batterie avec un ESP32C6 et que j’ai intégré à Home Assistant.
Le reTerminal E1002 peut être configuré et personnalisé en adoptant trois approches distinctes :
- Approche 1 : Visuelle (drag & drop), avec SenseCraft HMI
- Approche 2 : Par configuration déclarative (fichiers YAML), comme dans mon tutoriel Intégrer un reTerminal E1002 dans Home Assistant avec ESPHome
- Approche 3 : Par développement codé (IDE Arduino, C/C++), que je vais détailler dans cet article
C’est cette dernière approche que je vais utiliser dans cet article, car elle a l’avantage de ne pas vous imposer les limites des deux autres approches.
Configuration de l’IDE Arduino pour le reTerminal E1002
Il faut tout d’abord intégrer le support pour les microcontrôleurs ESP32 dans l’IDE Arduino, en ajoutant l’URL suivante dans le champ Additional Boards Manager URLs du menu File > Preferences :
https://espressif.github.io/arduino-esp32/package_esp32_index.json
Il faut ensuite installer le package ESP32, via le menu Tools > Board > Boards Manager, en recherchant “esp32” et installant le package esp32 by Espressif Systems (disponible sur github espressif).
Téléchargez ensuite la librairie GxEPD2 dans son format ZIP :

Et l’ajouter à l’IDE Arduino via le menu Sketch > Include Library > Add .ZIP Library…

Et pour finir, installer cette librairie GxEPD2 by Jean-Marc Zingg via le menu Tools > Manage libraries.
Premier programme “Hello World”
Voici le code qui affiche le célèbre “Hello World” sur l’écran du reTerminal E1002, qu’il faut installer sur l’appareil après avoir sélectionné la carte XIAO_ESP32S3 via le menu Tools > Board > ESP32 Arduino et choisi le port sur lequel le reTerminal est connecté.
#include <GxEPD2_7C.h>
#include <Fonts/FreeMonoBold12pt7b.h>
// Define ePaper SPI pins
#define EPD_SCK_PIN 7
#define EPD_MOSI_PIN 9
#define EPD_CS_PIN 10
#define EPD_DC_PIN 11
#define EPD_RES_PIN 12
#define EPD_BUSY_PIN 13
#define GxEPD2_DISPLAY_CLASS GxEPD2_7C
#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01 // 7.3'' Color driver
#define MAX_DISPLAY_BUFFER_SIZE 16000
#define MAX_HEIGHT(EPD) \
(EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) \
? EPD::HEIGHT \
: MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
// Initialize display object
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)>
display(GxEPD2_DRIVER_CLASS(/*CS=*/EPD_CS_PIN, /*DC=*/EPD_DC_PIN,
/*RST=*/EPD_RES_PIN, /*BUSY=*/EPD_BUSY_PIN));
SPIClass hspi(HSPI);
void helloWorld()
{
display.setRotation(0);
display.setFont(&FreeMonoBold12pt7b);
display.setTextColor(GxEPD_RED);
display.setFullWindow();
display.firstPage();
do
{
display.fillScreen(GxEPD_WHITE);
display.setCursor(100, 100);
display.print("Hello World!");
}
while (display.nextPage());
}
void setup()
{
pinMode(EPD_RES_PIN, OUTPUT);
pinMode(EPD_DC_PIN, OUTPUT);
pinMode(EPD_CS_PIN, OUTPUT);
// Initialize SPI
hspi.begin(EPD_SCK_PIN, -1, EPD_MOSI_PIN, -1);
display.epd2.selectSPI(hspi, SPISettings(2000000, MSBFIRST, SPI_MODE0));
// Initialize display
display.init(0);
helloWorld();
display.hibernate();
}
void loop() {};
Récupération d’une donnée depuis le serveur Home Assistant
L’objectif de ce tutoriel est d’afficher sur le reTerminal E1002 la température du capteur qui est disponible dans le serveur Home Assistant. Cette donnée sera récupérée via l’API RESTful du serveur Home Assistant disponible via l’URL http://ADRESSE_IP_HA:8123/api/. Cette API accepte et renvoie uniquement les objets codés en JSON. Nous utiliserons pour cela la librairie Arduinojson by Benoit Blanchon, que nous installons depuis le menu Tools > Manage Libraries de l’IDE Arduino.
Il faut récupérer, depuis la page web de Home Assistant, l’ID de l’entité que nous souhaitons afficher sur le reTerminal. Ici par exemple, la température du capteur correspond à l’entité dont l’ID est : sensor.tutoduino_esp32c6tempsensor_temperature.

Pour pouvoir récupérer des données de Home Assistant, notre programme va avoir besoin d’un jeton (Token) de Home Assistant afin de pouvoir l’interroger via son API. Il faut le faire à partir de la page web de votre serveur Home Assistant, en allant sur la page de votre profile et en cliquant sur le bouton Créer un jeton depuis le menu Sécurité.

Une fois le jeton créé, il suffit de l’indiquer dans votre programme sous l’IDE Arduino. Je recommande toujours de stocker vos secrets (mot de passe wifi, jeton home assistant…) dans un fichier secret.h qui sera inclus dans votre programme C. Cela évite par exemple de les faire fuiter sur Github après un copier-coller malencontreux…

Voici un exemple de programme qui va récupérer via l’API Home Assistant la température de notre capteur et l’afficher sur l’écran du reTerminal E1002. Notez que la lecture sur le serveur Home Assistant se fait toutes les 10 minutes et qu’entre deux lectures, le reTerminal se met en veille profonde afin d’économiser sa batterie. Il est cependant possible de réveiller à tout moment le reTerminal en cliquant sur son bouton vert.
/**
* @file TemperatureDisplay.ino
* @brief Fetches temperature data from a Home Assistant server and displays it on a reTerminal E1002 e-paper screen.
* The device can enter deep sleep and wake up via a button press or timer.
*/
#include <WiFi.h> // Wi-Fi library for ESP32
#include <HTTPClient.h> // HTTP client for making requests
#include <ArduinoJson.h> // JSON parsing library
#include <GxEPD2_7C.h> // E-paper display library for 7-color displays
#include <Fonts/FreeMonoBold12pt7b.h> // Custom font for the display
#include "secrets.h" // Custom file containing sensitive data (Wi-Fi credentials, API tokens)
// --- Pin Definitions ---
// Serial1 communication pins for debugging
#define SERIAL_RX 44
#define SERIAL_TX 43
// SPI pins for e-paper display communication
#define EPD_SCK_PIN 7 // SPI Clock pin
#define EPD_MOSI_PIN 9 // MOSI (Master Out Slave In) pin
#define EPD_CS_PIN 10 // Chip Select pin
#define EPD_DC_PIN 11 // Data/Command pin
#define EPD_RES_PIN 12 // Reset pin
#define EPD_BUSY_PIN 13 // Busy pin
// Button pin definitions according to the hardware schematic
#define GREEN_BUTTON 3 // KEY0 - GPIO3, connected to the green button
// --- E-paper Display Configuration ---
// Display class for 7.3" color e-paper
#define GxEPD2_DISPLAY_CLASS GxEPD2_7C
#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01
// Maximum display buffer size in bytes (limits memory usage)
#define MAX_DISPLAY_BUFFER_SIZE 16000
// Calculate the maximum display height based on the buffer size
#define MAX_HEIGHT(EPD) \
(EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE * 8) / EPD::WIDTH ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE * 8) / EPD::WIDTH)
// --- Device Initialization ---
// Initialize SPI interface for the display (HSPI = Host SPI)
SPIClass hspi(HSPI);
// Initialize the display object with the specified driver and buffer size
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)>
display(GxEPD2_DRIVER_CLASS(EPD_CS_PIN, EPD_DC_PIN, EPD_RES_PIN, EPD_BUSY_PIN));
/**
* @brief Displays the temperature on the e-paper screen.
* @param temperature A string representing the temperature to display.
*/
void displayTemperature(const char* temperature) {
Serial1.print("Temperature to display: ");
Serial1.println(temperature);
// Configure display settings
display.setRotation(0); // Set display orientation to default (0 degrees)
display.setFont(&FreeMonoBold12pt7b); // Set font to FreeMonoBold12pt7b
display.setTextColor(GxEPD_RED); // Set text color to red
display.setFullWindow(); // Use the full display window for rendering
// Start the display update process
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE); // Clear the screen with white background
display.setCursor(100, 100); // Set cursor position to (100, 100)
display.print("Temperature = ");
display.print(temperature); // Print the temperature value
display.print(" C"); // Print the degree symbol and 'C' for Celsius
} while (display.nextPage()); // Repeat until the display update is complete
}
/**
* @brief Fetches the temperature from the Home Assistant server.
* @return A dynamically allocated string containing the temperature or "Error" on failure.
* @note The caller is responsible for freeing the returned string using free().
*/
char* getTemperature() {
// Check if Wi-Fi is connected
if (WiFi.status() != WL_CONNECTED) {
Serial1.println("ERROR: Wi-Fi not connected.");
return strdup("Error"); // Return an error message
}
// Initialize HTTP client and configure the request
HTTPClient http;
http.begin(ha_url); // Set the target URL from secrets.h
http.addHeader("Authorization", "Bearer " + String(ha_token)); // Add authorization header
http.addHeader("Content-Type", "application/json"); // Set content type to JSON
// Send a GET request to the Home Assistant API
int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
Serial1.print("HTTP Error: ");
Serial1.println(httpCode);
Serial1.println(http.getString()); // Print the error response
http.end(); // Close the connection
return strdup("Error"); // Return an error message
}
// Read the server response
String payload = http.getString();
Serial1.println("Server response:");
Serial1.println(payload);
// Parse the JSON response
DynamicJsonDocument doc(1024); // Create a JSON document with a capacity of 1024 bytes
DeserializationError error = deserializeJson(doc, payload);
http.end(); // Close the HTTP connection
if (error) {
Serial1.print("JSON parsing error: ");
Serial1.println(error.c_str());
return strdup("Error"); // Return an error message if JSON parsing fails
}
// Check if the "state" field exists in the JSON response
if (!doc.containsKey("state")) {
Serial1.println("ERROR: 'state' field missing in JSON response.");
return strdup("Error"); // Return an error message if "state" is missing
}
const char* state = doc["state"]; // Extract the temperature value from the JSON
Serial1.print("Temperature = ");
Serial1.println(state);
// Return a dynamically allocated copy of the temperature string
return strdup(state);
}
/**
* @brief Prints the reason for waking up from deep sleep.
*/
void printWakeupReason() {
esp_sleep_wakeup_cause_t wakeupReason = esp_sleep_get_wakeup_cause();
switch (wakeupReason) {
case ESP_SLEEP_WAKEUP_EXT0:
Serial1.println("Wakeup caused by external signal (EXT0 - button press)");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial1.println("Wakeup caused by external signal (EXT1)");
break;
case ESP_SLEEP_WAKEUP_TIMER:
Serial1.println("Wakeup caused by timer");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial1.println("Wakeup caused by touchpad");
break;
default:
Serial1.printf("Wakeup not caused by deep sleep: %d\n", wakeupReason);
break;
}
}
/**
* @brief Initial system setup. Runs once at startup.
*/
void setup() {
// Initialize Serial1 for debugging
Serial1.begin(115200, SERIAL_8N1, SERIAL_RX, SERIAL_TX);
delay(1000); // Wait for serial port to initialize
Serial1.println("System starting...");
printWakeupReason(); // Print the reason for waking up
// Configure the green button as an input with pull-up resistor
pinMode(GREEN_BUTTON, INPUT_PULLUP);
// Enable wakeup from deep sleep when the button is pressed (LOW signal)
esp_sleep_enable_ext0_wakeup((gpio_num_t)GREEN_BUTTON, LOW);
// Configure display control pins as outputs
pinMode(EPD_RES_PIN, OUTPUT);
pinMode(EPD_DC_PIN, OUTPUT);
pinMode(EPD_CS_PIN, OUTPUT);
// Initialize SPI interface for the display
hspi.begin(EPD_SCK_PIN, -1, EPD_MOSI_PIN, -1);
display.epd2.selectSPI(hspi, SPISettings(2000000, MSBFIRST, SPI_MODE0));
// Connect to Wi-Fi
WiFi.begin(wifi_ssid, wifi_password);
Serial1.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial1.print(".");
}
Serial1.println("\nConnected to Wi-Fi");
// Initialize the e-paper display
display.init(0);
}
/**
* @brief Main loop. Runs repeatedly after setup().
*/
void loop() {
// Fetch the temperature from Home Assistant
char* temperature = getTemperature();
if (temperature != nullptr) {
displayTemperature(temperature); // Display the temperature
free(temperature); // Free the dynamically allocated memory
} else {
Serial1.println("ERROR: Failed to get temperature.");
displayTemperature("Error"); // Display an error message
}
// Put the display into hibernation to save power
display.hibernate();
delay(1000); // Wait for 1 seconds before entering deep sleep
// Configure deep sleep wakeup after 10 minutes
esp_sleep_enable_timer_wakeup(10 * 60 * 1000000); // 10 minutes in microseconds
esp_deep_sleep_start(); // Enter deep sleep mode
}
Un programme plus complet est disponible sur mon GitHub, il permet d’afficher les prévisions météo en plus de la température remontée par le capteur sous Home Assistant.