Version fonctionnelle 1.0

This commit is contained in:
2025-11-30 15:07:12 +01:00
parent 56d8cd96c8
commit 4c6c528d22
9 changed files with 269 additions and 124 deletions

8
DCC-Bench.code-workspace Normal file
View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

70
get-platformio.py Normal file

File diff suppressed because one or more lines are too long

View File

@@ -14,24 +14,24 @@
#define LED_INDICATOR_H #define LED_INDICATOR_H
#include <Arduino.h> #include <Arduino.h>
#include <FastLED.h> // #include <FastLED.h>
// Pin definition for WS2812 LEDs // Pin definition for WS2812 LEDs
#define LED_DATA_PIN 4 ///< Data pin for WS2812 strip #define LED_DATA_PIN 4 ///< Data pin for WS2812 strip
#define NUM_LEDS 2 ///< Number of LEDs (Power + Mode) #define NUM_LEDS 4 ///< Number of LEDs (Power + Mode)
// LED indices // // LED indices
#define LED_POWER 0 ///< Power status indicator #define LED_POWER 0 ///< Power status indicator
#define LED_MODE 1 ///< Mode indicator (DCC/Analog) #define LED_MODE 1 ///< Mode indicator (DCC/Analog)
/** // /**
* @class LEDIndicator // * @class LEDIndicator
* @brief Manages WS2812 RGB LED status displays // * @brief Manages WS2812 RGB LED status displays
* // *
* Controls two LEDs for system status indication: // * Controls two LEDs for system status indication:
* - Power LED: Shows system power state with boot animation // * - Power LED: Shows system power state with boot animation
* - Mode LED: Shows control mode with pulsing effect // * - Mode LED: Shows control mode with pulsing effect
*/ // */
class LEDIndicator { class LEDIndicator {
public: public:
/** /**
@@ -86,20 +86,20 @@ public:
*/ */
void modeChangeEffect(); void modeChangeEffect();
private: // private:
CRGB leds[NUM_LEDS]; ///< LED array // CRGB leds[NUM_LEDS]; ///< LED array
bool powerOn; ///< Power status flag // bool powerOn; ///< Power status flag
bool dccMode; ///< Mode flag (DCC/Analog) // bool dccMode; ///< Mode flag (DCC/Analog)
uint8_t brightness; ///< Current brightness level // uint8_t brightness; ///< Current brightness level
unsigned long lastUpdate; ///< Last update timestamp // unsigned long lastUpdate; ///< Last update timestamp
uint8_t pulsePhase; ///< Pulse animation phase // uint8_t pulsePhase; ///< Pulse animation phase
// LED color definitions // // LED color definitions
static constexpr CRGB COLOR_POWER_ON = CRGB::Green; ///< Power ON color // static constexpr CRGB COLOR_POWER_ON = CRGB::Green; ///< Power ON color
static constexpr CRGB COLOR_POWER_OFF = CRGB::Red; ///< Power OFF color // static constexpr CRGB COLOR_POWER_OFF = CRGB::Red; ///< Power OFF color
static constexpr CRGB COLOR_DCC = CRGB::Blue; ///< DCC mode color // static constexpr CRGB COLOR_DCC = CRGB::Blue; ///< DCC mode color
static constexpr CRGB COLOR_ANALOG = CRGB::Yellow; ///< Analog mode color // static constexpr CRGB COLOR_ANALOG = CRGB::Yellow; ///< Analog mode color
static constexpr CRGB COLOR_OFF = CRGB::Black; ///< LED off state // static constexpr CRGB COLOR_OFF = CRGB::Black; ///< LED off state
}; };
#endif #endif

View File

