7.6 KiB
AGENTS.md
Project Overview
ESP8266 PlatformIO firmware for a German word clock (Wortuhr). An 11×11 WS2812B LED matrix (121 LEDs) displays time as illuminated German words. WiFi-controllable with AP mode for configuration and Station mode for NTP time sync.
Build Commands
# Build (default environment: esp8266clone)
pio run
# Build for specific environment
pio run -e esp8266clone
pio run -e d1_mini
# Build and upload
pio run --target upload
# Serial monitor (115200 baud)
pio device monitor
# Clean build
pio run --target clean
# Update libraries
pio lib update
Pre-build Script
html_to_header.py runs automatically before each build. It converts html/index.html into gzip-compressed C header include/html.h. Do not edit html.h directly — edit html/index.html instead.
Testing
There is no unit test framework. Testing is done on-device:
# Run black box test (upload to device, output on serial)
# Edit lib/CharGraphTimeLogic/blackboxtest/config.h to configure:
# - testpatterns[]: 110-char grid patterns
# - hourStart/hourEnd, minuteStart/minuteEnd, hops
# Then upload the blackboxtest.ino sketch
# Enable word test mode via build flag in platformio.ini:
# -DTEST_ALLTHETIME=true
Debug Flags (platformio.ini build_flags)
DEBUG_SOURCE=true— Enable serial debug outputUSE_RTC=false— Disable RTC (DS1307) functionalityTEST_RGB_ONLY=false— Run only RGB LED test on bootTEST_ALLTHETIME=false— Run word-finding test for all timesPOWERLOSSDETECT=false— Enable power loss detectionCHARGRAPH_DEBUG— Enable CharGraphTimeLogic debug output
Architecture
File Organization
src/main.cpp— All application logic (~2660 lines): setup/loop, web handlers, EEPROM, NTP, OTA, WiFi, charsoap managementinclude/common.inc— Central include hub; imports all dependenciesinclude/defines.inc— Hardware defines, debug macros, EEPROM address mapinclude/vars.inc— Global variable definitions (also includeshtml.hat bottom)include/extern.inc—externdeclarations for cross-module globalsinclude/charPattern.inc— Default character pattern (PROGMEM), test words, MINUTE_LEDSinclude/WordFinder.h— Unified word search (PROGMEM + string, forward/backward)lib/rgbPanel/— LED matrix control, word finding/highlighting, fade effectslib/CharGraphTimeLogic/— Time-to-words conversion engine with validationlib/PowerOnDetector/— Boot reason analysis, power loss detectionlib/info/— Serial connection status display
Include System
main.cpp includes common.inc, which chains: extern.inc → defines.inc → version.inc → vars.inc → library headers. vars.inc includes html.h at bottom. charPattern.inc is included only by rgbPanel.cpp.
EEPROM Layout
512 bytes total. All addresses are #defined in defines.inc with ADDR_ prefix. Magic bytes (e.g., 0xAA, 0xBB) are used as "configured" flags. Always call EEPROM.commit() after writes.
Code Style Guidelines
Indentation and Formatting
- Indentation: Mixed — mostly 4 spaces; some files use 2-space indent inside
#ifndefguards. Use 4 spaces for new code. - Brace style: Allman style (opening brace on its own line) for function definitions; K&R style (opening brace on same line) for
if/for/while/switch. Both appear in the codebase — follow the style of the surrounding code. - Line length: No strict limit; keep reasonable (~120 chars).
- Section headers: Use decorated comment blocks:
// ════════════════════════════════════════════════════════════════ // SECTION NAME // ════════════════════════════════════════════════════════════════
Naming Conventions
- Functions:
camelCase—loadCharsoap(),setRunningFlag(),findWord(),displayTime() - Global variables:
camelCase—bootCounter,ntpEnabled,lastSyncTime,autoBrightnessMin - Constants:
UPPER_SNAKE_CASE—NUM_LEDS,MAX_BRIGHTNESS,ADDR_CHARSOAP - Macros/defines:
UPPER_SNAKE_CASE—DEBUG_PRINTLN,RUNNING_FLAG_MAGIC - Structs:
PascalCase—ValidationResult,RuleContext,CharGraphTimeWords,TestStats - Struct members:
camelCase—wordCount,ledHex,fallbackLevel - Header guards:
_NAME_H_for legacy files,NAME_Hfor newer CharGraphTimeLogic files
Types
- Prefer Arduino/fixed-width types:
uint8_t,uint16_t,uint32_t,int8_t,int16_t - Use
boolfor flags - Use
CRGBfor LED colors (FastLED) - Use
char[]/char*for strings —Stringclass is used sparingly (onlyotaError) - Use
const char*withPROGMEMfor flash-stored constant strings - Use
unsigned charwhen doing byte-level operations (UTF-8 handling)
Comments
- Comments are a mix of German and English. German is predominant in older code (
main.cpp,rgbPanel,PowerOnDetector). English is used in newerCharGraphTimeLogiclibrary. - Use
//for inline comments;/** ... */Javadoc-style for function documentation in CharGraphTimeLogic - Commented-out code is common (debug prints, alternative patterns) — leave existing commented code in place
Error Handling
- Functions return
-1orfalseon failure, valid values on success getCharGraphWords()returnsint8_t: positive = success, 0 or negative = error- Pattern validation returns
ValidationResultstruct withbool validandconst char* reason - Debug output via macros:
DEBUG_PRINT(x),DEBUG_PRINTLN(x),DEBUG_PRINTF(...)— these compile to no-ops whenDEBUG_SOURCEis false - Use
yield()in long loops to prevent watchdog resets - Always check
EEPROM.commit()after EEPROM writes
PROGMEM Usage
- Store constant strings and large data in flash with
PROGMEM - Read PROGMEM strings with
pgm_read_byte(),strcpy_P(),pgm_read_ptr() - Pattern strings are 110 characters (11 cols × 10 rows, row 11 is minute LEDs)
Include Patterns
Library headers include their own dependencies:
#include <Arduino.h> // Always first
#include <FastLED.h> // Hardware libraries
#include <EEPROM.h>
#include <defines.inc> // Project includes use angle brackets
#include <extern.inc>
#include "Types.h" // Intra-library includes use quotes
Web Server Handlers
Handlers in main.cpp follow this pattern:
server.on("/endpoint", []() {
// Parse server.arg("paramName")
// Process request
// EEPROM.write(...) + EEPROM.commit() for persistence
server.send(200, "application/json", "{\"status\":\"ok\"}");
});
Hardware Constraints
- ESP8266 has ~40KB usable RAM — avoid dynamic allocation, prefer stack buffers
- LED updates require interrupt protection: call
noInterrupts()beforeFastLED.show(),interrupts()after - LED index 2 is always off (photoresistor position)
- Zigzag LED mapping: even rows left-to-right, odd rows right-to-left
- Call
yield()ordelay()in loops to feed the watchdog and allow WiFi processing
Key Domain Knowledge
- "charsoap" = the 110-character letter grid that defines which words can be displayed
- Umlauts Ä→a, Ö→o, Ü→u in charsoap (stored as lowercase:
FuNF= FÜNF,ZWoLF= ZWÖLF) - Words list format: dash-separated string
"ES-IST-HALB-UHR-..." - Time display advances hour at minute >= 20 (German convention: "zehn vor drei" at 2:50)