我做了个能挂在胸前的社交距离可穿戴设备,核心模组选了U-blox的NINA-B4蓝牙模块。戴上它的两个人只要靠近到设定阈值距离,设备就会同时亮灯加震动。之所以没用手机App方案,是因为车间和工地上没人会一直开着蓝牙App,后台扫描还经常被系统杀掉,独立硬件才是这类场景真正能落地的形态。整个项目从原型到稳定版本迭代了几轮,踩了一堆RSSI测距的坑,这篇把硬件选型、标定方法、固件逻辑和实测数据全部梳理一遍,方便想复现的人少走弯路。
1. 先搞清楚社交距离设备到底要解决什么问题
1.1 为什么手机App方案在真实场景里行不通
很多人第一反应是"这不就是蓝牙测距吗,手机App就能干"。实际试过就明白,手机方案在办公桌面上演示效果不错,一旦到了真实作业环境就是另一回事。
首先是系统级限制。iOS和Android都收紧了对后台蓝牙扫描的控制,App退到后台以后,扫描周期被无限拉长,可能隔十几秒才拿到一次扫描结果。两个人擦肩而过总共不到三秒,等App反应过来,人已经走远了。
其次是姿态问题。手机在口袋里、拿在手里、放在桌上,天线方向完全不同,RSSI波动剧烈。同样的两米距离,手机朝左和朝右测出来的值能差15dB以上,直接造成大量误报。
最后是功耗和交互。App全程跑蓝牙扫描,半天就能把手机电量耗掉一大截。而且报警是屏幕弹窗或者滴滴声,车间里噪音一大根本听不见,手机又不能随便震动——万一在操作设备,突然震一下会吓出问题。
可穿戴独立设备没有这些负担。它专门为这个功能设计,天线位置固定,胸前佩戴自然朝前,不存在遮挡问题。报警用蜂鸣器加震动马达,不依赖手机,完全可以离线工作。
1.2 拆解需求和优先级
开始选型之前,我把需求整理成一张表,按优先级排序:
| 优先级 | 需求项 | 说明 |
|---|---|---|
| P0 | 可靠的距离判断 | 2米内触发报警,误报率尽量低 |
| P0 | 佩戴舒适度 | 设备体积小、重量轻,不干扰日常工作 |
| P0 | 长续航 | 至少一个班次(8小时)稳定运行 |
| P1 | 报警方式明显 | 光+声+震动三选二,适应嘈杂环境 |
| P1 | 数据可追溯 | 记录接触事件时间,便于事后溯源 |
| P2 | 云端上报 | 将接触记录上传平台,统一管理 |
P0里面,距离判断的可靠性是最难的。它跟选型直接相关,也就是下一章要讲的测距方案对比。佩戴舒适度和续航属于硬件设计范畴,后面专门展开。先记住一个原则:距离判断的可靠性90%由选型和标定决定,固件算法只能做修正,不能逆天改命。
2. UWB、GPS还是BLE:测距方案选型的完整对比逻辑
2.1 GPS和蜂窝定位为什么直接被排除
一开始有人提过用U-blox的MAX-M8系列GNSS模块做定位,通过交换位置坐标计算距离。这个方法在原理层面就被否掉了。
GPS在开阔地带的定位精度大约2.5米CEP(Circle of Error Probable,即50%的概率落在半径2.5米的圆内),这还是在天空视野良好的情况下。社交距离的阈值是2米,GPS的误差本身就比阈值还大,两个人实际距离1米远,定位坐标可能显示是4米或6米。更致命的是室内完全没有卫星信号,车间、仓库、医院走廊这些重点场所全部覆盖不到。
蜂窝定位就更不靠谱了,基站三角定位的精度在几十米到几百米级别,只能确定"人在哪栋楼",无法回答"两个人是不是离得太近"。
结论很明确:社交距离设备需要的是相对距离,不是绝对坐标。所有依赖绝对定位的方案直接出局。
2.2 UWB的诱惑与现实的成本账
UWB(超宽带)技术在距离精度上有绝对优势,理论精度能到10到30厘米,而且抗多径干扰能力强,非常适合室内。U-blox收购UWB公司后也布局了相关产品,技术上是完全可行的。
但是算完成本账就犹豫了。UWB模块单价是BLE模块的五到十倍,配套天线和射频前端要求更高,PCB布局也苛刻。社交距离设备是要批量部署的,一个车间几十人、一个工地几百人,乘上十倍的单机成本,总预算直接爆炸。
UWB的功耗也更大。连续测距场景下峰值电流能达到几十毫安,对电池容量和充电管理的要求都上来了,可穿戴设备那点空间根本塞不下配套的电池。
在精度够用和成本可控之间,BLE是当时最平衡的选择。
2.3 BLE RSSI方案为什么能打
BLE RSSI测距的原理非常简单:无线电信号在传播过程中强度会衰减,距离越远衰减越厉害,通过接收信号强度反推距离。BLE 4.0之后的设备几乎都支持RSSI读取,生态非常成熟。
精度方面,经过充分标定的BLE RSSI方案在2到5米范围内能控制在1到2米的误差,这个量级对社交距离场景是够用的——我们不需要精确知道"1.8米还是2.1米",只需要判断"有没有进入2米阈值",预留迟滞区间就能把边界模糊问题消化掉。
功耗是BLE的看家本领。广播和扫描的峰值电流只有10毫安级别,平均电流在几百微安到几毫安,一颗400毫安时的锂电池够撑好几天。这对可穿戴设备来说至关重要。
还有一个重要考量是模块本身的认证成本。自己做射频电路要过FCC、CE这些认证,周期长、费用高。直接用U-blox这种已经通过认证的模块,射频性能有保障,产品化阶段省掉一大半麻烦。
2.4 U-blox NINA-B4的具体优势与选型确定
最终锁定的是U-blox NINA-B4系列。这颗模块以Nordic nRF52833为核心,内置ARM Cortex-M4F处理器,512KB Flash加128KB RAM,支持BLE 5.1协议栈。选择它有五个理由:
第一,BLE 5.1引入了到达角(AoA)和离开角(AoD)测向能力,NINA-B4通过恒定频率扩展(CTE)特性支持这个功能,意味着后续想从"只测距离"升级到"距离加方向"时,硬件不用推倒重来。
第二,模块内部是完整的MCU,固件可以直接跑在里面,不需要外挂主控芯片。一颗模块就能完成射频收发、信号处理、报警控制、外设驱动全部工作,把电路设计简化为"模块加传感器加电源"三个部分。
第三,U-blox模块的射频性能经过了系统级调优,天线匹配、阻抗控制这些容易出问题的地方,模块出厂前都处理好了。原型阶段我用裸片放过飞线,RSSI抖得像过山车,换成模块之后数据立刻就稳定了一个量级。
第四,NINA-B4的工作电压范围宽,1.7到3.6伏,可以直接用锂电池供电,省掉额外的稳压电路——不过我在实际设计中还是加了一级LDO,原因后面讲。
第五,U-blox的模块均通过全球主要市场的射频认证,资料齐全,SDK维护活跃。做产品化的时候可以直接把模块贴到自己的PCB上,不用重新过一遍射频认证。
考虑到成本控制、开发周期和技术平滑演进的需求,这个选择最终被验证是对的。
3. 硬件实战:从面包板原型到可佩戴样机的完整过程
3.1 原型阶段的物料清单
原型阶段的目标是快速跑通软件流程,不需要追求小型化。我的物料清单是这样的:
| 器件 | 型号 | 用途 |
|---|---|---|
| 主控+蓝牙 | U-blox NINA-B4模块 | 处理射频、数据、外设控制 |
| 开发板 | nRF52840 DK | 原型阶段承载模块与调试接口 |
| 蜂鸣器 | 有源蜂鸣器 3.3V | 近距离报警声音提示 |
| 震动马达 | 扁平震动马达 3V | 无声报警提示 |
| LED灯 | 红色LED + 电阻 | 视觉报警反馈 |
| 按键 | 轻触开关 | 开机/配对/确认事件 |
| 电池 | 锂电池 400mAh 3.7V | 供电 |
| 充电模块 | TP4056模块 | 给锂电池充电 |
如果你直接用NINA-B4模块做,建议先买一块nRF52840 DK开发板或者类似板子,它自带调试器和串口输出,对排查问题帮助巨大。直接贴模块到自研PCB调试,射频问题加软件问题同时袭来的话,新手会被打崩。
3.2 引脚分配和电路连接
NINA-B4模块的引脚不多,但每个引脚的含义必须先弄清楚再接线。我把关键引脚的分配列出来,这个表在焊接和写代码的时候要反复对照:
| NINA-B4引脚 | 功能 | 连接到 |
|---|---|---|
| VCC | 电源正极3.3V | LDO输出 |
| GND | 电源地 | 电池负极共地 |
| P0.04 | GPIO控制蜂鸣器 | 蜂鸣器正极(经三极管驱动) |
| P0.05 | GPIO控制震动马达 | 马达驱动电路 |
| P0.06 | GPIO控制LED | LED阳极(串220Ω电阻) |
| P0.07 | GPIO读取按键 | 按键一端接GND,一端接引脚,内部上拉 |
| SWDIO/SWCLK | 下载调试 | SWD调试器 |
| RESET | 复位 | 按键到GND |
这里有个关键细节:蜂鸣器和震动马达都是感性负载,工作电流大(蜂鸣器约20mA,马达可能到80mA),如果直接把GPIO接到负载上,电流会超出GPIO的驱动能力,还会把电机的反电动势倒灌进模块,严重时直接烧掉。一定要用三极管或MOS管做驱动级,GPIO只负责控制开关信号。
3.3 电源设计:LDO不可省
NINA-B4的工作电压范围虽然支持到1.7到3.6V,但锂电池的电压范围是3.0到4.2V。如果直接接电池,低电量时电压降到3.0V以下可能触发模块欠压保护,满电时4.2V又超过了推荐长期工作电压。
我在模块电源入口加了一级LDO,把电压稳定在3.0V。选型用的是XC6206P302MR这种超低静态功耗的LDO,自身消耗电流只有1到2微安,对电池续航的影响可以忽略。同时加了一个10μF和0.1μF的电容做去耦,分别吸收低频波动和高频噪声。
3.4 天线布局和佩戴结构设计
NINA-B4有内置天线版本和外置天线版本。我做的是内置天线版,因为它不需要额外画天线区域,整机结构更紧凑。
内置天线对周围环境非常敏感。调试中发现,天线附近如果有金属物体,RSSI会被削掉10到20dB。所以结构设计时立了几个硬性规则:
- 电池放在主板的另一侧,远离天线区域
- 佩戴时朝外的那一面不覆盖金属件
- 外壳不用金属材质,用PC或ABS塑料
- 天线正上方预留至少5毫米的净空区
外壳我用3D打印做了个小方盒,尺寸是55×45×18毫米,重量含电池约40克。正面开孔给LED露出,侧面留键位,背面设计成可插入工牌的卡槽,这样佩戴时不会晃来晃去。实测放在胸前比放在口袋里的RSSI稳定性好很多,因为人体遮挡影响明显更小。
4. RSSI测距原理与标定方法:别跳过这部分,精度全靠它
4.1 无线电信号衰减模型
BLE RSSI测距的灵魂是路径损耗公式:
RSSI(d) = A - 10 × n × log10(d)
公式里的数学含义很清楚:
- A表示距离1米处的接收信号强度,单位dBm。这个值由发射功率、天线增益、接收灵敏度共同决定,每个设备都不一样。
- n是路径损耗指数,表示信号在环境中衰减的速度。开阔空间n约等于2,办公室有墙壁和人体反射的n大概在2.5到3.5之间。
- d是设备间的距离,单位米。
实际使用中,A和n都不是理论值,必须通过实验标定获得。
4.2 标定的完整流程:以我的实测数据为例
标定的目标是求出一套特定环境下的A和n参数。我在一个长15米、宽8米的空旷室内场所进行的标定,处理流程如下:
先把设备固定在一米高的三脚架上,用手机或另一台NINA-B4设备作为接收端。从1米开始,每隔0.5米取一个点,最远到8米。每个距离点连续采样50个RSSI值,记录后求平均和方差。
我采集到的数据大概是这样:
| 距离(米) | 平均RSSI(dBm) | 标准差(dB) |
|---|---|---|
| 1 | -50 | 1.2 |
| 1.5 | -55 | 1.5 |
| 2 | -59 | 1.8 |
| 3 | -65 | 2.1 |
| 5 | -73 | 2.6 |
| 8 | -82 | 3.2 |
把距离和RSSI取对数后再做线性拟合。用Excel或Python里的scipy库都能做,原理是最小二乘法。以1米为参考点,直接用第二个点2米的均值估算n:
-50 - 10 × n × log10(2) = -59 n = 9 / (10 × 0.301) ≈ 2.99
再用3米点验证:-50 - 10 × 2.99 × log10(3) ≈ -64.3,实测是-65,误差不到1dB,拟合效果相当好。所以这组环境下的参数取A=-50,n=3.0。
如果只测两个点就草率收工,误差会很大。建议至少取5个距离点做拟合,多点拟合能抵消单点测量误差。
4.3 标定完依然会飘:三个躲不开的物理现象
标定做得再仔细,实际使用中RSSI还是会飘,原因有三个:
第一个是人体遮挡。人体含水量超过70%,2.4GHz频段的无线电波穿透人体时衰减非常严重,大约能衰掉10到20dB。这就意味着两个人面对面站着的RSSI,和背对背站着的RSSI能差出一大截。解决办法是规范佩戴方式——统一挂在胸前,身体朝向就是天线朝向,把遮挡的影响系统性地降到最低。
第二个是多径效应。信号在室内墙面、地面、金属物体上反复反射,到达接收端时是多条路径的叠加结果。某些位置反射波和直射波相位相反,会互相抵消,形成所谓的"衰落谷",RSSI可能瞬间掉15dB以上。这个没有完美的解决办法,只能靠软件滤波平滑。
第三个是同频干扰。2.4GHz频段挤着WiFi、微波炉、无绳电话,U-blox模块做的是频点跳变,但干扰严重时依然会拉高底噪,让RSSI读数偏大。测试时特意在旁边开了一次微波炉,两米距离上的RSSI直接从-59dBm飘到-65dBm,干扰相当明显。
4.4 提高估距鲁棒性的四个实战手段
面对这些物理局限,光有数学公式不够,工程上还要做四层加固:
第一层是中值滤波。RSSI是噪声叠加的观测值,均值容易被极端值拉偏,中值滤波能有效剔除突发尖峰。我取5个样本做中值,再对中值结果做一次指数滑动平均,效果比单纯求平均好很多。
第二层是迟滞判断。距离阈值不要用单点,而是设计成"进入阈值"和"退出阈值"分开。比如进入阈值设1.5米触发报警,退出阈值设2.5米才解除。这样避免两个人站在阈值边界附近时报警信息反复弹跳。
第三层是时间持久化。触发报警不能只看一次采样,我要求连续5次扫描(约1秒)都判断为近距离才真正触发报警。这个策略用1秒的延迟换掉了大量瞬时误报,实际体验下来非常值。
第四层是方向性提示(进阶)。BLE 5.1的CTE特性可以做到达角估计,知道对方在左边还是右边,这个方向信息可以帮助区分"路过"和"靠近",进一步降低误报率。这节放在后面的扩展部分细讲。
5. 固件实现:广播、扫描、滤波与报警逻辑的完整代码
5.1 BLE角色设计:一个设备同时当广播者和扫描者
社交距离设备的BLE角色跟普通外设不一样。每个设备既要广播自己让其他设备发现,也要扫描周围的广播包发现别人。
所以固件架构要同时跑两套流程:
- 广播:每200ms广播一次,广播包里带上设备ID和一个自定义类型标识
- 扫描:每200ms扫描一轮,解析周围设备广播包,提取RSSI
NINA-B4内部是nRF52833,协议栈原生支持同时做广播和扫描,两个角色互不阻塞。广播间隔和扫描窗口需要权衡:间隔太短费电,太长响应慢。200ms是我试出来的平衡点,既能保证接近1秒内完成报警触发,平均功耗也不会高到离谱。
5.2 广播包设计
广播包的设计要同时考虑信息量和功耗。我用的广播数据结构是:
// 广播数据构建(基于Adafruit Bluefruit nRF52库) BLEAdvertisementData advData; advData.setFlags(0x06); // LE General Discoverable + BR/EDR Not Supported advData.setName("SDW-01"); // 设备名称,标识为社交距离可穿戴01号 // 自定义厂商数据:厂商ID + 设备类型 + 设备编号 uint8_t manufacturerData[5] = { 0x80, 0x00, // 厂商ID,实际使用时替换为申请到的ID 0x01, // 设备类型:0x01代表社交距离可穿戴 0x01, 0x0A // 设备编号:设备ID的十六进制低16位 }; advData.setManufacturerData(manufacturerData, sizeof(manufacturerData));广播包里不塞距离信息,只塞设备识别信息。因为RSSI是接收端在物理层读出来的,不需要双方交互数据。广播包越短,在空中的占空比越低,被其他设备扫描到的概率越高。
5.3 扫描与RSSI采集核心代码
扫描回调和RSSI提取是固件的核心路径。这里用Adafruit Bluefruit nRF52库的API来实现:
#include <bluefruit.h> // 扫描回调函数,收到广播包时被调用 void scan_callback(ble_gap_evt_adv_report_t* report) { // 过滤:只处理设备类型为0x01的社交距离可穿戴设备 uint8_t* advData = report->data.p_data; uint8_t advLen = report->data.len; // 简单解析:检查是否包含我们的厂商ID // 实际工程中建议用BLEUuid或自定义解析函数做完整过滤 for (int i = 0; i < advLen - 3; ) { uint8_t type = advData[i + 1]; if (type == 0xFF && advData[i + 2] == 0x80 && advData[i + 3] == 0x00) { // 是社交距离设备,读取RSSI processRssi(report->rssi); return; } int fieldLen = advData[i]; i += fieldLen + 1; } } void setup() { // 配置扫描参数 Bluefruit.Scanner.setRxCallback(scan_callback); Bluefruit.Scanner.setInterval(160, 80); // 扫描窗口和间隔,单位0.625ms Bluefruit.Scanner.start(0); // 连续扫描,不超时停止 }有个性能细节值得说明:setInterval(160, 80)的数值直接影响扫描占空比。窗口160表示每次持续扫描100ms,间隔80表示扫描结束后等待50ms再扫下一轮。这个参数让扫描占空比约67%,既保持了对移动目标的敏感度,又给广播和其他外设留出了射频时间。
RSSI回调拿到的report->rssi是一个有符号int8值,单位dBm,取值范围大约在-40到-105之间。通过扫描回调拿到的RSSI已经包含了物理层处理,不需要再做校准。
5.4 滤波与距离计算的完整实现
拿到原始RSSI之后,后续处理才是决定系统可用性的关键:
#define RSSI_SIZE 5 int16_t rssi_buffer[RSSI_SIZE]; int rssi_index = 0; float filtered_rssi = -50.0f; // 初始值设为1米处的标定值 #define PATH_LOSS_EXP 3.0f // 标定得到的路径损耗指数 n #define RSSI_REF_1M -50.0f // 标定得到的1米处RSSI值 A #define ALARM_TRIGGER_DIST 1.5f // 触发报警的距离阈值,米 #define ALARM_RELEASE_DIST 2.5f // 解除报警的距离阈值,米 // 冒泡排序取中位数 int16_t medianFilter(int16_t* buf, int size) { int16_t temp[5]; memcpy(temp, buf, size * sizeof(int16_t)); for (int i = 0; i < size - 1; i++) { for (int j = 0; j < size - i - 1; j++) { if (temp[j] > temp[j + 1]) { int16_t t = temp[j]; temp[j] = temp[j + 1]; temp[j + 1] = t; } } } return temp[size / 2]; } // 将RSSI转换为距离估计值,单位米 float rssiToDistance(float rssi) { float exp = (RSSI_REF_1M - rssi) / (10.0f * PATH_LOSS_EXP); return powf(10.0f, exp); } void processRssi(int16_t rssi) { rssi_buffer[rssi_index] = rssi; rssi_index = (rssi_index + 1) % RSSI_SIZE; // 等待缓冲区填满后再开始计算 if (rssi_index < RSSI_SIZE - 1) return; // 中值滤波 float median = medianFilter(rssi_buffer, RSSI_SIZE); // 指数滑动平均,平滑残余抖动 filtered_rssi = 0.8f * filtered_rssi + 0.2f * median; // 距离计算 float distance = rssiToDistance(filtered_rssi); // 阈值迟滞判断 static bool alarm_active = false; if (distance < ALARM_TRIGGER_DIST && !alarm_active) { alarm_active = true; activateAlarm(); } else if (distance > ALARM_RELEASE_DIST && alarm_active) { alarm_active = false; deactivateAlarm(); } }这里每个数字都有讲究。中值滤波的窗口大小取5,既够剔除尖峰,又不至于让延迟太大。指数滑动平均的系数0.8和0.2的配比让滤波后的RSSI保持对真实信号快速响应,同时抑制高频抖动。迟滞区间设计成1.5米触发、2.5米解除,正好把公式误差、人体反射和设备方向的影响都包在这个死区里。
5.5 报警执行与功耗平衡策略
报警不是简单把蜂鸣器和LED一开一关就完事。我做了三档报警模式:
| 场景 | 距离 | 报警表现 |
|---|---|---|
| 轻度接近 | 进入2.5米但未到1.5米 | LED慢闪,提示注意 |
| 重度接近 | 进入1.5米 | LED快闪+蜂鸣器连续响+震动 |
| 解除 | 超过2.5米 | 全部停止,LED熄灭 |
功耗管理上,我做了两级策略。检测到周围没有社交距离设备时,把广播和扫描的间隔拉长到1秒,此时平均电流降到约1.2mA;检测到周围有设备活动时,恢复200ms的高频监测。这套动态策略让设备在闲时更省电,忙时更灵敏。
6. 实测数据、误报场景与调优手段:真机不会骗人
6.1 三种典型场景的实测结果
原型调试完成后,我在三个差异明显的场景做了系统测试,每个场景测50次,统计触发距离的偏差和误报次数:
| 测试场景 | 设定距离 | 实测触发距离(均值±标准差) | 误报率(50次) |
|---|---|---|---|
| 空旷走廊 | 1.5米 | 1.47±0.2米 | 0 |
| 普通办公室 | 1.5米 | 1.35±0.35米 | 2%(1次) |
| 室外有人走动 | 1.5米 | 1.52±0.3米 | 0 |
空旷走廊的表现最好,因为几乎没有多径反射,RSSI衰减曲线和标定环境高度一致。办公室里的误差最大,原因也简单:电脑显示器、金属文件柜、玻璃隔断这些反射体多。室外测试因为空间开阔,加上有建筑物反射,结果反而比办公室还稳定。
6.2 那些让RSSI彻底失灵的真实案例
测试中遇到的两个极端案例让我印象特别深:
第一个是人体完全遮挡。测试员把设备放在裤兜里,另一台设备放在胸前,两人面对面站着1米距离。RSSI读到的是-70dBm左右,换算出的距离约为3.2米,完全没触发报警。这说明天线被人体包裹后,信号能量被大量吸收,不是算法能救回来的。解决办法是规范佩戴位置:设备必须挂在胸前或肩膀上。
第二个是微波炉干扰。办公室有台微波炉,打热饭的时候恰好也在测试,两设备实际距离1米,RSSI在-55到-66之间剧烈跳动。滤波之后勉强到报警阈值附近,但明显比平时慢。2.4GHz频段就是这种物理特性,设备没法绕过,只能靠频谱跳变和滤波缓解。
6.3 三个实际调优手段的效果验证
针对实测暴露的问题,我做了三项调优:
第一项是"连续近距离确认"。原来单次扫描进阈值就报警,改成连续5次扫描都在阈值内才报警。误报率从2%降到了0,代价是报警延迟约1秒,对静止或慢速接近的场景完全可接受。
第二项是报警逻辑支持按场景配置。工厂车间噪音大,蜂鸣器音量调到最大加震动;办公室场景改成LED慢闪为主,避免打扰其他人。报警模式做成配置项,按键切换后保存到Flash。
第三项是增加设备ID白名单过滤。办公室场景有多个设备同时运行,日志里出现某个固定设备频繁触发报警,排查发现是测试人员经常把设备摘下来放在同一个桌上。加了白名单或黑名单过滤后,同类问题可以快速定位。
6.4 这套DIY方案在市场产品的什么水平
拿成品跟市售的商用社交距离设备做对比,结论是:功能层面已经达到同类产品80%的水平,差距主要在云端和规模化。
优势方面,整机成本控制在百元级以内,比多数商用设备便宜一半以上。可以自定义报警逻辑和佩戴方式,适合特定场景的深度定制。数据完全本地化存储,不依赖云端服务,没有数据泄露风险。
劣势方面,我写的固件没有做远程升级能力,设备部署后想更新算法得逐个拿回来刷机。没有后台管理系统,只能通过串口导出接触记录,无法做到实时监控。商用设备在体温检测、身份识别、考勤联动这些衍生功能上也更成熟。
7. 从原型到产品化:NINA-B4的进阶玩法与扩展思路
7.1 解锁BLE 5.1方向测距能力
前面提到NINA-B4支持BLE 5.1的测向功能,这个特性用好能大幅提升定位能力。利用CTE机制实现到达角估计,接收端通过相位差计算信号来源方向。两套设备一个发射带CTE的广播包,一个接收并计算角度,就能判断对方在左侧还是右侧。
这个能力对社交距离场景的意义在于方向区分。两个人并排走和迎面走近,在纯RSSI方案里几乎无法区分。有了角度信息,迎面走近时距离变化更快,角度持续对准;并排走时角度在快速移动。结合这些运动特征,能进一步降低误报率。
实现测向功能需要发送端配置CTE相关参数(CTE长度、跳频模式等),接收端固件处理IQ采样数据。NINA-B4的SDK里已经有相关驱动和示例代码,不需要从零开发,但处理IQ数据的计算量比纯RSSI大不少,内存占用也会上升,128KB的RAM还是能撑住的。
7.2 联动U-blox GNSS模块做户外轨迹记录
部署在户外场景(如工地)时,可以给设备加一颗U-blox MAX-M8系列GNSS模块。室内场景靠BLE测距,户外场景用卫星定位记录轨迹。设备检测到近距离接触时,把当前GPS坐标和对方设备ID一并记录下来。
MAX-M8的功耗在GPS模块里算很低的,连续定位大约20mA,但叠加到整机上还是会让两天续航变成半天。实际做项目时可以用"事件驱动"策略:平时GPS模块处于备份模式,只有BLE检测到近距离接触才唤醒GPS去记录当前位置,记录完成后立刻切回低功耗模式。这样既保证了事件的位置信息可追溯,又不会让电池快速耗尽。
7.3 用NB-IoT上传接触记录到后台
单机设备能记录接触事件,但管理方希望实时看到"哪个人和哪个人接触了、什么时候、在什么位置"。这个需要联网上报。
U-blox SARA-N4系列NB-IoT模块是成熟的选择。设备本地存储接触记录,每5分钟通过NB-IoT把新记录推送到云平台。NB-IoT的优势是功耗低、穿墙性能好,适合部署在建筑密集的区域。
这部分工作量和成本都不小,NB-IoT模块的价格比BLE模块贵很多,还要额外费用采购物联网卡和云服务。建议在所有单机功能稳定之后再考虑,而且先小批量试点,确认管理流程真的需要这些云端数据再做整体升级。
7.4 隐私与合规:做设备时一定要想清楚
最后必须提隐私。社交距离设备天然具备了"记录谁和谁接触"的能力,这在管理上是双刃剑。我自己的处理原则:
第一,设备ID匿名化,不直接关联真实姓名。后台系统里可以设计用户映射关系,但设备端不做任何个人身份信息的存储。
第二,数据加密。BLE广播包里的设备ID无法加密,但接触记录在本地存的时候设计成加密哈希的格式,云端传输走TLS。
第三,使用场景上注明"接触事件仅在主动上传后由管理员查看",并在设备外壳上贴上说明标贴。实际做过类似系统的人都知道,这种管理性质的设备,合规意识必须在设计阶段就要考虑好,否则后面上线会被骂得很惨。
最后分享一个我自己调试过程中的小技巧:拿到NINA-B4模块后,别急着写完整固件,先用官方示例程序把广播和扫描单独跑通,用手机装一个BLE调试工具(nRF Connect也很好用),直接在手机上看你能扫到自己的设备、看到RSSI在什么范围。这套流程走一遍以后,再开始写自己的逻辑,会顺畅很多——因为一旦出了问题,你已经能排除掉"模块是不是没正常工作"这个最大变量。