- Migrates tactility-firmware and tactility-bluetooth from ~/.hermes/skills/ into .claude/skills/ for repo co-location - Adds AGENTS.md with local workstation context, board table, board-direct build rule, Hermes PYTHONPATH pitfall, common Tactility pitfalls, and in-repo skill index Refs: personal Gitea mirror
6.2 KiB
AGENTS.md — Tactility Firmware
This file is for AI agents (and humans) working in this repo. It documents local workflow, owned hardware, and in-repo skills.
Repo
Tactility OS firmware — C++23, ESP-IDF 5.3, LVGL, FreeRTOS, NimBLE. Supports 40+ boards via Devices/<id>/.
Upstream: https://github.com/TactilityProject/Tactility
Personal Gitea mirror: https://git.reynafamily.com/adolforeyna/tactility (personal remote)
Branch: feature/waveshare-esp32-s3-rlcd (active dev), main tracks upstream.
Local Workstation
- Host: M2 Air (14,2) macOS 26.5.2
- Project:
/Users/adolforeyna/Projects/Tactility/firmware - ESP-IDF:
/Users/adolforeyna/esp/esp-idf, envidf5.3_py3.9_envat~/.espressif/python_env/idf5.3_py3.9_env - Full clean via
idf.py fullclean—rm -rf buildis blocked by Hermes env guard.
Owned Boards
| Board | ID | Port / IP | Notes |
|---|---|---|---|
| ES3C28P 2.8" color 240x320 IPS | es3c28p |
/dev/cu.usbmodem101 (USB) |
16MB flash, OCT PSRAM 120MHz, SD, BAT_ADC GPIO9 ADC1_CH8 200K/200K divider, TP4054 charger, CHRG LED only (IsCharging via heuristic) |
| Waveshare RLCD 4.2" ST7305 mono 400x300 | waveshare-esp32-s3-rlcd |
/dev/cu.usbmodem1101 (USB) |
Reflective, DefaultDark theme, mono thresh 60 default (user tested), no Bayer, fontSize 18+invert, 2-button Nav (KEY=GPIO18 next, BOOT=GPIO0 select) |
| ES3C28P x2 WiFi | es3c28p |
192.168.68.112 + 192.168.68.111 |
OS 0.8.0-dev, SDK 0.8.0-dev |
When user says "color board" they mean ES3C28P. When they say "ending in 112" they mean WiFi board
.112. Don't flash RLCD firmware onto color board — checkgrep CONFIG_TT_DEVICE_ID sdkconfig.
Build — Board-Direct Rule
User rule (2026-07-12): "Don't compile for simulator is a waste of time, build for the board directly."
For any driver/display/power/BT change, build for real hardware, not POSIX simulator. Simulator hides HAL bugs (ST7305 invert, GPIO conflicts, BT stack).
Correct env wrapper (Hermes PYTHONPATH pollution fix)
Hermes desktop runs Python 3.11 with pydantic_core .so that leaks into IDF 3.9 venv and breaks idf.py and tactility.py (TypeError: | or ModuleNotFoundError: pydantic_core).
cd firmware
unset PYTHONPATH; unset PYTHONHOME
export IDF_PYTHON_ENV_PATH=/Users/adolforeyna/.espressif/python_env/idf5.3_py3.9_env
source /Users/adolforeyna/esp/esp-idf/export.sh
python device.py <board-id> # es3c28p or waveshare-esp32-s3-rlcd
idf.py fullclean # required when switching envs or font size
idf.py -p /dev/cu.usbmodem101 flash # color
idf.py -p /dev/cu.usbmodem1101 flash # RLCD
idf.py -p /dev/cu.usbmodem101 flash monitor
Simulator (only when explicitly requested, no IDF):
env -u ESP_IDF_VERSION -u IDF_PATH -u PYTHONPATH cmake -B /tmp/buildsim -G Ninja .
ninja -C /tmp/buildsim
Common Tactility Pitfalls in This Repo
- Board config cleanliness: User prefers clean
Devices/es3c28p/Source/Configuration.cpp— no custom battery logic outside board config. Battery uses native statusbar icon. - ES3C28P battery: GPIO9 BAT_ADC via
adc_oneshot+ curve-fitting, 2x divider. Official spec in~/Downloads/2.8inch_IPS_.../5-原理图_Schematic/+ Example_13.supported: ChargeLevel, BatteryVoltage, IsCharging(heuristic: <500mV or >4350mV → IsCharging=true). - RLCD ST7305 mono:
components/espressif__esp_lvgl_port/notmanaged_components/else hash mismatch. Mono conversionluma=(77R+150G+29B)>>8threshold (no Bayer dithering). Default themeDefaultDark—Monohides focus UI (breaks 2-button nav). - Settings persistence: Never hardcode
/data/...— usegetUserDataPath()which is/sdcard/tactilitywhenstorage.userDataLocation=SD(ES3C28P). Pattern:getSettingsFilePath() = getUserDataPath()+"/settings/<name>.properties". - DisplayIdle/MCP coexistence:
DisplayIdleService::onStart()must NOT early-return on!supportsBacklightDuty()— RLCD needs timer for MCP. MCP manual activation viastartMcpScreensaver(). WebServer + DevServicectrl_portcollision +CONFIG_HTTPD_MAX_URI_HANDLERS=20required. - Two-button nav: KEY first (next/Change), BOOT second (Enter/Select). Blank focus on fresh screen = expected LVGL group behavior, not crash.
- Serial debug: Hermes
terminal(background=true)has no TTY — usepyserialscript to capture 15s logs for Guru Meditation triage.
In-Repo Skills
Skills live under .claude/skills/<name>/ — auto-discovered by Claude Code / Hermes. Each has SKILL.md + references/*.md.
| Skill | When to use |
|---|---|
tactility-firmware |
Power/battery IsCharging, display settings persistence, screensaver DisplayIdle lifecycle, statusbar icons, WebServer/MCP overlay, device PowerDevice drivers (AXP192/AXP2101/M5PM1/EstimatedPower), RLCD init race, LVGL theme/font/threshold, build-env wrapper |
tactility-bluetooth |
BLE HID Host (central) keyboards/mice/combo touchpads (Fosmon B00BX0YKX4), Report ID stripping, mouse report parsing (std vs Logitech 12-bit), NimBLE scan cache nameless/RPA, PUBLIC vs RANDOM addr_type, auto-connect retry loop, hid_host_active guard |
Key references inside skills
tactility-firmware/references/power-system.md— IsCharging matrix + EstimatedPower + ES3C28P GPIO9 spectactility-firmware/references/display-idle.md— tick flow final + charging guard + deadlock3629ffeftactility-firmware/references/build-env-and-flash.md— Hermes PYTHONPATH fix, wrapper, ADC REQUIRES, idf5.3_py3.10_env pitfalltactility-firmware/references/mcp-system.md— MCP screensaver + video stream 8081/8083 + /api/mcp 404 triagetactility-firmware/references/rlcd-initialization.md— I2C conflict + ES8311 + ST7305 reset + wake logictactility-firmware/references/es3c28p-battery-debug.md— why battery N/A, PowerDevice add, no-exceptions pitfalltactility-bluetooth/references/mouse-report-parsing.md— dual-heuristic code + test vectorstactility-bluetooth/references/scan-and-reconnect.md— inclusive cache + addr_type matrix + timer pattern
Load a skill in a session: skill_view(name='tactility-firmware') etc. Reference docs via skill_view(name=..., file_path='references/power-system.md').