LuatOS 课程-011 讲:GNSS应用开发

在物联网项目开发中,智能定位系统是一类常见且实用的应用场景,本文将基于 LuatOS,分享一款智能定位系统的开发思路与相关实现要点。

在实际开发过程中,类似学生卡定位器的需求十分普遍,这类需求通常对定位精度、设备续航能力、轨迹显示效果以及多平台适配性均有明确要求,而基于 LuatOS 的智能定位系统,可针对性解决这类开发需求中的核心痛点。

项目特点

  • 🛰️ 三合一定位:GNSS + 基站 + WiFi
  • 🔋 续航:智能功耗管理,运动才定位
  • 🛣️ 轨迹优化:减少80%GPS静态漂移以及运动漂移,路线更符合真实路径
  • 🌐 多平台兼容:一次开发,同时上报多个服务器
  • 🎯 工业标准:完整JT808协议实现
  • 🚩特点:exgnss扩展库和exvib扩展库

适合场景

  • 👨🎓 学生安全卡、老人定位器
  • 🐕 宠物追踪、牲畜管理
  • 🚗 车辆监控、物流追踪
  • 📦 资产定位、贵重物品跟踪

前言

当前项目为演示项目,使用到的web平台为合宙自建的内部服务器,暂时不对外开放,此项目仅仅是为了给大家演示一下exgnss扩展库和exvib库如何融入到定位器相关项目内,也会重点讲解gnss应用场景有关的代码和业务逻辑,至于后期如何开放该平台等事宜,等待内部讨论后给出。

项目功能:

  1. 经纬度数据、电量、卫星信号质量、速度、电池状态(充电中/未充电)等数据上报到合宙定位测试服务器
  2. 根据服务器下发的指令 进入/退出快速定位模式
  3. 功耗优化,区分运动和静止状态

项目需求:

  1. 由Air8000A内部加速度传感器判断是否开启/关闭GNSS
  2. 按照JT808协议和服务器进行通讯
  3. 可以上传多个服务器(为后期做拓展)
  4. 尽可能的减少静态/动态漂移带来的轨迹失真
  5. 与服务器直接保持长连接状态,并且尽可能的做到功耗最低

使用到的最为重要的两个扩展库介绍:

exgnss:

exgnss库实际上是把底层提供的libgnss接口封装了一层,让配置过程更快速一点,更方便用户快速上手,exgnss库最重要的三个接口为:

lua 复制代码
exgnss.setup(gnssotps) --配置GNGSS输出相关
exgnss.open(mode,para)--硬件层面打开GNSS
exgnss.close(mode,para)--硬件层面关闭GNSS

至于这三个接口的具体使用方法,以及传参,这里有接口/参数说明以及demo示例,后面项目中也用到了,这里带大家简单过一次。就不做赘述了

常量定义:

exgnss.DEFAULT

lua 复制代码
常量含义:exgnss应用模式1,常开模式;
数据类型:number;
示例代码:--- exgnss应用模式1.
        -- 打开gnss后,gnss定位成功时,如果有回调函数,会调用回调函数
        -- 使用此应用模式调用exgnss.open打开的"gnss应用",必须主动调用exgnss.close
        -- 或者exgnss.close_all才能关闭此"gnss应用",主动关闭时,即使有回调函数,也不会调用回调函数
        -- 通俗点说就是一直打开,除非自己手动关闭掉
        local function mode_cb(tag)
            log.info("TAGmode_cb+++++++++",tag)
            log.info("nmea", "rmc", json.encode(exgnss.rmc(2)))
        end
        exgnss.open(exgnss.DEFAULT,{tag="MODE",cb=mode_cb})

本简易定位器demo中用到的就是这个常量

还有**exgnss.TIMERORSUC(**到规定时间或者GNSS定位成功后[开拓地带约为35S]关闭GNSS)和

exgnss.TIMER[到规定时间就关,不管GNSS定位成功与否]

lua 复制代码
exgnss=require("exgnss")  --因为是扩展库不是核心库 所以需要require(类似C语言中的#incloud)

local function mode_cb(tag)
    log.info("定位应用的回调函数",tag)
    log.info("让我看看当前经纬度", json.encode(exgnss.rmc(2)))
end

local function gnss_fnc()
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,对Air8000来说,星历时效性:北斗1小时,GPS2小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        debug=true,    --是否输出调试信息(GNSS芯片输出的nmea数据)

     --其他参数用户可以看接口说明,本demo中用不到其他参数
    }
     --设置gnss参数
    exgnss.setup(gnssotps)
    --开启gnss应用,到规定的60S就关闭,不管定位成功与否
    exgnss.open(exgnss.TIMER,{tag="用户自定义应用名",val=60,cb=mode_cb})  --使用TIMER模式,开启60s后关闭

    sys.wait(40000)
    log.info("关闭一个gnss应用")
    --关闭一个gnss应用
    exgnss.close(exgnss.TIMER,{tag="用户自定义应用名"})--关闭tag为"用户自定义应用名"应用
    --查询gnss应用状态
    log.info("gnss应用状态",exgnss.is_active(exgnss.TIMER,{tag="用户自定义应用名"}))
    sys.wait(10000)
    --关闭所有gnss应用
    exgnss.close_all()
    --查询最后一次定位结果
    local loc= exgnss.last_loc()
    if loc then
        log.info("lastloc", loc.lat,loc.lng)
    end
end

sys.taskInit(gnss_fnc)


--GNSS定位状态的消息处理函数:
local function gnss_state(event, ticks)
    -- event取值有
    -- "FIXED":string类型 定位成功
    -- "LOSE": string类型 定位丢失
    -- "CLOSE": string类型 GNSS关闭,仅配合使用exgnss.lua有效
    -- ticks number类型 是事件发生的时间,一般可以忽略
    log.info("exgnss", "state", event)
    if event=="FIXED" then
        --获取rmc数据
        --json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位,本示例保留5位小数
        log.info("nmea", "rmc0", json.encode(exgnss.rmc(0),"5f"))
    end
