跳转至

01 GNSS定位

作者:李源龙 | 最后修改:2026-07-31

一、GNSS 概述

GPS 最先来自美国,1978 年,美国发射了第一颗 GPS 卫星,发明和实践了卫星定位技术。卫星定位技术原理是,覆盖全球的多颗定位卫星连续发射一定频率的无线电信号,移动终端上集成便携式卫星信号接收机,接收机接收卫星信号并测量卫星到终端接收机之间的距离,最终由移动终端利用多颗卫星位置和与这些卫星的距离计算出移动终端的具体位置。后来出现了欧洲的 Galileo、俄罗斯的 GLONASS、中国的北斗等,所以如今的 GPS 实质上是作为一个卫星定位技术体系 GNSS 的代名词,而不是单只美国的 GPS 系统。

GNSS 提供的服务包括定位、授时和导航。定位服务就是 GNSS 终端获得其位置的服务,授时服务就是 GNSS 终端获得正确时间的服务,导航服务是计算 GNSS 终端速度和运动方向的服务。GNSS 不限制终端数,在 GNSS 卫星信号不被阻挡的情况下,在地球上任何地点、任何时间,任何 GNSS 终端都可以得到正确的位置和时间。定位只需要一个条件,那就是能够接收到足够多的卫星信号。因此在室内通常无法定位。

Air8201G 作为一款集成了 4G、GNSS(全球导航卫星系统)二合一的模块,其内置的 GNSS 模块能够提供稳定、准确的定位服务。

二、演示功能概述

使用 Air8201G,使用 exgnss 扩展库和 libgnss 核心库两种独立的方式来展示 GNSS 定位。更推荐使用 exgnss 扩展库,因为 exgnss 在 libgnss 核心库的基础上,对 gnss 的开和关做了函数优化,不需要繁琐的步骤进行开和关,同时加入了 gnss 应用的概念,可以选择常开或者定时开启,支持不同场景的应用方式,让用户在开发的过程中更简单。

2.1 exgnss:

exgnss 主要有两个文件,分别是 single 和 combination:

single 示例主要是展示 exgnss 库的三种应用模式,

exgnss.DEFAULT 模式

exgnss.TIMERORSUC 模式

exgnss.TIMER 模式

主要操作为:

1、打开三种应用模式

2、等待 40 秒关闭操作 TIMER 模式

3、然后查询三种应用模式是否处于激活模式

4、等待 10 秒关闭全部应用模式,再次查询三种模式是否处于激活模式

5、然后获取最后一次的定位经纬度数据打印

6、定位成功之后使用 exgnss 库获取 gnss 的 rmc 数据

combination 示例主要展示几种不同的应用场景:

第一种场景是:正常模式,第一步先是通过 tcp_client_main 文件连接服务器,然后第二步模块会配置 GNSS 参数,开启 GNSS 应用,第三步会开启一个 60s 的定时器,定时器每 60s 会打开一个 60sTIMERRORSUC 应用,第四步定位成功之后关闭 GNSS,然后获取 rmc 获取经纬度数据,发送经纬度数据到服务器上。

第二种场景是:低功耗模式,第一步先是通过 tcp_client_main 文件连接服务器,然后第二步模块会配置 GNSS 参数,开启 GNSS 应用,第三步会开启一个 60s 的定时器,定时器每 60s 会进入正常模式,打开一个 60sTIMERRORSUC 应用,第四步定位成功之后关闭 GNSS,然后获取 rmc 获取经纬度数据,发送经纬度数据到服务器上,进入低功耗模式。

第三种场景是:PSM+ 模式,唤醒之后第一步是配置 GNSS 参数,开启 GNSS 应用,第二步定位成功之后关闭 GNSS,然后获取 rmc 获取经纬度数据,拼接唤醒信息和经纬度信息,连接服务器,然后把数据发送数据到服务器上,配置休眠唤醒定时器 ,进入飞行模式,然后进入 PSM+ 模式。

第四种场景是:用 780EGG 的三轴加速度的应用场景,第一步先是通过 tcp_client_main 文件连接服务器,第二步配置 GNSS 参数,打开内部加速传感器,设置防抖和中断模式,关于中断触发提供了两种方案,有效震动模式和持续震动检测模式,第三步检测到有效震动或者持续震动后,打开 GNSS,每隔 5s 获取 rmc 获取经纬度数据,发送经纬度数据到服务器上。

2.2 libgnss

libgnss 的 demo 主要是打开 GNSS,设置串口初始化,然后绑定串口,设置调试日志,然后开启 AGPS 辅助定位,定位成功打印 rmc 数据

三、准备硬件环境

1、Air8201H 开发板一块 + 可上网的 sim 卡一张 +4g 天线一根+GNSS天线一根;

2、TYPE-C USB 数据线一根;

TYPE-C USB 数据线直接插到板子的 TYPE-C USB 座子,另外一端连接电脑 USB 口;

四、准备软件环境

1. 烧录工具 Luatools

2. 内核固件文件(底层 core 固件文件):

Air8201H固件(基于Air780EHM)

3. LuatOS 需要的脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8201/demo/gnss

4. lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件,如果没有下载最新的 lib,可以在 luatos 库里面下载最新的 lib 使用:https://gitee.com/openLuat/LuatOS/tree/master/script/libs

五、GNSS 软硬件参考

5.1 API 接口介绍

本教程使用 exgnss api 接口为:

https://docs.openluat.com/osapi/ext/exgnss/

libgnss api 接口为:

https://docs.openluat.com/osapi/core/libgnss/

六、代码示例介绍

本文主要提供 2 个 demo 目录,libgnss 和 exgnss,libgnss 主要是对用相对原始的方法打开 GNSS,然后利用 libgnss 核心库去获取数据。exgnss 相当于在 libgnss 上做了一层封装,仅使用设置和打开的方式即可一键开启和关闭 GNSS,不需要自己再去设置串口设置配置之类的操作了。

exgnss 分为 single 文件和 combination 文件:

其中 single 只是对 exgnss 库的三种 GNSS 应用的展示,combination 主要是提供了四种应用场景

noraml:正常功耗模式定位

lowpower:低功耗模式

psm:PSM+ 模式

vibration:三轴加速度传感器触发定位

6.1 exgnss 的示例

6.1.1 exgnss 的三种应用的用法

6.1.1.1 exgnss 的应用说明

关于 exgnss 库的三种应用说明:

--关于exgnss的三种应用场景:
exgnss.DEFAULT:
--- exgnss应用模式1.
-- 打开gnss后,gnss定位成功时,如果有回调函数,会调用回调函数
-- 使用此应用模式调用exgnss.open打开的“gnss应用”,必须主动调用exgnss.close
-- 或者exgnss.close_all才能关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是一直打开,除非自己手动关闭掉

exgnss.TIMERORSUC:
--- exgnss应用模式2.
-- 打开gnss后,如果在gnss开启最大时长到达时,没有定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,如果在gnss开启最大时长内,定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用exgnss.close或者
-- exgnss.close_all主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是设置规定时间打开,如果规定时间内定位成功就会自动关闭此应用,
-- 如果没有定位成功,时间到了也会自动关闭此应用

exgnss.TIMER:
--- exgnss应用模式3.
-- 打开gnss后,在gnss开启最大时长时间到达时,无论是否定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用exgnss.close或者exgnss.close_all
-- 主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是设置规定时间打开,无论是否定位成功,到了时间都会自动关闭此应用,
-- 和第二种的区别在于定位成功之后不会自动关闭,到时间之后才会自动关闭

关于应用需要注意的是:使用 exgnss.close()关闭的只是对应的应用,所有应用关闭,gnss 也会关闭,这样做的好处是可以处理不同场景下的不同逻辑,比如服务器下发一条定位指令,定位成功之后关掉,这个时候又下发了一个指令需要长开启定位,这样两种指令就会冲突,需要代码去处理到底是不是要关掉 gnss,这个时候应用的好处就体现出来了,开启 exgnss.TIMERORSUC 第一个应用,定位成功之后关掉对应的应用,同时开启 exgnss.DEFAULT 第二个应用,gnss 就会常开,在应用 1 结束之后,只会对应用进行自动关闭,不会影响到第二个应用,gnss 还会处于常开状态。

关于 agps 辅助定位的逻辑:agps 俗称辅助定位,其逻辑是把星历文件发送给 gnss 芯片,同时发送时间和用基站定位获取的大概位置,然后芯片解析处理,达到快速定位的效果。

exgnss 扩展库的辅助定位功能,我们处理方式是每个小时都会去更新星历,原因是北斗是 1h 一更新,gps 是 4h 一更新,所以取最小值,我们选择了一小时一更新

对于辅助定位需要注意的是,因为辅助定位属于强行把星历文件灌给 gnss 芯片,虽然可以达到快速定位的效果,但是 gnss 芯片实际解析星历需要时间,exgnss 库默认开启了一个 20s 的 gnss 应用,做这个操作是因为,假设开启辅助定位,定位到之后立马关掉 gnss 电源,会导致 gnss 星历解析不完全,这样会影响下次定位为冷启动,冷启动时间大概需要 35s。如果不需要这个逻辑,可以接受冷启动,或者每次都使用辅助定位,可以通过 exgnss.setup()的 auto_open=false 配置添加到配置表里面。

6.1.1.2 代码讲解

main.lua

主要是为了加载了 exgnss 扩展库,加载 gnss 代码

--[[
@module  main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
本demo演示的功能为:
使用Air8201H模块,通过exgnss.lua扩展库,开启GNSS定位,展示模块的三种应用状态
]]

