Improv (improv v0.1.0)

Copy Markdown View Source

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  :provisioned

A 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

fsm()

@type fsm() :: :disarmed | :advertising | :connected | :provisioning | :provisioned

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

command_action(arg)

@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,
  • :scan for request-networks,
  • {:reject, error_atom} for an invalid submit / decode failure.

current_state_atom(arg1)

@spec current_state_atom(fsm()) :: atom()

Map an FSM state to the current-state atom we advertise/notify. Pure.

start_link(opts \\ [])

status(server \\ __MODULE__)

@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).

status_topic()

@spec status_topic() :: String.t()

PubSub topic carrying {:improv_status, status} messages.

valid_ssid?(ssid)

@spec valid_ssid?(binary()) :: boolean()

Improv SSID length rule: 1..32 bytes. Pure.