Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bluetooth: fast_pair: fmdn: add API for checking the provisioning state #20970

Merged
merged 1 commit into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions include/bluetooth/services/fast_pair/fmdn.h
Original file line number Diff line number Diff line change
Expand Up @@ -538,17 +538,18 @@ struct bt_fast_pair_fmdn_info_cb {
/** @brief Indicate provisioning state changes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commit msg had 2 typos:

Added the new FMDN API - bt_fast_pair_fmdn_is_provisioned. The API can
be used to check the device provisioning in the synchronous manner.

Changed the bt_fast_pair_fmdn_info_cb.provisioning_state_changed
callback behaviour. The callback no longer reports the initial
provisioning state after the Fast Pair subsystem is enabled with
the bt_fast_pair_enable API.

Aligned the affected Fast Pair projects that use the FMDN extension.

*
* This callback is called to indicate that the FMDN accessory has been
* successfully provisioned or unprovisioned.
* successfully provisioned or unprovisioned by the connected Bluetooth
* peer.
*
* This callback also reports the initial provisioning state when the
* user enables Fast Pair with the @ref bt_fast_pair_enable API.
* This callback does not report the initial provisioning state when the
* user enables Fast Pair with the @ref bt_fast_pair_enable API. To check
* the initial state, use the @ref bt_fast_pair_fmdn_is_provisioned API.
*
* The first callback is executed in the workqueue context after the
* @ref bt_fast_pair_enable function call. Subsequent callbacks are
* also executed in the cooperative thread context. You can learn about
* the exact thread context by analyzing the @kconfig{CONFIG_BT_RECV_CONTEXT}
* configuration choice. By default, this callback is executed in the
* Bluetooth-specific workqueue thread (@kconfig{CONFIG_BT_RECV_WORKQ_BT}).
* This callback is executed in the cooperative thread context. You
* can learn about the exact thread context by analyzing the
* @kconfig{CONFIG_BT_RECV_CONTEXT} configuration choice. By default, this
* callback is executed in the Bluetooth-specific workqueue thread
* (@kconfig{CONFIG_BT_RECV_WORKQ_BT}).
*
* @param provisioned true if the accessory has been successfully provisioned.
* false if the accessory has been successfully unprovisioned.
Expand All @@ -559,6 +560,20 @@ struct bt_fast_pair_fmdn_info_cb {
sys_snode_t node;
};

/** @brief Check the FMDN provisioning state.
*
* This function can be used to synchronously check the FMDN provisioning state.
* To track the provisioning state asynchronously, use the
* @ref bt_fast_pair_fmdn_info_cb.provisioning_state_changed callback.
*
* The function shall only be used after the Fast Pair module is enabled with the
* @ref bt_fast_pair_enable API. In the disabled state, this function always returns
* false.
*
* @return True if the device is provisioned, false otherwise.
*/
bool bt_fast_pair_fmdn_is_provisioned(void);

/** @brief Register the information callbacks in the FMDN module.
*
* This function registers the information callbacks. You can call this function only
Expand Down
61 changes: 49 additions & 12 deletions samples/bluetooth/fast_pair/locator_tag/src/fp_adv.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ static void fp_advertising_update(void)
static void fp_adv_connected(struct bt_le_ext_adv *adv, struct bt_le_ext_adv_connected_info *info)
{
__ASSERT_NO_MSG(!fp_conn);
__ASSERT_NO_MSG(fp_adv_set);

fp_adv_set_active = false;
fp_adv_state_changed_notify(fp_adv_set_active);
Expand All @@ -344,10 +345,14 @@ static bool fp_adv_rpa_expired(struct bt_le_ext_adv *adv)
__ASSERT_NO_MSG(!k_is_preempt_thread());
__ASSERT_NO_MSG(!k_is_in_isr());

__ASSERT_NO_MSG(is_enabled);
__ASSERT_NO_MSG(fp_adv_set);

LOG_INF("Fast Pair: RPA expired");

if (!fp_adv_set_active) {
LOG_INF("Fast Pair: RPA rotation in the inactive advertising state");
}

if (!uptime) {
uptime = k_uptime_get();
} else {
Expand All @@ -369,6 +374,18 @@ static bool fp_adv_rpa_expired(struct bt_le_ext_adv *adv)
LOG_INF("Fast Pair: setting RPA timeout to %d [s]",
next_rpa_timeout);
}
} else {
if (!fp_adv_set_active) {
/* Keep the RPA in the valid state to ensure that the RPA expired callback
* will be received on a forced RPA rotation during the FMDN unprovisioning
* operation. The forced RPA rotation allows the Fast Pair advertising set
* to take control from the FMDN advertising set over the RPA timeout. The
* RPA expired callback will not be received if any RPA rotation was
* previously allowed here in the provisioned state and with inactive Fast
* Pair advertising set.
*/
expire_rpa = false;
}
}