--[[
必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为999
        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "gnsstest"
VERSION = "001.999.000"



-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
--     errDump.config(true, 600)
-- end


-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用


-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
--     log.info("mem.lua", rtos.meminfo())
--     log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)

exgnss=require("exgnss")    


require"gnss"


-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

gnss.lua

主要操作为:

1.开启协程,运行 gnss_fnc 函数,通过 exgnss.setup(gnssotps)配置 gnss 的参数,通过 exgnss.open 开启三种不同的应用

2.等待 40 秒使用 exgnss.close 关闭 TIMER 模式,使用 exgnss.close 需要两个参数,第一个是 exgnss 应用模式,第二个是 tag 标签

3.然后利用 exgnss.is_active 查询三种应用模式是否处于激活模式,查询应用是否处于激活状态同样需要两个参数,第一个是 exgnss 应用模式,第二个是 tag 标签

4.等待 10 秒使用 exgnss.close_all 关闭全部应用模式,再次查询三种模式是否处于激活模式

5.然后使用 exgnss.last_loc 获取最后一次的定位经纬度数据打印

6.定位成功之后使用 subscribe 订阅"GNSS_STATE"消息,根据获取到的值判断是否定位成功,定位成功用 exgnss.rmc 获取 rmc 数据

--[[
@module  gnss
@summary gnss应用测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
该文件演示的功能会用到exgnss.lua扩展库

--关于exgnss的三种应用场景:
exgnss.DEFAULT:
--- exgnss应用模式1.
-- 打开gnss后,gnss定位成功时,如果有回调函数,会调用回调函数
-- 使用此应用模式调用exgnss.open打开的“gnss应用”,必须主动调用exgnss.close
-- 或者exgnss.close_all才能关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是一直打开,除非自己手动关闭掉

exgnss.TIMERORSUC:
--- exgnss应用模式2.
-- 打开gnss后,如果在gnss开启最大时长到达时,没有定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,如果在gnss开启最大时长内,定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用exgnss.close或者
-- exgnss.close_all主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是设置规定时间打开,如果规定时间内定位成功就会自动关闭此应用,
-- 如果没有定位成功,时间到了也会自动关闭此应用

exgnss.TIMER:
--- exgnss应用模式3.
-- 打开gnss后,在gnss开启最大时长时间到达时,无论是否定位成功,如果有回调函数,
-- 会调用回调函数,然后自动关闭此“gnss应用”
-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用exgnss.close或者exgnss.close_all
-- 主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
-- 通俗点说就是设置规定时间打开,无论是否定位成功,到了时间都会自动关闭此应用,
-- 和第二种的区别在于定位成功之后不会自动关闭,到时间之后才会自动关闭

本示例主要是展示exgnss库的三种应用模式,然后关闭操作和查询应用是否有效操作,还有关闭全部应用操作,
以及定位成功之后如何使用exgnss库获取gnss的rmc数据
]]

local function mode1_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    log.info("nmea", "rmc", json.encode(exgnss.rmc(2)))
end

local function mode2_cb(tag)
    log.info("TAGmode2_cb+++++++++",tag)
    log.info("nmea", "rmc", json.encode(exgnss.rmc(2)))
end

local function mode3_cb(tag)
    log.info("TAGmode3_cb+++++++++",tag)
    log.info("nmea", "rmc", json.encode(exgnss.rmc(2)))
end

local function gnss_fnc()
    --GPIO26(备电,含RTC/星历SRAM,保持常开可支持热启动+星历保存)
    --GPIO25(主供电,交给exgnss动态开关)
    --所以此处先手动把GPIO26常置为高,只把GPIO25交给exgnss的gnss_volgpio参数动态控制
    gpio.setup(26, 1)
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        debug=true,    --是否输出调试信息
        gnss_volgpio=25,    --8201H使用GPIO25控制GNSS主供电,GPIO26(备电)已在上方常置为高
        -- uart=2,    --使用的串口,8201H默认串口2
        -- uartbaud=115200,    --串口波特率,8201H默认115200
        -- bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
         ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false,
        -- 定位频率,指gnss每秒输出多少次的定位数据,1hz=1次/秒,默认1hz,可选值:1/2/4/5
        -- hz=1,
        -- 静态抑制,指在gnss定位成功后,是否需要开启静态抑制,开启后,gnss会根据设置的static值,去进行速度值判断,
        -- 如果速度值小于static值,gnss会持续上报最后一条定位数据,直到速度值大于static值,才上报新的定位数据,
        -- 可选值:1-100,不开启该内容则表示不开启静态抑制,不管速度值多少,都上报原本的定位数据,单位:cm/s,
        -- 也就是说如果设置最大速度值为100cm/s,100cm/s=1m/s=3.6km/h,如果速度值小于100cm/s,gnss会持续上报最后一条定位数据
        -- 直到速度值大于100cm/s,才上报新的定位数据,这样做可以避免在静止状态下,gnss会有静态飘移的情况,导致定位数据不准确
        -- static=100
    }
    --设置gnss参数
    exgnss.setup(gnssotps)
    --开启gnss应用
    exgnss.open(exgnss.TIMER,{tag="modeTimer",val=60,cb=mode1_cb})  --使用TIMER模式,开启60s后关闭
    exgnss.open(exgnss.DEFAULT,{tag="modeDefault",cb=mode2_cb}) --使用DEFAULT模式,开启后一直运行  
    exgnss.open(exgnss.TIMERORSUC,{tag="modeTimerorsuc",val=60,cb=mode3_cb})    --使用TIMERORSUC模式,开启60s,如果定位成功,则直接关闭
    sys.wait(40000)
    log.info("关闭一个gnss应用,然后查看下所有应用的状态")
    --关闭一个gnss应用
    exgnss.close(exgnss.TIMER,{tag="modeTimer"})--关闭tag为modeTimer应用
    --查询3个gnss应用状态
    log.info("gnss应用状态1",exgnss.is_active(exgnss.TIMER,{tag="modeTimer"}))
    log.info("gnss应用状态2",exgnss.is_active(exgnss.DEFAULT,{tag="modeDefault"}))
    log.info("gnss应用状态3",exgnss.is_active(exgnss.TIMERORSUC,{tag="modeTimerorsuc"}))
    sys.wait(10000)
    --关闭所有gnss应用
    exgnss.close_all()
    --查询3个gnss应用状态
    log.info("gnss应用状态1",exgnss.is_active(exgnss.TIMER,{tag="modeTimer"}))
    log.info("gnss应用状态2",exgnss.is_active(exgnss.DEFAULT,{tag="modeDefault"}))
    log.info("gnss应用状态3",exgnss.is_active(exgnss.TIMERORSUC,{tag="modeTimerorsuc"}))
    --查询最后一次定位结果
    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)
6.1.1.3 效果展示

利用纠偏网站进行纠偏:合宙所有的 GNSS 功能的坐标系均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。 国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位。

以下是纠偏网站:

https://docs.openluat.com/file/GPS-Offset.html

6.1.2 正常功耗模式下的 GNSS 应用-exgnss

main.lua

主要是支持 4 个场景的切换,每次只能打开一个用于测试,选择对应的功能进行测试

--[[
@module  main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
本demo演示的功能为:
使用Air780EGH核心板,通过exgnss扩展库,开启GNSS定位,展示模块的三种功耗模式:正常模式,低功耗模式,PSM+模式 
]]

