跳转至

Air8000-exgnss 扩展库的使用

一、GNSS 概述

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

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

Air8000 作为一款集成了 4G、Wi-Fi、BLE、GNSS(全球导航卫星系统)等多功能的高性能工业引擎,其内置的 GNSS 模块能够提供稳定、准确的定位服务。

二、演示功能概述

使用 Air8000 开发板,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+ 模式。

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

三、准备硬件环境

GNSS 功能,目前只有 Air8000、Air8000A、Air8000G、Air8000D 模块可以用,因为这几款是 4G+ 所以本篇教程选用 Air8000 开发板来开发调试。也可以参考:Air8000 硬件环境清单,准备好硬件环境。

3.1 Air8000 开发板

淘宝购买链接:Air8000 开发板淘宝购买链接

开发板使用教程参考:Air8000 开发板使用说明

3.2 GNSS 天线

上面购买链接可以选择套餐十七,里面包含一个 GNSS 天线。

参考:硬件环境清单,准备以及组装好硬件环境。

四、准备软件环境

1. 烧录工具 Luatools

2. 内核固件文件(底层 core 固件文件):LuatOS-SoC_V2012_Air8000;此页面有新版本固件的话选用最新版本固件,本次操作用的是 101 号固件,可以根据自己的需求选择自己需要的固件。

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

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

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8000 开发板中,将本篇文章中演示使用的项目文件烧录到 Air8000 开发板中。

五、GNSS 软硬件参考

5.1 API 接口介绍

本教程使用 api 接口为:

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

5.2 GNSS 硬件设计

GNSS 在硬件设计中天线部分是比较关键的,可以参考这篇文章:Air8000 GNSS 硬件设计指导

在开发板上,内置了 3.3V LDO, 用于有源天线供电。因此可以使用外部有源天线直接连接 GNSS 连接器。

注意:目前有源天线供电仅支持 3.3V 有源天线,请注意连接的有源天线的供电范围。

六、代码示例介绍

本文主要提供 2 个 demo 目录,single 和 combination,其中 single 只是对 exgnss 库的三种 GNSS 应用的展示,combination 主要是提供了四种应用场景

noraml:正常功耗模式定位

lowpower:低功耗模式

psm:PSM+ 模式

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

6.1 exgnss 的三种应用的用法

6.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.2 代码讲解

main.lua

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

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

--[[
必须定义PROJECT和VERSION变量Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            XYZ各表示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"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.然后使用 xgnss.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()
    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左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false 
    }
    --设置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关闭,仅配合使用gnss.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.3 效果展示

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

以下是纠偏网站:

http://old.openluat.com/GPS-Offset.html

6.2 正常功耗模式下的 GNSS 应用

main.lua

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

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

--[[
必须定义PROJECT和VERSION变量Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            XYZ各表示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触发震动检测之后,持续发送经纬度数据到服务器

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

6.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
使用Air8000开发板,外接GPS天线,定位然后发送经纬度数据给服务器,
起一个60s定位一次的定时器,模块60s一定位,然后定位成功获取到经纬度发送到服务器上面
]]

tcp_client_main=require("tcp_client_main")

local function normal_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    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) --发送数据到服务器
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")
    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左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false 
    }
    exgnss.setup(gnssotps)  --配置GNSS参数
    exgnss.open(exgnss.TIMERORSUC,{tag="normal",val=60,cb=normal_cb}) --打开一个60s的TIMERORSUC应用,该模式定位成功关闭
    sys.timerLoopStart(normal_open,60000)       --每60s开启一次GNSS

end

sys.taskInit(gnss_fnc)

6.2.2 效果展示

测试服务器收到消息:

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

以下是纠偏网站:

http://old.openluat.com/GPS-Offset.html

6.3 低功耗模式下的 GNSS 应用

6.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
使用Air8000开发板,外接GPS天线,起一个60s定位一次的定时器,唤醒模块60s一定位
然后定位成功获取到经纬度发送到服务器上面,然后进入休眠
]]

tcp_client_main=require("tcp_client_main")

local function lowpower_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
    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", "gnsslowpower", data) --发送数据到服务器
    pm.power(pm.WORK_MODE, 1)--进入低功耗模式
    pm.power(pm.WORK_MODE,1,1)--wifi进入低功耗模式
end

local function lower_open()
    pm.power(pm.WORK_MODE, 0)   --进入正常模式
    exgnss.open(exgnss.TIMERORSUC,{tag="lowpower",val=60,cb=lowpower_cb})
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    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左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        auto_open=false 
    }
    exgnss.setup(gnssotps)  --配置GNSS参数
    exgnss.open(exgnss.TIMERORSUC,{tag="lowpower",val=60,cb=lowpower_cb}) --打开一个60s的TIMERORSUC应用,该模式定位成功关闭
    sys.timerLoopStart(lower_open,60000)       --每60s开启一次GNSS
    -- gpio.close(24)--此脚为gnss备电脚和三轴加速度传感器的供电脚,功能是热启动和保存星历文件,关掉会没有热启动,常开功耗会增高0.5-1MA左右
    -- --关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
    pm.power(pm.USB, false)
