Commande d'éclairage MQTT pour cartes domotiques DOMESTIA

Intégration domotique DOMESTIA à ma centrale Home Assistant

Commande d'éclairage MQTT pour cartes domotiques DOMESTIA

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

  • Un module de commande agissant comme les poussoirs de l’appartement;
  • Un module de retour d’états pour que ma centrale soit informée de tout changement d’état des relais.

Le module de commande

Cahier des charges

  • Alimentation secteur 220V
  • un écran TFT couleur pour afficher les points lumineux et leur état;
  • Un bouton rotatif avec contact pour sélection et activation des points lumineux, soit l’équivalent de 17 poussoirs dont un pour éteindre tout;
  • Un boîtier de petite dimension à imprimer en 3D;
  • Un connecteur externe pour le BUS Domestia;
  • L’adaptation du rétro éclairage de l’écran en fonction de la luminosité ambiante.

Matériel

  • 1 microcontrôleur WiFi à 27 GPIO. J’avais un ESP32-S2 Mini en stock;
  • 1 écran TFT. J’avais un modèle 1.8″ SPI en stock;
  • 1 encodeur rotatif avec contact;
  • 3 modules d’identification DOMESTIA DM006 (équivalent : 18 poussoirs);
  • 3 circuits imprimés double face de 50×70 mm au pas de 1,25 mm;
  • 18 relais DIL 5V DC GORDOS de type 835-C;
  • 18 transistors 2N2222 ou équivalent;
  • 18 résistances 1200 Ω;
  • 3 résistances de 10 000 Ω;
  • 1 transistor BC238 ou équivalent;
  • 1 résistance de 120 Ω;
  • 1 résistance de 560 Ω;
  • 2 connecteurs de type PCT-5211;
  • 1 fixation pour 2 connecteurs PCT-5211 imprimée en 3D;
  • 1 bouton pour l’encodeur;
  • 1 câble d’alimentation avec fiche plate;
  • Une alimentation MeanWell IRM10-05. Trouvée chez Reichelt;

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

Schéma commande Domestia

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.

Commande d'éclairage DOMESTIA module

Dans la boîte (3 circuits)

Commande d'éclairage Domestia Intérieur

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 :
17.89 KB
#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

Retour d'état MQTT pour cartes domotiques Domestia

Rôle

Transmettre l’état réel de chaque relais à ma centrale domotique !

Cahier des charges

  • Alimentation secteur 220V;
  • Capter l’état de la LED de chaque relais sans toucher aux cartes DOMESTIA;
  • Un bouton de redémarrage du microcontrôleur;
  • Une LED tricolore affichant l’état de la connexion du module;
  • Un connecteur d’alimentation;
  • La possibilité de démonter le tout sans rien abîmer.

Matériel

  • 1 microcontrôleur WiFi à 27 GPIO. J’avais un ESP32-S2 Mini en stock;
  • 2 réseaux de résistances de 9 x 10 000 Ω;
  • 16 LDR de type 5506;
  • 1 condensateur 100 nF;
  • 1 condensateur de 10 µF 16 V;
  • 1 LED RAGB (Rouge, Anode commune, Vert, Bleu);
  • 2 résistances 5600 Ω;
  • 1 résistances de 18 000 Ω;
  • 1 bouton poussoir NO;
  • 4 soquets DIL 14 pins;
  • 4 connecteurs DIL 14 pins pour câble plat;
  • Environ 50 cm de câble plat 14 conducteurs;
  • 2 chassis imprimés en 3D à glisser dans le coffret en regard des LEDs des relais;
  • 1 base imprimée en 3D pour le circuit imprimé du microcontrôleur;
  • 1 connecteur 2 broches mâle à souder;
  • 1 connecteur 2 broches femelle sur un câble rouge et noir;
  • 1 circuit imprimé esthétique et de qualité pour la partie microcontrôleur;
  • 2 petits morceaux de circuits imprimés pour deux soquets DIL 14 pins;
  • Une alimentation MeanWell 3,3V RS-15-3.3 trouvée sur Aliexpress.

Le schéma

Schéma Retour Domestia

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.

9.02 MB

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 :
4.86 KB
#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.

Commande d’éclairage Domestia

Conclusion

Le système est maintenant complet et fonctionne comme souhaité.

Laissez un commentaire et posez vos questions ci-dessous

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *