TB2026-YDS Firmware
Loading...
Searching...
No Matches
Bluetooth service

Classic Bluetooth A2DP source stack and connection management. More...

Collaboration diagram for Bluetooth service:

Files

file  components/services/bluetooth/bluetooth.c
 Classic Bluetooth A2DP source: stack bring-up, name-based discovery and connection.

Data Structures

struct  bluetooth_device_t
 One device found during a scan; the unit the UI lists and selects from. More...
struct  bt_known_device_t
 The identity of a remembered device, enough to reconnect without scanning again. More...

Macros

#define BLUETOOTH_MAX_DEVICES   16
 Maximum number of devices retained from a single scan.
#define BLUETOOTH_MAX_KNOWN   8
 Maximum number of paired devices remembered and persisted across reboots (LRU).

Enumerations

enum  bluetooth_conn_state_t { BLUETOOTH_CONN_IDLE , BLUETOOTH_CONN_CONNECTING , BLUETOOTH_CONN_CONNECTED , BLUETOOTH_CONN_FAILED }
 Coarse connection state for the UI to render (connecting / connected / failure). More...

Functions

esp_err_t bluetooth_init (void)
 Bring up the Bluetooth stack in A2DP source mode.
esp_err_t bluetooth_shutdown (void)
 Power the radio down: deinit A2DP/AVRCP, Bluedroid and the BR/EDR controller.
void bluetooth_set_auto_shutdown (bool enable)
 Enable/disable the automatic radio power-down when the A2DP link drops.
esp_err_t bluetooth_load_known (void)
 Load the persisted known-device list without powering the radio up.
esp_err_t bluetooth_scan_start (void)
 Start an inquiry that collects nearby devices into a list (does not auto-connect).
esp_err_t bluetooth_scan_stop (void)
 Stop an inquiry started by bluetooth_scan_start(). No-op if not scanning.
bool bluetooth_is_scanning (void)
 Report whether an inquiry is currently running.
bool bluetooth_is_powered (void)
 Report whether the radio (controller + Bluedroid) is currently powered up.
size_t bluetooth_get_devices (bluetooth_device_t *out, size_t cap)
 Copy the current scan results into out (thread-safe snapshot).
esp_err_t bluetooth_connect (const esp_bd_addr_t bda)
 Connect the A2DP source to a device chosen by address (e.g. a scan result).
esp_err_t bluetooth_connect_by_name (const char *name_substr)
 TEST ONLY — scan and connect to the first device whose name contains name_substr.
esp_err_t bluetooth_disconnect (void)
 Disconnect the current A2DP link, if any.
bool bluetooth_is_connected (void)
 Report whether an A2DP source connection is currently established.
bluetooth_conn_state_t bluetooth_get_conn_state (void)
 Read the current connection state.
size_t bluetooth_get_known_devices (bt_known_device_t *out, size_t cap)
 Copy the persisted list of known (paired) devices into out, most-recent-first.
bool bluetooth_get_last_device (bt_known_device_t *out)
 Read the last successfully connected device (the one to offer for quick reconnect).
void bluetooth_set_last_device (const bt_known_device_t *dev)
 Remember a device as the most-recent one and persist it to NVS.
esp_err_t bluetooth_reconnect_last (void)
 Reconnect to the remembered device by address, without scanning.
esp_err_t bluetooth_forget_device (const esp_bd_addr_t bda)
 Forget a known device: drop it from the persisted list and remove its bond from NVS.
esp_err_t bluetooth_audio_start (esp_a2d_source_data_cb_t pcm_cb)
 Start streaming PCM to the connected sink through the A2DP source data path.
esp_err_t bluetooth_audio_stop (void)
 Suspend the media stream started by bluetooth_audio_start().
esp_err_t bluetooth_set_absolute_volume (uint8_t volume)
 Set the connected speaker's absolute volume over AVRCP (controller role).
bool bluetooth_is_avrc_connected (void)
 Report whether the AVRCP control channel is established.
bool bluetooth_volume_acked (void)
 Report whether the speaker has confirmed the most recent absolute-volume set.

Detailed Description

Classic Bluetooth A2DP source stack and connection management.

Macro Definition Documentation

◆ BLUETOOTH_MAX_DEVICES

#define BLUETOOTH_MAX_DEVICES   16