--[[
必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "gnsstest"
VERSION = "001.000.000"

--添加硬狗防止程序卡死
if wdt then
    wdt.init(9000)--初始化watchdog设置为9s
    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
end

-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
--     errDump.config(true, 600)
-- end

-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用

-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
--     log.info("mem.lua", rtos.meminfo())
--     log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)

exgnss=require("exgnss")    

--以下功能每次只能打开一个用于测试,选择对应的功能进行测试

require"normal"  --正常模式,搭配GNSS定时开启发送经纬度数据到服务器,不进入任何低功耗模式
-- require"lowpower"    -- 低功耗模式,搭配GNSS定时开启发送经纬度数据到服务器,定位成功之后关闭GNSS进入低功耗模式
-- require"psm"     -- PSM+模式,唤醒开启GNSS定位,定位成功之后发送经纬度数据到服务器,然后关闭GNSS进入PSM+模式

-- require"vibration"  -- 振动检测模式,搭配GNSS触发震动检测之后,持续发送经纬度数据到服务器,该功能只有780EGG和780EGP支持,780EGH不支持该demo

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
6.1.2.1 代码讲解

normal.lua

normal 主要操作:

1.开启协程,运行 gnss_fnc 函数,通过 exgnss.setup(gnssotps)配置 gnss 的参数,然后 require tcp_client_main 文件,连接服务器

2.通过 exgnss.open 开启 exgnss.TIMERORSUC 应用模式,设置的 tag 为 normal,定位时间为 60s,回调函数为 normal_cb,这个模式定位成功之后会关掉 GNSS,然后触发回调函数,回调函数的操作为,通过 exgnss.rmc(0)接口获取 rmc 数据,然后把经纬度拼接成 json 字符串的形式,通过 sys.publish 函数,把数据传给 tcp_client_sender.lua 文件里面处理。

3.用 sys.timerLoopStart()起一个 60s 触发一次的打开 GNSS 定位的定时器,回调函数为 normal_open,回调函数内容和上面开启 exgnss.TIMERORSUC 应用模式一致

--[[
@module  normal
@summary gnss正常测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
使用Air8201H板子,外接GPS天线,定位然后发送经纬度数据给服务器,
起一个60s定位一次的定时器,模块120s一定位,然后定位成功获取到经纬度发送到服务器上面
如果GNSS没有定位成功则获取基站/wifi定位,用付费的基站/wifi定位服务,需要申请项目id和秘钥
不用免费的是因为当前免费的基站定位服务2小时只可以获取一次,测试情况下可以,实际项目运用大概率不符合需求
]]

tcp_client_main=require("tcp_client_main")

local airlbs=require("airlbs")

local timeout = 10 -- 扫描基站/wifi 做 基站/wifi定位 的超时时间,最小5S,最大60S

--  此服务为收费服务,需自行联系销售申请或者在 https://iot.openluat.com/finance/order 购买

--  以下为合宙LBS平台开通的项目id和秘钥
--  以下项目密钥和id请根据实际项目进行修改,https://iot.openluat.com/lbs/bs 在此网址中我的项目下
local airlbs_project_id = "此处换成自己的项目id"
local airlbs_project_key = "此处换成自己的项目key"


--多基站+多wifi定位
local function airlbs_multi_cells_wifi_task_func()
    while not socket.adapter(socket.dft()) do
        log.warn("airlbs_multi_cells_wifi_func", "wait IP_READY", socket.dft())
        -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
        -- 或者等待1秒超时退出阻塞等待状态;
        -- 注意:此处的1000毫秒超时不要修改的更长;
        -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
        -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
        -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
        sys.waitUntil("IP_READY", 1000)
    end

    -- 检测到了IP_READY消息
    log.info("airlbs_multi_cells_wifi_func", "recv IP_READY", socket.dft())

    socket.sntp() --进行NTP授时
    sys.waitUntil("NTP_UPDATE", 1000)

    -- 如需wifi定位,需要硬件以及固件支持wifi扫描功能
    local wifi_info = nil
    if wlan then
        wlan.init()--初始化wlan
        wlan.scan()--扫描wifi
        sys.waitUntil("WLAN_SCAN_DONE", timeout * 1000)--等待扫描完成
        wifi_info = wlan.scanResult()--获取扫描结果
        log.info("scan", "wifi_info", #wifi_info)--打印扫描结果
    end
    local result, data = airlbs.request({
        project_id = airlbs_project_id,-- 项目ID
        project_key = airlbs_project_key,-- 项目密钥
        wifi_info = wifi_info,-- wifi信息
        timeout = timeout * 1000,-- 实际的超时时间(单位:ms)
    })
    if result then
        local data_str = json.encode(data)
        log.info("airlbs多基站+多wifi定位返回的经纬度数据为", data_str)-- 解析经纬度
        local lat = data_str:match("\"lat\":([0-9.-]+)")-- 匹配lat
        log.info("airlbs", "lat", lat)-- 打印lat
        local lng = data_str:match("\"lng\":([0-9.-]+)")-- 匹配lng
        log.info("airlbs", "lng", lng)-- 打印lng
        local data=string.format('{"lat":%5f,"lng":%5f}', lat, lng)
        sys.publish("SEND_DATA_REQ", "gnssnormal", data) --发送数据到服务器
    else        
        log.warn("请检查project_id和project_key")-- 打印提示信息
    end
end

local function normal_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    if exgnss.is_fix() then
        local  rmc=exgnss.rmc(0)    --获取rmc数据
        log.info("nmea", "rmc", json.encode(exgnss.rmc(0)))
        local data=string.format('{"lat":%5f,"lng":%5f}', rmc.lat, rmc.lng)
        sys.publish("SEND_DATA_REQ", "gnssnormal", data) --发送数据到服务器
    else
        --GNSS定位失败,开启基站+WIFI定位,获取经纬度,需要注意的是,获取频率需要根据付费的时间来决定,否则会获取失败
        sys.taskInit(airlbs_multi_cells_wifi_task_func)
    end
end

local function normal_open()
    exgnss.open(exgnss.TIMERORSUC,{tag="normal",val=60,cb=normal_cb}) --打开一个60s的TIMERORSUC应用,该模式定位成功关闭
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    --GPIO26(备电,含RTC/星历SRAM,保持常开可支持热启动+星历保存)
    --GPIO25(主供电,交给exgnss动态开关)
    --所以此处先手动把GPIO26常置为高,只把GPIO25交给exgnss的gnss_volgpio参数动态控制
    gpio.setup(26, 1)
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        debug=true,    --是否输出调试信息
        gnss_volgpio=25,    --8201H使用GPIO25控制GNSS主供电,GPIO26(备电)已在上方常置为高
        -- uart=2,    --使用的串口,8201H默认串口2
        -- uartbaud=115200,    --串口波特率,8201H默认115200
        -- bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
        ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false,
        -- 定位频率,指gnss每秒输出多少次的定位数据,1hz=1次/秒,默认1hz,可选值:1/2/4/5
        -- hz=1,
        -- 静态抑制,指在gnss定位成功后,是否需要开启静态抑制,开启后,gnss会根据设置的static值,去进行速度值判断,
        -- 如果速度值小于static值,gnss会持续上报最后一条定位数据,直到速度值大于static值,才上报新的定位数据,
        -- 可选值:1-100,不开启该内容则表示不开启静态抑制,不管速度值多少,都上报原本的定位数据,单位:cm/s,
        -- 也就是说如果设置最大速度值为100cm/s,100cm/s=1m/s=3.6km/h,如果速度值小于100cm/s,gnss会持续上报最后一条定位数据
        -- 直到速度值大于100cm/s,才上报新的定位数据,这样做可以避免在静止状态下,gnss会有静态飘移的情况,导致定位数据不准确
        -- static=100
    }
    exgnss.setup(gnssotps)  --配置GNSS参数
    exgnss.open(exgnss.TIMER,{tag="normal",val=60,cb=normal_cb}) --打开一个60s的TIMERORSUC应用,该模式定位成功关闭
    -- sys.timerLoopStart(normal_open,120000)       --每120s开启一次GNSS

end

sys.taskInit(gnss_fnc)
6.1.2.2 效果展示

测试服务器收到消息:

利用纠偏网站进行纠偏:合宙所有的 GNSS 功能的坐标系均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。 国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位。

以下是纠偏网站:

https://docs.openluat.com/file/GPS-Offset.html

6.1.3 低功耗模式下的 GNSS 应用-exgnss

6.1.3.1 代码讲解

lowpower.lua

normal 主要操作:

1.开启协程,运行 gnss_fnc 函数,通过 exgnss.setup(gnssotps)配置 gnss 的参数,然后 require tcp_client_main 文件,连接服务器。

2.通过 exgnss.open 开启 exgnss.TIMERORSUC 应用模式,设置的 tag 为 lowpower,定位时间为 60s,回调函数为 lowpower_cb,这个模式定位成功之后会关掉 GNSS,然后触发回调函数,回调函数的操作为,通过 exgnss.rmc(0)接口获取 rmc 数据,然后把经纬度拼接成 json 字符串的形式,通过 sys.publish 函数,把数据传给 tcp_client_sender.lua 文件里面处理,然后调用 pm.power(pm.WORK_MODE, 1)--进入低功耗模式,pm.power(pm.WORK_MODE,1,1)--wifi 进入低功耗模式。这两个函数主要是为了让模块和 wifi 模块进入低功耗模式。

3.用 sys.timerLoopStart()起一个 60s 触发一次的打开 GNSS 定位的定时器,回调函数为 lower_open,回调函数内容主要是调用 pm.power(pm.WORK_MODE, 0)进入正常模式,然后和上面开启 exgnss.TIMERORSUC 应用模式一致,

然后关闭 USB,节省功耗

--[[
@module  lowpower
@summary gnss低功耗测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
使用Air8201H板子,外接GPS天线,起一个60s定位一次的定时器,唤醒模块120s一定位,
然后定位成功获取到经纬度发送到服务器上面,然后进入休眠

如果GNSS没有定位成功则获取基站/wifi定位,用付费的基站/wifi定位服务,需要申请项目id和秘钥
不用免费的是因为当前免费的基站定位服务2小时只可以获取一次,测试情况下可以,实际项目运用大概
率不符合需求
]]

tcp_client_main=require("tcp_client_main")

local airlbs=require("airlbs")

local timeout = 10 -- 扫描基站/wifi 做 基站/wifi定位 的超时时间,最小5S,最大60S

--  此服务为收费服务,需自行联系销售申请或者在 https://iot.openluat.com/finance/order 购买

--  以下为合宙LBS平台开通的项目id和秘钥
--  以下项目密钥和id请根据实际项目进行修改,https://iot.openluat.com/lbs/bs 在此网址中我的项目下
local airlbs_project_id = "此处换成自己的项目id"
local airlbs_project_key = "此处换成自己的项目key"

--多基站+多wifi定位
local function airlbs_multi_cells_wifi_task_func()
    while not socket.adapter(socket.dft()) do
        log.warn("airlbs_multi_cells_wifi_func", "wait IP_READY", socket.dft())
        -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
        -- 或者等待1秒超时退出阻塞等待状态;
        -- 注意:此处的1000毫秒超时不要修改的更长;
        -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
        -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
        -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
        sys.waitUntil("IP_READY", 1000)
    end

    -- 检测到了IP_READY消息
    log.info("airlbs_multi_cells_wifi_func", "recv IP_READY", socket.dft())

    socket.sntp() --进行NTP授时
    sys.waitUntil("NTP_UPDATE", 1000)

    -- 如需wifi定位,需要硬件以及固件支持wifi扫描功能
    local wifi_info = nil
    if wlan then
        wlan.init()--初始化wlan
        wlan.scan()--扫描wifi
        sys.waitUntil("WLAN_SCAN_DONE", timeout * 1000)--等待扫描完成
        wifi_info = wlan.scanResult()--获取扫描结果
        log.info("scan", "wifi_info", #wifi_info)--打印扫描结果
    end
    local result, data = airlbs.request({
        project_id = airlbs_project_id,-- 项目ID
        project_key = airlbs_project_key,-- 项目密钥
        wifi_info = wifi_info,-- wifi信息
        timeout = timeout * 1000,-- 实际的超时时间(单位:ms)
    })
    if result then
        local data_str = json.encode(data)
        log.info("airlbs多基站+多wifi定位返回的经纬度数据为", data_str)-- 解析经纬度
        local lat = data_str:match("\"lat\":([0-9.-]+)")-- 匹配lat
        log.info("airlbs", "lat", lat)-- 打印lat
        local lng = data_str:match("\"lng\":([0-9.-]+)")-- 匹配lng
        log.info("airlbs", "lng", lng)-- 打印lng
        local data=string.format('{"lat":%5f,"lng":%5f}', lat, lng)
        sys.publish("SEND_DATA_REQ", "gnssnormal", data) --发送数据到服务器
    else        
        log.warn("请检查project_id和project_key")-- 打印提示信息
    end
end

pm.power(pm.WORK_MODE, 1)--进入低功耗模式

local function lowpower_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    if exgnss.is_fix() then
        local  rmc=exgnss.rmc(0)    --获取rmc数据
        log.info("nmea", "rmc", json.encode(exgnss.rmc(0)))
        local data=string.format('{"lat":%5f,"lng":%5f}', rmc.lat, rmc.lng)
        sys.publish("SEND_DATA_REQ", "gnssnormal", data) --发送数据到服务器
    else
        --GNSS定位失败,开启基站+WIFI定位,获取经纬度,需要注意的是,获取频率需要根据付费的时间来决定,否则会获取失败
        sys.taskInit(airlbs_multi_cells_wifi_task_func)
    end

end

local function lower_open()
    exgnss.open(exgnss.TIMERORSUC,{tag="lowpower",val=60,cb=lowpower_cb})
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    --GPIO26(备电,含RTC/星历SRAM,保持常开可支持热启动+星历保存)
    --GPIO25(主供电,交给exgnss动态开关)
    --所以此处先手动把GPIO26常置为高,只把GPIO25交给exgnss的gnss_volgpio参数动态控制
    gpio.setup(26, 1)
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        gnss_volgpio=25,    --8201H使用GPIO25控制GNSS主供电,GPIO26(备电)已在上方常置为高
        -- debug=true,    --是否输出调试信息
        -- uart=2,    --使用的串口,8201H默认串口2
        -- uartbaud=115200,    --串口波特率,8201H默认115200
        -- bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
        ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false,
        -- 定位频率,指gnss每秒输出多少次的定位数据,1hz=1次/秒,默认1hz,可选值:1/2/4/5
        -- hz=1,
        -- 静态抑制,指在gnss定位成功后,是否需要开启静态抑制,开启后,gnss会根据设置的static值,去进行速度值判断,
        -- 如果速度值小于static值,gnss会持续上报最后一条定位数据,直到速度值大于static值,才上报新的定位数据,
        -- 可选值:1-100,不开启该内容则表示不开启静态抑制,不管速度值多少,都上报原本的定位数据,单位:cm/s,
        -- 也就是说如果设置最大速度值为100cm/s,100cm/s=1m/s=3.6km/h,如果速度值小于100cm/s,gnss会持续上报最后一条定位数据
        -- 直到速度值大于100cm/s,才上报新的定位数据,这样做可以避免在静止状态下,gnss会有静态飘移的情况,导致定位数据不准确
        -- static=100
    }
    exgnss.setup(gnssotps)  --配置GNSS参数
    exgnss.open(exgnss.TIMERORSUC,{tag="lowpower",val=60,cb=lowpower_cb}) --打开一个60s的TIMERORSUC应用,该模式定位成功关闭
    sys.timerLoopStart(lower_open,120000)       --每120s开启一次GNSS
    -- gpio.close(23)--此脚为gnss备电脚,功能是热启动和保存星历文件,关掉会没有热启动,常开功耗会增高0.5-1MA左右
    -- --关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
    pm.power(pm.USB, false)
end

sys.taskInit(gnss_fnc)
6.1.3.2 效果展示

利用纠偏网站进行纠偏:合宙所有的 GNSS 功能的坐标系均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。 国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位。

以下是纠偏网站:

https://docs.openluat.com/file/GPS-Offset.html

6.1.4 PSM+ 模式下的 GNSS 应用-exgnss

6.1.4.1 代码讲解

psm.lua

psm 主要操作:

1.开启协程,运行 gnss_fnc 函数,通过 exgnss.setup(gnssotps)配置 gnss 的参数。

2.通过 exgnss.open 开启 exgnss.TIMERORSUC 应用模式,设置的 tag 为 psm,定位时间为 60s,回调函数为 psm_cb,这个模式定位成功之后会关掉 GNSS,然后触发回调函数,回调函数的操作为,通过 exgnss.rmc(0)接口获取 rmc 数据,获取经纬度并赋值变量,然后使用 sysplus.taskInitEx 开启 TCP 短连接协程,连接服务器发送经纬度消息和唤醒消息内容。

3.发送完毕之后设置定时器,然后进入飞行模式,进入 psm+ 模式

--[[
@module  psm
@summary gnss使用psm测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
使用Air8201H模块,外接GPS天线,开启定位,获取到定位发送到服务器上面,然后启动一个60s的定时器唤醒PSM+模式
模块开启定位,然后定位成功获取到经纬度发送到服务器上面,然后进入PSM+模式,等待唤醒

如果GNSS没有定位成功则获取基站/wifi定位,用付费的基站/wifi定位服务,需要申请项目id和秘钥
不用免费的是因为当前免费的基站定位服务2小时只可以获取一次,测试情况下可以,实际项目运用大概
率不符合需求
]]
pm.power(pm.WORK_MODE, 0) 

local lat,lng

-- 电脑访问:https://netlab.luatos.com/
-- 点击 打开TCP 按钮,会创建一个TCP server
-- 将server的地址和端口赋值给下面这两个变量
local server_ip = "112.125.89.8" 
local server_port = 34825 -- 换成自己的

local period = 3 * 60 * 60 * 1000 -- 定时器唤醒时间,3小时唤醒一次

local reason, slp_state = pm.lastReson() -- 获取唤醒原因
log.info("wakeup state", pm.lastReson())
local libnet = require "libnet"

local d1Name = "D1_TASK"
local function netCB(msg)
    log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
end

local airlbs=require("airlbs")

local timeout = 10 -- 扫描基站/wifi 做 基站/wifi定位 的超时时间,最小5S,最大60S

--  此服务为收费服务,需自行联系销售申请或者在 https://iot.openluat.com/finance/order 购买

--  以下为合宙LBS平台开通的项目id和秘钥
--  以下项目密钥和id请根据实际项目进行修改,https://iot.openluat.com/lbs/bs 在此网址中我的项目下
local airlbs_project_id = "此处换成自己的项目id"
local airlbs_project_key = "此处换成自己的项目key"

--多基站+多wifi定位
local function airlbs_multi_cells_wifi_task_func()
    while not socket.adapter(socket.dft()) do
        log.warn("airlbs_multi_cells_wifi_func", "wait IP_READY", socket.dft())
        -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
        -- 或者等待1秒超时退出阻塞等待状态;
        -- 注意:此处的1000毫秒超时不要修改的更长;
        -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
        -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
        -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
        sys.waitUntil("IP_READY", 1000)
    end

    -- 检测到了IP_READY消息
    log.info("airlbs_multi_cells_wifi_func", "recv IP_READY", socket.dft())

    socket.sntp() --进行NTP授时
    sys.waitUntil("NTP_UPDATE", 1000)

    -- 如需wifi定位,需要硬件以及固件支持wifi扫描功能
    local wifi_info = nil
    if wlan then
        wlan.init()--初始化wlan
        wlan.scan()--扫描wifi
        sys.waitUntil("WLAN_SCAN_DONE", timeout * 1000)--等待扫描完成
        wifi_info = wlan.scanResult()--获取扫描结果
        log.info("scan", "wifi_info", #wifi_info)--打印扫描结果
    end
    local result, data = airlbs.request({
        project_id = airlbs_project_id,-- 项目ID
        project_key = airlbs_project_key,-- 项目密钥
        wifi_info = wifi_info,-- wifi信息
        timeout = timeout * 1000,-- 实际的超时时间(单位:ms)
    })
    if result then
        local data_str = json.encode(data)
        log.info("airlbs多基站+多wifi定位返回的经纬度数据为", data_str)-- 解析经纬度
        local latdata = data_str:match("\"lat\":([0-9.-]+)")-- 匹配lat
        log.info("airlbs", "lat", latdata)-- 打印lat
        local lngdata = data_str:match("\"lng\":([0-9.-]+)")-- 匹配lng
        log.info("airlbs", "lng", lngdata)-- 打印lng
        lat,lng=latdata,lngdata
    else        
        log.warn("请检查project_id和project_key")-- 打印提示信息
    end
end

local function testTask(ip, port)
    local txData
    --拼接处理定位数据和唤醒原因
    if reason == 0 then
        txData = "normal wakeup,"..string.format('{"lat":%5f,"lng":%5f}', lat, lng)
    elseif reason == 1 then
        txData = "timer wakeup,"..string.format('{"lat":%5f,"lng":%5f}', lat, lng)
    elseif reason == 2 then
        txData = "pad wakeup,"..string.format('{"lat":%5f,"lng":%5f}', lat, lng)
    elseif reason == 3 then
        txData = "uart1 wakeup,"..string.format('{"lat":%5f,"lng":%5f}', lat, lng)
    end
    if slp_state > 0 then
        mobile.flymode(0, false) -- 退出飞行模式,进入psm+前进入飞行模式,唤醒后需要主动退出
    end

    local netc, needBreak
    local result, param, is_err
    -- 创建socket client对象
    netc = socket.create(nil, d1Name)
    --关闭debug信息
    socket.debug(netc, false)
    -- 配置socket client对象为netc
    socket.config(netc, nil, nil, nil, 300, 10, 3) 
    local retry = 0
    --这边会尝试连接服务器并且发送,最多处理3次,如果3次都不行就退出,发送成功就直接退出
    while retry < 3 do
        while not socket.adapter(socket.dft()) do
            log.warn("tcp_client_main_task_func", "wait IP_READY")
            -- 在此处阻塞等待联网成功成功的消息"IP_READY"
            -- 或者等待30秒超时退出阻塞等待状态
            sys.waitUntil("IP_READY", 30000)
        end

        log.info(rtos.meminfo("sys"))
        -- 连接server
        result = libnet.connect(d1Name, 5000, netc, ip, port)

        if result then
            log.info("服务器连上了")
            --连接成功发送数据
            result, param = libnet.tx(d1Name, 15000, netc, txData)
            --如果发送失败就直接退出
            if not result then
                log.info("服务器断开了", result, param)
                break
            else
                needBreak = true
            end
        else
            log.info("服务器连接失败")
        end
        --关闭socket
        libnet.close(d1Name, 5000, netc)
        retry = retry + 1
        --如果发送成功结束循环
        if needBreak then
            break
        end
    end
    socket.release(netc)
    uart.setup(1, 9600) -- 配置uart1,外部唤醒用
    -- 配置GPIO以达到最低功耗的目的
    -- gpio.close(23) --此脚为gnss备电脚,功能是热启动和保存星历文件,关掉会没有热启动,常开功耗会增高30ua左右

    pm.dtimerStart(3, period) -- 启动深度休眠定时器

    mobile.flymode(0, true) -- 启动飞行模式,规避可能会出现的网络问题
    pm.power(pm.WORK_MODE, 3) -- 进入极致功耗模式

    sys.wait(15000) -- demo演示唤醒时间是三十分钟,如果15s后模块重启,则说明进入极致功耗模式失败,
    log.info("进入极致功耗模式失败,尝试重启")
    rtos.reboot()
end

local function psm_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    if exgnss.is_fix() then
        local  rmc=exgnss.rmc(0)
        log.info("nmea", "rmc", json.encode(exgnss.rmc(0)))
        lat,lng=rmc.lat,rmc.lng
    else
        --GNSS定位失败,开启基站+WIFI定位,获取经纬度,需要注意的是,获取频率需要根据付费的时间来决定,否则会获取失败
        sys.taskInit(airlbs_multi_cells_wifi_task_func)
    end
    sys.taskInitEx(testTask, d1Name, netCB, server_ip, server_port)
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    --GPIO26(备电,含RTC/星历SRAM,保持常开可支持热启动+星历保存)
    --GPIO25(主供电,交给exgnss动态开关)
    --所以此处先手动把GPIO26常置为高,只把GPIO25交给exgnss的gnss_volgpio参数动态控制
    gpio.setup(26, 1)
    local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        gnss_volgpio=25,    --8201H使用GPIO25控制GNSS主供电,GPIO26(备电)已在上方常置为高
        -- debug=true,    --是否输出调试信息
        -- uart=2,    --使用的串口,8201H默认串口2
        -- uartbaud=115200,    --串口波特率,8201H默认115200
        -- bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
         ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把agps_autoopen设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        auto_open=false,
        -- 定位频率,指gnss每秒输出多少次的定位数据,1hz=1次/秒,默认1hz,可选值:1/2/4/5
        -- hz=1,
        -- 静态抑制,指在gnss定位成功后,是否需要开启静态抑制,开启后,gnss会根据设置的static值,去进行速度值判断,
        -- 如果速度值小于static值,gnss会持续上报最后一条定位数据,直到速度值大于static值,才上报新的定位数据,
        -- 可选值:1-100,不开启该内容则表示不开启静态抑制,不管速度值多少,都上报原本的定位数据,单位:cm/s,
        -- 也就是说如果设置最大速度值为100cm/s,100cm/s=1m/s=3.6km/h,如果速度值小于100cm/s,gnss会持续上报最后一条定位数据
        -- 直到速度值大于100cm/s,才上报新的定位数据,这样做可以避免在静止状态下,gnss会有静态飘移的情况,导致定位数据不准确
        -- static=100
    }
    exgnss.setup(gnssotps)
    exgnss.open(exgnss.TIMERORSUC,{tag="psm",val=60,cb=psm_cb})
end

sys.taskInit(gnss_fnc)
6.1.4.2 效果展示

利用纠偏网站进行纠偏:合宙所有的 GNSS 功能的坐标系均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。 国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位。

以下是纠偏网站:

https://docs.openluat.com/file/GPS-Offset.html

6.1.5 TCP 部分代码-exgnss

TCP 部分代码为通用格式内容,里面有对注释的描述,在 GNSS 篇章就不过多对 TCP 的代码做讲解了,如果有需要可以看 TCP 章节

tcp_client_main.lua

--[[
@module  tcp_client_main
@summary tcp client socket主应用功能模块 
@version 1.0
@date    2025.07.01
@author  朱天华
@usage
本文件为tcp client socket主应用功能模块,核心业务逻辑为:
1、创建一个tcp client socket,连接server
2、处理连接异常,出现异常后执行重连动作;
3、调用tcp_client_receiver和tcp_client_sender中的外部接口,进行数据收发处理;

本文件没有对外接口,直接在main.lua中require "tcp_client_main"就可以加载运行;
]]

local libnet = require "libnet"

-- 加载tcp client socket数据接收功能模块
local tcp_client_receiver = require "tcp_client_receiver"
-- 加载tcp client socket数据发送功能模块
local tcp_client_sender = require "tcp_client_sender"

-- 电脑访问:https://iot.luatos.com/#/page6/netlab
-- 详细使用说明参考:[合宙 TCP/UDP web 测试工具使用说明](https://docs.openluat.com/common/TCPUDP_Test/) 。
-- 点击 打开TCP 按钮,会创建一个TCP server
-- 将server的地址和端口赋值给下面这两个变量
local SERVER_ADDR = "112.125.89.8"
local SERVER_PORT = 44333

-- tcp_client_main的任务名
local TASK_NAME = tcp_client_sender.TASK_NAME

-- 处理未识别的消息
local function tcp_client_main_cbfunc(msg)
    log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
end

-- tcp client socket的任务处理函数
local function tcp_client_main_task_func() 

    local socket_client
    local result, para1, para2

    while true do
        -- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
        while not socket.adapter(socket.dft()) do
            log.warn("mqtt_client_main_task_func", "wait IP_READY", socket.dft())
            -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
            -- 或者等待1秒超时退出阻塞等待状态;
            -- 注意:此处的1000毫秒超时不要修改的更长;
            -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
            -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
            -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
            sys.waitUntil("IP_READY", 1000)
        end

        -- 检测到了IP_READY消息
        log.info("tcp_client_main_task_func", "recv IP_READY")

        -- 创建socket client对象
        socket_client = socket.create(nil, TASK_NAME)
        -- 如果创建socket client对象失败
        if not socket_client then
            log.error("tcp_client_main_task_func", "socket.create error")
            goto EXCEPTION_PROC
        end

        -- 配置socket client对象为tcp client
        result = socket.config(socket_client)
        -- 如果配置失败
        if not result then
            log.error("tcp_client_main_task_func", "socket.config error")
            goto EXCEPTION_PROC
        end

        -- 连接server
        result = libnet.connect(TASK_NAME, 15000, socket_client, SERVER_ADDR, SERVER_PORT)
        -- 如果连接server失败
        if not result then
            log.error("tcp_client_main_task_func", "libnet.connect error")
            goto EXCEPTION_PROC
        end

        log.info("tcp_client_main_task_func", "libnet.connect success")

        -- 数据收发以及网络连接异常事件总处理逻辑
        while true do
            -- 数据接收处理(接收处理必须写在libnet.wait之前,因为老版本的内核固件要求必须这样,新版本的内核固件没这个要求,为了不出问题,写在libnet.wait之前就行了)
            -- 如果处理失败,则退出循环
            if not tcp_client_receiver.proc(socket_client) then
                log.error("tcp_client_main_task_func", "tcp_client_receiver.proc error")
                break
            end

            -- 数据发送处理
            -- 如果处理失败,则退出循环
            if not tcp_client_sender.proc(TASK_NAME, socket_client) then
                log.error("tcp_client_main_task_func", "tcp_client_sender.proc error")
                break
            end

            -- 阻塞等待socket.EVENT事件或者15秒钟超时
            -- 以下三种业务逻辑会发布事件:
            -- 1、socket client和server之间的连接出现异常(例如server主动断开,网络环境出现异常等),此时在内核固件中会发布事件socket.EVENT
            -- 2、socket client接收到server发送过来的数据,此时在内核固件中会发布事件socket.EVENT
            -- 3、socket client需要发送数据到server, 在tcp_client_sender.lua中会发布事件socket.EVENT
            result, para1, para2 = libnet.wait(TASK_NAME, 15000, socket_client)
            log.info("tcp_client_main_task_func", "libnet.wait", result, para1, para2)

            -- 如果连接异常,则退出循环
            if not result then
                log.warn("tcp_client_main_task_func", "connection exception")
                break
            end
        end

        -- 出现异常    
        ::EXCEPTION_PROC::

        -- 数据发送应用模块对来不及发送的数据做清空和通知失败处理
        tcp_client_sender.exception_proc()

        -- 如果存在socket client对象
        if socket_client then
            -- 关闭socket client连接
            libnet.close(TASK_NAME, 5000, socket_client)

            -- 释放socket client对象
            socket.release(socket_client)
            socket_client = nil
        end

        -- 5秒后跳转到循环体开始位置,自动发起重连
        sys.wait(5000)
    end
end

--创建并且启动一个task
--运行这个task的主函数tcp_client_main_task_func
sysplus.taskInitEx(tcp_client_main_task_func, TASK_NAME, tcp_client_main_cbfunc)

tcp_client_reveiver.lua

--[[
@module  tcp_client_receiver
@summary tcp client socket数据接收应用功能模块 
@version 1.0
@date    2025.07.01
@author  朱天华
@usage
本文件为tcp client socket数据接收应用功能模块,核心业务逻辑为:
从内核读取接收到的数据,然后将数据发送给其他应用功能模块做进一步处理;

本文件的对外接口有2个:
1tcp_client_receiver.proc(socket_client):数据接收应用逻辑处理入口,在tcp_client_main.lua中调用
2sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
   将接收到的数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
   需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息
]]

local tcp_client_receiver = {}

-- socket数据接收缓冲区
local recv_buff = nil

-- 数据接收应用入口函数
function tcp_client_receiver.proc(socket_client)
    -- 如果socket数据接收缓冲区还没有申请过空间,则先申请内存空间
    if recv_buff==nil then
        recv_buff = zbuff.create(1024)
        -- 当recv_buff不再使用时,不需要主动调用recv_buff:free()去释放
        -- 因为Lua的垃圾处理器会自动释放recv_buff所申请的内存空间
        -- 如果等不及垃圾处理器自动处理,在确定以后不会再使用recv_buff时,则可以主动调用recv_buff:free()释放内存空间
    end

    -- 循环从内核的缓冲区读取接收到的数据
    -- 如果读取失败,返回false,退出
    -- 如果读取成功,处理数据,并且继续循环读取
    -- 如果读取成功,并且读出来的数据为空,表示已经没有数据可读,返回true,退出
    while true do
        -- 从内核的缓冲区中读取数据到recv_buff中
        -- 如果recv_buff的存储空间不足,会自动扩容
        local result = socket.rx(socket_client, recv_buff)
        -- 读取数据失败
        -- 有两种情况:
        -- 1、recv_buff扩容失败
        -- 2、socket client和server之间的连接断开
        if not result then
            log.error("tcp_client_receiver.proc", "socket.rx error")
            return false
        end

        -- 如果读取到了数据, used()就必然大于0, 进行处理
        if recv_buff:used() > 0 then
            log.info("tcp_client_receiver.proc", "recv data len", recv_buff:used())
            -- 读取socket数据接收缓冲区中的数据,赋值给data
            local data = recv_buff:query()
            -- 将数据data通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理
            sys.publish("RECV_DATA_FROM_SERVER", "recv from tcp server: ", data)
            table.insert(tab,data)
            -- 清空socket数据接收缓冲区中的数据
            recv_buff:del()
            -- 读取成功,但是读出来的数据为空,表示已经没有数据可读,可以退出循环了
        else
            break
        end
    end

    return true
end

return tcp_client_receiver

tcp_client_sender.lua

--[[
@module  tcp_client_sender
@summary tcp client socket数据发送应用功能模块 
@version 1.0
@date    2025.07.01
@author  朱天华
@usage
本文件为tcp client socket数据发送应用功能模块,核心业务逻辑为:
1sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)订阅"SEND_DATA_REQ"消息,将其他应用模块需要发送的数据存储到队列send_queue中
2tcp_client_main主任务调用tcp_client_sender.proc接口,遍历队列send_queue,逐条发送数据到server
3tcp client socket和server之间的连接如果出现异常tcp_client_main主任务调用tcp_client_sender.exception_proc接口,丢弃掉队列send_queue中未发送的数据
4、任何一条数据无论发送成功还是失败,只要这条数据有回调函数,都会通过回调函数通知数据发送方;

本文件的对外接口有3个:
1sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func):订阅"SEND_DATA_REQ"消息;
   其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去
   demo项目中uart_app.lua和timer_app.lua中publish了这个消息
2tcp_client_sender.proc:数据发送应用逻辑处理入口,在tcp_client_main.lua中调用
3tcp_client_sender.exception_proc:数据发送应用逻辑异常处理入口,在tcp_client_main.lua中调用
]]

