MobBluetooth (mob_bluetooth v0.1.1)

Copy Markdown View Source

Bluetooth Classic (BR/EDR) — device-level discovery, pairing, and cross-profile session management.

Profile-specific operations live in submodules:

  • MobBluetooth.Hfp — Hands-Free Profile (audio + vendor AT commands). Use this for headsets, PTT-equipped earpieces, etc.
  • MobBluetooth.Spp — Serial Port Profile (RFCOMM byte streams). Use this for legacy serial-over-Bluetooth devices (Arduino HC-05, OBD-II readers, marine GPS, industrial sensors).

(HID is not supported on Android — receiving HID input requires input-method/HID-host privileges Android denies ordinary apps.)

API style

Same as the rest of Mob: callbacks return socket unchanged, results arrive in handle_info/2 as messages. There are two families, and they do not share a uniform arity:

Device-level events are tagged :bt and carry no session id:

{:bt, :discovery_started}                  # 2-tuple, no payload
{:bt, :discovery_finished}
{:bt, :discovery_cancelled}
{:bt, :discovered, device}                 # 3-tuple, device map
{:bt, :paired, device}
{:bt, :pair_failed, %{address: addr, reason: atom}}
{:bt, :unpaired, device}
{:bt, :paired_list, [device]}
{:bt, :error, payload}

Profile events are tagged by profile (:bt_hfp, :bt_spp) — not :bt. Once a session exists they carry its integer session_id as the third element; pre-session failures omit it:

{:bt_hfp, :connected, session_id, payload}     # 4-tuple, has session
{:bt_hfp, :connect_failed, %{address: addr, reason: atom}}  # 3-tuple, no session yet
{:bt_hfp, :disconnected, session_id, reason}

The profile submodules document their own event sets.

Permissions

Bluetooth requires runtime permissions on Android 12+ (API 31+):

Request via Mob.Permissions.request/2 before calling MobBluetooth functions.

iOS

Bluetooth Classic on iOS requires Apple's MFi (Made for iPhone) certification — a paid, NDA-gated program. MobBluetooth is Android-only. All functions return {:error, :unsupported} synchronously on iOS. For iOS-equivalent custom-hardware connectivity, use Mob.Ble.

Pairing flow

Two pairing modes, auto-selected by whether :pin is given:

# System UI flow — Android shows a system pairing dialog
socket = MobBluetooth.pair(socket, device)

# Programmatic — PIN supplied via API, no UI
socket = MobBluetooth.pair(socket, device, pin: "0000")

If the programmatic PIN fails or the device requires UI confirmation (e.g. numeric comparison), Android falls back to the system UI automatically.

Disconnect

One canonical disconnect for any profile session:

MobBluetooth.disconnect(socket, session_id)

The framework looks up which profile owns the session_id and routes to the right profile-disconnect internally. Emits a profile-specific disconnect event ({:bt_hfp, :disconnected, session_id, reason} etc).

Summary

Types

A discovered or paired Bluetooth device.

An opaque session identifier for an active profile connection.

Functions

Cancel an in-progress discovery.

Disconnect a profile session by session_id.

List currently paired (bonded) Bluetooth devices.

Pair (bond) with a Bluetooth device.

Begin Bluetooth Classic discovery. Discovered devices arrive as individual {:bt, :discovered, device} messages, terminated by {:bt, :discovery_finished}.

Remove an existing pairing (bond).

Types

device()

@type device() :: %{
  :address => String.t(),
  :name => String.t(),
  optional(:bond_state) => :none | :bonding | :bonded,
  optional(:device_class) => non_neg_integer(),
  optional(:uuids) => [String.t()]
}

A discovered or paired Bluetooth device.

session_id()

@type session_id() :: pos_integer()

An opaque session identifier for an active profile connection.

Functions

cancel_discovery(socket)

@spec cancel_discovery(socket :: term()) :: term()

Cancel an in-progress discovery.

disconnect(socket, session_id)

@spec disconnect(socket :: term(), session_id()) :: term()

Disconnect a profile session by session_id.

Works for any profile (MobBluetooth.Hfp, MobBluetooth.Spp) — the framework dispatches internally based on which profile owns the session.

Emits a profile-specific disconnect event:

  • {:bt_hfp, :disconnected, session_id, reason}
  • {:bt_spp, :disconnected, session_id, reason}

list_paired(socket)

@spec list_paired(socket :: term()) :: term()

List currently paired (bonded) Bluetooth devices.

Result arrives as {:bt, :paired_list, [device]}.

pair(socket, device, opts \\ [])

@spec pair(socket :: term(), device(), keyword()) :: term()

Pair (bond) with a Bluetooth device.

Without :pin, Android shows the system pairing dialog (user enters PIN). With :pin, attempts programmatic pairing using the supplied PIN; falls back to system UI if the device demands user confirmation.

Result arrives as one of:

  • {:bt, :paired, device}
  • {:bt, :pair_failed, %{address: String.t(), reason: atom()}}

start_discovery(socket)

@spec start_discovery(socket :: term()) :: term()

Begin Bluetooth Classic discovery. Discovered devices arrive as individual {:bt, :discovered, device} messages, terminated by {:bt, :discovery_finished}.

Discovery typically runs ~12 seconds on Android.

unpair(socket, device)

@spec unpair(socket :: term(), device()) :: term()

Remove an existing pairing (bond).

Result: {:bt, :unpaired, device}.