Initialisation depot
This commit is contained in:
282
Doxyfile
Normal file
282
Doxyfile
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# Doxyfile 1.9.1
|
||||||
|
# Configuration file for Doxygen documentation generation
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Project related configuration options
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
DOXYFILE_ENCODING = UTF-8
|
||||||
|
PROJECT_NAME = "Locomotive Test Bench"
|
||||||
|
PROJECT_NUMBER = 1.0
|
||||||
|
PROJECT_BRIEF = "ESP32-based test bench for DC and DCC model locomotives"
|
||||||
|
PROJECT_LOGO =
|
||||||
|
OUTPUT_DIRECTORY = ./doc
|
||||||
|
CREATE_SUBDIRS = NO
|
||||||
|
ALLOW_UNICODE_NAMES = NO
|
||||||
|
OUTPUT_LANGUAGE = English
|
||||||
|
BRIEF_MEMBER_DESC = YES
|
||||||
|
REPEAT_BRIEF = YES
|
||||||
|
ABBREVIATE_BRIEF =
|
||||||
|
ALWAYS_DETAILED_SEC = NO
|
||||||
|
INLINE_INHERITED_MEMB = NO
|
||||||
|
FULL_PATH_NAMES = YES
|
||||||
|
STRIP_FROM_PATH =
|
||||||
|
STRIP_FROM_INC_PATH =
|
||||||
|
SHORT_NAMES = NO
|
||||||
|
JAVADOC_AUTOBRIEF = YES
|
||||||
|
JAVADOC_BANNER = NO
|
||||||
|
QT_AUTOBRIEF = NO
|
||||||
|
MULTILINE_CPP_IS_BRIEF = NO
|
||||||
|
INHERIT_DOCS = YES
|
||||||
|
SEPARATE_MEMBER_PAGES = NO
|
||||||
|
TAB_SIZE = 4
|
||||||
|
OPTIMIZE_OUTPUT_FOR_C = NO
|
||||||
|
OPTIMIZE_OUTPUT_JAVA = NO
|
||||||
|
OPTIMIZE_FOR_FORTRAN = NO
|
||||||
|
OPTIMIZE_OUTPUT_VHDL = NO
|
||||||
|
OPTIMIZE_OUTPUT_SLICE = NO
|
||||||
|
MARKDOWN_SUPPORT = YES
|
||||||
|
TOC_INCLUDE_HEADINGS = 5
|
||||||
|
AUTOLINK_SUPPORT = YES
|
||||||
|
BUILTIN_STL_SUPPORT = YES
|
||||||
|
CPP_CLI_SUPPORT = NO
|
||||||
|
DISTRIBUTE_GROUP_DOC = NO
|
||||||
|
GROUP_NESTED_COMPOUNDS = NO
|
||||||
|
SUBGROUPING = YES
|
||||||
|
INLINE_GROUPED_CLASSES = NO
|
||||||
|
INLINE_SIMPLE_STRUCTS = NO
|
||||||
|
TYPEDEF_HIDES_STRUCT = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Build related configuration options
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
EXTRACT_ALL = YES
|
||||||
|
EXTRACT_PRIVATE = YES
|
||||||
|
EXTRACT_PRIV_VIRTUAL = NO
|
||||||
|
EXTRACT_PACKAGE = NO
|
||||||
|
EXTRACT_STATIC = YES
|
||||||
|
EXTRACT_LOCAL_CLASSES = YES
|
||||||
|
EXTRACT_LOCAL_METHODS = NO
|
||||||
|
EXTRACT_ANON_NSPACES = NO
|
||||||
|
HIDE_UNDOC_MEMBERS = NO
|
||||||
|
HIDE_UNDOC_CLASSES = NO
|
||||||
|
HIDE_FRIEND_COMPOUNDS = NO
|
||||||
|
HIDE_IN_BODY_DOCS = NO
|
||||||
|
INTERNAL_DOCS = NO
|
||||||
|
CASE_SENSE_NAMES = YES
|
||||||
|
HIDE_SCOPE_NAMES = NO
|
||||||
|
HIDE_COMPOUND_REFERENCE= NO
|
||||||
|
SHOW_INCLUDE_FILES = YES
|
||||||
|
SHOW_GROUPED_MEMB_INC = NO
|
||||||
|
FORCE_LOCAL_INCLUDES = NO
|
||||||
|
INLINE_INFO = YES
|
||||||
|
SORT_MEMBER_DOCS = YES
|
||||||
|
SORT_BRIEF_DOCS = NO
|
||||||
|
SORT_MEMBERS_CTORS_1ST = NO
|
||||||
|
SORT_GROUP_NAMES = NO
|
||||||
|
SORT_BY_SCOPE_NAME = NO
|
||||||
|
STRICT_PROTO_MATCHING = NO
|
||||||
|
GENERATE_TODOLIST = YES
|
||||||
|
GENERATE_TESTLIST = YES
|
||||||
|
GENERATE_BUGLIST = YES
|
||||||
|
GENERATE_DEPRECATEDLIST= YES
|
||||||
|
MAX_INITIALIZER_LINES = 30
|
||||||
|
SHOW_USED_FILES = YES
|
||||||
|
SHOW_FILES = YES
|
||||||
|
SHOW_NAMESPACES = YES
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to warning and progress messages
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
QUIET = NO
|
||||||
|
WARNINGS = YES
|
||||||
|
WARN_IF_UNDOCUMENTED = YES
|
||||||
|
WARN_IF_DOC_ERROR = YES
|
||||||
|
WARN_NO_PARAMDOC = YES
|
||||||
|
WARN_AS_ERROR = NO
|
||||||
|
WARN_FORMAT = "$file:$line: $text"
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the input files
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
INPUT = ./src \
|
||||||
|
./include \
|
||||||
|
./README.md
|
||||||
|
INPUT_ENCODING = UTF-8
|
||||||
|
FILE_PATTERNS = *.cpp \
|
||||||
|
*.h \
|
||||||
|
*.md
|
||||||
|
RECURSIVE = YES
|
||||||
|
EXCLUDE =
|
||||||
|
EXCLUDE_SYMLINKS = NO
|
||||||
|
EXCLUDE_PATTERNS = */build/* \
|
||||||
|
*/.pio/* \
|
||||||
|
*/data/*
|
||||||
|
EXCLUDE_SYMBOLS =
|
||||||
|
EXAMPLE_PATH =
|
||||||
|
EXAMPLE_PATTERNS = *
|
||||||
|
EXAMPLE_RECURSIVE = NO
|
||||||
|
IMAGE_PATH =
|
||||||
|
INPUT_FILTER =
|
||||||
|
FILTER_PATTERNS =
|
||||||
|
FILTER_SOURCE_FILES = NO
|
||||||
|
FILTER_SOURCE_PATTERNS =
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to source browsing
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
SOURCE_BROWSER = YES
|
||||||
|
INLINE_SOURCES = NO
|
||||||
|
STRIP_CODE_COMMENTS = YES
|
||||||
|
REFERENCED_BY_RELATION = YES
|
||||||
|
REFERENCES_RELATION = YES
|
||||||
|
REFERENCES_LINK_SOURCE = YES
|
||||||
|
SOURCE_TOOLTIPS = YES
|
||||||
|
USE_HTAGS = NO
|
||||||
|
VERBATIM_HEADERS = YES
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the alphabetical class index
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ALPHABETICAL_INDEX = YES
|
||||||
|
COLS_IN_ALPHA_INDEX = 5
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the HTML output
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GENERATE_HTML = YES
|
||||||
|
HTML_OUTPUT = html
|
||||||
|
HTML_FILE_EXTENSION = .html
|
||||||
|
HTML_HEADER =
|
||||||
|
HTML_FOOTER =
|
||||||
|
HTML_STYLESHEET =
|
||||||
|
HTML_EXTRA_STYLESHEET =
|
||||||
|
HTML_EXTRA_FILES =
|
||||||
|
HTML_COLORSTYLE_HUE = 220
|
||||||
|
HTML_COLORSTYLE_SAT = 100
|
||||||
|
HTML_COLORSTYLE_GAMMA = 80
|
||||||
|
HTML_TIMESTAMP = YES
|
||||||
|
HTML_DYNAMIC_MENUS = YES
|
||||||
|
HTML_DYNAMIC_SECTIONS = NO
|
||||||
|
HTML_INDEX_NUM_ENTRIES = 100
|
||||||
|
GENERATE_DOCSET = NO
|
||||||
|
GENERATE_HTMLHELP = NO
|
||||||
|
GENERATE_QHP = NO
|
||||||
|
GENERATE_ECLIPSEHELP = NO
|
||||||
|
DISABLE_INDEX = NO
|
||||||
|
GENERATE_TREEVIEW = YES
|
||||||
|
ENUM_VALUES_PER_LINE = 4
|
||||||
|
TREEVIEW_WIDTH = 250
|
||||||
|
EXT_LINKS_IN_WINDOW = NO
|
||||||
|
HTML_FORMULA_FORMAT = png
|
||||||
|
FORMULA_FONTSIZE = 10
|
||||||
|
FORMULA_TRANSPARENT = YES
|
||||||
|
FORMULA_MACROFILE =
|
||||||
|
USE_MATHJAX = NO
|
||||||
|
SEARCHENGINE = YES
|
||||||
|
SERVER_BASED_SEARCH = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the LaTeX output
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GENERATE_LATEX = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the RTF output
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GENERATE_RTF = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the man page output
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GENERATE_MAN = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the XML output
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GENERATE_XML = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the DOCBOOK output
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GENERATE_DOCBOOK = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options for the AutoGen Definitions output
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GENERATE_AUTOGEN_DEF = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the Perl module output
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GENERATE_PERLMOD = NO
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the preprocessor
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ENABLE_PREPROCESSING = YES
|
||||||
|
MACRO_EXPANSION = YES
|
||||||
|
EXPAND_ONLY_PREDEF = NO
|
||||||
|
SEARCH_INCLUDES = YES
|
||||||
|
INCLUDE_PATH = ./include
|
||||||
|
INCLUDE_FILE_PATTERNS =
|
||||||
|
PREDEFINED = ARDUINO \
|
||||||
|
ESP32
|
||||||
|
EXPAND_AS_DEFINED =
|
||||||
|
SKIP_FUNCTION_MACROS = YES
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to external references
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TAGFILES =
|
||||||
|
GENERATE_TAGFILE =
|
||||||
|
ALLEXTERNALS = NO
|
||||||
|
EXTERNAL_GROUPS = YES
|
||||||
|
EXTERNAL_PAGES = YES
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
# Configuration options related to the dot tool
|
||||||
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
CLASS_DIAGRAMS = YES
|
||||||
|
DIA_PATH =
|
||||||
|
HIDE_UNDOC_RELATIONS = YES
|
||||||
|
HAVE_DOT = NO
|
||||||
|
DOT_NUM_THREADS = 0
|
||||||
|
DOT_FONTNAME = Helvetica
|
||||||
|
DOT_FONTSIZE = 10
|
||||||
|
CLASS_GRAPH = YES
|
||||||
|
COLLABORATION_GRAPH = YES
|
||||||
|
GROUP_GRAPHS = YES
|
||||||
|
UML_LOOK = NO
|
||||||
|
UML_LIMIT_NUM_FIELDS = 10
|
||||||
|
TEMPLATE_RELATIONS = NO
|
||||||
|
INCLUDE_GRAPH = YES
|
||||||
|
INCLUDED_BY_GRAPH = YES
|
||||||
|
CALL_GRAPH = NO
|
||||||
|
CALLER_GRAPH = NO
|
||||||
|
GRAPHICAL_HIERARCHY = YES
|
||||||
|
DIRECTORY_GRAPH = YES
|
||||||
|
DOT_IMAGE_FORMAT = png
|
||||||
|
INTERACTIVE_SVG = NO
|
||||||
|
DOT_GRAPH_MAX_NODES = 50
|
||||||
|
MAX_DOT_GRAPH_DEPTH = 0
|
||||||
|
DOT_TRANSPARENT = NO
|
||||||
|
DOT_MULTI_TARGETS = NO
|
||||||
|
GENERATE_LEGEND = YES
|
||||||
|
DOT_CLEANUP = YES
|
||||||
48
PIN_REFERENCE.txt
Normal file
48
PIN_REFERENCE.txt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Pin Configuration Reference
|
||||||
|
*
|
||||||
|
* This file documents all pin assignments for quick reference.
|
||||||
|
* To change pins, edit the corresponding header files.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// MOTOR CONTROLLER (LM18200)
|
||||||
|
// Defined in: include/MotorController.h
|
||||||
|
// ===================================
|
||||||
|
#define MOTOR_PWM_PIN 25 // PWM signal for speed control
|
||||||
|
#define MOTOR_DIR_PIN 26 // Direction: HIGH=forward, LOW=reverse
|
||||||
|
#define MOTOR_BRAKE_PIN 27 // Brake: LOW=brake, HIGH=release
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// DCC GENERATOR
|
||||||
|
// Defined in: include/DCCGenerator.h
|
||||||
|
// ===================================
|
||||||
|
#define DCC_PIN_A 32 // DCC signal output A
|
||||||
|
#define DCC_PIN_B 33 // DCC signal output B (inverted)
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// LED INDICATORS (WS2812)
|
||||||
|
// Defined in: include/LEDIndicator.h
|
||||||
|
// ===================================
|
||||||
|
#define LED_DATA_PIN 4 // WS2812 data line
|
||||||
|
#define NUM_LEDS 2 // LED 0: Power, LED 1: Mode
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// AVAILABLE GPIO PINS (ESP32 D1 Mini)
|
||||||
|
// ===================================
|
||||||
|
// Used: 4, 25, 26, 27, 32, 33
|
||||||
|
// Available for expansion:
|
||||||
|
// - GPIO 5, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23
|
||||||
|
// - GPIO 34, 35, 36, 39 (Input only)
|
||||||
|
// Reserved for internal use:
|
||||||
|
// - GPIO 0, 2 (Boot/Flash)
|
||||||
|
// - GPIO 1, 3 (Serial TX/RX)
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// NOTES
|
||||||
|
// ===================================
|
||||||
|
// - All control pins are outputs except where noted
|
||||||
|
// - Ensure adequate current capacity for motor loads
|
||||||
|
// - DCC outputs require proper signal conditioning
|
||||||
|
// - PWM frequency: 20kHz (defined in MotorController.cpp)
|
||||||
|
// - DCC timing follows NMRA standards
|
||||||
438
README.md
Normal file
438
README.md
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
# 🚂 Locomotive Test Bench
|
||||||
|
|
||||||
|
A comprehensive testing platform for model/scale locomotives using ESP32 (D1 Mini ESP32) and LM18200 H-Bridge motor driver. This system supports both **DC Analog** and **DCC Digital** control modes with a responsive web interface.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Control Modes
|
||||||
|
- **DC Analog Mode**: Traditional PWM-based speed control with bidirectional operation
|
||||||
|
- **DCC Digital Mode**: Full DCC protocol support for digital model locomotives
|
||||||
|
- 128-step speed control
|
||||||
|
- Function control (F0-F12)
|
||||||
|
- Short and long address support (1-10239)
|
||||||
|
|
||||||
|
### WiFi Capabilities
|
||||||
|
- **Access Point Mode**: Create a standalone WiFi network
|
||||||
|
- **Client Mode**: Connect to existing WiFi networks
|
||||||
|
- Automatic reconnection in client mode
|
||||||
|
- Runtime WiFi configuration via web interface
|
||||||
|
|
||||||
|
### Web Interface
|
||||||
|
- Responsive Bootstrap-based design
|
||||||
|
- Real-time status monitoring
|
||||||
|
- Speed control with visual slider
|
||||||
|
- Direction control (forward/reverse)
|
||||||
|
- Emergency stop button
|
||||||
|
- DCC address configuration
|
||||||
|
- Function button controls (F0-F12) for DCC mode
|
||||||
|
- WiFi settings management
|
||||||
|
- Mobile-friendly design
|
||||||
|
|
||||||
|
## Hardware Requirements
|
||||||
|
|
||||||
|
### Components
|
||||||
|
- **ESP32 D1 Mini** (or compatible ESP32 board)
|
||||||
|
- **LM18200 H-Bridge Motor Driver**
|
||||||
|
- **2x WS2812 RGB LEDs** (for status indication)
|
||||||
|
- **Power Supply**: Suitable for your locomotive scale (typically 12-18V)
|
||||||
|
- Model locomotive (DC or DCC compatible)
|
||||||
|
|
||||||
|
### Pin Connections
|
||||||
|
|
||||||
|
#### LM18200 Motor Driver (DC Analog Mode)
|
||||||
|
| LM18200 Pin | ESP32 Pin | Description |
|
||||||
|
|-------------|-----------|-------------|
|
||||||
|
| PWM | GPIO 25 | PWM speed control |
|
||||||
|
| DIR | GPIO 26 | Direction control |
|
||||||
|
| BRAKE | GPIO 27 | Brake control (active low) |
|
||||||
|
| OUT1 | - | Motor terminal 1 |
|
||||||
|
| OUT2 | - | Motor terminal 2 |
|
||||||
|
| Vcc | 5V | Logic power |
|
||||||
|
| GND | GND | Ground |
|
||||||
|
|
||||||
|
#### DCC Signal Output
|
||||||
|
| Signal | ESP32 Pin | Description |
|
||||||
|
|--------|-----------|-------------|
|
||||||
|
| DCC A | GPIO 32 | DCC Signal A |
|
||||||
|
| DCC B | GPIO 33 | DCC Signal B (inverted) |
|
||||||
|
|
||||||
|
#### Status LEDs (WS2812)
|
||||||
|
| LED | ESP32 Pin | Function | Colors |
|
||||||
|
|-----|-----------|----------|---------|
|
||||||
|
| Data | GPIO 4 | LED strip data | - |
|
||||||
|
| LED 0 | - | Power status | Green=ON, Red=OFF |
|
||||||
|
| LED 1 | - | Mode indicator | Blue=DCC, Yellow=Analog |
|
||||||
|
|
||||||
|
**Note**: DCC signals require appropriate signal conditioning and booster circuitry for track connection.
|
||||||
|
|
||||||
|
### Wiring Diagram Notes
|
||||||
|
1. Connect LM18200 motor outputs to track or locomotive
|
||||||
|
2. Ensure proper power supply voltage for your scale
|
||||||
|
3. DCC mode requires additional booster circuit (not included in basic schematic)
|
||||||
|
4. Use appropriate heat sinking for LM18200
|
||||||
|
|
||||||
|
## Software Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- [Visual Studio Code](https://code.visualstudio.com/)
|
||||||
|
- [PlatformIO IDE Extension](https://platformio.org/install/ide?install=vscode)
|
||||||
|
|
||||||
|
### Installation Steps
|
||||||
|
|
||||||
|
1. **Clone or download this project**
|
||||||
|
```bash
|
||||||
|
cd /your/projects/folder
|
||||||
|
git clone <repository-url>
|
||||||
|
cd LocomotiveTestBench
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Open in VS Code**
|
||||||
|
- Open VS Code
|
||||||
|
- File → Open Folder → Select `LocomotiveTestBench` folder
|
||||||
|
|
||||||
|
3. **Download Bootstrap files for offline use**
|
||||||
|
```bash
|
||||||
|
cd data
|
||||||
|
chmod +x download_bootstrap.sh
|
||||||
|
./download_bootstrap.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or download manually:
|
||||||
|
- [Bootstrap CSS](https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css) → `data/css/bootstrap.min.css`
|
||||||
|
- [Bootstrap JS](https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js) → `data/js/bootstrap.bundle.min.js`
|
||||||
|
|
||||||
|
4. **Upload Filesystem (LittleFS)**
|
||||||
|
- Click PlatformIO icon in sidebar
|
||||||
|
- Under PROJECT TASKS → Upload Filesystem Image
|
||||||
|
- Wait for upload to complete
|
||||||
|
|
||||||
|
5. **Build the project**
|
||||||
|
- Select "Build" under PROJECT TASKS
|
||||||
|
|
||||||
|
6. **Upload to ESP32**
|
||||||
|
- Connect ESP32 via USB
|
||||||
|
- Click "Upload" under PROJECT TASKS
|
||||||
|
- Wait for upload to complete
|
||||||
|
|
||||||
|
7. **Monitor Serial Output** (optional)
|
||||||
|
- Click "Monitor" to see debug output
|
||||||
|
- Default baud rate: 115200
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### First Time Setup
|
||||||
|
|
||||||
|
1. **Power on the ESP32**
|
||||||
|
- Default mode: Access Point
|
||||||
|
- SSID: `LocoTestBench`
|
||||||
|
- Password: `12345678`
|
||||||
|
|
||||||
|
2. **Connect to WiFi**
|
||||||
|
- Use phone/computer to connect to `LocoTestBench` network
|
||||||
|
- Default IP: `192.168.4.1`
|
||||||
|
|
||||||
|
3. **Access Web Interface**
|
||||||
|
- Open browser: `http://192.168.4.1`
|
||||||
|
- You should see the Locomotive Test Bench interface
|
||||||
|
|
||||||
|
### WiFi Configuration
|
||||||
|
|
||||||
|
#### Access Point Mode (Default)
|
||||||
|
- Creates standalone network
|
||||||
|
- Default SSID: `LocoTestBench`
|
||||||
|
- Default Password: `12345678`
|
||||||
|
- IP Address: `192.168.4.1`
|
||||||
|
|
||||||
|
#### Client Mode
|
||||||
|
1. Open web interface
|
||||||
|
2. Expand "WiFi Configuration"
|
||||||
|
3. Select "Client (Connect to Network)"
|
||||||
|
4. Enter your network SSID and password
|
||||||
|
5. Click "Save & Restart"
|
||||||
|
6. Device will restart and connect to your network
|
||||||
|
7. Check serial monitor for assigned IP address
|
||||||
|
|
||||||
|
## Usage Guide
|
||||||
|
|
||||||
|
### DC Analog Mode
|
||||||
|
|
||||||
|
1. **Select Mode**
|
||||||
|
- Click "DC Analog" button in Control Mode section
|
||||||
|
|
||||||
|
2. **Set Speed**
|
||||||
|
- Use slider to adjust speed (0-100%)
|
||||||
|
- Speed shown in large display
|
||||||
|
|
||||||
|
3. **Change Direction**
|
||||||
|
- Click "🔄 Reverse" button to toggle direction
|
||||||
|
- Arrow indicator shows current direction (→ forward, ← reverse)
|
||||||
|
|
||||||
|
4. **Emergency Stop**
|
||||||
|
- Click "⏹ STOP" button to immediately stop locomotive
|
||||||
|
|
||||||
|
### DCC Digital Mode
|
||||||
|
|
||||||
|
1. **Select Mode**
|
||||||
|
- Click "DCC Digital" button in Control Mode section
|
||||||
|
- DCC sections will appear
|
||||||
|
|
||||||
|
2. **Set Locomotive Address**
|
||||||
|
- Enter DCC address (1-10239)
|
||||||
|
- Click "Set" button
|
||||||
|
- Address is saved to memory
|
||||||
|
|
||||||
|
3. **Control Speed**
|
||||||
|
- Use slider to adjust speed (0-100%)
|
||||||
|
- Direction control works same as analog mode
|
||||||
|
|
||||||
|
4. **DCC Functions**
|
||||||
|
- Function buttons (F0-F12) appear in DCC mode
|
||||||
|
- Click button to toggle function ON/OFF
|
||||||
|
- Active functions shown in darker color
|
||||||
|
|
||||||
|
## Pin Customization
|
||||||
|
|
||||||
|
To change pin assignments, edit these files:
|
||||||
|
|
||||||
|
### Motor Controller Pins
|
||||||
|
Edit `include/MotorController.h`:
|
||||||
|
```cpp
|
||||||
|
#define MOTOR_PWM_PIN 25 // Change as needed
|
||||||
|
#define MOTOR_DIR_PIN 26 // Change as needed
|
||||||
|
#define MOTOR_BRAKE_PIN 27 // Change as needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### DCC Output Pins
|
||||||
|
Edit `include/DCCGenerator.h`:
|
||||||
|
```cpp
|
||||||
|
#define DCC_PIN_A 32 // Change as needed
|
||||||
|
#define DCC_PIN_B 33 // Change as needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### LED Indicator Pins
|
||||||
|
Edit `include/LEDIndicator.h`:
|
||||||
|
```cpp
|
||||||
|
#define LED_DATA_PIN 4 // WS2812 data pin
|
||||||
|
#define NUM_LEDS 2 // Number of LEDs
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
This project includes comprehensive API documentation using Doxygen.
|
||||||
|
|
||||||
|
### Generate Documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Doxygen (if not already installed)
|
||||||
|
# Ubuntu/Debian: sudo apt-get install doxygen graphviz
|
||||||
|
# macOS: brew install doxygen graphviz
|
||||||
|
|
||||||
|
# Generate documentation
|
||||||
|
./generate_docs.sh
|
||||||
|
|
||||||
|
# View documentation
|
||||||
|
xdg-open doc/html/index.html # Linux
|
||||||
|
open doc/html/index.html # macOS
|
||||||
|
```
|
||||||
|
|
||||||
|
The documentation includes:
|
||||||
|
- Detailed class descriptions
|
||||||
|
- Function/method documentation
|
||||||
|
- Parameter and return value descriptions
|
||||||
|
- Code examples and usage notes
|
||||||
|
- Cross-referenced source code
|
||||||
|
|
||||||
|
See `doc/README.md` for more information.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
LocomotiveTestBench/
|
||||||
|
├── platformio.ini # PlatformIO configuration
|
||||||
|
├── Doxyfile # Doxygen configuration for API docs
|
||||||
|
├── generate_docs.sh # Script to generate documentation
|
||||||
|
├── README.md # This file
|
||||||
|
├── doc/ # Generated API documentation
|
||||||
|
│ └── html/ # HTML documentation (generated)
|
||||||
|
├── data/ # Filesystem (uploaded to LittleFS)
|
||||||
|
│ ├── index.html # Main web interface
|
||||||
|
│ ├── css/
|
||||||
|
│ │ ├── bootstrap.min.css # Bootstrap CSS (local)
|
||||||
|
│ │ └── style.css # Custom styles
|
||||||
|
│ └── js/
|
||||||
|
│ ├── bootstrap.bundle.min.js # Bootstrap JS (local)
|
||||||
|
│ └── app.js # Application JavaScript
|
||||||
|
├── include/ # Header files
|
||||||
|
│ ├── Config.h # Configuration management
|
||||||
|
│ ├── WiFiManager.h # WiFi connectivity
|
||||||
|
│ ├── MotorController.h # DC motor control
|
||||||
|
│ ├── DCCGenerator.h # DCC signal generation
|
||||||
|
│ ├── LEDIndicator.h # WS2812 LED status indicators
|
||||||
|
│ └── WebServer.h # Web server & API
|
||||||
|
└── src/ # Source files
|
||||||
|
├── main.cpp # Main application
|
||||||
|
├── Config.cpp # Configuration implementation
|
||||||
|
├── WiFiManager.cpp # WiFi implementation
|
||||||
|
├── MotorController.cpp # Motor control implementation
|
||||||
|
├── DCCGenerator.cpp # DCC implementation
|
||||||
|
├── LEDIndicator.cpp # LED indicator implementation
|
||||||
|
└── WebServer.cpp # Web server implementation
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### REST API Endpoints
|
||||||
|
|
||||||
|
#### GET /api/status
|
||||||
|
Returns current system status
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mode": "dcc",
|
||||||
|
"speed": 50,
|
||||||
|
"direction": 1,
|
||||||
|
"dccAddress": 3,
|
||||||
|
"ip": "192.168.4.1",
|
||||||
|
"wifiMode": "ap"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /api/mode
|
||||||
|
Set control mode
|
||||||
|
```json
|
||||||
|
{"mode": "dcc"} // or "analog"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /api/speed
|
||||||
|
Set speed and direction
|
||||||
|
```json
|
||||||
|
{"speed": 75, "direction": 1}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /api/dcc/address
|
||||||
|
Set DCC locomotive address
|
||||||
|
```json
|
||||||
|
{"address": 1234}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /api/dcc/function
|
||||||
|
Control DCC function
|
||||||
|
```json
|
||||||
|
{"function": 0, "state": true}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /api/wifi
|
||||||
|
Configure WiFi (triggers restart)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"isAPMode": false,
|
||||||
|
"ssid": "YourNetwork",
|
||||||
|
"password": "YourPassword"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Cannot Connect to WiFi AP
|
||||||
|
- Verify ESP32 has power
|
||||||
|
- Check default SSID: `LocoTestBench`
|
||||||
|
- Default password: `12345678`
|
||||||
|
- Try restarting ESP32
|
||||||
|
|
||||||
|
### Web Interface Not Loading
|
||||||
|
- Verify correct IP address (check serial monitor)
|
||||||
|
- Try `http://192.168.4.1` in AP mode
|
||||||
|
- Check if LittleFS mounted successfully (serial output)
|
||||||
|
- Ensure filesystem was uploaded (Upload Filesystem Image)
|
||||||
|
- Clear browser cache and reload
|
||||||
|
- Try different browser
|
||||||
|
|
||||||
|
### Bootstrap/CSS Not Loading
|
||||||
|
- Verify Bootstrap files are downloaded to `data/css/` and `data/js/`
|
||||||
|
- Re-run `data/download_bootstrap.sh` script
|
||||||
|
- Upload filesystem image again
|
||||||
|
- Check browser console for 404 errors
|
||||||
|
|
||||||
|
### Motor Not Running (DC Mode)
|
||||||
|
- Check LM18200 connections
|
||||||
|
- Verify power supply is connected
|
||||||
|
- Check pin definitions match your wiring
|
||||||
|
- Use serial monitor to verify commands are received
|
||||||
|
|
||||||
|
### DCC Not Working
|
||||||
|
- Verify DCC pins are correctly connected
|
||||||
|
- DCC requires proper signal conditioning/booster
|
||||||
|
- Check locomotive is DCC-compatible
|
||||||
|
- Verify correct address is programmed in locomotive
|
||||||
|
|
||||||
|
### Upload Failed
|
||||||
|
- Check USB cable connection
|
||||||
|
- Try different USB port
|
||||||
|
- Press BOOT button on ESP32 during upload
|
||||||
|
- Check correct board selected in platformio.ini
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### DCC Protocol Implementation
|
||||||
|
- NMRA DCC Standard compliant
|
||||||
|
- 128-step speed control
|
||||||
|
- Function groups support (F0-F12 currently implemented)
|
||||||
|
- Configurable preamble (14 bits)
|
||||||
|
- Error detection with XOR checksum
|
||||||
|
|
||||||
|
### PWM Specifications (DC Mode)
|
||||||
|
- Frequency: 20 kHz
|
||||||
|
- Resolution: 8-bit (0-255)
|
||||||
|
- Duty cycle: 0-100% (mapped from speed)
|
||||||
|
|
||||||
|
### WiFi Specifications
|
||||||
|
- AP Mode: 802.11 b/g/n
|
||||||
|
- Client Mode: Auto-reconnect enabled
|
||||||
|
- Default reconnect interval: 30 seconds
|
||||||
|
|
||||||
|
## Safety Notes
|
||||||
|
|
||||||
|
⚠️ **Important Safety Information**
|
||||||
|
- Always disconnect power before wiring changes
|
||||||
|
- Use appropriate fuses for your scale
|
||||||
|
- Never exceed voltage ratings of your locomotives
|
||||||
|
- LM18200 requires adequate heat sinking
|
||||||
|
- Test with low voltage before full power
|
||||||
|
- Emergency stop should be easily accessible
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential features for future versions:
|
||||||
|
- [ ] PWM frequency adjustment
|
||||||
|
- [ ] Current monitoring and overload protection
|
||||||
|
- [ ] Multiple locomotive support
|
||||||
|
- [ ] Consist/multi-unit control
|
||||||
|
- [ ] Extended DCC functions (F13-F28)
|
||||||
|
- [ ] MQTT integration
|
||||||
|
- [ ] Locomotive profile storage
|
||||||
|
- [ ] Mobile app
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is provided as-is for educational and hobbyist purposes.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- ESP32 Arduino Core
|
||||||
|
- ESPAsyncWebServer library
|
||||||
|
- Bootstrap CSS framework
|
||||||
|
- ArduinoJson library
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues, questions, or contributions:
|
||||||
|
- Check serial monitor output for debugging
|
||||||
|
- Verify hardware connections
|
||||||
|
- Review pin configurations
|
||||||
|
- Test with known-good locomotive
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version**: 1.0
|
||||||
|
**Last Updated**: November 2025
|
||||||
|
**Compatible Boards**: ESP32 D1 Mini, ESP32 DevKit, other ESP32 variants
|
||||||
|
**Framework**: Arduino for ESP32
|
||||||
107
WIRING.md
Normal file
107
WIRING.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# Wiring Diagram
|
||||||
|
|
||||||
|
## LM18200 H-Bridge Connection
|
||||||
|
|
||||||
|
```
|
||||||
|
ESP32 D1 Mini LM18200 Track/Motor
|
||||||
|
|
||||||
|
GPIO 25 (PWM) ──────────────► PWM
|
||||||
|
GPIO 26 (DIR) ──────────────► DIR
|
||||||
|
GPIO 27 (BRAKE)──────────────► BRAKE
|
||||||
|
|
||||||
|
5V ──────────────► Vcc
|
||||||
|
GND ──────────────► GND
|
||||||
|
|
||||||
|
VS ◄───────── 12-18V Power Supply (+)
|
||||||
|
GND ◄───────── Power Supply GND
|
||||||
|
|
||||||
|
OUT1 ──────────► Track Rail 1
|
||||||
|
OUT2 ──────────► Track Rail 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## DCC Signal Output (Optional Booster Required)
|
||||||
|
|
||||||
|
```
|
||||||
|
ESP32 D1 Mini DCC Booster Track
|
||||||
|
|
||||||
|
GPIO 32 (DCC_A) ────────────► Signal A
|
||||||
|
GPIO 33 (DCC_B) ────────────► Signal B
|
||||||
|
|
||||||
|
Power In ◄──── 12-18V Supply
|
||||||
|
|
||||||
|
Track A ──────────► Rail 1
|
||||||
|
Track B ──────────► Rail 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## WS2812 LED Indicators
|
||||||
|
|
||||||
|
```
|
||||||
|
ESP32 D1 Mini WS2812 LEDs
|
||||||
|
|
||||||
|
GPIO 4 (LED_DATA) ──────────► DIN
|
||||||
|
5V ──────────► VCC
|
||||||
|
GND ──────────► GND
|
||||||
|
|
||||||
|
LED 0: Power Status
|
||||||
|
- Green: Power ON
|
||||||
|
- Red: Power OFF
|
||||||
|
|
||||||
|
LED 1: Mode Indicator
|
||||||
|
- Blue (pulsing): DCC mode
|
||||||
|
- Yellow (pulsing): Analog mode
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete System Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Power Supply │
|
||||||
|
│ 12-18V DC │
|
||||||
|
└────────┬─────────┘
|
||||||
|
│
|
||||||
|
┌────────┴─────────┐
|
||||||
|
│ │
|
||||||
|
┌───────▼────────┐ ┌──────▼──────┐
|
||||||
|
│ LM18200 │ │ 5V Regulator│
|
||||||
|
│ H-Bridge │ │ (if needed) │
|
||||||
|
└───────┬────────┘ └──────┬───────┘
|
||||||
|
│ │
|
||||||
|
│ ┌────────▼────────┐
|
||||||
|
│ │ ESP32 D1 Mini │
|
||||||
|
│ │ │
|
||||||
|
│ │ GPIO 25 → PWM │───┐
|
||||||
|
│ │ GPIO 26 → DIR │───┤
|
||||||
|
│ │ GPIO 27 → BRAKE│───┤
|
||||||
|
│ │ │ │
|
||||||
|
│ │ GPIO 32 → DCC A│ │
|
||||||
|
│ │ GPIO 33 → DCC B│ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ GPIO 4 → LEDS │───┼──► WS2812 LEDs
|
||||||
|
│ │ │ │ (Power & Mode)
|
||||||
|
│ │ WiFi (Built-in)│ │
|
||||||
|
│ └─────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└───────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌───────▼────────┐
|
||||||
|
│ Track/Rails │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ │
|
||||||
|
│ │Locomotive│ │
|
||||||
|
│ └──────────┘ │
|
||||||
|
└────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pin Summary Table
|
||||||
|
|
||||||
|
| Function | ESP32 Pin | Device Pin | Notes |
|
||||||
|
|----------|-----------|------------|-------|
|
||||||
|
| Motor PWM | GPIO 25 | LM18200 PWM | 20kHz PWM signal |
|
||||||
|
| Motor Direction | GPIO 26 | LM18200 DIR | High=Forward, Low=Reverse |
|
||||||
|
| Motor Brake | GPIO 27 | LM18200 BRAKE | Active LOW |
|
||||||
|
| DCC Signal A | GPIO 32 | DCC Booster A | Requires booster circuit |
|
||||||
|
| DCC Signal B | GPIO 33 | DCC Booster B | Inverted signal |
|
||||||
|
| LED Data | GPIO 4 | WS2812 DIN | 2 LEDs for status |
|
||||||
|
| LED Power | 5V | WS2812 VCC | LED strip power |
|
||||||
|
| Power (5V) | 5V | LM18200 Vcc | Logic power |
|
||||||
|
| Ground | GND | LM18200/LEDs GND | Common ground |
|
||||||
110
data/README.md
Normal file
110
data/README.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Web Interface Files Setup
|
||||||
|
|
||||||
|
This directory contains the web interface files that will be uploaded to the ESP32's LittleFS filesystem.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
data/
|
||||||
|
├── index.html # Main HTML page
|
||||||
|
├── css/
|
||||||
|
│ ├── bootstrap.min.css # Bootstrap CSS (local copy)
|
||||||
|
│ └── style.css # Custom styles
|
||||||
|
└── js/
|
||||||
|
├── bootstrap.bundle.min.js # Bootstrap JS (local copy)
|
||||||
|
└── app.js # Application JavaScript
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to Upload Files to ESP32
|
||||||
|
|
||||||
|
### Method 1: Using PlatformIO (Recommended)
|
||||||
|
|
||||||
|
1. **Install LittleFS Uploader**:
|
||||||
|
- In VS Code, open PlatformIO Home
|
||||||
|
- Go to Platforms → Espressif 32
|
||||||
|
- Make sure it's installed/updated
|
||||||
|
|
||||||
|
2. **Download Bootstrap Files** (if not already present):
|
||||||
|
|
||||||
|
Download these files and place them in the appropriate directories:
|
||||||
|
|
||||||
|
- **Bootstrap CSS** (v5.3.0):
|
||||||
|
- URL: https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
|
||||||
|
- Save to: `data/css/bootstrap.min.css`
|
||||||
|
|
||||||
|
- **Bootstrap JS** (v5.3.0):
|
||||||
|
- URL: https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
|
||||||
|
- Save to: `data/js/bootstrap.bundle.min.js`
|
||||||
|
|
||||||
|
3. **Upload Filesystem**:
|
||||||
|
- In VS Code, click PlatformIO icon
|
||||||
|
- Under PROJECT TASKS → env:wemos_d1_mini32
|
||||||
|
- Click "Upload Filesystem Image"
|
||||||
|
- Wait for upload to complete
|
||||||
|
|
||||||
|
### Method 2: Manual Download and Upload
|
||||||
|
|
||||||
|
If you need to download Bootstrap files manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to data directories
|
||||||
|
cd data/css
|
||||||
|
wget https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
|
||||||
|
|
||||||
|
cd ../js
|
||||||
|
wget https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use curl:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd data/css
|
||||||
|
curl -o bootstrap.min.css https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
|
||||||
|
|
||||||
|
cd ../js
|
||||||
|
curl -o bootstrap.bundle.min.js https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verifying Upload
|
||||||
|
|
||||||
|
After uploading, you can verify the files are present by:
|
||||||
|
|
||||||
|
1. Opening Serial Monitor (115200 baud)
|
||||||
|
2. Look for "LittleFS mounted successfully" message
|
||||||
|
3. Access the web interface and check browser console for any 404 errors
|
||||||
|
|
||||||
|
## File Sizes (Approximate)
|
||||||
|
|
||||||
|
- `bootstrap.min.css`: ~190 KB
|
||||||
|
- `bootstrap.bundle.min.js`: ~220 KB
|
||||||
|
- `style.css`: ~1 KB
|
||||||
|
- `app.js`: ~4 KB
|
||||||
|
- `index.html`: ~4 KB
|
||||||
|
|
||||||
|
**Total**: ~420 KB (ESP32 has 1.5MB+ available for LittleFS)
|
||||||
|
|
||||||
|
## Benefits of LittleFS Approach
|
||||||
|
|
||||||
|
✅ **Better Organization**: Separate HTML, CSS, and JS files
|
||||||
|
✅ **Offline Operation**: Works without internet connection
|
||||||
|
✅ **Easier Maintenance**: Edit files without recompiling firmware
|
||||||
|
✅ **Faster Updates**: Only upload filesystem when web files change
|
||||||
|
✅ **Better Performance**: No need to parse embedded strings
|
||||||
|
✅ **Standard Development**: Use familiar web development workflow
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### LittleFS Mount Failed
|
||||||
|
- Ensure filesystem is properly formatted
|
||||||
|
- Try uploading filesystem image again
|
||||||
|
- Check serial output for detailed error messages
|
||||||
|
|
||||||
|
### 404 Errors on Static Files
|
||||||
|
- Verify files are in correct directories
|
||||||
|
- Check file names match exactly (case-sensitive)
|
||||||
|
- Re-upload filesystem image
|
||||||
|
|
||||||
|
### Bootstrap Not Loading
|
||||||
|
- Download Bootstrap files to `data/css/` and `data/js/`
|
||||||
|
- Upload filesystem image
|
||||||
|
- Clear browser cache and reload
|
||||||
6
data/css/bootstrap.min.css
vendored
Normal file
6
data/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
44
data/css/style.css
Normal file
44
data/css/style.css
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
body {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 0 20px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-connected {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-disconnected {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-value {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-btn {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direction-indicator {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
27
data/download_bootstrap.sh
Normal file
27
data/download_bootstrap.sh
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script to download Bootstrap files for offline use
|
||||||
|
|
||||||
|
echo "Downloading Bootstrap 5.3.0 files..."
|
||||||
|
|
||||||
|
# Create directories if they don't exist
|
||||||
|
mkdir -p css
|
||||||
|
mkdir -p js
|
||||||
|
|
||||||
|
# Download Bootstrap CSS
|
||||||
|
echo "Downloading Bootstrap CSS..."
|
||||||
|
curl -L -o css/bootstrap.min.css https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css
|
||||||
|
|
||||||
|
# Download Bootstrap JS Bundle (includes Popper)
|
||||||
|
echo "Downloading Bootstrap JS Bundle..."
|
||||||
|
curl -L -o js/bootstrap.bundle.min.js https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Download complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Files downloaded:"
|
||||||
|
echo " - css/bootstrap.min.css ($(du -h css/bootstrap.min.css | cut -f1))"
|
||||||
|
echo " - js/bootstrap.bundle.min.js ($(du -h js/bootstrap.bundle.min.js | cut -f1))"
|
||||||
|
echo ""
|
||||||
|
echo "Now you can upload the filesystem to your ESP32:"
|
||||||
|
echo " 1. In VS Code, open PlatformIO"
|
||||||
|
echo " 2. Click 'Upload Filesystem Image' under PROJECT TASKS"
|
||||||
124
data/index.html
Normal file
124
data/index.html
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Locomotive Test Bench</title>
|
||||||
|
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-center mb-4">🚂 Locomotive Test Bench</h1>
|
||||||
|
|
||||||
|
<!-- Status Section -->
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Status</h5>
|
||||||
|
<p><span class="status-indicator" id="statusIndicator"></span>
|
||||||
|
<span id="statusText">Connecting...</span></p>
|
||||||
|
<p class="mb-0"><small>IP: <span id="ipAddress">-</span></small></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Control Mode Section -->
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Control Mode</h5>
|
||||||
|
<div class="btn-group w-100" role="group">
|
||||||
|
<input type="radio" class="btn-check" name="mode" id="modeAnalog" value="analog" autocomplete="off">
|
||||||
|
<label class="btn btn-outline-primary" for="modeAnalog">DC Analog</label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="mode" id="modeDCC" value="dcc" autocomplete="off">
|
||||||
|
<label class="btn btn-outline-primary" for="modeDCC">DCC Digital</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DCC Address Section -->
|
||||||
|
<div class="card mb-3" id="dccSection" style="display: none;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">DCC Configuration</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label for="dccAddress" class="form-label">Locomotive Address</label>
|
||||||
|
<input type="number" class="form-control" id="dccAddress" min="1" max="10239" value="3">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label"> </label>
|
||||||
|
<button class="btn btn-primary w-100" onclick="setDCCAddress()">Set</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Speed Control Section -->
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Speed Control
|
||||||
|
<span class="direction-indicator" id="directionIndicator">→</span>
|
||||||
|
</h5>
|
||||||
|
<div class="speed-value" id="speedValue">0%</div>
|
||||||
|
<input type="range" class="form-range" id="speedSlider" min="0" max="100" value="0">
|
||||||
|
<div class="d-flex justify-content-between mt-3">
|
||||||
|
<button class="btn btn-danger" onclick="emergencyStop()">⏹ STOP</button>
|
||||||
|
<button class="btn btn-secondary" onclick="reverseDirection()">🔄 Reverse</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DCC Functions Section -->
|
||||||
|
<div class="card mb-3" id="functionsSection" style="display: none;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">DCC Functions</h5>
|
||||||
|
<div id="functionButtons" class="d-flex flex-wrap">
|
||||||
|
<!-- Function buttons will be generated dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- WiFi Configuration Section -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">WiFi Configuration</h5>
|
||||||
|
<button class="btn btn-info w-100 mb-3" type="button" data-bs-toggle="collapse" data-bs-target="#wifiConfig">
|
||||||
|
Show WiFi Settings
|
||||||
|
</button>
|
||||||
|
<div class="collapse" id="wifiConfig">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">WiFi Mode</label>
|
||||||
|
<select class="form-select" id="wifiMode">
|
||||||
|
<option value="ap">Access Point</option>
|
||||||
|
<option value="client">Client (Connect to Network)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="apSettings">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="apSSID" class="form-label">AP SSID</label>
|
||||||
|
<input type="text" class="form-control" id="apSSID" value="LocoTestBench">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="apPassword" class="form-label">AP Password</label>
|
||||||
|
<input type="password" class="form-control" id="apPassword" value="12345678">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="clientSettings" style="display: none;">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="wifiSSID" class="form-label">Network SSID</label>
|
||||||
|
<input type="text" class="form-control" id="wifiSSID">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="wifiPassword" class="form-label">Network Password</label>
|
||||||
|
<input type="password" class="form-control" id="wifiPassword">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-warning w-100" onclick="saveWiFiSettings()">Save & Restart</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
182
data/js/app.js
Normal file
182
data/js/app.js
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
let currentMode = 'analog';
|
||||||
|
let currentDirection = 1;
|
||||||
|
let dccFunctions = 0;
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
loadStatus();
|
||||||
|
setupEventListeners();
|
||||||
|
generateFunctionButtons();
|
||||||
|
setInterval(loadStatus, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupEventListeners() {
|
||||||
|
document.getElementById('speedSlider').addEventListener('input', function(e) {
|
||||||
|
updateSpeed(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('input[name="mode"]').forEach(radio => {
|
||||||
|
radio.addEventListener('change', function(e) {
|
||||||
|
setMode(e.target.value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('wifiMode').addEventListener('change', function(e) {
|
||||||
|
toggleWiFiSettings(e.target.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateFunctionButtons() {
|
||||||
|
const container = document.getElementById('functionButtons');
|
||||||
|
for (let i = 0; i <= 12; i++) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'btn btn-outline-secondary function-btn';
|
||||||
|
btn.id = 'f' + i;
|
||||||
|
btn.textContent = 'F' + i;
|
||||||
|
btn.onclick = () => toggleFunction(i);
|
||||||
|
container.appendChild(btn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/status');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
document.getElementById('statusIndicator').className = 'status-indicator status-connected';
|
||||||
|
document.getElementById('statusText').textContent = 'Connected';
|
||||||
|
document.getElementById('ipAddress').textContent = data.ip || '-';
|
||||||
|
|
||||||
|
currentMode = data.mode;
|
||||||
|
currentDirection = data.direction;
|
||||||
|
document.getElementById(data.mode === 'dcc' ? 'modeDCC' : 'modeAnalog').checked = true;
|
||||||
|
document.getElementById('speedSlider').value = data.speed;
|
||||||
|
document.getElementById('speedValue').textContent = data.speed + '%';
|
||||||
|
document.getElementById('dccAddress').value = data.dccAddress;
|
||||||
|
|
||||||
|
updateUIForMode(data.mode);
|
||||||
|
updateDirectionIndicator();
|
||||||
|
} catch (error) {
|
||||||
|
document.getElementById('statusIndicator').className = 'status-indicator status-disconnected';
|
||||||
|
document.getElementById('statusText').textContent = 'Disconnected';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUIForMode(mode) {
|
||||||
|
currentMode = mode;
|
||||||
|
document.getElementById('dccSection').style.display = mode === 'dcc' ? 'block' : 'none';
|
||||||
|
document.getElementById('functionsSection').style.display = mode === 'dcc' ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setMode(mode) {
|
||||||
|
try {
|
||||||
|
await fetch('/api/mode', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({mode: mode})
|
||||||
|
});
|
||||||
|
updateUIForMode(mode);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting mode:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSpeed(speed) {
|
||||||
|
document.getElementById('speedValue').textContent = speed + '%';
|
||||||
|
try {
|
||||||
|
await fetch('/api/speed', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({
|
||||||
|
speed: parseInt(speed),
|
||||||
|
direction: currentDirection
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting speed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function emergencyStop() {
|
||||||
|
document.getElementById('speedSlider').value = 0;
|
||||||
|
updateSpeed(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reverseDirection() {
|
||||||
|
currentDirection = currentDirection === 1 ? 0 : 1;
|
||||||
|
updateDirectionIndicator();
|
||||||
|
const speed = document.getElementById('speedSlider').value;
|
||||||
|
updateSpeed(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDirectionIndicator() {
|
||||||
|
document.getElementById('directionIndicator').textContent = currentDirection === 1 ? '→' : '←';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setDCCAddress() {
|
||||||
|
const address = document.getElementById('dccAddress').value;
|
||||||
|
try {
|
||||||
|
await fetch('/api/dcc/address', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({address: parseInt(address)})
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting DCC address:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleFunction(fn) {
|
||||||
|
const btn = document.getElementById('f' + fn);
|
||||||
|
const isActive = btn.classList.contains('btn-secondary');
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
btn.classList.remove('btn-secondary');
|
||||||
|
btn.classList.add('btn-outline-secondary');
|
||||||
|
} else {
|
||||||
|
btn.classList.remove('btn-outline-secondary');
|
||||||
|
btn.classList.add('btn-secondary');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetch('/api/dcc/function', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({
|
||||||
|
function: fn,
|
||||||
|
state: !isActive
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting function:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleWiFiSettings(mode) {
|
||||||
|
document.getElementById('apSettings').style.display = mode === 'ap' ? 'block' : 'none';
|
||||||
|
document.getElementById('clientSettings').style.display = mode === 'client' ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveWiFiSettings() {
|
||||||
|
const mode = document.getElementById('wifiMode').value;
|
||||||
|
const config = {
|
||||||
|
isAPMode: mode === 'ap',
|
||||||
|
apSSID: document.getElementById('apSSID').value,
|
||||||
|
apPassword: document.getElementById('apPassword').value,
|
||||||
|
ssid: document.getElementById('wifiSSID').value,
|
||||||
|
password: document.getElementById('wifiPassword').value
|
||||||
|
};
|
||||||
|
|
||||||
|
if (confirm('Save WiFi settings and restart?')) {
|
||||||
|
try {
|
||||||
|
await fetch('/api/wifi', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(config)
|
||||||
|
});
|
||||||
|
alert('Settings saved. Device will restart...');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving WiFi settings:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
data/js/bootstrap.bundle.min.js
vendored
Normal file
7
data/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
9
doc/.gitignore
vendored
Normal file
9
doc/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Ignore generated documentation
|
||||||
|
html/
|
||||||
|
latex/
|
||||||
|
man/
|
||||||
|
rtf/
|
||||||
|
xml/
|
||||||
|
|
||||||
|
# Keep this README
|
||||||
|
!README.md
|
||||||
143
doc/README.md
Normal file
143
doc/README.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# API Documentation
|
||||||
|
|
||||||
|
This directory contains the auto-generated API documentation for the Locomotive Test Bench project.
|
||||||
|
|
||||||
|
## Generating Documentation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Install Doxygen (and optionally Graphviz for diagrams):
|
||||||
|
|
||||||
|
**Ubuntu/Debian:**
|
||||||
|
```bash
|
||||||
|
sudo apt-get install doxygen graphviz
|
||||||
|
```
|
||||||
|
|
||||||
|
**macOS:**
|
||||||
|
```bash
|
||||||
|
brew install doxygen graphviz
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fedora/RHEL:**
|
||||||
|
```bash
|
||||||
|
sudo dnf install doxygen graphviz
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
Download from [doxygen.nl](https://www.doxygen.nl/download.html)
|
||||||
|
|
||||||
|
### Generate Documentation
|
||||||
|
|
||||||
|
Run the generation script from the project root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./generate_docs.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
doxygen Doxyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Documentation
|
||||||
|
|
||||||
|
Open the generated HTML documentation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux
|
||||||
|
xdg-open doc/html/index.html
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
open doc/html/index.html
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
start doc/html/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
Or navigate to: `doc/html/index.html` in your browser.
|
||||||
|
|
||||||
|
## Documentation Structure
|
||||||
|
|
||||||
|
The generated documentation includes:
|
||||||
|
|
||||||
|
### Main Pages
|
||||||
|
- **Main Page**: Project overview and introduction
|
||||||
|
- **Classes**: All class definitions with member details
|
||||||
|
- **Files**: Source and header file listings
|
||||||
|
- **Namespaces**: Code organization structure
|
||||||
|
|
||||||
|
### For Each Class
|
||||||
|
- **Detailed Description**: Purpose and functionality
|
||||||
|
- **Member Functions**: All public/private methods
|
||||||
|
- **Member Variables**: All data members
|
||||||
|
- **Constructor/Destructor**: Object lifecycle
|
||||||
|
- **Usage Examples**: Where available
|
||||||
|
|
||||||
|
### Key Classes Documented
|
||||||
|
|
||||||
|
1. **Config** - Configuration management and persistent storage
|
||||||
|
2. **WiFiManager** - WiFi connectivity (AP and Client modes)
|
||||||
|
3. **MotorController** - DC motor control via LM18200
|
||||||
|
4. **DCCGenerator** - DCC protocol signal generation
|
||||||
|
5. **LEDIndicator** - WS2812 LED status indicators
|
||||||
|
6. **WebServerManager** - Web interface and REST API
|
||||||
|
|
||||||
|
## Customizing Documentation
|
||||||
|
|
||||||
|
Edit `Doxyfile` in the project root to customize:
|
||||||
|
|
||||||
|
- `PROJECT_NAME` - Project title
|
||||||
|
- `PROJECT_NUMBER` - Version number
|
||||||
|
- `PROJECT_BRIEF` - Short description
|
||||||
|
- `OUTPUT_DIRECTORY` - Where to generate docs
|
||||||
|
- `EXTRACT_PRIVATE` - Include private members
|
||||||
|
- `GENERATE_LATEX` - Generate PDF documentation
|
||||||
|
- `HAVE_DOT` - Enable class diagrams (requires Graphviz)
|
||||||
|
|
||||||
|
## Documentation Format
|
||||||
|
|
||||||
|
The code uses **Doxygen-style comments**:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @brief Short description
|
||||||
|
*
|
||||||
|
* Detailed description can span
|
||||||
|
* multiple lines.
|
||||||
|
*
|
||||||
|
* @param paramName Description of parameter
|
||||||
|
* @return Description of return value
|
||||||
|
* @note Additional notes
|
||||||
|
* @warning Important warnings
|
||||||
|
*/
|
||||||
|
void exampleFunction(int paramName);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Updating Documentation
|
||||||
|
|
||||||
|
When you modify code:
|
||||||
|
|
||||||
|
1. Update Doxygen comments in source files
|
||||||
|
2. Run `./generate_docs.sh` to regenerate
|
||||||
|
3. Review changes in browser
|
||||||
|
4. Commit updated source files (not generated HTML)
|
||||||
|
|
||||||
|
## CI/CD Integration
|
||||||
|
|
||||||
|
To auto-generate docs in CI/CD:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Example GitHub Actions
|
||||||
|
- name: Generate Documentation
|
||||||
|
run: |
|
||||||
|
sudo apt-get install doxygen
|
||||||
|
./generate_docs.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The `doc/` directory is typically added to `.gitignore`
|
||||||
|
- Only source comments are version controlled
|
||||||
|
- Documentation is regenerated as needed
|
||||||
|
- HTML output is ~2-5 MB depending on project size
|
||||||
37
generate_docs.sh
Normal file
37
generate_docs.sh
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script to generate API documentation using Doxygen
|
||||||
|
|
||||||
|
echo "Generating API documentation with Doxygen..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if Doxygen is installed
|
||||||
|
if ! command -v doxygen &> /dev/null
|
||||||
|
then
|
||||||
|
echo "ERROR: Doxygen is not installed!"
|
||||||
|
echo ""
|
||||||
|
echo "To install Doxygen:"
|
||||||
|
echo " Ubuntu/Debian: sudo apt-get install doxygen graphviz"
|
||||||
|
echo " macOS: brew install doxygen graphviz"
|
||||||
|
echo " Fedora: sudo dnf install doxygen graphviz"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run Doxygen
|
||||||
|
doxygen Doxyfile
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Documentation generated successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "Output location: ./doc/html/index.html"
|
||||||
|
echo ""
|
||||||
|
echo "To view the documentation:"
|
||||||
|
echo " Open in browser: file://$(pwd)/doc/html/index.html"
|
||||||
|
echo " Or run: xdg-open doc/html/index.html"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "ERROR: Documentation generation failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
102
include/Config.h
Normal file
102
include/Config.h
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* @file Config.h
|
||||||
|
* @brief Configuration management for the Locomotive Test Bench
|
||||||
|
*
|
||||||
|
* This module handles persistent storage of WiFi and system settings
|
||||||
|
* using ESP32's Preferences library (NVS - Non-Volatile Storage).
|
||||||
|
*
|
||||||
|
* @author Locomotive Test Bench Project
|
||||||
|
* @date 2025
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Preferences.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct WiFiConfig
|
||||||
|
* @brief WiFi configuration parameters
|
||||||
|
*
|
||||||
|
* Stores both Access Point and Client mode settings.
|
||||||
|
*/
|
||||||
|
struct WiFiConfig {
|
||||||
|
String ssid; ///< WiFi network SSID (Client mode)
|
||||||
|
String password; ///< WiFi network password (Client mode)
|
||||||
|
bool isAPMode; ///< True = AP mode, False = Client mode
|
||||||
|
String apSSID; ///< Access Point SSID
|
||||||
|
String apPassword; ///< Access Point password (min 8 characters)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct SystemConfig
|
||||||
|
* @brief System operation configuration
|
||||||
|
*
|
||||||
|
* Stores current control mode and locomotive parameters.
|
||||||
|
*/
|
||||||
|
struct SystemConfig {
|
||||||
|
bool isDCCMode; ///< True = DCC digital, False = DC analog
|
||||||
|
uint16_t dccAddress; ///< DCC locomotive address (1-10239)
|
||||||
|
uint8_t speed; ///< Speed setting (0-100%)
|
||||||
|
uint8_t direction; ///< Direction: 0 = reverse, 1 = forward
|
||||||
|
uint32_t dccFunctions; ///< Bit field for DCC functions F0-F28
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Config
|
||||||
|
* @brief Configuration manager with persistent storage
|
||||||
|
*
|
||||||
|
* Manages all configuration parameters and provides persistent
|
||||||
|
* storage using ESP32's NVS (Non-Volatile Storage) via Preferences.
|
||||||
|
*
|
||||||
|
* @note All settings are automatically saved to flash memory
|
||||||
|
* and persist across reboots.
|
||||||
|
*/
|
||||||
|
class Config {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor - initializes with default values
|
||||||
|
*/
|
||||||
|
Config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize preferences and load saved settings
|
||||||
|
*
|
||||||
|
* Must be called during setup() before using configuration.
|
||||||
|
* Loads previously saved settings from NVS.
|
||||||
|
*/
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save current configuration to NVS
|
||||||
|
*
|
||||||
|
* Writes all WiFi and system settings to persistent storage.
|
||||||
|
* Should be called after any configuration changes.
|
||||||
|
*/
|
||||||
|
void save();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load configuration from NVS
|
||||||
|
*
|
||||||
|
* Reads previously saved settings. Called automatically
|
||||||
|
* by begin(), but can be called manually to reload.
|
||||||
|
*/
|
||||||
|
void load();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset all settings to defaults
|
||||||
|
*
|
||||||
|
* Clears all stored preferences and resets to factory defaults.
|
||||||
|
* Use with caution - all saved settings will be lost.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
WiFiConfig wifi; ///< WiFi configuration settings
|
||||||
|
SystemConfig system; ///< System operation settings
|
||||||
|
|
||||||
|
private:
|
||||||
|
Preferences preferences; ///< ESP32 NVS preferences object
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
158
include/DCCGenerator.h
Normal file
158
include/DCCGenerator.h
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* @file DCCGenerator.h
|
||||||
|
* @brief NMRA DCC (Digital Command Control) signal generator
|
||||||
|
*
|
||||||
|
* Generates DCC protocol signals for controlling digital model locomotives.
|
||||||
|
* Implements NMRA DCC standard with support for:
|
||||||
|
* - Short addresses (1-127) and long addresses (128-10239)
|
||||||
|
* - 128-step speed control
|
||||||
|
* - Function control (F0-F12 implemented, expandable to F28)
|
||||||
|
*
|
||||||
|
* @note Requires external DCC booster circuit for track output
|
||||||
|
* @author Locomotive Test Bench Project
|
||||||
|
* @date 2025
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DCC_GENERATOR_H
|
||||||
|
#define DCC_GENERATOR_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// Pin definitions for DCC output
|
||||||
|
#define DCC_PIN_A 32 ///< DCC Signal A output pin
|
||||||
|
#define DCC_PIN_B 33 ///< DCC Signal B output pin (inverted)
|
||||||
|
|
||||||
|
// DCC timing constants (microseconds) - NMRA standard
|
||||||
|
#define DCC_ONE_BIT_TOTAL_DURATION_MAX 64 ///< Max duration for '1' bit
|
||||||
|
#define DCC_ONE_BIT_TOTAL_DURATION_MIN 55 ///< Min duration for '1' bit
|
||||||
|
#define DCC_ZERO_BIT_TOTAL_DURATION_MAX 10000 ///< Max duration for '0' bit
|
||||||
|
#define DCC_ZERO_BIT_TOTAL_DURATION_MIN 95 ///< Min duration for '0' bit
|
||||||
|
|
||||||
|
#define DCC_ONE_BIT_PULSE_DURATION 58 ///< Half-cycle for '1' bit (58μs)
|
||||||
|
#define DCC_ZERO_BIT_PULSE_DURATION 100 ///< Half-cycle for '0' bit (100μs)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class DCCGenerator
|
||||||
|
* @brief DCC protocol signal generator
|
||||||
|
*
|
||||||
|
* Generates NMRA-compliant DCC signals for digital locomotive control.
|
||||||
|
* Supports variable speed, direction, and function commands.
|
||||||
|
*
|
||||||
|
* @warning Output signals are low-power logic level.
|
||||||
|
* Requires external booster circuit for track connection.
|
||||||
|
*/
|
||||||
|
class DCCGenerator {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
*/
|
||||||
|
DCCGenerator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize DCC generator hardware
|
||||||
|
*
|
||||||
|
* Configures output pins to idle state.
|
||||||
|
*/
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enable DCC signal generation
|
||||||
|
*
|
||||||
|
* Starts sending DCC packets to the track.
|
||||||
|
*/
|
||||||
|
void enable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disable DCC signal generation
|
||||||
|
*
|
||||||
|
* Stops DCC output and sets pins to safe state.
|
||||||
|
*/
|
||||||
|
void disable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set locomotive speed and direction
|
||||||
|
* @param address DCC address (1-10239)
|
||||||
|
* @param speed Speed value (0-100%)
|
||||||
|
* @param direction Direction: 0 = reverse, 1 = forward
|
||||||
|
*/
|
||||||
|
void setLocoSpeed(uint16_t address, uint8_t speed, uint8_t direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Control DCC function
|
||||||
|
* @param address DCC address (1-10239)
|
||||||
|
* @param function Function number (0-28)
|
||||||
|
* @param state true = ON, false = OFF
|
||||||
|
*/
|
||||||
|
void setFunction(uint16_t address, uint8_t function, bool state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update DCC signal generation
|
||||||
|
*
|
||||||
|
* Must be called regularly from main loop to send DCC packets.
|
||||||
|
* Sends speed and function packets at appropriate intervals.
|
||||||
|
*/
|
||||||
|
void update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if DCC is enabled
|
||||||
|
* @return true if DCC mode is active
|
||||||
|
*/
|
||||||
|
bool isEnabled() { return enabled; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool enabled; ///< DCC generator enabled flag
|
||||||
|
uint16_t currentAddress; ///< Current locomotive address
|
||||||
|
uint8_t currentSpeed; ///< Current speed setting
|
||||||
|
uint8_t currentDirection; ///< Current direction (0=rev, 1=fwd)
|
||||||
|
uint32_t functionStates; ///< Function states bit field
|
||||||
|
|
||||||
|
unsigned long lastPacketTime; ///< Timestamp of last packet sent
|
||||||
|
static const unsigned long PACKET_INTERVAL = 30; ///< Packet interval (ms)
|
||||||
|
|
||||||
|
// DCC packet construction and transmission
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a complete DCC packet
|
||||||
|
* @param data Byte array containing packet data
|
||||||
|
* @param length Number of bytes in packet
|
||||||
|
*/
|
||||||
|
void sendPacket(uint8_t* data, uint8_t length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a single DCC bit
|
||||||
|
* @param value true = '1' bit, false = '0' bit
|
||||||
|
*/
|
||||||
|
void sendBit(bool value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send DCC preamble (14 '1' bits)
|
||||||
|
*/
|
||||||
|
void sendPreamble();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a single byte
|
||||||
|
* @param data Byte to send
|
||||||
|
*/
|
||||||
|
void sendByte(uint8_t data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send speed command packet
|
||||||
|
*/
|
||||||
|
void sendSpeedPacket();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send function group packet
|
||||||
|
* @param group Function group number
|
||||||
|
*/
|
||||||
|
void sendFunctionPacket(uint8_t group);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate XOR checksum
|
||||||
|
* @param data Data bytes
|
||||||
|
* @param length Number of bytes
|
||||||
|
* @return XOR checksum byte
|
||||||
|
*/
|
||||||
|
uint8_t calculateChecksum(uint8_t* data, uint8_t length);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
105
include/LEDIndicator.h
Normal file
105
include/LEDIndicator.h
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* @file LEDIndicator.h
|
||||||
|
* @brief WS2812 RGB LED status indicators
|
||||||
|
*
|
||||||
|
* Provides visual feedback using two WS2812 LEDs:
|
||||||
|
* - LED 0: Power status (Green = ON, Red = OFF)
|
||||||
|
* - LED 1: Mode indicator (Blue = DCC, Yellow = Analog)
|
||||||
|
*
|
||||||
|
* @author Locomotive Test Bench Project
|
||||||
|
* @date 2025
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LED_INDICATOR_H
|
||||||
|
#define LED_INDICATOR_H
|
||||||
|
|
||||||
|
#include <Arduino.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)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
*/
|
||||||
|
LEDIndicator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize LED hardware
|
||||||
|
*
|
||||||
|
* Configures FastLED library and sets LEDs to off state.
|
||||||
|
*/
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update LED display
|
||||||
|
*
|
||||||
|
* Must be called regularly from main loop to update
|
||||||
|
* pulsing effects and animations.
|
||||||
|
*/
|
||||||
|
void update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set power status
|
||||||
|
* @param on true = power on (green), false = off (red)
|
||||||
|
*/
|
||||||
|
void setPowerOn(bool on);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set operating mode
|
||||||
|
* @param isDCC true = DCC mode (blue), false = Analog (yellow)
|
||||||
|
*/
|
||||||
|
void setMode(bool isDCC);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set LED brightness
|
||||||
|
* @param brightness Brightness level (0-255)
|
||||||
|
*/
|
||||||
|
void setBrightness(uint8_t brightness);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Play power-on animation sequence
|
||||||
|
*
|
||||||
|
* Shows 3-flash boot sequence on power LED.
|
||||||
|
*/
|
||||||
|
void powerOnSequence();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Play mode change animation
|
||||||
|
*
|
||||||
|
* Smooth fade transition when switching modes.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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
|
||||||
101
include/MotorController.h
Normal file
101
include/MotorController.h
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* @file MotorController.h
|
||||||
|
* @brief DC motor control using LM18200 H-Bridge driver
|
||||||
|
*
|
||||||
|
* Provides bidirectional PWM motor control with brake functionality.
|
||||||
|
* Suitable for DC analog model locomotive control.
|
||||||
|
*
|
||||||
|
* @author Locomotive Test Bench Project
|
||||||
|
* @date 2025
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MOTOR_CONTROLLER_H
|
||||||
|
#define MOTOR_CONTROLLER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// Pin definitions for LM18200
|
||||||
|
// These can be adjusted based on your D1 Mini ESP32 wiring
|
||||||
|
#define MOTOR_PWM_PIN 25 ///< PWM signal output pin
|
||||||
|
#define MOTOR_DIR_PIN 26 ///< Direction control pin
|
||||||
|
#define MOTOR_BRAKE_PIN 27 ///< Brake control pin (active low)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class MotorController
|
||||||
|
* @brief Controls DC motor via LM18200 H-Bridge
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Variable speed control (0-100%)
|
||||||
|
* - Bidirectional operation (forward/reverse)
|
||||||
|
* - Electronic braking
|
||||||
|
* - 20kHz PWM frequency for silent operation
|
||||||
|
* - 8-bit resolution (256 speed steps)
|
||||||
|
*/
|
||||||
|
class MotorController {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
*/
|
||||||
|
MotorController();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize motor controller hardware
|
||||||
|
*
|
||||||
|
* Configures GPIO pins and PWM channels.
|
||||||
|
* Sets motor to safe stopped state.
|
||||||
|
*/
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set motor speed and direction
|
||||||
|
* @param speed Speed value (0-100%)
|
||||||
|
* @param direction Direction: 0 = reverse, 1 = forward
|
||||||
|
*/
|
||||||
|
void setSpeed(uint8_t speed, uint8_t direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop motor (coast to stop)
|
||||||
|
*
|
||||||
|
* Sets speed to zero and releases brake.
|
||||||
|
* Motor will coast to a stop.
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Apply electronic brake
|
||||||
|
*
|
||||||
|
* Activates LM18200 brake function for quick stop.
|
||||||
|
* More aggressive than stop().
|
||||||
|
*/
|
||||||
|
void brake();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update motor controller state
|
||||||
|
*
|
||||||
|
* Called from main loop for safety checks.
|
||||||
|
* Currently placeholder for future features.
|
||||||
|
*/
|
||||||
|
void update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current speed setting
|
||||||
|
* @return Speed (0-100%)
|
||||||
|
*/
|
||||||
|
uint8_t getCurrentSpeed() { return currentSpeed; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current direction
|
||||||
|
* @return Direction: 0 = reverse, 1 = forward
|
||||||
|
*/
|
||||||
|
uint8_t getCurrentDirection() { return currentDirection; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t currentSpeed; ///< Current speed setting (0-100)
|
||||||
|
uint8_t currentDirection; ///< Current direction (0=rev, 1=fwd)
|
||||||
|
|
||||||
|
static const int PWM_CHANNEL = 0; ///< ESP32 PWM channel
|
||||||
|
static const int PWM_FREQUENCY = 20000; ///< PWM frequency in Hz
|
||||||
|
static const int PWM_RESOLUTION = 8; ///< PWM resolution in bits
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
140
include/WebServer.h
Normal file
140
include/WebServer.h
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* @file WebServer.h
|
||||||
|
* @brief Web server and REST API for remote control
|
||||||
|
*
|
||||||
|
* Provides web-based control interface with:
|
||||||
|
* - Responsive Bootstrap-based UI
|
||||||
|
* - RESTful API for control and configuration
|
||||||
|
* - LittleFS-based file serving
|
||||||
|
* - Real-time status updates
|
||||||
|
*
|
||||||
|
* @author Locomotive Test Bench Project
|
||||||
|
* @date 2025
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WEB_SERVER_H
|
||||||
|
#define WEB_SERVER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include "Config.h"
|
||||||
|
#include "MotorController.h"
|
||||||
|
#include "DCCGenerator.h"
|
||||||
|
#include "LEDIndicator.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class WebServerManager
|
||||||
|
* @brief Manages web server and API endpoints
|
||||||
|
*
|
||||||
|
* Serves web interface from LittleFS and provides REST API
|
||||||
|
* for controlling the locomotive test bench remotely.
|
||||||
|
*
|
||||||
|
* API Endpoints:
|
||||||
|
* - GET /api/status - Get current system status
|
||||||
|
* - POST /api/mode - Set control mode (analog/dcc)
|
||||||
|
* - POST /api/speed - Set speed and direction
|
||||||
|
* - POST /api/dcc/address - Set DCC address
|
||||||
|
* - POST /api/dcc/function - Control DCC functions
|
||||||
|
* - POST /api/wifi - Configure WiFi settings
|
||||||
|
*/
|
||||||
|
class WebServerManager {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param cfg Pointer to Config object
|
||||||
|
* @param motor Pointer to MotorController
|
||||||
|
* @param dcc Pointer to DCCGenerator
|
||||||
|
* @param led Pointer to LEDIndicator
|
||||||
|
*/
|
||||||
|
WebServerManager(Config* cfg, MotorController* motor, DCCGenerator* dcc, LEDIndicator* led);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize web server
|
||||||
|
*
|
||||||
|
* Mounts LittleFS, sets up routes, and starts AsyncWebServer.
|
||||||
|
*/
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update web server (currently unused)
|
||||||
|
*
|
||||||
|
* AsyncWebServer handles requests asynchronously.
|
||||||
|
*/
|
||||||
|
void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config; ///< Configuration manager
|
||||||
|
MotorController* motorController; ///< Motor controller instance
|
||||||
|
DCCGenerator* dccGenerator; ///< DCC generator instance
|
||||||
|
LEDIndicator* ledIndicator; ///< LED indicator instance
|
||||||
|
AsyncWebServer server; ///< Async web server (port 80)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set up all HTTP routes and handlers
|
||||||
|
*/
|
||||||
|
void setupRoutes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle root page request
|
||||||
|
* @param request HTTP request object
|
||||||
|
*/
|
||||||
|
void handleRoot(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle status request
|
||||||
|
* @param request HTTP request object
|
||||||
|
*/
|
||||||
|
void handleGetStatus(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle mode change request
|
||||||
|
* @param request HTTP request object
|
||||||
|
*/
|
||||||
|
void handleSetMode(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle speed setting request
|
||||||
|
* @param request HTTP request object
|
||||||
|
*/
|
||||||
|
void handleSetSpeed(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle DCC function request
|
||||||
|
* @param request HTTP request object
|
||||||
|
*/
|
||||||
|
void handleSetFunction(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle config retrieval request
|
||||||
|
* @param request HTTP request object
|
||||||
|
*/
|
||||||
|
void handleGetConfig(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle WiFi configuration request
|
||||||
|
* @param request HTTP request object
|
||||||
|
*/
|
||||||
|
void handleSetWiFi(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle restart request
|
||||||
|
* @param request HTTP request object
|
||||||
|
*/
|
||||||
|
void handleRestart(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get system status as JSON
|
||||||
|
* @return JSON string with status information
|
||||||
|
*/
|
||||||
|
String getStatusJSON();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get configuration as JSON
|
||||||
|
* @return JSON string with configuration
|
||||||
|
*/
|
||||||
|
String getConfigJSON();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
87
include/WiFiManager.h
Normal file
87
include/WiFiManager.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* @file WiFiManager.h
|
||||||
|
* @brief WiFi connection management for AP and Client modes
|
||||||
|
*
|
||||||
|
* Handles WiFi connectivity in both Access Point and Client modes,
|
||||||
|
* with automatic reconnection support.
|
||||||
|
*
|
||||||
|
* @author Locomotive Test Bench Project
|
||||||
|
* @date 2025
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WIFI_MANAGER_H
|
||||||
|
#define WIFI_MANAGER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class WiFiManager
|
||||||
|
* @brief Manages WiFi connectivity and modes
|
||||||
|
*
|
||||||
|
* Provides WiFi functionality in two modes:
|
||||||
|
* - Access Point (AP): Creates standalone network
|
||||||
|
* - Client (STA): Connects to existing WiFi network
|
||||||
|
*
|
||||||
|
* Features automatic reconnection in client mode.
|
||||||
|
*/
|
||||||
|
class WiFiManager {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param cfg Pointer to Config object for WiFi settings
|
||||||
|
*/
|
||||||
|
WiFiManager(Config* cfg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize WiFi based on configuration
|
||||||
|
*
|
||||||
|
* Sets up either AP or Client mode based on config settings.
|
||||||
|
* Called during system startup.
|
||||||
|
*/
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set up Access Point mode
|
||||||
|
*
|
||||||
|
* Creates a standalone WiFi network using configured
|
||||||
|
* SSID and password. Default IP: 192.168.4.1
|
||||||
|
*/
|
||||||
|
void setupAccessPoint();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connect to existing WiFi network
|
||||||
|
*
|
||||||
|
* Attempts to connect as client to configured network.
|
||||||
|
* Falls back to AP mode if connection fails after 10 seconds.
|
||||||
|
*/
|
||||||
|
void connectToWiFi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if WiFi is connected
|
||||||
|
* @return true if connected (or AP mode active), false otherwise
|
||||||
|
*/
|
||||||
|
bool isConnected();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current IP address
|
||||||
|
* @return IP address as string (AP IP or STA IP)
|
||||||
|
*/
|
||||||
|
String getIPAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update WiFi status and handle reconnection
|
||||||
|
*
|
||||||
|
* Should be called regularly from main loop.
|
||||||
|
* Handles automatic reconnection in client mode.
|
||||||
|
*/
|
||||||
|
void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config; ///< Pointer to configuration object
|
||||||
|
unsigned long lastReconnectAttempt; ///< Timestamp of last reconnect attempt
|
||||||
|
static const unsigned long RECONNECT_INTERVAL = 30000; ///< Reconnect interval (30 seconds)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
27
platformio.ini
Normal file
27
platformio.ini
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:wemos_d1_mini32]
|
||||||
|
platform = espressif32
|
||||||
|
board = wemos_d1_mini32
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 115200
|
||||||
|
upload_speed = 921600
|
||||||
|
build_flags =
|
||||||
|
-D ARDUINO_ARCH_ESP32
|
||||||
|
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1
|
||||||
|
-D CONFIG_ASYNC_TCP_USE_WDT=1
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson@^6.21.3
|
||||||
|
esp32async/ESPAsyncWebServer @ ^3.9.2
|
||||||
|
esp32async/AsyncTCP @ ^3.4.9
|
||||||
|
https://github.com/Locoduino/DCCpp
|
||||||
|
fastled/FastLED@^3.6.0
|
||||||
|
board_build.filesystem = littlefs
|
||||||
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