local tcp_client_sender = {}

local libnet = require "libnet"

--[[
数据发送队列,数据结构为:
{
    [1] = {data="data1", cb={func=callback_function1, para=callback_para1}},
    [2] = {data="data2", cb={func=callback_function2, para=callback_para2}},
}
data的内容为真正要发送的数据,必须存在;
func的内容为数据发送结果的用户回调函数,可以不存在
para的内容为数据发送结果的用户回调函数的回调参数,可以不存在;
]]
local send_queue = {}

-- tcp_client_main的任务名
tcp_client_sender.TASK_NAME = "tcp_client_main"

-- "SEND_DATA_REQ"消息的处理函数
local function send_data_req_proc_func(tag, data, cb)
    log.info("DATA",tag,data)
    -- 将原始数据增加前缀,然后插入到发送队列send_queue中
    table.insert(send_queue, {data=data, cb=cb})
    -- 通知tcp_client_main主任务有数据需要发送
    -- tcp_client_main主任务如果处在libnet.wait调用的阻塞等待状态,就会退出阻塞状态
    sysplus.sendMsg(tcp_client_sender.TASK_NAME, socket.EVENT, 0)
end

-- 数据发送应用逻辑处理入口
function tcp_client_sender.proc(task_name, socket_client)
    local send_item
    local result, buff_full

    -- 遍历数据发送队列send_queue
    while #send_queue>0 do
        -- 取出来第一条数据赋值给send_item
        -- 同时从队列send_queue中删除这一条数据
        send_item = table.remove(send_queue,1)

        -- 发送这条数据,超时时间15秒钟
        result, buff_full = libnet.tx(task_name, 15000, socket_client, send_item.data)

        -- 发送失败
        if not result then
            log.error("tcp_client_sender.proc", "libnet.tx error")

            -- 如果当前发送的数据有用户回调函数,则执行用户回调函数
            if send_item.cb and send_item.cb.func then
                send_item.cb.func(false, send_item.cb.para)
            end

            return false
        end

        -- 如果内核固件中缓冲区满了,则将send_item再次插入到send_queue的队首位置,等待下次尝试发送
        if buff_full then
            log.error("tcp_client_sender.proc", "buffer is full, wait for the next time")
            table.insert(send_queue, 1, send_item)
            return true
        end

        log.info("tcp_client_sender.proc", "send success")
        -- 发送成功,如果当前发送的数据有用户回调函数,则执行用户回调函数
        if send_item.cb and send_item.cb.func then
            send_item.cb.func(true, send_item.cb.para)
        end
    end

    return true