end
sys.subscribe("GNSS_STATE",gnss_state)

exvib:

exvib库实际上是操作了Air780EGP/Air780EGG/Air780EGH/Air8000A/Air8000AB/Air8000N/Air8000U/Air8000D/Air8000DB模组内的一颗G-Sensor加速度传感器,模组内部硬件具体怎么接的,咱们这个项目中无需关心,exvib库的三种模式主要用于以下场景:

  1. 微小震动检测:用于检测轻微震动的场景,例如用手敲击桌面,加速度量程2g;
  2. 运动检测:用于电动车或汽车行驶时的检测和人行走和跑步时的检测,加速度量程4g
  3. 跌倒检测:用于人或物体瞬间跌倒时的检测,加速度量程8g

咱们这个项目 使用的就是运动检测,exvib库就三个接口,分别为:

lua 复制代码
exvib.read_xyz()--读三轴(G-Sensor)传过来的XYZ轴数据
exvib.open(mode)--打开三轴(G-Sensor)
exvib.close()--关闭三轴(G-Sensor)

exvib库完整的说明可以看这里,后面项目具体代码中也有,这三个接口不难理解,唯一注意的一点是exvib.open这个接口

exvib.open(mode)

lua 复制代码
参数含义:加速度传感器的应用模式;
数据类型:number;
取值范围:
1 - 微小震动检测,用于检测轻微震动的场景,例如用手敲击桌面,加速度量程2g;
2 - 运动检测,用于电动车或汽车行驶时的检测和人行走和跑步时的检测,加速度量程4g;
3 - 跌倒检测,用于人或物体瞬间跌倒时的检测,加速度量程8g;
是否必选:是;

第一部分:项目整体架构

1.1 模块化架构设计

架构特点

  1. 分层设计
  • 定位核心层:commom(管理了exgnss和trackCompensate)

  • 传感器层:vibration(三轴)

  • 电源管理层:charge、manage

  • 通讯协议层:api、jt808

  • 服务器层:srvs、auxServer

  • 具体后面讲代码的时候会一个个lua文件打开,仔细说明,这里就不做过多赘述了

  1. 事件驱动
lua 复制代码
-- 发布订阅模式,完全解耦
sys.publish("SYS_STATUS_RUN")      -- 运动事件
sys.subscribe("GNSS_STATE", fn)    -- 定位状态变化
sys.waitUntil("IP_READY", 10000)   -- 等待网络就绪
  1. 配置驱动
lua 复制代码
-- cfg.lua 统一管理所有配置
_G.GNSS_LOWPOWER_ENABLE = true -- 低功耗开关
_G.GNSS_LOWPOWER_INTERVAL = 60 -- 定位间隔(单位:S)
-- 轨迹补偿参数
trackCompensate.setConfig({
    scene = "person", -- 人员模式
    autoAdaptive = true, -- 自动适应
    distanceThreshold = 50 -- 漂移过滤阈值
})
  1. gnss应用场景****管理
lua 复制代码
-- 避免资源冲突,智能管理GNSS
exgnss.open(exgnss.DEFAULT, {tag = "common_app"})
exgnss.open(exgnss.TIMERORSUC, {tag = "lowpower"})
-- 两个tag都关闭时,GNSS才真正关闭

1.2 核心模块介绍

第二部分:智能定位状态机

一、什么是状态机

​ 状态机的全称叫做有限状态机,是计算机的运行的基本的元素。

​ 状态机由三个要素组成:

​ 1,有限个状态,

​ 2,触发状态迁移的事件,

​ 3,不同状态之间迁移的逻辑。

​ 我们在设计软件之前,先把系统的状态机设计清楚, 再开发软件,可以使得软件更容易评审,也更容易编写测试用例,多人的协作也会更加容易。

​ 状态机设计清楚之后, 可以让产品经理,研发工程师,项目经理,测试,形成了统一的话语体系,基于状态机,以及状态机下的各个参数的沟通, 变得高效,简单。

二、设备的定位状态机

用我们现在正在讲解的项目为例, 状态机如下:

1、 4个状态描述

(1)捕获GPS

​ 没有GPS定位成功,并且设备是运动的,设备都会处于捕获状态。

​ 这是设备最耗电的一个状态。

(2)追踪GPS

​ 捕获成功后,进入追踪状态,追踪状态比捕获省电一些。

(3)静止GPS

​ 追踪状态下,检测到设备静止,就进行静止GPS,这个时候,关闭GPS,上报最近一次的GPS位置。

(4)LBS定位状态

​ 捕获失败,并且设备静止,进入 LBS 状态,上报 LBS 和 wifi 的信息,服务器端进行基站和WIFI的组合定位。

2、触发状态迁移的事件

(1)捕获成功;

(2)捕获失败;

(3)追踪过程中丢GPS;

(4)静止;

(5)运动;

(6)按键

(7)收到服务器的指令

3、 状态迁移的逻辑

三、状态机设计理念

核心问题:如何平衡定位精度与功耗?

合宙的方案:智能切换 → 精度够用且续航长(一周)

2.2 各状态详细逻辑

