« ESP32 Horloge à LED » : différence entre les versions

De knowledge
Aller à la navigation Aller à la recherche
mAucun résumé des modifications
mAucun résumé des modifications
Ligne 141 : Ligne 141 :
   }
   }
}
}
</syntaxhighlight>Le fichier "SSID.h" contient une ou deux macros définissant le SSID primaire et celui de backup.<syntaxhighlight lang="arduino">
#define PRIMARY_SSID  "MABOXP", "lsqkdjlqksjdlqskjdlksq"
#define SECONDARY_SSID "MABOXS", "pozaidlsqkjdlzajdlqksj"
</syntaxhighlight>Dans l'ordre l'afficheur nous indique
</syntaxhighlight>Dans l'ordre l'afficheur nous indique


Ligne 162 : Ligne 165 :
=== Gestion des "time zones" ===
=== Gestion des "time zones" ===
On va utiliser l'API [[Abstract API|Abstract]] qui, à partir de notre IP publique va nous donner notre position, notre timezone etc.
On va utiliser l'API [[Abstract API|Abstract]] qui, à partir de notre IP publique va nous donner notre position, notre timezone etc.
L'URL de base de cette API est : <nowiki>https://ipgeolocation.abstractapi.com/v1/</nowiki>
Pour avoir son certificat on suit la procédure décrite dans la page  l'API [[Abstract 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 <u>ipgeolocation.abstractapi.com</u> 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:<syntaxhighlight lang="pem">
-----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-----
</syntaxhighlight>On en fait une chaine de caractère et on la mets dans le fichier CA-certificat.h<syntaxhighlight lang="arduino">
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";
</syntaxhighlight>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:<syntaxhighlight lang="arduino">
#define ABSTRACT_URL "https://ipgeolocation.abstractapi.com/v1/?api_key=xxxxxx&fields=timezone"
</syntaxhighlight>On ajoute en tête de programme :<syntaxhighlight lang="arduino">
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include "Abstract.h"
</syntaxhighlight>Pour inclure les librairies http client et ssl (TLS)
Et on ajoute une fonction getAbstractApiInfo():<syntaxhighlight lang="arduino">
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");
}
</syntaxhighlight>Qui nous renvoie une chaine :<syntaxhighlight lang="text">
{"timezone":{"name":"Europe/Paris","abbreviation":"CET","gmt_offset":1,"current_time":"20:55:32","is_dst":false}}
</syntaxhighlight>Qui est du JSON :<syntaxhighlight lang="json">
{
    "timezone": {
        "name": "Europe/Paris",
        "abbreviation": "CET",
        "gmt_offset": 1,
        "current_time": "20:55:32",
        "is_dst": false
    }
}
</syntaxhighlight>

Version du 29 octobre 2023 à 20:01

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.

ESP32+LEDMATRIX.png

Attention.png
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:

LED MATRIX 1230.png

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

LED WiFi.png

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 :

LED Time.png

Et puis enfin l'heure (mise a jour toutes les secondes)

LED 15h54.png

Attention.png
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
    }
}