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
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Combo Keyboard + Touchpad Devices (Fosmon Example)
|
||||
|
||||
## Device
|
||||
Fosmon Mini Bluetooth Keyboard QWERTY + Touchpad, Amazon ASIN B00BX0YKX4
|
||||
Short link from user: https://a.co/d/0fg4vprm → redirects to B00BX0YKX4
|
||||
Compatible with Apple TV, Fire Stick, PS4/5, HTPC, smartphones — cheap BLE HID.
|
||||
|
||||
### BLE HID Report Map Structure (typical)
|
||||
- Top-level Collection 1: Generic Desktop, Usage Keyboard (0x06)
|
||||
- Report ID 1 (0x85 0x01)
|
||||
- Input: mod keys, reserved, 6 keys (8 bytes)
|
||||
- Top-level Collection 2: Generic Desktop, Usage Mouse (0x02)
|
||||
- Report ID 2 (0x85 0x02)
|
||||
- Collection Physical → Input: buttons (5 bits + 3 pad), X, Y, Wheel? → 3-4 bytes
|
||||
- Possibly Consumer (0x0C) for media keys on Report ID 3
|
||||
|
||||
Example hex fragments:
|
||||
```
|
||||
05 01 09 06 A1 01 85 01 ... 95 06 75 08 81 00 C0
|
||||
05 01 09 02 A1 01 85 02 09 01 A1 00 05 09 19 01 29 05 15 00 25 01 75 01 95 05 81 02 75 03 95 01 81 01 05 01 09 30 09 31 09 38 15 81 25 7F 75 08 95 03 81 06 C0 C0
|
||||
```
|
||||
|
||||
### Behavior
|
||||
- Sleeps after ~5-10 min idle, stops advertising
|
||||
- Wakes on any key/touch → sends directed advertising to bonded host for ~30s (RPA, often no complete name, only flags + maybe appearance 0x03C1 keyboard or 0x03C2 mouse or 0x03C0 combo)
|
||||
- Some cheap firmwares use PUBLIC address, others use RANDOM resolvable (RPA rotates every ~15 min)
|
||||
- After wake, expects central to connect quickly; if missed, sleeps again → need periodic scan retry
|
||||
|
||||
### Tactility Specifics
|
||||
- `hid_host_active` atomic in BleCtx prevents simultaneous central connect with name resolution chain `ble_resolve_next_unnamed_peer` (would get BLE_HS_EALREADY)
|
||||
- `getScanResults()` originally filtered nameless → missed directed adv → no auto-connect
|
||||
- `applyReportMapTypes` depth logic only considers depth 0 Collection type; works for Fosmon because collections are top-level, but nested mouse would fail
|
||||
|
||||
### Testing Checklist
|
||||
- Pair → verify Report Map log shows `Report val_handle=... reportId=1 type=0/1 etc` for 2 reports
|
||||
- Move touchpad slowly → cursor should move 1-5 px per packet, not jump 200+
|
||||
- Press keyboard keys → modifier handling (shift) works
|
||||
- Let KB sleep 10 min → press key → Tactility should scan within 3s and reconnect (log `Auto-connect retry timer fired` → `Connecting... addr_type=`)
|
||||
- Manual Connect from Paired screen with unknown addr_type should try PUBLIC then RANDOM fallback and log rc
|
||||
@@ -0,0 +1,46 @@
|
||||
# Mouse Report Parsing for Combo KB+Touchpad
|
||||
|
||||
## Context
|
||||
Session 2026-07-12, user device Fosmon Mini BT Keyboard + Touchpad (Amazon B00BX0YKX4, short link https://a.co/d/0fg4vprm). Field fix for Tactility HID Host.
|
||||
|
||||
### Typical Reports
|
||||
- Keyboard: 8 bytes boot `[mod, 0x00, key0..key5]` optionally prefixed with Report ID 1 → 9 bytes `[0x01, mod, 0, k0..k5]`
|
||||
- Mouse/touchpad: 3-5 bytes `[buttons, x_rel, y_rel, wheel, pan]` optionally `[ID, ...]`
|
||||
- Fosmon: likely `[btn, x, y, wheel]` 4 bytes, Report ID 2 → notify `[0x02, btn, x, y]` or `[btn, x, y, wheel]`
|
||||
- Logitech high-res: 5 bytes packed 12-bit `[btn, ?, x_low, x_hi_nibble|y_low_nibble, y_high]`
|
||||
|
||||
### Old Code Failure
|
||||
```cpp
|
||||
if (len >=5) {
|
||||
raw_x = data[2] | ((data[3] & 0x0F)<<8); // treats wheel as nibble
|
||||
...
|
||||
}
|
||||
```
|
||||
For standard packet `[0x01 btn, 0x05 dx, 0xFE dy (-2), 0x00 wheel, 0x00]` → raw_x = 0xFE | (0x00<<8)=254 → sign-extend → large jump.
|
||||
|
||||
### Fixed Code Snippet
|
||||
```cpp
|
||||
static void hidHostHandleMouseReportInternal(const uint8_t* data, uint16_t len, uint8_t expected_report_id) {
|
||||
const uint8_t *p=data; uint16_t plen=len;
|
||||
if (expected_report_id!=0 && len>=2 && data[0]==expected_report_id) { p=data+1; plen=len-1; }
|
||||
if (plen<3) return;
|
||||
if (plen>=5) {
|
||||
int8_t std_dx=(int8_t)p[1], std_dy=(int8_t)p[2];
|
||||
int16_t raw_x12 = p[2] | ((p[3]&0x0F)<<8); if (raw_x12&0x0800) raw_x12|=0xF000;
|
||||
int16_t raw_y12 = (int16_t)((p[3]>>4)|(p[4]<<4)); if (raw_y12&0x0800) raw_y12|=0xF000;
|
||||
bool std_small = abs(std_dx)<=48 && abs(std_dy)<=48;
|
||||
bool s12_large = abs(raw_x12)>128 || abs(raw_y12)>128;
|
||||
// heuristic: small std + large 12 → std (touchpad)
|
||||
// large std pathological + plausible 12 <=300 → 12-bit (Logitech)
|
||||
} else { btn=p[0]&1; dx=(int8_t)p[1]; dy=(int8_t)p[2]; }
|
||||
}
|
||||
```
|
||||
|
||||
### Keyboard ID Strip
|
||||
Same pattern for keyboardInternal.
|
||||
|
||||
### NOTIFY_RX Dispatch Update
|
||||
Pass `rpt.reportId` to Internal variants. For Unknown type, compute effective_len stripping ID, then route by len >=8 keyboard else >=3 mouse.
|
||||
|
||||
### Files Changed
|
||||
- Tactility/Source/bluetooth/BluetoothHidHost.cpp: added Internal variants, heuristic, ID strip, retry timer `hid_autoconn_retry_timer`, dual addr_type try
|
||||
@@ -0,0 +1,73 @@
|
||||
# BLE Scan & Reconnect Fixes
|
||||
|
||||
## Problem 1 — Nameless Filter Dropped Bonded Keyboards
|
||||
|
||||
### Location
|
||||
`Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble_scan.cpp:ble_gap_disc_event_handler`
|
||||
|
||||
### Old
|
||||
```cpp
|
||||
if (!found && record.name[0] != '\0' && ctx->scan_count < 64) { store }
|
||||
```
|
||||
|
||||
### Fixed
|
||||
```cpp
|
||||
if (!found && ctx->scan_count < 64) {
|
||||
// Store ALL unique advertisers, not only named
|
||||
// Bonded keyboards (e.g. Fosmon combo) may advertise with only Appearance+flags or directed RPA without name
|
||||
ctx->scan_results[ctx->scan_count]=record;
|
||||
ctx->scan_addrs[ctx->scan_count]=disc.addr;
|
||||
ctx->scan_count++;
|
||||
should_publish = true; // publish even nameless for cache
|
||||
}
|
||||
if (found) {
|
||||
// also update addr_type for RPA rotation
|
||||
ctx->scan_addrs[i]=disc.addr;
|
||||
}
|
||||
```
|
||||
|
||||
### Impact
|
||||
- `getScanResults()` now includes unknowns → UI shows "Unknown (aabbcc...)" entries; useful debug. Can filter weak RSSI later.
|
||||
- `cacheScanAddr()` / `getCachedScanAddrType()` now works for nameless adv, so `hidHostConnect` gets correct type.
|
||||
|
||||
## Problem 2 — Addr Type PUBLIC vs RANDOM
|
||||
|
||||
### Symptom
|
||||
`ble_gap_connect` fails rc=2 BLE_HS_EALREADY or other when using PUBLIC for device that uses RANDOM RPA.
|
||||
|
||||
### Fix in BluetoothHidHost.cpp:hidHostConnect
|
||||
- Lookup cache: log type
|
||||
- Try PUBLIC -> if fail, retry RANDOM
|
||||
- If cached type was RANDOM and both fail, retry PUBLIC
|
||||
- Increase timeout 5000 -> 8000ms
|
||||
- Log final `addr_type`
|
||||
|
||||
## Problem 3 — Single Scan Misses Wake Window
|
||||
|
||||
### Symptom
|
||||
Fosmon sleeps, stops advertising. Wakes on keypress with directed adv ~30s. If Tactility scanned just before wake, misses.
|
||||
|
||||
### Fix
|
||||
Add retry timer:
|
||||
```cpp
|
||||
static esp_timer_handle_t hid_autoconn_retry_timer = nullptr;
|
||||
static void hidAutoConnRetryCb(void*) {
|
||||
if (hidHostIsConnected()) return;
|
||||
if (!bluetooth_is_scanning(dev)) bluetooth_scan_start(dev);
|
||||
}
|
||||
```
|
||||
In autoConnectHidHost() when not found but autoConnect peers exist:
|
||||
```cpp
|
||||
esp_timer_start_once(hid_autoconn_retry_timer, 3*1e6);
|
||||
```
|
||||
Stopped in hidHostConnect() when connecting, and on successful ready chain.
|
||||
|
||||
Also `bt_event_bridge` SCAN_FINISHED already calls autoConnectHidHost, so loop: scan -> not found -> schedule 3s -> scan -> etc.
|
||||
|
||||
### Files Changed
|
||||
- esp32_ble_scan.cpp inclusive cache + addr update
|
||||
- BluetoothHidHost.cpp dual addr fallback + retry timer + logging
|
||||
- No change needed in Bluetooth.cpp bt_event_bridge, but benefits from inclusive cache
|
||||
|
||||
## Remaining TODO
|
||||
- Manual disconnect with autoConnect=true still retriggers immediate rescan (TODO in code "Fix auto reconnect if user manually disconnects"). Could require user to toggle Auto-connect switch off, or add 10s cooldown.
|
||||
Reference in New Issue
Block a user