@@ -19,6 +19,7 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <DNSServer.h>
#include "Config.h" #include "Config.h"
#include "MotorController.h" #include "MotorController.h"
#include "DCCGenerator.h" #include "DCCGenerator.h"
@@ -70,6 +71,7 @@ private:
DCCGenerator* dccGenerator; ///< DCC generator instance DCCGenerator* dccGenerator; ///< DCC generator instance
LEDIndicator* ledIndicator; ///< LED indicator instance LEDIndicator* ledIndicator; ///< LED indicator instance
AsyncWebServer server; ///< Async web server (port 80) AsyncWebServer server; ///< Async web server (port 80)
DNSServer dnsServer; ///< DNS server for captive portal
/** /**
* @brief Set up all HTTP routes and handlers * @brief Set up all HTTP routes and handlers

View File

@@ -8,9 +8,11 @@
; Please visit documentation for the other options and examples ; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[env:wemos_d1_mini32] ;[env:wemos_d1_mini32]
[env:esp32doit-devkit-v1]
platform = espressif32 platform = espressif32
board = wemos_d1_mini32 ; board = wemos_d1_mini32
board = esp32doit-devkit-v1
framework = arduino framework = arduino
monitor_speed = 115200 monitor_speed = 115200
upload_speed = 921600 upload_speed = 921600
@@ -23,5 +25,5 @@ lib_deps =
esp32async/ESPAsyncWebServer @ ^3.9.2 esp32async/ESPAsyncWebServer @ ^3.9.2
esp32async/AsyncTCP @ ^3.4.9 esp32async/AsyncTCP @ ^3.4.9
https://github.com/Locoduino/DCCpp https://github.com/Locoduino/DCCpp
fastled/FastLED@^3.6.0 ; fastled/FastLED@^3.6.0
board_build.filesystem = littlefs board_build.filesystem = littlefs

View File

@@ -18,7 +18,7 @@ Config::Config() {
wifi.password = ""; wifi.password = "";
wifi.isAPMode = true; wifi.isAPMode = true;
wifi.apSSID = "LocoTestBench"; wifi.apSSID = "LocoTestBench";
wifi.apPassword = "12345678"; wifi.apPassword = "123456789";
system.isDCCMode = false; system.isDCCMode = false;
system.dccAddress = 3; system.dccAddress = 3;

View File

@@ -8,107 +8,108 @@
/** /**
* @brief Constructor - initialize with default state * @brief Constructor - initialize with default state
*/ */
LEDIndicator::LEDIndicator() : // LEDIndicator::LEDIndicator() :
powerOn(false), // powerOn(false),
dccMode(false), // dccMode(false),
brightness(128), // brightness(128),
lastUpdate(0), // lastUpdate(0),
pulsePhase(0) { // pulsePhase(0)
} // {}
LEDIndicator::LEDIndicator(){}
void LEDIndicator::begin() { void LEDIndicator::begin() {
FastLED.addLeds<WS2812, LED_DATA_PIN, GRB>(leds, NUM_LEDS); // FastLED.addLeds<WS2812, LED_DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(brightness); // FastLED.setBrightness(brightness);
// Initialize both LEDs to off // Initialize both LEDs to off
leds[LED_POWER] = COLOR_OFF; // leds[LED_POWER] = COLOR_OFF;
leds[LED_MODE] = COLOR_OFF; // leds[LED_MODE] = COLOR_OFF;
FastLED.show(); // FastLED.show();
Serial.println("LED Indicator initialized"); // Serial.println("LED Indicator initialized");
Serial.printf("LED Data Pin: %d, Num LEDs: %d\n", LED_DATA_PIN, NUM_LEDS); // Serial.printf("LED Data Pin: %d, Num LEDs: %d\n", LED_DATA_PIN, NUM_LEDS);
} }
void LEDIndicator::update() { void LEDIndicator::update() {
unsigned long now = millis(); // unsigned long now = millis();
// Update power LED // // Update power LED
if (powerOn) { // if (powerOn) {
leds[LED_POWER] = COLOR_POWER_ON; // leds[LED_POWER] = COLOR_POWER_ON;
} else { // } else {
leds[LED_POWER] = COLOR_POWER_OFF; // leds[LED_POWER] = COLOR_POWER_OFF;
} // }
// Update mode LED with subtle pulsing effect // Update mode LED with subtle pulsing effect
if (now - lastUpdate > 20) { // if (now - lastUpdate > 20) {
lastUpdate = now; // lastUpdate = now;
pulsePhase++; // pulsePhase++;
// Create gentle pulse effect // // Create gentle pulse effect
uint8_t pulseBrightness = 128 + (sin8(pulsePhase * 2) / 4); // uint8_t pulseBrightness = 128 + (sin8(pulsePhase * 2) / 4);
CRGB baseColor = dccMode ? COLOR_DCC : COLOR_ANALOG; // CRGB baseColor = dccMode ? COLOR_DCC : COLOR_ANALOG;
leds[LED_MODE] = baseColor; // leds[LED_MODE] = baseColor;
leds[LED_MODE].fadeToBlackBy(255 - pulseBrightness); // leds[LED_MODE].fadeToBlackBy(255 - pulseBrightness);
} // }
FastLED.show(); // FastLED.show();
} }
void LEDIndicator::setPowerOn(bool on) { void LEDIndicator::setPowerOn(bool on) {
if (powerOn != on) { // if (powerOn != on) {
powerOn = on; // powerOn = on;
if (on) { // if (on) {
powerOnSequence(); // powerOnSequence();
} // }
} // }
} }
void LEDIndicator::setMode(bool isDCC) { void LEDIndicator::setMode(bool isDCC) {
if (dccMode != isDCC) { // if (dccMode != isDCC) {
dccMode = isDCC; // dccMode = isDCC;
modeChangeEffect(); // modeChangeEffect();
} // }
} }
void LEDIndicator::setBrightness(uint8_t newBrightness) { void LEDIndicator::setBrightness(uint8_t newBrightness) {
brightness = newBrightness; // brightness = newBrightness;
FastLED.setBrightness(brightness); // FastLED.setBrightness(brightness);
} }
void LEDIndicator::powerOnSequence() { void LEDIndicator::powerOnSequence() {
// Quick flash sequence on power on // // Quick flash sequence on power on
for (int i = 0; i < 3; i++) { // for (int i = 0; i < 3; i++) {
leds[LED_POWER] = COLOR_POWER_ON; // leds[LED_POWER] = COLOR_POWER_ON;
FastLED.show(); // FastLED.show();
delay(100); // delay(100);
leds[LED_POWER] = COLOR_OFF; // leds[LED_POWER] = COLOR_OFF;
FastLED.show(); // FastLED.show();
delay(100); // delay(100);
} // }
leds[LED_POWER] = COLOR_POWER_ON; // leds[LED_POWER] = COLOR_POWER_ON;
FastLED.show(); // FastLED.show();
Serial.println("LED: Power ON sequence"); // Serial.println("LED: Power ON sequence");
} }
void LEDIndicator::modeChangeEffect() { void LEDIndicator::modeChangeEffect() {
// Smooth transition effect when changing modes // // Smooth transition effect when changing modes
CRGB targetColor = dccMode ? COLOR_DCC : COLOR_ANALOG; // CRGB targetColor = dccMode ? COLOR_DCC : COLOR_ANALOG;
// Fade out // // Fade out
for (int i = 255; i >= 0; i -= 15) { // for (int i = 255; i >= 0; i -= 15) {
leds[LED_MODE].fadeToBlackBy(15); // leds[LED_MODE].fadeToBlackBy(15);
FastLED.show(); // FastLED.show();
delay(10); // delay(10);
} // }
// Fade in new color // // Fade in new color
for (int i = 0; i <= 255; i += 15) { // for (int i = 0; i <= 255; i += 15) {
leds[LED_MODE] = targetColor; // leds[LED_MODE] = targetColor;
leds[LED_MODE].fadeToBlackBy(255 - i); // leds[LED_MODE].fadeToBlackBy(255 - i);
FastLED.show(); // FastLED.show();
delay(10); // delay(10);
} // }
Serial.printf("LED: Mode changed to %s\n", dccMode ? "DCC (Blue)" : "Analog (Yellow)"); // Serial.printf("LED: Mode changed to %s\n", dccMode ? "DCC (Blue)" : "Analog (Yellow)");
} }

