Skip to Content

Interrupteur minuterie numérique avec affichage CrowPanel ESP32

Interrupteur minuterie numérique avec affichage CrowPanel ESP32

Dans ce tutoriel, vous apprendrez comment construire un interrupteur minuterie numérique avec le CrowPanel 3.5″ ESP32 Display. Nous allons synchroniser l’horloge interne avec un fournisseur de temps internet pour la garder précise et utiliser la bibliothèque TFT_eSPI pour créer une interface utilisateur agréable qui facilite la programmation des horaires.

Un interrupteur minuterie numérique est un appareil électronique qui allume ou éteint automatiquement d’autres appareils à des heures spécifiques définies par l’utilisateur. Il dispose généralement d’un affichage numérique pour programmer les heures d’activation/désactivation. Il peut être utilisé pour contrôler des lumières, des appareils électroménagers et d’autres dispositifs électriques à des heures prédéfinies. Par exemple, vous pouvez l’utiliser pour allumer les lumières de votre maison au coucher du soleil et les éteindre à l’heure du coucher, ou pour programmer votre arroseur afin d’arroser votre pelouse, puis l’éteindre après une durée spécifique pour éviter un arrosage excessif.

Démonstration de l’interrupteur minuterie numérique

L’interrupteur minuterie numérique que vous allez construire dans ce tutoriel sera meilleur que la plupart des interrupteurs minuterie numériques commerciaux. Son heure sera toujours précise et il ne sera pas nécessaire de régler l’heure pour l’heure d’été. De plus, grâce à son grand écran, il sera facile de définir ou de modifier les horaires.

Pièces requises

Pour ce projet, vous aurez besoin du CrowPanel 3.5″ ESP32 Display d’ELECROW et de l’IDE Arduino. Le panneau est livré avec un câble USB et un câble DuPont 4 broches, vous n’aurez donc pas besoin de câbles supplémentaires.

CrowPanel 3.5″ ESP32 Display

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Caractéristiques du CrowPanel 3.5″ ESP32 Display

Le CrowPanel 3.5″ ESP32 Display par ELECROW est un écran tactile résistif avec un affichage TFT de résolution 480*320 et un ESP32-WROVER-B intégré comme processeur de contrôle. Vous pouvez obtenir l’écran avec un boîtier acrylique élégant (voir photo ci-dessous), ce qui signifie que vous n’avez pas besoin de fabriquer un boîtier vous-même.

CrowPanel 3.5" ESP32 Display with Housing
CrowPanel 3.5″ ESP32 Display avec boîtier (source)

De plus, la carte est équipée d’un emplacement pour carte TF, d’une interface UART, d’une interface I2C, d’une interface haut-parleur, d’un connecteur batterie avec capacité de charge et d’un petit port GPIO avec deux broches GPIO. Voir le brochage ci-dessous :

Pinout of CrowPanel 3.5" ESP32 Display
Brochage du CrowPanel 3.5″ ESP32 Display

Le tableau suivant liste les broches GPIO assignées aux trois interfaces IO.

GPIO_DIO25 ; IO32
UARTRX(IO16) ; TX(IO17)
I2CSDA(IO22) ; SCL(IO21)

La carte peut être alimentée via le port USB (5V, 2A) ou en connectant une batterie LiPo standard 3,7V au connecteur BAT. Faites juste attention à la polarité du connecteur et de la batterie. Si le câble USB et la batterie sont connectés en même temps, la carte chargera la batterie (courant de charge maximal de 500mA).

Vous pouvez programmer la carte avec divers environnements de développement tels que Arduino IDE, Espressif IDF, Lua RTOS et Micro Python, et elle est compatible avec le LVGL graphics library. Cependant, dans ce tutoriel, nous utiliserons l’IDE Arduino et le TFT_eSPI graphics library.

Créer la structure du projet

Avant d’entrer dans les détails, créons la structure du projet et la configuration pour le TFT_eSPI library.

