MySensors -> MQTT

Bonjour à tous,

Même si le développement du protocole MySensors est à l’arrêt depuis quelques années, je l’utilise encore et j’ai une partie de ma domotique qui est basé sur ce protocole. Pour une question de pérennité j’ai voulu me séparer du plugin du même nom afin de pouvoir continuer à utiliser et fabriquer des nœuds MySensors.
C’est pour cela que je me suis tournés vers le protocole MQTT qui est très en vogue en ce moment et qui, je pense, à un avenir certain pour notre domotique.

Avant de communiquer en MQTT, il y a un prérequit ! A savoir que les nœuds de votre installation doivent avoir un ID défini car contrairement à une gateway classique, celle-ci n’attribue pas des numéros au nœuds.

#define MY_NODE_ID 6 //exemple

Nous pouvons déjà retrouver une Gateway sur les page du site MySensors, mais j’ai voulu allé plus loin. J’ai créée un PCB pour pouvoir faire 2 gateway différent : NRF24L01 et PJON vers MQTT

La Gateway NRF24L01

Basé sur un Mega 2650 Pro Mini pour le micro contrôleur, un W5500 pour l’Ethernet et 1 E01-2G4M27D (NRF24L01) pour la communication avec les nœuds.

Comme sa petite sœur, la gateway est équipé d’une antenne de routeur WIFI 2.4Ghz. Sur la photo si dessous, elle a évolué par rapport au départ. J’ai rajouté une DS18b20, ainsi qu’un diviseur de tension à résistance pour la monitorer. Oui la monitorer, car la gateway peut avoir des capteurs.


Capture d'écran 2024-11-10 232238

Voici le sketch de base sans les capteurs, pour avoir qu’un seul topic pour la gateway j’ai précisé comme ceci

// Set this node's subscribe and publish topic prefix
#define MY_MQTT_PUBLISH_TOPIC_PREFIX "nrf24l01"
#define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "nrf24l01/cmd"

Le sketch complet :

//#define MY_DEBUG
#define MY_RADIO_RF24
#define MY_RF24_PA_HIGH  // Options: MY_RF24_PA_MAX | MY_RF24_PA_HIGH | MY_RF24_PA_LOW | MY_RF24_PA_MIN

#define W5500_RESET_PIN 4

#define MY_GATEWAY_MQTT_CLIENT

#define MY_SOFTSPI
#define MY_SOFT_SPI_SCK_PIN 34   //34
#define MY_SOFT_SPI_MISO_PIN 32  //32
#define MY_SOFT_SPI_MOSI_PIN 36  //36

#define MY_RF24_CE_PIN 49
#define MY_RF24_CS_PIN 53

//#define MY_SECURITY_SIMPLE_PASSWD "pass"
//#define MY_SIGNING_SIMPLE_PASSWD
//#define MY_ENCRYPTION_SIMPLE_PASSWD
//#define MY_DEBUG_VERBOSE_SIGNING

#define NODE_NAME "Gateway NRF24L01 -> MQTT"
#define NODE_VERSION "1.1"

// Enable these if your MQTT broker requires username/password
//#define MY_MQTT_USER "user"
//#define MY_MQTT_PASSWORD "mdp"

// If using static ip you can define Gateway and Subnet address as well
#define MY_IP_GATEWAY_ADDRESS 192, 168, 1, 1
#define MY_IP_SUBNET_ADDRESS 255, 255, 255, 0

// Set this node's subscribe and publish topic prefix
#define MY_MQTT_PUBLISH_TOPIC_PREFIX "nrf24l01"
#define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "nrf24l01/cmd"

// Set MQTT client id
#define MY_MQTT_CLIENT_ID "nrf24l01"

// Enable MY_IP_ADDRESS here if you want a static ip address (no DHCP)
#define MY_IP_ADDRESS 192, 168, 1, 115

// MQTT broker ip address or url. Define one or the other.
//#define MY_CONTROLLER_URL_ADDRESS "m20.cloudmqtt.com"
#define MY_CONTROLLER_IP_ADDRESS 192, 168, 1, 36