end

sys.taskInit(gnss_fnc)

6.3.2 效果展示

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

以下是纠偏网站:

http://old.openluat.com/GPS-Offset.html

6.4 PSM+ 模式下的 GNSS 应用

6.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
使用Air8000开发板,外接GPS天线,开启定位,获取到定位发送到服务器上面,然后启动一个60s的定时器唤醒PSM+模式
模块开启定位,然后定位成功获取到经纬度发送到服务器上面,然后进入PSM+模式,等待唤醒
]]
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 = 43706 -- 换成自己的

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 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) 
    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(24) --此脚为gnss备电脚和三轴加速度传感器的供电脚,功能是热启动和保存星历文件,关掉会没有热启动,常开功耗会增高0.5-1MA左右

    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)
    local  rmc=exgnss.rmc(0)
    log.info("nmea", "rmc", json.encode(exgnss.rmc(0)))
    lat,lng=rmc.lat,rmc.lng
    sysplus.taskInitEx(testTask, d1Name, netCB, server_ip, server_port)
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    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)
    exgnss.open(exgnss.TIMERORSUC,{tag="psm",val=60,cb=psm_cb})
end

sys.taskInit(gnss_fnc)

6.4.2 效果展示

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

以下是纠偏网站:

http://old.openluat.com/GPS-Offset.html

6.5 运动检测定位模式下的 GNSS 应用

6.5.1 代码讲解

vibration.lua

vibration 主要操作:

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

2.设置三轴加速度传感器为微小震动模式 exvib.open(1),关于 exvib 的三种模式:

1,微小震动检测,用于检测轻微震动的场景,例如用手敲击桌面;加速度量程2g;

2,运动检测,用于电动车或汽车行驶时的检测和人行走和跑步时的检测;加速度量程4g;

3,跌倒检测,用于人或物体瞬间跌倒时的检测;加速度量程8g;

3.8000 内置的三轴加速度传感器的中断触发脚为 gpio.WAKEUP2,设置 gpio 防抖为 100ms,然后设置中断触发

3.代码里面提供了两种触发方式:

第一种是持续震动模式:

加速度传感器震动之后触发中断,中断触发之后使用 exgnss.open 打开定时器,设置 exgnss.DEFAULT 模式,这种模式是一直开启 GNSS,直到调用关闭接口关闭才会关掉,定位成功之后每 5s 上传一次经纬度数据,

假设 10s 内没有再触发震动,则关闭 gnss,等待下一次震动触发

第二种方式是有效震动模式:

震动触发之后计算为一次触发,如果 10s 内触发 5 次有效震动,则开启后续逻辑,如果 10s 内没有触发 5 次,

则判定为无效震动,等待下一次触发,如果是有效震动就使用 exgnss.open 打开定时器,设置 exgnss.DEFAULT 模式,进行定位,

定位成功之后每 5s 上传一次经纬度数据到服务器,有效震动触发之后有 30 分钟的等待时间,在此期间,如果触发有效震动则

不去进行后续逻辑处理,30 分钟时间到了之后,等待下一次有效震动

这两种模式通过打开不同的注释来执行

--[[
@module  vibration
@summary 利用加速度传感器da221实现中断触发gnss定位
@version 1.0
@date    2025.08.01
@author  李源龙
@usage
使用Air8000利用内置的da221加速度传感器实现震动中断触发gnss定位,给了两种方式,
第一种是加速度传感器震动之后触发,触发之后开始打开gnss,进行定位,定位成功之后每5s上传一次经纬度数据
假设10s内没有再触发震动,则关闭gnss,等待下一次震动触发

第二种方式是震动触发之后计算为一次触发,如果10s内触发5次有效震动,则开启后续逻辑,如果10s内没有触发5次
则判定为无效震动,等待下一次触发,如果是有效震动就打开gnss,进行定位,
定位成功之后每5s上传一次经纬度数据到服务器,有效震动触发之后有30分钟的等待时间,在此期间,如果触发有效震动则
不去进行后续逻辑处理,30分钟时间到了之后,等待下一次有效震动
具体使用哪种方式可以根据实际需求选择
]]

exvib=require("exvib")
tcp_client_main=require("tcp_client_main")

local intPin=gpio.WAKEUP2   --中断检测脚,内部固定wakeup2
local tid   --获取定时打开的定时器id
local num=0 --计数器 
local ticktable={0,0,0,0,0} --存放5次中断的tick值,用于做有效震动对比
local eff=false --有效震动标志位,用于判断是否触发定位

local function vib_cb(tag)
    log.info("TAGmode1_cb+++++++++",tag)
     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) --发送数据到服务器