Ouvrez votre IDE Arduino et créez un projet « digital_timer_switch » puis enregistrez-le (Enregistrer sous …). Cela créera un dossier « digital_timer_switch » contenant le fichier « digital_timer_switch.ino« . Dans ce dossier, créez un autre fichier nommé « datestrs.h » et un troisième nommé « tft_setup.h« . Votre dossier de projet devrait ressembler à ceci

Structure du dossier du projet

et dans votre IDE Arduino, vous devriez maintenant avoir trois onglets nommés « digital_timer_switch.ino« , « datestrs.h » et « tft_setup.h« .

Arduino IDE with three Tabs
IDE Arduino avec trois onglets

Cliquez sur l’onglet « tft_setup.h » pour ouvrir le fichier et copiez-y le code suivant :

// tft_setup.h
// makerguides.com
#define ILI9488_DRIVER
#define TFT_HEIGHT  480
#define TFT_WIDTH   320 

#define TFT_BACKLIGHT_ON HIGH
#define TFT_BL   27 
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS   15
#define TFT_DC    2 
#define TFT_RST  -1
#define TOUCH_CS 33

#define SPI_FREQUENCY        27000000
#define SPI_TOUCH_FREQUENCY   2500000
#define SPI_READ_FREQUENCY   16000000

#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

Ce code indique au TFT_eSPI library quel affichage nous utilisons. Plus précisément, nous informons la bibliothèque des dimensions de l’écran (TFT_WIDTH, TFT_WIDTH), de son pilote d’affichage (ILI9488_DRIVER), des broches SPI utilisées pour le contrôler et des polices à charger. Sans ces réglages, vous ne pourrez rien afficher à l’écran.

Pour plus d’informations détaillées, consultez le tutoriel CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Nous y expliquons comment configurer l’écran 2,8″. Mais les étapes et descriptions s’appliquent de la même manière à l’écran 3,5″. Seuls les paramètres de dimensions et de pilote de l’écran diffèrent.

Calibration de l’écran tactile

Le CrowPanel 3.5″ Display est équipé d’un écran tactile résistif que vous devez calibrer avant de pouvoir l’utiliser avec la bibliothèque TFT_eSPI. Copiez le code ci-dessous dans le fichier « digital_timer_switch.ino« , compilez-le et téléversez-le sur le CrowPanel 3.5 » Display.

#include "tft_setup.h"
#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();

void setup() {
  Serial.begin(115200);
  tft.begin();
  tft.setRotation(0);
}

void loop() {
  uint16_t cal[5];
  tft.fillScreen(TFT_BLACK);
  tft.setCursor(20, 0);
  tft.setTextFont(2);
  tft.setTextSize(1);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.print("Touch corners ... ");
  tft.calibrateTouch(cal, TFT_MAGENTA, TFT_BLACK, 15);
  tft.println("done.");

  Serial.printf("cal: {%d, %d, %d, %d, %d}\n",
                cal[0], cal[1], cal[2], cal[3], cal[4]);
  delay(10000);
}

Lorsque le code s’exécute, l’écran affiche une flèche et vous demande de toucher le coin vers lequel elle pointe. Cela se répète pour les trois autres coins. Utilisez le petit stylet fourni avec l’écran pour cela et essayez de toucher les coins aussi précisément que possible.

Calibration of touch screen
Calibration de l’écran tactile

À la fin du processus de calibration, le code affiche les 5 paramètres de calibration (coordonnées des coins et orientation de l’écran) dans le moniteur série. Vous devriez voir quelque chose comme ceci :

cal: { 260, 3518, 270, 3629, 4 }

Copiez ces paramètres quelque part, car vous en aurez besoin dans le code de l’interrupteur minuterie numérique. La calibration se répète toutes les 10 secondes, vous pouvez donc faire plusieurs essais pour obtenir les paramètres les plus précis.

Pour plus d’informations détaillées sur le processus de calibration, consultez le tutoriel CrowPanel 2.8″ ESP32 Display : Easy Setup Guide.

