diff --git a/_bmad-output/implementation-artifacts/1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill.md b/_bmad-output/implementation-artifacts/1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill.md index 520aad2..9ccd49a 100644 --- a/_bmad-output/implementation-artifacts/1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill.md +++ b/_bmad-output/implementation-artifacts/1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill.md @@ -1,6 +1,6 @@ # Story 1.5: Provisioning Execution — Tile Download, Cache Validation & WiFi Kill -Status: review +Status: done ## Story diff --git a/_bmad-output/implementation-artifacts/deferred-work.md b/_bmad-output/implementation-artifacts/deferred-work.md index 3808828..6e26fae 100644 --- a/_bmad-output/implementation-artifacts/deferred-work.md +++ b/_bmad-output/implementation-artifacts/deferred-work.md @@ -54,3 +54,32 @@ Description: Nominatim geocoding verified in tests with mocks only; real geocodi Story: `1-4-location-resolution-icao-and-address` Category: Technical debt Description: ICAO heuristic (`len(query) == 4 and query.isalpha()`) may misclassify 4-letter words (e.g. "BATH", "YORK") as ICAO codes, causing them to be looked up in `airports.csv` before falling back to Nominatim. Acceptable for MVP given the provisioning context, but noted for future hardening (e.g. validate against a known ICAO prefix list). + +--- + +## Story 1.5: Provisioning Execution — Tile Download, Cache Validation & WiFi Kill + +### [1-5] nmcli / NetworkManager dependency +Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill` +Category: Infrastructure/environment +Description: `nmcli` requires NetworkManager to be installed and running on the Pi; the `wlan0` interface must support managed mode. Raspberry Pi OS Lite uses `dhcpcd` by default — NetworkManager must be installed and enabled before `join_home_wifi()` will work. + +### [1-5] rfkill permission requirement +Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill` +Category: Infrastructure/environment +Description: `rfkill block wifi` requires the process to have permission to block the WiFi interface. The user running the provisioning service must be root or have the `CAP_NET_ADMIN` capability. The systemd unit must be configured accordingly. + +### [1-5] OSM tile download and OpenAIP API runtime verification +Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill` +Category: Runtime verification +Description: OSM tile download, OpenAIP API call, and the full provisioning sequence (WiFi join → tile download → airspace download → validate → write config → rfkill) can only be end-to-end verified on device with real network access. All tests use mocks only. + +### [1-5] provision.py port 80 requires root +Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill` +Category: Infrastructure/environment +Description: `provision.py` calls `app.run(port=80)` which requires root privileges or the `CAP_NET_BIND_SERVICE` capability to bind to a port below 1024. The systemd unit for the provisioning service must run as root or be granted the appropriate capability. + +### [1-5] Synchronous POST /submit — browser waits during provisioning +Story: `1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill` +Category: Technical debt +Description: The `POST /submit` handler is fully synchronous — the browser connection stays open while tile download, airspace download, and cache validation complete (potentially 2–5 minutes). This is acceptable for MVP but a streaming response (using `flask.stream_with_context` or a background thread with server-sent events) would improve UX by allowing the browser to render progress feedback without holding an open connection. diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index 268e0e1..a5a652a 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -35,7 +35,7 @@ # - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended) generated: 2026-04-22 -last_updated: 2026-04-22 # 1-5 story file created, marked ready-for-dev +last_updated: 2026-04-22 # 1-5 review complete, epic-1 done project: planeMapper project_key: NOKEY tracking_system: file-system @@ -43,12 +43,12 @@ story_location: _bmad-output/implementation-artifacts development_status: # Epic 1: Device Setup & Provisioning - epic-1: in-progress + epic-1: done 1-1-project-scaffold-and-verified-entry-points: done 1-2-configuration-read-write-wipe: done 1-3-wifi-hotspot-and-captive-portal-form: done 1-4-location-resolution-icao-and-address: done - 1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill: review + 1-5-provisioning-execution-tile-download-cache-validation-and-wifi-kill: done epic-1-retrospective: optional # Epic 2: Live Radar Display diff --git a/src/planemapper/provisioning/portal.py b/src/planemapper/provisioning/portal.py index 81a5193..47212d4 100644 --- a/src/planemapper/provisioning/portal.py +++ b/src/planemapper/provisioning/portal.py @@ -110,32 +110,33 @@ def _run_provisioning( wifi_ssid: str, wifi_password: str, ) -> str: + wifi.join_home_wifi(wifi_ssid, wifi_password) + tiles.download_and_composite(confirmed_lat, confirmed_lon, radius) + airspace.download(confirmed_lat, confirmed_lon, radius) try: - wifi.join_home_wifi(wifi_ssid, wifi_password) - tiles.download_and_composite(confirmed_lat, confirmed_lon, radius) - airspace.download(confirmed_lat, confirmed_lon, radius) tiles.validate_cache() - config.write( - { - "home_lat": confirmed_lat, - "home_lon": confirmed_lon, - "coverage_radius_nm": int(radius), - "wifi_ssid": wifi_ssid, - "wifi_password": wifi_password, - "provisioned": True, - } - ) - wifi.kill_wifi() - return ( - "
" - "Provisioning failed: {e}. Try again
" + f"Cache validation failed: {e}. " + f"Try again
" ) + config.write( + { + "home_lat": confirmed_lat, + "home_lon": confirmed_lon, + "coverage_radius_nm": int(radius), + "wifi_ssid": wifi_ssid, + "wifi_password": wifi_password, + "provisioned": True, + } + ) + wifi.kill_wifi() + return ( + "" + "