if (fp_adv_mode == APP_FP_ADV_MODE_DISCOVERABLE) {
Expand All @@ -394,6 +411,25 @@ static bool fp_adv_rpa_expired(struct bt_le_ext_adv *adv)
return expire_rpa;
}

static int fp_adv_set_rotate(void)
{
int err;
struct bt_le_oob oob;

__ASSERT(fp_adv_set, "Fast Pair: invalid state of the advertising set");

/* Force the RPA rotation to synchronize the Fast Pair advertising
* payload with its RPA address using rpa_expired callback.
*/
err = bt_le_oob_get_local(fp_adv_param.id, &oob);
if (err) {
LOG_ERR("Fast Pair: bt_le_oob_get_local failed: %d", err);
return err;
}

return 0;
}

static int fp_adv_set_setup(void)
{
int err;
Expand Down Expand Up @@ -502,16 +538,7 @@ static void fp_adv_provisioning_state_changed(bool provisioned)
}

if (!provisioned) {
int err;
struct bt_le_oob oob;

/* Force the RPA rotation to synchronize the Fast Pair advertising
* payload with its RPA address using rpa_expired callback.
*/
err = bt_le_oob_get_local(fp_adv_param.id, &oob);
if (err) {
LOG_ERR("Fast Pair: bt_le_oob_get_local failed: %d", err);
}
(void) fp_adv_set_rotate();
} else {
fp_adv_rpa_suspension_cancel();
}
Expand Down Expand Up @@ -688,13 +715,23 @@ int app_fp_adv_enable(void)
return 0;
}

fp_account_key_present = bt_fast_pair_has_account_key();
fmdn_provisioned = bt_fast_pair_fmdn_is_provisioned();

err = fp_adv_set_setup();
if (err) {
LOG_ERR("Fast Pair: fp_adv_set_setup failed (err %d)", err);
return err;
}

fp_account_key_present = bt_fast_pair_has_account_key();
if (!fmdn_provisioned) {
err = fp_adv_set_rotate();
if (err) {
LOG_ERR("Fast Pair: fp_adv_set_rotate failed: %d", err);
(void) fp_adv_set_teardown();
return err;
}
}

fp_adv_mode_update();

