- Add 02-02-SUMMARY.md: ESP32Transport asyncio DatagramProtocol implementation - Update STATE.md: advance to plan 3/6, 50% overall progress - Update ROADMAP.md: phase 02 in progress (1/6 summaries) - Mark CHR-05 complete in REQUIREMENTS.md
136 lines
5.7 KiB
Markdown
136 lines
5.7 KiB
Markdown
---
|
|
phase: 02-app-core-audio
|
|
plan: "02"
|
|
subsystem: transport
|
|
tags: [asyncio, udp, esp32, datagram-protocol, json, led-control]
|
|
|
|
# Dependency graph
|
|
requires:
|
|
- phase: 01-esp32-firmware
|
|
provides: "UDP JSON protocol spec (docs/protocol.md) — port 4210, v:1 commands, STATUS response format"
|
|
provides:
|
|
- "ESP32Transport: asyncio.DatagramProtocol for fire-and-forget UDP JSON commands to ESP32"
|
|
- "create_esp32_transport(): async factory coroutine returning ready ESP32Transport"
|
|
- "Connection state tracking (connected/disconnected) via STATUS ok responses"
|
|
- "led_sync.transport package with clean re-exports"
|
|
affects: [02-04-scheduler, 02-05-repl, 03-tui]
|
|
|
|
# Tech tracking
|
|
tech-stack:
|
|
added: [pytest-asyncio>=0.23]
|
|
patterns:
|
|
- "asyncio.DatagramProtocol for non-blocking UDP fire-and-forget"
|
|
- "v:1 protocol version injected by transport layer, not callers"
|
|
- "TDD red-green cycle for protocol-critical transport behavior"
|
|
|
|
key-files:
|
|
created:
|
|
- src/led_sync/transport/__init__.py
|
|
- src/led_sync/transport/udp_client.py
|
|
- tests/test_transport.py
|
|
modified:
|
|
- pyproject.toml
|
|
|
|
key-decisions:
|
|
- "D-10: asyncio DatagramProtocol, fire-and-forget sendto() — no blocking anywhere in send path"
|
|
- "D-11: send_status_ping() for optional heartbeat, non-blocking"
|
|
- "D-12: connected=True set only upon STATUS ok response, not upon socket bind"
|
|
- "D-14: Minimal error handling — silent drop on oversized payload, malformed JSON, pre-connection calls"
|
|
- "Protocol version v:1 injected by send_command(), callers never need to include it"
|
|
|
|
patterns-established:
|
|
- "Pattern 1: asyncio.DatagramProtocol subclass for ESP32 UDP transport (fire-and-forget, no awaits in send path)"
|
|
- "Pattern 2: Silent drop pattern — no exceptions from transport layer, only debug/warning logs"
|
|
- "Pattern 3: on_status callback for reactive STATUS response handling without polling"
|
|
|
|
requirements-completed: [CHR-05]
|
|
|
|
# Metrics
|
|
duration: 4min
|
|
completed: 2026-04-03
|
|
---
|
|
|
|
# Phase 02 Plan 02: ESP32 UDP Transport Client Summary
|
|
|
|
**asyncio DatagramProtocol UDP client for ESP32 on port 4210 — fire-and-forget JSON commands with STATUS-based connection tracking**
|
|
|
|
## Performance
|
|
|
|
- **Duration:** 4 min
|
|
- **Started:** 2026-04-03T12:15:55Z
|
|
- **Completed:** 2026-04-03T12:19:45Z
|
|
- **Tasks:** 1 (TDD: test commit + feat commit)
|
|
- **Files modified:** 4
|
|
|
|
## Accomplishments
|
|
|
|
- ESP32Transport implements asyncio.DatagramProtocol with all protocol behaviors matching docs/protocol.md exactly
|
|
- send_command() injects v:1, enforces 512-byte limit, silently drops if pre-connection — all non-blocking
|
|
- STATUS ok responses parsed in datagram_received() to set connected=True and store last_status
|
|
- 15 unit tests covering all public behaviors including async factory and oversized payload drop
|
|
- create_esp32_transport() async factory sends initial STATUS ping for D-12 connection bootstrap
|
|
|
|
## Task Commits
|
|
|
|
1. **RED — Failing tests for ESP32Transport** - `f10fcf0` (test)
|
|
2. **GREEN — ESP32Transport implementation** - `378620e` (feat)
|
|
|
|
## Files Created/Modified
|
|
|
|
- `src/led_sync/transport/udp_client.py` — ESP32Transport class + create_esp32_transport() factory
|
|
- `src/led_sync/transport/__init__.py` — Package marker, re-exports ESP32Transport and create_esp32_transport
|
|
- `tests/test_transport.py` — 15 unit tests covering all protocol behaviors
|
|
- `pyproject.toml` — Added pytest-asyncio to dev deps, asyncio_mode=auto
|
|
|
|
## Decisions Made
|
|
|
|
- Protocol version field v:1 is injected by send_command() — callers never include it (cleaner downstream API)
|
|
- on_status callback parameter in ESP32Transport constructor and create_esp32_transport() factory for reactive status handling without polling
|
|
- Silent drop on pre-connection and oversized payload (no exceptions) matches D-14 fire-and-forget philosophy
|
|
|
|
## Deviations from Plan
|
|
|
|
### Auto-fixed Issues
|
|
|
|
**1. [Rule 3 - Blocking] Added pytest-asyncio dependency for async test support**
|
|
- **Found during:** Task 1 (TDD RED phase)
|
|
- **Issue:** Tests use `@pytest.mark.asyncio` but pytest-asyncio was not in pyproject.toml dev deps; test would fail with UnknownMarkWarning and no async test support
|
|
- **Fix:** `uv add --dev pytest-asyncio` and added `asyncio_mode = "auto"` in `[tool.pytest.ini_options]`
|
|
- **Files modified:** pyproject.toml, uv.lock
|
|
- **Verification:** All 15 tests pass including async factory test
|
|
- **Committed in:** 378620e (Task 1 GREEN commit)
|
|
|
|
**2. [Rule 3 - Blocking] Discovered pyproject.toml and led_sync package already existed**
|
|
- **Found during:** Task 1 setup
|
|
- **Issue:** Plan 02-01 (scaffold) has not been committed to git yet, but the pyproject.toml and src/led_sync/ structure already existed on disk from a previous partial execution
|
|
- **Fix:** Worked with existing files; only added transport package and pytest-asyncio
|
|
- **Files modified:** None (existing structure was sufficient)
|
|
- **Verification:** `uv run python -c "from led_sync.transport import create_esp32_transport"` exits 0
|
|
|
|
---
|
|
|
|
**Total deviations:** 2 auto-fixed (2x Rule 3 blocking)
|
|
**Impact on plan:** Both fixes necessary for test infrastructure and working with existing project state. No scope creep.
|
|
|
|
## Issues Encountered
|
|
|
|
None beyond the deviations documented above.
|
|
|
|
## User Setup Required
|
|
|
|
None — no external service configuration required.
|
|
|
|
## Next Phase Readiness
|
|
|
|
- `from led_sync.transport import create_esp32_transport` is importable and ready for use
|
|
- Scheduler (02-04) and REPL (02-05) can depend on `transport.send_command(dict)` and `transport.connected` interfaces
|
|
- Transport is fully tested with 15 unit tests covering all edge cases
|
|
|
|
## Self-Check: PASSED
|
|
|
|
All created files verified present. Both commits (f10fcf0, 378620e) confirmed in git log.
|
|
|
|
---
|
|
*Phase: 02-app-core-audio*
|
|
*Completed: 2026-04-03*
|