FindMy 与查找网络示例
本页介绍 AW333N BLE SDK 中 FindMy(Apple 查找网络 / FMNA)与定位器示例的实现:demo app 的接入方式、FMNA 预编译库的集成、产测模块 findmy_product_test.c 的 HID 透传协议、SN/UUID/Token 管理以及双 Bank OTA 升级流程。
Purpose and Scope
本页覆盖 apps/demo/transfer 工程中与 FindMy / 查找网络 相关的完整能力:
- demo app 如何通过
CONFIG_APP_FINDMY接入(app_main.c); - FMNA(Find My Network Accessory)预编译库的内存布局与链接脚本约定;
- 产测模块 findmy_product_test.c 的通道、命令、帧格式与加解密;
- SN / UUID / Token 的读取与写入、Flash UUID 读取;
- 基于 Dongle(USB HID)中转 BLE 的产测链路与 OTA 远端升级。
不覆盖(属于其他页面的话题):Google FHN 例子(app_main.c 中提及的另一个定位器方案)、通用 BLE 透传/数传、AT 模组指令、HID 人机交互等。这些能力各有独立的示例与文档,本页只做交叉引用。
Overview
FindMy 是 Apple 的查找网络(Find My Network),配件端需要实现 FMNA 协议(广播密钥轮换、附近设备上报等)。在 AW333N SDK 中,FMNA 协议栈以预编译固件库形式提供:产测模块通过 fmna_api.h 调用其接口,链接脚本则为其预留独立的 _fmna_data_begin / _fmna_const_begin 内存段,避免与用户代码的数据区冲突。
示例工程 apps/demo/transfer 是一个多 demo 复用工程,通过编译宏选择当前 app:当定义 CONFIG_APP_FINDMY 时,app 名称为 "findmy",动作分发到 ACTION_FINDMY_MAIN(产测主循环)。配套的产测模块在 FMY_PRODUCT_TEST_MODE 下编译,实现与 PC 产测工具 的完整通信:
- 通信拓扑:PC 产测工具 ↔ USB HID Dongle ↔ BLE 空中链路 ↔ AW333N 设备;
- 支持产测数据帧的分包(64 字节 HID 报告)、CRC16 校验与可选的逐包加密(K 值由设备 MAC 与产测下发数据联合计算);
- 支持 Flash UUID 读取、SN/UUID/Token 读写、UARP 信息透传(查询/停止/取消)以及 1KB 分块的双 Bank OTA 升级;
- 产测完成后可配置无动作、关机(POFF)或复位(RESET),便于产线自动化。
硬件参考:AW333A FindMy 参考原理图见 doc/硬件资料/原理图/AW333A Findmy参考原理图V1.0.pdf;整体适用场景(定位器 Findmy & Find Hub)见 README.md。
Architecture
下图展示 FindMy 示例的整体架构与数据通路(所有节点均对应真实源码/组件):
flowchart TD
subgraph sg_PC["PC 产测端"]
PCTool["PC 产测工具"]
end
subgraph sg_Dongle["中转层"]
Dongle["BLE Dongle 固件<br/>USB HID 64B 帧"]
end
subgraph sg_Device["AW333N 设备端"]
AppMain["app_main.c<br/>CONFIG_APP_FINDMY → ACTION_FINDMY_MAIN"]
ProdTest["findmy_product_test.c<br/>产测模块 (FMY_PRODUCT_TEST_MODE)"]
FMNALib["FMNA 预编译库<br/>fmna_api.h / fmy_state_idle_set_active"]
BLEStack["BLE 协议栈<br/>GATT 特征 0x0029"]
Flash["Flash 存储<br/>SN / UUID / Token / Flash UUID"]
OTA["双 Bank OTA<br/>dual_bank_updata_api_v2"]
end
PCTool -->|"HID 帧(0x4A5E + channel + CRC + 0xBD)"| Dongle
Dongle -->|"BLE 空中链路"| BLEStack
BLEStack --> ProdTest
AppMain -->|"动作分发"| ProdTest
ProdTest --> FMNALib
ProdTest --> Flash
ProdTest --> OTA
OTA -->|"dual_bank_updata_api_v2.h"| Flash
FMNALib --> BLEStack
各组件职责:
| 组件 | 源码位置 | 职责 |
|---|---|---|
| App 入口 | app_main.c | 通过 CONFIG_APP_FINDMY 选择 findmy app,设置名称与动作 |
| 产测模块 | findmy_product_test.c | 命令解析、SN/UUID/Token 管理、帧封装、OTA 状态机 |
| FMNA 库 | fmna_api.h(闭源库) | 实现 Apple 查找网络协议;提供 doe() 加解密、fmy_state_idle_set_active() 空闲控制 |
| 链接脚本 | btstack_lib.ld 等 | 为 FMNA 库预留 _fmna_data_begin / _fmna_const_begin 数据段 |
| 存储 | tzflash_read_flash_uuid() / Flash 写接口 | 产测数据持久化 |
| OTA | dual_bank_updata_api_v2.h(BD57 平台) | 双 Bank 升级,START → WRITE → VERIFY |
设计意图:FMNA 协议栈闭源交付,SDK 通过链接脚本段符号(_fmna_data_begin、_fmna_const_begin)把库的全局数据固定到专属内存区域,用户程序只需链接即可使用,无需了解协议细节;产测链路则刻意与协议栈解耦——所有产测数据统一走“HID 帧 → Dongle → BLE → GATT 特征”的透传管道,使产测工具与设备端只需关心帧协议,不依赖 FMNA 内部实现。
Demo App 接入与启动流程
示例工程 apps/demo/transfer 采用“多 app 复用”结构,通过编译宏决定当前运行的 demo。app_main.c 中通过 CONFIG_APP_FINDMY 将 findmy 注册为可选 app:
#elif CONFIG_APP_FINDMY
it->name = "findmy";
it->action = ACTION_FINDMY_MAIN;
Source: app_main.c
该 app 条目包含两个关键字段:
name:app 名称"findmy",用于日志与 UI 展示;action:ACTION_FINDMY_MAIN,动作 ID,由框架消息循环分发到产测模块的主处理函数。
app_main.c 还在 app 切换逻辑中再次出现 findmy 分支(case 0 时默认选择 findmy),并在注释中明确说明移植方式:
//示例只修改findmy和google fhn切换例子,其他app参考移植
switch (next_app) {
...
case 0:
#if CONFIG_APP_FINDMY
it->name = "findmy";
it->action = ACTION_FINDMY_MAIN;
#endif
Source: app_main.c
设计意图:name + action 的抽象把“app 是什么”和“app 做什么”分离——不同 demo(findmy、google FHN、透传等)共用同一套 app 框架,切换只需改编译宏与少量 case 分支,其余代码参考移植即可。
FMNA 库与链接脚本约定
FMNA 协议栈以预编译库形式链接进固件。为确保库的内部全局变量与常量不被用户代码的段布局破坏,三个链接脚本都为 findmy 预留了专属段:
/*findmy*/
. = ALIGN(4);
_fmna_data_begin = .;
Source: btstack_lib.ld
同样的 /*findmy*/ 段标记还出现在:
- btstack_lib.ld#L53-L54 与
_fmna_const_begin(btstack_lib.ld#L130-L131); - btstack_lib_data.ld#L21-L22(data 段
_fmna_data_begin); - btstack_lib_bss.ld#L18-L19(bss 段对齐)。
产测模块通过 fmna_api.h 访问库接口,并声明了三个外部符号(findmy_product_test.c#L150-L152):
extern void doe(u16 k, void *pBuf, u32 lenIn, u32 addr); // 加解密
extern void fmy_state_idle_set_active(uint8_t active); // FMNA 空闲状态控制
extern void tzflash_read_flash_uuid(u8 *uuid_buf); // 读 Flash UUID
设计意图:_fmna_*_begin 段符号是“库 ↔ 链接脚本”的契约——库内部用这些符号定位自己的数据区起点,因此用户不得删除或移动这些段标记,否则 FMNA 库的全局状态会错乱;而 doe() 之类的符号则说明加解密能力由 FMNA 库提供,产测模块只负责计算 K 值并调用。
产测模块总览
findmy_product_test.c 整个文件由 #if FMY_PRODUCT_TEST_MODE 保护,即仅在产测固件中编译。它的核心职责是把 PC 产测工具的指令通过 Dongle 透传到设备,并返回结果。
通讯通道
数据在“PC ↔ Dongle ↔ 设备”之间按通道区分用途:
enum {
HID_RX_HANDLER_CHANNEL_COMMAND = 0x00 + DONGLE_OTA_VERSION,
HID_RX_HANDLER_CHANNEL_RESPONSE = 0x10 + DONGLE_OTA_VERSION,
HID_RX_HANDLER_CHANNEL_USB = 0x20 + DONGLE_OTA_VERSION,
HID_RX_HANDLER_CHANNEL_REMOTE1 = 0x30 + DONGLE_OTA_VERSION,
HID_RX_HANDLER_CHANNEL_REMOTE2 = 0x40 + DONGLE_OTA_VERSION,
...
HID_RX_HANDLER_CHANNEL_REMOTE8 = 0xA0 + DONGLE_OTA_VERSION,
};
Source: findmy_product_test.c
| 通道 | 用途 |
|---|---|
0x00 | PC → Dongle 命令通道 |
0x10 | Dongle → PC 响应通道 |
0x20 | PC → USB 透传 |
0x30 ~ 0xA0 | 远端升级透传(8 路,channel_3 ~ channel_9) |
产测命令集
enum {
APP_CMD_GET_FMY_PRODUCT_FLASH_UUID = 0x51, // 读 Flash UUID
APP_CMD_SET_FMY_PRODUCT_TEST_STATUS, // 0x52 设置产测状态
APP_CMD_GET_FMY_PRODUCT_INFO, // 0x53 获取产测信息
APP_CMD_GET_FMY_SN_AND_TOKEN, // 0x54 读 SN 与 Token
APP_CMD_SET_FMY_SN_AND_TOKEN, // 0x55 写 SN 与 Token
APP_CMD_TRANS_FMY_UARP_INFO = 0x5A, // UARP 信息透传
APP_CMD_QUERY_FMY_UARP_DATA, // 0x5B 查询 UARP 数据
APP_CMD_STOP_TRANS_FMY_UARP, // 0x5C 停止透传
APP_CMD_CANCEL_TRANS_FMY_UARP, // 0x5D 取消透传
};
Source: findmy_product_test.c
命令 0x51~0x55 负责产测基础项(Flash UUID、产测状态、产品信息、SN/Token 读写);0x5A~0x5D 负责 UARP(FMNA 相关的上行/下行透传数据)的传输控制,用于产测工具与设备间搬运 FMNA 协议数据。
产测状态机
//findmy产测状态
#define FINDMY_PRODUCT_TEST_DONE 0x00
#define FINDMY_PRODUCT_TEST_BEGIN 0x01
#define FINDMY_PRODUCT_TEST_FAIL 0x02
//产测完成后的动作
#define FINDMY_PRODUCT_TEST_DONE_NO_ACTION 0x00
#define FINDMY_PRODUCT_TEST_DONE_POFF 0x01
#define FINDMY_PRODUCT_TEST_DONE_RESET 0x02
Source: findmy_product_test.c
stateDiagram-v2
[*] --> BEGIN: 产测开始
BEGIN --> DONE: 全部项目通过
BEGIN --> FAIL: 任一项失败
DONE --> POFF: 配置关机
DONE --> RESET: 配置复位
DONE --> [*]: 无动作
FAIL --> BEGIN: 重新测试
命令处理结果使用独立状态码:FINDMY_CMD_HANDLE_SUCCESS 0x00 / FINDMY_CMD_HANDLE_FAIL 0x01,未处理状态为 FINDMY_CMD_STATUS_NULL 0xFF;读 SN/UUID/Token 时另有 READ_SN_CMD_HANDLE_NULL 0x02 表示数据为空(findmy_product_test.c#L50-L59)。
HID 帧协议(Dongle ↔ PC)
产测数据在 PC 与 Dongle 之间以 USB HID 报告承载。帧封装由 fmy_send_ble_data_to_dongle() 实现(设备端 BLE 收到的数据,组帧后发给 Dongle,由 Dongle 直接转发 PC):
static uint8_t fmy_send_ble_data_to_dongle(u16 channel, u8 *data, u16 len, u8 cmd_type)
{
u16 calc_crc;
len = len + 1;//加上cmd_type长度
u8 send_data_tag[HID_SEND_DATA_TAG_LONG - 2 - 1] = {0x4A, 0x5E, channel, 0x00, len / 256, len % 256, cmd_type};
len = len - 1;//去除cmd_type长度
u8 send_data[((len + HID_SEND_DATA_TAG_LONG + 1) / HID_USB_SEND_MAX + 1) * HID_USB_SEND_MAX]; // 包括crc 2bytes
memset(send_data, 0x00, sizeof(send_data));
memcpy(&send_data, &send_data_tag, HID_SEND_DATA_TAG_LONG - 2 - 1);
memcpy(&send_data[HID_SEND_DATA_TAG_LONG - 2 - 1], data, len);
calc_crc = CRC16(&send_data[2], (5 + len)); //crc: config(2)+length(2)+cmd type(1)+payload(len)
send_data[HID_SEND_DATA_TAG_LONG + len - 2 - 1] = (calc_crc >> 8) & 0xFF;
send_data[HID_SEND_DATA_TAG_LONG + len - 2] = calc_crc & 0xFF;
send_data[HID_SEND_DATA_TAG_LONG + len - 1] = HID_RX_HANDLER_TAIL_TAG;
//发送数据分包为每包64bytes发包,包含填包
u8 i = (len + HID_SEND_DATA_TAG_LONG) / HID_USB_SEND_MAX;
i = ((len + HID_SEND_DATA_TAG_LONG) % HID_USB_SEND_MAX) ? (i + 1) : (i);
for (u8 j = 1; j <= i; j++) {
log_info("fmy send ble data to dongle %d", j);
put_buf(&send_data[(j - 1) * 64], HID_USB_SEND_MAX);
//加密
if (crypt_k_value) {
doe(crypt_k_value, &send_data[(j - 1) * 64], HID_USB_SEND_MAX, 0);
}
}
...
}
Source: findmy_product_test.c
帧格式(HID_SEND_DATA_TAG_LONG = 10 为除 payload 外的包长度,HID_USB_SEND_MAX = 64):
| 偏移 | 内容 | 长度 | 说明 |
|---|---|---|---|
| 0~1 | 帧头 0x4A 0x5E | 2 | HID_RX_HANDLER_HEND_TAG |
| 2 | channel | 1 | 命令/响应/透传/OTA 通道 |
| 3 | 保留 | 1 | 0x00(HID_SEND_DATA_TAG_LONG - 2 - 1 后的高位长度位) |
| 4~5 | len | 2 | cmd_type + payload 长度 |
| 6 | cmd_type | 1 | 命令类型(如 0x55 写 SN/Token) |
| 7~N | payload | len | 有效数据 |
| N+1~N+2 | CRC16 | 2 | 覆盖 config(2) + length(2) + cmd(1) + payload |
| N+3 | 帧尾 0xBD | 1 | HID_RX_HANDLER_TAIL_TAG |
设计意图:
- 64 字节分包:USB HID 中断传输的报告长度固定为 64B,超长数据必须切包,接收端通过帧头/长度字段重组;
- CRC16 完整性:校验范围覆盖配置、长度、命令与负载,保证产测数据不被 USB 传输干扰破坏;
- 逐包加密:当
crypt_k_value非 0 时,每个 64B 包在发送前调用doe(k, buf, 64, 0)加密,防止空中/总线上产测数据(如 Token)被窃取;K 值为 0 时明文发送,兼容尚未配对/未下发密钥的阶段。
K 值计算
K 值是加解密的会话密钥,由 产测工具下发的 6 字节数据 + 设备 BLE MAC 6 字节 拼接成 12 字节后做 CRC16 得到:
void fmy_product_test_caculate_k_value(u8 *data, u8 len)
{
u8 tmp_addr12[12] = {0};
memcpy(&tmp_addr12[0], data, len);
memcpy(&tmp_addr12[6], &findmy_ble_mac[0], 6);
log_info_hexdump(tmp_addr12, 12);
crypt_k_value = CRC16(tmp_addr12, 12);
log_info("crypt_k_value: 0x%x", crypt_k_value);
}
Source: findmy_product_test.c
设备 BLE MAC 由 fmy_product_test_add_dev_mac() 写入:
void fmy_product_test_add_dev_mac(u8 *data, u8 len)
{
memcpy(&findmy_ble_mac[0], data, len);
}
Source: findmy_product_test.c
密钥绑定设备 MAC 的设计,使同一产测工具的会话密钥无法跨设备复用,提升产测数据(尤其 Token)的保密性。
SN / UUID / Token 管理
产测的核心数据是 SN(序列号)、UUID(查找网络标识)与 Token(认证令牌)。文件用一条宏定义了它们组合后的最大长度:
//定义ret\SN\UUID\TOKEN的固定长度
#define RET_SN_UUID_TOKEN_DATA_LEN 1 + 3 + 16 + 3 + 1024 + 3 + 16
Source: findmy_product_test.c
布局为:状态(1B) + SN[长度(3B) + 内容(16B)] + UUID[长度(3B) + 内容(1024B)] + Token[长度(3B) + 内容(16B)]。每段都带 3 字节长度前缀,采用 TLV 风格,接收端可精确切分变长字段;UUID 预留 1024B,说明产测允许写入较大体积的查找网络标识数据。
配套的产测信息变量(findmy_product_test.c#L136-L138):
static uint8_t FMY_ManufacturerName[64];
static uint8_t FMY_ModelName[64];
static uint8_t fmy_product_data[8];
这些变量由 APP_CMD_GET_FMY_PRODUCT_INFO(0x53)返回给产测工具。设备侧 Flash UUID 通过 tzflash_read_flash_uuid() 读取,由 APP_CMD_GET_FMY_PRODUCT_FLASH_UUID(0x51)命令触发——Flash UUID 用于在产测端唯一标识芯片,防止 SN 与芯片绑定错位。
BLE 数据通路
设备端 BLE 接收/发送路径的关键常量:
#define BLE_DATA_SEND_MAX 240
#define FMY_DEBUG_RX_ATT_HANDLE 0x0029
Source: findmy_product_test.c
BLE_DATA_SEND_MAX = 240:BLE 单包最大数据长度(长包/MTU 相关的发送上限);FMY_DEBUG_RX_ATT_HANDLE = 0x0029:调试接收特征ATT_CHARACTERISTIC_4F860005-943B-49EF-BED4-2F730304427A_01的 value handle,产测命令经该特征值写入设备;- 接收侧维护
ble_recv_long_buf与ota_buff_len,用于重组跨包到达的长数据; - 数据从 BLE 收到后,经
fmy_send_ble_data_to_dongle()组帧并转发给 Dongle → PC(见上文 HID 帧协议)。
OTA 升级(双 Bank)
产测还承担固件升级能力,允许产线通过同一链路刷写固件。关键定义:
#define FMY_REQ_OTA_DATA_MAX 1024 //一次申请1K升级数据
enum {
APP_OTA_UPDATE_SUCCESS,
APP_OTA_TRANS_FAILED,
APP_OTA_UPDATE_FAILED,
};
typedef enum {
DUAL_OTA_STEP_START = 0,
DUAL_OTA_STEP_WRITE,
DUAL_OTA_STEP_VERIFY,
} DUAL_OTA_STEP;
struct file_parameter_t {
uint16_t file_version;
uint32_t file_size;
uint16_t crc;
} __attribute__((packed));
struct file_parameter_t file_parameter;
static uint32_t global_file_offset = 0;
Source: findmy_product_test.c
要点:
- 分块传输:每次申请 1KB(
FMY_REQ_OTA_DATA_MAX)升级数据,global_file_offset记录写入偏移,配合file_parameter_t(版本、大小、CRC)做完整性校验; - 三步状态机:
DUAL_OTA_STEP_START → WRITE → VERIFY,升级包先写入 Bank 1,校验通过后再切换启动 Bank; - 双 Bank API:在
CONFIG_CPU_BD57平台包含dual_bank_updata_api_v2.h(findmy_product_test.c#L24-L26),升级失败不损坏当前固件; - 远端透传通道:升级数据走
0x30~0xA0的 8 路远端升级通道,与普通命令/响应通道(0x00/0x10)隔离,避免升级流量阻塞产测命令。
Core Flow(核心流程)
产测主流程:写 SN / UUID / Token
以 APP_CMD_SET_FMY_SN_AND_TOKEN(0x55)为例,展示从 PC 产测工具到设备 Flash 的完整链路:
sequenceDiagram
participant PC as PC 产测工具
participant DG as Dongle (USB HID)
participant DV as AW333N 设备
participant FL as Flash / FMNA 库
PC->>DG: HID 帧(0x51 读 Flash UUID)
DG->>DV: BLE 透传 (GATT 0x0029)
DV->>DV: 解析命令 & 调 tzflash_read_flash_uuid()
DV-->>DG: 响应(0x10 通道, 组帧+CRC)
DG-->>PC: HID 帧返回 UUID
PC->>DG: HID 帧(0x55 写 SN/UUID/Token, 含 K 值数据)
DG->>DV: BLE 透传
DV->>DV: fmy_product_test_caculate_k_value() 计算 K
DV->>FL: 写入 SN / UUID / Token (TLV 布局)
FL-->>DV: 写结果
DV-->>DG: 0x00 成功 / 0x01 失败
DG-->>PC: 响应帧
流程要点:
- 发现阶段:PC 先发
0x51读取芯片 Flash UUID,用于绑定芯片与 SN; - 密钥协商:PC 下发 6 字节随机数据,设备拼接自身 BLE MAC 得到 12 字节,
CRC16生成crypt_k_value——此后双向数据按包加密; - 数据写入:
0x55携带 TLV 格式的 SN/UUID/Token,设备校验并持久化; - 结果回传:设备以
0x10响应通道回FINDMY_CMD_HANDLE_SUCCESS(0x00) 或FAIL(0x01),产测工具据此判定 PASS/FAIL,并按配置触发关机/复位。
产测结束动作
产测完成(FINDMY_PRODUCT_TEST_DONE)后,设备按产测工具下发的配置执行收尾:
flowchart TD
Done["产测完成<br/>FINDMY_PRODUCT_TEST_DONE (0x00)"] --> Action{"结束动作配置"}
Action -->|"0x00"| NoAction["无动作, 继续运行"]
Action -->|"0x01"| Poff["关机<br/>FINDMY_PRODUCT_TEST_DONE_POFF"]
Action -->|"0x02"| Reset["复位<br/>FINDMY_PRODUCT_TEST_DONE_RESET"]
Poff --> End([产线流转])
Reset --> End
NoAction --> End
Usage Examples(源码示例)
示例 1:注册 findmy demo app
在 app_main.c 中通过编译宏选择 findmy app,设置名称与动作:
#elif CONFIG_APP_FINDMY
it->name = "findmy";
it->action = ACTION_FINDMY_MAIN;
Source: app_main.c
示例 2:计算会话密钥 K 值
设备端把产测工具下发的 6 字节与 BLE MAC 拼成 12 字节,CRC16 得到加解密 K 值:
void fmy_product_test_caculate_k_value(u8 *data, u8 len)
{
u8 tmp_addr12[12] = {0};
memcpy(&tmp_addr12[0], data, len);
memcpy(&tmp_addr12[6], &findmy_ble_mac[0], 6);
log_info_hexdump(tmp_addr12, 12);
crypt_k_value = CRC16(tmp_addr12, 12);
log_info("crypt_k_value: 0x%x", crypt_k_value);
}
Source: findmy_product_test.c
示例 3:设置设备 BLE MAC
产测模块记录设备 BLE MAC,供 K 值计算使用:
void fmy_product_test_add_dev_mac(u8 *data, u8 len)
{
memcpy(&findmy_ble_mac[0], data, len);
}
Source: findmy_product_test.c
示例 4:HID 帧组包与加密发送
设备把 BLE 收到的产测数据封装成 HID 帧(帧头 0x4A5E + channel + 长度 + cmd_type + payload + CRC16 + 帧尾 0xBD),按 64B 分包并逐包加密:
u8 send_data_tag[HID_SEND_DATA_TAG_LONG - 2 - 1] = {0x4A, 0x5E, channel, 0x00, len / 256, len % 256, cmd_type};
...
calc_crc = CRC16(&send_data[2], (5 + len)); //crc: config(2)+length(2)+cmd type(1)+payload(len)
send_data[HID_SEND_DATA_TAG_LONG + len - 2 - 1] = (calc_crc >> 8) & 0xFF;
send_data[HID_SEND_DATA_TAG_LONG + len - 2] = calc_crc & 0xFF;
send_data[HID_SEND_DATA_TAG_LONG + len - 1] = HID_RX_HANDLER_TAIL_TAG;
u8 i = (len + HID_SEND_DATA_TAG_LONG) / HID_USB_SEND_MAX;
i = ((len + HID_SEND_DATA_TAG_LONG) % HID_USB_SEND_MAX) ? (i + 1) : (i);
for (u8 j = 1; j <= i; j++) {
put_buf(&send_data[(j - 1) * 64], HID_USB_SEND_MAX);
if (crypt_k_value) {
doe(crypt_k_value, &send_data[(j - 1) * 64], HID_USB_SEND_MAX, 0);
}
}
Source: findmy_product_test.c
Configuration Options(配置项)
FindMy 示例由编译宏与源码常量共同控制,汇总如下:
| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
CONFIG_APP_FINDMY | 编译宏 | 关闭 | 使能 findmy demo app(app_main.c 中选择 "findmy" / ACTION_FINDMY_MAIN) |
FMY_PRODUCT_TEST_MODE | 编译宏 | 关闭 | 使能产测模块编译(findmy_product_test.c 整体由 #if 保护) |
DONGLE_OTA_VERSION | 常量 | 0 | Dongle OTA 版本,参与通道基址计算 |
HID_USB_SEND_MAX | 常量 | 64 | USB HID 单包长度,产测帧分包粒度 |
HID_SEND_DATA_TAG_LONG | 常量 | 10 | 除 payload 外的帧头+CRC+帧尾长度 |
BLE_DATA_SEND_MAX | 常量 | 240 | BLE 单包最大发送长度 |
FMY_REQ_OTA_DATA_MAX | 常量 | 1024 | OTA 单次申请升级数据量(1KB) |
RET_SN_UUID_TOKEN_DATA_LEN | 常量 | 1+3+16+3+1024+3+16 | SN/UUID/Token 组合数据最大长度(TLV) |
FINDMY_CMD_STATUS_NULL | 常量 | 0xFF | 命令未处理状态 |
FINDMY_CMD_HANDLE_SUCCESS/FAIL | 常量 | 0x00/0x01 | 命令处理结果 |
READ_SN_CMD_HANDLE_NULL | 常量 | 0x02 | 读 SN/UUID/Token 结果为空 |
FINDMY_PRODUCT_TEST_DONE/BEGIN/FAIL | 常量 | 0x00/0x01/0x02 | 产测状态 |
FINDMY_PRODUCT_TEST_DONE_* | 常量 | 0x00/0x01/0x02 | 产测完成动作:无动作/关机/复位 |
API Reference(接口参考)
void fmy_product_test_add_dev_mac(u8 *data, u8 len)
记录设备 BLE MAC(6 字节),供会话密钥 K 值计算使用。
参数:
data(u8*):MAC 数据指针len(u8):MAC 长度
返回: 无
Source: findmy_product_test.c#L165-L168
void fmy_product_test_caculate_k_value(u8 *data, u8 len)
将产测工具下发的 6 字节与设备 BLE MAC 拼成 12 字节,CRC16 计算加解密 K 值并存入全局 crypt_k_value。
参数:
data(u8*):产测工具下发的数据(6 字节)len(u8):数据长度
返回: 无;副作用:更新 crypt_k_value,为 0 时后续发送不加密。
Source: findmy_product_test.c#L181-L192
static uint8_t fmy_send_ble_data_to_dongle(u16 channel, u8 *data, u16 len, u8 cmd_type)
将数据封装为 HID 帧(帧头 0x4A5E + channel + 长度 + cmd_type + payload + CRC16 + 0xBD 帧尾),按 64B 分包发送,crypt_k_value 非 0 时逐包调用 doe() 加密。
参数:
channel(u16):通讯通道(命令0x00/ 响应0x10/ USB 透传0x20/ 远端升级0x30~0xA0)data(u8*):payloadlen(u16):payload 长度cmd_type(u8):命令类型(0x51~0x5D)
返回: 发送状态(uint8_t)。
Source: findmy_product_test.c#L205-L240
外部库接口(由 FMNA 库 / 平台提供)
| 签名 | 说明 |
|---|---|
extern void doe(u16 k, void *pBuf, u32 lenIn, u32 addr) | FMNA 库加解密函数,k 为 K 值 |
extern void fmy_state_idle_set_active(uint8_t active) | 控制 FMNA 空闲状态激活 |
extern void tzflash_read_flash_uuid(u8 *uuid_buf) | 读取芯片 Flash UUID |
Source: findmy_product_test.c#L150-L152
Failure Modes, Edge Cases & Concurrency(故障模式与边界)
- 命令未处理:
FINDMY_CMD_STATUS_NULL (0xFF)表示命令尚未进入处理流程,产测工具侧应将其视为超时/重试信号。 - 读数据为空:
READ_SN_CMD_HANDLE_NULL (0x02)区分“读取失败”与“数据不存在”,产线可据此决定是否先写 SN 再校验。 - CRC 校验失败:帧尾与 CRC16 覆盖整帧关键字段,任一字节被 USB 传输破坏都会导致接收端丢弃该帧;分包重组的计数变量
all_packet/remain_packet用于恢复同步。 - 加密降级:
crypt_k_value == 0时明文发送——必须保证在写入 Token 之前完成 K 值协商,否则 Token 会明文暴露。 - OTA 失败分类:
APP_OTA_TRANS_FAILED(传输层失败,可重传)与APP_OTA_UPDATE_FAILED(写入/校验失败)分离,配合双 Bank 机制保证升级失败不损坏当前固件;DUAL_OTA_STEP(START→WRITE→VERIFY)任一阶段失败都会中止。 - 并发/连接管理:8 路远端升级透传通道(
0x30~0xA0)可并行;模块用全局fmy_conn_handle记录当前 BLE 连接句柄,产测期间应保证单连接,断连时需复位fmy_product_test_begined等状态标志。 - 产测中断恢复:
global_file_offset与file_parameter(版本/大小/CRC)保证 OTA 可断点续传或重传;产测状态标志fmy_product_test_begined防止重复进入产测流程。
Performance & Operational Notes(性能与运维)
- 链路瓶颈在 HID 分包:每 64B 一包,长数据(如 1024B UUID)会拆成多包,
HID_SEND_DATA_TAG_LONG(10) 的帧开销占比约 15%,产线吞吐由 USB 轮询周期决定。 - OTA 分块粒度:单次申请 1KB,
global_file_offset顺序写入,配合 VERIFY 阶段的 CRC 校验,兼顾速度与可靠性。 - 加密开销:
doe()逐包(64B)执行,K 值协商一次后全程复用,产测周期内开销可忽略。 - 产线自动化:产测完成后可配置
POFF(关机)或RESET(复位),便于流水线流转;FINDMY_PRODUCT_TEST_DONE_NO_ACTION供调试。 - 日志:模块启用
LOG_ERROR/DEBUG/INFO/DUMP/CLI(LOG_TAG "[FMY_PROD_TEST]"),关键路径(K 值、分包序号、hexdump)均有打印,产线问题可据此定位。
Extension Points(扩展点)
- 新增产测命令:在命令枚举(
0x51~0x5D)后追加新命令号,并在接收分发处增加对应处理分支即可;响应统一走0x10通道与同一帧封装函数。 - 新增通讯通道:通道枚举(
0x00~0xA0)按 0x10 步进扩展,远端升级通道已预留 8 路,可仿照增加。 - 移植到其他 app:
app_main.c注释明确说明“示例只修改 findmy 和 google fhn 切换例子,其他 app 参考移植”——新定位器方案只需提供name与action并在switch中注册。 - FMNA 库能力:通过
fmna_api.h暴露的接口(如fmy_state_idle_set_active())控制查找网络行为;库为闭源,接口以 extern 声明为准。 - 硬件平台:
CONFIG_CPU_BD57时启用dual_bank_updata_api_v2.h双 Bank 升级;其他平台的 OTA 实现需按平台 API 替换。
Tests(测试与验证)
仓库中未提供独立的单元测试文件;FindMy 示例的验证主要依赖:
- 产测固件自检:
findmy_product_test.c本身即产线测试逻辑,通过 PC 产测工具逐项下发命令并核对响应状态码; - 硬件验证:参考原理图 AW333A Findmy参考原理图V1.0.pdf 搭建定位器硬件;
- 端到端联调:PC 工具 ↔ Dongle ↔ 设备链路,覆盖 Flash UUID 读取、SN/Token 读写、UARP 透传与 OTA 升级四个环节。
Related Links
- README.md — BLE 透传/数传与定位器场景
- findmy_product_test.c — 产测模块实现
- app_main.c — demo app 注册与切换
- btstack_lib.ld — FMNA 库链接段
- btstack_lib_data.ld — FMNA data 段
- btstack_lib_bss.ld — FMNA bss 段
- AW333A Findmy 参考原理图
相关主题:BLE 透传/数传示例、AT 模组指令、Google FHN 定位器示例(见
app_main.c中的 app 切换逻辑)属于独立页面,本页不再展开。