Ajout gestion LM18220

This commit is contained in:
Serge NOEL
2026-08-05 11:51:54 +02:00
parent fd3fc380d8
commit be3dbaa824
7 changed files with 207 additions and 46 deletions
+171 -13
View File
@@ -1,22 +1,180 @@
# MQTT-DCC ESP32 PlatformIO Project
This project runs on an ESP32 (PlatformIO) and:
- Connects to WiFi and an MQTT broker
- Listens on the `dcc/command` topic for JSON accessory commands
- Generates a simplified DCC accessory packet and simulates sending it via a GPIO pin
- Publishes status/acknowledgements to `dcc/status`
Ce projet cible un ESP32 sous PlatformIO et permet de :
- se connecter à un réseau Wi-Fi
- se connecter à un broker MQTT
- recevoir des commandes d'accessoires DCC au format JSON sur le topic `dcc/command`
- générer un paquet DCC et le transmettre sur une alimenation à base de LM18200 via sortie GPIO
- publier des retours d'état sur le topic `dcc/status`
Setup:
1. Edit `include/config.h` and set `WIFI_SSID`, `WIFI_PASSWORD`, `MQTT_SERVER`, and other values.
2. Build and upload with PlatformIO.
## Fonctionnalités principales
Command format (JSON published to `dcc/command`):
- Configuration Wi-Fi via un portail web AP si la connexion Wi-Fi échoue
- Stockage des identifiants Wi-Fi dans la mémoire non volatile (NVS)
- Stockage des identifiants MQTT et nom d'hôte dans la mémoire non volatile (NVS)
- Séparation du code Wi-Fi et MQTT dans des modules dédiés
- Génération de paquets d'accessoire DCC
## Configuration
1. Ouvrir `include/config.h`.
2. Vérifier ou modifier les paramètres MQTT :
- `MQTT_SERVER`
- `MQTT_PORT`
- `MQTT_TOPIC_CMD`
- `MQTT_TOPIC_STATUS`
- `MQTT_TOPIC_RESPONSE`
3. Si nécessaire, ajuster les pins DCC :
- `DCC_DIR_PIN`
- `DCC_PWM_PIN`
4. Le SSID et le mot de passe Wi-Fi sont stockés dans la mémoire non volatile et sont configurables via le portail AP.
> Ce projet utilise désormais la librairie `locoduino/DCCpp` pour générer un vrai signal DCC NMRA compatible avec un driver LM18200.
> `DCC_DIR_PIN` est utilisé pour le signal DCC et `DCC_PWM_PIN` pour l'activation de l'interface.
- Au démarrage, l'ESP tente de se connecter au Wi-Fi avec les identifiants enregistrés.
- Si l'accès Wi-Fi n'est pas disponible, il démarre un point d'accès nommé `DCC_Config`. Et permet via un smartphone de définir les SSID / Key à l'aide d'un portail captif
- Ouvrir le navigateur à l'adresse IP affichée dans la console série pour configurer le SSID et le mot de passe.
- Après enregistrement, l'ESP redémarre et tente de se reconnecter au Wi-Fi.
## Format de commande MQTT
Publier un message JSON sur le topic `dcc/command` :
```json
{
"id": "optional-correlation-id",
"id": "1234",
"address": 123,
"state": true
}
```
Notes:
- The DCC packet generator here is a simplified representation. For NMRA-compliant output you will need a timing-accurate bitstream and the proper packet framing.
- Want me to add NMRA timing/pulse generation, OTA, or an example MQTT client for testing? Ask and I'll add it.
- `id` : identifiant éventuel pour corrélation
- `address` : adresse de l'accessoire
- `state` : `true` pour activer / `false` pour désactiver
## Exemple Node-RED
Dans Node-RED, utilisez un noeud `inject` puis un noeud `mqtt out` :
1. Configurez un noeud `mqtt broker` avec l'adresse de votre broker MQTT.
2. Ajoutez un noeud `inject` et définissez la charge utile (`payload`) avec le JSON ci-dessus.
3. Mettez le `topic` du noeud `mqtt out` à `dcc/command`.
4. Connectez le noeud `inject` au noeud `mqtt out`.
Exemple de flux simple :
```json
[{"id":"inject_dcc","type":"inject","z":"flow","name":"Commande DCC","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"dcc/command","payload":"{\"id\":\"1234\",\"address\":123,\"state\":true}","payloadType":"json","wires":[["mqtt_out"]]},{"id":"mqtt_out","type":"mqtt out","z":"flow","name":"Envoi MQTT","topic":"dcc/command","qos":"0","retain":"false","broker":"mqtt_broker"}]
```
Cela permet dintégrer facilement le projet dans un système global basé sur Node-RED.
## Déploiement
Compiler et téléverser avec PlatformIO :
```bash
pio run -e esp32dev --target upload
```
## Structure du projet
- `src/main.cpp` : programme principal
- `src/WifiManager.cpp` : gestion du Wi-Fi et du portail de configuration
- `src/MqttManager.cpp` : gestion de la connexion MQTT
- `lib/Dcc/` : génération de paquets DCC
- `include/config.h` : paramètres de configuration
## Améliorations possibles
- ajout d'un vrai générateur NMRA DCC avec horloge de sortie
- interface web de configuration plus complète
- gestion OTA
- publication d'un message de statut plus riche
# Annexe - Le DCC
Le pilotage des accessoires en DCC classique (NMRA) et via l'écosystème DCC-EX repose sur le même principe fondamental : l'envoi de paquets de données numériques sur les rails (ou un bus dédié) à destination de décodeurs d'accessoires (aiguillages, signaux, relais).
Cependant, la manière de spécifier l'Adresse et la Valeur (Commande/État) diffère légèrement selon qu'il s'agit du protocole bas niveau sur les rails ou des commandes textuelles envoyées à une centrale DCC-EX.
## Le Pilotage DCC Classique
Dans le standard NMRA (S-9.2.1), les accessoires sont pilotés via des paquets d'accessoires de base (Basic Accessory Decoder Packet).
### L'Adresse
Contrairement aux locomotives qui ont une adresse simple (1 à 10239), l'adressage des accessoires en DCC est historiquement découpé :
- **L'Adresse de Décodeur** (Decoder Address) : Un décodeur physique d'accessoires (ex: une carte à 4 sorties) possède une adresse principale (1 à 511).
- **La Sortie / Pair** (Sub-address / Output) : Chaque décodeur gère généralement 4 sorties (paires d'adresses).
- **L'Adresse Unitaire** (Linear / Output Address) : Pour simplifier, les centrales et logiciels traduisent cela en une adresse linéaire continue (1, 2, 3, 4, 5, 6...).
On a :
```
Adresse Linéaire = ((AdresseDécodeur - 1) * 4) + Numéro de Sortie
```
### La Valeur (Position et État)
Un paquet d'accessoire DCC ne transmet pas une vitesse (0-128) comme une locomotive, mais un bit de direction et un bit d'activation :
- Direction / Position (1 bit) :
- 1 = Dévie / Closed / Red / Active (ex: Aiguillage en déviation)
- 0 = Droit / Thrown / Green / Inactive (ex: Aiguillage en voie directe)
- Power / Enable (1 bit) :
- 1 = Bobine sous tension (Commande envoyée)
- 0 = Bobine coupée (Relâchement de l'impulsion pour éviter de brûler le moteur solénoïde)
## Le Pilotage avec DCC-EX
DCC-EX est un système open-source pour Arduino/ESP32. Il simplifie la complexité des octets NMRA en proposant une API texte très simple via port série, Wi-Fi ou Bluetooth.
Dans DCC-EX, les accessoires sont pilotés selon deux approches : **Directe** (numéro de pin ou paquet DCC) ou **Virtuelle** (Turnouts/Routes).
### Méthode 1 : Commande Directe d'Aiguillage / Accessoire DCC
Pour envoyer une commande DCC d'accessoire directement sur les rails à une adresse linéaire
```
a <ADRESSE> <SUB_ADDR> <ACTIF>
```
ou plus communément pour un aiguillage (Turnout) :
```
<T <ADRESSE> <VALEUR>>
```
Paramètres :
- **ADRESSE** : L'adresse linéaire de l'accessoire (ex: 1 à 2048).
- **VALEUR / ÉTAT** :
- 1 : Thrown (Dévié / Actif)
- 0 : Closed (Droit / Inactif)
- C : Toggle / Inverser la position actuelle.
Exemple :
- <T 1 12> -> Met l'aiguillage DCC N°12 en position Déviée (Thrown).
- <T 0 12> -> Met l'aiguillage DCC N°12 en position Directe (Closed).
### Méthode 2 : Définition des "Turnouts" (Accessoires déclarés dans DCC-EX)
DCC-EX permet d'associer des adresses virtuels ou des broches physiques (Servomoteurs, Relais sur Arduino) à des objets "Turnout".
Dans le fichier de configuration myAutomations.cpp ou config.h, vous définissez vos accessoires :
```cpp
// Aiguillage sur décodeur DCC à l'adresse 5
Turnout::createDCC(5, "Aiguillage Gare");
// Aiguillage piloté directement par un Servomoteur connecté sur la Pin 9 de l'Arduino
Turnout::createSERVO(6, 9, 100, 400, 1, "Aiguillage Servo");
```
Une fois définis, ils se pilotent via le protocole API texte :
```
<T <ID> <COMMANDE>>
```
- Exemple : <T 5 T> (Mettre le Turnout 5 en Thrown).
- Exemple : <T 5 C> (Mettre le Turnout 5 en Closed).
+4 -3
View File
@@ -2,14 +2,15 @@
#define CONFIG_H
// MQTT broker settings (leave MQTT server values as needed)
#define MQTT_SERVER "192.168.1.10"
#define MQTT_SERVER "192.168.1.53"
#define MQTT_PORT 1883
#define MQTT_TOPIC_CMD "dcc/command"
#define MQTT_TOPIC_STATUS "dcc/status"
#define MQTT_TOPIC_RESPONSE "dcc/response"
// Pin used for simulated DCC output (adjust as needed)
#define DCC_OUTPUT_PIN 27
// Pin used for DCC output (LM18200 IC or similar)
#define DCC_DIR_PIN 27
#define DCC_PWM_PIN 26
// Access point SSID shown when no WiFi credentials are configured
#define DEFAULT_AP_SSID "DCC_Config"
+13 -19
View File
@@ -1,26 +1,20 @@
#include "Dcc.h"
#include <Arduino.h>
std::vector<uint8_t> Dcc::generateAccessoryPacket(uint16_t addr, bool activate) {
// This produces a simple, application-level packet representation.
// For NMRA-compliant bitstreams you will need a timing-accurate generator.
std::vector<uint8_t> p;
p.push_back((uint8_t)(addr >> 8));
p.push_back((uint8_t)(addr & 0xFF));
p.push_back(activate ? 1 : 0);
return p;
void Dcc::begin(int dirPin, int pwmPin) {
DCCpp::begin();
DCCpp::beginMain(UNDEFINED_PIN, dirPin, pwmPin, UNDEFINED_PIN);
DCCpp::powerOn(true, false);
}
void Dcc::sendPacket(const std::vector<uint8_t> &packet, int pin) {
// Simple simulation: print packet and toggle pin briefly for each byte.
Serial.print("DCC packet: ");
for (size_t i = 0; i < packet.size(); ++i) {
Serial.print(packet[i], HEX);
Serial.print(' ');
digitalWrite(pin, HIGH);
delay(2);
digitalWrite(pin, LOW);
delay(2);
void Dcc::sendAccessory(uint16_t linearAddress, bool activate) {
if (linearAddress == 0) {
Serial.println("Adresse DCC invalide");
return;
}
Serial.println();
int boardAddress = ((linearAddress - 1) / 4) + 1;
int subAddress = (linearAddress - 1) % 4;
byte activateByte = activate ? 1 : 0;
DCCpp::setAccessory(boardAddress, subAddress, activateByte);
}
+7 -7
View File
@@ -1,14 +1,14 @@
#pragma once
#include <vector>
#include <stdint.h>
#include <DCCpp.h>
#ifndef UNDEFINED_PIN
#define UNDEFINED_PIN 255
#endif
class Dcc {
public:
// Generate a simplified DCC accessory packet (bytes).
// addr: accessory address (application-dependent)
// activate: true = ON, false = OFF
static std::vector<uint8_t> generateAccessoryPacket(uint16_t addr, bool activate);
// Send packet via a GPIO pin (simulation/pulse). Implementation may be hardware-specific.
static void sendPacket(const std::vector<uint8_t> &packet, int pin);
static void begin(int dirPin, int pwmPin);
static void sendAccessory(uint16_t linearAddress, bool activate);
};
+1
View File
@@ -6,6 +6,7 @@ monitor_speed = 115200
lib_deps =
PubSubClient
ArduinoJson
locoduino/DCCpp
build_flags =
-DCORE_DEBUG_LEVEL=0
+3
View File
@@ -62,6 +62,8 @@ void WifiManager::handleSave() {
void WifiManager::begin() {
loadCredentials();
WiFi.mode(WIFI_STA);
if (ssid.length() > 0) {
Serial.print("Connecting to WiFi");
WiFi.begin(ssid.c_str(), pass.c_str());
@@ -78,6 +80,7 @@ void WifiManager::begin() {
Serial.println(WiFi.localIP());
apMode = false;
} else {
WiFi.disconnect(true);
startAP();
}
}
+8 -4
View File
@@ -25,8 +25,7 @@ void onMqttMessage(char* topic, uint8_t* payload, unsigned int length) {
bool state = doc["state"] | false;
String id = doc["id"] | "";
auto pkt = Dcc::generateAccessoryPacket(addr, state);
Dcc::sendPacket(pkt, DCC_OUTPUT_PIN);
Dcc::sendAccessory(addr, state);
// send ACK
StaticJsonDocument<128> ack;
@@ -48,8 +47,12 @@ void onMqttMessage(char* topic, uint8_t* payload, unsigned int length) {
void setup() {
Serial.begin(115200);
pinMode(DCC_OUTPUT_PIN, OUTPUT);
digitalWrite(DCC_OUTPUT_PIN, LOW);
pinMode(DCC_DIR_PIN, OUTPUT);
pinMode(DCC_PWM_PIN, OUTPUT);
digitalWrite(DCC_DIR_PIN, LOW);
digitalWrite(DCC_PWM_PIN, LOW);
Dcc::begin(DCC_DIR_PIN, DCC_PWM_PIN);
wifiManager.begin();
@@ -60,6 +63,7 @@ void setup() {
void loop() {
wifiManager.loop();
mqttManager.loop();
DCCpp::loop();
if (wifiManager.isConnected() && mqttManager.connected() && !mqttSubscribed) {
mqttManager.subscribe(MQTT_TOPIC_CMD);