Initialisation depot
This commit is contained in:
95
src/Config.cpp
Normal file
95
src/Config.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @file Config.cpp
|
||||
* @brief Implementation of configuration management
|
||||
*/
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor - sets default configuration values
|
||||
*
|
||||
* Initializes all settings to safe defaults:
|
||||
* - WiFi: AP mode with default SSID "LocoTestBench"
|
||||
* - System: DC analog mode, address 3, stopped
|
||||
*/
|
||||
Config::Config() {
|
||||
// Default values
|
||||
wifi.ssid = "";
|
||||
wifi.password = "";
|
||||
wifi.isAPMode = true;
|
||||
wifi.apSSID = "LocoTestBench";
|
||||
wifi.apPassword = "12345678";
|
||||
|
||||
system.isDCCMode = false;
|
||||
system.dccAddress = 3;
|
||||
system.speed = 0;
|
||||
system.direction = 1;
|
||||
system.dccFunctions = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize configuration system
|
||||
*
|
||||
* Opens NVS namespace and loads saved configuration.
|
||||
* If no saved config exists, defaults are used.
|
||||
*/
|
||||
void Config::begin() {
|
||||
preferences.begin("loco-config", false);
|
||||
load();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Save all configuration to persistent storage
|
||||
*
|
||||
* Writes WiFi and system settings to NVS flash memory.
|
||||
* Settings persist across power cycles and reboots.
|
||||
*/
|
||||
void Config::save() {
|
||||
// WiFi settings
|
||||
preferences.putString("wifi_ssid", wifi.ssid);
|
||||
preferences.putString("wifi_pass", wifi.password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load configuration from persistent storage
|
||||
*
|
||||
* Reads all settings from NVS. If a setting doesn't exist,
|
||||
* the current (default) value is retained.
|
||||
*/
|
||||
void Config::load() {
|
||||
// WiFi settingstring("ap_ssid", wifi.apSSID);
|
||||
preferences.putString("ap_pass", wifi.apPassword);
|
||||
|
||||
// System settings
|
||||
preferences.putBool("is_dcc", system.isDCCMode);
|
||||
preferences.putUShort("dcc_addr", system.dccAddress);
|
||||
preferences.putUChar("speed", system.speed);
|
||||
preferences.putUChar("direction", system.direction);
|
||||
preferences.putUInt("dcc_func", system.dccFunctions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all settings to factory defaults
|
||||
*
|
||||
* Clears NVS storage and reinitializes with default values.
|
||||
* @warning All saved configuration will be permanently lost!
|
||||
*/
|
||||
void Config::reset() {
|
||||
preferences.clear();
|
||||
Config(); // Reset to defaults
|
||||
save();
|
||||
|
||||
|
||||
wifi.ssid = preferences.getString("wifi_ssid", "");
|
||||
wifi.password = preferences.getString("wifi_pass", "");
|
||||
wifi.isAPMode = preferences.getBool("wifi_ap", true);
|
||||
wifi.apSSID = preferences.getString("ap_ssid", "LocoTestBench");
|
||||
wifi.apPassword = preferences.getString("ap_pass", "12345678");
|
||||
|
||||
// System settings
|
||||
system.isDCCMode = preferences.getBool("is_dcc", false);
|
||||
system.dccAddress = preferences.getUShort("dcc_addr", 3);
|
||||
system.speed = preferences.getUChar("speed", 0);
|
||||
system.direction = preferences.getUChar("direction", 1);
|
||||
system.dccFunctions = preferences.getUInt("dcc_func", 0);
|
||||
}
|
||||
199
src/DCCGenerator.cpp
Normal file
199
src/DCCGenerator.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* @file DCCGenerator.cpp
|
||||
* @brief Implementation of DCC signal generation
|
||||
*/
|
||||
|
||||
#include "DCCGenerator.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor - initialize with safe defaults
|
||||
*/
|
||||
DCCGenerator::DCCGenerator() :
|
||||
enabled(false),
|
||||
currentAddress(3),
|
||||
currentSpeed(0),
|
||||
currentDirection(1),
|
||||
functionStates(0),
|
||||
lastPacketTime(0) {
|
||||
}
|
||||
|
||||
void DCCGenerator::begin() {
|
||||
pinMode(DCC_PIN_A, OUTPUT);
|
||||
pinMode(DCC_PIN_B, OUTPUT);
|
||||
digitalWrite(DCC_PIN_A, LOW);
|
||||
digitalWrite(DCC_PIN_B, LOW);
|
||||
|
||||
Serial.println("DCC Generator initialized");
|
||||
Serial.printf("DCC Pin A: %d, DCC Pin B: %d\n", DCC_PIN_A, DCC_PIN_B);
|
||||
}
|
||||
|
||||
void DCCGenerator::enable() {
|
||||
enabled = true;
|
||||
Serial.println("DCC mode enabled");
|
||||
}
|
||||
|
||||
void DCCGenerator::disable() {
|
||||
enabled = false;
|
||||
digitalWrite(DCC_PIN_A, LOW);
|
||||
digitalWrite(DCC_PIN_B, LOW);
|
||||
Serial.println("DCC mode disabled");
|
||||
}
|
||||
|
||||
void DCCGenerator::setLocoSpeed(uint16_t address, uint8_t speed, uint8_t direction) {
|
||||
currentAddress = address;
|
||||
currentSpeed = speed;
|
||||
currentDirection = direction;
|
||||
|
||||
Serial.printf("DCC: Addr=%d, Speed=%d, Dir=%s\n",
|
||||
address, speed, direction ? "FWD" : "REV");
|
||||
}
|
||||
|
||||
void DCCGenerator::setFunction(uint16_t address, uint8_t function, bool state) {
|
||||
currentAddress = address;
|
||||
|
||||
if (function <= 28) {
|
||||
if (state) {
|
||||
functionStates |= (1UL << function);
|
||||
} else {
|
||||
functionStates &= ~(1UL << function);
|
||||
}
|
||||
Serial.printf("DCC: Function F%d = %s\n", function, state ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
void DCCGenerator::update() {
|
||||
if (!enabled) return;
|
||||
|
||||
unsigned long now = millis();
|
||||
if (now - lastPacketTime >= PACKET_INTERVAL) {
|
||||
lastPacketTime = now;
|
||||
sendSpeedPacket();
|
||||
|
||||
// Periodically send function packets
|
||||
static uint8_t packetCount = 0;
|
||||
packetCount++;
|
||||
if (packetCount % 3 == 0) {
|
||||
sendFunctionPacket(1); // F0-F4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCCGenerator::sendBit(bool value) {
|
||||
int duration = value ? DCC_ONE_BIT_PULSE_DURATION : DCC_ZERO_BIT_PULSE_DURATION;
|
||||
|
||||
// First half-cycle
|
||||
digitalWrite(DCC_PIN_A, HIGH);
|
||||
digitalWrite(DCC_PIN_B, LOW);
|
||||
delayMicroseconds(duration);
|
||||
|
||||
// Second half-cycle
|
||||
digitalWrite(DCC_PIN_A, LOW);
|
||||
digitalWrite(DCC_PIN_B, HIGH);
|
||||
delayMicroseconds(duration);
|
||||
}
|
||||
|
||||
void DCCGenerator::sendPreamble() {
|
||||
for (int i = 0; i < 14; i++) {
|
||||
sendBit(1); // Send '1' bits
|
||||
}
|
||||
}
|
||||
|
||||
void DCCGenerator::sendByte(uint8_t data) {
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
sendBit((data >> i) & 0x01);
|
||||
}
|
||||
}
|
||||
|
||||
void DCCGenerator::sendPacket(uint8_t* data, uint8_t length) {
|
||||
sendPreamble();
|
||||
|
||||
// Packet start bit
|
||||
sendBit(0);
|
||||
|
||||
// Send data bytes with separator bits
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
sendByte(data[i]);
|
||||
if (i < length - 1) {
|
||||
sendBit(0); // Data byte separator
|
||||
}
|
||||
}
|
||||
|
||||
// Packet end bit
|
||||
sendBit(1);
|
||||
}
|
||||
|
||||
uint8_t DCCGenerator::calculateChecksum(uint8_t* data, uint8_t length) {
|
||||
uint8_t checksum = 0;
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
checksum ^= data[i];
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
void DCCGenerator::sendSpeedPacket() {
|
||||
uint8_t packet[4];
|
||||
uint8_t packetLength = 0;
|
||||
|
||||
// Address byte (short address: 1-127)
|
||||
if (currentAddress <= 127) {
|
||||
packet[packetLength++] = currentAddress & 0x7F;
|
||||
} else {
|
||||
// Long address (128-10239)
|
||||
packet[packetLength++] = 0xC0 | ((currentAddress >> 8) & 0x3F);
|
||||
packet[packetLength++] = currentAddress & 0xFF;
|
||||
}
|
||||
|
||||
// Speed and direction instruction (128-step mode)
|
||||
// Instruction: 0b00111111
|
||||
uint8_t speedByte = 0b00111111; // 128-step speed control
|
||||
|
||||
// Convert speed (0-100) to DCC speed (0-126)
|
||||
uint8_t dccSpeed = map(currentSpeed, 0, 100, 0, 126);
|
||||
|
||||
// Encode direction and speed
|
||||
if (dccSpeed == 0) {
|
||||
speedByte = 0b00111111; // Stop
|
||||
} else {
|
||||
// Bit 7: direction (1=forward, 0=reverse)
|
||||
// Bits 0-6: speed (1-126, with 0 and 1 both meaning stop)
|
||||
speedByte = 0b00111111;
|
||||
speedByte |= (currentDirection ? 0x80 : 0x00);
|
||||
speedByte = (speedByte & 0x80) | (dccSpeed & 0x7F);
|
||||
}
|
||||
|
||||
packet[packetLength++] = speedByte;
|
||||
|
||||
// Error detection byte
|
||||
packet[packetLength++] = calculateChecksum(packet, packetLength);
|
||||
|
||||
sendPacket(packet, packetLength);
|
||||
}
|
||||
|
||||
void DCCGenerator::sendFunctionPacket(uint8_t group) {
|
||||
uint8_t packet[4];
|
||||
uint8_t packetLength = 0;
|
||||
|
||||
// Address byte
|
||||
if (currentAddress <= 127) {
|
||||
packet[packetLength++] = currentAddress & 0x7F;
|
||||
} else {
|
||||
packet[packetLength++] = 0xC0 | ((currentAddress >> 8) & 0x3F);
|
||||
packet[packetLength++] = currentAddress & 0xFF;
|
||||
}
|
||||
|
||||
// Function group 1 (F0-F4)
|
||||
if (group == 1) {
|
||||
uint8_t functionByte = 0b10000000; // Function group 1
|
||||
functionByte |= ((functionStates & 0x01) ? 0x10 : 0x00); // F0
|
||||
functionByte |= ((functionStates & 0x02) ? 0x01 : 0x00); // F1
|
||||
functionByte |= ((functionStates & 0x04) ? 0x02 : 0x00); // F2
|
||||
functionByte |= ((functionStates & 0x08) ? 0x04 : 0x00); // F3
|
||||
functionByte |= ((functionStates & 0x10) ? 0x08 : 0x00); // F4
|
||||
packet[packetLength++] = functionByte;
|
||||
}
|
||||
|
||||
// Error detection byte
|
||||
packet[packetLength++] = calculateChecksum(packet, packetLength);
|
||||
|
||||
sendPacket(packet, packetLength);
|
||||
}
|
||||
114
src/LEDIndicator.cpp
Normal file
114
src/LEDIndicator.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @file LEDIndicator.cpp
|
||||
* @brief Implementation of LED status indicators
|
||||
*/
|
||||
|
||||
#include "LEDIndicator.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor - initialize with default state
|
||||
*/
|
||||
LEDIndicator::LEDIndicator() :
|
||||
powerOn(false),
|
||||
dccMode(false),
|
||||
brightness(128),
|
||||
lastUpdate(0),
|
||||
pulsePhase(0) {
|
||||
}
|
||||
|
||||
void LEDIndicator::begin() {
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
// 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++;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
void LEDIndicator::setPowerOn(bool on) {
|
||||
if (powerOn != on) {
|
||||
powerOn = on;
|
||||
if (on) {
|
||||
powerOnSequence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LEDIndicator::setMode(bool isDCC) {
|
||||
if (dccMode != isDCC) {
|
||||
dccMode = isDCC;
|
||||
modeChangeEffect();
|
||||
}
|
||||
}
|
||||
|
||||
void LEDIndicator::setBrightness(uint8_t newBrightness) {
|
||||
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");
|
||||
}
|
||||
|
||||
void LEDIndicator::modeChangeEffect() {
|
||||
// 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 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)");
|
||||
}
|
||||
68
src/MotorController.cpp
Normal file
68
src/MotorController.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file MotorController.cpp
|
||||
* @brief Implementation of DC motor control
|
||||
*/
|
||||
|
||||
#include "MotorController.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor - initialize with safe defaults
|
||||
*/
|
||||
MotorController::MotorController() : currentSpeed(0), currentDirection(1) {
|
||||
}
|
||||
|
||||
void MotorController::begin() {
|
||||
// Configure pins
|
||||
pinMode(MOTOR_DIR_PIN, OUTPUT);
|
||||
pinMode(MOTOR_BRAKE_PIN, OUTPUT);
|
||||
|
||||
// Setup PWM
|
||||
ledcSetup(PWM_CHANNEL, PWM_FREQUENCY, PWM_RESOLUTION);
|
||||
ledcAttachPin(MOTOR_PWM_PIN, PWM_CHANNEL);
|
||||
|
||||
// Initialize to safe state
|
||||
digitalWrite(MOTOR_BRAKE_PIN, HIGH); // Release brake (active low)
|
||||
digitalWrite(MOTOR_DIR_PIN, HIGH); // Forward direction
|
||||
ledcWrite(PWM_CHANNEL, 0); // Zero speed
|
||||
|
||||
Serial.println("Motor Controller initialized");
|
||||
Serial.printf("PWM Pin: %d, DIR Pin: %d, BRAKE Pin: %d\n",
|
||||
MOTOR_PWM_PIN, MOTOR_DIR_PIN, MOTOR_BRAKE_PIN);
|
||||
}
|
||||
|
||||
void MotorController::setSpeed(uint8_t speed, uint8_t direction) {
|
||||
currentSpeed = speed;
|
||||
currentDirection = direction;
|
||||
|
||||
// Release brake
|
||||
digitalWrite(MOTOR_BRAKE_PIN, HIGH);
|
||||
|
||||
// Set direction
|
||||
digitalWrite(MOTOR_DIR_PIN, direction ? HIGH : LOW);
|
||||
|
||||
// Set PWM duty cycle
|
||||
// Speed is 0-100, convert to 0-255
|
||||
uint16_t pwmValue = map(speed, 0, 100, 0, 255);
|
||||
ledcWrite(PWM_CHANNEL, pwmValue);
|
||||
|
||||
Serial.printf("Motor: Speed=%d%%, Direction=%s, PWM=%d\n",
|
||||
speed, direction ? "FWD" : "REV", pwmValue);
|
||||
}
|
||||
|
||||
void MotorController::stop() {
|
||||
currentSpeed = 0;
|
||||
ledcWrite(PWM_CHANNEL, 0);
|
||||
digitalWrite(MOTOR_BRAKE_PIN, HIGH); // Release brake
|
||||
Serial.println("Motor stopped");
|
||||
}
|
||||
|
||||
void MotorController::brake() {
|
||||
ledcWrite(PWM_CHANNEL, 0);
|
||||
digitalWrite(MOTOR_BRAKE_PIN, LOW); // Activate brake (active low)
|
||||
currentSpeed = 0;
|
||||
Serial.println("Motor brake activated");
|
||||
}
|
||||
|
||||
void MotorController::update() {
|
||||
// Placeholder for future safety checks or smooth acceleration
|
||||
}
|
||||
158
src/WebServer.cpp
Normal file
158
src/WebServer.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* @file WebServer.cpp
|
||||
* @brief Implementation of web server and REST API
|
||||
*/
|
||||
|
||||
#include "WebServer.h"
|
||||
#include <LittleFS.h>
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
WebServerManager::WebServerManager(Config* cfg, MotorController* motor, DCCGenerator* dcc, LEDIndicator* led)
|
||||
: config(cfg), motorController(motor), dccGenerator(dcc), ledIndicator(led), server(80) {
|
||||
}
|
||||
|
||||
void WebServerManager::begin() {
|
||||
// Initialize LittleFS
|
||||
if (!LittleFS.begin(true)) {
|
||||
Serial.println("LittleFS Mount Failed");
|
||||
return;
|
||||
}
|
||||
Serial.println("LittleFS mounted successfully");
|
||||
|
||||
setupRoutes();
|
||||
server.begin();
|
||||
Serial.println("Web server started on port 80");
|
||||
}
|
||||
|
||||
void WebServerManager::setupRoutes() {
|
||||
// Serve main page
|
||||
server.on("/", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
request->send(LittleFS, "/index.html", "text/html");
|
||||
});
|
||||
|
||||
// Serve static files (CSS, JS, Bootstrap)
|
||||
server.serveStatic("/css/", LittleFS, "/css/");
|
||||
server.serveStatic("/js/", LittleFS, "/js/");
|
||||
|
||||
// API endpoints
|
||||
server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
handleGetStatus(request);
|
||||
});
|
||||
|
||||
server.on("/api/mode", HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
handleSetMode(request);
|
||||
}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
// Body handler
|
||||
DynamicJsonDocument doc(256);
|
||||
deserializeJson(doc, (const char*)data);
|
||||
|
||||
String mode = doc["mode"].as<String>();
|
||||
config->system.isDCCMode = (mode == "dcc");
|
||||
|
||||
if (config->system.isDCCMode) {
|
||||
motorController->stop();
|
||||
dccGenerator->enable();
|
||||
ledIndicator->setMode(true);
|
||||
} else {
|
||||
dccGenerator->disable();
|
||||
ledIndicator->setMode(false);
|
||||
}
|
||||
|
||||
config->save();
|
||||
request->send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
});
|
||||
|
||||
server.on("/api/speed", HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
// Will be handled by body handler
|
||||
}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
DynamicJsonDocument doc(256);
|
||||
deserializeJson(doc, (const char*)data);
|
||||
|
||||
uint8_t speed = doc["speed"];
|
||||
uint8_t direction = doc["direction"];
|
||||
|
||||
config->system.speed = speed;
|
||||
config->system.direction = direction;
|
||||
|
||||
if (config->system.isDCCMode) {
|
||||
dccGenerator->setLocoSpeed(config->system.dccAddress, speed, direction);
|
||||
} else {
|
||||
motorController->setSpeed(speed, direction);
|
||||
}
|
||||
|
||||
request->send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
});
|
||||
|
||||
server.on("/api/dcc/address", HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
// Will be handled by body handler
|
||||
}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
DynamicJsonDocument doc(256);
|
||||
deserializeJson(doc, (const char*)data);
|
||||
|
||||
config->system.dccAddress = doc["address"];
|
||||
config->save();
|
||||
|
||||
request->send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
});
|
||||
|
||||
server.on("/api/dcc/function", HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
// Will be handled by body handler
|
||||
}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
DynamicJsonDocument doc(256);
|
||||
deserializeJson(doc, (const char*)data);
|
||||
|
||||
uint8_t function = doc["function"];
|
||||
bool state = doc["state"];
|
||||
|
||||
dccGenerator->setFunction(config->system.dccAddress, function, state);
|
||||
|
||||
request->send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
});
|
||||
|
||||
server.on("/api/wifi", HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
// Will be handled by body handler
|
||||
}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
DynamicJsonDocument doc(512);
|
||||
deserializeJson(doc, (const char*)data);
|
||||
|
||||
config->wifi.isAPMode = doc["isAPMode"];
|
||||
config->wifi.apSSID = doc["apSSID"].as<String>();
|
||||
config->wifi.apPassword = doc["apPassword"].as<String>();
|
||||
config->wifi.ssid = doc["ssid"].as<String>();
|
||||
config->wifi.password = doc["password"].as<String>();
|
||||
|
||||
config->save();
|
||||
|
||||
request->send(200, "application/json", "{\"status\":\"ok\"}");
|
||||
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
});
|
||||
}
|
||||
|
||||
void WebServerManager::handleGetStatus(AsyncWebServerRequest *request) {
|
||||
String json = getStatusJSON();
|
||||
request->send(200, "application/json", json);
|
||||
}
|
||||
|
||||
String WebServerManager::getStatusJSON() {
|
||||
DynamicJsonDocument doc(512);
|
||||
|
||||
doc["mode"] = config->system.isDCCMode ? "dcc" : "analog";
|
||||
doc["speed"] = config->system.speed;
|
||||
doc["direction"] = config->system.direction;
|
||||
doc["dccAddress"] = config->system.dccAddress;
|
||||
// doc["ip"] = config->wifi.isAPMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString();
|
||||
doc["ip"] = "TODO";
|
||||
doc["wifiMode"] = config->wifi.isAPMode ? "ap" : "client";
|
||||
|
||||
String output;
|
||||
serializeJson(doc, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
void WebServerManager::update() {
|
||||
// AsyncWebServer handles requests asynchronously
|
||||
}
|
||||
103
src/WiFiManager.cpp
Normal file
103
src/WiFiManager.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @file WiFiManager.cpp
|
||||
* @brief Implementation of WiFi management
|
||||
*/
|
||||
|
||||
#include "WiFiManager.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
WiFiManager::WiFiManager(Config* cfg) : config(cfg), lastReconnectAttempt(0) {
|
||||
}
|
||||
|
||||
void WiFiManager::begin() {
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
delay(100);
|
||||
|
||||
if (config->wifi.isAPMode) {
|
||||
setupAccessPoint();
|
||||
} else {
|
||||
connectToWiFi();
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManager::setupAccessPoint() {
|
||||
Serial.println("Setting up Access Point...");
|
||||
WiFi.mode(WIFI_AP);
|
||||
|
||||
bool success = WiFi.softAP(
|
||||
config->wifi.apSSID.c_str(),
|
||||
config->wifi.apPassword.c_str()
|
||||
);
|
||||
|
||||
if (success) {
|
||||
IPAddress IP = WiFi.softAPIP();
|
||||
Serial.print("AP IP address: ");
|
||||
Serial.println(IP);
|
||||
Serial.print("AP SSID: ");
|
||||
Serial.println(config->wifi.apSSID);
|
||||
} else {
|
||||
Serial.println("Failed to create Access Point!");
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManager::connectToWiFi() {
|
||||
if (config->wifi.ssid.length() == 0) {
|
||||
Serial.println("No WiFi credentials configured. Starting AP mode.");
|
||||
config->wifi.isAPMode = true;
|
||||
setupAccessPoint();
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println("Connecting to WiFi...");
|
||||
Serial.print("SSID: ");
|
||||
Serial.println(config->wifi.ssid);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(config->wifi.ssid.c_str(), config->wifi.password.c_str());
|
||||
|
||||
int attempts = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.println("\nWiFi connected!");
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
} else {
|
||||
Serial.println("\nFailed to connect to WiFi. Starting AP mode.");
|
||||
config->wifi.isAPMode = true;
|
||||
setupAccessPoint();
|
||||
}
|
||||
}
|
||||
|
||||
bool WiFiManager::isConnected() {
|
||||
if (config->wifi.isAPMode) {
|
||||
return WiFi.softAPgetStationNum() > 0 || true; // AP is always "connected"
|
||||
}
|
||||
return WiFi.status() == WL_CONNECTED;
|
||||
}
|
||||
|
||||
String WiFiManager::getIPAddress() {
|
||||
if (config->wifi.isAPMode) {
|
||||
return WiFi.softAPIP().toString();
|
||||
}
|
||||
return WiFi.localIP().toString();
|
||||
}
|
||||
|
||||
void WiFiManager::update() {
|
||||
// Auto-reconnect if in STA mode and disconnected
|
||||
if (!config->wifi.isAPMode && WiFi.status() != WL_CONNECTED) {
|
||||
unsigned long now = millis();
|
||||
if (now - lastReconnectAttempt > RECONNECT_INTERVAL) {
|
||||
lastReconnectAttempt = now;
|
||||
Serial.println("Attempting to reconnect to WiFi...");
|
||||
WiFi.disconnect();
|
||||
WiFi.begin(config->wifi.ssid.c_str(), config->wifi.password.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
133
src/main.cpp
Normal file
133
src/main.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief Main application entry point for Locomotive Test Bench
|
||||
*
|
||||
* Orchestrates all system components:
|
||||
* - Configuration management
|
||||
* - WiFi connectivity
|
||||
* - Motor control (DC analog)
|
||||
* - DCC signal generation
|
||||
* - LED status indicators
|
||||
* - Web server interface
|
||||
*
|
||||
* @author Locomotive Test Bench Project
|
||||
* @date 2025
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "Config.h"
|
||||
#include "WiFiManager.h"
|
||||
#include "MotorController.h"
|
||||
#include "DCCGenerator.h"
|
||||
#include "LEDIndicator.h"
|
||||
#include "WebServer.h"
|
||||
|
||||
// Global objects
|
||||
Config config;
|
||||
WiFiManager wifiManager(&config);
|
||||
MotorController motorController;
|
||||
DCCGenerator dccGenerator;
|
||||
LEDIndicator ledIndicator;
|
||||
WebServerManager webServer(&config, &motorController, &dccGenerator, &ledIndicator);
|
||||
|
||||
/**
|
||||
* @brief Setup function - runs once at startup
|
||||
*
|
||||
* Initializes all hardware and software components in correct order:
|
||||
* 1. Serial communication
|
||||
* 2. Configuration system
|
||||
* 3. WiFi connectivity
|
||||
* 4. LED indicators
|
||||
* 5. Motor controller
|
||||
* 6. DCC generator
|
||||
* 7. Web server
|
||||
*/
|
||||
void setup() {
|
||||
// Initialize serial communication
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
Serial.println("\n\n=================================");
|
||||
Serial.println(" Locomotive Test Bench v1.0");
|
||||
Serial.println("=================================\n");
|
||||
|
||||
// Load configuration
|
||||
config.begin();
|
||||
Serial.println("Configuration loaded");
|
||||
|
||||
// Initialize WiFi
|
||||
wifiManager.begin();
|
||||
|
||||
// Initialize LED indicator
|
||||
ledIndicator.begin();
|
||||
ledIndicator.setPowerOn(true);
|
||||
|
||||
// Initialize motor controller
|
||||
motorController.begin();
|
||||
|
||||
// Initialize DCC generator
|
||||
dccGenerator.begin();
|
||||
|
||||
// Set initial mode and LED
|
||||
if (config.system.isDCCMode) {
|
||||
dccGenerator.enable();
|
||||
ledIndicator.setMode(true);
|
||||
dccGenerator.setLocoSpeed(
|
||||
config.system.dccAddress,
|
||||
config.system.speed,
|
||||
config.system.direction
|
||||
);
|
||||
} else {
|
||||
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() {
|
||||
// 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("Web interface: http://");
|
||||
Serial.println(wifiManager.getIPAddress());
|
||||
Serial.println("=================================\n");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Update WiFi connection status
|
||||
wifiManager.update();
|
||||
|
||||
// Update LED indicators
|
||||
ledIndicator.update();
|
||||
|
||||
// Update DCC signal generation (if enabled)
|
||||
if (config.system.isDCCMode) {
|
||||
dccGenerator.update();
|
||||
} else {
|
||||
motorController.update();
|
||||
}
|
||||
|
||||
// Web server updates (handled by AsyncWebServer)
|
||||
webServer.update();
|
||||
|
||||
// Small delay to prevent watchdog issues
|
||||
delay(1);
|
||||
}
|
||||
Reference in New Issue
Block a user