Compare commits
5 Commits
59b2bf01b7
...
dfd511e499
| Author | SHA1 | Date | |
|---|---|---|---|
| dfd511e499 | |||
| 4da32466a8 | |||
| a3164d722e | |||
| dd1c13fbbc | |||
| ec8ec4cd18 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ vendor/esp32-s3-lcd-43/GFX_Library_for_Arduino/
|
|||||||
*~
|
*~
|
||||||
.DS_Store
|
.DS_Store
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
.cache/
|
||||||
|
|||||||
246
AGENTS.md
246
AGENTS.md
@@ -4,53 +4,66 @@ Multi-target Arduino/ESP32 doorbell alert system using ntfy.sh.
|
|||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
|
**Default BOARD**: `esp32-s3-lcd-43` (set in mise.toml). To switch boards:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Compile, upload, monitor
|
mise set BOARD=esp32-32e-4 # switch to ESP32-32E-4"
|
||||||
BOARD=esp32-32e-4 mise run compile # compile
|
mise set BOARD=esp32-32e # switch to ESP32-32E
|
||||||
BOARD=esp32-32e-4 mise run upload # upload (auto-kills monitor)
|
mise set BOARD=esp32-s3-lcd-43 # switch to ESP32-S3-LCD-4.3
|
||||||
BOARD=esp32-32e-4 mise run monitor # start JSON monitor daemon
|
```
|
||||||
BOARD=esp32-32e-4 mise run log-tail # watch colored logs
|
|
||||||
BOARD=esp32-32e-4 mise run cmd COMMAND=dashboard # send command
|
Then run commands without `BOARD=` prefix:
|
||||||
BOARD=esp32-32e-4 mise run state # show device state
|
|
||||||
|
```bash
|
||||||
|
mise run compile # compile
|
||||||
|
mise run upload # upload (auto-kills monitor)
|
||||||
|
mise run monitor # start JSON monitor daemon
|
||||||
|
mise run log-tail # watch colored logs
|
||||||
|
mise run cmd COMMAND=dashboard # send command
|
||||||
|
mise run state # show device state
|
||||||
|
|
||||||
# Install libs (run after cloning)
|
# Install libs (run after cloning)
|
||||||
mise run install-libs-shared # shared libs (ArduinoJson, NTPClient)
|
mise run install-libs-shared # shared libs (ArduinoJson, NTPClient)
|
||||||
BOARD=esp32-32e-4 mise run install # shared + board-specific libs
|
mise run install # shared + board-specific libs
|
||||||
```
|
```
|
||||||
|
|
||||||
**Default BOARD**: `esp32-s3-lcd-43` (set in mise.toml)
|
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Three board targets share business logic via a common library:
|
Three board targets share business logic via a common library:
|
||||||
|
|
||||||
| Board | Display | Library | Build Command |
|
| Board | Display | Library | Build Command |
|
||||||
|-------|---------|---------|--------------|
|
|-------|---------|---------|--------------|
|
||||||
| ESP32-32E | SPI TFT 320x240 (ILI9341) | TFT_eSPI | `BOARD=esp32-32e mise run compile` |
|
| ESP32-32E | SPI TFT 320x240 (ILI9341) | TFT_eSPI | `mise set BOARD=esp32-32e && mise run compile` |
|
||||||
| ESP32-32E-4" | SPI TFT 320x480 (ST7796) | TFT_eSPI | `BOARD=esp32-32e-4 mise run compile` |
|
| ESP32-32E-4" | SPI TFT 320x480 (ST7796) | TFT_eSPI | `mise set BOARD=esp32-32e-4 && mise run compile` |
|
||||||
| ESP32-S3-Touch-LCD-4.3 | 800x480 RGB parallel | LovyanGFX | `BOARD=esp32-s3-lcd-43 mise run compile` |
|
| ESP32-S3-Touch-LCD-4.3 | 800x480 RGB parallel | LovyanGFX | `mise set BOARD=esp32-s3-lcd-43 && mise run compile` |
|
||||||
|
|
||||||
## Essential Commands
|
## Essential Commands
|
||||||
|
|
||||||
All commands run via **mise**:
|
All commands run via **mise**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Running multiple tasks - use && to chain them
|
||||||
|
mise run compile && mise run upload && mise run monitor
|
||||||
|
|
||||||
# Install all dependencies (shared libs + vendored display libs)
|
# Install all dependencies (shared libs + vendored display libs)
|
||||||
mise run install-libs-shared
|
mise run install-libs-shared
|
||||||
mise run install # install shared + board-specific libs (requires BOARD env)
|
mise run install # install shared + board-specific libs (requires BOARD env)
|
||||||
|
|
||||||
# Generic commands (set BOARD environment variable)
|
# Generic commands (set BOARD with mise set first)
|
||||||
BOARD=esp32-32e mise run compile # compile for ESP32-32E
|
mise set BOARD=esp32-32e # switch to ESP32-32E
|
||||||
BOARD=esp32-32e mise run upload # upload to ESP32-32E
|
mise run compile # compile for ESP32-32E
|
||||||
BOARD=esp32-32e mise run monitor # monitor ESP32-32E
|
mise run upload # upload to ESP32-32E
|
||||||
|
mise run monitor # monitor ESP32-32E
|
||||||
|
|
||||||
BOARD=esp32-32e-4 mise run compile # compile for ESP32-32E-4"
|
mise set BOARD=esp32-32e-4 # switch to ESP32-32E-4"
|
||||||
BOARD=esp32-32e-4 mise run upload # upload to ESP32-32E-4"
|
mise run compile # compile for ESP32-32E-4"
|
||||||
BOARD=esp32-32e-4 mise run monitor # monitor ESP32-32E-4"
|
mise run upload # upload to ESP32-32E-4"
|
||||||
|
mise run monitor # monitor ESP32-32E-4"
|
||||||
|
|
||||||
BOARD=esp32-s3-lcd-43 mise run compile # compile for ESP32-S3-LCD-4.3
|
mise set BOARD=esp32-s3-lcd-43 # switch to ESP32-S3-LCD-4.3
|
||||||
BOARD=esp32-s3-lcd-43 mise run upload # upload to ESP32-S3-LCD-4.3
|
mise run compile # compile for ESP32-S3-LCD-4.3
|
||||||
BOARD=esp32-s3-lcd-43 mise run monitor # monitor ESP32-S3-LCD-4.3
|
mise run upload # upload to ESP32-S3-LCD-4.3
|
||||||
|
mise run monitor # monitor ESP32-S3-LCD-4.3
|
||||||
|
|
||||||
# Other useful tasks
|
# Other useful tasks
|
||||||
mise run format # format code
|
mise run format # format code
|
||||||
@@ -92,7 +105,7 @@ LIBS="--libraries ./vendor/esp32-32e-4/TFT_eSPI"
|
|||||||
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM"
|
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Port override**: `PORT=/dev/ttyXXX BOARD=esp32-32e mise run upload`
|
**Port override**: `mise set PORT=/dev/ttyXXX` before running upload/monitor commands.
|
||||||
|
|
||||||
**Prerequisites**: arduino-cli with `esp32:esp32` platform installed, mise.
|
**Prerequisites**: arduino-cli with `esp32:esp32` platform installed, mise.
|
||||||
|
|
||||||
@@ -188,13 +201,21 @@ Use `#pragma once` (not `#ifndef` guards).
|
|||||||
- Use `millis()` for timing (not `delay()`)
|
- Use `millis()` for timing (not `delay()`)
|
||||||
- Serial console at 115200 baud for debug commands
|
- Serial console at 115200 baud for debug commands
|
||||||
|
|
||||||
|
### Style System
|
||||||
|
|
||||||
|
The project uses a CSS-like styling system for consistent UI across different display sizes:
|
||||||
|
|
||||||
|
- **Style constants** in each board's `board_config.h`: `STYLE_SPACING_X`, `STYLE_HEADER_HEIGHT`, `STYLE_COLOR_BG`, etc.
|
||||||
|
- **Font abstraction** via `IDisplayDriver` methods: `setTitleFont()`, `setBodyFont()`, `setLabelFont()`, `setDefaultFont()`
|
||||||
|
- **Layout helpers** in `KlubhausCore/src/Style.h`: `Layout` and `TileMetrics` structs
|
||||||
|
|
||||||
## Testing/Debugging
|
## Testing/Debugging
|
||||||
|
|
||||||
**No unit tests exist** - This is an embedded Arduino sketch. Verify changes by building and deploying to hardware:
|
**No unit tests exist** - This is an embedded Arduino sketch. Verify changes by building and deploying to hardware:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BOARD=esp32-s3-lcd-43 mise run compile # compile for default board
|
mise set BOARD=esp32-s3-lcd-43 # compile for esp32-s3-lcd-43
|
||||||
BOARD=esp32-32e-4 mise run compile # compile for ESP32-32E-4"
|
mise set BOARD=esp32-32e-4 # compile for ESP32-32E-4"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Serial commands** (type into serial monitor at 115200 baud):
|
**Serial commands** (type into serial monitor at 115200 baud):
|
||||||
@@ -216,6 +237,12 @@ BOARD=esp32-32e-4 mise run compile # compile for ESP32-32E-4"
|
|||||||
| `TEST:touch X Y release` | Inject synthetic release at raw panel coords (X,Y) |
|
| `TEST:touch X Y release` | Inject synthetic release at raw panel coords (X,Y) |
|
||||||
| `TEST:touch clear` | Clear test mode (required between touch sequences) |
|
| `TEST:touch clear` | Clear test mode (required between touch sequences) |
|
||||||
|
|
||||||
|
**Debug Features** (only when `DEBUG_MODE` is enabled in board-config.sh):
|
||||||
|
|
||||||
|
| Feature | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| Red crosshair | Draws a red crosshair at exact touch coordinates on tap (DEBUG_MODE only) |
|
||||||
|
|
||||||
Note: The S3 touch panel is rotated 180° relative to the display. Use raw panel coordinates:
|
Note: The S3 touch panel is rotated 180° relative to the display. Use raw panel coordinates:
|
||||||
|
|
||||||
- Display (100,140) → Raw (700, 340)
|
- Display (100,140) → Raw (700, 340)
|
||||||
@@ -232,22 +259,22 @@ The build system includes a Python-based monitor agent that provides JSON loggin
|
|||||||
Commands to interact with the daemon:
|
Commands to interact with the daemon:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BOARD=esp32-32e mise run monitor # Start monitor daemon (background)
|
mise set BOARD=esp32-32e
|
||||||
BOARD=esp32-32e mise run log-tail # Tail colored logs
|
mise run monitor # Start monitor daemon (background)
|
||||||
BOARD=esp32-32e mise run cmd COMMAND=dashboard # Send command
|
mise run log-tail # Tail colored logs
|
||||||
BOARD=esp32-32e mise run state # Show current device state
|
mise run cmd COMMAND=dashboard # Send command
|
||||||
|
mise run state # Show current device state
|
||||||
```
|
```
|
||||||
|
|
||||||
The monitor daemon automatically starts after upload and is killed before upload to avoid serial port conflicts.
|
The monitor daemon automatically starts after upload and is killed before upload to avoid serial port conflicts.
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
1. **secrets.h is gitignored**: Copy from `.example` before building:
|
1. **secrets.h can be shared**: KlubhausCore/src/secrets.h provides default credentials. Boards with `LOCAL_SECRETS` defined in board-config.sh will use their local `secrets.h` instead.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp boards/esp32-32e/secrets.h.example boards/esp32-32e/secrets.h
|
# For boards without local secrets.h, the defaults will be used
|
||||||
cp boards/esp32-32e-4/secrets.h.example boards/esp32-32e-4/secrets.h
|
# To use board-specific credentials, add -DLOCAL_SECRETS to OPTS in board-config.sh
|
||||||
cp boards/esp32-s3-lcd-43/secrets.h.example boards/esp32-s3-lcd-43/secrets.h
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Display libs are vendored**: Each board uses a different display library. The build system uses `--libraries` to link only the board's vendored lib — never link both TFT_eSPI and LovyanGFX in the same build.
|
2. **Display libs are vendored**: Each board uses a different display library. The build system uses `--libraries` to link only the board's vendored lib — never link both TFT_eSPI and LovyanGFX in the same build.
|
||||||
@@ -260,7 +287,7 @@ The monitor daemon automatically starts after upload and is killed before upload
|
|||||||
|
|
||||||
6. **WiFi credentials are per-board**: Each board directory has its own `secrets.h` because boards may be on different networks.
|
6. **WiFi credentials are per-board**: Each board directory has its own `secrets.h` because boards may be on different networks.
|
||||||
|
|
||||||
7. **Use BOARD environment variable**: All build commands require the `BOARD` environment variable (e.g., `BOARD=esp32-32e mise run compile`). The default BOARD is `esp32-s3-lcd-43` (set in mise.toml).
|
7. **Use BOARD environment variable**: All build commands require the `BOARD` environment variable. The default BOARD is `esp32-s3-lcd-43` (set in mise.toml). Use `mise set BOARD=xxx` to switch boards.
|
||||||
|
|
||||||
8. **Serial port contention**: The `upload`, `monitor`, and `monitor-raw` tasks automatically depend on `kill` which uses `fuser` to terminate any process using the serial port before starting.
|
8. **Serial port contention**: The `upload`, `monitor`, and `monitor-raw` tasks automatically depend on `kill` which uses `fuser` to terminate any process using the serial port before starting.
|
||||||
|
|
||||||
@@ -325,9 +352,9 @@ Track changes that were reverted to avoid flapping:
|
|||||||
|
|
||||||
| Issue | Solution |
|
| Issue | Solution |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| "Another instance is running" error | Run `mise run kill BOARD=<board>` or `FORCE=1 mise run <task> BOARD=<board>` |
|
| "Another instance is running" error | Run `mise run kill` to stop monitor daemon and release port |
|
||||||
| Upload fails - port in use | Run `mise run kill` to stop monitor daemon and release port |
|
| Upload fails - port in use | Run `mise run kill` to stop monitor daemon and release port |
|
||||||
| Build fails - missing libraries | Run `mise run install-libs-shared` then `BOARD=<board> mise run install` |
|
| Build fails - missing libraries | Run `mise run install-libs-shared` then `mise run install` |
|
||||||
| LSP shows errors but build works | Run `mise run gen-compile-commands` to generate compile_commands.json for your BOARD |
|
| LSP shows errors but build works | Run `mise run gen-compile-commands` to generate compile_commands.json for your BOARD |
|
||||||
| No serial output | Check baud rate is set to 115200 in serial monitor |
|
| No serial output | Check baud rate is set to 115200 in serial monitor |
|
||||||
| State file not updating | Ensure serial output contains `[STATE]` or `[ADMIN]` tags |
|
| State file not updating | Ensure serial output contains `[STATE]` or `[ADMIN]` tags |
|
||||||
@@ -355,13 +382,15 @@ The project uses **clangd** for C++ via the `compile_commands.json` generated by
|
|||||||
**Generate compile_commands.json** for accurate IDE diagnostics:
|
**Generate compile_commands.json** for accurate IDE diagnostics:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BOARD=esp32-32e-4 mise run gen-compile-commands
|
mise set BOARD=esp32-32e-4
|
||||||
|
mise run gen-compile-commands
|
||||||
```
|
```
|
||||||
|
|
||||||
**Generate static .crush.json** for a specific board:
|
**Generate static .crush.json** for a specific board:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BOARD=esp32-32e-4 mise run gen-crush-config
|
mise set BOARD=esp32-32e-4
|
||||||
|
mise run gen-crush-config
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hardware Research Log
|
## Hardware Research Log
|
||||||
@@ -397,143 +426,26 @@ BOARD=esp32-32e-4 mise run gen-crush-config
|
|||||||
- Backlight on IO27 (HIGH = on)
|
- Backlight on IO27 (HIGH = on)
|
||||||
- Common anode RGB LEDs on IO16, IO17, IO22 (LOW = on)
|
- Common anode RGB LEDs on IO16, IO17, IO22 (LOW = on)
|
||||||
<!-- rtk-instructions v2 -->
|
<!-- rtk-instructions v2 -->
|
||||||
# RTK (Rust Token Killer) - Token-Optimized Commands
|
# RTK (Rust Token Killer)
|
||||||
|
|
||||||
## Golden Rule
|
RTK filters command output to reduce tokens. It's optional but recommended for saving tokens.
|
||||||
|
|
||||||
**Always prefix commands with `rtk`**. If RTK has a dedicated filter, it uses it. If not, it passes through unchanged. This means RTK is always safe to use.
|
## Installation Check
|
||||||
|
|
||||||
**Important**: Even in command chains with `&&`, use `rtk`:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ❌ Wrong
|
which rtk # Check if installed
|
||||||
git add . && git commit -m "msg" && git push
|
|
||||||
|
|
||||||
# ✅ Correct
|
|
||||||
rtk git add . && rtk git commit -m "msg" && rtk git push
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## RTK Commands by Workflow
|
If installed, use it before git commands:
|
||||||
|
|
||||||
### Build & Compile (80-90% savings)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rtk cargo build # Cargo build output
|
rtk git status
|
||||||
rtk cargo check # Cargo check output
|
rtk git diff
|
||||||
rtk cargo clippy # Clippy warnings grouped by file (80%)
|
rtk git add .
|
||||||
rtk tsc # TypeScript errors grouped by file/code (83%)
|
rtk git commit -m "message"
|
||||||
rtk lint # ESLint/Biome violations grouped (84%)
|
rtk git push
|
||||||
rtk prettier --check # Files needing format only (70%)
|
|
||||||
rtk next build # Next.js build with route metrics (87%)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test (90-99% savings)
|
RTK passes through unchanged if no filter exists, so it's always safe to use.
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk cargo test # Cargo test failures only (90%)
|
|
||||||
rtk vitest run # Vitest failures only (99.5%)
|
|
||||||
rtk playwright test # Playwright failures only (94%)
|
|
||||||
rtk test <cmd> # Generic test wrapper - failures only
|
|
||||||
```
|
|
||||||
|
|
||||||
### Git (59-80% savings)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk git status # Compact status
|
|
||||||
rtk git log # Compact log (works with all git flags)
|
|
||||||
rtk git diff # Compact diff (80%)
|
|
||||||
rtk git show # Compact show (80%)
|
|
||||||
rtk git add # Ultra-compact confirmations (59%)
|
|
||||||
rtk git commit # Ultra-compact confirmations (59%)
|
|
||||||
rtk git push # Ultra-compact confirmations
|
|
||||||
rtk git pull # Ultra-compact confirmations
|
|
||||||
rtk git branch # Compact branch list
|
|
||||||
rtk git fetch # Compact fetch
|
|
||||||
rtk git stash # Compact stash
|
|
||||||
rtk git worktree # Compact worktree
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Git passthrough works for ALL subcommands, even those not explicitly listed.
|
|
||||||
|
|
||||||
### GitHub (26-87% savings)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk gh pr view <num> # Compact PR view (87%)
|
|
||||||
rtk gh pr checks # Compact PR checks (79%)
|
|
||||||
rtk gh run list # Compact workflow runs (82%)
|
|
||||||
rtk gh issue list # Compact issue list (80%)
|
|
||||||
rtk gh api # Compact API responses (26%)
|
|
||||||
```
|
|
||||||
|
|
||||||
### JavaScript/TypeScript Tooling (70-90% savings)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk pnpm list # Compact dependency tree (70%)
|
|
||||||
rtk pnpm outdated # Compact outdated packages (80%)
|
|
||||||
rtk pnpm install # Compact install output (90%)
|
|
||||||
rtk npm run <script> # Compact npm script output
|
|
||||||
rtk npx <cmd> # Compact npx command output
|
|
||||||
rtk prisma # Prisma without ASCII art (88%)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Files & Search (60-75% savings)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk ls <path> # Tree format, compact (65%)
|
|
||||||
rtk read <file> # Code reading with filtering (60%)
|
|
||||||
rtk grep <pattern> # Search grouped by file (75%)
|
|
||||||
rtk find <pattern> # Find grouped by directory (70%)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Analysis & Debug (70-90% savings)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk err <cmd> # Filter errors only from any command
|
|
||||||
rtk log <file> # Deduplicated logs with counts
|
|
||||||
rtk json <file> # JSON structure without values
|
|
||||||
rtk deps # Dependency overview
|
|
||||||
rtk env # Environment variables compact
|
|
||||||
rtk summary <cmd> # Smart summary of command output
|
|
||||||
rtk diff # Ultra-compact diffs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Infrastructure (85% savings)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk docker ps # Compact container list
|
|
||||||
rtk docker images # Compact image list
|
|
||||||
rtk docker logs <c> # Deduplicated logs
|
|
||||||
rtk kubectl get # Compact resource list
|
|
||||||
rtk kubectl logs # Deduplicated pod logs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Network (65-70% savings)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk curl <url> # Compact HTTP responses (70%)
|
|
||||||
rtk wget <url> # Compact download output (65%)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Meta Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rtk gain # View token savings statistics
|
|
||||||
rtk gain --history # View command history with savings
|
|
||||||
rtk discover # Analyze Claude Code sessions for missed RTK usage
|
|
||||||
rtk proxy <cmd> # Run command without filtering (for debugging)
|
|
||||||
rtk init --global # Add RTK to ~/.claude/CLAUDE.md
|
|
||||||
```
|
|
||||||
|
|
||||||
rtk init # Add RTK instructions to CLAUDE.md
|
|
||||||
|
|
||||||
## Token Savings Overview
|
|
||||||
|
|
||||||
| Category | Commands | Typical Savings |
|
|
||||||
|----------|----------|-----------------|
|
|
||||||
| Tests | vitest, playwright, cargo test | 90-99% |
|
|
||||||
| Build | next, tsc, lint, prettier | 70-87% |
|
|
||||||
| Git | status, log, diff, add, commit | 59-80% |
|
|
||||||
| GitHub | gh pr, gh run, gh issue | 26-87% |
|
|
||||||
| Package Managers | pnpm, npm, npx | 70-90% |
|
| Package Managers | pnpm, npm, npx | 70-90% |
|
||||||
| Files | ls, read, grep, find | 60-75% |
|
| Files | ls, read, grep, find | 60-75% |
|
||||||
| Infrastructure | docker, kubectl | 85% |
|
| Infrastructure | docker, kubectl | 85% |
|
||||||
|
|||||||
77
SESSION_NOTES.md
Normal file
77
SESSION_NOTES.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Session Notes - 2026-02-19
|
||||||
|
|
||||||
|
## What's Been Done
|
||||||
|
|
||||||
|
### Build System
|
||||||
|
- Fixed mise.toml compile task (single quotes, proper shell escaping)
|
||||||
|
- Added upload task back
|
||||||
|
- All 3 boards now compile successfully
|
||||||
|
|
||||||
|
### Code Changes
|
||||||
|
- Added font abstraction (`setTitleFont`, `setBodyFont`, `setLabelFont`, `setDefaultFont`)
|
||||||
|
- Added CSS-like styling constants (`STYLE_*` in board_config.h)
|
||||||
|
- Added Layout helpers in `KlubhausCore/src/Style.h`
|
||||||
|
- Added `drawDebugTouch()` for red crosshair debug feature
|
||||||
|
- Added test harness for touch injection (`TEST:touch` commands)
|
||||||
|
- Fixed dashboard tile layout to use STYLE constants
|
||||||
|
|
||||||
|
### Neovim Integration
|
||||||
|
- Added keymaps in `~/.config/nvim/lua/config/keymaps.lua`
|
||||||
|
- Board auto-detection from current file path
|
||||||
|
- Notifications via `vim.notify()` on task completion/failure
|
||||||
|
- Keybindings: `<leader>mc`, `<leader>mu`, `<leader>mm`, `<leader>ma`, `<leader>mk`
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- Updated AGENTS.md with Style System section
|
||||||
|
- Updated RTK section (slimmed down)
|
||||||
|
- Added note about multiple mise tasks with `&&`
|
||||||
|
|
||||||
|
## Current Issues / TODO
|
||||||
|
|
||||||
|
### High Priority
|
||||||
|
- [ ] Test touch coordinates on esp32-s3-lcd-43 - crosshair should show where user taps
|
||||||
|
- [ ] Debug why tiles overlap header (should be fixed with STYLE_HEADER_HEIGHT)
|
||||||
|
|
||||||
|
### Medium Priority
|
||||||
|
- [ ] Add theme support (dark/light mode)
|
||||||
|
- [ ] Fix esp32-32e and esp32-32e-4 dashboard rendering
|
||||||
|
|
||||||
|
### Low Priority
|
||||||
|
- [ ] Add more tile labels for esp32-s3-lcd-43 (currently just "1", "2", "3"...)
|
||||||
|
|
||||||
|
## Board Status
|
||||||
|
|
||||||
|
| Board | Display | Build | Notes |
|
||||||
|
|-------|---------|-------|-------|
|
||||||
|
| esp32-s3-lcd-43 | 800x480 | ✅ 35% | Main development board |
|
||||||
|
| esp32-32e-4 | 320x480 | ✅ 83% | |
|
||||||
|
| esp32-32e | 320x240 | ✅ 82% | |
|
||||||
|
|
||||||
|
## Style Constants Reference
|
||||||
|
|
||||||
|
### Spacing (in board_config.h)
|
||||||
|
- `STYLE_SPACING_X` - Base horizontal margin
|
||||||
|
- `STYLE_SPACING_Y` - Base vertical margin
|
||||||
|
- `STYLE_HEADER_HEIGHT` - Header bar height
|
||||||
|
- `STYLE_TILE_GAP` - Gap between tiles
|
||||||
|
- `STYLE_TILE_PADDING` - Tile internal padding
|
||||||
|
- `STYLE_TILE_RADIUS` - Tile border radius
|
||||||
|
|
||||||
|
### Colors
|
||||||
|
- `STYLE_COLOR_BG` - Screen background
|
||||||
|
- `STYLE_COLOR_HEADER` - Header background
|
||||||
|
- `STYLE_COLOR_FG` - Primary text color
|
||||||
|
- `STYLE_COLOR_ALERT` - Alert screen
|
||||||
|
- `STYLE_COLOR_TILE_1` through `STYLE_COLOR_TILE_4` - Tile colors
|
||||||
|
|
||||||
|
## Useful Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compile and upload
|
||||||
|
mise run compile && mise run upload && mise run monitor
|
||||||
|
|
||||||
|
# Debug touch (in serial monitor)
|
||||||
|
TEST:touch X Y press
|
||||||
|
TEST:touch X Y release
|
||||||
|
TEST:touch clear
|
||||||
|
```
|
||||||
@@ -1,9 +1,86 @@
|
|||||||
#include "DisplayDriverTFT.h"
|
#include "DisplayDriverTFT.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
#include <KlubhausCore.h>
|
#include <KlubhausCore.h>
|
||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
extern DisplayManager display;
|
extern DisplayManager display;
|
||||||
|
|
||||||
|
// ── Fonts ───────────────────────────────────────────────────
|
||||||
|
// TFT_eSPI built-in fonts for 320x480 display (scaled from 800x480)
|
||||||
|
// Using FreeFonts - scaled bitmap fonts via setTextSize would be too pixelated
|
||||||
|
// Note: FreeFonts are enabled via LOAD_GFXFF=1 in board-config.sh
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setTitleFont() { _tft.setFreeFont(&FreeSansBold18pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setBodyFont() { _tft.setFreeFont(&FreeSans12pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setLabelFont() { _tft.setFreeFont(&FreeSans9pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setDefaultFont() { _tft.setTextFont(2); }
|
||||||
|
|
||||||
|
// ── Test harness ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Test harness: parse serial commands to inject synthetic touches
|
||||||
|
// Commands:
|
||||||
|
// TEST:touch x y press - simulate press at (x, y)
|
||||||
|
// TEST:touch x y release - simulate release at (x, y)
|
||||||
|
// TEST:touch clear - clear test mode
|
||||||
|
bool DisplayDriverTFT::parseTestTouch(int* outX, int* outY, bool* outPressed) {
|
||||||
|
if(!Serial.available())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(Serial.peek() != 'T') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String cmd = Serial.readStringUntil('\n');
|
||||||
|
cmd.trim();
|
||||||
|
|
||||||
|
if(!cmd.startsWith("TEST:touch"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int firstSpace = cmd.indexOf(' ');
|
||||||
|
if(firstSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String args = cmd.substring(firstSpace + 1);
|
||||||
|
args.trim();
|
||||||
|
|
||||||
|
if(args.equals("clear")) {
|
||||||
|
_testMode = false;
|
||||||
|
Serial.println("[TEST] Test mode cleared");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secondSpace = args.indexOf(' ');
|
||||||
|
if(secondSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String xStr = args.substring(0, secondSpace);
|
||||||
|
String yState = args.substring(secondSpace + 1);
|
||||||
|
yState.trim();
|
||||||
|
|
||||||
|
int x = xStr.toInt();
|
||||||
|
int y = yState.substring(0, yState.indexOf(' ')).toInt();
|
||||||
|
String state = yState.substring(yState.indexOf(' ') + 1);
|
||||||
|
state.trim();
|
||||||
|
|
||||||
|
bool pressed = state.equals("press");
|
||||||
|
|
||||||
|
Serial.printf("[TEST] Injecting touch: (%d,%d) %s\n", x, y, pressed ? "press" : "release");
|
||||||
|
|
||||||
|
if(outX)
|
||||||
|
*outX = x;
|
||||||
|
if(outY)
|
||||||
|
*outY = y;
|
||||||
|
if(outPressed)
|
||||||
|
*outPressed = pressed;
|
||||||
|
|
||||||
|
_testMode = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void DisplayDriverTFT::begin() {
|
void DisplayDriverTFT::begin() {
|
||||||
// Backlight
|
// Backlight
|
||||||
pinMode(PIN_LCD_BL, OUTPUT);
|
pinMode(PIN_LCD_BL, OUTPUT);
|
||||||
@@ -74,14 +151,17 @@ void DisplayDriverTFT::drawBoot(const ScreenState& st) {
|
|||||||
|
|
||||||
_tft.fillScreen(TFT_BLACK);
|
_tft.fillScreen(TFT_BLACK);
|
||||||
_tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
_tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
_tft.setTextSize(2);
|
|
||||||
|
setTitleFont();
|
||||||
_tft.setCursor(10, 10);
|
_tft.setCursor(10, 10);
|
||||||
_tft.printf("KLUBHAUS v%s", FW_VERSION);
|
_tft.print("KLUBHAUS");
|
||||||
_tft.setTextSize(1);
|
|
||||||
|
setBodyFont();
|
||||||
_tft.setCursor(10, 40);
|
_tft.setCursor(10, 40);
|
||||||
_tft.print(BOARD_NAME);
|
_tft.print(BOARD_NAME);
|
||||||
|
|
||||||
// Show boot stage status
|
// Show boot stage status
|
||||||
|
setLabelFont();
|
||||||
_tft.setCursor(10, 70);
|
_tft.setCursor(10, 70);
|
||||||
switch(stage) {
|
switch(stage) {
|
||||||
case BootStage::SPLASH:
|
case BootStage::SPLASH:
|
||||||
@@ -113,15 +193,15 @@ void DisplayDriverTFT::drawAlert(const ScreenState& st) {
|
|||||||
_tft.fillScreen(bg);
|
_tft.fillScreen(bg);
|
||||||
_tft.setTextColor(TFT_WHITE, bg);
|
_tft.setTextColor(TFT_WHITE, bg);
|
||||||
|
|
||||||
_tft.setTextSize(3);
|
setTitleFont();
|
||||||
_tft.setCursor(10, 20);
|
_tft.setCursor(10, 20);
|
||||||
_tft.print(st.alertTitle.length() > 0 ? st.alertTitle : "ALERT");
|
_tft.print(st.alertTitle.length() > 0 ? st.alertTitle : "ALERT");
|
||||||
|
|
||||||
_tft.setTextSize(2);
|
setBodyFont();
|
||||||
_tft.setCursor(10, 80);
|
_tft.setCursor(10, 80);
|
||||||
_tft.print(st.alertBody);
|
_tft.print(st.alertBody);
|
||||||
|
|
||||||
_tft.setTextSize(1);
|
setLabelFont();
|
||||||
_tft.setCursor(10, DISPLAY_HEIGHT - 20);
|
_tft.setCursor(10, DISPLAY_HEIGHT - 20);
|
||||||
_tft.print("Hold to silence...");
|
_tft.print("Hold to silence...");
|
||||||
}
|
}
|
||||||
@@ -130,17 +210,18 @@ void DisplayDriverTFT::drawDashboard(const ScreenState& st) {
|
|||||||
_tft.fillScreen(TFT_BLACK);
|
_tft.fillScreen(TFT_BLACK);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
_tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
_tft.fillRect(0, 0, DISPLAY_WIDTH, 30, 0x1A1A); // Dark gray header
|
||||||
_tft.setTextSize(1);
|
setBodyFont();
|
||||||
_tft.setCursor(5, 5);
|
_tft.setTextColor(TFT_WHITE);
|
||||||
_tft.printf("KLUBHAUS");
|
_tft.setCursor(5, 10);
|
||||||
|
_tft.print("KLUBHAUS");
|
||||||
|
|
||||||
// WiFi indicator
|
// WiFi indicator
|
||||||
_tft.setCursor(DISPLAY_WIDTH - 50, 5);
|
_tft.setCursor(DISPLAY_WIDTH - 60, 10);
|
||||||
_tft.printf("WiFi:%s", st.wifiSsid.length() > 0 ? "ON" : "OFF");
|
_tft.print(st.wifiSsid.length() > 0 ? "WiFi:ON" : "WiFi:OFF");
|
||||||
|
|
||||||
// Get tile layouts from library helper
|
// Get tile layouts from library helper
|
||||||
int tileCount = display.calculateDashboardLayouts(30, 8);
|
int tileCount = display.calculateDashboardLayouts(STYLE_HEADER_HEIGHT, STYLE_TILE_GAP);
|
||||||
const TileLayout* layouts = display.getTileLayouts();
|
const TileLayout* layouts = display.getTileLayouts();
|
||||||
|
|
||||||
const char* tileLabels[] = { "Alert", "Silent", "Status", "Reboot" };
|
const char* tileLabels[] = { "Alert", "Silent", "Status", "Reboot" };
|
||||||
@@ -160,8 +241,8 @@ void DisplayDriverTFT::drawDashboard(const ScreenState& st) {
|
|||||||
_tft.drawRoundRect(x, y, w, h, 8, TFT_WHITE);
|
_tft.drawRoundRect(x, y, w, h, 8, TFT_WHITE);
|
||||||
|
|
||||||
// Tile label
|
// Tile label
|
||||||
|
setBodyFont();
|
||||||
_tft.setTextColor(TFT_WHITE);
|
_tft.setTextColor(TFT_WHITE);
|
||||||
_tft.setTextSize(2);
|
|
||||||
int textLen = strlen(tileLabels[i]);
|
int textLen = strlen(tileLabels[i]);
|
||||||
int textW = textLen * 12;
|
int textW = textLen * 12;
|
||||||
_tft.setCursor(x + w / 2 - textW / 2, y + h / 2 - 10);
|
_tft.setCursor(x + w / 2 - textW / 2, y + h / 2 - 10);
|
||||||
@@ -173,6 +254,34 @@ void DisplayDriverTFT::drawDashboard(const ScreenState& st) {
|
|||||||
|
|
||||||
TouchEvent DisplayDriverTFT::readTouch() {
|
TouchEvent DisplayDriverTFT::readTouch() {
|
||||||
TouchEvent evt;
|
TouchEvent evt;
|
||||||
|
|
||||||
|
// Check for test injection via serial
|
||||||
|
int testX, testY;
|
||||||
|
bool testPressed;
|
||||||
|
if(parseTestTouch(&testX, &testY, &testPressed)) {
|
||||||
|
if(testPressed && !_touchWasPressed) {
|
||||||
|
evt.pressed = true;
|
||||||
|
_touchDownX = testX;
|
||||||
|
_touchDownY = testY;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
} else if(!testPressed && _touchWasPressed) {
|
||||||
|
evt.released = true;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(testPressed) {
|
||||||
|
evt.x = testX;
|
||||||
|
evt.y = testY;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
_touchWasPressed = testPressed;
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t tx, ty;
|
uint16_t tx, ty;
|
||||||
uint8_t touched = _tft.getTouch(&tx, &ty, 100);
|
uint8_t touched = _tft.getTouch(&tx, &ty, 100);
|
||||||
|
|
||||||
@@ -239,11 +348,3 @@ HoldState DisplayDriverTFT::updateHold(unsigned long holdMs) {
|
|||||||
}
|
}
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverTFT::updateHint(int x, int y, bool active) {
|
|
||||||
float period = active ? 500.0f : 2000.0f;
|
|
||||||
float t = fmodf(millis(), period) / period;
|
|
||||||
uint8_t v = static_cast<uint8_t>(30.0f + 30.0f * sinf(t * 2.0f * PI));
|
|
||||||
uint16_t col = _tft.color565(v, v, v);
|
|
||||||
_tft.drawRect(x - 40, y - 20, 80, 40, col);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,13 +12,18 @@ public:
|
|||||||
void render(const ScreenState& state) override;
|
void render(const ScreenState& state) override;
|
||||||
TouchEvent readTouch() override;
|
TouchEvent readTouch() override;
|
||||||
HoldState updateHold(unsigned long holdMs) override;
|
HoldState updateHold(unsigned long holdMs) override;
|
||||||
void updateHint(int x, int y, bool active) override;
|
|
||||||
int width() override { return _tft.width(); }
|
int width() override { return _tft.width(); }
|
||||||
int height() override { return _tft.height(); }
|
int height() override { return _tft.height(); }
|
||||||
|
|
||||||
// Dashboard - uses transform for touch coordinate correction
|
// Dashboard - uses transform for touch coordinate correction
|
||||||
void transformTouch(int* x, int* y) override;
|
void transformTouch(int* x, int* y) override;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
void setTitleFont() override;
|
||||||
|
void setBodyFont() override;
|
||||||
|
void setLabelFont() override;
|
||||||
|
void setDefaultFont() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawBoot(const ScreenState& st);
|
void drawBoot(const ScreenState& st);
|
||||||
void drawAlert(const ScreenState& st);
|
void drawAlert(const ScreenState& st);
|
||||||
@@ -36,4 +41,8 @@ private:
|
|||||||
bool _touchWasPressed = false;
|
bool _touchWasPressed = false;
|
||||||
int _touchDownX = -1;
|
int _touchDownX = -1;
|
||||||
int _touchDownY = -1;
|
int _touchDownY = -1;
|
||||||
|
|
||||||
|
// Test mode for touch injection
|
||||||
|
bool _testMode = false;
|
||||||
|
bool parseTestTouch(int* outX, int* outY, bool* outPressed);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FQBN="esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default"
|
FQBN="esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default"
|
||||||
PORT="/dev/ttyUSB0"
|
PORT="/dev/ttyUSB0"
|
||||||
LIBS="--libraries ./vendor/esp32-32e-4/TFT_eSPI"
|
LIBS="--libraries ./vendor/esp32-32e-4/TFT_eSPI"
|
||||||
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM"
|
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLOAD_GFXFF=1"
|
||||||
|
|||||||
@@ -16,3 +16,22 @@
|
|||||||
|
|
||||||
// Touch — XPT2046 configured in tft_user_setup.h
|
// Touch — XPT2046 configured in tft_user_setup.h
|
||||||
// Touch CS: GPIO33, Touch IRQ: GPIO36
|
// Touch CS: GPIO33, Touch IRQ: GPIO36
|
||||||
|
|
||||||
|
// ── Style Constants (CSS-like) ────────────────────────────────────────
|
||||||
|
// Spacing - scaled for 320x480
|
||||||
|
#define STYLE_SPACING_X 6
|
||||||
|
#define STYLE_SPACING_Y 6
|
||||||
|
#define STYLE_HEADER_HEIGHT 24
|
||||||
|
#define STYLE_TILE_GAP 4
|
||||||
|
#define STYLE_TILE_PADDING 8
|
||||||
|
#define STYLE_TILE_RADIUS 4
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
#define STYLE_COLOR_BG TFT_BLACK
|
||||||
|
#define STYLE_COLOR_HEADER 0x1A1A
|
||||||
|
#define STYLE_COLOR_FG TFT_WHITE
|
||||||
|
#define STYLE_COLOR_ALERT TFT_RED
|
||||||
|
#define STYLE_COLOR_TILE_1 0x0280
|
||||||
|
#define STYLE_COLOR_TILE_2 0x0400
|
||||||
|
#define STYLE_COLOR_TILE_3 0x0440
|
||||||
|
#define STYLE_COLOR_TILE_4 0x0100
|
||||||
@@ -4,7 +4,14 @@
|
|||||||
|
|
||||||
#include "DisplayDriverTFT.h"
|
#include "DisplayDriverTFT.h"
|
||||||
#include "board_config.h"
|
#include "board_config.h"
|
||||||
|
|
||||||
|
// Include local secrets.h if it exists (board-specific credentials),
|
||||||
|
// otherwise KlubhausCore/src/secrets.h provides defaults.
|
||||||
|
#ifdef LOCAL_SECRETS
|
||||||
#include "secrets.h"
|
#include "secrets.h"
|
||||||
|
#else
|
||||||
|
#include <secrets.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <KlubhausCore.h>
|
#include <KlubhausCore.h>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,85 @@
|
|||||||
#include "DisplayDriverTFT.h"
|
#include "DisplayDriverTFT.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <KlubhausCore.h>
|
||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
|
extern DisplayManager display;
|
||||||
|
|
||||||
|
// ── Fonts ───────────────────────────────────────────────────
|
||||||
|
// TFT_eSPI built-in fonts for 320x240 display (further scaled)
|
||||||
|
// Using FreeFonts for better readability
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setTitleFont() { _tft.setFreeFont(&FreeSansBold12pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setBodyFont() { _tft.setFreeFont(&FreeSans9pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setLabelFont() { _tft.setFreeFont(&FreeSans9pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverTFT::setDefaultFont() { _tft.setTextFont(2); }
|
||||||
|
|
||||||
|
// ── Test harness ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Test harness: parse serial commands to inject synthetic touches
|
||||||
|
// Commands:
|
||||||
|
// TEST:touch x y press - simulate press at (x, y)
|
||||||
|
// TEST:touch x y release - simulate release at (x, y)
|
||||||
|
// TEST:touch clear - clear test mode
|
||||||
|
bool DisplayDriverTFT::parseTestTouch(int* outX, int* outY, bool* outPressed) {
|
||||||
|
if(!Serial.available())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(Serial.peek() != 'T') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String cmd = Serial.readStringUntil('\n');
|
||||||
|
cmd.trim();
|
||||||
|
|
||||||
|
if(!cmd.startsWith("TEST:touch"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int firstSpace = cmd.indexOf(' ');
|
||||||
|
if(firstSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String args = cmd.substring(firstSpace + 1);
|
||||||
|
args.trim();
|
||||||
|
|
||||||
|
if(args.equals("clear")) {
|
||||||
|
_testMode = false;
|
||||||
|
Serial.println("[TEST] Test mode cleared");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secondSpace = args.indexOf(' ');
|
||||||
|
if(secondSpace < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String xStr = args.substring(0, secondSpace);
|
||||||
|
String yState = args.substring(secondSpace + 1);
|
||||||
|
yState.trim();
|
||||||
|
|
||||||
|
int x = xStr.toInt();
|
||||||
|
int y = yState.substring(0, yState.indexOf(' ')).toInt();
|
||||||
|
String state = yState.substring(yState.indexOf(' ') + 1);
|
||||||
|
state.trim();
|
||||||
|
|
||||||
|
bool pressed = state.equals("press");
|
||||||
|
|
||||||
|
Serial.printf("[TEST] Injecting touch: (%d,%d) %s\n", x, y, pressed ? "press" : "release");
|
||||||
|
|
||||||
|
if(outX)
|
||||||
|
*outX = x;
|
||||||
|
if(outY)
|
||||||
|
*outY = y;
|
||||||
|
if(outPressed)
|
||||||
|
*outPressed = pressed;
|
||||||
|
|
||||||
|
_testMode = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void DisplayDriverTFT::begin() {
|
void DisplayDriverTFT::begin() {
|
||||||
// Backlight
|
// Backlight
|
||||||
pinMode(PIN_LCD_BL, OUTPUT);
|
pinMode(PIN_LCD_BL, OUTPUT);
|
||||||
@@ -145,6 +225,34 @@ void DisplayDriverTFT::drawDashboard(const ScreenState& st) {
|
|||||||
|
|
||||||
TouchEvent DisplayDriverTFT::readTouch() {
|
TouchEvent DisplayDriverTFT::readTouch() {
|
||||||
TouchEvent evt;
|
TouchEvent evt;
|
||||||
|
|
||||||
|
// Check for test injection via serial
|
||||||
|
int testX, testY;
|
||||||
|
bool testPressed;
|
||||||
|
if(parseTestTouch(&testX, &testY, &testPressed)) {
|
||||||
|
if(testPressed && !_touchWasPressed) {
|
||||||
|
evt.pressed = true;
|
||||||
|
_touchDownX = testX;
|
||||||
|
_touchDownY = testY;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
} else if(!testPressed && _touchWasPressed) {
|
||||||
|
evt.released = true;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(testPressed) {
|
||||||
|
evt.x = testX;
|
||||||
|
evt.y = testY;
|
||||||
|
evt.downX = _touchDownX;
|
||||||
|
evt.downY = _touchDownY;
|
||||||
|
}
|
||||||
|
|
||||||
|
_touchWasPressed = testPressed;
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t tx, ty;
|
uint16_t tx, ty;
|
||||||
uint8_t touched = _tft.getTouch(&tx, &ty, 100);
|
uint8_t touched = _tft.getTouch(&tx, &ty, 100);
|
||||||
|
|
||||||
@@ -219,10 +327,8 @@ HoldState DisplayDriverTFT::updateHold(unsigned long holdMs) {
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverTFT::updateHint(int x, int y, bool active) {
|
void DisplayDriverTFT::drawDebugTouch(int x, int y) {
|
||||||
float period = active ? 500.0f : 2000.0f;
|
const int size = 20;
|
||||||
float t = fmodf(millis(), period) / period;
|
_tft.drawLine(x - size, y, x + size, y, TFT_RED);
|
||||||
uint8_t v = static_cast<uint8_t>(30.0f + 30.0f * sinf(t * 2.0f * PI));
|
_tft.drawLine(x, y - size, x, y + size, TFT_RED);
|
||||||
uint16_t col = _tft.color565(v, v, v);
|
|
||||||
_tft.drawRect(x - 40, y - 20, 80, 40, col);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,20 @@ public:
|
|||||||
void setBacklight(bool on) override;
|
void setBacklight(bool on) override;
|
||||||
void render(const ScreenState& state) override;
|
void render(const ScreenState& state) override;
|
||||||
TouchEvent readTouch() override;
|
TouchEvent readTouch() override;
|
||||||
int dashboardTouch(int x, int y) override;
|
int dashboardTouch(int x, int y);
|
||||||
HoldState updateHold(unsigned long holdMs) override;
|
HoldState updateHold(unsigned long holdMs) override;
|
||||||
void updateHint(int x, int y, bool active) override;
|
|
||||||
int width() override { return DISPLAY_WIDTH; }
|
int width() override { return DISPLAY_WIDTH; }
|
||||||
int height() override { return DISPLAY_HEIGHT; }
|
int height() override { return DISPLAY_HEIGHT; }
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
void setTitleFont() override;
|
||||||
|
void setBodyFont() override;
|
||||||
|
void setLabelFont() override;
|
||||||
|
void setDefaultFont() override;
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
void drawDebugTouch(int x, int y) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawBoot(const ScreenState& st);
|
void drawBoot(const ScreenState& st);
|
||||||
void drawAlert(const ScreenState& st);
|
void drawAlert(const ScreenState& st);
|
||||||
@@ -34,4 +42,8 @@ private:
|
|||||||
bool _touchWasPressed = false;
|
bool _touchWasPressed = false;
|
||||||
int _touchDownX = -1;
|
int _touchDownX = -1;
|
||||||
int _touchDownY = -1;
|
int _touchDownY = -1;
|
||||||
|
|
||||||
|
// Test mode for touch injection
|
||||||
|
bool _testMode = false;
|
||||||
|
bool parseTestTouch(int* outX, int* outY, bool* outPressed);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FQBN="esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default"
|
FQBN="esp32:esp32:esp32:FlashSize=4M,PartitionScheme=default"
|
||||||
PORT="/dev/ttyUSB0"
|
PORT="/dev/ttyUSB0"
|
||||||
LIBS="--libraries ./vendor/esp32-32e/TFT_eSPI"
|
LIBS="--libraries ./vendor/esp32-32e/TFT_eSPI"
|
||||||
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM"
|
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLOCAL_SECRETS -DLOAD_GFXFF=1"
|
||||||
|
|||||||
@@ -20,3 +20,22 @@
|
|||||||
// If using capacitive touch (e.g. FT6236), configure I2C pins here:
|
// If using capacitive touch (e.g. FT6236), configure I2C pins here:
|
||||||
// #define TOUCH_SDA 21
|
// #define TOUCH_SDA 21
|
||||||
// #define TOUCH_SCL 22
|
// #define TOUCH_SCL 22
|
||||||
|
|
||||||
|
// ── Style Constants (CSS-like) ────────────────────────────────────────
|
||||||
|
// Spacing - scaled for 320x240
|
||||||
|
#define STYLE_SPACING_X 4
|
||||||
|
#define STYLE_SPACING_Y 4
|
||||||
|
#define STYLE_HEADER_HEIGHT 20
|
||||||
|
#define STYLE_TILE_GAP 4
|
||||||
|
#define STYLE_TILE_PADDING 6
|
||||||
|
#define STYLE_TILE_RADIUS 4
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
#define STYLE_COLOR_BG TFT_BLACK
|
||||||
|
#define STYLE_COLOR_HEADER 0x1A1A
|
||||||
|
#define STYLE_COLOR_FG TFT_WHITE
|
||||||
|
#define STYLE_COLOR_ALERT TFT_RED
|
||||||
|
#define STYLE_COLOR_TILE_1 0x0280
|
||||||
|
#define STYLE_COLOR_TILE_2 0x0400
|
||||||
|
#define STYLE_COLOR_TILE_3 0x0440
|
||||||
|
#define STYLE_COLOR_TILE_4 0x0100
|
||||||
|
|||||||
@@ -4,7 +4,14 @@
|
|||||||
|
|
||||||
#include "DisplayDriverTFT.h"
|
#include "DisplayDriverTFT.h"
|
||||||
#include "board_config.h"
|
#include "board_config.h"
|
||||||
|
|
||||||
|
// Include local secrets.h if it exists (board-specific credentials),
|
||||||
|
// otherwise KlubhausCore/src/secrets.h provides defaults.
|
||||||
|
#ifdef LOCAL_SECRETS
|
||||||
#include "secrets.h"
|
#include "secrets.h"
|
||||||
|
#else
|
||||||
|
#include <secrets.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <KlubhausCore.h>
|
#include <KlubhausCore.h>
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,16 @@ int DisplayDriverGFX::width() { return _gfx ? _gfx->width() : DISP_W; }
|
|||||||
|
|
||||||
int DisplayDriverGFX::height() { return _gfx ? _gfx->height() : DISP_H; }
|
int DisplayDriverGFX::height() { return _gfx ? _gfx->height() : DISP_H; }
|
||||||
|
|
||||||
|
// ── Fonts ──
|
||||||
|
// LovyanGFX built-in fonts for 800x480 display
|
||||||
|
void DisplayDriverGFX::setTitleFont() { _gfx->setFont(&fonts::FreeSansBold24pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverGFX::setBodyFont() { _gfx->setFont(&fonts::FreeSans18pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverGFX::setLabelFont() { _gfx->setFont(&fonts::FreeSans12pt7b); }
|
||||||
|
|
||||||
|
void DisplayDriverGFX::setDefaultFont() { _gfx->setFont(&fonts::Font2); }
|
||||||
|
|
||||||
// Transform touch coordinates to match display orientation
|
// Transform touch coordinates to match display orientation
|
||||||
// GT911 touch panel on this board is rotated 180° relative to display
|
// GT911 touch panel on this board is rotated 180° relative to display
|
||||||
void DisplayDriverGFX::transformTouch(int* x, int* y) {
|
void DisplayDriverGFX::transformTouch(int* x, int* y) {
|
||||||
@@ -121,9 +131,12 @@ bool DisplayDriverGFX::parseTestTouch(int* outX, int* outY, bool* outPressed) {
|
|||||||
|
|
||||||
Serial.printf("[TEST] Injecting touch: (%d,%d) %s\n", x, y, pressed ? "press" : "release");
|
Serial.printf("[TEST] Injecting touch: (%d,%d) %s\n", x, y, pressed ? "press" : "release");
|
||||||
|
|
||||||
if(outX) *outX = x;
|
if(outX)
|
||||||
if(outY) *outY = y;
|
*outX = x;
|
||||||
if(outPressed) *outPressed = pressed;
|
if(outY)
|
||||||
|
*outY = y;
|
||||||
|
if(outPressed)
|
||||||
|
*outPressed = pressed;
|
||||||
|
|
||||||
_testMode = true;
|
_testMode = true;
|
||||||
return true;
|
return true;
|
||||||
@@ -323,18 +336,19 @@ void DisplayDriverGFX::render(const ScreenState& state) {
|
|||||||
void DisplayDriverGFX::drawBoot(const ScreenState& state) {
|
void DisplayDriverGFX::drawBoot(const ScreenState& state) {
|
||||||
BootStage stage = state.bootStage;
|
BootStage stage = state.bootStage;
|
||||||
|
|
||||||
_gfx->fillScreen(0x000000);
|
_gfx->fillScreen(TFT_BLACK);
|
||||||
_gfx->setTextColor(0xFFFF);
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
_gfx->setTextSize(2);
|
setTitleFont();
|
||||||
_gfx->setCursor(10, 10);
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_SPACING_Y);
|
||||||
_gfx->print("KLUBHAUS");
|
_gfx->print("KLUBHAUS");
|
||||||
|
|
||||||
_gfx->setTextSize(1);
|
setBodyFont();
|
||||||
_gfx->setCursor(10, 50);
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_SPACING_Y + STYLE_HEADER_HEIGHT);
|
||||||
_gfx->print(BOARD_NAME);
|
_gfx->print(BOARD_NAME);
|
||||||
|
|
||||||
// Show boot stage status
|
// Show boot stage status
|
||||||
_gfx->setCursor(10, 80);
|
setLabelFont();
|
||||||
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_SPACING_Y + STYLE_HEADER_HEIGHT + 30);
|
||||||
switch(stage) {
|
switch(stage) {
|
||||||
case BootStage::SPLASH:
|
case BootStage::SPLASH:
|
||||||
_gfx->print("Initializing...");
|
_gfx->print("Initializing...");
|
||||||
@@ -363,30 +377,29 @@ void DisplayDriverGFX::drawAlert(const ScreenState& state) {
|
|||||||
uint16_t bg = _gfx->color565(pulse, 0, 0);
|
uint16_t bg = _gfx->color565(pulse, 0, 0);
|
||||||
|
|
||||||
_gfx->fillScreen(bg);
|
_gfx->fillScreen(bg);
|
||||||
_gfx->setTextColor(0xFFFF, bg);
|
_gfx->setTextColor(STYLE_COLOR_FG, bg);
|
||||||
|
|
||||||
_gfx->setTextSize(3);
|
setTitleFont();
|
||||||
_gfx->setCursor(10, 20);
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_HEADER_HEIGHT);
|
||||||
_gfx->print(state.alertTitle.length() > 0 ? state.alertTitle.c_str() : "ALERT");
|
_gfx->print(state.alertTitle.length() > 0 ? state.alertTitle.c_str() : "ALERT");
|
||||||
|
|
||||||
_gfx->setTextSize(2);
|
setBodyFont();
|
||||||
_gfx->setCursor(10, 80);
|
_gfx->setCursor(STYLE_SPACING_X, STYLE_HEADER_HEIGHT + 50);
|
||||||
_gfx->print(state.alertBody);
|
_gfx->print(state.alertBody);
|
||||||
|
|
||||||
_gfx->setTextSize(1);
|
setLabelFont();
|
||||||
_gfx->setCursor(10, DISP_H - 20);
|
_gfx->setCursor(STYLE_SPACING_X, DISP_H - STYLE_SPACING_Y);
|
||||||
_gfx->print("Hold to silence...");
|
_gfx->print("Hold to silence...");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
||||||
_gfx->fillScreen(0x001030); // Dark blue
|
_gfx->fillScreen(STYLE_COLOR_BG);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
_gfx->fillRect(0, 0, DISP_W, 40, 0x1A1A); // Dark gray
|
_gfx->fillRect(0, 0, DISP_W, STYLE_HEADER_HEIGHT, STYLE_COLOR_HEADER);
|
||||||
_gfx->setFont(&fonts::Font2);
|
setBodyFont();
|
||||||
_gfx->setTextColor(0xFFFF);
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
_gfx->setTextSize(1);
|
_gfx->setCursor(STYLE_SPACING_X, 12);
|
||||||
_gfx->setCursor(10, 12);
|
|
||||||
_gfx->printf("KLUBHAUS");
|
_gfx->printf("KLUBHAUS");
|
||||||
|
|
||||||
// WiFi status
|
// WiFi status
|
||||||
@@ -394,7 +407,7 @@ void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
|||||||
_gfx->printf("WiFi:%s", state.wifiSsid.length() > 0 ? "ON" : "OFF");
|
_gfx->printf("WiFi:%s", state.wifiSsid.length() > 0 ? "ON" : "OFF");
|
||||||
|
|
||||||
// Get tile layouts from library helper
|
// Get tile layouts from library helper
|
||||||
int tileCount = display.calculateDashboardLayouts(30, 8);
|
int tileCount = display.calculateDashboardLayouts(STYLE_HEADER_HEIGHT, STYLE_TILE_GAP);
|
||||||
const TileLayout* layouts = display.getTileLayouts();
|
const TileLayout* layouts = display.getTileLayouts();
|
||||||
|
|
||||||
const char* tileLabels[] = { "1", "2", "3", "4", "5", "6", "7", "8" };
|
const char* tileLabels[] = { "1", "2", "3", "4", "5", "6", "7", "8" };
|
||||||
@@ -407,34 +420,24 @@ void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
|||||||
int h = lay.h;
|
int h = lay.h;
|
||||||
|
|
||||||
// Tile background
|
// Tile background
|
||||||
_gfx->fillRoundRect(x, y, w, h, 8, 0x0220);
|
_gfx->fillRoundRect(x, y, w, h, STYLE_TILE_RADIUS, 0x0220);
|
||||||
|
|
||||||
// Tile border
|
// Tile border
|
||||||
_gfx->drawRoundRect(x, y, w, h, 8, 0xFFFF);
|
_gfx->drawRoundRect(x, y, w, h, STYLE_TILE_RADIUS, STYLE_COLOR_FG);
|
||||||
|
|
||||||
// Tile label
|
// Tile label
|
||||||
_gfx->setTextColor(0xFFFF);
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
_gfx->setTextSize(2);
|
setBodyFont();
|
||||||
_gfx->setCursor(x + w / 2 - 10, y + h / 2 - 10);
|
_gfx->setCursor(x + w / 2 - 30, y + h / 2 - 10);
|
||||||
_gfx->print(tileLabels[i]);
|
_gfx->print(tileLabels[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverGFX::updateHint(int x, int y, bool active) {
|
void DisplayDriverGFX::drawDebugTouch(int x, int y) {
|
||||||
if(!_gfx)
|
if(!_gfx)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
static uint32_t lastTime = 0;
|
const int size = 20;
|
||||||
uint32_t now = millis();
|
_gfx->drawLine(x - size, y, x + size, y, TFT_RED);
|
||||||
if(now - lastTime < 100)
|
_gfx->drawLine(x, y - size, x, y + size, TFT_RED);
|
||||||
return;
|
|
||||||
lastTime = now;
|
|
||||||
|
|
||||||
// active=true: faster pulse (500ms), active=false: slower pulse (2000ms)
|
|
||||||
float period = active ? 500.0f : 2000.0f;
|
|
||||||
float t = fmodf(now, period) / period;
|
|
||||||
uint8_t v = static_cast<uint8_t>(30.0f + 30.0f * sinf(t * 2.0f * 3.14159f));
|
|
||||||
uint16_t col = _gfx->color565(v, v, v);
|
|
||||||
|
|
||||||
_gfx->drawCircle(x, y, 50, col);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,17 @@ public:
|
|||||||
|
|
||||||
TouchEvent readTouch() override;
|
TouchEvent readTouch() override;
|
||||||
HoldState updateHold(unsigned long holdMs) override;
|
HoldState updateHold(unsigned long holdMs) override;
|
||||||
void updateHint(int x, int y, bool active) override;
|
void drawDebugTouch(int x, int y) override;
|
||||||
|
|
||||||
int width() override;
|
int width() override;
|
||||||
int height() override;
|
int height() override;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
void setTitleFont() override;
|
||||||
|
void setBodyFont() override;
|
||||||
|
void setLabelFont() override;
|
||||||
|
void setDefaultFont() override;
|
||||||
|
|
||||||
// Transform touch coordinates (handles rotated touch panels)
|
// Transform touch coordinates (handles rotated touch panels)
|
||||||
void transformTouch(int* x, int* y) override;
|
void transformTouch(int* x, int* y) override;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FQBN="esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=enabled,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB"
|
FQBN="esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=enabled,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB"
|
||||||
PORT="/dev/ttyACM0"
|
PORT="/dev/ttyACM0"
|
||||||
LIBS="--libraries ~/Arduino/libraries/LovyanGFX"
|
LIBS="--libraries ~/Arduino/libraries/LovyanGFX"
|
||||||
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLGFX_USE_V1"
|
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLGFX_USE_V1 -DLOCAL_SECRETS"
|
||||||
|
|||||||
@@ -38,3 +38,22 @@
|
|||||||
// ── GT911 Touch ──
|
// ── GT911 Touch ──
|
||||||
#define GT911_ADDR 0x5D
|
#define GT911_ADDR 0x5D
|
||||||
// #define TOUCH_INT -1
|
// #define TOUCH_INT -1
|
||||||
|
|
||||||
|
// ── Style Constants (CSS-like) ────────────────────────────────────────
|
||||||
|
// Spacing
|
||||||
|
#define STYLE_SPACING_X 10
|
||||||
|
#define STYLE_SPACING_Y 10
|
||||||
|
#define STYLE_HEADER_HEIGHT 45
|
||||||
|
#define STYLE_TILE_GAP 8
|
||||||
|
#define STYLE_TILE_PADDING 16
|
||||||
|
#define STYLE_TILE_RADIUS 8
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
#define STYLE_COLOR_BG 0x001030 // Dark blue
|
||||||
|
#define STYLE_COLOR_HEADER 0x1A1A // Dark gray
|
||||||
|
#define STYLE_COLOR_FG TFT_WHITE
|
||||||
|
#define STYLE_COLOR_ALERT TFT_RED
|
||||||
|
#define STYLE_COLOR_TILE_1 0x0280 // Green
|
||||||
|
#define STYLE_COLOR_TILE_2 0x0400 // Dark green
|
||||||
|
#define STYLE_COLOR_TILE_3 0x0440 // Teal
|
||||||
|
#define STYLE_COLOR_TILE_4 0x0100 // Dark red
|
||||||
|
|||||||
@@ -139,51 +139,9 @@ public:
|
|||||||
|
|
||||||
HoldState updateHold(unsigned long ms) { return _drv ? _drv->updateHold(ms) : HoldState {}; }
|
HoldState updateHold(unsigned long ms) { return _drv ? _drv->updateHold(ms) : HoldState {}; }
|
||||||
|
|
||||||
void updateHint(int x, int y, bool active) {
|
void drawDebugTouch(int x, int y) {
|
||||||
if(_drv)
|
if(_drv)
|
||||||
_drv->updateHint(x, y, active);
|
_drv->drawDebugTouch(x, y);
|
||||||
}
|
|
||||||
|
|
||||||
/// Show touch feedback - highlights the tile at given coordinates
|
|
||||||
/// Returns true if a valid tile is being touched
|
|
||||||
bool showTouchFeedback(int x, int y) {
|
|
||||||
if(!_drv || _gridCols <= 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Transform touch coordinates
|
|
||||||
_drv->transformTouch(&x, &y);
|
|
||||||
|
|
||||||
int headerH = 30;
|
|
||||||
if(y < headerH)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Calculate which cell
|
|
||||||
int cellW = _drv->width() / _gridCols;
|
|
||||||
int cellH = (_drv->height() - headerH) / _gridRows;
|
|
||||||
|
|
||||||
int col = x / cellW;
|
|
||||||
int row = (y - headerH) / cellH;
|
|
||||||
|
|
||||||
if(col < 0 || col >= _gridCols || row < 0 || row >= _gridRows)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Find which tile is at this position
|
|
||||||
for(int i = 0; i < _tileCount; i++) {
|
|
||||||
const TileLayout& lay = _layouts[i];
|
|
||||||
if(lay.col <= col && col < lay.col + lay.cols && lay.row <= row
|
|
||||||
&& lay.row + lay.rows > row) {
|
|
||||||
// Found the tile - draw highlight via driver
|
|
||||||
_drv->updateHint(lay.x, lay.y, true); // active=true means show feedback
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear touch feedback
|
|
||||||
void clearTouchFeedback() {
|
|
||||||
if(_drv)
|
|
||||||
_drv->updateHint(0, 0, false); // active=false means clear
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if current position is still in same tile as touch-down
|
/// Check if current position is still in same tile as touch-down
|
||||||
|
|||||||
@@ -330,18 +330,17 @@ int DoorbellLogic::handleTouch(const TouchEvent& evt) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show touch feedback on press
|
// Draw debug crosshair at touch point
|
||||||
|
#ifdef DEBUG_MODE
|
||||||
if(_state.screen == ScreenID::DASHBOARD) {
|
if(_state.screen == ScreenID::DASHBOARD) {
|
||||||
_display->showTouchFeedback(evt.x, evt.y);
|
_display->drawDebugTouch(evt.x, evt.y);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle release - fire action if same tile
|
// Handle release - fire action if same tile
|
||||||
if(evt.released) {
|
if(evt.released) {
|
||||||
// Clear feedback
|
|
||||||
_display->clearTouchFeedback();
|
|
||||||
|
|
||||||
if(_state.screen == ScreenID::DASHBOARD) {
|
if(_state.screen == ScreenID::DASHBOARD) {
|
||||||
// Only fire action if finger stayed on same tile
|
// Only fire action if finger stayed on same tile
|
||||||
if(evt.downX >= 0 && _display->isSameTile(evt.downX, evt.downY, evt.x, evt.y)) {
|
if(evt.downX >= 0 && _display->isSameTile(evt.downX, evt.downY, evt.x, evt.y)) {
|
||||||
@@ -405,10 +404,6 @@ bool DoorbellLogic::updateHold(const TouchEvent& evt) {
|
|||||||
holdStartY = evt.y;
|
holdStartY = evt.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(holdStartX >= 0) {
|
|
||||||
_display->updateHint(holdStartX, holdStartY, h.active);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,16 @@ public:
|
|||||||
// ── Touch ──
|
// ── Touch ──
|
||||||
virtual TouchEvent readTouch() = 0;
|
virtual TouchEvent readTouch() = 0;
|
||||||
virtual HoldState updateHold(unsigned long holdMs) = 0;
|
virtual HoldState updateHold(unsigned long holdMs) = 0;
|
||||||
virtual void updateHint(int x, int y, bool active) = 0;
|
virtual void drawDebugTouch(int x, int y) { /* default: no-op */ }
|
||||||
virtual int width() = 0;
|
virtual int width() = 0;
|
||||||
virtual int height() = 0;
|
virtual int height() = 0;
|
||||||
|
|
||||||
|
// ── Fonts ──
|
||||||
|
virtual void setTitleFont() = 0; // Large titles (KLUBHAUS, ALERT)
|
||||||
|
virtual void setBodyFont() = 0; // Normal text (status, body)
|
||||||
|
virtual void setLabelFont() = 0; // Small text (hints, captions)
|
||||||
|
virtual void setDefaultFont() = 0; // Reset to default font
|
||||||
|
|
||||||
// ── Touch transform (for rotated panels) ──
|
// ── Touch transform (for rotated panels) ──
|
||||||
virtual void transformTouch(int* x, int* y) { /* default: no transform */ }
|
virtual void transformTouch(int* x, int* y) { /* default: no transform */ }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,3 +7,4 @@
|
|||||||
#include "IDisplayDriver.h"
|
#include "IDisplayDriver.h"
|
||||||
#include "NetManager.h"
|
#include "NetManager.h"
|
||||||
#include "ScreenState.h"
|
#include "ScreenState.h"
|
||||||
|
#include "Style.h"
|
||||||
|
|||||||
62
libraries/KlubhausCore/src/Style.h
Normal file
62
libraries/KlubhausCore/src/Style.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct Layout {
|
||||||
|
uint16_t x, y, w, h;
|
||||||
|
|
||||||
|
Layout()
|
||||||
|
: x(0)
|
||||||
|
, y(0)
|
||||||
|
, w(0)
|
||||||
|
, h(0) { }
|
||||||
|
Layout(uint16_t x_, uint16_t y_, uint16_t w_, uint16_t h_)
|
||||||
|
: x(x_)
|
||||||
|
, y(y_)
|
||||||
|
, w(w_)
|
||||||
|
, h(h_) { }
|
||||||
|
|
||||||
|
static Layout fullScreen(uint16_t w, uint16_t h) { return Layout(0, 0, w, h); }
|
||||||
|
|
||||||
|
static Layout header(uint16_t screenW, uint16_t headerH, uint16_t padding = 10) {
|
||||||
|
return Layout(0, 0, screenW, headerH);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Layout content(
|
||||||
|
uint16_t screenW, uint16_t screenH, uint16_t headerH, uint16_t padding = 10) {
|
||||||
|
return Layout(
|
||||||
|
padding, headerH + padding, screenW - 2 * padding, screenH - headerH - 2 * padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Layout tile(uint8_t col, uint8_t row, uint8_t cols, uint8_t rows, uint16_t contentW,
|
||||||
|
uint16_t contentH, uint8_t gap = 8) {
|
||||||
|
uint16_t tileW = (contentW - (cols - 1) * gap) / cols;
|
||||||
|
uint16_t tileH = (contentH - (rows - 1) * gap) / rows;
|
||||||
|
uint16_t x = col * (tileW + gap);
|
||||||
|
uint16_t y = row * (tileH + gap);
|
||||||
|
return Layout(x, y, tileW, tileH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TileMetrics {
|
||||||
|
uint16_t tileW;
|
||||||
|
uint16_t tileH;
|
||||||
|
uint16_t gap;
|
||||||
|
uint8_t cols;
|
||||||
|
uint8_t rows;
|
||||||
|
|
||||||
|
static TileMetrics calculate(
|
||||||
|
uint16_t contentW, uint16_t contentH, uint8_t cols, uint8_t rows, uint8_t gap = 8) {
|
||||||
|
TileMetrics m;
|
||||||
|
m.cols = cols;
|
||||||
|
m.rows = rows;
|
||||||
|
m.gap = gap;
|
||||||
|
m.tileW = (contentW - (cols - 1) * gap) / cols;
|
||||||
|
m.tileH = (contentH - (rows - 1) * gap) / rows;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout get(uint8_t col, uint8_t row) const {
|
||||||
|
return Layout(col * (tileW + gap), row * (tileH + gap), tileW, tileH);
|
||||||
|
}
|
||||||
|
};
|
||||||
16
mise.toml
16
mise.toml
@@ -21,18 +21,18 @@ pkl = "latest"
|
|||||||
[tasks.compile]
|
[tasks.compile]
|
||||||
description = "Compile (uses BOARD env var)"
|
description = "Compile (uses BOARD env var)"
|
||||||
depends = ["gen-compile-commands"]
|
depends = ["gen-compile-commands"]
|
||||||
run = """
|
run = '''
|
||||||
source ./boards/$BOARD/board-config.sh
|
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
|
arduino-cli compile --fqbn "$FQBN" --libraries ./libraries $LIBS --build-property "compiler.cpp.extra_flags=$OPTS" --warnings default ./boards/$BOARD
|
||||||
"""
|
'''
|
||||||
|
|
||||||
[tasks.upload]
|
[tasks.upload]
|
||||||
description = "Upload (uses BOARD env var)"
|
description = "Upload (uses BOARD env var)"
|
||||||
depends = ["compile", "kill"]
|
depends = ["kill"]
|
||||||
run = """
|
run = '''
|
||||||
source ./boards/$BOARD/board-config.sh
|
source ./boards/$BOARD/board-config.sh
|
||||||
arduino-cli upload --fqbn $FQBN --port $PORT ./boards/$BOARD
|
arduino-cli upload --fqbn "$FQBN" --port "$PORT" ./boards/$BOARD
|
||||||
"""
|
'''
|
||||||
|
|
||||||
[tasks.monitor-raw]
|
[tasks.monitor-raw]
|
||||||
depends = ["kill"]
|
depends = ["kill"]
|
||||||
@@ -86,7 +86,7 @@ exit 0
|
|||||||
description = "Monitor agent with JSON log + command pipe (Python-based)"
|
description = "Monitor agent with JSON log + command pipe (Python-based)"
|
||||||
depends = ["kill"]
|
depends = ["kill"]
|
||||||
run = """
|
run = """
|
||||||
python3 ./scripts/monitor-agent.py "$BOARD"
|
python3 ./scripts/monitor-agent.py "$BOARD" &
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[tasks.log-tail]
|
[tasks.log-tail]
|
||||||
@@ -215,4 +215,4 @@ echo "[OK] Generated .crush.json with FQBN: $FQBN"
|
|||||||
run = "git add .; lumen draft | git commit -F - "
|
run = "git add .; lumen draft | git commit -F - "
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
BOARD = "esp32-s3-lcd-43"
|
BOARD = "esp32-32e-4"
|
||||||
|
|||||||
Reference in New Issue
Block a user