Lampe de chevet Dimmable avec Horloge NTP ESP32
Belle ambiance

Présentation
J’ai récupéré une ancienne lampe de chevet pour ne garder que le cylindre que je trouvais joli. J’ai remplacé la partie éclairage par une LED dimmable alimentée en 5V, un bouton pour allumer et éteindre la lampe et un bouton pour afficher l’heure pendant un laps de temps ou jusqu’a une nouvelle pression sur le bouton poussoir.
L’horloge se met à l’heure par le protocole NTP toutes les 12 heures et le wifi ne fonctionne que pendant ces courts instants.
Le passage aux heures d’hiver et d’été est automatique.
Une prise USB permet également de brancher un appareil à alimenter en 5V.
Composants
Schéma

J’ai récupéré la partie horloge du programme de mon article « Grande horloge… » .
Boîtier
J’ai conçu ce boîtier en fonction de la partie de lampe récupérée. J’ai également dessiné un support pour le module LED en 3D afin qu’il se clipse simplement dessus.
Les fichiers 3D pourront bientôt être téléchargés en cliquant sur le bouton ci-dessous

Si vous souhaitez les obtenir avant, laissez-moi un message.
Vous pouvez adapter les dimensions et modifier les formes à partir du fichier FreeCAD.
Code
Pour la programmation, j’utilise Platformio mais l’IDE Arduino convient également.
Il vous faudra adapter le code en fonction de votre fuseau horaire pour le passage à l’heure d’été/hiver !
Platformio.ini
[env:esp32-c3-devkitm-1]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed=115200
build_flags =
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
lib_deps =
thomasfredericks/Bounce2@^2.72
sstaub/TickTwo@^4.4.0
marvinroger/AsyncMqttClient@^0.9.0
adafruit/Adafruit SSD1306@^2.5.15
adafruit/Adafruit NeoPixel@^1.15.2
jagprod.h
#ifndef jagprod_h
#define jagprod_h
// WiFi
#define sid "votre SSID"
#define wifi_sesame "Votre mot de passe WiFi"
// IP (Si utilisation en DHCP, supprimer cette partie)
const uint8_t ip[4] = {Adresse IP de l'hôte dans votre réseau};
const uint8_t mask[4] = {Masque de sous-réseaux};
const uint8_t ns[4] = {Adresse IP du serveur DNS};
const uint8_t gw[4] = {Adresse de la passerelle};
#endif
main.cpp
Vous devrez remplacer les *** le serveur NTP à utiliser.
#include <Arduino.h>
#include <WiFi.h>
#include <TickTwo.h>
#include <Bounce2.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Fonts/FreeSans24pt7b.h>
#include <Fonts/FreeSansOblique9pt7b.h>
#include <Adafruit_SSD1306.h>
#include <WiFiUdp.h>
#include <jagprod.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
String heure;
unsigned int localPort = 2390;
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];
IPAddress timeServerIP;
WiFiUDP udp;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
int s;
int ds;
int m;
int dm;
int h;
int dh;
int gmtbe;
void quelleheure(IPAddress &address);
bool temoinntp = true;
// Mettre la ligne suivante en remarque Si affichage temporisé
bool displaystate = true;
long secondes;
// Variables DSTAuto
int dst = 1;
unsigned long epoch;
unsigned long epochmars;
unsigned long epochoctobre;
unsigned long epochannee;
int seconde;
int minute;
int Heure;
int bis = 0;
int annee;
unsigned long epoch01012025 = 1735689600;
unsigned long nombredejours;
// int mois;
int nombredejoursannee;
int jour;
String joursemaine;
int queljoursemaine;
String mois;
String epoch2string;
bool premierefois = true;
void plus1s();
void plus1s() // Action à réaliser lorsque le timer uneseconde est écoulé
{
s = s + 1;
// Serial.println(s);
}
// Relais et bouton
const int relais_pin = 3;
bool etatrelais = false;
const int poussoir_onoff_pin = 1;
const int poussoir_ecran_pin = 0;
Bounce2::Button poussoir_onoff = Bounce2::Button();
Bounce2::Button poussoir_ecran = Bounce2::Button();
TickTwo uneseconde(plus1s, 1000);
void setup()
{
Serial.begin(115200);
// Bouton
poussoir_onoff.attach(poussoir_onoff_pin, INPUT_PULLUP);
poussoir_onoff.setPressedState(LOW);
poussoir_onoff.interval(5);
poussoir_ecran.attach(poussoir_ecran_pin, INPUT_PULLUP);
poussoir_ecran.setPressedState(LOW);
poussoir_ecran.interval(5);
// Relais
pinMode(relais_pin, OUTPUT);
digitalWrite(relais_pin, etatrelais);
Wire.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
{
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
display.setRotation(2);
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
}
void loop()
{
uneseconde.update();
poussoir_onoff.update();
poussoir_ecran.update();
if (poussoir_onoff.pressed())
{
etatrelais = !etatrelais;
digitalWrite(relais_pin, etatrelais);
}
if (poussoir_ecran.fell())
{
// Pour l'affichage temporisé
/* display.ssd1306_command(SSD1306_DISPLAYON);
uneseconde.pause();
delay(4000);
uneseconde.resume();
display.ssd1306_command(SSD1306_DISPLAYOFF);
s = s - 4;
*/
// Pour l'affichage switché
displaystate = !displaystate;
if (displaystate == true)
{
display.ssd1306_command(SSD1306_DISPLAYON);
}
else
{
display.ssd1306_command(SSD1306_DISPLAYOFF);
}
}
secondes = dh * 36000 + h * 3600 + dm * 600 + m * 60 + ds * 10 + s;
if (secondes == 32400 or secondes == 75600)
{ // Synchro NTP à 9h et 21h
temoinntp = true;
}
if (temoinntp == true) // il faut faire une synchro NTP
{
quelleheure(timeServerIP);
temoinntp = false;
}
if (s > 9)
{
s = 0;
ds = ds + 1;
if (ds > 5)
{
ds = 0;
m = m + 1;
if (m > 9)
{
m = 0;
dm = dm + 1;
if (dm > 5)
{
dm = 0;
h = h + 1;
if (dh == 2 and h == 4)
{
dh = 0;
h = 0;
}
if (h > 9)
{
h = 0;
dh = dh + 1;
}
}
}
heure = String(dh) + String(h) + ":" + String(dm) + String(m);
Serial.println(heure);
display.clearDisplay();
display.setFont(&FreeSans24pt7b);
display.setCursor(0, 32);
display.print(heure);
display.display();
// Mettre la ligne suivante en remarque si affichage switché
// display.ssd1306_command(SSD1306_DISPLAYOFF);
}
}
}
void quelleheure(IPAddress &address) // Synchronisation du temps via NTP
{
uneseconde.pause();
WiFi.mode(WIFI_STA);
display.setFont(&FreeSansOblique9pt7b);
display.clearDisplay();
display.setCursor(37, 14);
display.print("Mise a");
display.setCursor(37, 31);
display.println("l'heure");
display.display();
if (!WiFi.config(ip, gw, mask, ns))
{
}
WiFi.begin(sid, wifi_sesame);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
}
display.clearDisplay();
display.setCursor(10, 20);
display.print("WIFI OK");
display.display();
udp.begin(localPort);
WiFi.hostByName(ntpServerName, timeServerIP);
memset(packetBuffer, 0, NTP_PACKET_SIZE);
packetBuffer[0] = 0b11100011;
packetBuffer[1] = 0;
packetBuffer[2] = 6;
packetBuffer[3] = 0xEC;
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
udp.beginPacket(address, 123);
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
delay(1000);
int cb = udp.parsePacket();
while (!cb)
{
cb = udp.parsePacket();
}
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
unsigned long secsSince1900 = highWord << 16 | lowWord;
const unsigned long seventyYears = 2208988800UL;
unsigned long epoch = secsSince1900 - seventyYears - 1; // moins délais udp
WiFi.mode(WIFI_OFF);
annee = 2025;
bis = 0;
epochannee = epoch01012025;
nombredejours = (epoch - epoch01012025) / 86400L + 1; // car le 1 janvier correspond aux secondes 0 à 86400 soit le jour 0
nombredejoursannee = nombredejours;
while (nombredejoursannee > 365 + bis)
{
nombredejoursannee = nombredejoursannee - (365 + bis);
annee = annee + 1;
epochannee = epochannee + (365 + bis) * 86400L;
bis = 0;
if (annee % 400 == 0 or (annee % 4 == 0 and annee % 100 != 0))
{
bis = 1;
}
}
if (nombredejoursannee <= 31)
{
mois = "janvier";
jour = nombredejoursannee;
}
else if (nombredejoursannee <= (59 + bis))
{
mois = "fevrier";
jour = nombredejoursannee - 31;
}
else if (nombredejoursannee <= 90 + bis)
{
mois = "mars";
jour = nombredejoursannee - (59 + bis);
}
else if (nombredejoursannee <= 120 + bis)
{
mois = "avril";
jour = nombredejoursannee - (90 + bis);
}
else if (nombredejoursannee <= 151 + bis)
{
mois = "mai";
jour = nombredejoursannee - (120 + bis);
}
else if (nombredejoursannee <= 181 + bis)
{
mois = "juin";
jour = nombredejoursannee - (151 + bis);
}
else if (nombredejoursannee <= 212 + bis)
{
mois = "juillet";
jour = nombredejoursannee - (181 + bis);
}
else if (nombredejoursannee <= 243 + bis)
{
mois = "aout";
jour = nombredejoursannee - (212 + bis);
}
else if (nombredejoursannee <= 273 + bis)
{
mois = "septembre";
jour = nombredejoursannee - (243 + bis);
}
else if (nombredejoursannee <= 304 + bis)
{
mois = "octobre";
jour = nombredejoursannee - (273 + bis);
}
else if (nombredejoursannee <= 334 + bis)
{
mois = "novembre";
jour = nombredejoursannee - (304 + bis);
}
else if (nombredejoursannee <= 365 + bis)
{
mois = "decembre";
jour = nombredejoursannee - (334 + bis);
}
queljoursemaine = (epoch / 86400L + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
switch (queljoursemaine)
{
case 0:
joursemaine = "Dimanche";
break;
case 1:
joursemaine = "Lundi";
break;
case 2:
joursemaine = "Mardi";
break;
case 3:
joursemaine = "Mercredi";
break;
case 4:
joursemaine = "Jeudi";
break;
case 5:
joursemaine = "Vendredi";
break;
case 6:
joursemaine = "Samedi";
break;
}
epochmars = epochannee + (90 + bis) * 86400UL; // correspond au dernier jour de de mars
queljoursemaine = (epochmars / 86400UL + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
while (queljoursemaine != 0)
{
epochmars = epochmars - 86400UL;
queljoursemaine = (epochmars / 86400UL + 4) % 7;
}
epochmars = epochmars + 1 * 3600UL; // le changement se fait à UTC+1 soit +2H car UTC+1 +DST hiver
epochoctobre = epochannee + (303 + bis) * 86400UL; // correspond au dernier jour d'octobre
queljoursemaine = (epochoctobre / 86400UL + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
while (queljoursemaine != 0)
{
epochoctobre = epochoctobre - 86400UL;
queljoursemaine = (epochoctobre / 86400UL + 4) % 7;
}
epochoctobre = epochoctobre + 1 * 3600UL; // le changement se fait à UTC+1 soit +3H car UTC+1 +DST été
if (epoch >= epochmars and epoch < epochoctobre)
{
dst = 2;
}
else
{
dst = 1;
}
Heure = ((epoch % 86400UL) / 3600UL) + dst; // +1 = Heure d'hiver et +2 = Heure d'été
if (Heure >= 24)
{
Heure = Heure - 24;
}
minute = ((epoch % 3600) / 60);
seconde = (epoch % 60);
// Mise en forme pour l'affichage
dh = Heure / 10;
h = Heure - (dh * 10);
dm = minute / 10;
m = minute - (dm * 10);
ds = seconde / 10;
s = seconde - (ds * 10);
uneseconde.resume();
heure = String(dh) + String(h) + ":" + String(dm) + String(m);
if (premierefois == true)
{
displaystate = true;
premierefois = false;
display.clearDisplay();
display.setFont(&FreeSans24pt7b);
display.setCursor(0, 32);
display.print(heure);
display.display();
}
else
{
displaystate = false;
}
}