// The port to keep open on node server mode / or port to contact in client mode
#define MY_PORT 1883

// The MAC address can be anything you want but should be unique on your network.
//#define MY_MAC_ADDRESS 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED

#include <Ethernet.h>
#include <MySensors.h>

#define IP_ID 0

MyMessage msgIp(IP_ID, V_TEXT);

// Fonction pour convertir l'adresse IP en chaîne
void getIpString(char* buffer) {
  snprintf(buffer, 16, "%d.%d.%d.%d", MY_IP_ADDRESS);
}

void before() {
  pinMode(W5500_RESET_PIN, OUTPUT);
  digitalWrite(W5500_RESET_PIN, LOW);
  delay(10);
  digitalWrite(W5500_RESET_PIN, HIGH);
  delay(1000);
}

void setup() {
}

void presentation() {
  // Envoyer les informations du sketch à la passerelle et au contrôleur
  sendSketchInfo(NODE_NAME, NODE_VERSION);

  present(IP_ID, S_INFO, "Adresse IP");
 
  char ipStr[16];
  getIpString(ipStr);      // Convertir l'adresse IP en chaîne
  send(msgIp.set(ipStr));  // Envoyer l'adresse IP
}

void loop() {
}

Les PIN pour la gateway NRF24L01:

Pour le NRF24L01:

CNS 53
CE 49
MOSI 36
SCK 34
MISO 32

Pour le W5500:

RST 4
MISO 50
INT 2
CS 10
SCK 52
MOSI 51

Le topic est construit comme ça :

MY_MQTT_PUBLISH_TOPIC_PREFIX/FROM-NODE-ID/SENSOR-ID/CMD-TYPE/ACK-FLAG/SUB-TYPE

La gateway PJON (en cours de développement)

Pour faire court, le PJON est un protocole filaire à un fil comme le 1Wire. Il faut utiliser une librairie spéciale qui intègre le protocole.
J’ai fait cette passerelle pour l’intégrer dans un tableau électrique pour pouvoir piloter des cartes relais, cartes entrées ,…
La gateway PJON embarque un INA226 pour monitorer les conso électrique, car l’alimentation des cartes filles passeront par la gateway.

Voici le fichier Gerber du PCP, il n’y a pas de schéma !
Gerber_MySensors-Gateway-MQTT-v2.zip.txt (142,0 Ko)

Les PIN sont marqués au dos du PCB

3 « J'aime »

Bonjour,

Petite mise à jour de la gateway avec différentes statistiques remonté :

  • Le nombre de message reçu, envoyé et en erreur
  • L’adresse IP et MAC
  • L’uptime de la passerelle
  • Le pourcentage de mémoire disponible
  • La température interne avec un DS18b20
  • La tension du 5V et du 3.3V

widget

Cette fois ci j’ai fait un schéma, et j’ai modifié en volant le PCB de la gateway

Et pour finir le sketch

/*
31/10/2023 
version 1.0 : Version initiale

20/11/2024 
version 1.1 : Envoie l'adresse IP au controleur

08/12/2024 
version 1.2 : Ajout de UPTIME et l'envoie de l'adresse MAC au controleur

12/12/2024
version 1.3 : Ajout des statistiques des messages (send, received, error)
              Ajout info mémoire libre
*/

#define NODE_NAME "Gateway NRF24L01 -> MQTT"
#define NODE_VERSION "1.3"

// Variables pour gérer l'intervalle de mesure
const unsigned long interval = 140000;  // 2 minutes 20 s

#define SRAM_TOTAL 8192  //mémoire totale sur un MEGA 2650 (2048 pour un Nano)

// Permet de personnaliser les comportements liés aux indications
#define MY_INDICATION_HANDLER

//#define MY_DEBUG
#define MY_RADIO_RF24
//#define MY_DEBUG_VERBOSE_RF24
#define MY_RF24_PA_HIGH  // Options: MY_RF24_PA_MAX | MY_RF24_PA_HIGH | MY_RF24_PA_LOW | MY_RF24_PA_MIN

