# AGENTS.md — Klubhaus Doorbell Multi-target Arduino/ESP32 doorbell alert system using ntfy.sh. Default BOARD: `esp32-s3-lcd-43`. ## Build Commands ```bash # All tasks use just (see justfile). Default BOARD: esp32-s3-lcd-43 # Set target board just BOARD=esp32-32e-4 compile # ESP32-32E 4" (320x480 ST7796) just BOARD=esp32-32e compile # ESP32-32E 3.5" (320x240 ILI9341) just BOARD=esp32-s3-lcd-43 compile # ESP32-S3-Touch-LCD-4.3 (800x480 RGB) just BOARD=uno-r4-wifi compile # Arduino Uno R4 WiFi (12x8 LED matrix) # Core commands just compile # compile for current BOARD just upload # upload (auto-kills monitor first) just monitor # start JSON monitor daemon (background) just kill # kill monitor/release serial port just full # compile + upload + monitor + log-tail (full feedback loop) just watch # tail colored logs # Formatting & cleanup just format # format code with clang-format just clean # remove build artifacts just clean-temp # remove monitor logs/FIFOs/state files # Debugging just cmd dashboard # send command to device just state # show device state just monitor-raw # raw serial monitor (115200 baud) just monitor-tio # show tio command for terminal UI # Install dependencies just install-libs-shared # shared libs (ArduinoJson, NTPClient) just install # shared + board-specific libs # LSP / IDE just gen-compile-commands # generate compile_commands.json just gen-crush-config # generate .crush.json for BOARD ``` **Serial debug commands** (115200 baud): `alert`, `silence`, `dashboard`, `off`, `status`, `reboot` **No unit tests exist** — verify changes by compiling and deploying to hardware. ## Code Style ### Formatting (.clang-format) - BasedOnStyle: WebKit - 4-space indentation, no tabs - Column limit: 100 - Opening brace on same line (`BreakBeforeBraces: Attach`) - Run `just format` to format code ### Header Guards Use `#pragma once` (not `#ifndef` guards). ### Naming Conventions | Type | Convention | Example | |------|------------|---------| | Classes | PascalCase | `DisplayManager`, `IDisplayDriver` | | Constants/enums | SCREAMING_SNAKE | `POLL_INTERVAL_MS`, `ScreenState::DASHBOARD` | | Variables/functions | camelCase | `currentState`, `updateDisplay` | | Member variables | `_` prefix | `_screenWidth`, `_isConnected` | ### Types - Use fixed-width types for protocol/serialization (`uint8_t`, not `byte`) - Use `size_t` for sizes and array indices - Avoid `bool` for pin states — use `uint8_t` or `int` ### Imports Organization (in order) 1. Arduino core (`Arduino.h`) 2. Standard C/C++ (``, ``, ``) 3. Third-party libs (`TFT_eSPI.h`, `ArduinoJson.h`) 4. Local project (`"Config.h"`, `"ScreenState.h"`) ### Error Handling - Serial logging: `Serial.println("[ERROR] message")` - Use `Serial.printf()` for formatted debug - Return error codes, not exceptions - Log state: `[STATE] → DASHBOARD` ## Architecture Patterns ### Display Driver Interface - Pure virtual `IDisplayDriver` in shared `KlubhausCore` - Each board implements concrete driver (`DisplayDriverTFT`, `DisplayDriverGFX`) - `DisplayManager` delegates to `IDisplayDriver` — no display-lib coupling in shared code ### Arduino Patterns - `setup()` — call `begin()` on managers - `loop()` — call `update()` on managers - Use `millis()` for timing (not `delay()`) - Serial baud: 115200 ### Style System - Style constants in board's `board_config.h`: `STYLE_SPACING_X`, `STYLE_COLOR_BG`, etc. - Font abstraction via `IDisplayDriver`: `setTitleFont()`, `setBodyFont()`, etc. - Layout helpers in `KlubhausCore/src/Style.h` ## Key Files ``` libraries/KlubhausCore/src/ ├── KlubhausCore.h # Umbrella include ├── Config.h # Timing, WiFiCred struct ├── ScreenState.h # State enums/structs ├── IDisplayDriver.h # Pure virtual interface ├── DisplayManager.h # Delegates to IDisplayDriver ├── NetManager.* # WiFi, HTTP, NTP └── DoorbellLogic.* # State machine, ntfy polling boards/{BOARD}/ ├── {BOARD}.ino # Main sketch ├── board_config.h # Board-specific config (ESP32 boards) ├── secrets.h # WiFi credentials (optional, use env vars) ├── tft_user_setup.h # TFT_eSPI config (TFT boards) └── DisplayDriver*.{h,cpp} # Concrete IDisplayDriver (ESP32 boards) boards/uno-r4-wifi/ # Uno R4 WiFi (LED matrix display) ├── uno-r4-wifi.ino # Main sketch (standalone) ├── test_messages.sh # Test script to send ntfy messages └── README.md # Quick start guide ``` ## Gotchas 1. **secrets.h**: Boards with `-DLOCAL_SECRETS` use local `secrets.h`; others use `KlubhausCore/src/secrets.h` 2. **Vendored libs**: Each board links only its display lib — never TFT_eSPI + LovyanGFX together 3. **LSP errors**: Run `just gen-compile-commands` then restart LSP; build works regardless 4. **Serial port**: `upload`/`monitor` auto-depend on `kill` to release port 5. **State tags**: Use `[STATE] → DASHBOARD`, `[ADMIN]`, `[TOUCH]`, `[ALERT]` for monitor parsing ## Config Constants (Config.h) | Constant | Default | Description | |----------|---------|-------------| | `FW_VERSION` | "5.1" | Firmware version | | `POLL_INTERVAL_MS` | 15000 | ntfy.sh poll interval | | `ALERT_TIMEOUT_MS` | 120000 | Auto-clear alert | | `INACTIVITY_TIMEOUT_MS` | 30000 | Display off timeout | | `HOLD_TO_SILENCE_MS` | 3000 | Hold to silence | | `WIFI_CONNECT_TIMEOUT_MS` | 15000 | WiFi timeout | | `HTTP_TIMEOUT_MS` | 10000 | HTTP request timeout | ## Screen States - **BOOT** — Initializing - **DASHBOARD** — Normal operation - **ALERT** — Doorbell ring detected - **OFF** — Display backlight off (polling continues)