Intégration domotique DOMESTIA à ma centrale Home Assistant

Etat des lieux
Les 16 points d’éclairage de mon appartement sont gérés par 2 cartes domotiques DOMESTIA ( C’est du Belge !) à 12 relais chacune. 8 sont donc inutilisés.
Les interrupteurs poussoirs de l’appartement sont connectés sur des modules d’identification à 4 ou 6 fils ( DM004, DM006) qui sont eux-même connectés aux cartes dans le coffret électrique par un bus basse tension sur paire de cuivre.
Ce système permet de programmer les cartes pour que chaque bouton poussoir commande l’un ou plusieurs relais.
Une diode LED, sur ces cartes, indique l’état de chaque relais ON ou OFF.
Motivation
Je voulais intégrer la gestion de l’éclairage dans ma propre centrale domotique publiée sur ce site. Et ce, de manière simple sans toucher aux cartes du coffret électrique !
L’idée
Le module de commande
Cahier des charges
Matériel
Je privilégie ces alimentations car elles sont, entre autre, certifiées UL et TUV. (Organismes américains et allemands de certification de sécurité). Cela signifie que les produits concernés ont passé avec succès une série de tests destructifs recréant des conditions de fonctionnement anormaux et qu’ils n’ont présentés aucun danger pour les biens et les personnes.
J’ai décidé de faire l’impasse sur la LDR et l’adaptation de luminosité de l’écran car avec le TFT SPI dont je disposais.
Je n’avais plus assez de GPIO sur mon microcontrôleur. Avec un affichage I2C, le problème ne se serait pas présenté.
Toutefois, à l’usage, je m’en passe très bien. J’ai simplement introduit un mode veille après quelques secondes diminuant la luminosité du TFT.
NB. Le relais de type 835-C possède une diode interne de protection. Si vous utilisez un relais qui n’en possède pas, il faut en ajouter une de type 1N4148, 1N914 ou équivalent.
Le schéma

Un circuit (6 poussoirs)
Le circuit imprimé avec 6 relais, 6 transistors 2N2222, 6 résistances 1200 Ω et un module d’identification DM006 est à faire en trois exemplaires.

Dans la boîte (3 circuits)