end

-- 数据发送应用逻辑异常处理入口
function tcp_client_sender.exception_proc()
    -- 遍历数据发送队列send_queue
    while #send_queue>0 do
        local send_item = table.remove(send_queue,1)
        -- 发送失败,如果当前发送的数据有用户回调函数,则执行用户回调函数
        if send_item.cb and send_item.cb.func then
            send_item.cb.func(false, send_item.cb.para)
        end
    end
end

-- 订阅"SEND_DATA_REQ"消息;
-- 其他应用模块如果需要发送数据,直接sys.publish这个消息即可,将需要发送的数据以及回调函数和回调参数一起publish出去;
-- 本demo项目中uart_app.lua和timer_app.lua中publish了这个消息;
sys.subscribe("SEND_DATA_REQ", send_data_req_proc_func)

return tcp_client_sender

将定位成功后的经纬度复制到此页面。可以查看位置信息和坐标系转换后的经纬度。

6.2 libgnss 的示例

6.2.1 代码讲解

main.lua

主要是加载 gnss.lua 模块

--[[
@module  main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
本demo演示的功能为:
使用Air8201H,通过libgnss核心库,实现GNSS定位功能
]]

--[[
必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为999
        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "gnsstest"
VERSION = "001.999.000"



-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
--     errDump.config(true, 600)
-- end


-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用


-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
--     log.info("mem.lua", rtos.meminfo())
--     log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)

require"gnss"


-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

gnss.lua

主要功能是加载 lbsLoc2 基站定位,设置串口号为串口 2,设置 gnss 的模式为 1 全卫星开启,然后开启函数为 gnss_open 的 task 任务,清空 gnss 的数据,设置串口号 2 波特率 115200,然后打开 GNSS 的电源,绑定为串口 2,转发到虚拟 uart 口,打开 NEMA 调试日志输出到 luatools 日志里面,然后开启 GLL,VTG,ZDA 等信息,然后开启 agps 辅助定位,agps 辅助定位主要是进行是否定位成功的判断,定位成功结束函数,定位还没成功往下进行网络判断,然后获取 ntp 时间,然后进行时间对比,进行星历下载,下载完成之后,进行基站定位获取一个大概的位置,然后把获取的时间和位置还有星历传给 GNSS 芯片进行辅助定位。

定位成功获取 rmc 值进行打印

--[[
@module  gnss
@summary gnss应用测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
主要功能:
1、开启gnss定位,使用agps辅助定位
2、定位成功获取经纬度

]]
local lbsLoc2 = require("lbsLoc2")
--设置串口2
local gps_uart_id = 2

--8201H使用GPIO26和GPIO25控制GNSS供电,两路电源同时供电GNSS才能启动:
--GPIO26(备电,含RTC/星历SRAM,保持常开可支持热启动+星历保存)
--GPIO25(主供电,工作时拉高,睡眠时可关掉省电)
local gnss_volgpio1 = 26
local gnss_volgpio2 = 25

--设置gnss的模式为1,1为全卫星开启,2为单北斗开启
local gnssmode=1


local function agps()
    if libgnss.isFix() then return end
    local lat, lng

    -- 判断星历时间和下载星历   
    while not socket.adapter(socket.dft()) do
        log.warn("airlbs_multi_cells_wifi_func", "wait IP_READY", socket.dft())
        -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
        -- 或者等待1秒超时退出阻塞等待状态;
        -- 注意:此处的1000毫秒超时不要修改的更长;
        -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
        -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
        -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
        local result=sys.waitUntil("IP_READY", 30000)
        if result == false then
            log.warn("gnss_agps", "wait IP_READY timeout")
            return
        end
    end
    socket.sntp()
    sys.waitUntil("NTP_UPDATE", 5000)
    local now = os.time()
    local agps_time = tonumber(io.readFile("/hxxt_tm") or "0") or 0
    log.info("os.time",now)
    log.info("agps_time",agps_time)
    if now - agps_time > 3600 or io.fileSize("/hxxt.dat") < 1024 then
        local url 

        if gnssmode and 2 == gnssmode then
            -- 单北斗
            url = "http://download.openluat.com/9501-xingli/HXXT_BDS_AGNSS_DATA.dat"
        else
            url = "http://download.openluat.com/9501-xingli/HXXT_GPS_BDS_AGNSS_DATA.dat"
        end
        local code = http.request("GET", url, nil, nil, {dst="/hxxt.dat"}).wait()
        if code and code == 200 then
            log.info("exgnss.opts", "下载星历成功", url)
            io.writeFile("/hxxt_tm", tostring(now))
        else
            log.info("exgnss.opts", "下载星历失败", code)
        end
    else
        log.info("exgnss.opts", "星历不需要更新", now - agps_time)
    end
    --进行基站定位,给到gnss芯片一个大概的位置

    lat, lng = lbsLoc2.request(5000)
    -- local lat, lng, t = lbsLoc2.request(5000, "bs.openluat.com")
    -- log.info("lbsLoc2", lat, lng)
    if lat and lng then
        lat = tonumber(lat)
        lng = tonumber(lng)
        log.info("lbsLoc2", lat, lng)
        -- 转换单位
        local lat_dd,lat_mm = math.modf(lat)
        local lng_dd,lng_mm = math.modf(lng)
        lat = lat_dd * 100 + lat_mm * 60
        lng = lng_dd * 100 + lng_mm * 60
    end

    --获取基站定位失败则使用本地之前保存的位置
    if not lat then
        -- 获取最后的本地位置
        local locStr = io.readFile("/hxxtloc")
        if locStr then
            local jdata = json.decode(locStr)
            if jdata and jdata.lat then
                lat = jdata.lat
                lng = jdata.lng
            end
        end
    end

    -- 写入星历
    local agps_data = io.readFile("/hxxt.dat")
    if agps_data and #agps_data > 1024 then
        log.info("exgnss.opts", "写入星历数据", "长度", #agps_data)
        for offset=1,#agps_data,512 do
            log.info("exgnss", "AGNSS", "write >>>", #agps_data:sub(offset, offset + 511))
            uart.write(gps_uart_id, agps_data:sub(offset, offset + 511))
            sys.wait(100) -- 等100ms反而更成功
        end
        -- uart.write(gps_uart_id, agps_data)
    else
        log.info("exgnss.opts", "没有星历数据")
        return
    end
    -- "lat":23.4068813,"min":27,"valid":true,"day":27,"lng":113.2317505
    --如果没有经纬度的话,定位时间会变长,大概10-20s左右
    if not lat or not lng then
        -- lat, lng = 23.4068813, 113.2317505
        log.info("exgnss.opts", "没有GPS坐标", lat, lng)
        return --暂时不写入参考位置
    else
        log.info("exgnss.opts", "写入GPS坐标", lat, lng)
    end
    --写入时间
    local date = os.date("!*t")
    if date.year > 2023 then
        local str = string.format("$AIDTIME,%d,%d,%d,%d,%d,%d,000", date["year"], date["month"], date["day"],
            date["hour"], date["min"], date["sec"])
        log.info("exgnss.opts", "参考时间", str)
        uart.write(gps_uart_id, str .. "\r\n")
        sys.wait(20)
    end
    -- 写入参考位置
    local str = string.format("$AIDPOS,%.7f,%s,%.7f,%s,1.0\r\n",
    lat > 0 and lat or (0 - lat), lat > 0 and 'N' or 'S',
    lng > 0 and lng or (0 - lng), lng > 0 and 'E' or 'W')
    log.info("exgnss.opts", "写入AGPS参考位置", str)
    uart.write(gps_uart_id, str)

end


local function gnss_open()
    log.info("GPS", "start")
    libgnss.clear() -- 清空数据,兼初始化
    --设置串口波特率
    uart.setup(gps_uart_id, 115200)
    -- 打开GPS供电,8201H使用GPIO26(备电)和GPIO25(主供电)都拉高
    gpio.setup(gnss_volgpio1, 1)
    gpio.setup(gnss_volgpio2, 1)
    -- 绑定uart,底层自动处理GNSS数据
    -- 第二个参数是转发到虚拟UART, 方便上位机分析
    libgnss.bind(gps_uart_id, uart.VUART_0)
    sys.wait(200) -- GPNSS芯片启动需要时间
    -- 调试日志,可选
    libgnss.debug(true)

    -- 增加显示的语句
    uart.write(gps_uart_id, "$CFGMSG,0,1,1\r\n") -- GLL
    sys.wait(20)
    uart.write(gps_uart_id, "$CFGMSG,0,5,1\r\n") -- VTG
    sys.wait(20)
    uart.write(gps_uart_id, "$CFGMSG,0,6,1\r\n") -- ZDA
    sys.wait(20)
    sys.taskInit(agps)

end

local function gnss_close()
    log.info("GPS", "close")
    libgnss.clear()
    uart.close(gps_uart_id)
    -- 关闭时只关GPIO25(主供电),GPIO26(备电)保持常开,让星历/RTC继续供电,下次可以走热启动秒定位
    -- 如果不需要热启动、可以接受下次冷启动(35s左右),可以把下面这行注释放开,进一步降低约30ua功耗
    -- gpio.setup(gnss_volgpio1, 0)
    gpio.setup(gnss_volgpio2, 0)
end

sys.taskInit(gnss_open)


--GNSS定位状态的消息处理函数:
local function gnss_state(event, ticks)
    -- event取值有
    -- "FIXED":string类型 定位成功
    -- "LOSE": string类型 定位丢失

    -- ticks number类型 是事件发生的时间,一般可以忽略
    log.info("exgnss", "state", event)
    if event=="FIXED" then
        --获取rmc数据
        --json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位,本示例保留5位小数
        log.info("nmea", "rmc0", json.encode(libgnss.getRmc(0),"5f"))
    end
end

sys.subscribe("GNSS_STATE",gnss_state)

6.2.2 效果展示

6.3 AGPS 辅助定位的原理

AGPS(Assisted GPS,辅助全球卫星定位系统)通过移动网络预先获取卫星星历、时间等辅助数据,可大幅缩短 GPS 首次定位时间。LuatOS 提供 AGPS 原生支持,适用于物流追踪、车载定位等场景。

6.3.1 GPS、WiFi、基站、AGPS 几种常用定位原理介绍与区别

GPS

GPS(Global Positioning System)即全球定位系统,它是由美国研究的一种定位方式,特点是:不需要 SIM 卡,不需要连接网络,只要在户外,基本上就能随时随地的准确定位。但是 GPS 启动后搜索卫星的时间比较多,一般需要 2 分钟左右(俗称冷启动) 冷启动包括: 1.GPS 初次使用 2.GPS 电池耗尽 3.关机状态下移动 1000 公里以上的距离或持续关机超过 4 小时。

原理:接收机接收 GPS 卫星广播,通过解析可见 GPS 卫星的位置、距离等信息以及相应算法得出自己的位置信息。

优势:定位精度高,只要能接收到四颗卫星的定位信号,就可以进行定位。 缺点:GPS 受天气和位置的影响较大。当遇到天气不佳的时候、或者处于高架桥/树荫的下面,或者在高楼的旁边角落、地下车库、室内或露天的下层车库(或者简单地说当见不到天空的时候),GPS 的定位就会受到相当大的影响,甚至无法进行定位服务。

注意:各国定位系统有:中国北斗、美国 GPS、俄罗斯 GLONASS、欧洲伽利略等,统称卫星定位。

合宙 gps 模组定位精度:3~5 米

LBS(基站)定位

基站包括移动、联通和电信基站。基站定位是通过移动通信的基站信号差异,通过一定的算法来计算出手机所在的位置,取决于定位地点附近所处的基站覆盖密度,如果基站多,定位则准确,如果是山区,基站少,则定位就不那么精确;LBS 定位必须联网,手机处于 SIM 卡注册状态(飞行模式下开 wifi 和拔出 SIM 卡都不行)。

原理:运营商蜂窝基站的位置信息都是固定的,通过接收一个或多个基站信号终端,再根据信号强度及基站位置进行推算自身位置

优点:方便,因为它是通过 SIM 卡接收基站信号进行定位的。理论上说,只要计算三个基站的信号差异,就可以判断出手机所在的位置。因此,只要用户手机处于移动通信网络的有效范围之内,就可以随时进行位置定位,而不受天气、高楼、位置等等的影响。

缺点:通过计算基站信号差异而得出的位置坐标值,很明显地逊于 GPS 的定位精度,受环境影响较大,在郊区和农村可以将移动台定位在 10~20 米范围内,在城区由于高大建筑物较多,电波传播环境不好,信号很难直接从基站到达移动台,一般要经过折射或反射,因此定位精度会受到影响;其次是使用范围较窄,LBS 虽然不会受到天气、高架桥或高楼的影响,但如果超出手机的服务范围,或者手机所处的基站数量不足,则无法进行 LBS 定位,从这一点上说不太适合野外使用。

定位精度:10-2000 米。

WIFI 定位

Wifi 定位,顾名思义,周围必须有 wifi 才可以!WiFi 定位的目的是解决室内精确定位,原理类似基站定位。WiFi 定位的条件是:1、必须具有 WIFISCAN 功能,可以扫描到周围 wifi 的 mac 地址。2、必须能上网,移动数据或者 wifi 联网皆可。

原理:每一个无线 AP 都有一个全球唯一的 MAC 地址, WiFi 定位靠的是侦测附近周围所有的无线网络基地台 (WiFi Access Point) 的 MAC 地址,去比对数据库中该 MAC 地址的坐标,交叉计算出所在地。

优点: 1.定位精度高,wifi 密集人流多的地方相当精确; 2.速度快; 3.周围的 wifi 即使连接不上也能定位。在不依赖外部设备的情况下,没有比 WIFI 定位更精准的室内定位方式,室内定位不精准,偏二三十米远,这个问题当前科技无解。

缺点: 1.依赖 wifiscan! 没有 wifiscan 就不能定位; 2.必须处于联网状态。 3.用户被迫共享了一点流量

精确度:10~200 米

AGPS 辅助定位

AGPS 是辅助 GPS 定位的一种方法。AGPS 和 GPS 是一样的,只是加上网络的辅助而已,定位时,必须有 GPS 模块存在,如果没有 GPS 模块,这种定位是不起什么作用的。A-GPS 定位是用来加快定位速度的,由于 GPS 冷启动时,搜星速度很慢(需要把头上二十多颗卫星挨个搜一遍),大约 2 分钟才能搜到,定位模组在搜星正常、信号正常的情况下冷启动 35s 左右就可以定上位。增加了 AGPS 定位之后可以利用基站大体定位下你所在的位置,然后通过网络将这个位置发送到服务器,服务器根据这个位置将此时经过你头顶的卫星参数(哪几颗、频率、位置、仰角等信息)反馈给你的定位设备,设备上的 GPS 就可以很有目的地去搜索卫星,此时你的搜星速度大大提高,几秒钟就可以定位。

原理:接收机接收 GPS 星历文件,通过解析可见 GPS 卫星的位置、距离等信息以及相应算法得出自己的位置信息。

和 GPS 相比比优势: 1.搜星定位快,不管是冷启动还是热启动都秒定 2.有效减少设备的电量消耗 3.缓解弱 GPS 信号情况下无法定位或精度降低的问题 4.对移动设备的计算能力要求更低。

缺点: 1.必须联网,如果你的设备不能上网,或是停机了身边又没有 wifi,是没法应用 AGPS 达到秒定效果的。 2.必须有 AGPS 位置服务器的支持 3.与 GPS 一样,仍无法完美解决室内(室内无法接收 GPS 信号)定位的问题。

精确度:3~5 米

Air780EGH 的 AGPS 实现原理为:

通过蜂窝基站或 Wi-Fi 定位获取粗略位置坐标,再结合网络下发的卫星辅助数据(星历),显著缩短卫星信号搜索时间并加速定位解算,最终实现快速精准定位。

注意:GPS 星历文件能保持 4 小时,定位成功后会自动更新星历。

由上面原理可知 Air780EGH 使用 agps 功能需要访问两个服务器,基站定位服务器和星历下载服务器。(注意:如果使用的是专网卡,需要将下面两个服务器配置在 sim 卡的白名单中,基站定位服务器:free.bs.luatos.com 和星历下载服务器:download.openluat.com

6.3.2 代码讲解

具体实现代码:

其中 agps()为 agps 辅助定位的实现代码,主要功能为,获取时间,判断时间,然后获取星历文件,下载完成之后,进行基站定位,定位成功之后写入星历到 gnss 芯片,然后把经纬度数据和时间也一起写入到 gnss 芯片,完成辅助定位的操作。

--[[
@module  gnss
@summary gnss应用测试功能模块
@version 1.0
@date    2025.07.27
@author  李源龙
@usage
主要功能:
1、开启gnss定位,使用agps辅助定位
2、定位成功获取经纬度

]]
local lbsLoc2 = require("lbsLoc2")
--设置串口2
local gps_uart_id = 2

--设置gnss的模式为1,1为全卫星开启,2为单北斗开启
local gnssmode=1

local function agps()
    if libgnss.isFix() then return end
    local lat, lng

    -- 判断星历时间和下载星历   
    while not socket.adapter(socket.dft()) do
        log.warn("airlbs_multi_cells_wifi_func", "wait IP_READY", socket.dft())
        -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
        -- 或者等待1秒超时退出阻塞等待状态;
        -- 注意:此处的1000毫秒超时不要修改的更长;
        -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
        -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
        -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
        local result=sys.waitUntil("IP_READY", 30000)
        if result == false then
            log.warn("gnss_agps", "wait IP_READY timeout")
            return
        end
    end
    socket.sntp()
    sys.waitUntil("NTP_UPDATE", 5000)
    local now = os.time()
    local agps_time = tonumber(io.readFile("/hxxt_tm") or "0") or 0
    log.info("os.time",now)
    log.info("agps_time",agps_time)
    if now - agps_time > 3600 or io.fileSize("/hxxt.dat") < 1024 then
        local url 

        if gnssmode and 2 == gnssmode then
            -- 单北斗
            url = "http://download.openluat.com/9501-xingli/HXXT_BDS_AGNSS_DATA.dat"
        else
            url = "http://download.openluat.com/9501-xingli/HXXT_GPS_BDS_AGNSS_DATA.dat"
        end
        local code = http.request("GET", url, nil, nil, {dst="/hxxt.dat"}).wait()
        if code and code == 200 then
            log.info("exgnss.opts", "下载星历成功", url)
            io.writeFile("/hxxt_tm", tostring(now))
        else
            log.info("exgnss.opts", "下载星历失败", code)
        end
    else
        log.info("exgnss.opts", "星历不需要更新", now - agps_time)
    end
    --进行基站定位,给到gnss芯片一个大概的位置

    lat, lng = lbsLoc2.request(5000)
    -- local lat, lng, t = lbsLoc2.request(5000, "bs.openluat.com")
    -- log.info("lbsLoc2", lat, lng)
    if lat and lng then
        lat = tonumber(lat)
        lng = tonumber(lng)
        log.info("lbsLoc2", lat, lng)
        -- 转换单位
        local lat_dd,lat_mm = math.modf(lat)
        local lng_dd,lng_mm = math.modf(lng)
        lat = lat_dd * 100 + lat_mm * 60
        lng = lng_dd * 100 + lng_mm * 60
    end

    --获取基站定位失败则使用本地之前保存的位置
    if not lat then
        -- 获取最后的本地位置
        local locStr = io.readFile("/hxxtloc")
        if locStr then
            local jdata = json.decode(locStr)
            if jdata and jdata.lat then
                lat = jdata.lat
                lng = jdata.lng
            end
        end
    end

    -- 写入星历
    local agps_data = io.readFile("/hxxt.dat")
    if agps_data and #agps_data > 1024 then
        log.info("exgnss.opts", "写入星历数据", "长度", #agps_data)
        for offset=1,#agps_data,512 do
            log.info("exgnss", "AGNSS", "write >>>", #agps_data:sub(offset, offset + 511))
            uart.write(gps_uart_id, agps_data:sub(offset, offset + 511))
            sys.wait(100) -- 等100ms反而更成功
        end
        -- uart.write(gps_uart_id, agps_data)
    else
        log.info("exgnss.opts", "没有星历数据")
        return
    end
    -- "lat":23.4068813,"min":27,"valid":true,"day":27,"lng":113.2317505
    --如果没有经纬度的话,定位时间会变长,大概10-20s左右
    if not lat or not lng then
        -- lat, lng = 23.4068813, 113.2317505
        log.info("exgnss.opts", "没有GPS坐标", lat, lng)
        return --暂时不写入参考位置
    else
        log.info("exgnss.opts", "写入GPS坐标", lat, lng)
    end
    --写入时间
    local date = os.date("!*t")
    if date.year > 2023 then
        local str = string.format("$AIDTIME,%d,%d,%d,%d,%d,%d,000", date["year"], date["month"], date["day"],
            date["hour"], date["min"], date["sec"])
        log.info("exgnss.opts", "参考时间", str)
        uart.write(gps_uart_id, str .. "\r\n")
        sys.wait(20)
    end
    -- 写入参考位置
    local str = string.format("$AIDPOS,%.7f,%s,%.7f,%s,1.0\r\n",
    lat > 0 and lat or (0 - lat), lat > 0 and 'N' or 'S',
    lng > 0 and lng or (0 - lng), lng > 0 and 'E' or 'W')
    log.info("exgnss.opts", "写入AGPS参考位置", str)
    uart.write(gps_uart_id, str)

end

local function gnss_open()
    log.info("GPS", "start")
    libgnss.clear() -- 清空数据,兼初始化
    --设置串口波特率
    uart.setup(gps_uart_id, 115200)
    -- 打开GPS
    pm.power(pm.GPS, true)
    -- 绑定uart,底层自动处理GNSS数据
    -- 第二个参数是转发到虚拟UART, 方便上位机分析
    libgnss.bind(gps_uart_id, uart.VUART_0)
    sys.wait(200) -- GPNSS芯片启动需要时间
    -- 调试日志,可选
    libgnss.debug(true)

    -- 增加显示的语句
    uart.write(gps_uart_id, "$CFGMSG,0,1,1\r\n") -- GLL
    sys.wait(20)
    uart.write(gps_uart_id, "$CFGMSG,0,5,1\r\n") -- VTG
    sys.wait(20)
    uart.write(gps_uart_id, "$CFGMSG,0,6,1\r\n") -- ZDA
    sys.wait(20)
    sys.taskInit(agps)

end

sys.taskInit(gnss_open)

--GNSS定位状态的消息处理函数:
local function gnss_state(event, ticks)
    -- event取值有
    -- "FIXED":string类型 定位成功
    -- "LOSE": string类型 定位丢失

    -- ticks number类型 是事件发生的时间,一般可以忽略
    log.info("exgnss", "state", event)
    if event=="FIXED" then
        --获取rmc数据
        --json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位,本示例保留5位小数
        log.info("nmea", "rmc0", json.encode(libgnss.getRmc(0),"5f"))
    end
end

sys.subscribe("GNSS_STATE",gnss_state)

6.3.3 效果展示

七、总结

本教程详细介绍了如何使用 exgnss 扩展库搭配 Air8201H 的 GNSS 功能,包括硬件连接、软件配置、代码编写等步骤。通过本教程的学习,读者应该能够掌握 exgnss 扩展库功能的基本使用方法,并能够根据实际需求进行扩展和应用。

八、注意事项与常见问题

8.1、GNSS 定位经纬度不准确

1、坐标没有纠偏,参考:https://docs.openluat.com/file/GPS-Offset.html 进行纠偏处理

2、周围有比较高的障碍物,会导致定位误差

3、在开阔地带,正常情况下定位精度只能做到 5 米

4、不能在室内测试,必须到室外测试;如果只能在室内测试,可以淘宝搜索“GNSS 信号转发器”

8.2、Air8201H 获取到的经纬度数据是基于什么坐标系

使用国际标准 WGS-84 坐标系,需要自己进行坐标系转换各 GNSS 坐标系说明以及转换方法

8.3、GPS 天线如何设计

参考:合宙 Air780Exx 系列模组管脚详细说明 GNSS 章节

8.4、可视卫星、可用卫星有什么区别

可视卫星是当前区域,接收条件良好情况下,应该可以收到卫星信号的卫星。

可用卫星是当前已经收到信号并正在使用参与定位的卫星。

8.5、 GGA 和 RMC 应该用哪个

视具体情况而定,建议用 gga,信息相对更全面。

8.6、如何解读 NMEA 报文每个字段的含义

参考:NMEA-0183 协议简介

8.7、车载使用时需要天线引出到车顶上吗

1、挡风玻璃如果没有贴膜或者贴了不含金属材料的膜,可以放在挡风玻璃下,但是 GNSS 信号会有一定衰减,在万不得已的情况下,可以放在挡风玻璃下,最好再实际测试确认一下。 2、挡风玻璃如果贴了含有金属材料的膜,则不能放在挡风玻璃下,必须将天线到车顶。

8.8、如何输出原始 NMEA 数据

可以通过 exgnss.setup()接口的 bind 参数进行绑定。

local gnssotps={
        gnssmode=1, --1为卫星全定位,2为单北斗
        agps_enable=true,    --是否使用AGPS,开启AGPS后定位速度更快,会访问服务器下载星历,星历时效性为北斗1小时,GPS4小时,默认下载星历的时间为1小时,即一小时内只会下载一次
        -- debug=true,    --是否输出调试信息
        -- uart=2,    --使用的串口,780EGH和8000默认串口2
        -- uartbaud=115200,    --串口波特率,780EGH和8000默认115200
        bind=1, --绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号
        -- rtc=false    --定位成功后自动设置RTC true开启,flase关闭
         ----因为GNSS使用辅助定位的逻辑,是模块下载星历文件,然后把数据发送给GNSS芯片,
        ----芯片解析星历文件需要10-30s,默认GNSS会开启20s,该逻辑如果不执行,会导致下一次GNSS开启定位是冷启动,
        ----定位速度慢,大概35S左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把agps_autoopen设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- agps_autoopen=false 
    }
    exgnss.setup(gnssotps)

libgnss 核心库可以用 libgnss.bind()接口去修改获取。

8.9 GNSS 常见名词解释

[1]GNSS:混合定位,不同于 GPS 定位,狭义上讲的 GPS 系统,单指美国的 24 颗 GPS 卫星以及地面上 1 个主控站、3 个数据注入站和 5 个监测站及作为用户端的 GPS 接收机组成的一整套系统。GNSS 是指通过观测 GNSS 卫星获得坐标系内绝对定位坐标的测量技术。 GNSS 是所有导航定位卫星的总称,凡是可以通过捕获跟踪其卫星信号实现定位的系统,均可纳入 GNSS 系统的范围。国内用户接触最多的应该是美国的 24 颗 GPS 卫星,以及中国的北斗卫星(截至到 2023 年 5 月 17 日 10 时 49 分,中国已有五十六颗北斗导航卫星),其余还有俄罗斯 GLONASS、欧盟 GALILEO、 日本的准天顶卫星系统、印度的 IRNSS(独立的区域导航系统,覆盖印度领土及周边 1500 km 范围内,提供定位精度优于 20 米的服务)等其余定位系统。

[2]冷启动:指在一个陌生的环境下启动 GPS,直到 GPS 芯片和可用卫星联系并且计算出坐标的过程。以下几种情况开机均属冷启动:

  1. 初次开机使用时;
  2. 电池耗尽导致 GPS 芯片内星历信息丢失时;
  3. 关机状态下将接收机移动 1000 公里以上距离。

也就是说,冷启动是通过硬件方式的强制性启动,因为物理距离较远,或者时间间隔很久,GPS 芯片已经把内部的星历信息清除掉,或者内部的星历信息完全失效。GPS 接收机失去卫星参数,或者已经存在的参数和实际接收到卫星参数相差太多,导致 GPS 芯片无法靠星历快速搜星,所以必须从新获得卫星提供的坐标数据。

这也是很多定位器(譬如车载定位器)启动后,搜星时间长、定位耗时久的原因

[3]热启动:指在上次关机的地方没有过多移动过,且距离上次定位时间小于 1 个小时。再次定位时,GPS 芯片通过软件的方式,可以继续使用之前的星历快速搜星,实现秒定位。PS:普通的 GNSS 芯片,星历最长有效期为 12 小时,故此星历过期后,GPS 芯片无法使用星历实现快速定位。

[4]温启动:指距离上次定位时间超过 1 个小时的启动,搜星定位时间介于冷启动和热启动之间的情况。

譬如某时间使用过 GPS 定位实现 3D FIX,GPS 芯片内部生成星历(或者外部灌入 AGPS 数据),那么在 1 小时内启动 GPS 芯片进行定位的行为就属于温启动。启动后,GPS 芯片首先会输出上次的位置信息。因为上次关机前的经纬度和高度已知,但由于关机时间过长,卫星状态发生了变化,之前 3D FIX 时的卫星接受不到了,所以星历中参数中的若干颗卫星已经和 GPS 接收机失去了联系,GPS 芯片需要继续搜星补充位置信息,所以搜星的时间要长于热启动,短于冷启动。

[5]星历:是用于描述太空飞行体位置和速度的表达式———两行式轨道数据系统。卫星、航天器或飞行体一旦进入太空,即被列入 NORAD 卫星星历编号目录。列入 NORAD 卫星星历编号目录的太空飞行体将被终生跟踪。卫星、火箭残骸等飞行体成为太空垃圾时,仍被列入 NORAD 卫星编号目录,直到目标消失。卫星星历以开普勒定律的 6 个轨道参数之间的数学关系确定飞行体的时间、坐标、方位、速度等各项参数,具有极高的精度。卫星星历能精确计算、预测、描绘、跟踪卫星、飞行体的时间、位置、速度等运行状态;能表达天体、卫星、航天器、导弹、太空垃圾等飞行体的精确参数;能将飞行体置于三维的空间;用时间立体描绘天体的过去、现在和将来。卫星星历的时间按世界标准时间(UTC)计算。卫星星历定时更新。

[6]AGPS:辅助全球卫星定位系统(英语:Assisted Global Positioning System,简称:AGPS)指的是一种 GPS 的运行方式。它可以利用地面基地站的资讯,配合传统 GPS 卫星,让定位的速度更快

[7]有源天线:通常对于设备或车载机而言,由于设备与 GPS 接收模块之间往往有距离,考虑到安装的便利性可能会有超过 1 米的距离,在这种情况下我们只能选择有源 GPS 天线,由于天线长度的信号衰减需要进行补偿,一般有两级低噪声放大器(LNA)进行天线前端信号放大,放大后的信号经电缆输出,电缆同步提供 LNA 所需要的直流电压

由于天线收到的信号在有源天线接受头内完成信号接受与天线放大,并且远离 GPS 设备或其他电器设备,干扰源最小,而且安装位置由于天线距离延长安装位置可以选择非常理想的环境,所以实际使用时往往感觉信号较强

[8]无源天线:使用无源 GPS 天线时,由于只有一个陶瓷片接收天空的卫星信号,直接连接到模块的 RF-IN 脚,这种联接方式结构简单,而且标准的 25_25_4 的陶瓷片成本低廉,技术成熟,占空体积小,适合于强调紧凑型空间 GPS 导航产品,蓝牙 GPS,手机 GPS 及其他小型 GPS 消费类产品。

这种天线的布局是从天线的引脚直达模块的 RF-IN 脚,这根导线需要进行 50 欧阻抗匹配,而且在天线附近不能有电磁干扰,对 PCB 的设计及整机的 EMI 设计要求较高,但如果设计得优良的无源天线 GPS 产品同样有非常好的表现效果,而且功耗比较低,无需考虑天线自身的功耗。

[9]半边天以及开拓地带:GPS 卫星运行在距地 36000KM 的轨道上,信号强度相当弱(GPS 卫星的功率有多大?)。GPS 的民用 C/A 码从卫星发出来的时候信号只有 27W 左右,达到地球的时候在-158.5dBW 以上。用对数形式表示可能不直观,换算成十进制等于将近 0.0000000000000001W,相当小。所以,只有室外开阔的、无遮挡、晴好的地方,才能搜到更多的卫星,SNR 值更高(阴天都会有影响哦),GPS 芯片才能更快、更好的实现定位。

半边天一般指楼宇内窗边,打开窗户,只能搜到一半天空的卫星。

[10]定位纠偏:OpenLuat 的所有 GNSS 模块均使用国际标准 WGS-84 坐标系,所以开发者在国内常见地图定位时,会发现与实际情况有几十米甚至上百米的误差。这并非模块问题, 而是国内地图采用了非标坐标系所致。

国内常见地图如高德地图使用 GCJ-02 坐标系, 百度地图使用 BD-09 坐标系,故此开发者需要对模块输出的经纬度进行加偏处理,才能在国内的地图上实现精确定位,坐标转换可在合宙提供的坐标转换网站上直观的展示处理

[11]重捕:是指接收终端在丢失所接收信号状态下,从重新接收到信号开始,至终端设备输出符合定位精度要求的定位结果所需的时间。失锁重捕时间反映了在接收机信号失锁,定位中断后重新恢复定位的速度。失锁重捕时间短的接收机在易中断环境中(如隧道等)的定位性能好,因此失锁重捕时间可以有效评估车载终端的性能

[12]低噪声放大器:主要用于接收信号的前端,放大天线从空中接收到的微弱信号,降低噪声干扰,以供系统解调出所需的信息数据

8.10、静态漂移是什么,如抑制静态漂移?

1、静态漂移是什么?为什么会出现静态漂移

简单来说就是GNSS在静止时的定位会东飘西飘,无法固定在一个点,无法像运动状态时可以比较准确;从地图上来看的话,会看到无数个漂移的点在实际位置飘来飘去,我们通常把这种现象叫做GNSS静态漂移;

GNSS静态漂移无法根除,原因是:

卫星信号相关因素:

GPS卫星在发射信号后传播过程中会遇到多种干扰因素,包括大气层电离层变化、云层遮挡等天气影响;

卫星信号受到干扰或遮挡会导致定位不准确,特别是在高林立的城市环境或山区、森林地带;

环境干扰因素:

周边高大建筑物的多径反射造成信号误差;

网络传输不稳定可能导致定位数据丢失,造成设备在地图上显示"飞跃"现象;

2、如何抑制静态漂移

静态漂移目前主要靠两个办法抑制:

1.GNSS速度与位移一致性检查​: 通过exgnss.vtg()获取GNSS速度值,搭配经纬度信息一起发送到服务器端,服务器端做算法优化过滤,计算当前与上次定位点之间的平面位移距离。若位移量显著大于基于速度推算的理论最大合理位移(例如:位移 > 速度 × 时间间隔 × 容错系数),则认为本次定位数据存在跳变,不可信,系统将自动丢弃当前数据,继续沿用上一次的定位结果。

2.基于Gsensor的运动状态一致性检查: 启用内置Gsensor的运动检测模式(参考Demo实现),持续检测设备动态。若在连续10秒内有效运动次数低于5次,则判定设备处于静止状态。在静止状态下,维持使用上一次的可信定位结果。

搜索