Improv.Wifi (improv v0.1.1)

Copy Markdown View Source

Wi-Fi operations for Improv provisioning: scan for networks, apply submitted credentials, and derive the post-provisioning redirect URL. The seam the Improv manager calls into.

All VintageNet access is guarded (Code.ensure_loaded?) so the module loads on host, and is injectable (:scan_trigger / :vintage_get / :configure_fn) so the pure shaping — the configure map, the AP→network mapping, the secured? rule — is host-tested without a radio. The Wi-Fi interface defaults to "wlan0"; pass ifname: in each call's opts to target another interface.

scan_networks/1 reads the live access_points property (kept fresh by wpa_supplicant) rather than VintageNetWiFi.quick_scan/1, whose fresh-scan + 2 s sleep read an empty list mid-scan on hardware. It also kicks an async VintageNet.scan/1 to refresh the property for the next call.

Summary

Functions

Apply submitted credentials to the Wi-Fi interface. :configure_fn (2-arity (ifname, config) -> term) is injectable for tests.

VintageNet config map for a submitted SSID/password. An empty password yields an open (key_mgmt: :none) network; otherwise WPA-PSK. Pure.

Map a VintageNetWiFi.AccessPoint-shaped value to a network/0. Pure.

The web-UI URL to hand back to the provisioner once joined, or nil if no IPv4 address is bound yet. :vintage_get injectable for tests.

Nearby networks → {:ok, [network]} (empty SSIDs dropped, deduped by SSID keeping the strongest signal), or {:error, reason}.

Whether an AP's :flags indicate a secured network. Pure.

Types

network()

@type network() :: %{ssid: binary(), rssi: integer(), secured: boolean()}

Functions

configure(ssid, password, opts \\ [])

@spec configure(binary(), binary(), keyword()) :: term()

Apply submitted credentials to the Wi-Fi interface. :configure_fn (2-arity (ifname, config) -> term) is injectable for tests.

configure_map(ssid, password)

@spec configure_map(binary(), binary()) :: map()

VintageNet config map for a submitted SSID/password. An empty password yields an open (key_mgmt: :none) network; otherwise WPA-PSK. Pure.

network_from_ap(map)

@spec network_from_ap(map()) :: network()

Map a VintageNetWiFi.AccessPoint-shaped value to a network/0. Pure.

redirect_url(opts \\ [])

@spec redirect_url(keyword()) :: String.t() | nil

The web-UI URL to hand back to the provisioner once joined, or nil if no IPv4 address is bound yet. :vintage_get injectable for tests.

scan_networks(opts \\ [])

@spec scan_networks(keyword()) :: {:ok, [network()]} | {:error, term()}

Nearby networks → {:ok, [network]} (empty SSIDs dropped, deduped by SSID keeping the strongest signal), or {:error, reason}.

Reads the live ["interface", ifname, "wifi", "access_points"] property that wpa_supplicant keeps refreshed via periodic background scans — NOT VintageNetWiFi.quick_scan/1, which triggers a fresh ioctl scan and only waits 2s, so it lands in the empty window mid-scan (HW-found: returned 0 while the property held 31 APs). We also kick an async VintageNet.scan/1 to keep the property fresh for the next request. :vintage_get/:scan_trigger injectable.

secured?(flags)

@spec secured?([atom()] | term()) :: boolean()

Whether an AP's :flags indicate a secured network. Pure.