#define W5500_RESET_PIN 4

#define MY_GATEWAY_MQTT_CLIENT
#define MY_HOSTNAME "Gateway.NRF24l01"

#define MY_SOFTSPI
#define MY_SOFT_SPI_SCK_PIN 34
#define MY_SOFT_SPI_MISO_PIN 32
#define MY_SOFT_SPI_MOSI_PIN 36

#define MY_RF24_CE_PIN 49
#define MY_RF24_CS_PIN 53

#define ONE_WIRE_BUS 33  // Pin où le capteur Dallas est connecté

//#define MY_SECURITY_SIMPLE_PASSWD "motdepasse"
//#define MY_SIGNING_SIMPLE_PASSWD
//#define MY_ENCRYPTION_SIMPLE_PASSWD
//#define MY_DEBUG_VERBOSE_SIGNING

// Enable these if your MQTT broker requires username/password
//#define MY_MQTT_USER "mqtt"
//#define MY_MQTT_PASSWORD "pass"

// If using static ip you can define Gateway and Subnet address as well
#define MY_IP_GATEWAY_ADDRESS 192, 168, 1, 1
#define MY_IP_SUBNET_ADDRESS 255, 255, 255, 0

// Set this node's subscribe and publish topic prefix
#define MY_MQTT_PUBLISH_TOPIC_PREFIX "nrf24l01"
#define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "nrf24l01/cmd"

// Set MQTT client id
#define MY_MQTT_CLIENT_ID "nrf24l01"

// Enable MY_IP_ADDRESS here if you want a static ip address (no DHCP)
#define MY_IP_ADDRESS 192, 168, 1, 115

// MQTT broker ip address or url. Define one or the other.
//#define MY_CONTROLLER_URL_ADDRESS "m20.cloudmqtt.com"
#define MY_CONTROLLER_IP_ADDRESS 192, 168, 1, 36

// The port to keep open on node server mode / or port to contact in client mode
#define MY_PORT 1883

// The MAC address can be anything you want but should be unique on your network.
#define MY_MAC_ADDRESS 0x07, 0xAD, 0xBB, 0xE1, 0x5E, 0xE3

#include <Ethernet.h>
#include <MySensors.h>
#include <DallasTemperature.h>
#include <OneWire.h>

#define LAN_ID 0
#define TENSION5V_ID 1
#define TENSION3_3V_ID 2
#define TEMPERATURE_ID 3
#define STAT_ID 4

//Variables Uptime
unsigned long totalMillis = 0;  // Temps total accumulé
unsigned long lastMillis = 0;   // Dernière lecture de millis()

// Variables pour le comptage
unsigned long receivedMessageCount = 0;  // Compteur pour les messages reçus
unsigned long sentMessageCount = 0;      // Compteur pour les messages envoyés
unsigned long errorMessageCount = 0;     // Comptreur pour les messages NACK

OneWire oneWire(ONE_WIRE_BUS);        // Instance OneWire pour le capteur
DallasTemperature sensors(&oneWire);  // Référence OneWire passée à DallasTemperature
float lastTemperature = 0;
float lastVcc5v = 0;
float lastVcc3_3v = 0;
float lastMem = 0;

// Coefficient diviseur du pont de résistance
const float COEFF_PONT_DIVISEUR_VIN = 1.541;
const float COEFF_PONT_DIVISEUR_3_3V = 0.965;
int raw_vin;
int raw_ref;
float real_vin;

// Déclare des variables spéciales utilisées par le compilateur pour suivre les segments de mémoire
extern unsigned int __heap_start, *__brkval;  // Début du tas (heap) et pointeur actuel de rupture (break value)
extern char __bss_end;                        // Fin de la section BSS (variables statiques/non initialisées)

