diff --git a/AGENTS.md b/AGENTS.md index 5fe5097..e3f7997 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,35 +5,40 @@ Multi-target Arduino/ESP32 doorbell alert system using ntfy.sh. Default BOARD: ` ## Build Commands ```bash +# All tasks use just (see justfile). Default BOARD: esp32-s3-lcd-43 + # Set target board -mise set BOARD=esp32-32e-4 # ESP32-32E 4" (320x480 ST7796) -mise set BOARD=esp32-32e # ESP32-32E 3.5" (320x240 ILI9341) -mise set BOARD=esp32-s3-lcd-43 # ESP32-S3-Touch-LCD-4.3 (800x480 RGB) +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 -mise run compile # compile for current BOARD -mise run upload # upload (auto-kills monitor first) -mise run monitor # start JSON monitor daemon -mise run kill # kill monitor/release serial port +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 -mise run format # format code with clang-format -mise run clean # remove build artifacts +just format # format code with clang-format +just clean # remove build artifacts +just clean-temp # remove monitor logs/FIFOs/state files # Debugging -mise run log-tail # tail colored logs -mise run cmd COMMAND=dashboard # send command to device -mise run state # show device state -mise run monitor-raw # raw serial monitor (115200 baud) -mise run monitor-tio # show tio command for terminal UI +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 -mise run install-libs-shared # shared libs (ArduinoJson, NTPClient) -mise run install # shared + board-specific libs +just install-libs-shared # shared libs (ArduinoJson, NTPClient) +just install # shared + board-specific libs # LSP / IDE -mise run gen-compile-commands # generate compile_commands.json -mise run gen-crush-config # generate .crush.json for BOARD +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` @@ -47,7 +52,7 @@ mise run gen-crush-config # generate .crush.json for BOARD - 4-space indentation, no tabs - Column limit: 100 - Opening brace on same line (`BreakBeforeBraces: Attach`) -- Run `mise run format` to format code +- Run `just format` to format code ### Header Guards Use `#pragma once` (not `#ifndef` guards). @@ -109,17 +114,22 @@ libraries/KlubhausCore/src/ boards/{BOARD}/ ├── {BOARD}.ino # Main sketch -├── board_config.h # Board-specific config -├── secrets.h # WiFi credentials +├── 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 +└── 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 `mise run gen-compile-commands` then restart LSP; build works regardless +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 diff --git a/justfile b/justfile index a06e807..07c6ac9 100644 --- a/justfile +++ b/justfile @@ -6,18 +6,28 @@ default: @just --list -# Set default BOARD -BOARD := "esp32-32e-4" +# Default BOARD — overridable via env var or CLI: BOARD=uno-r4-wifi just compile +BOARD := env_var_or_default('BOARD', 'esp32-s3-lcd-43') # Helper to source board config # just passes args as positional, so we source in each recipe +# Build token header for -include (generated fresh each compile) +BUILD_TOKEN_HEADER := "/tmp/doorbell-build-token.h" + # Compile firmware compile: #!/usr/bin/env bash set -e source ./boards/{{BOARD}}/board-config.sh + BUILD_TOKEN=$(git describe --always --dirty 2>/dev/null || echo "unknown") + echo "#pragma once" > /tmp/doorbell-build-token.h + echo "#define BUILD_TOKEN \"$BUILD_TOKEN\"" >> /tmp/doorbell-build-token.h + echo "Build token: $BUILD_TOKEN" + + CFLAGS="$OPTS -include /tmp/doorbell-build-token.h" + # Only regenerate compile_commands.json if needed (board changed or first run) NEED_GEN=false if [ ! -f compile_commands.json ]; then @@ -28,7 +38,7 @@ compile: if [ "$NEED_GEN" = "true" ]; then rm -rf /tmp/arduino-build - arduino-cli compile --only-compilation-database --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$OPTS" --build-path /tmp/arduino-build ./boards/{{BOARD}} + arduino-cli compile --only-compilation-database --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$CFLAGS" --build-path /tmp/arduino-build ./boards/{{BOARD}} cp /tmp/arduino-build/compile_commands.json . echo "{{BOARD}}" > .board-last echo "[OK] Generated compile_commands.json for {{BOARD}}" @@ -36,7 +46,7 @@ compile: echo "[SKIP] compile_commands.json already up to date for {{BOARD}}" fi - arduino-cli compile --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$OPTS" --warnings default ./boards/{{BOARD}} + arduino-cli compile --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$CFLAGS" --warnings default ./boards/{{BOARD}} # Upload firmware upload: kill @@ -77,10 +87,16 @@ monitor-tio: kill TARGET="$(readlink -f "$PORT" 2>/dev/null || echo "$PORT")" echo "Run: tio --map INLCRNL $TARGET -e" -# Monitor with JSON logging +# Monitor with JSON logging (runs in background) monitor: kill #!/usr/bin/env bash - python3 ./scripts/monitor-agent.py "{{BOARD}}" & + nohup python3 ./scripts/monitor-agent.py "{{BOARD}}" > /dev/null 2>&1 & + PID=$! + echo "Monitor agent started for {{BOARD}} (PID: $PID)" + echo " Log: /tmp/doorbell-{{BOARD}}.jsonl" + echo " State: /tmp/doorbell-{{BOARD}}-state.json" + echo " Cmd: echo 'dashboard' > /tmp/doorbell-{{BOARD}}-cmd.fifo" + echo " Stop: kill $PID" # Tail colored logs watch: @@ -124,26 +140,31 @@ install-libs-shared: # Install all libraries install: install-libs-shared #!/usr/bin/env bash - ./boards/{{BOARD}}/install.sh - arduino-cli core install esp32:esp32 + if [ -f ./boards/{{BOARD}}/install.sh ]; then + ./boards/{{BOARD}}/install.sh + arduino-cli core install esp32:esp32 + else + echo "[SKIP] No board-specific install script for {{BOARD}}" + fi + +# Full build-compile-monitor loop +full: compile upload monitor + @just watch # Clean build artifacts clean: #!/usr/bin/env bash rm -rf vendor/ rm -rf .cache/ - rm -rf boards/esp32-32e/build - rm -rf boards/esp32-32e-4/build - rm -rf boards/esp32-s3-lcd-43/build + rm -rf boards/*/build/ rm -f .board-last + rm -f /tmp/doorbell-build-token.h echo "[OK] Build artifacts cleaned" # Clean temporary files (monitor logs, FIFOs, state files) clean-temp: #!/usr/bin/env bash - rm -f /tmp/doorbell-esp32-32e.jsonl /tmp/doorbell-esp32-32e-state.json /tmp/doorbell-esp32-32e-cmd.fifo - rm -f /tmp/doorbell-esp32-32e-4.jsonl /tmp/doorbell-esp32-32e-4-state.json /tmp/doorbell-esp32-32e-4-cmd.fifo - rm -f /tmp/doorbell-esp32-s3-lcd-43.jsonl /tmp/doorbell-esp32-s3-lcd-43-state.json /tmp/doorbell-esp32-s3-lcd-43-cmd.fifo + rm -f /tmp/doorbell-*.jsonl /tmp/doorbell-*-state.json /tmp/doorbell-*-cmd.fifo rm -f .board-last echo "[OK] Temp files cleaned" @@ -176,10 +197,29 @@ format: boards/esp32-s3-lcd-43/*.cpp \ boards/esp32-s3-lcd-43/*.h \ boards/esp32-s3-lcd-43/*.ino \ + boards/uno-r4-wifi/*.cpp \ + boards/uno-r4-wifi/*.h \ + boards/uno-r4-wifi/*.ino \ libraries/KlubhausCore/src/*.cpp \ libraries/KlubhausCore/src/*.h \ libraries/KlubhausCore/*.properties +# Generate compile_commands.json for LSP (no full compile) +gen-compile-commands: + #!/usr/bin/env bash + set -e + source ./boards/{{BOARD}}/board-config.sh + BUILD_TOKEN=$(git describe --always --dirty 2>/dev/null || echo "unknown") + echo "#pragma once" > /tmp/doorbell-build-token.h + echo "#define BUILD_TOKEN \"$BUILD_TOKEN\"" >> /tmp/doorbell-build-token.h + CFLAGS="$OPTS -include /tmp/doorbell-build-token.h" + rm -rf /tmp/arduino-build + arduino-cli compile --only-compilation-database --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$CFLAGS" --build-path /tmp/arduino-build ./boards/{{BOARD}} + cp /tmp/arduino-build/compile_commands.json . + echo "{{BOARD}}" > .board-last + rm -f /tmp/doorbell-build-token.h + echo "[OK] Generated compile_commands.json for {{BOARD}}" + # Generate crush config gen-crush-config: #!/usr/bin/env bash diff --git a/libraries/KlubhausCore/src/Config.h b/libraries/KlubhausCore/src/Config.h index f0a470e..c43fe41 100644 --- a/libraries/KlubhausCore/src/Config.h +++ b/libraries/KlubhausCore/src/Config.h @@ -1,6 +1,10 @@ #pragma once -#define FW_VERSION "5.1" +#define FW_VERSION "6.0" + +#ifndef BUILD_TOKEN +#define BUILD_TOKEN "dev" +#endif // ── ntfy.sh ── #define NTFY_SERVER "ntfy.sh" diff --git a/libraries/KlubhausCore/src/DoorbellLogic.cpp b/libraries/KlubhausCore/src/DoorbellLogic.cpp index cad88b4..099da4a 100644 --- a/libraries/KlubhausCore/src/DoorbellLogic.cpp +++ b/libraries/KlubhausCore/src/DoorbellLogic.cpp @@ -21,7 +21,7 @@ void DoorbellLogic::begin( #endif Serial.println(F("========================================")); - Serial.printf(" KLUBHAUS ALERT v%s — %s\n", _version, _board); + Serial.printf(" KLUBHAUS ALERT v%s (%s) — %s\n", _version, BUILD_TOKEN, _board); if(_debug) Serial.println(F(" *** DEBUG MODE — _test topics ***")); Serial.println(F("========================================\n")); diff --git a/mise.toml b/mise.toml index 46a2304..1be7f57 100644 --- a/mise.toml +++ b/mise.toml @@ -1,222 +1,14 @@ # ═══════════════════════════════════════════════════════════ -# Klubhaus Doorbell — Multi-Target Build Harness +# Klubhaus Doorbell — Tool Versions +# +# Tasks are managed by just (see justfile), not mise. +# Run `just --list` for available commands. # ═══════════════════════════════════════════════════════════ -# Required tools [tools] hk = "latest" +just = "latest" pkl = "latest" -# screen = "latest" # Install via system package manager (brew install screen / apt install screen) -# zellij = "latest" # Install manually if needed (brew install zellij) - -# Usage: -# BOARD=esp32-32e-4 mise run compile # compile -# BOARD=esp32-32e-4 mise run upload # upload -# BOARD=esp32-32e-4 mise run monitor # monitor -# BOARD=esp32-32e-4 mise run monitor-screen # shows tio command to run manually -# -# Valid BOARD: esp32-32e, esp32-32e-4, esp32-s3-lcd-43 -# Board config lives in: boards/{BOARD}/board-config.sh - -[tasks.compile] -description = "Compile (uses BOARD env var)" -depends = ["gen-compile-commands"] -run = ''' -source ./boards/$BOARD/board-config.sh -arduino-cli compile --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$OPTS" --warnings default ./boards/$BOARD -''' - -[tasks.upload] -description = "Upload (uses BOARD env var)" -depends = ["kill"] -run = ''' -source ./boards/$BOARD/board-config.sh -arduino-cli upload --fqbn "$FQBN" --port "$PORT" ./boards/$BOARD -''' - -[tasks.monitor-raw] -depends = ["kill"] -run = """ -source ./boards/$BOARD/board-config.sh - -PORT="${PORT:-$PORT}" -TARGET="$(readlink -f "$PORT" 2>/dev/null || echo "$PORT")" -arduino-cli monitor -p "$TARGET" --config baudrate=115200 -""" - -[tasks.monitor-tio] -description = "Show tio command to run in separate terminal" -depends = ["kill"] -run = """ -source ./boards/$BOARD/board-config.sh - -PORT="${PORT:-$PORT}" -TARGET="$(readlink -f "$PORT" 2>/dev/null || echo "$PORT")" -tio --map INLCRNL "$TARGET" -e -""" - -[tasks.kill] -description = "Kill any process using the serial port for BOARD" -run = ''' -set +e - -# Load board config -source ./boards/$BOARD/board-config.sh -PORT="${PORT:-$PORT}" - -# Kill any processes using the serial port -echo "Killing processes on $PORT..." -fuser -k "$PORT" 2>/dev/null || true - -# Kill monitor-agent Python processes for this board -for pid in $(pgrep -f "monitor-agent.py.*$BOARD" 2>/dev/null || true); do - echo "Killing monitor-agent.py (PID: $pid)..." - kill "$pid" 2>/dev/null || true -done - -# Clean up any stale lockfiles (legacy) -rm -f "/tmp/doorbell-${BOARD}.lock" 2>/dev/null || true - -sleep 1 -echo "[OK] Killed processes for $BOARD" -exit 0 -''' - -[tasks.monitor] -description = "Monitor agent with JSON log + command pipe (Python-based)" -depends = ["kill"] -run = """ -python3 ./scripts/monitor-agent.py "$BOARD" & -""" - -[tasks.log-tail] -description = "Tail the logs with human-readable formatting" -run = ''' -tail -f "/tmp/doorbell-$BOARD.jsonl" | while read -r line; do - ts=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['ts'])" 2>/dev/null) - txt=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['line'])" 2>/dev/null) - if [[ "$txt" == *"[STATE]"* ]]; then - echo -e "\\033[1;35m[$ts]\\033[0m $txt" - elif [[ "$txt" == *"[ADMIN]"* ]]; then - echo -e "\\033[1;36m[$ts]\\033[0m $txt" - elif [[ "$txt" == *"[TOUCH]"* ]]; then - echo -e "\\033[1;33m[$ts]\\033[0m $txt" - elif [[ "$txt" == *"ALERT"* ]]; then - echo -e "\\033[1;31m[$ts]\\033[0m $txt" - else - echo "[$ts] $txt" - fi -done -''' - -[tasks.cmd] -description = "Send command to device (COMMAND=dashboard mise run cmd)" -run = """ -echo -n "$COMMAND" > "/tmp/doorbell-$BOARD-cmd.fifo" -echo "[SENT] $COMMAND" -""" - -[tasks.state] -description = "Show current device state" -run = """ -cat "/tmp/doorbell-$BOARD-state.json" -""" - -[tasks.install-libs-shared] -description = "Install shared Arduino libraries" -run = """ -./scripts/install-shared.sh -""" - -[tasks.install] -description = "Install libraries for BOARD (shared + board-specific)" -run = """ -./scripts/install-shared.sh -./boards/$BOARD/install.sh -""" - -[tasks.detect] -description = "Detect connected doorbell board" -run = "bash ./scripts/detect-device.sh" - -# Convenience - -[tasks.clean] -description = "Remove build artifacts" -run = """ -rm -rf vendor/ -rm -rf boards/esp32-32e/build -rm -rf boards/esp32-32e-4/build -rm -rf boards/esp32-s3-lcd-43/build -echo "[OK] Build artifacts cleaned" -""" - -[tasks.arduino-clean] -description = "Clean Arduino CLI cache (staging + packages)" -run = """ -echo "Checking ~/.arduino15..." -du -sh ~/.arduino15/staging 2>/dev/null || echo "No staging folder" -du -sh ~/.arduino15/packages 2>/dev/null || echo "No packages folder" -read -p "Delete staging + packages folders? [y/N] " -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]]; then - rm -rf ~/.arduino15/staging - rm -rf ~/.arduino15/packages - echo "[OK] Arduino staging + packages cleared" -else - echo "Aborted" -fi -""" - -[tasks.format] -run = """ -clang-format -i --style=file \ - boards/esp32-32e/*.cpp \ - boards/esp32-32e/*.h \ - boards/esp32-32e/*.ino \ - boards/esp32-32e-4/*.cpp \ - boards/esp32-32e-4/*.h \ - boards/esp32-32e-4/*.ino \ - boards/esp32-s3-lcd-43/*.cpp \ - boards/esp32-s3-lcd-43/*.h \ - boards/esp32-s3-lcd-43/*.ino \ - libraries/KlubhausCore/src/*.cpp \ - libraries/KlubhausCore/src/*.h \ - libraries/KlubhausCore/*.properties -""" - -[tasks.gen-compile-commands] -description = "Generate compile_commands.json for LSP (uses BOARD env var)" -run = """ -rm -rf /tmp/arduino-build -source ./boards/$BOARD/board-config.sh -arduino-cli compile --only-compilation-database --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$OPTS" --build-path /tmp/arduino-build ./boards/$BOARD -cp /tmp/arduino-build/compile_commands.json . -echo "[OK] Generated compile_commands.json for $BOARD" -""" - -[tasks.gen-crush-config] -description = "Generate .crush.json with BOARD-based FQBN" -run = """ -source ./boards/$BOARD/board-config.sh -cat > .crush.json << EOF -{ - "lsp": { - "arduino": { - "command": "arduino-language-server", - "args": ["-fqbn", "$FQBN"] - }, - "cpp": { - "command": "clangd" - } - } -} -EOF -echo "[OK] Generated .crush.json with FQBN: $FQBN" -""" - -[tasks.snap] -run = "git add .; lumen draft | git commit -F - " [env] -BOARD = "esp32-32e-4" +BOARD = "uno-r4-wifi"