CharGraph-FW/AGENTS.md
2026-02-13 20:24:52 +01:00

7.6 KiB
Raw Blame History

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 output
  • USE_RTC=false — Disable RTC (DS1307) functionality
  • TEST_RGB_ONLY=false — Run only RGB LED test on boot
  • TEST_ALLTHETIME=false — Run word-finding test for all times
  • POWERLOSSDETECT=false — Enable power loss detection
  • CHARGRAPH_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 management
  • include/common.inc — Central include hub; imports all dependencies
  • include/defines.inc — Hardware defines, debug macros, EEPROM address map
  • include/vars.inc — Global variable definitions (also includes html.h at bottom)
  • include/extern.incextern declarations for cross-module globals
  • include/charPattern.inc — Default character pattern (PROGMEM), test words, MINUTE_LEDS
  • include/WordFinder.h — Unified word search (PROGMEM + string, forward/backward)
  • lib/rgbPanel/ — LED matrix control, word finding/highlighting, fade effects
  • lib/CharGraphTimeLogic/ — Time-to-words conversion engine with validation
  • lib/PowerOnDetector/ — Boot reason analysis, power loss detection
  • lib/info/ — Serial connection status display

Include System

main.cpp includes common.inc, which chains: extern.incdefines.incversion.incvars.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 #ifndef guards. 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: camelCaseloadCharsoap(), setRunningFlag(), findWord(), displayTime()
  • Global variables: camelCasebootCounter, ntpEnabled, lastSyncTime, autoBrightnessMin
  • Constants: UPPER_SNAKE_CASENUM_LEDS, MAX_BRIGHTNESS, ADDR_CHARSOAP
  • Macros/defines: UPPER_SNAKE_CASEDEBUG_PRINTLN, RUNNING_FLAG_MAGIC
  • Structs: PascalCaseValidationResult, RuleContext, CharGraphTimeWords, TestStats
  • Struct members: camelCasewordCount, ledHex, fallbackLevel
  • Header guards: _NAME_H_ for legacy files, NAME_H for newer CharGraphTimeLogic files

Types

  • Prefer Arduino/fixed-width types: uint8_t, uint16_t, uint32_t, int8_t, int16_t
  • Use bool for flags
  • Use CRGB for LED colors (FastLED)
  • Use char[] / char* for strings — String class is used sparingly (only otaError)
  • Use const char* with PROGMEM for flash-stored constant strings
  • Use unsigned char when 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 newer CharGraphTimeLogic library.
  • 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 -1 or false on failure, valid values on success
  • getCharGraphWords() returns int8_t: positive = success, 0 or negative = error
  • Pattern validation returns ValidationResult struct with bool valid and const char* reason
  • Debug output via macros: DEBUG_PRINT(x), DEBUG_PRINTLN(x), DEBUG_PRINTF(...) — these compile to no-ops when DEBUG_SOURCE is 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() before FastLED.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() or delay() 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)