ESP32 Horloge à LED
Présentation
On se propose de réaliser une Horloge à LED.
Les besoins sont :
- Récupérer l'heure (avec une connectivité Internet c'est facile) en NTP sur pool.ntp.org par exemple.
- Afficher l'heure sur une matrice de LED basée sur un MAX7219.
- Il faut aussi se localiser car un serveur NTP donne l'heure TU (avant on disait GMT) il nous faut connaitre la "time zone" (le fuseau horaire) et l'état DST (Heure d'été / heure d'Hiver). Des API existent pour cela, Abstract API en est une.
- Comme cette API est implémentée en HTTPS il faudra également utilise la librairie https.
Montage et branchements
On se base sur la page déjà écrite pour brancher une carte "breakout" ESP32 et un module à base de MAX7219.
ATTENTION: Je refais ma remarque faite dans la page correspondante. Alimenter les 256 LEDs consomme pas mal (256x20 mA) > 5 A! L'alim USB branchée sur le port USB ne peut pas suivre. Branché sur le port USB A de mon laptop... ça a du mal, en revanche avec un câble µUSB - USBC branché sur le "dock" ca marche bien. |
Code
Code de test
On teste le montage avec le code suivant.
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4 // 4 blocks
#define CS_PIN 21
// create an instance of the MD_Parola class
MD_Parola ledMatrix = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
void setup() {
// Initialize the breakout buildin led
pinMode(2,OUTPUT);
digitalWrite(2,HIGH);
ledMatrix.begin(); // initialize the LED Matrix
ledMatrix.setIntensity(0); // set the brightness of the LED matrix display (from 0 to 15)
ledMatrix.displayClear(); // clear LED matrix display
}
void loop() {
ledMatrix.setTextAlignment(PA_CENTER);
ledMatrix.print("12:30"); // display text
Résultat:
Ca marche on a une horloge à l'heure… deux fois par jours.
Mise à l'heure
En se connectant à un serveur NTP on pourrait afficher l'heure UT.
#include <WiFi.h>
#include <WiFiMulti.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include "SSIDs.h"
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4 // 4 blocks
#define CS_PIN 21
// create an instance of the MD_Parola class
MD_Parola ledMatrix = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Time update
void setClock() {
configTime(0, 0, "pool.ntp.org");
Serial.println("Waiting for NTP time sync: ");
time_t nowSecs = time(nullptr);
while (nowSecs < 8 * 3600 * 2) {
delay(500);
Serial.print(F("."));
yield();
nowSecs = time(nullptr);
}
Serial.println();
struct tm timeinfo;
gmtime_r(&nowSecs, &timeinfo);
Serial.print("Current time: ");
Serial.println(asctime(&timeinfo));
}
// Manage Wifi
WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
// Initialize the breakout buildin led
pinMode(2,OUTPUT);
digitalWrite(2,HIGH);
ledMatrix.begin(); // initialize the LED Matrix
ledMatrix.setIntensity(0); // set the brightness of the LED matrix display (from 0 to 15)
ledMatrix.displayClear(); // clear LED matrix display
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(PRIMARY_SSID);
#ifdef SECONDARY_SSID
WiFiMulti.addAP(SECONDARY_SSID);
#endif
// wait for WiFi connection
Serial.println("Serach WIFI");
ledMatrix.setTextAlignment(PA_CENTER);
ledMatrix.print("WiFi");
Serial.print("Waiting for WiFi to connect...");
while ((WiFiMulti.run() != WL_CONNECTED)) {
delay (100);
}
ledMatrix.setTextAlignment(PA_CENTER);
ledMatrix.print(WiFi.SSID());
delay (1000);
Serial.println(" connected");
ledMatrix.setTextAlignment(PA_CENTER);
ledMatrix.print("Time");
setClock();
}
void loop() {
struct tm timeinfo;
time_t nows = time(nullptr);
gmtime_r(&nows, &timeinfo);
Serial.print(asctime(&timeinfo));
int h = timeinfo.tm_hour;
int m = timeinfo.tm_min;
char timeBuffer[6];
sprintf(timeBuffer,"%d:%02d", h, m);
ledMatrix.setTextAlignment(PA_CENTER);
ledMatrix.print(timeBuffer); // display time
unsigned long old=m;
// Wait for next minute
while (old==m) {
nows = time(nullptr);
gmtime_r(&nows, &timeinfo);
m = timeinfo.tm_min;
}
}
Le fichier "SSID.h" contient une ou deux macros définissant le SSID primaire et celui de backup.
#define PRIMARY_SSID "MABOXP", "lsqkdjlqksjdlqskjdlksq"
#define SECONDARY_SSID "MABOXS", "pozaidlsqkjdlzajdlqksj"
Dans l'ordre l'afficheur nous indique
La connexion wifi s'établit.
Une fois celle-ci établie, comme on utilise WiFiMulti on affiche le SSID qui a été choisit.
Puis on affiche :
Et puis enfin l'heure (mise a jour toutes les secondes)
On y crois mais ATTENTION c'est l'heure TU (GMT). L'heure à Greenwich en hivers! |
Gestion des "time zones"
On va utiliser l'API Abstract qui, à partir de notre IP publique va nous donner notre position, notre timezone etc.
L'URL de base de cette API est : https://ipgeolocation.abstractapi.com/v1/
Pour avoir son certificat on suit la procédure décrite dans la page l'API Abstract. A l'heure ou j'écris ces lignes c'est : Amazon Root CA 1 et elle est valable jusqu'au : January 16, 2038... on a du temps. En revanche le certificat de ipgeolocation.abstractapi.com expire le mercredi 22 mai 2024. Rien n'assure qu'après cette date le nouveau certificat de renouvellement sera toujours signé par Amazon!
Quand on développe un objet connecté on doit, de préférence, utiliser une URL dont on maitrise le certificat. Surtout prendre garde à ce que le serveur gérant le FW Update ne soit pas derrière un certificat que l'on ne maitrise pas.
Le certificat CA de Amazon est le suivant:
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy
rqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----
On en fait une chaine de caractère et on la mets dans le fichier CA-certificat.h
const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n" \
"ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" \
"b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n" \
"MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" \
"b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" \
"ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" \
"9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" \
"IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" \
"VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" \
"93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" \
"jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" \
"AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n" \
"A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n" \
"U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n" \
"N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n" \
"o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n" \
"5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n" \
"rqXRfboQnoZsG4q5WTP468SQvvG5\n" \
"-----END CERTIFICATE-----\n";
On inclus ce fichier en tête de notre programme. On crée également un fichier Abstract.h qui contient l'API_KEY qui nous identifie associé à l'URL:
#define ABSTRACT_URL "https://ipgeolocation.abstractapi.com/v1/?api_key=xxxxxx&fields=timezone"
On ajoute en tête de programme :
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include "Abstract.h"
Pour inclure les librairies http client et ssl (TLS) Et on ajoute une fonction getAbstractApiInfo():
String getAbstractApiInfo(){
WiFiClientSecure *client = new WiFiClientSecure;
if(client) {
client -> setCACert(rootCACertificate);
{
HTTPClient https;
Serial.print("[HTTPS] begin...\n");
if (https.begin(*client, ABSTRACT_URL)) { // Defined in Abstract.h
Serial.print("[HTTPS] GET...\n");
// start connection and send HTTP header
int httpCode = https.GET();
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = https.getString();
return payload;
}
} else {
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
} else {
Serial.printf("[HTTPS] Unable to connect\n");
}
}
}
return ("ERROR");
}
Qui nous renvoie une chaine :
{"timezone":{"name":"Europe/Paris","abbreviation":"CET","gmt_offset":1,"current_time":"20:55:32","is_dst":false}}
Qui est du JSON :
{
"timezone": {
"name": "Europe/Paris",
"abbreviation": "CET",
"gmt_offset": 1,
"current_time": "20:55:32",
"is_dst": false
}
}