蓝牙辅助配网功能是针对Wi-Fi + BLE
的 Combo
芯片方案,通过 BLE
创建指定的 GATT
服务,手机连接该 GATT SERVER
,利用 BLE
的无线通信能力,将物联网设备连接所需的 SSID
、``PASSWORD等信息传输给
Wi-Fi + BLE` 的 `Combo` 芯片或模组,使设备顺利接入物联网平台,继而完设备绑定等功能。
蓝牙辅助配网移植的主要流程如下。
- 硬件设备选择
- 控制台创建产品
- 获取
SDK
- 移植
SDK
- 验证蓝牙辅助配网功能
需要您根据产品特性选择合适的Combo
芯片或模组。
-
请参考控制台操作创建设备。
-
在控制台选择设备配网方式,配网方式确定后可以通过扫描二维码的方式连接设备蓝牙进行配网。
蓝牙辅助配网功能使用了C SDK和LLSync SDK,请下载最新版本使用。您可以下载ESP32
使用LLSync配网示例程序参考。
蓝牙辅助配网功能使用了WI-Fi
和BLE
的通信能力,因此包括C SDK
和LLSync SDK
的移植。
-
LLSync SDK
移植主要是做配置编译、HAL实现、API调用。a. 配置编译
修改
config/ble_qiot_config.h
文件,对配网相关功能进行配置,不关心标准蓝牙功能。// 配置为0,SDK初始化后默认广播。配置为1,SDK初始化不广播,需要通过设备UI操作触发广播(例如按键)。 #define BLE_QIOT_BUTTON_BROADCAST (0) #define __ORDER_LITTLE_ENDIAN__ (1234) #define __ORDER_BIG_ENDIAN__ (4321) // 设备大小端定义。 #define __BYTE_ORDER__ (__ORDER_LITTLE_ENDIAN__) // 设备端串口输出函数。 #define BLE_QIOT_LOG_PRINT(...) printf(__VA_ARGS__) #define BLE_QIOT_USER_DEFINE_HEXDUMP 0 // 设备端和小程序通信过程中的单条数据最大长度,配网功能默认128即可。 #define BLE_QIOT_EVENT_MAX_SIZE (128) // 取MTU和BLE_QIOT_EVENT_MAX_SIZE中的较小值,LLSync会对数据分片后再发送。 #define BLE_QIOT_EVENT_BUF_SIZE (23) // 配置为1,建立蓝牙连接后小程序尝试设置MTU,通过ble_get_user_data_mtu_size接口获取;配置为0,默认MTU为23。 #define BLE_QIOT_REMOTE_SET_MTU (1) // 配置为1,使能LLSync配网功能 #define BLE_QIOT_LLSYNC_CONFIG_NET (1)
LLSync使用配网功能不需要ota、数据模版等能力,
SDK
会通过功能宏控制编译。b. HAL实现
inc/ble_qiot_import.h
中定义了LLSync SDK
依赖的设备HAL
实现,需要您在自己的硬件平台上进行实现。/* 获取设备MAC地址,示例: int ble_get_mac(char *mac) { char *address = (char *)esp_bt_dev_get_address(); memcpy(mac, address, 6); return 0; } */ int ble_get_mac(char *mac); /* 设置WIFI模式,当前支持wifi station,示例: ble_qiot_ret_status_t ble_combo_wifi_mode_set(BLE_WIFI_MODE mode) { if (mode != BLE_WIFI_MODE_STA){ return ble_event_report_wifi_mode(-1) } ESP_ERROR_CHECK(esp_wifi_set_mode(mode); return ble_event_report_wifi_mode(0); } */ ble_qiot_ret_status_t ble_combo_wifi_mode_set(BLE_WIFI_MODE mode); /* 设置WIFI SSID和PASSWORD,示例: ble_qiot_ret_status_t ble_combo_wifi_info_set(const char *ssid, uint8_t ssid_len, const char *passwd, uint8_t passwd_len) { ...... memcpy(wifi_config.sta.ssid, ssid, ssid_len); memcpy(wifi_config.sta.password, passwd, passwd_len); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); return ble_event_report_wifi_info(0); } */ ble_qiot_ret_status_t ble_combo_wifi_info_set(const char *ssid, uint8_t ssid_len, const char *passwd, uint8_t passwd_len); /* 使用设置的WIFI信息请求连接WIFI,示例: ble_qiot_ret_status_t ble_combo_wifi_connect() { ESP_ERROR_CHECK(esp_wifi_connect()); return 0; } */ ble_qiot_ret_status_t ble_combo_wifi_connect();
inc/ble_qiot_import.h
中定义了LLSync SDK
依赖的蓝牙协议栈HAL
实现,需要您在自己的硬件平台上进行实现。/* 通过ble_get_qiot_services获取蓝牙服务,将蓝牙服务添加到协议栈。配网功能使用的服务UUID是0xFFF0,特征值UUID是0xFFE1和0xFFE3。您也可以选择其他方式将服务添加到协议栈,示例为ESP32上添加蓝牙服务代码: static uint8_t llsync_service_uuid[16] = { 0xe2, 0xa4, 0x1b, 0x54, 0x93, 0xe4, 0x6a, 0xb5, 0x20, 0x4e, 0xd0, 0x65, 0xf0, 0xff, 0x00, 0x00,}; static uint8_t llsync_device_info_uuid[16] = { 0xe2, 0xa4, 0x1b, 0x54, 0x93, 0xe4, 0x6a, 0xb5, 0x20, 0x4e, 0xd0, 0x65, 0xe1, 0xff, 0x00, 0x00,}; static uint8_t llsync_event_uuid[16] = { 0xe2, 0xa4, 0x1b, 0x54, 0x93, 0xe4, 0x6a, 0xb5, 0x20, 0x4e, 0xd0, 0x65, 0xe3, 0xff, 0x00, 0x00,}; static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] = { [IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, sizeof(llsync_service_uuid), sizeof(llsync_service_uuid), (uint8_t *)llsync_service_uuid}}, [IDX_CHAR_A] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_write}}, [IDX_CHAR_VAL_A] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_128, (uint8_t *)llsync_device_info_uuid, ESP_GATT_PERM_WRITE, LLSYNC_CHAR_VAL_LEN_MAX, 0, NULL}}, [IDX_CHAR_C] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_notify}}, [IDX_CHAR_VAL_C] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_128, (uint8_t *)llsync_event_uuid, 0, LLSYNC_CHAR_VAL_LEN_MAX, 0, NULL}}, [IDX_CHAR_CFG_C] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}}, } …… esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID); }; */ void ble_services_add(const qiot_service_init_s *p_service); /* 通过ble_get_my_broadcast_data获取广播数据,将其作为厂商数据开始广播,Compnay ID是0xFEE7。示例: …… adv_info_s my_adv_info; adv_data_len = ble_get_my_broadcast_data((char *)adv_data, sizeof(adv_data)); my_adv_info.manufacturer_info.company_identifier = TENCENT_COMPANY_IDENTIFIER; my_adv_info.manufacturer_info.adv_data = adv_data; my_adv_info.manufacturer_info.adv_data_len = adv_data_len; ble_advertising_start(&my_adv_info); …… ble_qiot_ret_status_t ble_advertising_start(adv_info_s *adv) { static uint8_t raw_adv_data[32] = {0x02, 0x01, 0x06, 0x03, 0x03, 0xF0, 0xFF}; uint8_t usr_adv_data[31] = {0}; uint8_t len = 0; uint8_t index = 0; memcpy(usr_adv_data, &adv->manufacturer_info.company_identifier, sizeof(uint16_t)); len = sizeof(uint16_t); memcpy(usr_adv_data + len, adv->manufacturer_info.adv_data, adv->manufacturer_info.adv_data_len); len += adv->manufacturer_info.adv_data_len; index = 7; raw_adv_data[index++] = len + 1; raw_adv_data[index++] = 0xFF; // 添加厂商广播数据 memcpy(raw_adv_data + index, usr_adv_data, len); index += len; …… esp_log_buffer_hex("adv", raw_adv_data, index); esp_err_t ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, index); if (ret) { ESP_LOGE(LLSYNC_LOG_TAG, "config adv data failed, error code = %x", ret); } adv_config_done |= ADV_CONFIG_FLAG; esp_ble_gap_start_advertising(&adv_params); return 0; } */ ble_qiot_ret_status_t ble_advertising_start(adv_info_s *adv); /* 停止广播接口,示例: ble_qiot_ret_status_t ble_advertising_stop(void) { esp_ble_gap_stop_advertising(); return 0; } */ ble_qiot_ret_status_t ble_advertising_stop(void); /* 特征值UUID FFE3向小程序写数据的接口,示例: ble_qiot_ret_status_t ble_send_notify(uint8_t *buf, uint8_t len) { esp_ble_gatts_send_indicate(llsync_profile_tab[PROFILE_APP_IDX].gatts_if, llsync_profile_tab[PROFILE_APP_IDX].conn_id, llsync_handle_table[IDX_CHAR_VAL_C], len, buf, false); return BLE_QIOT_RS_OK; } */ ble_qiot_ret_status_t ble_send_notify(uint8_t *buf, uint8_t len); /* 用户指定MTU。当BLE_QIOT_REMOTE_SET_MTU设置为1时,小程序连接设备后会去修改MTU并通知设备。否则使用默认MTU(23)进行通信。 uint16_t ble_get_user_data_mtu_size(e_system type) { return 128; } */ uint16_t ble_get_user_data_mtu_size(e_system type);
c. API调用
inc/ble_qiot_export.h
中定义了LLSync SDK
对外提供的API
。/* 获取LLSync蓝牙服务,您可以在代码中获取蓝牙服务后将蓝牙服务添加到蓝牙协议栈*/ const qiot_service_init_s *ble_get_qiot_services(void); /* LLSync SDK初始化接口,主要进行蓝牙服务添加。默认在start_device_btcomboconfig内已调用,您也可以选择在其他位置调用。*/ ble_qiot_ret_status_t ble_qiot_explorer_init(void); /* LLSync广播启动接口,请你选择合适的位置调用。例如在蓝牙断开时重新开始广播。 …… ESP_LOGI(LLSYNC_LOG_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason); ble_qiot_advertising_start(); …… */ ble_qiot_ret_status_t ble_qiot_advertising_start(void); /* LLSync广播停止接口,请你选择合适的位置调用。例如在配网结束时停止广播。 int stop_device_btcomboconfig(void) { ble_qiot_advertising_stop(); return QCLOUD_RET_SUCCESS; } */ ble_qiot_ret_status_t ble_qiot_advertising_stop(void); /* GAP连接时通知LLSync SDK。例如: case ESP_GATTS_CONNECT_EVT: …… esp_ble_gap_update_conn_params(&conn_params); ble_gap_connect_cb(); …… */ void ble_gap_connect_cb(void); /* GAP断开时通知LLSync SDK。例如: …… ESP_LOGI(LLSYNC_LOG_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason); ble_gap_disconnect_cb(); …… */ void ble_gap_disconnect_cb(void); /* 小程序无法获取蓝牙MTU接口,设备端收到MTU修改通知后调用此接口通知小程序。例如: case ESP_GATTS_MTU_EVT: ESP_LOGI(LLSYNC_LOG_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); ble_event_sync_mtu(param->mtu.mtu); break; */ ble_qiot_ret_status_t ble_event_sync_mtu(uint16_t llsync_mtu); /* 特征值UUID FFE1数据处理接口,收到数据后调用此接口。例如: case ESP_GATTS_WRITE_EVT: if (!param->write.is_prep) { // the data length of gattc write must be less than LLSYNC_CHAR_VAL_LEN_MAX. if (param->write.handle == llsync_handle_table[IDX_CHAR_VAL_A]) { ble_device_info_write_cb(param->write.value, param->write.len);} …… } */ void ble_device_info_write_cb(const uint8_t *buf, uint16_t len); /* 设置WIFI Mode后,设备端给小程序回复设置结果。result 为0表示设置成功,其他表示错误。示例: ble_qiot_ret_status_t ble_combo_wifi_mode_set(BLE_WIFI_MODE mode) { if (mode != BLE_WIFI_MODE_STA){ return ble_event_report_wifi_mode(-1) } ESP_ERROR_CHECK(esp_wifi_set_mode(mode); return ble_event_report_wifi_mode(0); } */ ble_qiot_ret_status_t ble_event_report_wifi_mode(uint8_t result); /* 设置WIFI信息后,设备端给小程序回复设置结果。result 为0表示设置成功,其他表示错误。示例: ble_qiot_ret_status_t ble_combo_wifi_info_set(const char *ssid, uint8_t ssid_len, const char *passwd, uint8_t passwd_len) { ...... memcpy(wifi_config.sta.ssid, ssid, ssid_len); memcpy(wifi_config.sta.password, passwd, passwd_len); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); return ble_event_report_wifi_info(0); } */ ble_qiot_ret_status_t ble_event_report_wifi_info(uint8_t result); /* WIFI连接结束后,将WIFI连接结果回复给小程序。WIFI连接成功后回复示例: static void bt_combo_report_wificonn_success() { wifi_config_t cfg; esp_wifi_get_config(WIFI_IF_STA, &cfg); ble_event_report_wifi_connect(BLE_WIFI_MODE_STA, BLE_WIFI_STATE_CONNECT, (uint8_t)strlen((const char *)cfg.sta.ssid), (const char *)cfg.sta.ssid); } 注意:WIFI连接失败也要回复小程序。 */ ble_qiot_ret_status_t ble_event_report_wifi_connect(BLE_WIFI_MODE mode, BLE_WIFI_STATE state, uint8_t ssid_len, const char *ssid);
-
C SDK
移植请参考C SDK使用参考。C SDK
已经实现了配网代码框架,您只需要根据需要实现相关的HAL
接口即可。为进一步简化适配步骤,在qcloud-iot-explorer-BLE-sdk-embedded/lib/qcloud_iot_c_sdk
目录下已经实现了部分C SDK
的HAL接口,您需要拷贝文件覆盖qcloud-iot-explorer-sdk-embedded-c/platform
和qcloud-iot-explorer-sdk-embedded-c/sdk_src/wifi_config
目录下对应文件。cp qcloud-iot-explorer-BLE-sdk-embedded/lib/qcloud_iot_c_sdk/platform/* qcloud-iot-explorer-sdk-embedded-c/platform/ cp qcloud-iot-explorer-BLE-sdk-embedded/lib/qcloud_iot_c_sdk/sdk_src/* qcloud-iot-explorer-sdk-embedded-c/sdk_src/wifi_config/
您还需要实现以下HAL接口以完成配网功能的适配工作。
/* COMBO设备配网功能启动接口,默认调用了LLSync SDK初始化接口,您可以按照需要修改。示例 int start_device_btcomboconfig(void) { // TODO other init(例如蓝牙协议栈初始化) ble_qiot_explorer_init(); // init llsync sdk return QCLOUD_RET_SUCCESS; } */ int start_device_btcomboconfig(void); /* COMBO设备配网功能关闭接口,默认调用了LLSync停止广播接口,您可以按照需要修改。示例 int stop_device_btcomboconfig(void) { ble_qiot_advertising_stop(); // TODO other deinit return QCLOUD_RET_SUCCESS; } */ int stop_device_btcomboconfig(void) /* WIFI连接成功后上报给小程序,您需要获取当前连接的WIFI信息并调用ble_event_report_wifi_connect接口上报。示例 static void bt_combo_report_wificonn_success() { wifi_config_t cfg; esp_wifi_get_config(WIFI_IF_STA, &cfg); ble_event_report_wifi_connect(BLE_WIFI_MODE_STA, BLE_WIFI_STATE_CONNECT, (uint8_t)strlen((const char *)cfg.sta.ssid), (const char *)cfg.sta.ssid); } */ void bt_combo_report_wificonn_success(); /* 设置WIFI配置状态,当WIFI信息设置后根据结果修改状态。示例 …… ESP_LOGI(TAG, "wifi info %s, %s", wifi_config.sta.ssid,wifi_config.sta.password); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); // WIFI信息设置成功 set_bt_combo_config_state(WIFI_CONFIG_SUCCESS); …… */ void set_bt_combo_config_state(eWiFiConfigState state); /* 获取WIFI连接状态,建议在获取到IP后才认为WIFI连接成功。示例 bool HAL_Wifi_IsConnected(void) { // TODO, retrun true when you got ip return false; } */ bool HAL_Wifi_IsConnected(void); /* 配网错误日志操作接口,需要固化存储日志时可以实现以下接口。当出现错误时设备存储日志,设备重启时读取日志,防止错误信息丢失。 */ int HAL_Wifi_read_err_log(uint32_t offset, void *log, size_t log_size); int HAL_Wifi_write_err_log(uint32_t offset, void *log, size_t log_size); int HAL_Wifi_clear_err_log(void);
qcloud-iot-explorer-sdk-embedded-c/sdk_src/internal_inc/qcloud_wifi_config.h
中进行选择配网功能配置。#define WIFI_ERR_LOG_POST 1 // 配网出错时向小程序上报配网错误日志 #define WIFI_LOG_UPLOAD 1 // 配网出错时向小程序上报配网过程日志 #define WIFI_PROV_BT_COMBO_CONFIG_ENABLE 1 // 选择LLSync配网功能,soft ap,airkiss等配网选项需要关闭