Code pour l’interrupteur minuterie numérique

Dans cette section, nous allons écrire le code complet pour l’interrupteur minuterie numérique. Commencez par copier le code suivant dans le fichier « datestrs.h » qui se trouve dans votre dossier de projet digital_timer_switch.

// datestrs.h
const char *DAYSTR[] = {
    "Su",
    "Mo",
    "Tu",
    "We",
    "Th",
    "Fr",
    "Sa"
};

const char *MONTHSTR[] = {
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec"
};

Ensuite, copiez tout le code ci-dessous dans le fichier « digital_timer_switch.ino » et remplacez le code de calibration qui s’y trouve.

C’est le code principal. Il permet aux utilisateurs de définir des alarmes basées sur un planning hebdomadaire, de voir l’heure actuelle et d’activer ou désactiver les alarmes. L’affichage se met à jour en temps réel, et l’utilisateur peut interagir via des boutons tactiles. Lisez-le rapidement, puis nous discuterons des détails du code.

// digital_timer_switch.ino
#include "tft_setup.h"
#include "TFT_eSPI.h"
#include "TFT_eWidget.h"
#include "stdarg.h"
#include "WiFi.h"
#include "esp_sntp.h"
#include "datestrs.h"

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* SSID = "SSID";
const char* PWD = "PASSWORD";

const int rs = 24, cs = 7;
const int mx = 60, my = 40;
const int gw = TFT_WIDTH - mx - 30;
const int gh = TFT_HEIGHT - my - 50;
const int cw = gw / cs;
const int ch = gh / rs;

const int alarmPin = 25;

bool schedule[rs][cs];
bool isTimeView = true;
bool isAlarmOn = false;
uint16_t cal[5] = { 260, 3518, 270, 3629, 4 };

TFT_eSPI tft = TFT_eSPI();
ButtonWidget btnView = ButtonWidget(&tft);
ButtonWidget btnAlarm = ButtonWidget(&tft);
ButtonWidget* btns[] = { &btnView, &btnAlarm };


int c2x(int c) {
  return mx + gw * c / cs;
}

int r2y(int r) {
  return my + gh * r / rs;
}

int x2c(int x) {
  return map(x - mx, 0, gw, 0, cs);
}

int y2r(int y) {
  return map(y - my, 0, gh, 0, rs);
}

void drawLabels() {
  tft.setTextFont(1);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  for (int r = 0; r < rs; r++) {
    tft.setCursor(24, r2y(r));
    tft.printf("%02d", r);
  }
  for (int c = 0; c < cs; c++) {
    tft.setCursor(c2x(c), 15);
    tft.print(DAYSTR[c]);
  }
}

void drawSlot(int r, int c) {
  uint32_t color = schedule[r][c] ? TFT_WHITE : TFT_BLACK;
  tft.fillRect(c2x(c) + 1, r2y(r) + 1, cw - 2, ch - 2, color);
}

void drawSchedule() {
  for (int r = 0; r < rs; r++) {
    for (int c = 0; c < cs; c++) {
      tft.drawRect(c2x(c), r2y(r), cw, ch, TFT_DARKGREY);
      drawSlot(r, c);
    }
  }
}

void selectSlot(int x, int y) {
  int c = x2c(x);
  int r = y2r(y);
  if (c < 0 || c >= cs || r < 0 || r >= rs) return;
  schedule[r][c] = !schedule[r][c];
  drawSlot(r, c);
}

void initWiFi() {
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }
}

void initSNTP() {
  sntp_set_sync_interval(15 * 60 * 1000UL);  // 15 minutes
  esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
  esp_sntp_setservername(0, "pool.ntp.org");
  esp_sntp_init();
  setTimezone();
}

void setTimezone() {
  setenv("TZ", TIMEZONE, 1);
  tzset();
}

