Pure wire codec for the Improv Wi-Fi BLE protocol.
No D-Bus, no processes — every byte the GATT server reads or notifies is built
and parsed here, so the protocol is 100% host-testable. Higher layers
(Improv and its GATT server) only move opaque binaries.
Wire format (locked against improv-wifi.com/ble + improv-wifi/sdk-js)
The RPC command/result characteristics carry framed packets:
[command_id] [data_length] [data … data_length bytes] [checksum]checksum is the low byte of the sum of every preceding byte in the frame
(command, length, and all data bytes). The current-state, error-state and
capabilities characteristics each carry a single bare byte (no framing).
Submit Wi-Fi (0x01) data payload
[ssid_len] [ssid …] [pwd_len] [pwd …]Open networks send pwd_len = 0.
Identify (0x02) / Device Info (0x03)
Both carry no data ([cmd][0x00][cs]). Identify has no RPC result — the
device just does something physically observable. Device Info replies with one
RPC result carrying four length-prefixed strings: firmware name, firmware
version, hardware variant, device name.
Request scanned networks (0x04)
Command has no data ([0x04][0x00][cs]). The reply is sent as one RPC-result
notification per network, each carrying three strings [ssid, rssi, auth],
followed by a final empty result (data_len 0) that terminates the list.
Capabilities
The capabilities byte is derived from what the host configures (see
capabilities/1): bit2 (0x04, scan-wifi) is always set — the command is
built in, and it's what makes improv-wifi.com show the network dropdown.
bit0 (0x01) is set iff an identify callback is configured, bit1 (0x02)
iff device-info strings are provided. bit3 (hostname) is unsupported.
Summary
Functions
Capabilities characteristic value (single byte), derived from what the host
configures: bit2 (scan-wifi) is always set — the command is built in;
bit0 iff identify?: true; bit1 iff device_info?: true. Pure.
Map of the five characteristic roles to their UUIDs:
:current_state, :error_state, :rpc_command, :rpc_result, :capabilities.
Checksum of a complete frame without its trailing checksum byte: the low byte of the sum of every byte. Exposed for framing helpers/tests.
Decode an inbound RPC-command frame written to the rpc-command characteristic.
Command id for a device-info result frame.
Encode an error-state value (single byte, no framing).
Encode a framed RPC result for command_id carrying strings.
Encode a current-state value (single byte, no framing).
Encode one Wi-Fi network entry as a scan RPC-result notification:
three strings [ssid, rssi, auth] where rssi is rendered as a signed
decimal and secured becomes "YES"/"NO". Terminate the list with
encode_rpc_result(0x04, []).
Command id of the identify command.
Command id for a request-scanned-networks result frame.
The Improv primary service UUID.
Command id for a submit-wifi result frame.
Types
Functions
Capabilities characteristic value (single byte), derived from what the host
configures: bit2 (scan-wifi) is always set — the command is built in;
bit0 iff identify?: true; bit1 iff device_info?: true. Pure.
The advertisement's ServiceData capabilities byte MUST carry this same derived value — derive once and share (the supervisor does this).
Map of the five characteristic roles to their UUIDs:
:current_state, :error_state, :rpc_command, :rpc_result, :capabilities.
Checksum of a complete frame without its trailing checksum byte: the low byte of the sum of every byte. Exposed for framing helpers/tests.
@spec decode_command(binary()) :: command() | decode_error()
Decode an inbound RPC-command frame written to the rpc-command characteristic.
Validates structure and checksum before dispatching. Returns the decoded
command/0, or {:error, reason}:
:invalid— too short, length field inconsistent, or a malformed submit-wifi payload (maps to Improv errorinvalid RPC packet).:bad_checksum— checksum mismatch (alsoinvalid RPC packet).:unknown_command— well-formed frame for a command we don't implement.
@spec device_info_command() :: byte()
Command id for a device-info result frame.
Encode an error-state value (single byte, no framing).
Encode a framed RPC result for command_id carrying strings.
Each string is length-prefixed; an empty list yields a bare [cmd][0][cs]
frame (the terminator for a scan, or a result with no payload).
Encode a current-state value (single byte, no framing).
Encode one Wi-Fi network entry as a scan RPC-result notification:
three strings [ssid, rssi, auth] where rssi is rendered as a signed
decimal and secured becomes "YES"/"NO". Terminate the list with
encode_rpc_result(0x04, []).
@spec identify_command() :: byte()
Command id of the identify command.
@spec request_networks_command() :: byte()
Command id for a request-scanned-networks result frame.
@spec service_uuid() :: String.t()
The Improv primary service UUID.
@spec submit_wifi_command() :: byte()
Command id for a submit-wifi result frame.