Maximum number of devices retained from a single scan.

◆ BLUETOOTH_MAX_KNOWN

#define BLUETOOTH_MAX_KNOWN   8

Maximum number of paired devices remembered and persisted across reboots (LRU).

Enumeration Type Documentation

◆ bluetooth_conn_state_t

Coarse connection state for the UI to render (connecting / connected / failure).

Enumerator
BLUETOOTH_CONN_IDLE 

No link and no attempt in progress.

BLUETOOTH_CONN_CONNECTING 

A connect is in progress (set when issued, cleared on result).

BLUETOOTH_CONN_CONNECTED 

A2DP link established.

BLUETOOTH_CONN_FAILED 

The last attempt failed, or an established link dropped abnormally.

Function Documentation

◆ bluetooth_audio_start()

esp_err_t bluetooth_audio_start ( esp_a2d_source_data_cb_t pcm_cb)

Start streaming PCM to the connected sink through the A2DP source data path.

Registers pcm_cb and kicks the media-control handshake (CHECK_SRC_RDY -> START). The stack pulls PCM from pcm_cb in its own task and encodes SBC itself; this service stays codec-agnostic and only wires the opaque PCM callback to the link. Requires an established connection.

Parameters
pcm_cbPull callback supplying interleaved 16-bit PCM at the negotiated rate.
Returns
ESP_OK if the start was issued; ESP_ERR_INVALID_STATE if not connected; ESP_ERR_INVALID_ARG if pcm_cb is NULL; otherwise the A2DP error.

◆ bluetooth_audio_stop()

esp_err_t bluetooth_audio_stop ( void )

Suspend the media stream started by bluetooth_audio_start().

Returns
ESP_OK if the suspend was issued, otherwise the A2DP error.

◆ bluetooth_connect()

esp_err_t bluetooth_connect ( const esp_bd_addr_t bda)

Connect the A2DP source to a device chosen by address (e.g. a scan result).

Cancels any running inquiry, then initiates the connection. Connection progress is reported through the log; query bluetooth_is_connected() for the outcome. Requires bluetooth_init().

Parameters
bdaAddress to connect to, typically taken from a bluetooth_device_t.
Returns
ESP_OK if the connect was issued; ESP_ERR_INVALID_STATE if not initialised; ESP_ERR_INVALID_ARG if bda is NULL; otherwise the A2DP error.

◆ bluetooth_connect_by_name()

esp_err_t bluetooth_connect_by_name ( const char * name_substr)

TEST ONLY — scan and connect to the first device whose name contains name_substr.

Convenience for bring-up/host tests: starts an inquiry and auto-connects to the first name match. Not used by the final build (the UI uses bluetooth_scan_start() + bluetooth_connect()).

Parameters
name_substrSubstring to look for in advertised device names, e.g. "JBL".
Returns
ESP_OK if the inquiry started; ESP_ERR_INVALID_STATE if not initialised; ESP_ERR_INVALID_ARG if name_substr is NULL/empty; otherwise the GAP error.

◆ bluetooth_disconnect()

esp_err_t bluetooth_disconnect ( void )

Disconnect the current A2DP link, if any.

Returns
ESP_OK (also when nothing is connected), or the underlying A2DP error.

◆ bluetooth_forget_device()

esp_err_t bluetooth_forget_device ( const esp_bd_addr_t bda)

Forget a known device: drop it from the persisted list and remove its bond from NVS.

Removes the matching entry from the known-device list (saving the updated list to NVS) and removes its bond (link keys) via esp_bt_gap_remove_bond_device(). After this, the device no longer reconnects and must be paired again from a fresh scan. The bond is removed even if the address was not in the list.

Parameters
bdaAddress of the device to forget (e.g. from bluetooth_get_known_devices()).
Returns
ESP_OK; ESP_ERR_INVALID_ARG if bda is NULL; otherwise the bond-removal error.

◆ bluetooth_get_conn_state()

bluetooth_conn_state_t bluetooth_get_conn_state ( void )

Read the current connection state.

Richer than bluetooth_is_connected(): lets the UI show a "connecting…" spinner while an attempt is in flight and a failure message when it does not complete. The state stays BLUETOOTH_CONN_FAILED until the next connect attempt or a clean disconnect.