void drawTime() {
  static char buff[50];
  static struct tm t;
  getLocalTime(&t);

  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(1);

  sprintf(buff, " %2d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
  tft.drawString(buff, tft.width() / 2, 130, 7);

  sprintf(buff, "  %s, %s %d, %d ",
          DAYSTR[t.tm_wday], MONTHSTR[t.tm_mon],
          t.tm_mday, t.tm_year + 1900);
  tft.drawString(buff, tft.width() / 2, 200, 4);
}

bool isOn() {
  static struct tm t;
  getLocalTime(&t);
  return schedule[t.tm_hour][t.tm_wday] && isAlarmOn;
}

void initDisplay() {
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTouch(cal);
}

void btnView_pressed(void) {
  if (btnView.justPressed()) {
    isTimeView = !btnView.getState();
    drawView();
    tft.setTextFont(4);
    tft.setTextSize(1);
    btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK, isTimeView ? "alarm" : "time");
  }
}

void btnAlarm_pressed(void) {
  if (btnAlarm.justPressed()) {
    isAlarmOn = !btnAlarm.getState();
    tft.setTextFont(4);
    tft.setTextSize(1);
    btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_DARKGREY, isAlarmOn ? "on" : "off");
  }
}

void initButtons() {
  uint16_t w = 100;
  uint16_t h = 50;
  uint16_t y = tft.height() - h + 14;
  uint16_t x = tft.width() / 2;
  tft.setTextFont(4);
  tft.setTextSize(1);

  btnView.initButtonUL(x - w - 10, y, w, h, TFT_DARKGREY, TFT_DARKGREY, TFT_BLACK, "alarm", 1);
  btnView.setPressAction(btnView_pressed);
  btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK);

  btnAlarm.initButtonUL(x + 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "off", 1);
  btnAlarm.setPressAction(btnAlarm_pressed);
  btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_BLACK);
}

void handleButtons() {
  tft.setTextFont(4);
  uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
  uint16_t x = 0, y = 0;
  bool touched = tft.getTouch(&x, &y);
  for (uint8_t b = 0; b < nBtns; b++) {
    if (touched) {
      if (btns[b]->contains(x, y)) {
        btns[b]->press(true);
        btns[b]->pressAction();
      }
    } else {
      btns[b]->press(false);
      btns[b]->releaseAction();
    }
  }
}

void drawView() {
  tft.fillScreen(TFT_BLACK);
  initButtons();
  if (isTimeView) {
    drawTime();
  } else {
    drawSchedule();
    drawLabels();
  }
}

void updateView() {
  static uint16_t x = 0, y = 0;
  if (isTimeView) {
    drawTime();
  } else {
    if (tft.getTouch(&x, &y)) {
      selectSlot(x, y);
    }
  }
}

void setup() {
  initWiFi();
  initSNTP();
  initDisplay();
  pinMode(alarmPin, OUTPUT);
  drawView();
}

void loop() {
  updateView();
  handleButtons();
  digitalWrite(alarmPin, isOn() ? HIGH : LOW);
  delay(100);
}

Comme vous pouvez le voir, c’est un code assez conséquent. La section suivante vous donne un aperçu rapide des fonctions et de leur rôle.

Aperçu des fonctions

  • drawLabels(): Dessine les étiquettes pour les lignes et colonnes sur l’écran.
  • drawSlot(int r, int c): Dessine un créneau horaire sur la grille du planning selon la ligne et la colonne fournies.
  • drawSchedule(): Dessine la grille complète du planning sur l’écran.
  • selectSlot(int x, int y): Permet aux utilisateurs de sélectionner un créneau horaire sur la grille en touchant l’écran.
  • initWiFi(): Initialise la connexion WiFi avec le SSID et le mot de passe fournis.
  • initSNTP(): Initialise le SNTP (Simple Network Time Protocol) pour la synchronisation de l’heure.
  • setTimezone(): Définit le fuseau horaire de l’appareil selon la constante fournie.
  • drawTime(): Affiche l’heure et la date actuelles sur l’écran.
  • isOn(): Vérifie si l’heure actuelle correspond à un créneau programmé et si l’alarme est activée.
  • initDisplay(): Initialise l’écran et configure la fonction tactile.
  • btnView_pressed() et btnAlarm_pressed(): Gèrent les événements de pression des boutons pour basculer entre la vue de l’heure et la vue du planning, et pour activer/désactiver l’alarme.
  • initButtons(): Initialise les boutons sur l’écran pour interagir avec le projet.
  • handleButtons(): Gère les actions de pression et de relâchement des boutons sur l’écran.
  • drawView(): Dessine soit la vue de l’heure, soit la vue du planning selon le mode actuel.
  • updateView(): Met à jour l’affichage selon les interactions utilisateur et le mode actuel.

