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
#include <Arduino.h>
#include <FastLED.h>
// #include <FastLED.h>
// Pin definition for WS2812 LEDs
#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_MODE 1 ///< Mode indicator (DCC/Analog)
/**
* @class LEDIndicator
* @brief Manages WS2812 RGB LED status displays
*
* Controls two LEDs for system status indication:
* - Power LED: Shows system power state with boot animation
* - Mode LED: Shows control mode with pulsing effect
*/
// /**
// * @class LEDIndicator
// * @brief Manages WS2812 RGB LED status displays
// *
// * Controls two LEDs for system status indication:
// * - Power LED: Shows system power state with boot animation
// * - Mode LED: Shows control mode with pulsing effect
// */
class LEDIndicator {
public:
/**
@@ -86,20 +86,20 @@ public:
*/
void modeChangeEffect();
private:
CRGB leds[NUM_LEDS]; ///< LED array
bool powerOn; ///< Power status flag
bool dccMode; ///< Mode flag (DCC/Analog)
uint8_t brightness; ///< Current brightness level
unsigned long lastUpdate; ///< Last update timestamp
uint8_t pulsePhase; ///< Pulse animation phase
// private:
// CRGB leds[NUM_LEDS]; ///< LED array
// bool powerOn; ///< Power status flag
// bool dccMode; ///< Mode flag (DCC/Analog)
// uint8_t brightness; ///< Current brightness level
// unsigned long lastUpdate; ///< Last update timestamp
// uint8_t pulsePhase; ///< Pulse animation phase
// LED color definitions
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_DCC = CRGB::Blue; ///< DCC mode color
static constexpr CRGB COLOR_ANALOG = CRGB::Yellow; ///< Analog mode color
static constexpr CRGB COLOR_OFF = CRGB::Black; ///< LED off state
// // LED color definitions
// 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_DCC = CRGB::Blue; ///< DCC mode color
// static constexpr CRGB COLOR_ANALOG = CRGB::Yellow; ///< Analog mode color
// static constexpr CRGB COLOR_OFF = CRGB::Black; ///< LED off state
};
#endif

View File

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

View File

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

View File

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

View File