// Fonction pour calculer la mémoire libre (en octets) dans la SRAM
int freeMemory() {
  int free_memory;  // Variable locale pour stocker la mémoire libre

  // Si __brkval est nul, cela signifie que le tas (heap) n'a pas été utilisé
  if ((int)__brkval == 0) {
    // La mémoire libre est la distance entre le sommet actuel de la pile (stack)
    // et le début du tas (heap)
    free_memory = (int)&free_memory - (int)&__heap_start;
  } else {
    // Sinon, la mémoire libre est la distance entre le sommet actuel de la pile
    // et l'extrémité actuelle du tas
    free_memory = (int)&free_memory - (int)__brkval;
  }

  return free_memory;  // Retourne la mémoire libre calculée
}


MyMessage msgIp(LAN_ID, V_VAR1);
MyMessage msgMac(LAN_ID, V_VAR2);
MyMessage msg5v(TENSION5V_ID, V_VOLTAGE);
MyMessage msg3_3v(TENSION3_3V_ID, V_VOLTAGE);
MyMessage msgTemp(TEMPERATURE_ID, V_TEMP);
MyMessage msgUp(STAT_ID, V_VAR1);
MyMessage msgSent(STAT_ID, V_VAR2);
MyMessage msgRec(STAT_ID, V_VAR3);
MyMessage msgErr(STAT_ID, V_VAR4);
MyMessage msgMem(STAT_ID, V_VAR5);


// Fonction pour convertir l'adresse IP en chaîne
void getIpString(char* buffer) {
  snprintf(buffer, 16, "%d.%d.%d.%d", MY_IP_ADDRESS);
}