Examinons de plus près ces fonctions et d’autres parties du code.

Inclusions de bibliothèques

Le code commence par inclure plusieurs bibliothèques nécessaires au fonctionnement de l’ESP32 et de l’écran. Nous avons besoin de bibliothèques pour l’écran TFT, la connexion Wi-Fi, la synchronisation de l’heure via un serveur SNTP, ainsi que les noms des mois et des jours (datestrsl.h)

#include "tft_setup.h"
#include "TFT_eSPI.h"
#include "TFT_eWidget.h"
#include "stdarg.h"
#include "WiFi.h"
#include "esp_sntp.h"
#include "datestrs.h"

Constantes et variables

Ensuite, nous définissons plusieurs constantes et variables utilisées dans tout le programme. Cela inclut les identifiants Wi-Fi, les dimensions de l’écran, la broche d’alarme et un tableau de planning.

const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* SSID = "SSID";
const char* PWD = "PASSWORD";

const int rs = 24, cs = 7;
const int alarmPin = 25;

bool schedule[rs][cs];
bool isTimeView = true;
bool isAlarmOn = false;

Vous devrez définir le SSID et le PWD pour votre WiFi et vous devrez probablement aussi modifier le TIMEZONE. La spécification de fuseau horaire ci-dessus « AEST-10AEDT,M10.1.0,M4.1.0/3 » correspond à l’Australie, soit l’heure normale de l’Est australien (AEST) avec ajustements pour l’heure d’été.

Les éléments de cette définition de fuseau horaire sont les suivants

  • AEST : Heure normale de l’Est australien
  • -10 : Décalage UTC de 10 heures en avance sur le Temps Universel Coordonné (UTC)
  • AEDT : Heure d’été de l’Est australien
  • M10.1.0 : Passage à l’heure d’été le premier dimanche d’octobre
  • M4.1.0/3 : Retour à l’heure normale le premier dimanche d’avril, avec un décalage de 3 heures par rapport à l’UTC.

Pour d’autres définitions de fuseaux horaires, consultez le Posix Timezones Database. Il suffit de copier-coller la chaîne que vous y trouverez et de modifier la constante TIMEZONE en conséquence.

Fonctions de conversion de coordonnées

Les fonctions suivantes convertissent entre les systèmes de coordonnées pour l’écran :

int c2x(int c) { ... }
int r2y(int r) { ... }
int x2c(int x) { ... }
int y2r(int y) { ... }

c2x() et r2x() mappent les colonnes et lignes (c,r) de la grille du planning aux coordonnées écran (x,y), tandis que x2c() et y2r() effectuent l’opération inverse. Elles sont nécessaires pour convertir un événement tactile en réservation d’un créneau horaire, par exemple.

Fonctions de dessin

Le code inclut plusieurs fonctions pour dessiner des éléments à l’écran :

drawLabels

Cette fonction dessine les étiquettes pour les lignes (heures) et colonnes (jours de la semaine) du planning.

void drawLabels() {
  tft.setTextFont(1);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  for (int r = 0; r < rs; r++) {
    tft.setCursor(24, r2y(r));
    tft.printf("%02d", r);
  }
  for (int c = 0; c < cs; c++) {
    tft.setCursor(c2x(c), 15);
    tft.print(DAYSTR[c]);
  }
}

Hour and Day labels for Schedule
Étiquettes des heures et jours pour le planning

drawSlot

Cette fonction dessine un créneau horaire unique dans le planning, le remplissant en blanc si l’alarme est activée pour ce créneau.

void drawSlot(int r, int c) {
  uint32_t color = schedule[r][c] ? TFT_WHITE : TFT_BLACK;
  tft.fillRect(c2x(c) + 1, r2y(r) + 1, cw - 2, ch - 2, color);
}
A hour time slot
Un créneau horaire

drawSchedule

Cette fonction dessine la grille complète du planning, appelant drawSlot() pour chaque créneau.

void drawSchedule() {
  for (int r = 0; r < rs; r++) {
    for (int c = 0; c < cs; c++) {
      tft.drawRect(c2x(c), r2y(r), cw, ch, TFT_DARKGREY);
      drawSlot(r, c);
    }
  }
}
Hourly grid for Schedule
Grille horaire pour le planning

selectSlot

Cette fonction bascule l’état et la couleur d’un créneau horaire lorsque l’utilisateur le touche sur l’écran.

void selectSlot(int x, int y) {
  int c = x2c(x);
  int r = y2r(y);
  if (c < 0 || c >= cs || r < 0 || r >= rs) return;
  schedule[r][c] = !schedule[r][c];
  drawSlot(r, c);
}

Initialisation Wi-Fi et SNTP

Les fonctions suivantes gèrent l’initialisation du Wi-Fi et du SNTP (Simple Network Time Protocol) :

initWiFi

Cette fonction connecte l’ESP32 au réseau Wi-Fi spécifié.

void initWiFi() {
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }
}

initSNTP

Cette fonction configure le service SNTP pour synchroniser l’heure. Elle se connecte essentiellement à un serveur internet fournissant une heure très précise et synchronise l’horloge interne de l’ESP32 en conséquence.

void initSNTP() {
  sntp_set_sync_interval(15 * 60 * 1000UL);  // 15 minutes
  esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
  esp_sntp_setservername(0, "pool.ntp.org");
  esp_sntp_init();
  setTimezone();
}

Tant qu’il y a une connexion Wi-Fi et internet, cela garantit que l’horloge de notre interrupteur minuterie numérique est toujours précise et s’ajuste automatiquement à l’heure d’été. Pour plus de détails, consultez le tutoriel How to synchronize ESP32 clock with SNTP server.

setTimezone

Cette fonction définit le fuseau horaire pour l’application. Vous devez vous assurer qu’elle s’exécute à chaque démarrage de l’ESP32, sinon votre horloge sera décalée.

void setTimezone() {
  setenv("TZ", TIMEZONE, 1);
  tzset();
}

Fonction d’affichage de l’heure

La fonction drawTime() récupère l’heure locale actuelle, synchronisée via SNTP, et affiche l’heure et la date à l’écran.