@@ -8,107 +8,108 @@
/**
* @brief Constructor - initialize with default state
*/
LEDIndicator::LEDIndicator() :
powerOn(false),
dccMode(false),
brightness(128),
lastUpdate(0),
pulsePhase(0) {
}
// LEDIndicator::LEDIndicator() :
// powerOn(false),
// dccMode(false),
// brightness(128),
// lastUpdate(0),
// pulsePhase(0)
// {}
LEDIndicator::LEDIndicator(){}
void LEDIndicator::begin() {
FastLED.addLeds<WS2812, LED_DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(brightness);
// FastLED.addLeds<WS2812, LED_DATA_PIN, GRB>(leds, NUM_LEDS);
// FastLED.setBrightness(brightness);
// Initialize both LEDs to off
leds[LED_POWER] = COLOR_OFF;
leds[LED_MODE] = COLOR_OFF;
FastLED.show();
// leds[LED_POWER] = COLOR_OFF;
// leds[LED_MODE] = COLOR_OFF;
// FastLED.show();
Serial.println("LED Indicator initialized");
Serial.printf("LED Data Pin: %d, Num LEDs: %d\n", LED_DATA_PIN, NUM_LEDS);
// Serial.println("LED Indicator initialized");
// Serial.printf("LED Data Pin: %d, Num LEDs: %d\n", LED_DATA_PIN, NUM_LEDS);
}
void LEDIndicator::update() {
unsigned long now = millis();
// unsigned long now = millis();
// Update power LED
if (powerOn) {
leds[LED_POWER] = COLOR_POWER_ON;
} else {
leds[LED_POWER] = COLOR_POWER_OFF;
}
// // Update power LED
// if (powerOn) {
// leds[LED_POWER] = COLOR_POWER_ON;
// } else {
// leds[LED_POWER] = COLOR_POWER_OFF;
// }
// Update mode LED with subtle pulsing effect
if (now - lastUpdate > 20) {
lastUpdate = now;
pulsePhase++;
// if (now - lastUpdate > 20) {
// lastUpdate = now;
// pulsePhase++;
// Create gentle pulse effect
uint8_t pulseBrightness = 128 + (sin8(pulsePhase * 2) / 4);
// // Create gentle pulse effect
// uint8_t pulseBrightness = 128 + (sin8(pulsePhase * 2) / 4);
CRGB baseColor = dccMode ? COLOR_DCC : COLOR_ANALOG;
leds[LED_MODE] = baseColor;
leds[LED_MODE].fadeToBlackBy(255 - pulseBrightness);
}
// CRGB baseColor = dccMode ? COLOR_DCC : COLOR_ANALOG;
// leds[LED_MODE] = baseColor;
// leds[LED_MODE].fadeToBlackBy(255 - pulseBrightness);
// }
FastLED.show();
// FastLED.show();
}
void LEDIndicator::setPowerOn(bool on) {
if (powerOn != on) {
powerOn = on;
if (on) {
powerOnSequence();
}
}
// if (powerOn != on) {
// powerOn = on;
// if (on) {
// powerOnSequence();
// }
// }
}
void LEDIndicator::setMode(bool isDCC) {
if (dccMode != isDCC) {
dccMode = isDCC;
modeChangeEffect();
}
// if (dccMode != isDCC) {
// dccMode = isDCC;
// modeChangeEffect();
// }
}
void LEDIndicator::setBrightness(uint8_t newBrightness) {
brightness = newBrightness;
FastLED.setBrightness(brightness);
// brightness = newBrightness;
// FastLED.setBrightness(brightness);
}
void LEDIndicator::powerOnSequence() {
// Quick flash sequence on power on
for (int i = 0; i < 3; i++) {
leds[LED_POWER] = COLOR_POWER_ON;
FastLED.show();
delay(100);
leds[LED_POWER] = COLOR_OFF;
FastLED.show();
delay(100);
}
leds[LED_POWER] = COLOR_POWER_ON;
FastLED.show();
Serial.println("LED: Power ON sequence");
// // Quick flash sequence on power on
// for (int i = 0; i < 3; i++) {
// leds[LED_POWER] = COLOR_POWER_ON;
// FastLED.show();
// delay(100);
// leds[LED_POWER] = COLOR_OFF;
// FastLED.show();
// delay(100);
// }
// leds[LED_POWER] = COLOR_POWER_ON;
// FastLED.show();
// Serial.println("LED: Power ON sequence");
}
void LEDIndicator::modeChangeEffect() {
// Smooth transition effect when changing modes
CRGB targetColor = dccMode ? COLOR_DCC : COLOR_ANALOG;
// // Smooth transition effect when changing modes
// CRGB targetColor = dccMode ? COLOR_DCC : COLOR_ANALOG;
// Fade out
for (int i = 255; i >= 0; i -= 15) {
leds[LED_MODE].fadeToBlackBy(15);
FastLED.show();
delay(10);
}
// // Fade out
// for (int i = 255; i >= 0; i -= 15) {
// leds[LED_MODE].fadeToBlackBy(15);
// FastLED.show();
// delay(10);
// }
// Fade in new color
for (int i = 0; i <= 255; i += 15) {
leds[LED_MODE] = targetColor;
leds[LED_MODE].fadeToBlackBy(255 - i);
FastLED.show();
delay(10);
}
// // Fade in new color
// for (int i = 0; i <= 255; i += 15) {
// leds[LED_MODE] = targetColor;
// leds[LED_MODE].fadeToBlackBy(255 - i);
// FastLED.show();
// 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 <LittleFS.h>
#include <WiFi.h>
/**
* @brief Constructor
@@ -14,19 +15,70 @@ WebServerManager::WebServerManager(Config* cfg, MotorController* motor, DCCGener
}
void WebServerManager::begin() {
Serial.println("Initializing web server...");
// Initialize LittleFS
if (!LittleFS.begin(true)) {
Serial.println("LittleFS Mount Failed");
return;
Serial.println("ERROR: LittleFS Mount Failed!");
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();
// 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();
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() {
// 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
server.on("/", HTTP_GET, [this](AsyncWebServerRequest *request) {
request->send(LittleFS, "/index.html", "text/html");
@@ -36,13 +88,25 @@ void WebServerManager::setupRoutes() {
server.serveStatic("/css/", LittleFS, "/css/");
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
server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
handleGetStatus(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) {
// Body handler
DynamicJsonDocument doc(256);
@@ -154,5 +218,6 @@ String WebServerManager::getStatusJSON() {
}
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
wifiManager.begin();
// Initialize LED indicator
ledIndicator.begin();
ledIndicator.setPowerOn(true);
// Initialize LED indicator TODO
//ledIndicator.begin();
//ledIndicator.setPowerOn(true);
// Initialize motor controller
motorController.begin();
@@ -72,40 +72,37 @@ void setup() {
// Set initial mode and LED
if (config.system.isDCCMode) {
dccGenerator.enable();
ledIndicator.setMode(true);
// ledIndicator.setMode(true);
dccGenerator.setLocoSpeed(
config.system.dccAddress,
config.system.speed,
config.system.direction
);
} else {
ledIndicator.setMode(false);
// ledIndicator.setMode(false);
motorController.setSpeed(
config.system.speed,
config.system.direction
);
Serial.println("=================================\\n");
}
// }
/**
* @brief Main loop - runs continuously
*
* 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() {
// Start web server BEFORE final status
Serial.println("\nStarting web server...");
webServer.begin();
// Update WiFi connection status
Serial.println("\n=================================");
Serial.println("Setup complete!");
Serial.println("=================================");
Serial.print("Mode: ");
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.println(wifiManager.getIPAddress());
Serial.println("=================================\n");
@@ -116,7 +113,7 @@ void loop() {
wifiManager.update();
// Update LED indicators
ledIndicator.update();
//ledIndicator.update();
// Update DCC signal generation (if enabled)
if (config.system.isDCCMode) {