Fonctionnement
L’écran affiche les 16 points lumineux et une zone « tout OFF ».
Le bouton rotatif permet de sélectionner le point lumineux à basculer.
Les couleurs de l’élément sélectionné sont inversées pour éviter la confusion.
L’appui sur le bouton ou la réception d’un message MQTT commute le relais 835-C correspondant pendant un bref instant pour simuler l’appui sur un poussoir de l’installation électrique.
Le poussoir virtuel est identifié par le module DM006 et transmis sur le bus jusqu’à la centrale DOMESTIA qui exécute l’action correspondante.
L’élément correspondant de l’écran TFT est mis en surbrillance si le point lumineux est ON et est affiché normalement dans le cas contraire.
Après quelques secondes d’inactivité, l’écran TFT passe en mode « veille ». C’est-à-dire que le rétro éclairage est fortement diminué.
Aussitôt que le bouton est tourné ou poussé ou qu’un message MQTT arrive, l’écran s’affiche avec la luminosité maximale. Lors de chaque poussée sur le bouton, un message MQTT est envoyé à la centrale Home Assistant.
Le programme
J’utilise VSCode avec l’extension PlatformIO.
Le fichier platformio.ini :
[env:lolin_s2_mini]
platform = espressif32
board = lolin_s2_mini
framework = arduino
lib_deps =
adafruit/Adafruit ST7735 and ST7789 Library@^1.11.0
thomasfredericks/Bounce2@^2.72
madhephaestus/ESP32Encoder@^0.12.0
sstaub/TickTwo@^4.4.0
marvinroger/AsyncMqttClient@^0.9.0
Le fichier jagprod.h :
Il faut remplacer les valeurs en fonction de votre infrastructure.
#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};
// MQTT
const char *mqtt_serveur = "adresse IP du serveur MQTT";
const uint16_t mqtt_port = port MQTT;
const char *mqtt_identifiant = "identifiant MQTT";
const char *mqtt_sesame = "Mot de passe MQTT";
#endif
Le code :
#include <Arduino.h>
#include <jagprod.h>
// Wifi
#include <WiFi.h>
void WiFiEvent(WiFiEvent_t event);
bool wifi_statut = false;
void WiFiEvent(WiFiEvent_t event)
{
switch (event)
{
case SYSTEM_EVENT_STA_CONNECTED:
Serial.println("Assoscié");
break;
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("Connecté");
wifi_statut = true;
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("Wifi perdu");
wifi_statut = false;
break;
default:
break;
}
}
// MQTT
#include <AsyncMqttClient.h>
AsyncMqttClient mqtt_client;
unsigned long derniere_connexion_mqtt = 0;
unsigned long mqtt_timeout = 5000;
bool mqtt_statut = false;
static String message;
float valeur;
char val[7];
#include <Adafruit_ST7735.h>
#define TFT_DC 21
#define TFT_MOSI 35
#define TFT_SCLK 36
#define TFT_RST 33
#define TFT_CS 34
#define TFT_BL 18
Adafruit_ST7735 ecran = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
int largeur = 128;
int hauteur = 160;
int ecran_niveau_veille = 252;
int ecran_niveau_actif = 150;
uint16_t blanc = 65535;
uint16_t noir = 0;
uint16_t bleu = 31;
uint16_t jaune = 65504;
uint16_t cyan = 2047;
uint16_t couleur_dessus = blanc;
uint16_t couleur_fond = noir;
uint16_t couleur_bord = bleu;
bool ecran_etat = true;
bool appel_from_encodeur = false;
bool cellule_selectionnee = false;
const uint8_t point_pin[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
const char *point[] = {"Barbacanes", "Ilot Evier", "Cuisine", "Entree", "Salon", "Table", "Chambre", "Terrasse", "Bureau Pg", "Bureau Pc", "Buanderie", "Hall Nuit", "Salle Bain", "Douche", "Eviers", "WC", "Tout OFF"};
int cellule_hauteur = 17;
int cellule_largeur = 64;
bool point_etat[17];
int point_index_encodeur;
int point_index_etat;
int point_index_commande;
int cellule_position_x;
int cellule_position_y;
int texte_position_x;
int texte_position_y;
void affichage_cellule();
void action();
// === MQTT EVENTS ===
void onMqttConnect(bool sessionPresent)
{
Serial.println("✅ Connecté au broker MQTT !");
mqtt_statut = true;
mqtt_client.subscribe("domestia/ida1", 1);
mqtt_client.subscribe("domestia/ida2", 1);
mqtt_client.subscribe("domestia/ida3", 1);
mqtt_client.subscribe("domestia/ida4", 1);
mqtt_client.subscribe("domestia/ida5", 1);
mqtt_client.subscribe("domestia/ida6", 1);
mqtt_client.subscribe("domestia/idb1", 1);
mqtt_client.subscribe("domestia/idb2", 1);
mqtt_client.subscribe("domestia/idb3", 1);
mqtt_client.subscribe("domestia/idb4", 1);
mqtt_client.subscribe("domestia/idb5", 1);
mqtt_client.subscribe("domestia/idb6", 1);
mqtt_client.subscribe("domestia/idc1", 1);
mqtt_client.subscribe("domestia/idc2", 1);
mqtt_client.subscribe("domestia/idc3", 1);
mqtt_client.subscribe("domestia/idc4", 1);
mqtt_client.subscribe("domestia/idc5", 1);
mqtt_client.subscribe("domestia/idc6", 1);
mqtt_client.subscribe("domestia/ida1/etat", 1);
mqtt_client.subscribe("domestia/ida2/etat", 1);
mqtt_client.subscribe("domestia/ida3/etat", 1);
mqtt_client.subscribe("domestia/ida4/etat", 1);
mqtt_client.subscribe("domestia/ida5/etat", 1);
mqtt_client.subscribe("domestia/ida6/etat", 1);
mqtt_client.subscribe("domestia/idb1/etat", 1);
mqtt_client.subscribe("domestia/idb2/etat", 1);
mqtt_client.subscribe("domestia/idb3/etat", 1);
mqtt_client.subscribe("domestia/idb4/etat", 1);
mqtt_client.subscribe("domestia/idb5/etat", 1);
mqtt_client.subscribe("domestia/idb6/etat", 1);
mqtt_client.subscribe("domestia/idc1/etat", 1);
mqtt_client.subscribe("domestia/idc2/etat", 1);
mqtt_client.subscribe("domestia/idc3/etat", 1);
mqtt_client.subscribe("domestia/idc4/etat", 1);
mqtt_client.subscribe("homeassistant/status", 1);
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
Serial.printf("⚠️ Déconnecté du broker MQTT (raison: %d)\n", static_cast<uint8_t>(reason));
mqtt_statut = false;
}
void onMqttPublish(uint16_t packetId)
{
Serial.printf("📤 Publication MQTT confirmée (packetId: %u)\n", packetId);
}
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
appel_from_encodeur = false;
message = "";
for (size_t i = 0; i < len; i++)
{
message += payload[i];
}
Serial.printf("📩 Message reçu [%s] : %s\n", topic, message.c_str());
if (strcmp(topic, "domestia/ida1") == 0)
{
point_index_commande = 0;
action();
}
if (strcmp(topic, "domestia/ida2") == 0)
{
point_index_commande = 1;
action();
}
if (strcmp(topic, "domestia/ida3") == 0)
{
point_index_commande = 2;
action();
}
if (strcmp(topic, "domestia/ida4") == 0)
{
point_index_commande = 3;
action();
}
if (strcmp(topic, "domestia/ida5") == 0)
{
point_index_commande = 4;
action();
}
if (strcmp(topic, "domestia/ida6") == 0)
{
point_index_commande = 5;
action();
}
if (strcmp(topic, "domestia/idb1") == 0)
{
point_index_commande = 6;
action();
}
if (strcmp(topic, "domestia/idb2") == 0)
{
point_index_commande = 7;
action();
}
if (strcmp(topic, "domestia/idb3") == 0)
{
point_index_commande = 8;
action();
}
if (strcmp(topic, "domestia/idb4") == 0)
{
point_index_commande = 9;
action();
}
if (strcmp(topic, "domestia/idb5") == 0)
{
point_index_commande = 10;
action();
}
if (strcmp(topic, "domestia/idb6") == 0)
{
point_index_commande = 11;
action();
}
if (strcmp(topic, "domestia/idc1") == 0)
{
point_index_commande = 12;
action();
}
if (strcmp(topic, "domestia/idc2") == 0)
{
point_index_commande = 13;
action();
}
if (strcmp(topic, "domestia/idc3") == 0)
{
point_index_commande = 14;
action();
}
if (strcmp(topic, "domestia/idc4") == 0)
{
point_index_commande = 15;
action();
}
if (strcmp(topic, "domestia/idc5") == 0)
{
point_index_commande = 16;
action();
}
if (strcmp(topic, "domestia/idc6") == 0)
{
point_index_commande = 17;
action();
}
if (strcmp(topic, "domestia/ida1/etat") == 0)
{
point_index_etat = 0;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/ida2/etat") == 0)
{
point_index_etat = 1;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/ida3/etat") == 0)
{
point_index_etat = 2;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/ida4/etat") == 0)
{
point_index_etat = 3;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/ida5/etat") == 0)
{
point_index_etat = 4;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/ida6/etat") == 0)
{
point_index_etat = 5;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idb1/etat") == 0)
{
point_index_etat = 6;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idb2/etat") == 0)
{
point_index_etat = 7;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idb3/etat") == 0)
{
point_index_etat = 8;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idb4/etat") == 0)
{
point_index_etat = 9;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idb5/etat") == 0)
{
point_index_etat = 10;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idb6/etat") == 0)
{
point_index_etat = 11;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idc1/etat") == 0)
{
point_index_etat = 12;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idc2/etat") == 0)
{
point_index_etat = 13;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idc3/etat") == 0)
{
point_index_etat = 14;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "domestia/idc4/etat") == 0)
{
point_index_etat = 15;
if (message == "ON")
{
point_etat[point_index_etat] = true;
}
if (message == "OFF")
{
point_etat[point_index_etat] = false;
}
affichage_cellule();
}
if (strcmp(topic, "homeassistant/status") == 0)
{
if (message == "online")
{
// A faire
}
}
}
void ecran_eteint()
{
analogWrite(TFT_BL, ecran_niveau_veille);
}
// Timers
#include <TickTwo.h>
TickTwo affichage_duree(ecran_eteint, 30000, 1);
#include <ESP32Encoder.h>
// #define ENCODER_DO_NOT_USE_INTERRUPTS
#define CLK_PIN 40
#define DATA_PIN 39
ESP32Encoder encodeur;
long encodeur_position_precedente = -999;
long encodeur_position;
#include <Bounce2.h>
const int action_pin = 38;
Bounce2::Button action_bouton = Bounce2::Button();
void action()
{
digitalWrite(point_pin[point_index_commande], HIGH);
Serial.print("ID ");
Serial.print(point_index_commande);
Serial.print(" est : ");
Serial.println(digitalRead(point_pin[point_index_commande]));
delay(500);
digitalWrite(point_pin[point_index_commande], LOW);
Serial.print("ID ");
Serial.print(point_index_commande);
Serial.print(" est : ");
Serial.println(digitalRead(point_pin[point_index_commande]));
}
void affichage_cellule()
{
// Réactivation de l'écran
affichage_duree.stop();
analogWrite(TFT_BL, ecran_niveau_actif);
if (appel_from_encodeur == true)
{
if (cellule_selectionnee == true)
{
if (point_etat[point_index_encodeur] == true)
{
couleur_bord = blanc;
couleur_dessus = jaune;
couleur_fond = bleu;
}
else
{
couleur_bord = blanc;
couleur_dessus = cyan;
couleur_fond = noir;
}
}
else
{
if (point_etat[point_index_encodeur] == true)
{
couleur_bord = bleu;
couleur_dessus = jaune;
couleur_fond = bleu;
}
else
{
couleur_bord = bleu;
couleur_dessus = blanc;
couleur_fond = noir;
}
}
if (point_index_encodeur == 16)
{
cellule_largeur = 128;
cellule_hauteur = 24;
cellule_position_x = 0;
cellule_position_y = 136;
texte_position_x = 42;
texte_position_y = 145;
}
else
{
cellule_largeur = 64;
cellule_hauteur = 17;
if ((point_index_encodeur & 1) == 0)
{
cellule_position_x = 0;
cellule_position_y = cellule_hauteur * point_index_encodeur / 2;
}
else
{
cellule_position_x = 64;
cellule_position_y = cellule_hauteur * (point_index_encodeur - 1) / 2;
}
texte_position_x = cellule_position_x + 2;
texte_position_y = cellule_position_y + 5;
}
ecran.fillRoundRect(cellule_position_x, cellule_position_y, cellule_largeur, cellule_hauteur, 5, couleur_fond);
ecran.drawRoundRect(cellule_position_x, cellule_position_y, cellule_largeur, cellule_hauteur, 5, couleur_bord);
ecran.setTextColor(couleur_dessus);
ecran.setCursor(texte_position_x, texte_position_y);
ecran.print(point[point_index_encodeur]);
}
else
{ // L'appel vient d'un message MQTT
if (point_index_encodeur == point_index_etat)
{
cellule_selectionnee = true;
}
else
{
cellule_selectionnee = false;
}
// Définition des couleurs en fonction de la situation
if (cellule_selectionnee == true)
{
if (point_etat[point_index_etat] == true)
{
couleur_bord = blanc;
couleur_dessus = jaune;
couleur_fond = bleu;
}
else
{
couleur_bord = blanc;
couleur_dessus = cyan;
couleur_fond = noir;
}
}
else
{
if (point_etat[point_index_etat] == true)
{
couleur_bord = bleu;
couleur_dessus = jaune;
couleur_fond = bleu;
}
else
{
couleur_bord = bleu;
couleur_dessus = blanc;
couleur_fond = noir;
}
}
cellule_largeur = 64;
cellule_hauteur = 17;
if ((point_index_etat & 1) == 0)
{
cellule_position_x = 0;
cellule_position_y = cellule_hauteur * point_index_etat / 2;
}
else
{
cellule_position_x = 64;
cellule_position_y = cellule_hauteur * (point_index_etat - 1) / 2;
}
texte_position_x = cellule_position_x + 2;
texte_position_y = cellule_position_y + 5;
ecran.fillRoundRect(cellule_position_x, cellule_position_y, cellule_largeur, cellule_hauteur, 5, couleur_fond);
ecran.drawRoundRect(cellule_position_x, cellule_position_y, cellule_largeur, cellule_hauteur, 5, couleur_bord);
ecran.setTextColor(couleur_dessus);
ecran.setCursor(texte_position_x, texte_position_y);
ecran.print(point[point_index_etat]);
}
affichage_duree.start();
}
void setup()
{
Serial.begin(115200);
delay(100);
pinMode(TFT_BL, OUTPUT);
analogWrite(TFT_BL, ecran_niveau_actif);
for (int i = 0; i < sizeof(point_pin); i++)
{
pinMode(point_pin[i], OUTPUT);
digitalWrite(point_pin[i], LOW);
}
//----WIFI----
WiFi.setSleep(false);
WiFi.setAutoReconnect(true);
WiFi.config(ip, gw, mask, ns);
WiFi.onEvent(WiFiEvent);
WiFi.begin(sid, wifi_sesame);
// MQTT
mqtt_client.setServer(mqtt_serveur, mqtt_port);
mqtt_client.setCredentials(mqtt_identifiant, mqtt_sesame);
mqtt_client.setClientId("domestia"); // nom du client mqtt
mqtt_client.onConnect(onMqttConnect);
mqtt_client.onDisconnect(onMqttDisconnect);
mqtt_client.onMessage(onMqttMessage);
mqtt_client.onPublish(onMqttPublish);
ecran.initR(INITR_BLACKTAB);
ecran.fillScreen(ST77XX_BLACK);
ecran.setRotation(0);
ecran.setTextWrap(false);
ecran.setTextColor(ST77XX_WHITE);
ecran.setTextSize(1);
for (point_index_encodeur = 0; point_index_encodeur < sizeof(point_pin); point_index_encodeur++)
{
cellule_selectionnee = false;
appel_from_encodeur = true;
affichage_cellule();
}
point_index_encodeur = 0;
encodeur.attachHalfQuad(DATA_PIN, CLK_PIN);
encodeur.clearCount();
action_bouton.attach(action_pin, INPUT_PULLUP);
action_bouton.setPressedState(LOW);
action_bouton.interval(10);
affichage_duree.start();
}
void loop()
{
if (wifi_statut == true and mqtt_statut == false and millis() - derniere_connexion_mqtt > mqtt_timeout)
{
Serial.println("Tentative de connexion MQTT");
mqtt_client.connect();
derniere_connexion_mqtt = millis();
}
affichage_duree.update();
action_bouton.update();
if (action_bouton.pressed())
{
point_index_commande = point_index_encodeur;
action();
}
long encodeur_position = encodeur.getCount() / 2;
if (encodeur_position != encodeur_position_precedente) // Si l'encodeur est tourné
{
// Réactivation de l'affichage
affichage_duree.stop();
analogWrite(TFT_BL, ecran_niveau_actif);
affichage_duree.start();
if (encodeur_position > 16)
{
encodeur_position = 0;
encodeur.setCount(0);
}
if (encodeur_position < 0)
{
encodeur_position = 16;
encodeur.setCount(32);
}
appel_from_encodeur = true;
cellule_selectionnee = false;
affichage_cellule();
point_index_encodeur = encodeur_position;
encodeur_position_precedente = encodeur_position;
appel_from_encodeur = true;
cellule_selectionnee = true;
affichage_cellule();
cellule_selectionnee = false;
}
}
Le boîtier
On voit bien, sur la gauche,les réceptacles des modules DM006 et les glissières des 3 circuits imprimés et, sur la droite, le support de l’alimentation et le serre-câble.
Reprogrammation de la centrale DOMESTIA
Il faut maintenant informer la centrale qu’il existe 17 nouveaux interrupteurs poussoirs dans l’installation électrique. Pour cela, il y a lieu de procéder à une programmation de ces cartes DOMESTIA. Fort heureusement, la procédure est assez simple et bien documentée dans le manuel.
Conclusion
Le module de commande fonctionne parfaitement. Les relais s’actionnent et l’affichage de l’état des relais est correct.
Tant que j’utilise exclusivement cette commande ou une autre passant par ma centrale domotique, c’est parfait.
Cependant, lorsque j’actionne un interrupteur poussoir de l’installation électrique, les modifications de l’état des relais ne sont pas connus de ma centrale et c’est l’anarchie 😉 .
C’est la raison d’être de la deuxième partie de cet article !
Le module retour d’états

Rôle
Transmettre l’état réel de chaque relais à ma centrale domotique !
Cahier des charges
Matériel
Le schéma

Le circuit imprimé
Le montage devant être intégré au coffret électrique, j’ai fait l’effort de dessiner un circuit imprimé. Il a été réalisé comme prototype en 5 exemplaires, pour un prix défiant toute concurrence par JLCPCB.
Cependant, j’ai commis une petite erreur en définissant l’écartement des connecteurs J1 et J2 qui devait correspondre à un circuit intégré DIL. Sur le PCB, cet écartement est plus faible. Cela m’a obligé à utiliser des soquets DIL à « wrapper » dont j’ai plié les pattes pour les adapter.
Le programme
Le fichier platformio.ini :
[env:lolin_s2_mini]
platform = espressif32
board = lolin_s2_mini
framework = arduino
lib_deps =
marvinroger/AsyncMqttClient@^0.9.0
sstaub/TickTwo@^4.4.0
monitor_speed = 115200
Le fichier 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};
// MQTT
const char *mqtt_serveur = "adresse IP du serveur MQTT";
const uint16_t mqtt_port = port MQTT;
const char *mqtt_identifiant = "identifiant MQTT";
const char *mqtt_sesame = "Mot de passe MQTT";
#endif
Le code :
#include <Arduino.h>
#include <jagprod.h>
// Wifi
#include <WiFi.h>
void WiFiEvent(WiFiEvent_t event);
bool wifi_statut = false;
const uint8_t rouge_pin = 21;
const uint8_t vert_pin = 16;
const uint8_t bleu_pin = 17;
void WiFiEvent(WiFiEvent_t event)
{
switch (event)
{
case SYSTEM_EVENT_STA_CONNECTED:
Serial.println("Assoscié");
break;
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("Connecté");
digitalWrite(rouge_pin, HIGH);
digitalWrite(bleu_pin, HIGH);
digitalWrite(vert_pin, LOW);
wifi_statut = true;
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("Wifi perdu");
digitalWrite(rouge_pin, LOW);
digitalWrite(bleu_pin, HIGH);
digitalWrite(vert_pin, HIGH);
wifi_statut = false;
break;
default:
break;
}
}
// MQTT
#include <AsyncMqttClient.h>
AsyncMqttClient mqtt_client;
unsigned long derniere_connexion_mqtt = 0;
unsigned long mqtt_timeout = 5000;
bool mqtt_statut = false;
static String message;
float valeur;
char val[7];
const uint8_t point_pin[] = {1, 2, 3, 5, 7, 9, 10, 11, 12, 34, 35, 36, 37, 38, 39, 40};
const char *point[] = {"domestia/idb2/etat", "domestia/idb1/etat", "domestia/idb3/etat", "domestia/idb4/etat", "domestia/idb5/etat", "domestia/ida5/etat", "domestia/ida4/etat", "domestia/ida3/etat", "domestia/idc4/etat", "domestia/idc3/etat", "domestia/idc1/etat", "domestia/idc2/etat", "domestia/ida2/etat", "domestia/idb6/etat", "domestia/ida6/etat", "domestia/ida1/etat"};
bool point_etat[16];
bool point_etat_precedent[16];
int point_index;
bool premiere_fois = true;
int ledcompte = 0;
void lecture();
// === MQTT EVENTS ===
void onMqttConnect(bool sessionPresent)
{
Serial.println("✅ Connecté au broker MQTT !");
mqtt_statut = true;
digitalWrite(rouge_pin, HIGH);
digitalWrite(bleu_pin, LOW);
digitalWrite(vert_pin, HIGH);
mqtt_client.subscribe("homeassistant/status", 1);
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
Serial.printf("⚠️ Déconnecté du broker MQTT (raison: %d)\n", static_cast<uint8_t>(reason));
mqtt_statut = false;
digitalWrite(rouge_pin, HIGH);
digitalWrite(bleu_pin, HIGH);
digitalWrite(vert_pin, LOW);
}
void onMqttPublish(uint16_t packetId)
{
Serial.printf("📤 Publication MQTT confirmée (packetId: %u)\n", packetId);
}
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
message = "";
for (size_t i = 0; i < len; i++)
{
message += payload[i];
}
Serial.printf("📩 Message reçu [%s] : %s\n", topic, message.c_str());
if (strcmp(topic, "homeassistant/status") == 0)
{
if (message == "online")
{
premiere_fois = true;
}
}
}
// Timers
#include <TickTwo.h>
TickTwo delai_lecture(lecture, 500);
void setup()
{
pinMode(rouge_pin, OUTPUT);
pinMode(vert_pin, OUTPUT);
pinMode(bleu_pin, OUTPUT);
digitalWrite(rouge_pin, LOW);
digitalWrite(vert_pin, LOW);
digitalWrite(bleu_pin, LOW);
for (int i = 0; i < sizeof(point_pin); i++)
{
pinMode(point_pin[i], INPUT_PULLUP);
}
Serial.begin(115200);
delay(100);
//----WIFI----
WiFi.setSleep(false);
WiFi.setAutoReconnect(true);
WiFi.config(ip, gw, mask, ns);
WiFi.onEvent(WiFiEvent); // Attache la fonction de gestion des événements
WiFi.begin(sid, wifi_sesame);
// MQTT
mqtt_client.setServer(mqtt_serveur, mqtt_port);
mqtt_client.setCredentials(mqtt_identifiant, mqtt_sesame);
mqtt_client.setClientId("domestiaetat"); // nom du client mqtt
mqtt_client.onConnect(onMqttConnect);
mqtt_client.onDisconnect(onMqttDisconnect);
mqtt_client.onMessage(onMqttMessage);
mqtt_client.onPublish(onMqttPublish);
delai_lecture.start();
}
void loop()
{
delai_lecture.update();
if (wifi_statut == true and mqtt_statut == false and millis() - derniere_connexion_mqtt > mqtt_timeout)
{
Serial.println("Tentative de connexion MQTT");
mqtt_client.connect();
derniere_connexion_mqtt = millis();
}
}
void lecture()
{
if (premiere_fois == true)
{
for (int i = 0; i < sizeof(point_pin); i++)
{
point_etat[i] = digitalRead(point_pin[i]);
if (mqtt_client.connected())
{
mqtt_client.publish(point[i], 1, false, point_etat[i] ? "OFF" : "ON");
point_etat_precedent[i] = point_etat[i];
premiere_fois = false;
}
delay(10);
}
}
else
{
for (int i = 0; i < sizeof(point_pin); i++)
{
point_etat[i] = digitalRead(point_pin[i]);
if (point_etat[i] != point_etat_precedent[i])
{
if (mqtt_client.connected())
{
mqtt_client.publish(point[i], 1, false, point_etat[i] ? "OFF" : "ON");
}
point_etat_precedent[i] = point_etat[i];
}
delay(10);
}
}
}
Les éléments 3D
Ci-dessous, de gauche à droite et de haut en bas :
- Un élément de 12 LDR imprimé en 3D 2 couleurs;
- Un élément de positionnement pour positionner la LDR dans le tube;
- La plaque de montage de l’alimentation verso et recto.
Intégration au coffret électrique
L’alimentation est placée à l’intérieur, dans le bas du coffret.
Il faut impérativement couper le courant au disjoncteur général avant de réaliser ce branchement ! Si vous n’avez pas les compétences nécessaires, n’effectuez pas ce travail !
Il est possible de remplacer l’alimentation par un bloc externe se branchant dans une prise de courant.
Fonctionnement
Ici, c’est plus simple !
Lorsqu’un relais de la carte DOMESTIA change d’état, il est détecté par la LDR et le microcontrôleur transmet l’information par un message MQTT. La centrale Home Assistant ainsi que le module de commande du début d’article reçoivent le message et affichent l’état sur le tableau de bord et sur l’écran TFT.
Conclusion
Le système est maintenant complet et fonctionne comme souhaité.