void drawTime() {
  static char buff[50];
  static struct tm t;
  getLocalTime(&t);

  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(1);

  sprintf(buff, " %2d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
  tft.drawString(buff, tft.width() / 2, 130, 7);

  sprintf(buff, "  %s, %s %d, %d ",
          DAYSTR[t.tm_wday], MONTHSTR[t.tm_mon],
          t.tm_mday, t.tm_year + 1900);
  tft.drawString(buff, tft.width() / 2, 200, 4);
}

La fonction utilise les constantes pour les noms des jours et des mois stockées dans le fichier datestrs.h.


Time and Date shown on Display
Heure et date affichées à l’écran

Si vous avez besoin de plus de détails, consultez les tutoriels Digital Clock with CrowPanel 3.5″ ESP32 Display et Digital Clock on e-Paper Display où nous implémentons également une horloge numérique.

Fonction de vérification d’alarme

La fonction isOn() vérifie si l’alarme doit être déclenchée en fonction de l’heure actuelle et du planning. Si la cellule de la grille schedule[t.tm_hour][t.tm_wday] contient true et que l’interrupteur d’alarme global est activé (isAlarmOn), la fonction retourne true, sinon false.

bool isOn() {
  static struct tm t;
  getLocalTime(&t);
  return schedule[t.tm_hour][t.tm_wday] && isAlarmOn;
}

Initialisation de l’affichage

La fonction initDisplay() initialise l’écran en réglant la rotation, en remplissant l’écran en noir, et en configurant les paramètres de calibration de l’écran tactile.

void initDisplay() {
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTouch(cal);
}

Fonctions des boutons

Le code inclut des fonctions pour gérer les pressions de boutons permettant de basculer entre la vue de l’heure et les réglages d’alarme :

btnView_pressed

Cette fonction bascule la vue entre l’heure actuelle et le planning d’alarme.

void btnView_pressed(void) {
  if (btnView.justPressed()) {
    isTimeView = !btnView.getState();
    drawView();
    tft.setTextFont(4);
    tft.setTextSize(1);
    btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK, isTimeView ? "alarm" : "time");
  }
}
Toggling time and schedule view
Basculement entre la vue heure et planning

btnAlarm_pressed

Cette fonction active ou désactive l’interrupteur d’alarme global. Si l’interrupteur est désactivé, le planning d’alarme est désactivé.

void btnAlarm_pressed(void) {
  if (btnAlarm.justPressed()) {
    isAlarmOn = !btnAlarm.getState();
    tft.setTextFont(4);
    tft.setTextSize(1);
    btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_DARKGREY, isAlarmOn ? "on" : "off");
  }
}
Basculement de l’interrupteur d’alarme

Initialisation des boutons

La fonction initButtons() initialise les boutons sur l’écran.

void initButtons() {
  uint16_t w = 100;
  uint16_t h = 50;
  uint16_t y = tft.height() - h + 14;
  uint16_t x = tft.width() / 2;
  tft.setTextFont(4);
  tft.setTextSize(1);

  btnView.initButtonUL(x - w - 10, y, w, h, TFT_DARKGREY, TFT_DARKGREY, TFT_BLACK, "alarm", 1);
  btnView.setPressAction(btnView_pressed);
  btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK);

  btnAlarm.initButtonUL(x + 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "off", 1);
  btnAlarm.setPressAction(btnAlarm_pressed);
  btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_BLACK);
}
Buttons for View and Alarm
Boutons pour la vue et l’alarme

Gestion des boutons

La fonction handleButtons() vérifie les pressions de boutons et exécute les actions correspondantes.

void handleButtons() {
  tft.setTextFont(4);
  uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
  uint16_t x = 0, y = 0;
  bool touched = tft.getTouch(&x, &y);
  for (uint8_t b = 0; b < nBtns; b++) {
    if (touched) {
      if (btns[b]->contains(x, y)) {
        btns[b]->press(true);
        btns[b]->pressAction();
      }
    } else {
      btns[b]->press(false);
      btns[b]->releaseAction();
    }
  }
}

Fonction de dessin de la vue

La fonction drawView() met à jour l’affichage selon le mode de vue actuel (heure ou planning).

void drawView() {
  tft.fillScreen(TFT_BLACK);
  initButtons();
  if (isTimeView) {
    drawTime();
  } else {
    drawSchedule();
    drawLabels();
  }
}

Fonction de mise à jour de la vue

La fonction updateView() rafraîchit l’affichage en fonction des interactions utilisateur.

void updateView() {
  static uint16_t x = 0, y = 0;
  if (isTimeView) {
    drawTime();
  } else {
    if (tft.getTouch(&x, &y)) {
      selectSlot(x, y);
    }
  }
}

Fonction setup

La fonction setup() initialise le Wi-Fi, le SNTP, l’affichage, et configure GPIO25 en mode sortie. Nous connecterons une LED ou un relais à cette broche et les contrôlerons via le planning d’alarme.