end
--定位成功就5s发送一包数据到服务器
local function gnss_state(event, ticks)
    -- event取值有
    -- "FIXED":string类型 定位成功
    -- "LOSE": string类型 定位丢失
    -- "CLOSE": string类型 GNSS关闭,仅配合使用gnss.lua有效

    -- ticks number类型 是事件发生的时间,一般可以忽略
    log.info("exgnss", "state", event)
    if event=="FIXED" then
        log.info("定位成功")
    end
end

sys.subscribe("GNSS_STATE",gnss_state)

--有效震动模式
--tick计数器,每秒+1用于存放5次中断的tick值,用于做有效震动对比
-- local function tick()
--     num=num+1
-- end
-- --每秒运行一次计时
-- sys.timerLoopStart(tick,1000)

-- --有效震动判断
-- local function ind()
--     log.info("int", gpio.get(intPin))
--     if gpio.get(intPin) == 1 then
--         --接收数据如果大于5就删掉第一个
--         if #ticktable>=5 then
--             log.info("table.remove",table.remove(ticktable,1))
--         end
--         --存入新的tick值
--         table.insert(ticktable,num)
--         log.info("tick",num,(ticktable[5]-ticktable[1]<10),ticktable[5]>0)
--         log.info("tick2",ticktable[1],ticktable[2],ticktable[3],ticktable[4],ticktable[5])
--         --表长度为5且,第5次中断时间间隔减去第一次间隔小于10s,且第5次值为有效值
--         if #ticktable>=5 and (ticktable[5]-ticktable[1]<10 and ticktable[1]>0) then
--             log.info("vib", "xxx")
--             --是否要去触发有效震动逻辑
--             if eff==false then
--                 sys.publish("EFFECTIVE_VIBRATION")
--             end
--         end
--     end
-- end

-- --设置30s分钟之后再判断是否有效震动函数
-- local function num_cb()
--     eff=false
-- end

-- local function eff_vib()
--     --触发之后eff设置为true,30分钟之后再触发有效震动
--     eff=true
--     --30分钟之后再触发有效震动
--     sys.timerStart(num_cb,180000)
--     --判断gnss是否处于打开状态
--     if exgnss.is_active(exgnss.DEFAULT,{tag="vib"})~=true then
--         log.info("nmea", "is_open", "false")
--         exgnss.open(exgnss.DEFAULT,{tag="vib",cb=vib_cb}) 
--         tid=sys.timerLoopStart(vib_cb, 5000)
--     else
--         log.info("nmea", "is_open", "true")
--     end
-- end

-- sys.subscribe("EFFECTIVE_VIBRATION",eff_vib)

--持续震动模式
--10s没有触发中断就停止
local function vib_close()
    exgnss.close(exgnss.DEFAULT,{tag="vib"})
    sys.timerStop(tid)
end

--持续震动模式中断函数
local function ind()
    log.info("int", gpio.get(intPin))
    if gpio.get(intPin) == 1 then
        --10s没有触发中断就停止
        sys.timerStart(vib_close,10000)
        local x,y,z =  exvib.read_xyz()      --读取x,y,z轴的数据
        log.info("x", x..'g', "y", y..'g', "z", z..'g')
        --判断gnss是否处于打开状态
        if exgnss.is_active(exgnss.DEFAULT,{tag="vib"})~=true then
            log.info("nmea", "is_open", "false")
            exgnss.open(exgnss.DEFAULT,{tag="vib",cb=vib_cb}) 
            tid=sys.timerLoopStart(vib_cb, 5000)
        else
            log.info("nmea", "is_open", "true")
        end
    end
end

local function gnss_fnc()
    log.info("gnss_fnc111")
    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左右,所以默认开启,如果可以接受下一次定位是冷启动,可以把auto_open设置成false
        ----需要注意的是热启动在定位成功之后,需要再开启3s左右才能保证本次的星历获取完成,如果对定位速度有要求,建议这么处理
        -- auto_open=false 
    }
    exgnss.setup(gnssotps)
    -- 1,微小震动检测,用于检测轻微震动的场景,例如用手敲击桌面;加速度量程2g;
    -- 2,运动检测,用于电动车或汽车行驶时的检测和人行走和跑步时的检测;加速度量程4g;
    -- 3,跌倒检测,用于人或物体瞬间跌倒时的检测;加速度量程8g;
    --打开震动检测功能
    exvib.open(1)
    --设置gpio防抖100ms
    gpio.debounce(intPin, 100)
    --设置gpio中断触发方式wakeup2唤醒脚默认为双边沿触发
    gpio.setup(intPin, ind)

end

sys.taskInit(gnss_fnc)

6.5.2 效果展示

有效中断模式:

有效震动模式:

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

以下是纠偏网站:

http://old.openluat.com/GPS

6.6 TCP 部分代码

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://netlab.luatos.com/
-- 点击 打开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

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

七、总结

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

八、注意事项与常见问题

8.1、GNSS 定位经纬度不准确

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

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

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

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

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

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

8.3、GPS 天线如何设计

参考:https://docs.openluat.com/air8000/luatos/hardware/design/gnssant/

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)