405 lines
12 KiB
C
405 lines
12 KiB
C
/****************************************************************************
|
||
* Copyright (C) 2025 Xiaomi Corporation
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
***************************************************************************/
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
|
||
#include "createbond.h"
|
||
|
||
static bt_instance_t* g_bt_ins = NULL;
|
||
static void* adapter_callback = NULL;
|
||
static app_demo_t app_demo;
|
||
|
||
/**
|
||
* @brief peer device address.
|
||
*
|
||
* @note The address is in reverse order. If the address of the peer device
|
||
* is 11:22:33:44:55:66, it should be written as 66:55:44:33:22:11 here.
|
||
*/
|
||
static const bt_address_t test_remote_addr = {
|
||
{ 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }
|
||
};
|
||
|
||
/**
|
||
* @brief Block the current thread and wait to be woken up.
|
||
*/
|
||
static void wait_awakened(void)
|
||
{
|
||
sem_wait(&app_demo.sem);
|
||
}
|
||
|
||
/**
|
||
* @brief Wake up the main thread of the app.
|
||
*/
|
||
static void wakeup_thread(void)
|
||
{
|
||
sem_post(&app_demo.sem);
|
||
}
|
||
|
||
/**
|
||
* @brief Add a node to the message queue.
|
||
*/
|
||
static void app_list_add_tail(struct list_node* node)
|
||
{
|
||
pthread_mutex_lock(&app_demo.mutex);
|
||
list_add_tail(&app_demo.message_queue, node);
|
||
pthread_mutex_unlock(&app_demo.mutex);
|
||
wakeup_thread();
|
||
}
|
||
|
||
/**
|
||
* @brief Remove the head node of the message queue.
|
||
*/
|
||
static node_t* app_list_remove_head(void)
|
||
{
|
||
node_t* node_data;
|
||
|
||
wait_awakened();
|
||
|
||
pthread_mutex_lock(&app_demo.mutex);
|
||
struct list_node* node = list_remove_head(&app_demo.message_queue);
|
||
pthread_mutex_unlock(&app_demo.mutex);
|
||
if (node == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
node_data = list_entry(node, node_t, node);
|
||
return node_data;
|
||
}
|
||
|
||
/**
|
||
* @brief Set the scanning mode to make the device locally connectable and discoverable.
|
||
*/
|
||
static void app_bt_set_scan_mode(bt_scan_mode_t mode, bool bondable)
|
||
{
|
||
node_t* node = (node_t*)malloc(sizeof(node_t));
|
||
if (node == NULL) {
|
||
LOGE("malloc failed.");
|
||
return;
|
||
}
|
||
|
||
node->data.msg_type = APP_BT_GAP_SET_SCANMODE;
|
||
node->data.gap_req._bt_adapter_set_scan_mode.mode = mode;
|
||
node->data.gap_req._bt_adapter_set_scan_mode.bondable = bondable;
|
||
app_list_add_tail(&node->node);
|
||
}
|
||
|
||
/**
|
||
* @brief Set io capability to NOINPUTNOOUTPUT.
|
||
*/
|
||
static void app_bt_set_io_capability(bt_io_capability_t capability)
|
||
{
|
||
node_t* node = (node_t*)malloc(sizeof(node_t));
|
||
if (node == NULL) {
|
||
LOGE("malloc failed.");
|
||
return;
|
||
}
|
||
|
||
node->data.msg_type = APP_BT_GAP_SET_IO_CAPABILITY;
|
||
node->data.gap_req._bt_adapter_set_io_capability.cap = capability;
|
||
app_list_add_tail(&node->node);
|
||
}
|
||
|
||
static void app_bt_gap_init(void)
|
||
{
|
||
app_bt_set_io_capability(BT_IO_CAPABILITY_NOINPUTNOOUTPUT);
|
||
|
||
app_bt_set_scan_mode(BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE, true);
|
||
}
|
||
|
||
/**
|
||
* @brief Discover nearby Bluetooth devices.
|
||
*/
|
||
static void app_bt_discovery(void)
|
||
{
|
||
node_t* node = (node_t*)malloc(sizeof(node_t));
|
||
if (node == NULL) {
|
||
LOGE("malloc failed.");
|
||
return;
|
||
}
|
||
|
||
node->data.msg_type = APP_BT_GAP_START_DISCOVERY;
|
||
node->data.gap_req._bt_adapter_start_discovery.timeout = 2;
|
||
app_list_add_tail(&node->node);
|
||
}
|
||
|
||
/**
|
||
* @brief Adapter state change callback.
|
||
*
|
||
* This function is executed in the bt_client thread, the app needs to handle the callback
|
||
* in another thread.
|
||
*/
|
||
static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state)
|
||
{
|
||
if (state != BT_ADAPTER_STATE_ON && state != BT_ADAPTER_STATE_OFF)
|
||
return;
|
||
|
||
node_t* node = (node_t*)malloc(sizeof(node_t));
|
||
if (node == NULL) {
|
||
LOGE("malloc failed.");
|
||
return;
|
||
}
|
||
|
||
node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED;
|
||
node->data.gap_cb._on_adapter_state_changed.state = state;
|
||
app_list_add_tail(&node->node);
|
||
|
||
if (state == BT_ADAPTER_STATE_ON) {
|
||
app_bt_gap_init();
|
||
app_bt_discovery();
|
||
} else if (state == BT_ADAPTER_STATE_OFF) {
|
||
app_demo.running = 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Connection state change callback.
|
||
*
|
||
* This function is executed in the bt_client thread, the app needs to handle the callback
|
||
* in another thread.
|
||
*/
|
||
static void gap_connection_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state)
|
||
{
|
||
|
||
node_t* node = (node_t*)malloc(sizeof(node_t));
|
||
if (node == NULL) {
|
||
LOGE("malloc failed.");
|
||
return;
|
||
}
|
||
|
||
node->data.msg_type = APP_BT_GAP_ON_CONNECTION_STATE_CHANGED;
|
||
node->data.gap_cb._on_connection_state_changed.state = state;
|
||
node->data.gap_cb._on_connection_state_changed.transport = transport;
|
||
memcpy(&node->data.gap_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t));
|
||
|
||
app_list_add_tail(&node->node);
|
||
}
|
||
|
||
/**
|
||
* @brief Bond state change callback.
|
||
*
|
||
* This function is executed in the bt_client thread, the app needs to handle the callback
|
||
* in another thread.
|
||
*/
|
||
static void gap_bond_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd)
|
||
{
|
||
node_t* node = (node_t*)malloc(sizeof(node_t));
|
||
if (node == NULL) {
|
||
LOGE("malloc failed.");
|
||
return;
|
||
}
|
||
|
||
node->data.msg_type = APP_BT_GAP_ON_BOND_STATE_CHANGED;
|
||
node->data.gap_cb._on_bond_state_changed.state = state;
|
||
node->data.gap_cb._on_bond_state_changed.transport = transport;
|
||
node->data.gap_cb._on_bond_state_changed.is_ctkd = is_ctkd;
|
||
memcpy(&node->data.gap_cb._on_bond_state_changed.addr, addr, sizeof(bt_address_t));
|
||
|
||
app_list_add_tail(&node->node);
|
||
}
|
||
|
||
/**
|
||
* @brief Discovery result callback.
|
||
*
|
||
* This function is executed in the bt_client thread, the app needs to handle the callback
|
||
* in another thread.
|
||
*/
|
||
static void gap_discovery_result_callback(void* cookie, bt_discovery_result_t* remote)
|
||
{
|
||
char addr_str[BT_ADDR_STR_LENGTH] = { 0 };
|
||
bt_addr_ba2str(&remote->addr, addr_str);
|
||
|
||
LOGI("Discovery result: device [%s], name: %s, code: %08x, is HEADSET: %s, rssi: %d\n",
|
||
addr_str, remote->name, remote->cod,
|
||
IS_HEADSET(remote->cod) ? "true" : "false",
|
||
remote->rssi);
|
||
}
|
||
|
||
static void app_create_bond(void)
|
||
{
|
||
node_t* node = (node_t*)malloc(sizeof(node_t));
|
||
if (node == NULL) {
|
||
LOGE("malloc failed.");
|
||
return;
|
||
}
|
||
|
||
node->data.msg_type = APP_BT_GAP_CREATE_BOND;
|
||
node->data.gap_req._bt_device_create_bond.transport = BT_TRANSPORT_BREDR;
|
||
memcpy(&node->data.gap_req._bt_device_create_bond.addr, &test_remote_addr, sizeof(bt_address_t));
|
||
|
||
app_list_add_tail(&node->node);
|
||
}
|
||
|
||
/**
|
||
* @brief Discovery state change callback.
|
||
*
|
||
* This function is executed in the bt_client thread, the app needs to handle the callback
|
||
* in another thread.
|
||
*/
|
||
static void gap_discovery_state_changed_callback(void* cookie, bt_discovery_state_t state)
|
||
{
|
||
node_t* node = (node_t*)malloc(sizeof(node_t));
|
||
if (node == NULL) {
|
||
LOGE("malloc failed.");
|
||
return;
|
||
}
|
||
|
||
node->data.msg_type = APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED;
|
||
node->data.gap_cb._on_discovery_state_changed.state = state;
|
||
|
||
app_list_add_tail(&node->node);
|
||
|
||
if (state == BT_DISCOVERY_STATE_STOPPED)
|
||
app_create_bond();
|
||
}
|
||
|
||
// gap callback
|
||
const static adapter_callbacks_t app_gap_cbs = {
|
||
.on_adapter_state_changed = gap_adapter_state_changed_callback,
|
||
.on_discovery_result = gap_discovery_result_callback,
|
||
.on_discovery_state_changed = gap_discovery_state_changed_callback,
|
||
.on_connection_state_changed = gap_connection_state_changed_callback,
|
||
.on_bond_state_changed = gap_bond_state_changed_callback,
|
||
};
|
||
|
||
/**
|
||
* @brief Initialize semaphore, mutex, message queue.
|
||
*
|
||
* @note Semaphores are used to control the number of concurrently executing threads.
|
||
* A semaphore has a counter, and threads need to acquire the semaphore before
|
||
* accessing a resource. When the semaphore counter is greater than 0, the thread
|
||
* can continue executing. When the semaphore counter is equal to 0, the thread
|
||
* needs to wait for other threads to release resources so that the semaphore
|
||
* counter can increase before it can continue executing.
|
||
*
|
||
* @note Mutex locks are used to protect shared resources, ensuring that only one thread
|
||
* can access the shared resource at a time, while other threads must wait until
|
||
* the lock is released by that thread before they can access it.
|
||
*
|
||
* @note Message queues is used to store events to be processed. When calling the Bluetooth
|
||
* synchronization interface, receiving and sending Bluetooth messages from the Bluetooth
|
||
* module should be done in different threads.
|
||
*/
|
||
static void app_demo_init(void)
|
||
{
|
||
app_demo.running = 1;
|
||
sem_init(&app_demo.sem, 0, 1);
|
||
pthread_mutex_init(&app_demo.mutex, NULL);
|
||
list_initialize(&app_demo.message_queue);
|
||
}
|
||
|
||
/**
|
||
* @brief Destroy semaphore, mutex, clear up message queue.
|
||
*/
|
||
static void app_demo_deinit(void)
|
||
{
|
||
sem_destroy(&app_demo.sem);
|
||
pthread_mutex_destroy(&app_demo.mutex);
|
||
|
||
node_t* entry = NULL;
|
||
node_t* temp_entry = NULL;
|
||
list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node)
|
||
{
|
||
list_delete(&entry->node);
|
||
free(entry);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief The main thread processes events.
|
||
*/
|
||
static void app_handle_message(node_t* node)
|
||
{
|
||
if (node == NULL) {
|
||
return;
|
||
}
|
||
|
||
if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END)
|
||
demo_createbond_handle_gap_message(g_bt_ins, node);
|
||
}
|
||
|
||
/**
|
||
* @brief Check the exit condition of the while loop in the main function.
|
||
*
|
||
* The condition for exiting the while loop can be multiple, but in this demo,
|
||
* only one scenario is provided: Bluetooth is turned off.
|
||
*/
|
||
static bool app_if_running(void)
|
||
{
|
||
// Developers can add additional exit condition checks.
|
||
|
||
return app_demo.running;
|
||
}
|
||
|
||
int main(int argc, char* argv[])
|
||
{
|
||
node_t* node = NULL;
|
||
|
||
// 1. Initialize semaphore;
|
||
// 2. Initialize mutex;
|
||
// 3. Initialize message queue.
|
||
app_demo_init();
|
||
|
||
// Create bluetooth client instance.
|
||
g_bt_ins = bluetooth_create_instance();
|
||
if (g_bt_ins == NULL) {
|
||
LOGE("create instance error");
|
||
goto error;
|
||
}
|
||
|
||
// Register gap callback.
|
||
adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs);
|
||
if (adapter_callback == NULL) {
|
||
LOGE("register callback error.");
|
||
goto error;
|
||
}
|
||
|
||
// Enable bluetooth.
|
||
if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) {
|
||
LOGE("enable adapter error.");
|
||
goto error;
|
||
}
|
||
|
||
// The app main thread,is used to handle bluetooth events.
|
||
while (app_if_running()) {
|
||
// Obtain the msg to be processed.
|
||
node = app_list_remove_head();
|
||
|
||
// The main thread processes events.
|
||
app_handle_message(node);
|
||
}
|
||
|
||
error:
|
||
// Unregister gap callback;
|
||
if (adapter_callback) {
|
||
bt_adapter_unregister_callback(g_bt_ins, adapter_callback);
|
||
adapter_callback = NULL;
|
||
}
|
||
|
||
if (g_bt_ins) {
|
||
// Delete bluetooth client instance;
|
||
bluetooth_delete_instance(g_bt_ins);
|
||
g_bt_ins = NULL;
|
||
}
|
||
|
||
// 1. Destroy semaphore;
|
||
// 2. Destroy mutex;
|
||
// 3. clean up message queue.
|
||
app_demo_deinit();
|
||
|
||
LOGI("Bluetooth closed.");
|
||
|
||
return 0;
|
||
} |