# Next Session: Pi-Local "Graceful Wander" + Push ESTOP Notifications

**Status:** deferred from session that implemented Hailo L1 V2
(`docs/NEXT-SESSION-HAILO-ACTIVATION-V2.md`). The L1 reflex protects against
collisions; this session would ADD the autonomous behavior the perspective
doc originally promised: "graceful wander, not 2-second freeze" when Annie/Panda
are unreachable.

---

## What this session covers

Three explicitly-deferred follow-ups bundled into one Pi-local autonomy plan:

1. **C4 — Pi-local graceful-wander loop (5–10 lines, learning-mode candidate)**
   When Annie has been silent for 5 s **AND** `safe_forward == True` per the
   updated `/obstacles` (now respects `_hardware_estop` per BEAR-12), drive
   forward at low speed (~25 PWM) for short pulses. Stop instantly the moment
   any safety gate flips or Annie reconnects. Turns the WiFi-cliff freeze
   into a slow exploratory drift while preserving every L1 guarantee.

2. **PUSH-23 — Push-based ESTOP notifications to Titan/Panda**
   Today the L2 nav loop polls `/obstacles` ~1 Hz; an ESTOP that fires
   between polls is invisible to L2 for up to 1 s. With BEAR-12 now in place
   (`safe_forward = not _hardware_estop.is_set() and ...`), L2 polling is
   correct but slow. Add a WebSocket `/events` channel from Pi to Titan that
   pushes `safety.estop_latched` and `safety.estop_cleared` events with <200 ms
   latency, so Annie can re-plan without waiting for the next poll.

3. **STOP-Scope — Pi-local emergency-stop hotword/button**
   Voice "STOP!" over phone/WhatsApp routes through L2 (`POST /drive
   action=stop`), which has 2–3 s latency in degraded WiFi. Pi has no mic;
   nearest equivalents are (a) a physical e-stop button wired into a Pi GPIO
   with an interrupt handler, (b) a BLE key-fob the user wears in-pocket.
   Hardware decision required first. Out of scope for prior session because
   no parts on hand; flagged here as the natural place to revisit when the
   user has decided which form factor they want.

---

## Plan (sketch — flesh out in session)

### Phase A — Background reading

- `services/turbopi-server/safety.py` — review `_safety_safe_for_clear` /
  `_lidar_safe_for_clear` to understand the ground-truth gate Pi-local autonomy
  must respect.
- `services/turbopi-server/main.py:_safety_estop_callback` —
  C6 race-guard pattern; the wander loop must NOT touch motors while
  `_motor_lock.locked()`.
- `services/annie-voice/robot_tools.py` (`navigate_robot`) — current L2 caller
  pattern; understand what Annie expects from `/obstacles` and `/drive`.

### Phase B — C4 autonomy loop (Pi-local)

- New module `services/turbopi-server/wander.py` (~80 lines):
  - `WanderController` thread, gated by `enable_wander: bool` config + env var
  - Subscribes to `_last_command_time` (already a global in `main.py`)
  - When `now - _last_command_time > 5.0` AND `_hardware_estop.is_set() is False`
    AND forward gate (yolo+sonar+lidar) clear: dispatch a `_drive_sync(_, "forward", 25)`
    pulse for 0.3 s, then stop, settle 0.5 s, repeat.
  - Cancel immediately on: ESTOP fire, new `/drive` command from L2, or wander
    disabled via `/drive/wander/disable`.
  - Maintain `_motor_lock` discipline: acquire before each pulse, release after stop.
- New `/drive/wander/{enable,disable,status}` endpoints in `main.py`.
- New `wander_active` field in `/health`.

**User contribution candidate:** the wander pattern itself — pulse duration,
turn-when-blocked behavior, "spiral outward vs random walk" exploration policy.
This is where the personality of the wander shows up.

### Phase C — PUSH-23 push-events channel

- Add `aiohttp` or `fastapi.WebSocket` `/events` endpoint on Pi.
- Push event when `_hardware_estop.set()` and when `_safety_estop_clear_callback`
  fires successfully. Payload: `{"type":"estop","latched":true,"reason":"...","ts":...}`.