Expand Down
122 changes: 71 additions & 51 deletions samples/bluetooth/fast_pair/locator_tag/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,29 @@ APP_FP_ADV_TRIGGER_REGISTER(fp_adv_trigger_fmdn_provisioning, "fmdn_provisioning
*/
APP_FP_ADV_TRIGGER_REGISTER(fp_adv_trigger_ui, "ui");

static bool factory_reset_executed;
static enum factory_reset_trigger factory_reset_trigger;

static void init_work_handle(struct k_work *w);

static K_SEM_DEFINE(init_work_sem, 0, 1);
static K_WORK_DEFINE(init_work, init_work_handle);

static void fmdn_provisioning_state_set(bool provisioned)
{
__ASSERT_NO_MSG(bt_fast_pair_is_ready());
__ASSERT_NO_MSG(bt_fast_pair_fmdn_is_provisioned() == provisioned);

if (fmdn_provisioned == provisioned) {
return;
}

LOG_INF("FMDN: state changed to %s",
provisioned ? "provisioned" : "unprovisioned");

app_ui_state_change_indicate(APP_UI_STATE_PROVISIONED, provisioned);
fmdn_provisioned = provisioned;
}

static void fmdn_factory_reset_prepare(void)
{
/* Disable advertising requests related to the FMDN. */
Expand All @@ -100,9 +115,21 @@ static void fmdn_factory_reset_prepare(void)

static void fmdn_factory_reset_executed(void)
{
if (factory_reset_trigger != FACTORY_RESET_TRIGGER_NONE) {
LOG_INF("The device has been reset to factory settings");
LOG_INF("Please press a button to put the device in the Fast Pair discoverable "
"advertising mode");
}

/* Clear the trigger state for the scheduled factory reset operations. */
factory_reset_trigger = FACTORY_RESET_TRIGGER_NONE;
factory_reset_executed = true;

if (bt_fast_pair_is_ready()) {
fp_account_key_present = bt_fast_pair_has_account_key();
}
__ASSERT_NO_MSG(!fp_account_key_present);

__ASSERT_NO_MSG(!fmdn_provisioned);
}

APP_FACTORY_RESET_CALLBACKS_REGISTER(factory_reset_cbs, fmdn_factory_reset_prepare,
Expand Down Expand Up @@ -421,75 +448,66 @@ static void fmdn_conn_authenticated(struct bt_conn *conn)
fmdn_conn_auth_bm_conn_status_set(conn, true);
}

static bool fmdn_provisioning_state_is_first_cb_after_bootup(void)
static void fmdn_provisioning_state_changed(bool provisioned)
{
static bool first_cb_after_bootup = true;
bool is_first_cb_after_bootup = first_cb_after_bootup;
fmdn_provisioning_state_set(provisioned);
if (provisioned) {
/* Fast Pair Implementation Guidelines for the locator tag use case:
* cancel the provisioning timeout.
*/
if (factory_reset_trigger == FACTORY_RESET_TRIGGER_PROVISIONING_TIMEOUT) {
fmdn_factory_reset_cancel();
}

first_cb_after_bootup = false;
app_fp_adv_request(&fp_adv_trigger_fmdn_provisioning, false);

return is_first_cb_after_bootup;
}
fp_adv_ui_request = false;
app_fp_adv_request(&fp_adv_trigger_ui, fp_adv_ui_request);
} else {
/* Fast Pair Implementation Guidelines for the locator tag use case:
* trigger the reset to factory settings on the unprovisioning operation.
*
* Delay the factory reset operation to allow the local device
* to send a response to the unprovisioning command and give
* the connected peer necessary time to finalize its operations
* and shutdown the connection.
*/
fmdn_factory_reset_schedule(FACTORY_RESET_TRIGGER_KEY_STATE_MISMATCH,
K_SECONDS(FACTORY_RESET_DELAY));

static void fmdn_provisioning_state_changed(bool provisioned)
{
bool clock_sync_required = fmdn_provisioning_state_is_first_cb_after_bootup() &&
provisioned;
app_fp_adv_request(&fp_adv_trigger_clock_sync, false);
}
}

LOG_INF("FMDN: state changed to %s",
provisioned ? "provisioned" : "unprovisioned");
static struct bt_fast_pair_fmdn_info_cb fmdn_info_cb = {
.clock_synced = fmdn_clock_synced,
.conn_authenticated = fmdn_conn_authenticated,
.provisioning_state_changed = fmdn_provisioning_state_changed,
};

app_ui_state_change_indicate(APP_UI_STATE_PROVISIONED, provisioned);
fmdn_provisioned = provisioned;
static void fmdn_provisioning_state_init(void)
{
bool provisioned;

/* Fast Pair Implementation Guidelines for the locator tag use case:
* cancel the provisioning timeout.
*/
if (provisioned &&
(factory_reset_trigger == FACTORY_RESET_TRIGGER_PROVISIONING_TIMEOUT)) {
fmdn_factory_reset_cancel();
}
provisioned = bt_fast_pair_fmdn_is_provisioned();
fmdn_provisioning_state_set(provisioned);

/* Fast Pair Implementation Guidelines for the locator tag use case:
* trigger the reset to factory settings on the unprovisioning operation
* or on the loss of the Owner Account Key.
* trigger the reset to factory settings on the mismatch between the
* Owner Account Key and the FMDN provisioning state.
*/
fp_account_key_present = bt_fast_pair_has_account_key();
if (fp_account_key_present != provisioned) {
/* Delay the factory reset operation to allow the local device
* to send a response to the unprovisioning command and give
* the connected peer necessary time to finalize its operations
* and shutdown the connection.
*/
fmdn_factory_reset_schedule(
FACTORY_RESET_TRIGGER_KEY_STATE_MISMATCH,
K_SECONDS(FACTORY_RESET_DELAY));
fmdn_factory_reset_schedule(FACTORY_RESET_TRIGGER_KEY_STATE_MISMATCH, K_NO_WAIT);
return;
}

/* Triggered on the unprovisioning operation. */
if (factory_reset_executed) {
LOG_INF("The device has been reset to factory settings");
LOG_INF("Please press a button to put the device in the Fast Pair discoverable "
"advertising mode");

factory_reset_executed = false;
return;
}

app_fp_adv_request(&fp_adv_trigger_clock_sync, clock_sync_required);
app_fp_adv_request(&fp_adv_trigger_fmdn_provisioning, false);
app_fp_adv_request(&fp_adv_trigger_clock_sync, provisioned);

fp_adv_ui_request = !provisioned;
app_fp_adv_request(&fp_adv_trigger_ui, fp_adv_ui_request);
}

static struct bt_fast_pair_fmdn_info_cb fmdn_info_cb = {
.clock_synced = fmdn_clock_synced,
.conn_authenticated = fmdn_conn_authenticated,
.provisioning_state_changed = fmdn_provisioning_state_changed,
};

static void fp_adv_state_changed(bool enabled)
{
app_ui_state_change_indicate(APP_UI_STATE_FP_ADV, enabled);
Expand Down Expand Up @@ -686,6 +704,8 @@ static void init_work_handle(struct k_work *w)
return;
}

fmdn_provisioning_state_init();

k_sem_give(&init_work_sem);
}

Expand Down
12 changes: 6 additions & 6 deletions subsys/bluetooth/services/fast_pair/fmdn/beacon_actions.c
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ static ssize_t provisioning_state_read_handle(struct bt_conn *conn,
struct fp_account_key account_key;
uint8_t eid[PROVISIONING_STATE_RSP_EID_LEN];
uint8_t provisioning_state_flags;
const bool provisioned = fp_fmdn_state_is_provisioned();
const bool provisioned = bt_fast_pair_fmdn_is_provisioned();
static const uint8_t req_data_len = PROVISIONING_STATE_REQ_PAYLOAD_LEN;
uint8_t rsp_data_len;
enum provisioning_state_bit_flag {
Expand Down Expand Up @@ -348,7 +348,7 @@ static ssize_t ephemeral_identity_key_set_handle(struct bt_conn *conn,
struct fp_account_key account_key;
uint8_t *encrypted_eik;
uint8_t new_eik[EPHEMERAL_IDENTITY_KEY_SET_REQ_EIK_LEN];
const bool provisioned = fp_fmdn_state_is_provisioned();
const bool provisioned = bt_fast_pair_fmdn_is_provisioned();
const uint8_t req_data_len = provisioned ?
EPHEMERAL_IDENTITY_KEY_SET_REQ_PROVISIONED_PAYLOAD_LEN :
EPHEMERAL_IDENTITY_KEY_SET_REQ_UNPROVISIONED_PAYLOAD_LEN;
Expand Down Expand Up @@ -483,7 +483,7 @@ static ssize_t ephemeral_identity_key_clear_handle(struct bt_conn *conn,
struct fp_account_key account_key;
uint8_t *current_eik_hash;
uint8_t *random_nonce;
const bool provisioned = fp_fmdn_state_is_provisioned();
const bool provisioned = bt_fast_pair_fmdn_is_provisioned();
static const uint8_t req_data_len = EPHEMERAL_IDENTITY_KEY_CLEAR_REQ_PAYLOAD_LEN;
static const uint8_t rsp_data_len = EPHEMERAL_IDENTITY_KEY_CLEAR_RSP_PAYLOAD_LEN;

Expand Down Expand Up @@ -933,7 +933,7 @@ static ssize_t activate_utp_mode_handle(struct bt_conn *conn,
return BT_GATT_ERR(BEACON_ACTIONS_ATT_ERR_INVALID_VALUE);
}

if (!fp_fmdn_state_is_provisioned()) {
if (!bt_fast_pair_fmdn_is_provisioned()) {
LOG_ERR("Beacon Actions: Activate Unwanted Tracking Protection mode request:"
" Device is not provisioned");

Expand Down Expand Up @@ -1029,7 +1029,7 @@ static ssize_t deactivate_utp_mode_handle(struct bt_conn *conn,
return BT_GATT_ERR(BEACON_ACTIONS_ATT_ERR_INVALID_VALUE);
}

if (!fp_fmdn_state_is_provisioned()) {
if (!bt_fast_pair_fmdn_is_provisioned()) {
LOG_ERR("Beacon Actions: Deactivate Unwanted Tracking Protection mode request:"
" Device is not provisioned");

Expand Down Expand Up @@ -1139,7 +1139,7 @@ int bt_fast_pair_fmdn_ring_state_update(
}

/* Check if connected peers should be notified about the ring state change. */
if (!fp_fmdn_state_is_provisioned()) {
if (!bt_fast_pair_fmdn_is_provisioned()) {
return 0;
}

Expand Down
Loading
Loading