# `Improv.Wifi`
[🔗](https://github.com/bbangert/improv/blob/v0.1.1/lib/improv/wifi.ex#L1)

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.

# `network`

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

# `configure`

```elixir
@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`

```elixir
@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`

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

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

# `redirect_url`

```elixir
@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`

```elixir
@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?`

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

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