Returns
The current bluetooth_conn_state_t.

◆ bluetooth_get_devices()

size_t bluetooth_get_devices ( bluetooth_device_t * out,
size_t cap )

Copy the current scan results into out (thread-safe snapshot).

Copies up to cap entries from the internal list under a lock, so it is safe to call from the UI task while the inquiry fills the list from the Bluetooth task.

Parameters
outDestination array; must hold at least cap entries. Ignored if NULL.
capCapacity of out.
Returns
Number of devices copied (min of the list size and cap); 0 if out is NULL.

◆ bluetooth_get_known_devices()

size_t bluetooth_get_known_devices ( bt_known_device_t * out,
size_t cap )

Copy the persisted list of known (paired) devices into out, most-recent-first.

The list is restored from NVS at bluetooth_init() and updated on each successful connection; s_known[0] is the same device bluetooth_get_last_device() returns. Use it to show the paired devices for quick reconnect after a reboot, without scanning.

Parameters
outDestination array; must hold at least cap entries. Ignored if NULL.
capCapacity of out.
Returns
Number of devices copied (min of the list size and cap); 0 if out is NULL.

◆ bluetooth_get_last_device()

bool bluetooth_get_last_device ( bt_known_device_t * out)

Read the last successfully connected device (the one to offer for quick reconnect).

The most-recent entry of the known-device list (see bluetooth_get_known_devices()). Set automatically on each successful connection and persisted to NVS by this service, so it survives a reboot.

Parameters
[out]outFilled with the remembered device. Ignored if NULL.
Returns
true if a device is remembered, false if none yet (then out is untouched).

◆ bluetooth_init()

esp_err_t bluetooth_init ( void )

Bring up the Bluetooth stack in A2DP source mode.

Initialises NVS (needed for bonding), the BR/EDR controller and Bluedroid, sets the device name, registers the GAP and A2DP callbacks, starts the A2DP source and advertises one SBC stream endpoint. Pairing is "just works" (no IO capability). Call once at startup. Idempotent.

Returns
ESP_OK; otherwise the first failing controller/Bluedroid/A2DP error.

◆ bluetooth_is_avrc_connected()

bool bluetooth_is_avrc_connected ( void )

Report whether the AVRCP control channel is established.

AVRCP connects a few seconds after the A2DP link. Useful before streaming if you want the volume controllable from the first sample (otherwise the speaker starts at its own default).

Returns
true if AVRCP is connected, false otherwise.

◆ bluetooth_is_connected()

bool bluetooth_is_connected ( void )

Report whether an A2DP source connection is currently established.

Returns
true if connected to a sink device, false otherwise.

◆ bluetooth_is_powered()

bool bluetooth_is_powered ( void )

Report whether the radio (controller + Bluedroid) is currently powered up.

True between bluetooth_init() and bluetooth_shutdown(). Lets the UI show radio activity (e.g. the status-bar antenna) and distinguish "radio off" from "on but not connected", which bluetooth_get_conn_state() alone cannot (IDLE covers both).

Returns
true if the radio is up, false if powered down.

◆ bluetooth_is_scanning()

bool bluetooth_is_scanning ( void )

Report whether an inquiry is currently running.

Returns
true while scanning (cleared when the inquiry ends, is stopped, or a connect begins).

◆ bluetooth_load_known()

esp_err_t bluetooth_load_known ( void )

Load the persisted known-device list without powering the radio up.

The "cold" part of bluetooth_init(): creates the internal mutex and restores the known (paired) device list from NVS, but does NOT bring up the controller. Lets the UI show the paired devices (bluetooth_get_known_devices()) with the radio off, so it only powers up to scan or connect. bluetooth_init() calls this itself; idempotent.

Returns
ESP_OK; ESP_ERR_NO_MEM if the mutex cannot be created; otherwise the NVS error.

◆ bluetooth_reconnect_last()

esp_err_t bluetooth_reconnect_last ( void )

Reconnect to the remembered device by address, without scanning.

Manual reconnect (no auto-reconnect at boot by design): the UI calls this from the menu. Works on a bonded device because pairing is already stored. Watch bluetooth_get_conn_state() for the outcome.

Returns
ESP_OK if the connect was issued; ESP_ERR_INVALID_STATE if not initialised; ESP_ERR_NOT_FOUND if no device is remembered; otherwise the A2DP error.