// Fonction pour convertir l'adresse MAC en chaîne
void getMacString(char* buffer) {
  uint8_t mac[6] = { MY_MAC_ADDRESS };
  snprintf(buffer, 18, "%02x:%02x:%02x:%02x:%02x:%02x",
           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

// Fonction pour récuperer les indications RX et TX
void indication(indication_t ind) {
  switch (ind) {
    case INDICATION_TX:
      sentMessageCount++;
      send(msgSent.set(sentMessageCount));
      break;
    case INDICATION_ERR_TX:
      errorMessageCount++;
      send(msgErr.set(errorMessageCount));
      break;
    case INDICATION_RX:
      receivedMessageCount++;
      send(msgRec.set(receivedMessageCount));
      break;
  }
}

void before() {
  // Reset du W5500 lors du démarrage
  pinMode(W5500_RESET_PIN, OUTPUT);
  digitalWrite(W5500_RESET_PIN, LOW);
  delay(10);
  digitalWrite(W5500_RESET_PIN, HIGH);
  delay(1000);
}

void setup() {
  sensors.begin();                      // Initialiser la bibliothèque DallasTemperature
  sensors.setWaitForConversion(false);  // Non-bloquant pour requestTemperatures()
}

// Mesure la référence interne à 1.1 volts
unsigned int analogReadReference(void) {

  // Elimine toutes charges résiduelles
  ADCSRB &= ~(1 << MUX5);
  ADMUX = 0x5F;

  delayMicroseconds(5);

  // Sélectionne la référence interne à 1.1 volts comme point de mesure, avec comme limite haute VCC
  ADCSRB &= ~(1 << MUX5);
  ADMUX = 0x5E;

  delayMicroseconds(200);

  // Active le convertisseur analogique -> numérique
  ADCSRA |= (1 << ADEN);

  // Lance une conversion analogique -> numérique
  ADCSRA |= (1 << ADSC);

  // Attend la fin de la conversion
  while (ADCSRA & (1 << ADSC))
    ;

  // Récupère le résultat de la conversion
  return ADCL | (ADCH << 8);
}

void presentation() {
  // Envoyer les informations du sketch à la passerelle et au contrôleur
  sendSketchInfo(NODE_NAME, NODE_VERSION);

  present(LAN_ID, S_CUSTOM, "Réseau");
  present(TENSION5V_ID, S_MULTIMETER, "VCC 5V");
  present(TENSION3_3V_ID, S_MULTIMETER, "VCC 3.3V");
  present(TEMPERATURE_ID, S_TEMP, "Température");
  present(STAT_ID, S_CUSTOM, "Statistiques");

  // Envoi de l'IP
  char ipStr[16];
  getIpString(ipStr);      // Convertir l'adresse IP en chaîne
  send(msgIp.set(ipStr));  // Envoyer l'adresse IP

  // Envoi de l'adresse MAC
  char macStr[18];
  getMacString(macStr);      // Convertir l'adresse MAC en chaîne
  send(msgMac.set(macStr));  // Envoyer l'adresse MAC

  // Envoi mise à zéro indication
  send(msgSent.set(sentMessageCount));
  send(msgErr.set(errorMessageCount));
  send(msgRec.set(receivedMessageCount));
}

void loop() {

  uptime();
  memoire_libre();
  wait(interval);

  mesureTemp();
  wait(interval);

  mesure5v();
  wait(interval);

  mesure3_3v();
  wait(interval);

}

void uptime() {
  // Calcul du temps écoulé
  unsigned long currentMillis = millis();
  totalMillis += (currentMillis - lastMillis);  // Gestion automatique de l'overflow
  lastMillis = currentMillis;

  // Conversion en secondes
  unsigned long uptimeSeconds = totalMillis / 1000;

  // Calcul des jours, heures et minutes
  unsigned int days = uptimeSeconds / 86400;
  uptimeSeconds %= 86400;
  unsigned int hours = uptimeSeconds / 3600;
  uptimeSeconds %= 3600;
  unsigned int minutes = uptimeSeconds / 60;

  // Construction de la chaîne JJ/HH/MM
  char uptimeFormatted[32];
  snprintf(uptimeFormatted, sizeof(uptimeFormatted), "%u jour%s, %02uh %02umin",
           days, (days > 1 ? "s" : ""), hours, minutes);

  // Envoi de l'uptime
  send(msgUp.set(uptimeFormatted));
}

void mesureTemp() {
  sensors.requestTemperatures();  // Récupérer la température
  int16_t conversionTime = sensors.millisToWaitForConversion(sensors.getResolution());
  wait(conversionTime);  // attendre pendant la conversion

  // Lire et arrondir la température à un chiffre après la virgule
  float temperature = static_cast<float>(static_cast<int>(sensors.getTempCByIndex(0) * 10.)) / 10.;

  // Envoyer la température seulement si elle a changé et qu'elle est valide
  if (temperature != lastTemperature && temperature != -127.00 && temperature != 85.00) {
    send(msgTemp.set(temperature, 1));  // Envoyer la nouvelle température
    lastTemperature = temperature;      // Mettre à jour la dernière température
  }
}

void mesure5v() {
  // Lecture de la valeur analogique sur la broche 0 (A0)
  raw_vin = analogRead(A0);
  raw_ref = analogReadReference();
  // Calcul de la tension réelle avec un produit en croix
  real_vin = ((raw_vin * 1.1) / raw_ref) * COEFF_PONT_DIVISEUR_VIN;

  // Envoyer la valeur seulement si elle a changé
  if (real_vin != lastVcc5v) {
    send(msg5v.set(real_vin, 2));
    lastVcc5v = real_vin;  // Mettre à jour la dernière valeur
  }
}

void mesure3_3v() {
  // Lecture de la valeur analogique sur la broche 1 (A1)
  raw_vin = analogRead(A1);
  raw_ref = analogReadReference();
  // Calcul de la tension réelle avec un produit en croix
  real_vin = ((raw_vin * 1.1) / raw_ref) * COEFF_PONT_DIVISEUR_3_3V;

  // Envoyer la valeur seulement si elle a changé
  if (real_vin != lastVcc3_3v) {
    send(msg3_3v.set(real_vin, 2));
    lastVcc3_3v = real_vin;  // Mettre à jour la dernière valeur
  }
}

void memoire_libre() {
  float free_mem = freeMemory();                     // Obtenir la mémoire libre
  float percentage = (free_mem * 100) / SRAM_TOTAL;  // Calculer le pourcentage de mémoire libre

  // Envoyer la valeur seulement si elle a changé
  if (percentage != lastMem) {
    send(msgMem.set(percentage, 1));
    lastMem = percentage;  // Mettre à jour la dernière valeur
  }
}