CAPTURE模式 - 定位捕获
lua 复制代码
-- 代码位置:common.lua 第200-250行
function captureMode()
    logF("========== 进入CAPTURE模式 ==========")

    -- 1. 打开GNSS,启用AGPS
    exgnss.open(exgnss.DEFAULT, {
        tag = "common_app"
    })

    -- 2. WiFi扫描辅助定位
    wlan.scan()

    -- 3. 最多尝试10次(首次)或4次(后续)
    for i = 1, maxAttempts do
        srvs.dataSend() -- 触发定位数据采集-- 等待定位结果
        local result = sys.waitUntil("GNSS_STATE", 3000)
        if result == "FIXED" or exgnss.is_fix() then
            logF("定位成功!耗时", i * 3, "秒")
            return "TRACKING" -- 进入追踪模式
        end
        logF("定位尝试", i, "/", maxAttempts, "失败")
    end
    -- 4. 定位失败处理
    if not manage.isRun() then -- 静止状态
        logF("静止状态,降级到基站定位")
        return "STATIC_LBS"
    else
        logF("运动状态,继续尝试")
        return "CAPTURE" -- 继续尝试
    end
end
TRACKING模式 - 运动追踪
lua 复制代码
-- 关键特性:
-- 1. 1Hz定位频率,实时性高
-- 2. 自动轨迹补偿,减少漂移
-- 3. 双模式上传:正常5分钟/快速3秒
-- 4. 智能休眠检测:20秒无震动
-- 上传频率控制
if fastUpload then -- 快速模式:3秒/次(紧急情况)
    srvs.dataSend() -- 立即上传
else -- 正常模式:缓存+批量上传
    if waitUploadTimes >= 30 then -- 约5分钟
        uploadCache() -- 批量上传缓存数据
    end
end
STATIC模式 - 静止优化
lua 复制代码
-- STATIC_GNSS:使用最后有效位置
-- STATIC_LBS:使用基站定位(GNSS失败时)
-- 共同特点:
-- 1. 关闭GNSS,功耗降至1mA
-- 2. 定期心跳保持连接
-- 3. 运动立即唤醒,无感知切换

2.3 状态切换条件表

第三部分:轨迹补偿算法详解

3.1 GPS为什么需要补偿?

现场演示:请大家看这张图,红色框为原始GPS轨迹:

问题根源(上一节也讲过了)

  1. 多路径效应:信号反射,误差10-50米
  2. 卫星几何:仰角低时误差大
  3. 大气延迟:电离层影响
  4. 时钟误差:接收机时钟不准

3.2 四层补偿算法

3.3 核心算法代码解析

