Files
tactility/AGENTS.md
Adolfo Reyna 12035f2f39 chore: move firmware skills in-repo + AGENTS.md
- 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
2026-07-17 09:51:08 -04:00

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, env idf5.3_py3.9_env at ~/.espressif/python_env/idf5.3_py3.9_env
  • Full clean via idf.py fullcleanrm -rf build is 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 — check grep 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/ not managed_components/ else hash mismatch. Mono conversion luma=(77R+150G+29B)>>8 threshold (no Bayer dithering). Default theme DefaultDarkMono hides focus UI (breaks 2-button nav).
  • Settings persistence: Never hardcode /data/... — use getUserDataPath() which is /sdcard/tactility when storage.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 via startMcpScreensaver(). WebServer + DevService ctrl_port collision + CONFIG_HTTPD_MAX_URI_HANDLERS=20 required.
  • 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 — use pyserial script 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 spec
  • tactility-firmware/references/display-idle.md — tick flow final + charging guard + deadlock 3629ffef
  • tactility-firmware/references/build-env-and-flash.md — Hermes PYTHONPATH fix, wrapper, ADC REQUIRES, idf5.3_py3.10_env pitfall
  • tactility-firmware/references/mcp-system.md — MCP screensaver + video stream 8081/8083 + /api/mcp 404 triage
  • tactility-firmware/references/rlcd-initialization.md — I2C conflict + ES8311 + ST7305 reset + wake logic
  • tactility-firmware/references/es3c28p-battery-debug.md — why battery N/A, PowerDevice add, no-exceptions pitfall
  • tactility-bluetooth/references/mouse-report-parsing.md — dual-heuristic code + test vectors
  • tactility-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').