- Update `services/annie-voice/` to subscribe at startup (background task);
  on event, invalidate any in-flight nav decision and re-plan.
- Backwards-compat: poll-based `/obstacles` stays intact; WebSocket is purely
  an early-warning fast-path, not a replacement.
- Pre-mortem: WebSocket auto-reconnect, missed events during L2 restart,
  duplicate events on reconnect (idempotent consumer required).

### Phase D — STOP-Scope (hardware)

- Pre-meeting: user picks form factor (button vs BLE fob).
- If button: GPIO pin selection, kernel device tree overlay, debouncing,
  interrupt-driven Python hook into `_safety_estop_callback("HARDWARE BUTTON")`.
- If BLE fob: `bluetoothctl` pairing, `bleak` Python lib, fob-button event
  → same callback. Battery monitoring on the fob (low-battery → loud warn).
- **Both options:** the button/fob ONLY fires ESTOP — it never clears. Clear
  requires the L1 auto-clear path (sensors confirm safe).

### Phase E — Validation

- Wander golden-path: kill Annie, observe robot drives forward in short pulses,
  stops near obstacle. Recover Annie → wander stops within next pulse boundary.
- WiFi-drop test re-run: with C4 active, the test now expects "robot wanders
  during outage, holds position when wander would collide" — not the original
  "robot freezes" behavior.
- PUSH-23 test: simulate detection, measure end-to-end latency from Pi-side
  ESTOP fire to Annie observing the event. Pass criteria: ≤ 200 ms p95.
- STOP-Scope test: physical button press during forward drive — measure time
  to motor zero. Pass criteria: ≤ 100 ms (hardware-interrupt-fast).

---

## Out of scope for this session

- SLAM-aware wander (use SLAM map to bias direction). Current SLAM is local;
  wander is also local — keep them decoupled until both stabilize.
- Battery-aware wander (stop wandering below 20% charge). Battery sensing
  is already broken (no voltage divider); fix that first.
- Multi-room exploration (open doors, climb thresholds). TurboPi is single-room.

---

## Open questions for user before starting

1. **Wander aesthetic — random walk or expanding spiral?** Random feels
   organic but may revisit the same spot. Spiral covers ground systematically
   but is mechanical. (Frame: which one feels more "Annie"?)
2. **STOP-Scope hardware — button or BLE fob?** Button is cheaper and faster
   (interrupt-driven, sub-ms); fob is more lifestyle (always on user, even
   in another room). Decision affects Phase D scope significantly.
3. **PUSH-23 priority — does the 1 Hz polling delay actually hurt today?**
   Worth measuring before building. If L2 nav is decision-bound (Gemma 4 takes
   1.5 s/decision), the polling delay is in the noise.

---

## Files this session would touch

- `services/turbopi-server/wander.py` (NEW)
- `services/turbopi-server/main.py` (wander endpoints, /events WebSocket)
- `services/turbopi-server/test_wander.py` (NEW)
- `services/annie-voice/event_subscriber.py` (NEW — WebSocket consumer)
- `services/turbopi-server/pi-files/` (GPIO interrupt handler, if button option)
- `docs/RESOURCE-REGISTRY.md` (if BLE fob added — small power budget entry)
- `docs/perspectives-vlm-primary-hybrid-nav-v3.html` (Lens 04 — wander now
  closes the WiFi-cliff gap that L1 alone could not)

---

## Done when

- `/obstacles.safe_forward == True` for ≥ 5 s AND Annie silent for ≥ 5 s →
  robot starts gentle forward pulses
- ESTOP fires during wander pulse → robot stops within one pulse boundary
  (≤ 0.5 s)
- Annie sends `/drive` command → wander cancels immediately
- WebSocket `/events` end-to-end ESTOP latency p95 ≤ 200 ms (PUSH-23)
- Hardware STOP button (or BLE fob) triggers ESTOP within 100 ms (STOP-Scope)
- Updated WiFi-hiccup scene in both day-in-life pages reflects "Annie wanders"
  not "Annie freezes" — bidirectional sync
