Architecture & Data Flow

Technical documentation for developers and contributors.

Dual-Core Architecture

SpojBoard uses both cores of the ESP32-S3 with FreeRTOS tasks for optimal performance:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ CORE 0 (WiFi Network Stack)                             โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ WiFi interrupt handlers (sub-ms response)               โ”‚
โ”‚ LwIP TCP/IP stack                                       โ”‚
โ”‚ NO application tasks                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ CORE 1 (Application Tasks)                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ displayRenderTask()  [Priority 2]                       โ”‚
โ”‚   Waits for notification, copies data via mutex,        โ”‚
โ”‚   renders to HUB75 (~100ms)                             โ”‚
โ”‚                                                         โ”‚
โ”‚ apiFetchTask()       [Priority 1]                       โ”‚
โ”‚   Handles blocking HTTP calls (200-2000ms)              โ”‚
โ”‚   Updates departures via mutex, sleeps 100ms            โ”‚
โ”‚                                                         โ”‚
โ”‚ Arduino loop()       [Priority 1]                       โ”‚
โ”‚   Web server, ETA recalculation, state management       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Thread Safety

Two mutexes protect shared data with short lock durations (~1ms):

  • displayMutex โ€” Protects display update requests (display task โ†” loop)
  • apiDataMutex โ€” Protects departures array and weather data (API task โ†” loop)

Data is copied under mutex, then processed without locks. Rendering and HTTP calls never hold a mutex.

Configuration Constants

ConstantValuePurpose
MAX_DEPARTURES12Cache size (hardcoded)
MAX_TEMP_DEPARTURES144Collection buffer (12 stops ร— 12 departures)
config.numDepartures1-3Display rows (user setting)
Important: numDepartures only controls how many rows to show on the LED matrix (1-3), not API fetch size. APIs always fetch 12 departures per stop for better caching and sorting.

Complete Data Pipeline

1. User Configuration

City selection, stop IDs, display rows (1-3)

2. API Queries

Fetch 12 departures per stop โ†’ temp buffer (max 144)

1-second delay between stops for rate limiting

3. Sort by ETA

All departures sorted by time across all stops

4. Copy to Cache

Top 12 soonest departures stored with timestamps

5. ETA Recalculation

Every 10 seconds: recalculate ETAs, filter stale entries

6. Display Rendering

Show configured rows (1-3) on LED matrix

State Machine

The device operates in multiple modes with priority-based evaluation:

Operating Modes

AP Mode

Creates WiFi network for setup. Display shows credentials. API calls disabled.

STA Mode

Connects to configured WiFi. Fetches departures, recalculates ETAs, serves web dashboard.

Demo Mode

Pauses API polling. Shows user-configurable sample departures. Available in both AP and STA modes.

Rest Mode

Display cleared, brightness 0. API polling continues. Triggered manually or by schedule.

Display Priority (highest to lowest)

  1. Demo mode โ€” custom sample departures
  2. Rest mode โ€” display off
  3. AP mode โ€” WiFi setup credentials
  4. WiFi connecting โ€” connection status
  5. Setup required โ€” web UI address
  6. API error โ€” error message
  7. No departures โ€” info message
  8. Normal operation โ€” real departures

Memory Allocation

StructureSizeLocation
Temp buffer (144 departures)~7KBStatic in API functions
Cache (12 departures)~600 bytesGlobal in main.cpp
JSON buffer (Golemio)8KBHeap during API call
JSON buffer (BVG)24KBHeap during API call
Configuration~1KBNVS flash (persistent)

Typical: ~200KB free heap, 21.4% RAM used (70KB of 327KB), 94.7% flash used (1.24MB of 1.31MB)

Module Architecture

Layered design with zero circular dependencies:

Layer 6: Application
  main.cpp (orchestrates all modules, runtime API selection)

Layer 5: Business Logic
  TransitAPI (abstract), GolemioAPI, BvgAPI, MqttAPI,
  GitHubOTA, WeatherAPI

Layer 4: Network Services
  WiFiManager, CaptivePortal, ConfigWebServer, OTAUpdateManager

Layer 3: Hardware Abstraction
  DisplayController, DisplayManager, DisplayColors,
  TimeUtils, RestMode

Layer 2: Data Layer
  AppConfig, DepartureData

Layer 1: Foundation
  Logger, UTF-8 utilities (gfxlatin2, decodeutf8)

Key Patterns

  • Zero Circular Dependencies: Lower layers never depend on higher layers
  • Callback Pattern: Modules communicate upward via callbacks
  • Pure Data Structures: Config passed as parameter, not stored in modules
  • Static Allocation: No dynamic allocation in main loop for stability

Multi-Stop Behavior

When multiple stop IDs are configured (comma-separated, max 12 stops):

  1. Query each stop individually (12 departures per stop)
  2. Apply 1-second delay between API calls (rate limiting)
  3. Collect in temp buffer (capacity: 144)
  4. Sort by ETA across all stops
  5. Cache top 12 soonest departures
  6. Display configured rows on LED matrix

This ensures you always see the soonest departures across all stops.

Performance

OperationTiming
Single stop API call~1-2 seconds
12 stops full query~12-24 seconds
ETA recalculation<1ms
Display render~10-20ms

Debugging

When config.debugMode = true:

  • Telnet server on port 23 โ€” mirrors all debug logs over WiFi
  • Memory logging at key checkpoints (api_start, api_complete, display_update)
  • API response logging with timestamps

Serial output always available at 115200 baud for boot sequence, WiFi status, and errors.