Improv-over-BLE Wi-Fi provisioning manager — the GenServer that ties the protocol codec, GATT server and advertisement into a working subsystem.
Always started on BT targets but disarmed by default. It arms (exports the GATT app + advertisement, starts a session timer) only when the device boots with no network connectivity, and tears itself back down on a 5-minute idle timeout or once provisioning completes.
State machine
:disarmed → :advertising → :connected → :provisioning → :provisionedA failed/timed-out connect reverts :provisioning → :connected and surfaces the
reason via state.error (an error byte to the client) — there is no distinct
:error FSM state.
We always advertise current-state AUTHORIZED until a submit moves us to PROVISIONING; security comes from the no-connectivity arm gate + the session timeout, not from the Improv authorization handshake.
Arm policy
Arm only on a no-connectivity boot, and only once per boot — we do NOT re-arm if connectivity drops later (re-arm requires a reboot). After a session ends (timeout or provisioned) we disarm and stay disarmed.
Timeout reset rule
The 5-minute idle timer resets only on a meaningful state advance (first client connect, a valid submit) — never on arbitrary client activity, so a flooding peer cannot hold the session open forever. An absolute session cap (default 15 minutes) bounds the whole session regardless of activity.
State changes broadcast {:improv_status, status} on the "bluetooth:improv"
PubSub topic (status_topic/0) when a pubsub: is configured, for host UIs.
Wi-Fi scan/apply goes through the :wifi dep (Improv.Wifi by default);
this module routes RPCs to it and pushes the resulting GATT notifications.
Summary
Functions
Returns a specification to start this module under a supervisor.
Map a decoded command (or decode error) to the manager action. Pure — the GenServer executes the returned action. Returns
Map an FSM state to the current-state atom we advertise/notify. Pure.
Current provisioning status: %{state: fsm, error: atom | nil}. Returns the
disarmed shape when the manager isn't running (off-target / subtree down).
PubSub topic carrying {:improv_status, status} messages.
Improv SSID length rule: 1..32 bytes. Pure.
Types
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec command_action(Improv.Protocol.command() | Improv.Protocol.decode_error()) :: {:submit, binary(), binary()} | :scan | :identify | :device_info | {:reject, atom()}
Map a decoded command (or decode error) to the manager action. Pure — the GenServer executes the returned action. Returns:
{:submit, ssid, pwd}for a valid submit,:scanfor request-networks,{:reject, error_atom}for an invalid submit / decode failure.
Map an FSM state to the current-state atom we advertise/notify. Pure.
@spec status(GenServer.server()) :: %{state: fsm(), error: atom() | nil}
Current provisioning status: %{state: fsm, error: atom | nil}. Returns the
disarmed shape when the manager isn't running (off-target / subtree down).
@spec status_topic() :: String.t()
PubSub topic carrying {:improv_status, status} messages.
Improv SSID length rule: 1..32 bytes. Pure.