算法1:距离阈值过滤
lua 复制代码
-- trackCompensate.lua 第80-120行
local function distanceFilter(lat, lng, params)
    if #historyBuffer == 0 then
        return lat, lng -- 无历史数据,直接使用
    end

    local last = historyBuffer[#historyBuffer]
    local distance = calculateDistance(last.lat, last.lng, lat, lng)
    -- 自适应阈值:人员50米,车辆200米
    local threshold = params.distanceThreshold
    if distance > threshold then
        logF("漂移点过滤:距离", distance, "米 > 阈值", threshold, "米")
        return last.lat, last.lng -- 返回历史位置
    end
    return lat, lng -- 正常点
end
算法2:航向平滑(解决360°跳变)
lua 复制代码
-- 关键问题:359° → 1° 实际只变了2°,但差值算成358°
-- 解决方案:使用三角函数处理角度循环
local function smoothCourse(currentCourse, historyCourses)
    local sumSin = 0
    local sumCos = 0
    -- 加权平均,最近的数据权重高
    for i, course in ipairs(historyCourses) do
        local weight = 1.0 / (i + 1) -- 权重递减
        sumSin = sumSin + math.sin(math.rad(course)) * weight
        sumCos = sumCos + math.cos(math.rad(course)) * weight
    end
    -- 计算平均航向
    local avgCourse = math.deg(math.atan2(sumSin, sumCos))
    avgCourse = (avgCourse + 360) % 360 -- 确保0-360范围
    -- 检查是否跳变
    local diff = math.abs(currentCourse - avgCourse)
    if diff > 180 then
        diff = 360 - diff
        -- 处理循环
    end
    if diff > params.courseChangeThreshold then
        logF("航向跳变补偿:", currentCourse, "→", avgCourse)
        return avgCourse
    end
    return currentCourse
end
算法3:速度突变过滤
lua 复制代码
-- 原理:GNSS速度有时会从0突然跳到30km/h
-- 验证:用实际位移计算的速度更可靠
local function smoothSpeed(gnssSpeed, distance, timeDiff)
    -- 计算基于位移的实际速度
    local displacementSpeed = 0
    if timeDiff > 0 then
        displacementSpeed = (distance / timeDiff) * 3.6 -- m/s → km/h
    end
    -- 差异过大时,使用位移速度
    local speedDiff = math.abs(gnssSpeed - displacementSpeed)
    if speedDiff > params.speedChangeThreshold then
        logF("速度突变:GNSS=", gnssSpeed, "位移=", displacementSpeed, "差值=", speedDiff)
        return displacementSpeed
    end
    return gnssSpeed
end
算法4:拐点补偿
bash 复制代码
-- 解决急转弯被拉直的问题
local function cornerCompensation(lat, lng, bearing, speed)
    -- 1. 检测转弯角度
    local angleDiff = calculateAngleDifference()

    -- 2. 急转弯判定(人员120°,车辆90°)
    if angleDiff > params.cornerAngleThreshold then
        -- 3. 沿当前航向预测位置
        local distance = (speed / 3.6) * 3
        -- 假设3秒间隔
        local estimatedLat = lat + (distance / 6371000) * math.cos(math.rad(bearing))
        local estimatedLng = lng + (distance / 6371000) * math.sin(math.rad(bearing)) / math.cos(math.rad(lat))

        -- 4. 检查预测位置是否合理
        local predictionError = calculateDistance(lat, lng, estimatedLat, estimatedLng)
        if predictionError < params.distanceThreshold then
            logF("拐点补偿:角度", angleDiff, "°")
            return estimatedLat, estimatedLng
        end
    end
    return lat, lng
end

3.4 双场景自适应系统

lua 复制代码
-- 人员模式 vs 车辆模式
local config = {
    scene = "person", -- 默认人员模式
    autoAdaptive = true, -- 自动切换
    personParams = {
        distanceThreshold = 50, -- 50米漂移过滤
        speedChangeThreshold = 10, -- 10km/h速度突变
        courseChangeThreshold = 60, -- 60°航向跳变
        cornerAngleThreshold = 120 -- 120°急转弯
    },
    vehicleParams = {
        distanceThreshold = 200, -- 200米漂移过滤
        speedChangeThreshold = 50, -- 50km/h速度突变
        courseChangeThreshold = 30, -- 30°航向跳变
        cornerAngleThreshold = 90 -- 90°急转弯
    },

    adaptiveSpeedThreshold = 15 -- 15km/h切换阈值
}

-- 自动场景判断
local function getCurrentParams()
    if config.autoAdaptive and #historyBuffer >= 3 then
        -- 计算最近3个点的平均速度
        local avgSpeed = 0
        for i = #historyBuffer - 2, #historyBuffer do
            avgSpeed = avgSpeed + historyBuffer[i].speed
        end
        avgSpeed = avgSpeed / 3
        if avgSpeed >= config.adaptiveSpeedThreshold then
            logF("自动切换:车辆模式(速度", avgSpeed, "km/h)")
            return config.vehicleParams
        else
            logF("自动切换:人员模式(速度", avgSpeed, "km/h)")
            return config.personParams
        end
    end
    -- 固定模式
    return config.scene == "vehicle" and config.vehicleParams or config.personParams
end

3.5 优化效果对比

测试数据

lua 复制代码
原始轨迹数据:
  点1: 31.123456, 121.654321, 速度5, 航向180
  点2: 31.123500, 121.654300, 速度25, 航向185 ← 速度突变!
  点3: 31.123800, 121.654000, 速度6, 航向359 ← 航向跳变!

优化后数据:
  点1: 31.123456, 121.654321, 速度5, 航向180
  点2: 31.123480, 121.654310, 速度5, 航向182 ← 速度修正
  点3: 31.123520, 121.654280, 速度5, 航向181 ← 航向修正

量化提升

第四部分:功耗优化策略

4.1 三层功耗管理架构

lua 复制代码
-- 第一层:硬件级控制
-- 引用计数管理,避免资源冲突
local function hardwarePowerControl() -- 不同模块使用独立tag
    exgnss.open(exgnss.DEFAULT, {
        tag = "common_app"
    }) -- 主定位
    exgnss.open(exgnss.TIMERORSUC, {
        tag = "lowpower"
    }) -- 低功耗模式
    -- 只有所有tag都关闭,硬件才真正断电
    -- 避免:A模块关闭影响B模块使用
end
-- 第二层:应用级控制
-- 状态机自动切换
local function applicationPowerControl()
    -- TRACKING模式:GNSS常开
    -- STATIC模式:GNSS关闭
    -- 根据运动状态自动调整
    if manage.isRun() then
        logF("运动状态,保持GNSS开启")
    else
        logF("静止状态,关闭GNSS节省功耗")
        exgnss.close(exgnss.DEFAULT, {
            tag = "common_app"
        })
    end
end
-- 第三层:系统级控制
-- 统一休眠管理
local function systemPowerControl() -- manage模块引用计数
    manage.wake("READ_GNSS_DATA") -- 读取数据时唤醒
    manage.sleep("READ_GNSS_DATA") -- 读取完成后休眠

    manage.wake("charge") -- 充电时唤醒
    manage.sleep("charge") -- 充电结束休眠
    -- 检查所有tag,全部休眠时进入低功耗模式
    local allSleeping = true
    for tag, state in pairs(manage.tags) do
        if state > 0 then
            allSleeping = false
            break
        end
    end
    if allSleeping then
        pm.power(pm.WORK_MODE, 1) -- 进入长连接低功耗模式休眠
        logF("所有模块休眠,进入LIGHT模式")
    end
end

4.2 震动检测优化算法

lua 复制代码
-- vibration.lua 核心逻辑
local function vibrationOptimization()
-- 静止→运动:快速响应(5秒内2次震动)
-- 避免误判:防止偶尔震动误触发
-- 运动→静止:延迟确认(20秒内0次震动)
-- 避免误判:防止行走时停顿误判为静止
-- 有效震动检测:10秒内5次震动
-- 防止误触:过滤无效震动(如放口袋里的晃动)
-- 冷却机制:有效震动后30分钟内不再重复触发
-- 避免频繁:如车辆持续震动场景
end

优化空间:

  1. 进一步降低GNSS功耗:按需定位
  2. 优化网络连接:心跳间隔调整
  3. 深度休眠:夜间完全关闭

第五部分:数据通信架构及代码完整实现

5.1 JT808协议完整实现

5.2 数据包结构详解

lua 复制代码
-- JT808 0x0200 位置信息报文结构
local function buildPositionPacket()
    -- 1. 消息头(12字节)
    local header = api.NumToBigBin(0x0200, 2) -- 消息ID
    .. api.NumToBigBin(bodyLen, 2) -- 消息体属性
    .. simID -- 终端手机号(6字节BCD)
    .. api.NumToBigBin(msgSn, 2) -- 消息流水号

    -- 2. 消息体基础部分(28字节)
    local body = api.NumToBigBin(0, 4) -- 报警标志
    .. api.NumToBigBin(status, 4) -- 状态位
    .. api.NumToBigBin(lat, 4) -- 纬度(度*1000000)
    .. api.NumToBigBin(lng, 4) -- 经度(度*1000000)
    .. api.NumToBigBin(altitude, 2) -- 海拔(米)
    .. api.NumToBigBin(speed, 2) -- 速度(0.1km/h)
    .. api.NumToBigBin(course, 2) -- 方向(度)
    .. timeBCD -- 时间(6字节BCD)

    -- 3. 扩展信息(附加项)
    -- 里程信息
    body = body .. api.NumToBigBin(0x01, 1) -- 附加项ID
    .. api.NumToBigBin(4, 1) -- 长度4字节
    .. api.NumToBigBin(mileage, 4) -- 里程值

    -- 电量信息
    body = body .. api.NumToBigBin(0x04, 1) -- 附加项ID
    .. api.NumToBigBin(2, 1) -- 长度2字节
    .. api.NumToBigBin(chargeState, 1) -- 充电状态
    .. api.NumToBigBin(batteryPercent, 1) -- 电量百分比

    -- WiFi信息(如有)
    if wifiList and #wifiList > 0 then
        body = body .. api.NumToBigBin(0x54, 1) -- WiFi附加项
        .. buildWifiInfo(wifiList)
    end

    -- 4. 组装完整报文
    local packet = header .. body
    packet = packet .. string.char(api.XorCheck(packet)) -- 校验码
    packet = msgEncode(packet) -- 转义处理
    packet = "\x7E" .. packet .. "\x7E" -- 添加起始结束符

    return packet
end

5.3 多服务器管理架构

lua 复制代码
local srvs = {
    servers = {}, -- 服务器实例列表-- 添加服务器
    add = function(self, server)
        table.insert(self.servers, server)
        logF("添加服务器,当前数量:", #self.servers)
    end,

    -- 发送数据到所有服务器
    dataSend = function(self, data)
        logF("========== 开始发送数据 ==========")

        -- 数据预处理和日志
        if data then
            self:_logDataInfo(data)
        else
            logF("数据为空,自动调用common.monitorRecord()")
            data = common.monitorRecord()
        end -- 分发到所有服务器
        local successCount = 0
        for i, srv in ipairs(self.servers) do
            if srv.dataSend then
                local ok, err = pcall(srv.dataSend, data)
                if ok then
                    successCount = successCount + 1
                    logF("服务器", i, "发送成功")
                else
                    logF("服务器", i, "发送失败:", err)
                end
            end
        end

        logF("发送完成,成功:", successCount, "/", #self.servers)
        return successCount
    end,

    -- 检查连接状态(任意一个连接成功即返回true)
    isConnected = function(self)
        for _, srv in ipairs(self.servers) do
            if srv.isConnected and srv.isConnected() then
                return true
            end
        end
        return false
    end
}

-- 使用示例
local auxServer = require "auxServer"
local productionServer = require "productionServer"

srvs:add(auxServer) -- 测试服务器
srvs:add(productionServer) -- 生产服务器
-- 一次调用,多处发送
srvs:dataSend(positionData)

5.4 数据上传频率控制

lua 复制代码
-- 双模式上传策略
local uploadConfig = {
    normal = {
        interval = 5 * 60, -- 5分钟
        cacheSize = 30, -- 缓存30次后上传
        description = "正常模式:平衡功耗与实时性"
    },

    fast = {
        interval = 3, -- 3秒
        immediate = true, -- 立即上传
        description = "快速模式:紧急追踪场景"
    }
}

-- 模式切换接口
function common.setfastUpload(duration)
    if duration > 0 then -- 进入快速上传模式
        logF("进入快速上传模式,持续", duration, "分钟")
        fastUpload = true -- 立即触发一次上传
        srvs.dataSend()

        -- 定时自动退出
        sys.timerStart(function()
            fastUpload = false
            logF("快速上传模式结束")
        end, duration * 60 * 1000)
    else -- 退出快速上传模式
        fastUpload = false
        logF("退出快速上传模式")
    end
end
-- 触发方式
-- 1. 服务器指令:0x8202消息设置上传间隔
-- 2. 本地逻辑:检测到特殊场景(如放学时间)
-- 3. 手动调用:
common.setfastUpload(10)

第五部分:项目代码逐行过

项目代码 :[Gitee仓库链接]

打开代码一行行过就行,不用写出来了

第六部分:实际应用演示

6.1 演示准备

硬件准备

  1. Air8000A开发板(已刷本项目固件)
  2. SIM卡(已开通数据业务)
  3. 锂电池(3000mAh)
  4. 电脑(串口调试+Web平台)

软件准备

  1. LuaTools(串口调试/代码下载)
  2. Web监控平台(显示轨迹)

注:目前定位监控平台不对外开放,仅在直播过程中,演示使用一下

第七部分:扩展与定制

7.1 如何适配新硬件?

lua 复制代码
-- 步骤1:修改硬件配置
-- 在bootup.lua中调整GPIO和参数
local hardwareConfig = {
    gnssUart = 2, -- GNSS串口号
    gnssBaudrate = 115200, -- 波特率
    powerKeyPin = 46, -- 电源键GPIO
    chargeDetectPin = 40, -- 充电检测GPIO
    vibrationPin = gpio.WAKEUP2, -- 震动传感器
    leds = { -- LED指示灯
        network = 1,
        gnss = 17,
        charge = 21
    }
}

-- 步骤2:调整功耗参数
if _G.NEW_HARDWARE then
    _G.GNSS_LOWPOWER_ENABLE = true
    _G.GNSS_LOWPOWER_INTERVAL = 120 -- 新硬件续航更长
end
-- 步骤3:校准电池曲线
local newBatteryCurve = {4200, 4180, 4160, 4140, 4120 -- 100%-96%-- ... 根据实际硬件调整
}

7.2 添加新服务器

lua 复制代码
-- 新建一个服务器模块:myServer.lualocal 
myServer = {}

function myServer.dataSend(data) -- 1. 建立TCP连接
    local socket = socket.create(nil, "myServer")
    socket.config(socket, myConfig.port, false)
    socket.connect(socket, myConfig.host, myConfig.port)

    -- 2. 数据格式转换(如果需要)
    local myFormatData = convertToMyFormat(data)

    -- 3. 发送数据
    local result = socket.tx(socket, myFormatData)

    -- 4. 返回结果
    return result
end
function myServer.isConnected() -- 检查连接状态
    return connectStatus
end
-- 在bootup.lua中添加
local myServer = require "myServer"
srvs.add(myServer)

7.3 自定义轨迹算法

lua 复制代码
-- 继承或替换trackCompensate模块
local myTrack = require "trackCompensate" -- 方法1:修改配置
myTrack.setConfig({
    scene = "vehicle",
    personParams = {
        distanceThreshold = 30, -- 更严格的过滤
        cornerAngleThreshold = 90 -- 更早的拐点检测
    }
})

-- 方法2:完全自定义算法
local function myCompensateAlgorithm(lat, lng, course, speed)
    -- 添加卡尔曼滤波
    local filtered = kalmanFilter(lat, lng)

    -- 添加地图匹配(如果有地图数据)
    if hasMapData then
        return mapMatching(filtered.lat, filtered.lng)
    end
    return filtered.lat, filtered.lng, course, speed
end
-- 在common.lua中替换调用
-- 原调用:trackCompensate.compensate()
-- 新调用:myCompensateAlgorithm()

7.4 添加新功能模块

lua 复制代码
-- 示例:添加温度监控模块
local temperature = {}

function temperature.init() -- 初始化温度传感器
    sensor.init()

    -- 定时读取温度
    sys.timerLoopStart(function()
        local temp = sensor.read()
        logF("当前温度:", temp, "°C")

        -- 高温报警
        if temp > 50 then
            sys.publish("HIGH_TEMP_ALARM", temp)
        end
    end, 60000) -- 每分钟检查一次
end
function temperature.getCurrent()
    return currentTemperature
end
-- 在bootup.lua中加载
_G.temperature = require "temperature"
temperature.init()

第八部分:Q&A环节

8.1 技术问题

Q1:AGPS如何工作的?需要服务器支持吗?

A:这个在上一讲中有提到,AGPS(辅助GPS)通过两个步骤加速定位:

  1. 基站定位:通过附近的基站(单基站单独定位,多基站的话进行三角定位),确定大致位置(精度50-1500米)
  2. 星历下载:从服务器获取当前可用的卫星信息,减少搜索时间

在我们的项目中,exgnss库已经内置了完整的AGPS逻辑,包括:

  • 自动获取基站信息
  • 从互联网下载星历数据
  • 时间同步校准
  • 无需额外的客户自建服务器支持,使用合宙的AGPS服务

Q2:轨迹补偿算法会增加延迟吗?

A:几乎零延迟。原因:

  1. 轻量计算:所有算法都是简单的数学运算,在毫秒级完成
  2. 本地处理:在设备端实时处理,不依赖网络
  3. 历史缓存:最多保存5个点,内存占用极小
  4. 异步执行:在数据上报周期(5分钟)内完成,不影响实时性

实际测试中算法执行时间 < 10ms,完全可以忽略。

Q3:如何保证数据不丢失?

A:四级数据保障机制:

lua 复制代码
-- 1. 本地缓存
local dataCache = {} -- 最大200条
if #dataCache > 200 then -- FIFO淘汰,但记录已删除数量
    fskv.set("lost_data_count", lostCount + 1)
end
-- 2. 失败重试
local retryCount = 0
while not sendSuccess and retryCount < 3 do
    sendSuccess = srvs.dataSend(data)
    retryCount = retryCount + 1
    if not sendSuccess then
        sys.wait(1000) -- 1秒后重试
    end
end
-- 3. 确认机制
-- JT808协议要求服务器回复0x8001确认包
-- 未收到确认会触发重传

-- 4. 持久化存储
-- 关键数据(如最后位置)写入fskv
-- 断电后仍可恢复

Q4:最多支持多少个服务器同时连接?

A:理论上64个socket通道限制,实际受硬件内存限制:

  • 内存限制:每个TCP连接约10-20KB内存
  • Air8000A:建议不超过8个同时连接
  • 网络带宽:每个连接需要独立的数据流

优化建议:

  1. 主备模式:主服务器故障时切换备用
  2. 分级上报:关键数据报所有服务器,普通数据只报主服务器
  3. 按需连接:非实时服务器可以间歇性连接

8.2 业务问题

Q5:这个项目适合学生卡场景吗?有什么特殊考虑?

A:适合!这个示例demo就是根据定制的学生卡项目进行了脱敏和算法优化而来,

学生场景

  • 作息规律:上学、放学时间固定
  • 活动范围:家→学校→辅导班
  • 运动模式:步行+公共交通
  • 安全需求:实时追踪+电子围栏

可以额外加的优化

bash 复制代码
-- 1. 上下学时间自动快速上传
local schoolTimeConfig = {
    goToSchool = "07:00-08:30",
    goHome = "15:30-18:00"
}

-- 2. 电子围栏自动预警
function checkGeoFence(lat, lng)
    if not isInSchool(lat, lng) and isSchoolTime() then
        sys.publish("OUT_OF_SCHOOL_ALARM")
    end
end
-- 3. 低电量家长提醒
if batteryPercent < 20 then
    sendSmsToParent("设备电量低,请及时充电")
end

Q6:项目部署需要哪些准备工作?

A:四步部署法:

第一步:硬件准备

lua 复制代码
1. 采购硬件清单所有部件
(本项目只用到了Air8000A核心板、一块电池、以及一张物联网卡[可以拿手机副卡零时顶替一下])
2. 烧录固件(本项目代码)
4. 插入SIM卡(开通数据业务)

第二步:服务器准备(这里就不做过多介绍了,每个客户的服务器都不一样,本示例中的服务器为合宙临时测试服务器,即使客户烧录了代码,没有账号也看不到设备,本web平台仅作展示使用,具体后期怎么开放出来,内部还在商量中)

lua 复制代码
1. 准备云服务器(1核2G足够)
2. 部署JT808协议解析服务
3. 配置数据库(MySQL/PostgreSQL)
4. 部署Web管理平台

第三步:配置对接

lua 复制代码
1. 在cfg.lua中配置服务器地址
2. 在平台注册设备ID
3. 测试数据收发
4. 配置报警规则和电子围栏

第四步:批量部署

lua 复制代码
1. 使用量产工具批量烧录
2. 自动化测试每台设备
3. 包装和发货
4. 提供用户使用文档

8.3 进阶问题

Q7:如何进一步降低功耗?

A:五级功耗优化策略:

已实现(当前项目):

  1. GNSS按需开关
  2. 状态机智能切换
  3. 心跳间隔优化

可进一步优化

lua 复制代码
-- 1. 深度睡眠模式

pm.power(pm.WORK_MODE, 3) -- 深度睡眠,功耗<12uA
-- 2. 事件唤醒机制
gpio.setup(wakeupPin, function()
    pm.power(pm.WORK_MODE, 0) -- 震动唤醒,进入工作模式
end, gpio.PULLUP, gpio.RISING)

-- 3. 自适应心跳
local function adaptiveHeartbeat()
    if isMovingFast then
        return 10 -- 快速移动,10秒心跳
    elseif isNight then
        return 300 -- 夜间,5分钟心跳
    else
        return 60 -- 默认60秒
    end
end

具体实现

lua 复制代码
function handleSignalLoss()-- 1. 检测信号丢失
if not exgnss.is_fix() and lastFixTime then
local lossDuration = os.time() - lastFixTime

        -- 2. 短期丢失:惯性导航
        if lossDuration < 120 then  -- 2分钟内
        local estimated = inertialNavigation(
                lastPosition, 
                lastSpeed, 
                lastCourse,
                lossDuration
            )
            return estimated
        end
        -- 3. 长期丢失:切换LBSelse
            logF("GNSS信号丢失超过2分钟,切换基站定位")
            return lbsLocation()
        endendend-- 信号恢复后的轨迹修正
        function correctAfterRecovery(estimatedPoints, actualPoints)
        -- 使用B样条曲线平滑过渡
        local smoothed = bsplineSmooth(estimatedPoints, actualPoints)
    return smoothed
end

8.4 天线相关问题

天线相关问题又分了很多种,有如下几个大类

  1. 天线使用问题
  2. 天线设计问题
  3. 星系切换问题
  4. 干扰问题

8.4.1 天线使用问题

有源/无源天线混淆

有部分开发者经常遇到,自己去了户外,按理说应该在35S左右就能定位成功了啊,怎么自己一两分钟都没几颗星,等了10多20分钟依旧还是定位不成功,同步对比手机,发现差距不止一点点,此时应该先检查GNSS天线设计问题,看看自己是不是将有源天线插给了无源天线预留的底座,或者无源天线插给了有源天线预留的底座

8.4.2 天线设计问题

更多客户遇到的,不是户外定位不到,而是户外定位速度极其的慢的问题,常见于无源天线(因为无源天线对结构、PCB、走线要求都比较高),如果自己设计没有注意下面几点,是很有可能定位不到/定位极其的慢的。

8.4.2.1 无源天线设计注意事项
  • 我们的GPS模块上均内置18dBm增益的GPS LNA,可以直接将陶瓷介质的无源天线焊接在模块GPS_ANT PIN脚处使用。 产品布局的时候,GPS陶瓷天线朝上摆放;模块可以放到PCB的另一面。这样就可以做到GPS_ANT PIN到天线焊盘走线尽可能短。
  • 匹配电路;如果天线焊盘离模块的GPS_ANT PIN脚很近,那么可以不预留匹配电路。如果由于结构等其他原因造成GPS天线远离模块GPS_ANT PIN,那么建议预留pi型匹配电路。模块 GPS_ANT PIN到GPS天线焊盘之间走线必须做50欧姆特性阻抗控制;如果是多层板,建议阻抗线走L1层,L2层镂空参考L3的地。2层板走线线宽可以参考GSM天线部分走线线宽。
  • 天线下方不要走线并做漏铜处理做天线的反射面;见下图:
  • 天线周边不要有干扰源,特别是DCDC等器件;另外周边也不要有比GPS天线高的金属器件:如下图:

8.4.2.2 有源天线注意事项

有源天线构造与实物,见下图

红框内GPS有源天线组成部分为:陶瓷天线、声表滤波器、低噪声放大电路、射频线缆、RF接头。 其中低噪声放大电路是将信号进行放大和滤波的部分。

  1. PCB尺寸对天线性能的影响 承载陶瓷天线的PCB形状及面积。由于GPS有触地反弹的特性,当背景是7cm×7cm无间断大地时,patch天线的效能可以发挥到极致。虽然受外观结构等因素制约,但尽量保持相当的面积且形状均匀。另外放大电路增益的选择必须配合后端LNA增益;一般不建议有源天线增益超过29dBm,否则信号过饱和可能会导致自激。
  2. 内外置天线兼容和供电处理; 参考电路如下,R5和R6是为了兼容陶瓷PATCH天线和有源天线做的共PAD兼容设计;L6和C38是有源天线供电电源滤波电路。
  1. GPS模块使用外置天线时的供电处理。PCB部分如下图
8.4.2.3 GPS天线选型建议
  1. 在终端结构空间容许,能够统一保证GPS天线面朝上的安装使用状态;并且周边没有大的金属物件遮挡的情况下,建议使用GPS陶瓷天线,在空间容许的情况下尽量选择大尺寸的陶瓷天线。
  2. 在不能保证终端使用状态,且空间受限:比如手机,带定位功能的胸牌;建议使用FPC天线
  3. 在明确终端安装环境恶劣,并且对GPS性能有较高要求的;建议使用GPS有源天线
  4. 在不能保证产品安装使用状态,但是空间不受限制,也可以选择类似于GSM的外置棒状天线。
8.4.2.4 对天线厂家的要求
  1. VSWR(电压驻波比):GPS天线电压驻波比一般要求调到1.5左右。
  2. 功率:效率一般要求在40%左右
  3. 平均增益:平均增益要求在-0.5dB
  4. OTA:一般天线厂大多不具备GPS 天线OTA测试环境,天线调试好后可以以实际测试数据做标准来衡量,一般我们GPS实测时要求是:可用于定位卫星颗数大于6颗以上,最强的信号在45 dB/Hz左右,要有3颗卫星信号大于40 dB/Hz。
8.4.3 星系切换问题

有很多客户遇到过,模组默认固件,只打开GNSS电源,35S左右就能定位到了,但是切换成单北斗,就需要2分钟多甚至更长时间才能定位成功。

首先明确一点,合宙的大多数模组,均使用的单频(L1)GNSS芯片,所以内部能搜到的北斗卫星,只有B1C或者B1I,这两个频段的北斗卫星,上一讲中我也提到过,北斗卫星为高轨卫星,在同一片区域内,卫星数可能不会很多,实测在我家附近的广场上,单频(L1)GNSS芯片,只能搜到这几颗北斗卫星

所以,在明确自己是真正需要单北斗/单GPS或者其他星系前,尽量不要将模块切换为单星系状态,如果客户对单北斗需求非常明确,建议选择真正的单北斗芯片,杜绝后患,因为很多单北斗应用是需要进实验室过多项认证的,使用多星系GNSS芯片,有极大概率过不去单北斗的认证。

8.4.4 外部干扰源问题

此种情况不能说常见,但是确实客观存在,之前有部分客户就遇到了,在他们公司附近一直定位不到,但是客户放在自己小区前面广场上就能定位成功,查看地图得知,客户的公司附近,有类似"中国军工"等涉密单位,不只是GNSS定位不到,偶尔自己的手机5G/4G信号也没有,此种情况定位不到的原因不言而喻了。

不过还有少量客户遇到的干扰源还是比较明显,例如只针对GPS频段发射的干扰源,此时切换为单北斗模式,即使是单频模组,在部分情况下,还是能够正常定位成功的。

以上四点是最为常见的四种无法定位的情况,如果你使用合宙的GNSS模组排除了这四点,依旧无法定位,欢迎你来找合宙,我们将会竭力为您排查您所遇到的问题

结束语

项目价值总结

技术创新

  1. 🎯 智能四状态机:平衡精度与功耗
  2. 🛣️ 自适应轨迹补偿:减少80%GPS漂移
  3. 🔋 三层功耗管理:续航从一两天提升到一周左右
  4. 🌐 多服务器架构:一次开发,多处部署
  5. 🛡️ 工业级可靠性:完整JT808协议+多重保障

实用价值

  • 低成本:软件开源,硬件上用户只需Air8000A核心板+电池即可实现主要功能
  • 易部署:模块化设计,快速定制
  • 可扩展:支持百万级设备接入
  • 标准化:符合行业协议,易于集成

学习收获

通过这个项目,你可以学到:

  1. 嵌入式开发全流程:从硬件选型到软件部署
  2. 物联网架构设计:设备端+服务器端+平台端
  3. 算法优化实践:轨迹补偿、功耗优化、网络传输
  4. 工程化思维:模块化、配置化、可测试性
  5. 产品化思维:用户体验、成本控制、可维护性
  6. 天线设计建议:有源无源设计及其干扰源排查

下一步建议

初学者

  1. 下载代码,编译运行
  2. 修改配置,体验不同模式
  3. 添加一个简单功能(如LED控制)

进阶者

  1. 优化轨迹算法(尝试卡尔曼滤波)
  2. 实现Web管理平台
  3. 设计硬件PCB,降低成本

企业用户

  1. 基于此架构开发产品
  2. 部署到云平台,服务客户
  3. 根据反馈持续优化
相关推荐
乐迪信息2 小时前
乐迪信息:精准识别每一艘船:船舶AI类型分类算法技术解析
大数据·人工智能·物联网·安全·目标跟踪·分类·数据挖掘
优化Henry2 小时前
LTE站点频闪退服告警根因定位与处理
运维·网络·信息与通信
艾莉丝努力练剑3 小时前
【Linux网络】计算机网络入门:Socket编程预备,从字节序共识到 Socket 地址结构的“伪多态”设计
linux·服务器·网络·c++·学习·计算机网络
Oll Correct10 小时前
实验二十一:验证OSPF可以划分区域
网络·笔记
半个西瓜.12 小时前
车联网安全:GPS定位测试.(静态欺骗)
网络·安全·网络安全·车载系统·安全威胁分析
pengyi87101513 小时前
独享IP+动态IP结合核心逻辑,破解稳定与灵活的矛盾
linux·运维·网络
梅羽落18 小时前
MSF基础1
网络·网络协议·tcp/ip
被摘下的星星18 小时前
子网de划分
网络·算法
xiaoshuaishuai818 小时前
C# modbustcp的ack包通信延迟原因
网络·tcp/ip·c#