Initialisation depot
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
# 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`
|
||||||
|
|
||||||
|
Setup:
|
||||||
|
1. Edit `include/config.h` and set `WIFI_SSID`, `WIFI_PASSWORD`, `MQTT_SERVER`, and other values.
|
||||||
|
2. Build and upload with PlatformIO.
|
||||||
|
|
||||||
|
Command format (JSON published to `dcc/command`):
|
||||||
|
{
|
||||||
|
"id": "optional-correlation-id",
|
||||||
|
"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.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <PubSubClient.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
class MqttManager {
|
||||||
|
public:
|
||||||
|
using MessageCallback = std::function<void(char*, uint8_t*, unsigned int)>;
|
||||||
|
MqttManager();
|
||||||
|
void begin(const char* server, uint16_t port, MessageCallback cb);
|
||||||
|
void loop();
|
||||||
|
bool publish(const char* topic, const char* payload);
|
||||||
|
bool subscribe(const char* topic);
|
||||||
|
bool connected();
|
||||||
|
private:
|
||||||
|
WiFiClient espClient;
|
||||||
|
PubSubClient client{espClient};
|
||||||
|
MessageCallback messageCb;
|
||||||
|
const char* serverAddr = nullptr;
|
||||||
|
uint16_t serverPort = 0;
|
||||||
|
static MqttManager* instance;
|
||||||
|
static void staticCallback(char* topic, byte* payload, unsigned int length);
|
||||||
|
void connect();
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
|
||||||
|
class WifiManager {
|
||||||
|
public:
|
||||||
|
WifiManager();
|
||||||
|
void begin();
|
||||||
|
bool isConnected();
|
||||||
|
void loop();
|
||||||
|
String getSSID();
|
||||||
|
String getPassword();
|
||||||
|
private:
|
||||||
|
WebServer server{80};
|
||||||
|
String ssid;
|
||||||
|
String pass;
|
||||||
|
bool apMode;
|
||||||
|
void startAP();
|
||||||
|
void handleRoot();
|
||||||
|
void handleSave();
|
||||||
|
void loadCredentials();
|
||||||
|
void saveCredentials(const String &s, const String &p);
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
// MQTT broker settings (leave MQTT server values as needed)
|
||||||
|
#define MQTT_SERVER "192.168.1.10"
|
||||||
|
#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
|
||||||
|
|
||||||
|
// Access point SSID shown when no WiFi credentials are configured
|
||||||
|
#define DEFAULT_AP_SSID "DCC_Config"
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#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::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);
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[env:esp32dev]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32dev
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
PubSubClient
|
||||||
|
ArduinoJson
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-DCORE_DEBUG_LEVEL=0
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#include "MqttManager.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
MqttManager* MqttManager::instance = nullptr;
|
||||||
|
|
||||||
|
MqttManager::MqttManager() {}
|
||||||
|
|
||||||
|
void MqttManager::begin(const char* server, uint16_t port, MessageCallback cb) {
|
||||||
|
serverAddr = server;
|
||||||
|
serverPort = port;
|
||||||
|
messageCb = cb;
|
||||||
|
client.setServer(serverAddr, serverPort);
|
||||||
|
instance = this;
|
||||||
|
client.setCallback(MqttManager::staticCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttManager::staticCallback(char* topic, byte* payload, unsigned int length) {
|
||||||
|
if (instance && instance->messageCb) instance->messageCb(topic, payload, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttManager::connect() {
|
||||||
|
while (!client.connected()) {
|
||||||
|
Serial.print("Connecting to MQTT...");
|
||||||
|
if (client.connect("esp32-dcc")) {
|
||||||
|
Serial.println("connected");
|
||||||
|
} else {
|
||||||
|
Serial.print("failed rc=");
|
||||||
|
Serial.print(client.state());
|
||||||
|
Serial.println("; retrying in 2s");
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttManager::loop() {
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
if (!client.connected()) connect();
|
||||||
|
client.loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MqttManager::publish(const char* topic, const char* payload) {
|
||||||
|
if (!client.connected()) return false;
|
||||||
|
return client.publish(topic, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MqttManager::subscribe(const char* topic) {
|
||||||
|
if (!client.connected()) return false;
|
||||||
|
return client.subscribe(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MqttManager::connected() { return client.connected(); }
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#include "WifiManager.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <Preferences.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
static const char* AP_SSID = DEFAULT_AP_SSID;
|
||||||
|
|
||||||
|
WifiManager::WifiManager(): ssid(""), pass(""), apMode(false) {}
|
||||||
|
|
||||||
|
void WifiManager::loadCredentials() {
|
||||||
|
Preferences prefs;
|
||||||
|
prefs.begin("dcc", true);
|
||||||
|
ssid = prefs.getString("ssid", "");
|
||||||
|
pass = prefs.getString("pass", "");
|
||||||
|
prefs.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiManager::saveCredentials(const String &s, const String &p) {
|
||||||
|
Preferences prefs;
|
||||||
|
prefs.begin("dcc", false);
|
||||||
|
prefs.putString("ssid", s);
|
||||||
|
prefs.putString("pass", p);
|
||||||
|
prefs.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiManager::startAP() {
|
||||||
|
apMode = true;
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAP(AP_SSID);
|
||||||
|
IPAddress ip = WiFi.softAPIP();
|
||||||
|
Serial.print("AP started: ");
|
||||||
|
Serial.println(AP_SSID);
|
||||||
|
Serial.print("Configure at http://");
|
||||||
|
Serial.println(ip);
|
||||||
|
server.on("/", std::bind(&WifiManager::handleRoot, this));
|
||||||
|
server.on("/save", std::bind(&WifiManager::handleSave, this));
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiManager::handleRoot() {
|
||||||
|
String page = "<html><body><h1>Configure WiFi</h1>"
|
||||||
|
"<form action=\"/save\" method=\"POST\">"
|
||||||
|
"SSID: <input name=\"ssid\"><br>"
|
||||||
|
"Password: <input name=\"pass\" type=\"password\"><br>"
|
||||||
|
"<input type=\"submit\" value=\"Save\"></form></body></html>";
|
||||||
|
server.send(200, "text/html", page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiManager::handleSave() {
|
||||||
|
if (server.hasArg("ssid")) {
|
||||||
|
String s = server.arg("ssid");
|
||||||
|
String p = server.arg("pass");
|
||||||
|
saveCredentials(s, p);
|
||||||
|
String resp = "<html><body>Saved. Rebooting...</body></html>";
|
||||||
|
server.send(200, "text/html", resp);
|
||||||
|
delay(1000);
|
||||||
|
ESP.restart();
|
||||||
|
} else {
|
||||||
|
server.send(400, "text/plain", "Missing ssid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiManager::begin() {
|
||||||
|
loadCredentials();
|
||||||
|
if (ssid.length() > 0) {
|
||||||
|
Serial.print("Connecting to WiFi");
|
||||||
|
WiFi.begin(ssid.c_str(), pass.c_str());
|
||||||
|
unsigned long start = millis();
|
||||||
|
while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) {
|
||||||
|
delay(250);
|
||||||
|
Serial.print('.');
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
Serial.print("WiFi connected, IP: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
apMode = false;
|
||||||
|
} else {
|
||||||
|
startAP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WifiManager::isConnected() { return WiFi.status() == WL_CONNECTED; }
|
||||||
|
|
||||||
|
void WifiManager::loop() { if (apMode) server.handleClient(); }
|
||||||
|
|
||||||
|
String WifiManager::getSSID() { return ssid; }
|
||||||
|
String WifiManager::getPassword() { return pass; }
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <PubSubClient.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include "config.h"
|
||||||
|
#include "Dcc.h"
|
||||||
|
#include "WifiManager.h"
|
||||||
|
#include "MqttManager.h"
|
||||||
|
|
||||||
|
WifiManager wifiManager;
|
||||||
|
MqttManager mqttManager;
|
||||||
|
|
||||||
|
static bool mqttSubscribed = false;
|
||||||
|
|
||||||
|
void onMqttMessage(char* topic, uint8_t* payload, unsigned int length) {
|
||||||
|
StaticJsonDocument<256> doc;
|
||||||
|
DeserializationError err = deserializeJson(doc, payload, length);
|
||||||
|
if (err) {
|
||||||
|
Serial.println("Invalid JSON in MQTT message");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(topic, MQTT_TOPIC_CMD) == 0) {
|
||||||
|
uint16_t addr = doc["address"] | 0;
|
||||||
|
bool state = doc["state"] | false;
|
||||||
|
String id = doc["id"] | "";
|
||||||
|
|
||||||
|
auto pkt = Dcc::generateAccessoryPacket(addr, state);
|
||||||
|
Dcc::sendPacket(pkt, DCC_OUTPUT_PIN);
|
||||||
|
|
||||||
|
// send ACK
|
||||||
|
StaticJsonDocument<128> ack;
|
||||||
|
ack["id"] = id;
|
||||||
|
ack["status"] = "sent";
|
||||||
|
char buf[128];
|
||||||
|
size_t n = serializeJson(ack, buf);
|
||||||
|
mqttManager.publish(MQTT_TOPIC_STATUS, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(topic, MQTT_TOPIC_RESPONSE) == 0) {
|
||||||
|
// forward any response to status topic
|
||||||
|
// ensure payload is null-terminated
|
||||||
|
String resp;
|
||||||
|
for (unsigned int i = 0; i < length; ++i) resp += (char)payload[i];
|
||||||
|
mqttManager.publish(MQTT_TOPIC_STATUS, resp.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
pinMode(DCC_OUTPUT_PIN, OUTPUT);
|
||||||
|
digitalWrite(DCC_OUTPUT_PIN, LOW);
|
||||||
|
|
||||||
|
wifiManager.begin();
|
||||||
|
|
||||||
|
// MQTT manager can be started even if WiFi is not connected yet; it will connect when possible
|
||||||
|
mqttManager.begin(MQTT_SERVER, MQTT_PORT, onMqttMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
wifiManager.loop();
|
||||||
|
mqttManager.loop();
|
||||||
|
|
||||||
|
if (wifiManager.isConnected() && mqttManager.connected() && !mqttSubscribed) {
|
||||||
|
mqttManager.subscribe(MQTT_TOPIC_CMD);
|
||||||
|
mqttManager.subscribe(MQTT_TOPIC_RESPONSE);
|
||||||
|
mqttManager.publish(MQTT_TOPIC_STATUS, "ready");
|
||||||
|
mqttSubscribed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user