void setup() {
  initWiFi();
  initSNTP();
  initDisplay();
  pinMode(alarmPin, OUTPUT);
  drawView();
}

Fonction loop

Enfin, la fonction loop() met continuellement à jour la vue, gère les pressions de boutons et contrôle la broche d’alarme selon le planning.

void loop() {
  updateView();
  handleButtons();
  digitalWrite(alarmPin, isOn() ? HIGH : LOW);
  delay(100);
}

Et voici le code complet pour un interrupteur minuterie numérique. Dans les deux sections suivantes, je vous montre comment connecter une LED ou un relais au CrowPanel Display, que nous pourrons ensuite contrôler via le planning d’alarme.

Connexion d’une LED à l’interrupteur minuterie numérique

Pour tester que le code de notre interrupteur minuterie numérique fonctionne, nous connectons une LED à GPIO25 comme suit :

LED connected to CrowPanel 3.5" ESP32 Display
LED connectée au CrowPanel 3.5″ ESP32 Display

Assurez-vous que la broche courte (cathode) de la LED est connectée à la masse (fil noir) et la broche longue via une résistance à GPIO25 (fil jaune). Le CrowPanel est livré avec un connecteur codé par couleur, donc si vous suivez les couleurs, vous ne pouvez pas vous tromper. La photo suivante montre le câblage réel avec les câbles codés par couleur :

Wiring of LED with Crowpanel Display
Câblage de la LED avec le CrowPanel Display

Pour tester l’interrupteur minuterie numérique, lisez l’heure et la date actuelles, par exemple 12:37:00 un mercredi, puis réglez le créneau horaire correspondant dans le planning d’alarme :

Schedule set for 12:00-13:00 on Wednesday
Planning réglé pour 12:00-13:00 le mercredi

Si le bouton d’alarme global est activé, la LED devrait s’allumer. Vous pouvez ensuite l’éteindre en basculant le bouton d’alarme global ou en modifiant le planning.

Connexion d’un relais à l’interrupteur minuterie numérique

Si vous souhaitez contrôler les lumières de votre maison ou la pompe de votre arroseur, vous aurez besoin d’un relais. Le schéma suivant montre comment le câbler :

Relais connecté au CrowPanel 3.5″ ESP32 Display

VCC (câble rouge) et GND (câble noir) sont connectés aux broches correspondantes d’un module relais. GPIO25 est connecté à la broche d’entrée (câble jaune) du relais.

L’appareil haute tension et fort courant que vous souhaitez contrôler est connecté aux bornes COM et NC (normalement ouvertes) du module relais.Soyez très prudent lorsque vous manipulez des tensions supérieures à 50V ! Vous devez vous assurer que le module relais est adapté à la tension et au courant que vous souhaitez commuter. Les modules relais typiques (AITRIP, HiLetgo) peuvent commuter 220V à 4A jusqu’à 10A.

La photo ci-dessous montre le câblage du module relais avec le CrowPanel Display :

Wiring of Relay with Crowpanel Display
Câblage du relais avec le CrowPanel Display

Conclusion

Dans ce tutoriel, vous avez appris comment construire un interrupteur minuterie numérique avec le CrowPanel 3.5″ ESP32 Display. Comparé à de nombreux produits commerciaux, notre interrupteur minuterie numérique a une heure toujours précise et, grâce à son grand écran, il est très facile de définir et modifier rapidement les plannings.

De nombreuses extensions sont possibles pour ce projet. Vous pourriez vouloir définir des plannings avec une résolution en minutes ou secondes. Ou peut-être programmer un planning pour un mois ou une année entière.

Au lieu de connecter un relais, vous pourriez aussi utiliser Bluetooth, MQTT ou d’autres protocoles pour contrôler des appareils sans fil. Le code ci-dessus vous fournit un cadre pour cela.

Si vous avez des questions, n’hésitez pas à les laisser dans la section commentaires.

Bon bricolage 😉

Liens

Voici quelques liens que j’ai trouvés utiles pour rédiger ce post.