View File

@@ -5,6 +5,7 @@
#include "WebServer.h" #include "WebServer.h"
#include <LittleFS.h> #include <LittleFS.h>
#include <WiFi.h>
/** /**
* @brief Constructor * @brief Constructor
@@ -14,19 +15,70 @@ WebServerManager::WebServerManager(Config* cfg, MotorController* motor, DCCGener
} }
void WebServerManager::begin() { void WebServerManager::begin() {
Serial.println("Initializing web server...");
// Initialize LittleFS // Initialize LittleFS
if (!LittleFS.begin(true)) { if (!LittleFS.begin(true)) {
Serial.println("LittleFS Mount Failed"); Serial.println("ERROR: LittleFS Mount Failed!");
return; Serial.println("Did you upload the filesystem? Run: pio run -t uploadfs");
} else {
Serial.println("✓ LittleFS mounted successfully");
// List files for debugging
File root = LittleFS.open("/");
if (root) {
Serial.println("Files in LittleFS:");
File file = root.openNextFile();
while (file) {
Serial.print(" - ");
Serial.print(file.name());
Serial.print(" (");
Serial.print(file.size());
Serial.println(" bytes)");
file = root.openNextFile();
}
}
} }
Serial.println("LittleFS mounted successfully");
setupRoutes(); setupRoutes();
// Start DNS server for captive portal (redirect all domains to ESP32)
dnsServer.start(53, "*", WiFi.softAPIP());
Serial.println("✓ DNS server started for captive portal");
server.begin(); server.begin();
Serial.println("Web server started on port 80"); Serial.println("Web server started on port 80");
Serial.print("✓ Access at: http://");
Serial.println(WiFi.softAPIP());
Serial.println("✓ Captive portal enabled - phones will auto-redirect");
} }
void WebServerManager::setupRoutes() { void WebServerManager::setupRoutes() {
// Android captive portal detection
server.on("/generate_204", HTTP_GET, [this](AsyncWebServerRequest *request) {
request->redirect("http://" + WiFi.softAPIP().toString());
});
// iOS/macOS captive portal detection
server.on("/hotspot-detect.html", HTTP_GET, [this](AsyncWebServerRequest *request) {
request->redirect("http://" + WiFi.softAPIP().toString());
});
// Windows captive portal detection
server.on("/connecttest.txt", HTTP_GET, [this](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Microsoft Connect Test");
});
// Firefox captive portal detection
server.on("/canonical.html", HTTP_GET, [this](AsyncWebServerRequest *request) {
request->send(200, "text/html", "<html><head><meta http-equiv='refresh' content='0;url=http://" + WiFi.softAPIP().toString() + "'></head></html>");
});
// Success page that prevents disconnection
server.on("/success.txt", HTTP_GET, [this](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "success");
});
// Serve main page // Serve main page
server.on("/", HTTP_GET, [this](AsyncWebServerRequest *request) { server.on("/", HTTP_GET, [this](AsyncWebServerRequest *request) {
request->send(LittleFS, "/index.html", "text/html"); request->send(LittleFS, "/index.html", "text/html");
@@ -35,6 +87,18 @@ void WebServerManager::setupRoutes() {
// Serve static files (CSS, JS, Bootstrap) // Serve static files (CSS, JS, Bootstrap)
server.serveStatic("/css/", LittleFS, "/css/"); server.serveStatic("/css/", LittleFS, "/css/");
server.serveStatic("/js/", LittleFS, "/js/"); server.serveStatic("/js/", LittleFS, "/js/");
// Captive portal - redirect any unknown domain to our interface
server.onNotFound([this](AsyncWebServerRequest *request) {
// Check if request is for API or static files
String path = request->url();
if (path.startsWith("/api/") || path.startsWith("/css/") || path.startsWith("/js/")) {
request->send(404);
} else {
// Redirect to main page for captive portal
request->send(LittleFS, "/index.html", "text/html");
}
});
// API endpoints // API endpoints
server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest *request) { server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
@@ -42,7 +106,7 @@ void WebServerManager::setupRoutes() {
}); });
server.on("/api/mode", HTTP_POST, [this](AsyncWebServerRequest *request) { server.on("/api/mode", HTTP_POST, [this](AsyncWebServerRequest *request) {
handleSetMode(request); // handleSetMode(request);
}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { }, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// Body handler // Body handler
DynamicJsonDocument doc(256); DynamicJsonDocument doc(256);
@@ -154,5 +218,6 @@ String WebServerManager::getStatusJSON() {
} }
void WebServerManager::update() { void WebServerManager::update() {
// AsyncWebServer handles requests asynchronously // Process DNS requests for captive portal
dnsServer.processNextRequest();
} }

View File

@@ -59,9 +59,9 @@ void setup() {
// Initialize WiFi // Initialize WiFi
wifiManager.begin(); wifiManager.begin();
// Initialize LED indicator // Initialize LED indicator TODO
ledIndicator.begin(); //ledIndicator.begin();
ledIndicator.setPowerOn(true); //ledIndicator.setPowerOn(true);
// Initialize motor controller // Initialize motor controller
motorController.begin(); motorController.begin();
@@ -72,40 +72,37 @@ void setup() {
// Set initial mode and LED // Set initial mode and LED
if (config.system.isDCCMode) { if (config.system.isDCCMode) {
dccGenerator.enable(); dccGenerator.enable();
ledIndicator.setMode(true); // ledIndicator.setMode(true);
dccGenerator.setLocoSpeed( dccGenerator.setLocoSpeed(
config.system.dccAddress, config.system.dccAddress,
config.system.speed, config.system.speed,
config.system.direction config.system.direction
); );
} else { } else {
ledIndicator.setMode(false); // ledIndicator.setMode(false);
motorController.setSpeed( motorController.setSpeed(
config.system.speed, config.system.speed,
config.system.direction config.system.direction
); );
Serial.println("=================================\\n"); Serial.println("=================================\\n");
} }
// }
/** // Start web server BEFORE final status
* @brief Main loop - runs continuously Serial.println("\nStarting web server...");
* webServer.begin();
* Updates all system components:
* - WiFi connection monitoring
* - LED status display
* - DCC signal generation (if enabled)
* - Motor control updates (if in analog mode)
*
* @note Small delay prevents watchdog timer issues
*/
// void loop() {
// Update WiFi connection status // Update WiFi connection status
Serial.println("\n================================="); Serial.println("\n=================================");
Serial.println("Setup complete!"); Serial.println("Setup complete!");
Serial.println("================================="); Serial.println("=================================");
Serial.print("Mode: "); Serial.print("Mode: ");
Serial.println(config.system.isDCCMode ? "DCC" : "DC Analog"); Serial.println(config.system.isDCCMode ? "DCC" : "DC Analog");
Serial.print("WiFi Mode: ");
Serial.println(config.wifi.isAPMode ? "Access Point" : "Client");
Serial.print("SSID: ");
Serial.println(config.wifi.isAPMode ? config.wifi.apSSID : config.wifi.ssid);
Serial.print("IP Address: ");
Serial.println(wifiManager.getIPAddress());
Serial.print("Web interface: http://"); Serial.print("Web interface: http://");
Serial.println(wifiManager.getIPAddress()); Serial.println(wifiManager.getIPAddress());
Serial.println("=================================\n"); Serial.println("=================================\n");
@@ -116,7 +113,7 @@ void loop() {
wifiManager.update(); wifiManager.update();
// Update LED indicators // Update LED indicators
ledIndicator.update(); //ledIndicator.update();
// Update DCC signal generation (if enabled) // Update DCC signal generation (if enabled)
if (config.system.isDCCMode) { if (config.system.isDCCMode) {