去年接了一个资产追踪项目,要求终端在室外能精准定位,室内也要有位置参考,而且续航要撑一周以上。单靠 GPS 搞不定,纯 LBS 精度又不够。最后走的是北斗+GPS+LBS 三源融合的路子——这篇文章把完整的开发流程、AT 指令、数据格式和云平台对接方案整理出来。
一、需求分析——为什么单一定位源不够
在动手写代码之前,先搞清楚三种定位方式的适用边界:
| 定位方式 | 精度 | 功耗 | 可用场景 |
|---|---|---|---|
| 北斗/GPS | 2-10 米 | 高(搜星 50-100mA) | 户外开阔环境 |
| 基站定位(LBS) | 50-500 米 | 极低 | 市区/有信号的地方 |
| Wi-Fi 定位 | 10-50 米 | 低 | 城市室内外 |
实际场景中,设备会在不同环境之间切换——出了仓库是 GPS 主场,进了地库 GPS 直接失效,到了郊区 LBS 又因为基站少而偏到离谱。
所以多源融合的核心思路不是"选哪个最好",而是"每个场景用最合适的那个,多个源互相兜底"。
二、硬件选型——核心板决定了开发起点
做定位终端有两种起步方式:从芯片级自己画板子,或者用现成的 4G Cat.1 核心板直接开发。
芯片级方案的优点是极致成本和体积控制,但工作量巨大——射频调试、EMC 认证、天线匹配,没个半年下不来。
核心板方案是把 4G 模组、SIM 卡槽(或 eSIM)、电源管理、天线接口全部集成到一块板子上,留出 TTL 串口给你发 AT 指令。开发周期大幅压缩。
选核心板时重点看几个参数:
| 参数 | 建议值 | 原因 |
|---|---|---|
| 模组型号 | A7670E / ML307A 等 | Cat.1 够用,不用上 Cat.4 |
| 是否支持 GNSS | 必须 | 北斗+GPS 双模是底线 |
| 体积 | 25×20mm 级别 | 终端产品对体积敏感 |
| 接口 | TTL 串口 + USB 可选 | 串口对接 MCU,USB 调试方便 |
| 工作温度 | -20~70℃ 起步 | 户外设备基本要求 |
| 功耗 | 待机 < 5mA,关机 < 50μA | 影响续航的核心 |
以目前市面上 ML307 系列为例,核心板做到了 24×19.5mm,邮票孔封装直接 SMT,板载 eSIM 省掉卡座厚度。支持北斗+GPS+LBS 三源定位,TTL 串口 AT 指令控制,3.3V 供电,适合大多数 MCU 对接。
三、定位数据获取——AT 指令实战
4G Cat.1 模组基本都走 AT 指令控制。下面是多源定位的完整指令流程。
3.1 GNSS 定位(北斗+GPS)
// 1. 开启 GNSS 模块
AT+CGNSSPWR=1
// 返回: OK
// 2. 设置定位模式(北斗+GPS 双模)
AT+CGNSSMODE=15
// 返回: OK
// 3. 查询当前定位信息
AT+CGNSSINFO
// 返回示例:
// +CGNSSINFO: 1,3451.12345,N,11341.67890,E,250120,065432.0,45.2,2.1,12
// 解析:
// 1 -> 定位状态(1=已定位)
// 3451.12345 -> 纬度 ddmm.mmmmm
// N -> 北纬
// 11341.67890-> 经度 dddmm.mmmmm
// E -> 东经
// 250120 -> 日期 DDMMYY
// 065432.0 -> 时间 HHMMSS.S
// 45.2 -> 海拔(米)
// 2.1 -> 速度(公里/小时)
// 12 -> 搜星数量
GNSS 定位结果需要转成标准十进制度数:
// 原始格式: ddmm.mmmmm -> 十进制度数
float gnss_to_decimal(char *raw, char direction) {
float ddmm = atof(raw);
int degrees = (int)(ddmm / 100);
float minutes = ddmm - degrees * 100;
float decimal = degrees + minutes / 60.0;
if (direction == 'S' || direction == 'W') {
decimal = -decimal;
}
return decimal;
}
3.2 LBS 基站定位
走基站定位不需要额外开模块,直接用 AT 指令获取周围基站信息,上传到 LBS 服务解析就行:
// 1. 查询服务小区信息(当前连接的主基站)
AT+CREG?
// 返回: +CREG: 0,1
// 说明: 0=禁用网络注册URC, 1=已注册到归属网络
// 2. 获取基站信息(LAC + CellID)
AT+CENG?
// 返回: +CENG:
// 0,1 -> mode, 当前状态
// 460,00,12345,98765 -> MCC,MNC,LAC,CellID
// 2,-85 -> 网络类型, 信号强度(dBm)
// 3. 获取邻区基站信息(可选,提升精度)
AT+CENG=2
// 返回附近最多 6 个基站的信号强度
拿到 LAC 和 CellID 后,不是本地算位置——基站位置数据库在服务器端。终端上报原始基站参数,服务器去 LBS 服务商那里查询对应的经纬度。
3.3 Wi-Fi 辅助定位
如果你的终端加了 Wi-Fi 扫描能力(比如 ESP32 做协处理器),可以扫描周围 MAC 地址用于辅助定位:
// ESP32 AT 指令扫描周围 Wi-Fi
AT+CWLAP
// 返回:
// +CWLAP:(3,"TP-LINK_5G",-54,"a0:ab:1b:xx:xx:xx",1,36,0)
// +CWLAP:(3,"ChinaNet-xxx",-72,"c8:3a:35:xx:xx:xx",1,11,0)
// +CWLAP:(4,"CMCC-xxx",-80,"34:e3:80:xx:xx:xx",1,1,0)
扫描到的 MAC 地址和信号强度列表上报到服务器,服务器去 Wi-Fi 位置数据库查对应坐标。原理和基站定位一样——热点的位置是固定的,反向推断设备位置。
四、上报数据格式设计
多源定位意味着一次上报可能同时包含 GNSS、LBS、Wi-Fi 三种数据。数据格式要设计好,不然服务端没法做融合。
4.1 推荐:JSON 扩展格式
{
"device_id": "LX20260615001",
"timestamp": 1765879234,
"battery": 85,
"sources": {
"gnss": {
"available": true,
"lat": 34.8520,
"lng": 113.6941,
"altitude": 45.2,
"speed": 2.1,
"satellites": 12,
"hdop": 1.2
},
"lbs": {
"available": true,
"mcc": 460,
"mnc": 0,
"lac": 12345,
"cell_id": 98765,
"signal": -85,
"neighbors": [
{"lac": 12345, "cell_id": 98766, "signal": -92},
{"lac": 12345, "cell_id": 98764, "signal": -95}
]
},
"wifi": {
"available": false,
"aps": []
}
}
}
4.2 精简格式(省流量)
如果对流量敏感,可以用二进制协议或紧凑 JSON:
{
"d": "LX20260615001",
"t": 1765879234,
"b": 85,
"g": [1, 34.8520, 113.6941, 12],
"l": [460, 0, 12345, 98765, -85],
"w": []
}
字段含义:
g: [可用标志, 纬度, 经度, 搜星数]l: [MCC, MNC, LAC, CellID, 信号强度]w: Wi-Fi MAC 列表,空数组表示无数据
精简格式比完整 JSON 省约 60% 字节数。定位数据一次上报通常在 200-500 字节之间,对 Cat.1 的上行速率(约 5Mbps)来说差别不大,但对大量设备、高频上报的场景,累积起来流量成本差距可观。
五、上报策略——对续航影响最大的决策
同样的硬件,上报策略不同,续航可以差 4-5 倍。
5.1 基础策略:按频率分级
| 模式 | 上报间隔 | 定位方式 | 功耗级别 |
|---|---|---|---|
| 实时追踪 | 10-30 秒 | GNSS 全开 | 极高(1 天一充) |
| 日常监控 | 5-10 分钟 | LBS 为主 + GNSS 偶尔 | 中(3-5 天) |
| 省电模式 | 30-60 分钟 | 纯 LBS | 低(7-10 天) |
| 仓库待机 | 2-4 小时 | 纯 LBS | 极低(2 周+) |
5.2 进阶:事件驱动切换
频率分级只解决了一半问题。更好的做法是事件驱动的动态切换:
设备静止(加速度计无变化 10 分钟)
→ 进入休眠模式:4 小时上报一次 LBS
→ 震动传感器检测到移动
→ 唤醒,切到日常监控模式:10 分钟上报一次 GNSS
→ 电子围栏报警触发
→ 升频到实时追踪:30 秒上报一次 GNSS
→ 设备再次静止 10 分钟
→ 回到休眠模式
5.3 MCU 端核心逻辑示意
typedef enum {
MODE_SLEEP,
MODE_MONITOR,
MODE_TRACK
} report_mode_t;
report_mode_t current_mode = MODE_SLEEP;
void positioning_loop() {
while (1) {
switch (current_mode) {
case MODE_SLEEP:
// 纯 LBS,低频上报
do_lbs_report();
deep_sleep(14400); // 4 小时
break;
case MODE_MONITOR:
// GNSS 优先,LBS 兜底
if (gnss_available()) {
do_gnss_report();
} else {
do_lbs_report();
}
light_sleep(600); // 10 分钟
break;
case MODE_TRACK:
// 全开 GNSS 高频上报
do_gnss_report();
sleep(30); // 30 秒
break;
}
// 检查是否需要切换模式
check_mode_switch();
}
}
void check_mode_switch() {
if (fence_alarm_triggered()) {
current_mode = MODE_TRACK;
} else if (motion_detected()) {
current_mode = MODE_MONITOR;
} else if (stationary_for(600)) { // 静止 10 分钟
current_mode = MODE_SLEEP;
}
}
六、服务端定位融合——把多源数据变成一个坐标
终端发上来的数据可能是 GNSS + LBS 都有,也可能只有其中一个。服务器端要做的是根据可用数据源,融合出最优位置。
简化版融合策略:
def fuse_position(report):
gnss = report.get("sources", {}).get("gnss", {})
lbs = report.get("sources", {}).get("lbs", {})
wifi = report.get("sources", {}).get("wifi", {})
# 第一优先级:GNSS 有效直接采用
if gnss.get("available") and gnss.get("satellites", 0) >= 6:
return {
"lat": gnss["lat"],
"lng": gnss["lng"],
"accuracy": gnss.get("hdop", 2) * 2, # 估算精度米
"source": "gnss"
}
# 第二优先级:Wi-Fi 定位(室内场景)
if wifi.get("available") and len(wifi.get("aps", [])) >= 3:
position = wifi_position_lookup(wifi["aps"])
if position:
return {**position, "source": "wifi"}
# 第三优先级:LBS 解析
if lbs.get("available"):
position = lbs_position_lookup(
lbs["mcc"], lbs["mnc"],
lbs["lac"], lbs["cell_id"],
lbs.get("neighbors", [])
)
if position:
return {**position, "source": "lbs"}
# 所有方式都不可用
return {"lat": None, "lng": None, "accuracy": None, "source": "none"}
融合策略给前方调用者(APP/管理后台)返回一个统一的位置对象,前端不感知底层用的是哪种定位方式。
七、联调避坑
实际开发中踩过几个坑,记录一下:
坑 1:GNSS 冷启动超时
首次上电或者长时间未定位后,GNSS 需要下载完整的星历数据(冷启动),可能需要 30 秒到 2 分钟。这期间 AT+CGNSSINFO 返回的是最后一次有效位置,看不到状态是"定位中"还是"已失败"。
解决方案:先用 AT+CGNSSSTATUS 确认状态,再读位置数据。
坑 2:LBS 基站信息上报时机
基站 LAC/CellID 在用 AT+CENG 查询时拿到的数据是即时的,但设备可能在移动中,信号从一个小区的边缘切到另一个边缘。建议在每次上报前 2-3 秒内获取基站信息,取两次取平均信号强度以过滤抖动。
坑 3:MQTT 连接超时导致数据积压
4G 信号不好时 MQTT 断连,数据在本地积压。建议本地做一个环形缓冲区(比如保留最近 50 条记录),重连成功后批量补传,带上原始时间戳让服务端按时间排序。
坑 4:模组关机功耗不达标
不少模组标称"关机 < 10μA",但实际板子上有 LDO、电平转换芯片等,总功耗可能翻倍。用核心板方案能部分避免这个问题——好的核心板已经把电源管理做透了。比如有些产品做到了关机 9μA 的水平,依靠的是模组直接切 VDD 供电、板上没有多余静态功耗器件。
坑 5:服务端 LBS 解析精度依赖数据库
免费 LBS 库(比如 OpenCellID)在国内的基站覆盖不全,商用 LBS API 才够用。按日活评估,商用 LBS 的月费一般几百到几千元不等,做产品预算时别忘了这一项。
八、总结
一个完整的 4G 定位终端开发链路:硬件选型(4G Cat.1 核心板)→ AT 指令获取 GPS/LBS/Wi-Fi 数据 → 结构化上报 JSON → 云平台融合定位 → APP 展示。
| 环节 | 关键决策 |
|---|---|
| 硬件 | 选核心板还是芯片级?核心板开发周期短,芯片级成本和体积更极致 |
| 定位策略 | GNSS 为主还是 LBS 为主?取决于你的场景是精度优先还是续航优先 |
| 上报频率 | 定频还是事件驱动?事件驱动对续航的提升是最显著的 |
| 数据格式 | JSON 可读性 vs 二进制省流量?前期开发选 JSON,调试方便 |
| 服务端融合 | 多源数据到了服务器怎么合并成一个坐标?优先级:GNSS > Wi-Fi > LBS |
如果不想从零折腾硬件和 AT 指令,市面上已经有把模组、定位、电源管理全部封好的成品终端——类似 LEXIN SOS-ML307C 这种,23×30mm 一体化设计,拿到手就是支持三源定位 + 平台上报的成品。先用成品验证业务逻辑,再决定要不要自己做硬件,是投入产出比最高的路径。
本文基于实际定位终端开发经验整理,适用 4G Cat.1 模组(A7670E/ML307 系列等)。不同模组的具体 AT 指令可能有差异,以对应 AT 手册为准。