◆ bluetooth_scan_start()

esp_err_t bluetooth_scan_start ( void )

Start an inquiry that collects nearby devices into a list (does not auto-connect).

Clears the device list and starts a general inquiry. Results accumulate in the background (deduplicated by address; a name that arrives after the address updates the existing entry); read them with bluetooth_get_devices(). The inquiry stops on its own after the inquiry window or when bluetooth_scan_stop()/bluetooth_connect() is called. Query bluetooth_is_scanning().

Caller responsibility: an inquiry floods the radio and breaks an active A2DP stream, so the caller must pause Bluetooth playback before scanning (the service stays player-agnostic and does not do this itself). Requires bluetooth_init().

Returns
ESP_OK if the inquiry started; ESP_ERR_INVALID_STATE if not initialised; otherwise the GAP error.

◆ bluetooth_scan_stop()

esp_err_t bluetooth_scan_stop ( void )

Stop an inquiry started by bluetooth_scan_start(). No-op if not scanning.

Returns
ESP_OK, or the underlying GAP error.

◆ bluetooth_set_absolute_volume()

esp_err_t bluetooth_set_absolute_volume ( uint8_t volume)

Set the connected speaker's absolute volume over AVRCP (controller role).

Sends an AVRCP SetAbsoluteVolume to the sink. The AVRCP control channel comes up a few seconds after the A2DP connection, so a call made before then is remembered and applied automatically once AVRCP connects. The speaker applies and renders the level itself; this does not attenuate the PCM stream.

Parameters
volumeTarget volume, 0 (silent) to 0x7F (loudest); values above 0x7F are clamped.
Returns
ESP_OK if the command was issued or deferred; otherwise the AVRCP error.

◆ bluetooth_set_auto_shutdown()

void bluetooth_set_auto_shutdown ( bool enable)

Enable/disable the automatic radio power-down when the A2DP link drops.

Enabled by default: when the link disconnects (speaker powered off, out of range, or a connect attempt that fails after the UI moved on), the service powers the whole radio down by itself (deferred by ~1 s, in its own task) — otherwise a dropped link would leave the controller drawing current until the user next visits the Bluetooth screen. Consequence: a dropped speaker cannot reconnect on its own; the user reconnects from the Bluetooth screen, per the manual lifecycle this project uses.

The Bluetooth settings screen disables it while it is open (it owns the radio there: a manual disconnect or a failed connect must keep the radio up for the next action) and re-enables it on exit.

Parameters
enabletrue to arm the auto power-down (default), false to suppress it.

◆ bluetooth_set_last_device()

void bluetooth_set_last_device ( const bt_known_device_t * dev)

Remember a device as the most-recent one and persist it to NVS.

Inserts dev at the front of the known-device list (or moves it there) and saves the list, so bluetooth_reconnect_last() can target it. Normally the service does this itself on each connection; this is the manual entry point. Does not connect by itself. The bond (link keys) must still exist in NVS for a silent reconnect.

Parameters
devDevice to remember. Ignored if NULL.

◆ bluetooth_shutdown()

esp_err_t bluetooth_shutdown ( void )

Power the radio down: deinit A2DP/AVRCP, Bluedroid and the BR/EDR controller.

The counterpart to bluetooth_init(), so the UI can keep the radio off by default and only power it up while scanning or connected (a meaningful current saving on this battery device). bluetooth_init() may be called again afterwards to bring it back. The known-device list survives (it lives in NVS), so reconnection still works after a power-up.

Refuses to tear down under a live or forming link (that would kill audio streaming): call bluetooth_disconnect() first if you really mean to drop the connection.

Returns
ESP_OK when powered down (or already off); ESP_ERR_INVALID_STATE if connected/connecting; otherwise the controller-deinit error.

◆ bluetooth_volume_acked()

bool bluetooth_volume_acked ( void )

Report whether the speaker has confirmed the most recent absolute-volume set.

Cleared when bluetooth_set_absolute_volume() sends a command, set when the sink's SetAbsoluteVolume response arrives. Poll this before streaming the first sample so the speaker is known to be at the intended (e.g. silent) level — never blast the user's ears.

Returns
true once the sink has acknowledged the last volume set.