Răsfoiți Sursa

add:新增780EGH的gnss的demo

liszt123 7 luni în urmă
părinte
comite
cfdc82f076

+ 990 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/gnss/gnss.lua

@@ -0,0 +1,990 @@
+--[[
+@module gnss
+@summary gnss拓展库
+@version 1.0
+@date    2025.07.16
+@author  李源龙
+@usage
+-- 用法实例
+-- 注意:gnss.lua适用的产品范围,只能用于合宙内部集成GNSS功能的产品,目前有Air780EGH,Air8000系列
+-- 提醒: 本库输出的坐标,均为 WGS84 坐标系
+-- 如需要在国内地图使用, 要转换成对应地图的坐标系, 例如 GCJ02 BD09
+-- 相关链接: https://lbsyun.baidu.com/index.php?title=coordinate
+-- 相关链接: https://www.openluat.com/GPS-Offset.html
+
+--关于gnss的三种应用场景:
+gnss.DEFAULT:
+--- gnss应用模式1.
+-- 打开gnss后,gnss定位成功时,如果有回调函数,会调用回调函数
+-- 使用此应用模式调用gnss.open打开的“gnss应用”,必须主动调用gnss.close
+-- 或者gnss.closeAll才能关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
+-- 通俗点说就是一直打开,除非自己手动关闭掉
+
+gnss.TIMERORSUC:
+--- gnss应用模式2.
+-- 打开gnss后,如果在gnss开启最大时长到达时,没有定位成功,如果有回调函数,
+-- 会调用回调函数,然后自动关闭此“gnss应用”
+-- 打开gnss后,如果在gnss开启最大时长内,定位成功,如果有回调函数,
+-- 会调用回调函数,然后自动关闭此“gnss应用”
+-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用gnss.close或者
+-- gnss.closeAll主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
+-- 通俗点说就是设置规定时间打开,如果规定时间内定位成功就会自动关闭此应用,
+-- 如果没有定位成功,时间到了也会自动关闭此应用
+
+gnss.TIMER:
+--- gnss应用模式3.
+-- 打开gnss后,在gnss开启最大时长时间到达时,无论是否定位成功,如果有回调函数,
+-- 会调用回调函数,然后自动关闭此“gnss应用”
+-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用gnss.close或者gnss.closeAll
+-- 主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
+-- 通俗点说就是设置规定时间打开,无论是否定位成功,到了时间都会自动关闭此应用,
+-- 和第二种的区别在于定位成功之后不会自动关闭,到时间之后才会自动关闭
+
+gnss=require("gnss")    
+
+local function mode1_cb(tag)
+    log.info("TAGmode1_cb+++++++++",tag)
+    log.info("nmea", "rmc", json.encode(gnss.getRmc(2)))
+end
+
+local function mode2_cb(tag)
+    log.info("TAGmode2_cb+++++++++",tag)
+    log.info("nmea", "rmc", json.encode(gnss.getRmc(2)))
+end
+
+local function mode3_cb(tag)
+    log.info("TAGmode3_cb+++++++++",tag)
+    log.info("nmea", "rmc", json.encode(gnss.getRmc(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.setup(gnssotps)
+    --开启gnss应用
+    gnss.open(gnss.TIMER,{tag="MODE1",val=60,cb=mode1_cb})
+    gnss.open(gnss.DEFAULT,{tag="MODE2",cb=mode2_cb})
+    gnss.open(gnss.TIMERORSUC,{tag="MODE3",val=60,cb=mode3_cb})
+    sys.wait(40000)
+    log.info("关闭一个gnss应用,然后查看下所有应用的状态")
+    --关闭一个gnss应用
+    gnss.close(gnss.TIMER,{tag="MODE1"})
+    --查询3个gnss应用状态
+    log.info("gnss应用状态1",gnss.isActive(gnss.TIMER,{tag="MODE1"}))
+    log.info("gnss应用状态2",gnss.isActive(gnss.DEFAULT,{tag="MODE2"}))
+    log.info("gnss应用状态3",gnss.isActive(gnss.TIMERORSUC,{tag="MODE3"}))
+    sys.wait(10000)
+    --关闭所有gnss应用
+    gnss.closeAll()
+    --查询3个gnss应用状态
+    log.info("gnss应用状态1",gnss.isActive(gnss.TIMER,{tag="MODE1"}))
+    log.info("gnss应用状态2",gnss.isActive(gnss.DEFAULT,{tag="MODE2"}))
+    log.info("gnss应用状态3",gnss.isActive(gnss.TIMERORSUC,{tag="MODE3"}))
+    --查询最后一次定位结果
+    local loc= gnss.getlastloc()
+    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("gnss", "state", event)
+end
+sys.subscribe(gnss_state)
+
+]]
+local gnss = {}
+--gnss开启标志,true表示开启状态,false或者nil表示关闭状态
+local openFlag
+--gnss定位标志,true表示,其余表示未定位
+local fixFlag=nil
+
+--串口配置
+local uart_baudrate = 115200
+local uart_id = 2
+
+--gnss 的串口线程是否在工作;
+local taskFlag=false
+
+local agpsFlag=false
+
+--保存经纬度到文件区
+function gnss.saveloc(lat, lng)
+    if not lat or not lng then
+        if libgnss.isFix() then
+            local rmc = libgnss.getRmc(0)
+            if rmc then
+                lat, lng = rmc.lat, rmc.lng
+            end
+        end
+    end
+    if lat and lng then
+        -- log.info("待保存的GPS位置", lat, lng)
+        local locStr = string.format('{"lat":%.5f,"lng":%.5f}', lat, lng)
+        -- log.info("gnss", "保存GPS位置", locStr)
+        io.writeFile("/hxxtloc", locStr)
+    end
+    local now = os.time()
+    io.writeFile("/hxxt_tm", tostring(now))
+    -- log.info("now", now)
+end
+local tid
+
+sys.subscribe("GNSS_STATE", function(event)
+    -- log.info("libagps","libagps is "..event)
+    if event == "FIXED" then
+        gnss.saveloc()
+        tid=sys.timerLoopStart(gnss.saveloc,600000)
+    elseif event == "LOSE" or event == "CLOSE" then
+        -- log.info("libagps","libagps is close")
+        sys.timerStop(tid)
+    end
+end)
+
+--agps操作,联网访问服务器获取星历数据
+local function _agps()
+    local lat, lng
+
+    --此逻辑在agps定位成功之后,还会继续开启10s-15s,
+    --原因是因为如果第一次冷启动之后,定位成功之后,
+    --如果直接关闭gnss会导致gnss芯片的星历没有解析完毕,会影响下一次的定位为冷启动
+    --如果对功耗有需求,需要定位快,可以每次都使用agps,不需要这句,直接屏蔽掉即可
+    --代价是每次定位都会进行基站定位,
+    -- gnss.open(gnss.TIMER,{tag="libagps",val=20}) 
+    -- 判断星历时间和下载星历   
+    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 = gnss.opts.url
+        if not gnss.opts.url then
+            if gnss.opts.gnssmode and 2 == gnss.opts.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
+        end
+        local code = http.request("GET", url, nil, nil, {dst="/hxxt.dat"}).wait()
+        if code and code == 200 then
+            log.info("gnss.opts", "下载星历成功", url)
+            io.writeFile("/hxxt_tm", tostring(now))
+        else
+            log.info("gnss.opts", "下载星历失败", code)
+        end
+    else
+        log.info("gnss.opts", "星历不需要更新", now - agps_time)
+    end
+    --进行基站定位,给到gnss芯片一个大概的位置
+    if mobile then
+        local lbsLoc2 = require("lbsLoc2")
+        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
+    elseif wlan then
+        -- wlan.scan()
+        -- sys.waitUntil("WLAN_SCAN_DONE", 5000)
+    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 gps_uart_id = uart_id
+
+    -- 写入星历
+    local agps_data = io.readFile("/hxxt.dat")
+    if agps_data and #agps_data > 1024 then
+        log.info("gnss.opts", "写入星历数据", "长度", #agps_data)
+        for offset=1,#agps_data,512 do
+            log.info("gnss", "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("gnss.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("gnss.opts", "没有GPS坐标", lat, lng)
+        return --暂时不写入参考位置
+    else
+        log.info("gnss.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("gnss.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("gnss.opts", "写入AGPS参考位置", str)
+    uart.write(gps_uart_id, str)
+
+    -- 结束
+    gnss.opts.agps_tm = now
+    agpsFlag=true
+end
+
+--执行agps操作判断
+function gnss.agps()
+    -- 如果不是强制写入AGPS信息, 而且是已经定位成功的状态,那就没必要了
+    if libgnss.isFix() then return end
+    -- 先判断一下时间
+    while not socket.adapter() do
+        log.warn("gnss_agps", "wait IP_READY")
+        -- 在此处阻塞等待WIFI连接成功的消息"IP_READY"
+        -- 或者等待30秒超时退出阻塞等待状态
+        local result=sys.waitUntil("IP_READY", 30000)
+        if result == false then
+            log.warn("gnss_agps", "wait IP_READY timeout")
+            return
+        end
+    end
+    if not gnss.opts.agps_tm then
+        socket.sntp()
+        sys.waitUntil("NTP_UPDATE", 5000)
+    end
+    local now = os.time()
+    local agps_time = tonumber(io.readFile("/hxxt_tm") or "0") or 0
+    -- if ((not gnss.opts.agps_tm) and (now - agps_time > 300))  or  now - agps_time > 3600 then
+    if not gnss.opts.agps_tm  or  now - agps_time > 3600 then
+        -- 执行AGPS
+        log.info("gnss.opts", "开始执行AGPS")
+        sys.taskInit(_agps)
+    else
+        log.info("gnss.opts", "暂不需要写入AGPS")
+    end
+end
+
+--打开gnss,内部函数使用,不推荐给脚本层使用
+local function _open()
+    if openFlag then return end
+    libgnss.clear() -- 清空数据,兼初始化
+    uart.setup(uart_id, uart_baudrate)
+    -- pm.power(pm.GPS, false)
+    pm.power(pm.GPS, true)
+    if gnss.opts.gnssmode==1 then
+        --默认全开启
+        log.info("全卫星开启")
+        elseif gnss.opts.gnssmode==2 then
+        --默认开启单北斗
+        sys.timerStart(function()
+            uart.write(uart_id, "$CFGSYS,h10\r\n")
+        end,200)
+        log.info("单北斗开启")
+    end
+    if gnss.opts.debug==true then
+        log.info("debug开启")
+        libgnss.debug(true)
+    elseif gnss.opts.debug==false then
+        log.info("debug关闭")
+        libgnss.debug(false)
+    end
+    if type(gnss.opts.bind)=="number"  then
+        log.info("绑定bind事件")
+        libgnss.bind(uart_id,gnss.opts.bind)
+    else
+        libgnss.bind(uart_id)
+    end
+    if gnss.opts.rtc==true then
+        log.info("rtc开启")
+        libgnss.rtcAuto(true)
+    elseif gnss.opts.rtc==false then
+        log.info("rtc关闭")
+        libgnss.rtcAuto(false)
+    end
+    if gnss.opts.agps_enable==true then
+        log.info("agps开启")
+        sys.taskInit(gnss.agps)
+    end
+    --设置输出VTG内容
+    sys.timerStart(function()
+        uart.write(uart_id,"$CFGMSG,0,5,1,1\r\n")
+    end,800)
+     --设置输出ZDA内容
+     sys.timerStart(function()
+        uart.write(uart_id,"$CFGMSG,0,6,1,1\r\n")
+    end,900)
+    openFlag = true
+    sys.publish("GNSS_STATE","OPEN")
+    log.info("gnss._open")
+end
+
+--关闭gnss,内部函数使用,不推荐给脚本层使用
+local function _close()
+    if not openFlag then return end
+    gnss.saveloc()
+    pm.power(pm.GPS, false)
+    uart.close(uart_id)
+    openFlag = false
+    fixFlag = false
+    sys.publish("GNSS_STATE","CLOSE",fixFlag)    
+    log.info("gnss._close")
+    libgnss.clear()
+end
+
+
+--- gnss应用模式1.
+--
+-- 打开gnss后,gnss定位成功时,如果有回调函数,会调用回调函数
+--
+-- 使用此应用模式调用gnss.open打开的“gnss应用”,必须主动调用gnss.close或者gnss.closeAll才能关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
+gnss.DEFAULT = 1
+--- gnss应用模式2.
+--
+-- 打开gnss后,如果在gnss开启最大时长到达时,没有定位成功,如果有回调函数,会调用回调函数,然后自动关闭此“gnss应用”
+--
+-- 打开gnss后,如果在gnss开启最大时长内,定位成功,如果有回调函数,会调用回调函数,然后自动关闭此“gnss应用”
+--
+-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用gnss.close或者gnss.closeAll主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
+gnss.TIMERORSUC = 2
+--- gnss应用模式3.
+--
+-- 打开gnss后,在gnss开启最大时长时间到达时,无论是否定位成功,如果有回调函数,会调用回调函数,然后自动关闭此“gnss应用”
+--
+-- 打开gnss后,在自动关闭此“gnss应用”前,可以调用gnss.close或者gnss.closeAll主动关闭此“gnss应用”,主动关闭时,即使有回调函数,也不会调用回调函数
+gnss.TIMER = 3
+
+--“gnss应用”表
+local tList = {}
+
+--[[
+函数名:delItem
+功能  :从“gnss应用”表中删除一项“gnss应用”,并不是真正的删除,只是设置一个无效标志
+参数  :
+        mode:gnss应用模式
+        para:
+            para.tag:“gnss应用”标记
+            para.val:gnss开启最大时长
+            para.cb:回调函数
+返回值:无
+]]
+local function delItem(mode,para)
+    for i=1,#tList do
+        --标志有效 并且 gnss应用模式相同 并且 “gnss应用”标记相同
+        if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then
+            --设置无效标志
+            tList[i].flag,tList[i].delay = false
+            break
+        end
+    end
+end
+
+
+--[[
+函数名:addItem
+功能  :新增一项“gnss应用”到“gnss应用”表
+参数  :
+        mode:gnss应用模式
+        para:
+            para.tag:“gnss应用”标记
+            para.val:gnss开启最大时长
+            para.cb:回调函数
+返回值:无
+]]
+local function addItem(mode,para)
+    --删除相同的“gnss应用”
+    delItem(mode,para)
+    local item,i,fnd = {flag=true, mode=mode, para=para}
+    --如果是TIMERORSUC或者TIMER模式,初始化gnss工作剩余时间
+    if mode==gnss.TIMERORSUC or mode==gnss.TIMER then item.para.remain = para.val end
+    for i=1,#tList do
+        --如果存在无效的“gnss应用”项,直接使用此位置
+        if not tList[i].flag then
+            tList[i] = item
+            fnd = true
+            break
+        end
+    end
+    --新增一项
+    if not fnd then table.insert(tList,item) end
+end
+
+--退出GNSS定时器
+local function existTimerItem()
+    for i=1,#tList do
+        if tList[i].flag and (tList[i].mode==gnss.TIMERORSUC or tList[i].mode==gnss.TIMER or tList[i].para.delay) then return true end
+    end
+end
+
+--GNSS定时器
+local function timerFnc()
+    for i=1,#tList do
+        if tList[i].flag then
+            log.info("gnss.timerFnc@"..i,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay)
+            local rmn,dly,md,cb = tList[i].para.remain,tList[i].para.delay,tList[i].mode,tList[i].para.cb
+
+            if rmn and rmn>0 then
+                tList[i].para.remain = rmn-1
+            end
+            if dly and dly>0 then
+                tList[i].para.delay = dly-1
+            end
+            rmn = tList[i].para.remain
+
+            if libgnss.isFix() and md==gnss.TIMER and rmn==0 and not tList[i].para.delay then
+                tList[i].para.delay = 1
+            end
+            dly = tList[i].para.delay
+            if libgnss.isFix() then
+                if dly and dly==0 then
+                    if cb then cb(tList[i].para.tag) end
+                    if md == gnss.DEFAULT then
+                        tList[i].para.delay = nil
+                    else
+                        gnss.close(md,tList[i].para)
+                    end
+                end
+            else
+                if rmn and rmn == 0 then
+                    if cb then cb(tList[i].para.tag) end
+                    gnss.close(md,tList[i].para)
+                end
+            end
+        end
+    end
+    if existTimerItem() then sys.timerStart(timerFnc,1000) end
+end
+
+--[[
+函数名:statInd
+功能  :处理gnss定位成功的消息
+参数  :
+        evt:gnss消息类型
+返回值:无
+]]
+local function statInd(evt)
+    --定位成功的消息
+    if evt == "FIXED" then
+        fixFlag = true
+        for i=1,#tList do
+            log.info("gnss.statInd@"..i,tList[i].flag,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay,tList[i].para.cb)
+            if tList[i].flag then
+                if tList[i].mode ~= gnss.TIMER then
+                    tList[i].para.delay = 1
+                    if tList[i].mode == gnss.DEFAULT then
+                        if existTimerItem() then sys.timerStart(timerFnc,1000) end
+                    end
+                end
+            end
+        end
+    end
+end
+
+
+--[[
+设置gnss定位参数
+@api gnss.setup(opts)
+@table opts gnss定位参数,可选值gnssmode:定位卫星模式,1为卫星全定位,2为单北斗,默认为卫星全定位
+agps_enable:是否启用AGPS,true为启用,false为不启用,默认为false
+debug:是否输出调试信息到luatools,true为输出,false为不输出,默认为false
+uart:GNSS串口配置,780EGH和8000默认为uart2,可不填
+uartbaud:GNSS串口波特率,780EGH和8000默认为115200,可不填
+bind:绑定uart端口进行GNSS数据读取,是否设置串口转发,指定串口号,不需要转发可不填
+rtc:定位成功后自动设置RTC true开启,flase关闭,默认为flase,不需要可不填
+@return nil
+@usage
+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.setup(gnssotps)
+]]
+function gnss.setup(opts)
+    gnss.opts=opts
+    if hmeta.model():find("780EGH") or hmeta.model():find("8000") then
+        uart_id=2
+        uart_baudrate=115200
+    else
+        if gnss.opts.uart_id then
+            uart_id=gnss.opts.uart_id
+        else
+            uart_id=2    
+        end
+        if gnss.opts.uartbaud then
+            uart_baudrate=gnss.opts.uartbaud
+        else
+            uart_baudrate=115200
+        end
+    end   
+end
+
+--[[
+打开一个“gnss应用”
+@api gnss.open(mode,para)
+@number mode gnss应用模式,支持gnss.DEFAULT,gnss.TIMERORSUC,gnss.TIMER三种
+@param para table类型,gnss应用参数,para.tag:string类型,gnss应用标记,para.val:number类型,gnss应用开启最大时长,单位:秒,mode参数为gnss.TIMERORSUC或者gnss.TIMER时,此值才有意义;使用close接口时,不需要传入此参数,para.cb:gnss应用结束时的回调函数,回调函数的调用形式为para.cb(para.tag);使用close接口时,不需要传入此参数
+@return nil
+@usage
+-- “gnss应用”:指的是使用gnss功能的一个应用
+-- 例如,假设有如下3种需求,要打开gnss,则一共有3个“gnss应用”:
+-- “gnss应用1”:每隔1分钟打开一次gnss
+-- “gnss应用2”:设备发生震动时打开gnss
+-- “gnss应用3”:收到一条特殊短信时打开gnss
+-- 只有所有“gnss应用”都关闭了,才会去真正关闭gnss
+-- 每个“gnss应用”打开或者关闭gnss时,最多有4个参数,其中 gnss应用模式和gnss应用标记 共同决定了一个唯一的“gnss应用”:
+-- 1、gnss应用模式(必选)
+-- 2、gnss应用标记(必选)
+-- 3、gnss开启最大时长[可选]
+-- 4、回调函数[可选]
+-- 例如gnss.open(gnss.TIMER,{tag="MODE1",val=60,cb=mode1_cb})
+-- gnss.TIMER为gnss应用模式,"MODE1"为gnss应用标记,60秒为gnss开启最大时长,mode1_cb为回调函数
+gnss.open(gnss.TIMER,{tag="MODE1",val=60,cb=mode1_cb})
+gnss.open(gnss.DEFAULT,{tag="MODE2",cb=mode2_cb})
+gnss.open(gnss.TIMERORSUC,{tag="MODE3",val=60,cb=mode3_cb})
+]]
+function gnss.open(mode,para)
+    assert((para and type(para) == "table" and para.tag and type(para.tag) == "string"),"gnss.open para invalid")
+    log.info("gnss.open",mode,para.tag,para.val,para.cb)
+    --如果gnss定位成功
+    if libgnss.isFix() then
+        if mode~=gnss.TIMER then
+            --执行回调函数
+            if para.cb then para.cb(para.tag) end
+            if mode==gnss.TIMERORSUC then return end
+        end
+    end
+    addItem(mode,para)
+    --真正去打开gnss
+    _open()
+    --启动1秒的定时器
+    if existTimerItem() and not sys.timerIsActive(timerFnc) then
+        sys.timerStart(timerFnc,1000)
+    end
+end
+
+
+--[[
+关闭一个“gnss应用”,只是从逻辑上关闭一个gnss应用,并不一定真正关闭gnss,是有所有的gnss应用都处于关闭状态,才会去真正关闭gnss
+@api gnss.close()
+@number mode gnss应用模式,支持gnss.DEFAULT,gnss.TIMERORSUC,gnss.TIMER三种
+@param para table类型,gnss应用参数,para.tag:string类型,gnss应用标记,para.val:number类型,gnss应用开启最大时长,单位:秒,mode参数为gnss.TIMERORSUC或者gnss.TIMER时,此值才有意义;使用close接口时,不需要传入此参数,para.cb:gnss应用结束时的回调函数,回调函数的调用形式为para.cb(para.tag);使用close接口时,不需要传入此参数
+@return nil
+@usage
+gnss.open(gnss.TIMER,{tag="MODE1",val=60,cb=mode1_cb})
+gnss.close(gnss.TIMER,{tag="MODE1"})
+]]
+function gnss.close(mode,para)
+    assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gnss.close para invalid")
+    log.info("gnss.close",mode,para.tag,para.val,para.cb)
+    --删除此“gnss应用”
+    delItem(mode,para)
+    local valid,i
+    for i=1,#tList do
+        if tList[i].flag then
+            valid = true
+        end
+    end
+    --如果没有一个“gnss应用”有效,则关闭gnss
+    if not valid then _close() end
+end
+
+--[[
+关闭所有“gnss应用”
+@api gnss.closeAll()
+@return nil
+@usage
+gnss.open(gnss.TIMER,{tag="MODE1",val=60,cb=mode1_cb})
+gnss.open(gnss.DEFAULT,{tag="MODE2",cb=mode2_cb})
+gnss.open(gnss.TIMERORSUC,{tag="MODE3",val=60,cb=mode3_cb})
+gnss.closeAll()
+]]
+function gnss.closeAll()
+    for i=1,#tList do
+        if tList[i].flag and tList[i].para.cb then tList[i].para.cb(tList[i].para.tag) end
+        gnss.close(tList[i].mode,tList[i].para)
+    end
+end
+
+--[[
+判断一个“gnss应用”是否处于激活状态
+@api gnss.isActive(mode,para)
+@number mode gnss应用模式,支持gnss.DEFAULT,gnss.TIMERORSUC,gnss.TIMER三种
+@param para table类型,gnss应用参数,para.tag:string类型,gnss应用标记,para.val:number类型,gnss应用开启最大时长,单位:秒,mode参数为gnss.TIMERORSUC或者gnss.TIMER时,此值才有意义;使用close接口时,不需要传入此参数,para.cb:gnss应用结束时的回调函数,回调函数的调用形式为para.cb(para.tag);使用close接口时,不需要传入此参数,gnss应用模式和gnss应用标记唯一确定一个“gnss应用”,调用本接口查询状态时,mode和para.tag要和gnss.open打开一个“gnss应用”时传入的mode和para.tag保持一致
+@return bool result,处于激活状态返回true,否则返回nil
+@usage
+gnss.open(gnss.TIMER,{tag="MODE1",val=60,cb=mode1_cb})
+gnss.open(gnss.DEFAULT,{tag="MODE2",cb=mode2_cb})
+gnss.open(gnss.TIMERORSUC,{tag="MODE3",val=60,cb=mode3_cb})
+log.info("gnss应用状态1",gnss.isActive(gnss.TIMER,{tag="MODE1"}))
+log.info("gnss应用状态2",gnss.isActive(gnss.DEFAULT,{tag="MODE2"}))
+log.info("gnss应用状态3",gnss.isActive(gnss.TIMERORSUC,{tag="MODE3"}))
+]]
+function gnss.isActive(mode,para)
+    assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gnss.isActive para invalid")
+    for i=1,#tList do
+        if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then return true end
+    end
+end
+
+sys.subscribe("GNSS_STATE",statInd)
+
+
+--[[
+当前是否已经定位成功
+@api gnss.isFix()
+@return boolean   true/false,定位成功返回true,否则返回false
+@usage
+log.info("nmea", "isFix", gnss.isFix())
+]]
+function gnss.isFix()
+   return libgnss.isFix()
+end
+
+
+--[[
+获取number类型的位置和速度信息
+@api gnss.getIntLocation(speed_type)
+@number 速度单位,默认是m/h,
+0 - m/h 米/小时, 默认值, 整型
+1 - m/s 米/秒, 浮点数
+2 - km/h 千米/小时, 浮点数
+3 - kn/h 英里/小时, 浮点数
+@return number lat数据, 格式为 DDDDDDDDD,示例:343482649,DDDDDDDDD格式是由DD.DDDDDDD*10000000转换而来,目的是作为整数,方便某些场景使用
+@return number lng数据, 格式为 DDDDDDDDD,示例:1135039700,DDDDDDDDD格式是由DD.DDDDDDD*10000000转换而来,目的是作为整数,方便某些场景使用
+@return number speed数据, 单位根据speed_type决定,m/h, m/s, km/h, kn/h
+@usage
+--DDDDDDDDD格式是由DD.DDDDDDD*10000000转换而来,目的是作为整数,方便某些场景使用,示例:343482649对应的原始值是34.3482649
+-- 该数据是通过RMC转换的,如果想获取更详细的可以用gnss.getRmc(1)
+-- speed数据默认 米/小时,返回值例如:343482649	1135039700	390m/h
+log.info("nmea", "loc", gnss.getIntLocation())
+-- speed数据米/秒,返回值例如:343482649	1135039700	0.1085478m/s
+log.info("nmea", "loc", gnss.getIntLocation(1))
+-- speed数据千米/小时,返回值例如:343482649	1135039700	0.3907720km/h
+log.info("nmea", "loc", gnss.getIntLocation(2))
+-- speed数据英里/小时,返回值例如:343482649	1135039700	0.2110000kn/h
+log.info("nmea", "loc", gnss.getIntLocation(3))
+]]
+function gnss.getIntLocation(speed_type)
+    return libgnss.getIntLocation(speed_type)
+end
+
+
+--[[
+获取RMC的信息,经纬度,时间,速度,航向,定位是否有效,磁偏角
+@api gnss.getRmc(lnglat_mode)
+@number 经纬度数据的格式, 0-ddmm.mmmmm格式, 1-DDDDDDDDD格式, 2-DD.DDDDDDD格式, 3-原始RMC字符串
+@return table/string rmc数据
+@usage
+-- 解析nmea
+log.info("nmea", "rmc", json.encode(gnss.getRmc(2)))
+-- 实例输出,获取值的解释
+-- {
+--     "course":344.9920044,     // 地面航向,单位为度,从北向起顺时针计算
+--     "valid":true,   // true定位成功,false定位丢失
+--     "lat":34.5804405,  // 纬度, 正数为北纬, 负数为南纬
+--     "lng":113.8399506,  // 经度, 正数为东经, 负数为西经
+--     "variation":0,  // 磁偏角,固定为0
+--     "speed":0.2110000       // 地面速度, 单位为"节"
+--     "year":2023,    // 年份
+--     "month":1,      // 月份, 1-12
+--     "day":5,        // 月份天, 1-31
+--     "hour":7,       // 小时,0-23
+--     "min":23,       // 分钟,0-59
+--     "sec":20,       // 秒,0-59
+-- }
+--模式0示例:
+--json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位,本示例保留5位小数
+log.info("nmea", "rmc0", json.encode(gnss.getRmc(0),"5f"))
+{"variation":0,"lat":3434.82666,"min":54,"valid":true,"day":17,"lng":11350.39746,"speed":0.21100,"year":2025,"month":7,"sec":30,"hour":11,"course":344.99200}
+--模式1示例:
+--DDDDDDDDD格式是由DD.DDDDDDD*10000000转换而来,目的是作为整数,方便某些场景使用
+log.info("nmea", "rmc1", json.encode(gnss.getRmc(1)))
+{"variation":0,"lat":345804414,"min":54,"valid":true,"day":17,"lng":1138399500,"speed":0.2110000,"year":2025,"month":7,"sec":30,"hour":11,"course":344.9920044}
+--模式2示例:
+--json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位
+log.info("nmea", "rmc2", json.encode(gnss.getRmc(2)))
+{"variation":0,"lat":34.5804405,"min":54,"valid":true,"day":17,"lng":113.8399506,"speed":0.2110000,"year":2025,"month":7,"sec":30,"hour":11,"course":344.9920044}
+--模式3示例:
+log.info("nmea", "rmc3", gnss.getRmc(3))
+$GNRMC,115430.000,A,3434.82649,N,11350.39700,E,0.211,344.992,170725,,,A,S*02\r
+]]
+function gnss.getRmc(lnglat_mode)
+    return libgnss.getRmc(lnglat_mode)
+end
+
+--[[
+获取原始GSV信息
+@api gnss.getGsv()
+@return table 原始GSV数据
+@usage
+-- 解析nmea
+log.info("nmea", "gsv", json.encode(gnss.getGsv()))
+-- 实例输出
+-- {
+--     "total_sats":24,      // 总可见卫星数量
+--     "sats":[
+--         {
+--             "snr":27,     // 信噪比
+--             "azimuth":278, // 方向角
+--             "elevation":59, // 仰角
+--             "tp":0,        // 0 - GPS, 1 - BD, 2 - GLONASS, 3 - Galileo, 4 - QZSS
+--             "nr":4         // 卫星编号
+--         },
+--         // 这里忽略了22个卫星的信息
+--         {
+--             "snr":0,
+--             "azimuth":107,
+--             "elevation":19,
+--             "tp":1,
+--             "nr":31
+--         }
+--     ]
+-- }
+]]
+function gnss.getGsv() 
+    return libgnss.getGsv() 
+end
+
+
+--[[
+获取原始GSA信息
+@api gnss.getGsa(data_mode)
+@number 模式,默认为0 -所有卫星系统全部输出在一起,1 - 每个卫星系统单独分开输出
+@return table 原始GSA数据
+@usage
+-- 获取
+log.info("nmea", "gsa", json.encode(gnss.getGsa()))
+-- 示例数据(模式0, 也就是默认模式)
+--sysid:1为GPS,4为北斗,2为GLONASS,3为Galileo
+{"pdop":1.1770000,  垂直精度因子,0.00 - 99.99,不定位时值为 99.99
+"sats":[15,13,5,18,23,20,24,30,24,13,33,38,8,14,28,41,6,39,25,16,32,27],    // 正在使用的卫星编号
+"vdop":1.0160000,   垂直精度因子,0.00 - 99.99,不定位时值为 99.99
+"hdop":0.5940000,   // 位置精度因子,0.00 - 99.99,不定位时值为 99.99
+"sysid":1,         // 卫星系统编号1为GPS,4为北斗,2为GLONASS,3为Galileo
+"fix_type":3       // 定位模式, 1-未定位, 2-2D定位, 3-3D定位
+}
+
+--模式1
+log.info("nmea", "gsa", json.encode(gnss.getGsa()))
+
+[{"pdop":1.1770000,"sats":[15,13,5,18,23,20,24],"vdop":1.0160000,"hdop":0.5940000,"sysid":1,"fix_type":3},
+{"pdop":1.1770000,"sats":[30,24,13,33,38,8,14,28,41,6,39,25],"vdop":1.0160000,"hdop":0.5940000,"sysid":4,"fix_type":3},
+{"pdop":1.1770000,"sats":[16,32,27],"vdop":1.0160000,"hdop":0.5940000,"sysid":4,"fix_type":3},
+{"pdop":1.1770000,"sats":{},"vdop":1.0160000,"hdop":0.5940000,"sysid":2,"fix_type":3},
+{"pdop":1.1770000,"sats":{},"vdop":1.0160000,"hdop":0.5940000,"sysid":3,"fix_type":3}]
+
+]]
+
+function gnss.getGsa(data_mode)
+    return libgnss.getGsa(data_mode)
+end
+
+
+--[[
+获取VTG速度信息
+@api gnss.getVtg(data_mode)
+@number 可选, 3-原始字符串, 不传或者传其他值, 则返回浮点值
+@return table/string 原始VTG数据
+@usage
+-- 解析nmea
+log.info("nmea", "vtg", json.encode(gnss.getVtg()))
+-- 示例
+{
+    "speed_knots":0,        // 速度, 英里/小时
+    "true_track_degrees":0,  // 真北方向角
+    "magnetic_track_degrees":0, // 磁北方向角
+    "speed_kph":0           // 速度, 千米/小时
+}
+
+--模式3
+log.info("nmea", "vtg", gnss.getVtg(3))
+-- 返回值:$GNVTG,0.000,T,,M,0.000,N,0.000,K,A*13\r
+-- 提醒: 在速度<5km/h时, 不会返回方向角
+]]
+function gnss.getVtg(data_mode)
+    return  libgnss.getVtg(data_mode)
+end
+
+--获取原始ZDA时间和日期信息
+--[[
+获取原始ZDA时间和日期信息
+@api gnss.getZda()
+@return table 原始zda数据
+@usage
+log.info("nmea", "zda", json.encode(gnss.getZda()))
+-- 实例输出
+-- {
+--     "minute_offset":0,   // 本地时区的分钟, 一般固定输出0
+--     "hour_offset":0,     // 本地时区的小时, 一般固定输出0
+--     "year":2023         // UTC 年,四位数字
+--     "month":1,          // UTC 月,两位,01 ~ 12
+--     "day":5,            // UTC 日,两位数字,01 ~ 31
+--     "hour":7,           // 小时
+--     "min":50,           // 分
+--     "sec":14,           // 秒
+-- }
+]]
+function gnss.getZda()
+    return  libgnss.getZda()
+end
+
+--[[
+获取GGA数据
+@api gnss.getGga(lnglat_mode)
+@number 经纬度数据的格式, 0-ddmm.mmmmm格式, 1-DDDDDDDDD格式, 2-DD.DDDDDDD格式, 3-原始GGA字符串
+@return table GGA数据, 若如不存在会返回nil
+@usage
+local gga = gnss.getGga(2)
+log.info("GGA", json.encode(gga, "11g"))
+--实例输出,获取值的解释:
+-- {
+--     "dgps_age":0,             // 差分校正时延,单位为秒
+--     "fix_quality":1,          // 定位状态标识 0 - 无效,1 - 单点定位,2 - 差分定位
+--     "satellites_tracked":14,  // 参与定位的卫星数量
+--     "altitude":0.255,         // 海平面分离度, 或者成为海拔, 单位是米,
+--     "hdop":0.0335,            // 水平精度因子,0.00 - 99.99,不定位时值为 99.99
+--     "longitude":113.231,      // 经度, 正数为东经, 负数为西经
+--     "latitude":23.4067,       // 纬度, 正数为北纬, 负数为南纬
+--     "height":0                // 椭球高,固定输出 1 位小数
+-- }
+模式0示例:
+json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位,本示例保留5位小数
+local gga = gnss.getGga(0)
+if gga then
+    log.info("GGA0", json.encode(gga, "5f"))
+end
+{"longitude":11419.19531,"dgps_age":0,"altitude":86.40000,"hdop":0.59400,"height":-13.70000,"fix_quality":1,"satellites_tracked":22,"latitude":3447.86914}
+模式1示例:
+DDDDDDDDD格式是由DD.DDDDDDD*10000000转换而来,目的是作为整数,方便某些场景使用
+local gga1 = gnss.getGga(1)
+if gga1 then
+    log.info("GGA1", json.encode(gga1))
+end
+{"longitude":1143199103,"dgps_age":0,"altitude":86.4000015,"hdop":0.5940000,"height":-13.6999998,"fix_quality":1,"satellites_tracked":22,"latitude":347978178}
+模式2示例:
+json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位
+local gga2 = gnss.getGga(2)
+if gga2 then
+    log.info("GGA2", json.encode(gga2))
+end
+{"longitude":114.3199081,"dgps_age":0,"altitude":86.4000015,"hdop":0.5940000,"height":-13.6999998,"fix_quality":1,"satellites_tracked":22,"latitude":34.7978172}
+模式3示例:
+local gga3 = gnss.getGga(3)
+if gga3 then
+    log.info("GGA3", gga3)
+end
+$GNGGA,131241.000,3434.81372,N,11350.39930,E,1,05,4.924,165.5,M,-15.2,M,,*6D\r
+]]
+function gnss.getGga(lnglat_mode)
+    return  libgnss.getGga(lnglat_mode)
+end
+
+--[[
+获取GLL数据
+@api gnss.getGll(data_mode)
+@number 经纬度数据的格式, 0-ddmm.mmmmm格式, 1-DDDDDDDDD格式, 2-DD.DDDDDDD格式
+@return table GLL数据, 若如不存在会返回nil
+@usage
+local gll = gnss.getGll(2)
+if gll then
+    log.info("GLL", json.encode(gll, "11g"))
+end
+-- 实例数据,获取值的解释:
+-- {
+--     "status":"A",        // 定位状态, A有效, B无效
+--     "mode":"A",          // 定位模式, V无效, A单点解, D差分解
+--     "sec":20,            // 秒, UTC时间为准
+--     "min":23,            // 分钟, UTC时间为准
+--     "hour":7,            // 小时, UTC时间为准
+--     "longitude":113.231, // 经度, 正数为东经, 负数为西经
+--     "latitude":23.4067,  // 纬度, 正数为北纬, 负数为南纬
+--     "us":0               // 微妙数, 通常为0
+-- }
+--模式0示例:
+--json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位,本示例保留5位小数
+local gll = gnss.getGll(0)
+if gll then
+    log.info("GLL0", json.encode(gll, "5f"))
+end
+{"longitude":11419.19531,"sec":14,"min":32,"mode":"A","hour":6,"us":0,"status":"A","latitude":3447.86914}
+--模式1示例:
+--DDDDDDDDD格式是由DD.DDDDDDD*10000000转换而来,目的是作为整数,方便某些场景使用
+local gll1 = gnss.getGll(1)
+if gll1 then
+    log.info("GLL1", json.encode(gll1))
+end
+{"longitude":1143199103,"sec":14,"min":32,"mode":"A","hour":6,"us":0,"status":"A","latitude":347978178}
+模式2示例:
+--json.encode默认输出"7f"格式保留7位小数,可以根据自己需要的格式调整小数位
+local gll2 = gnss.getGll(2)
+if gll2 then
+    log.info("GLL2", json.encode(gll2))
+end
+{"longitude":114.3199081,"sec":14,"min":32,"mode":"A","hour":6,"us":0,"status":"A","latitude":34.7978172}
+]]
+function gnss.getGll(data_mode)
+    return  libgnss.getGll(data_mode)
+end
+--[[
+获取最后的经纬度数据
+@api gnss.getlastloc()
+@return table 经纬度数据,格式:ddmm.mmmmm0000,返回nil表示没有数据,此数据在定位成功,关闭gps时,会自动保存到文件系统中,定位成功之后每10分钟如果还处于定位成功状态会更新
+@usage
+local loc= gnss.getlastloc()
+if loc then
+    log.info("lastloc", loc.lat,loc.lng)
+end
+输出示例:
+3434.7937000000 11350.386720000
+]]
+function gnss.getlastloc()
+    local locStr = io.readFile("/hxxtloc")
+    if locStr then
+        local jdata = json.decode(locStr)
+        return jdata 
+    end
+end
+return gnss

+ 223 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/gnss/lbsLoc2.lua

@@ -0,0 +1,223 @@
+--[[
+@module lbsLoc2
+@summary 基站定位v2
+@version 1.0
+@date    2023.5.23
+@author  wendal
+@demo    lbsLoc2
+@usage
+-- 注意:
+-- 1. 因使用了sys.wait()所有api需要在协程中使用
+-- 2. 仅支持单基站定位, 即当前联网的基站
+-- 3. 本服务当前处于测试状态
+sys.taskInit(function()
+    sys.waitUntil("IP_READY", 30000)
+    -- mobile.reqCellInfo(60)
+    -- sys.wait(1000)
+    while mobile do -- 没有mobile库就没有基站定位
+        mobile.reqCellInfo(15)
+        sys.waitUntil("CELL_INFO_UPDATE", 3000)
+        local lat, lng, t = lbsLoc2.request(5000)
+        -- local lat, lng, t = lbsLoc2.request(5000, "bs.openluat.com")
+        log.info("lbsLoc2", lat, lng, (json.encode(t or {})))
+        sys.wait(60000)
+    end
+end)
+]]
+
+local sys = require "sys"
+
+local lbsLoc2 = {}
+
+local function numToBcdNum(inStr,destLen)
+    local l,t,num = string.len(inStr or ""),{}
+    destLen = destLen or (inStr:len()+1)/2
+    for i=1,l,2 do
+        num = tonumber(inStr:sub(i,i+1),16)
+        if i==l then
+            num = 0xf0+num
+        else
+            num = (num%0x10)*0x10 + (num-(num%0x10))/0x10
+        end
+        table.insert(t,num)
+    end
+
+    local s = string.char(unpack(t))
+
+    l = string.len(s)
+    if l < destLen then
+        s = s .. string.rep("\255",destLen-l)
+    elseif l > destLen then
+        s = string.sub(s,1,destLen)
+    end
+
+    return s
+end
+
+--- BCD编码格式字符串 转化为 号码ASCII字符串(仅支持数字)
+-- @string num 待转换字符串
+-- @return string data,转换后的字符串
+-- @usage
+local function bcdNumToNum(num)
+	local byte,v1,v2
+	local t = {}
+
+	for i=1,num:len() do
+		byte = num:byte(i)
+		v1,v2 = bit.band(byte,0x0f),bit.band(bit.rshift(byte,4),0x0f)
+
+		if v1 == 0x0f then break end
+		table.insert(t,v1)
+
+		if v2 == 0x0f then break end
+		table.insert(t,v2)
+	end
+
+	return table.concat(t)
+end
+
+lbsLoc2.imei = numToBcdNum(mobile.imei())
+
+local function enCellInfo(s)
+    -- 改造成单基站, 反正服务器也只认单基站
+    local v = s[1]
+    log.info("cell", json.encode(v))
+    local ret = pack.pack(">HHbbi",v.tac,v.mcc,v.mnc,31,v.cid)
+    return string.char(1)..ret
+end
+
+local function trans(str)
+    local s = str
+    if str:len()<10 then
+        s = str..string.rep("0",10-str:len())
+    end
+
+    return s:sub(1,3).."."..s:sub(4,10)
+end
+
+--[[
+执行定位请求
+@api lbsLoc2.request(timeout, host, port, reqTime)
+@number 请求超时时间,单位毫秒,默认15000
+@number 服务器地址,有默认值,可以是域名,一般不需要填
+@number 服务器端口,默认12411,一般不需要填
+@bool   是否要求返回服务器时间
+@return string  若成功,返回定位坐标的纬度,否则会返还nil
+@return string  若成功,返回定位坐标的经度,否则会返还nil
+@return table   服务器时间,东八区时间. 当reqTime为true且定位成功才会返回
+@usage
+-- 关于坐标系
+-- 部分情况下会返回GCJ02坐标系, 部分情况返回的是WGS84坐标
+-- 历史数据已经无法分辨具体坐标系
+-- 鉴于两种坐标系之间的误差并不大,小于基站定位本身的误差, 纠偏的意义不大
+sys.taskInit(function()
+    sys.waitUntil("IP_READY", 30000)
+    -- mobile.reqCellInfo(60)
+    -- sys.wait(1000)
+    while mobile do -- 没有mobile库就没有基站定位
+        mobile.reqCellInfo(15)
+        sys.waitUntil("CELL_INFO_UPDATE", 3000)
+        local lat, lng, t = lbsLoc2.request(5000)
+        -- local lat, lng, t = lbsLoc2.request(5000, "bs.openluat.com")
+        log.info("lbsLoc2", lat, lng, (json.encode(t or {})))
+        sys.wait(60000)
+    end
+end)
+]]
+function lbsLoc2.request(timeout, host, port, reqTime)
+    if mobile.status() == 0 then
+        return
+    end
+    local hosts = host and {host} or {"free.bs.air32.cn", "bs.openluat.com"}
+    port = port and tonumber(port) or 12411
+    local sc = socket.create(nil, function(sc, event)
+        -- log.info("lbsLoc", "event", event, socket.ON_LINE, socket.TX_OK, socket.EVENT)
+        if event == socket.ON_LINE then
+            --log.info("lbsLoc", "已连接")
+            sys.publish("LBS_CONACK")
+        elseif event == socket.TX_OK then
+            --log.info("lbsLoc", "发送完成")
+            sys.publish("LBS_TX")
+        elseif event == socket.EVENT then
+            --log.info("lbsLoc", "有数据来")
+            sys.publish("LBS_RX")
+        end
+    end)
+    if sc == nil then
+        return
+    end
+    -- socket.debug(sc, true)
+    socket.config(sc, nil, true)
+    local rxbuff = zbuff.create(64)
+    for k, rhost in pairs(hosts) do
+        local reqStr = string.char(0, (reqTime and 4 or 0) +8) .. lbsLoc2.imei
+        local tmp = nil
+        if mobile.scell then
+            local scell = mobile.scell()
+            if scell and scell.mcc then
+                -- log.debug("lbsLoc2", "使用当前驻网基站的信息")
+                tmp = pack.pack(">bHHbbi", 1, scell.tac, scell.mcc, scell.mnc, 31, scell.eci)
+            end
+        end
+        if tmp == nil then
+            local cells = mobile.getCellInfo()
+            if cells == nil or #cells == 0 then
+                socket.release(sc)
+                return
+            end
+            reqStr = reqStr .. enCellInfo(cells)
+        else
+            reqStr = reqStr .. tmp
+        end
+        -- log.debug("lbsLoc2", "待发送数据", (reqStr:toHex()))
+        log.debug("lbsLoc2", rhost, port)
+        if socket.connect(sc, rhost, port) and sys.waitUntil("LBS_CONACK", 1000) then
+            if socket.tx(sc, reqStr) and sys.waitUntil("LBS_TX", 1000) then
+                socket.wait(sc)
+                if sys.waitUntil("LBS_RX", timeout or 15000) then
+                    local succ, data_len = socket.rx(sc, rxbuff)
+                    -- log.debug("lbsLoc", "rx", succ, data_len)
+                    if succ and data_len > 0 then
+                        socket.close(sc)
+                        break
+                    else
+                        log.debug("lbsLoc", "rx数据失败", rhost)
+                    end
+                else
+                    log.debug("lbsLoc", "等待数据超时", rhost)
+                end
+            else
+                log.debug("lbsLoc", "tx调用失败或TX_ACK超时", rhost)
+            end
+        else
+            log.debug("lbsLoc", "connect调用失败或CONACK超时", rhost)
+        end
+        socket.close(sc)
+        --sys.wait(100)
+    end
+    sys.wait(100)
+    socket.release(sc)
+    if rxbuff:used() > 0 then
+        local resp = rxbuff:toStr(0, rxbuff:used())
+        log.debug("lbsLoc2", "rx", (resp:toHex()))
+        if resp:len() >= 11 and(resp:byte(1) == 0 or resp:byte(1) == 0xFF) then
+            local lat = trans(bcdNumToNum(resp:sub(2, 6)))
+            local lng = trans(bcdNumToNum(resp:sub(7, 11)))
+            local t = nil
+            if resp:len() >= 17 then
+                t = {
+                    year=resp:byte(12) + 2000,
+                    month=resp:byte(13),
+                    day=resp:byte(14),
+                    hour=resp:byte(15),
+                    min=resp:byte(16),
+                    sec=resp:byte(17),
+                }
+            end
+            return lat, lng, t
+        end
+    end
+    rxbuff:del()
+end
+
+return lbsLoc2

+ 47 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/gnss/lowpower.lua

@@ -0,0 +1,47 @@
+--[[
+@module  lowpower
+@summary gnss低功耗测试功能模块
+@version 1.0
+@date    2025.07.27
+@author  李源龙
+@usage
+使用Air780EGH核心板,外接GPS天线,起一个60s定位一次的定时器,唤醒模块60s一定位,然后定位成功获取到经纬度发送到服务器上面,然后进入休眠
+]]
+
+gnss=require("gnss")
+tcp=require("tcp")
+
+local function mode1_cb(tag)
+    log.info("TAGmode1_cb+++++++++",tag)
+    local  rmc=gnss.getRmc(0)
+    log.info("nmea", "rmc", json.encode(gnss.getRmc(0)))
+    tcp.latlngfnc(rmc.lat,rmc.lng)
+    pm.power(pm.WORK_MODE, 1)
+end
+local function timer1()
+    pm.power(pm.WORK_MODE, 0)
+    gnss.open(gnss.TIMERORSUC,{tag="MODE1",val=60,cb=mode1_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.setup(gnssotps)
+    sys.timerLoopStart(timer1,60000)
+    gnss.open(gnss.TIMER,{tag="MODE1",val=20,cb=mode1_cb})
+    -- gpio.close(23)--此脚为gnss备电脚,功能是热启动和保存星历文件,关掉会没有热启动,常开功耗会增高
+    -- gpio.close(33) -- 如果功耗偏高,开始尝试关闭WAKEUPPAD1
+    -- --关闭USB以后可以降低约150ua左右的功耗,如果不需要USB可以关闭
+    pm.power(pm.USB, false)
+
+end
+
+sys.taskInit(gnss_fnc)

+ 65 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/gnss/main.lua

@@ -0,0 +1,65 @@
+
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.07.27
+@author  李源龙
+@usage
+本demo演示的功能为:
+使用Air780GH核心板,通过gnss.lua扩展库,开启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)
+
+gnss=require("gnss")    
+
+
+normal=require("normal")
+-- lowpower=require("lowpower")
+-- psm=require("psm")
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 39 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/gnss/normal.lua

@@ -0,0 +1,39 @@
+--[[
+@module  normal
+@summary gnss正常测试功能模块
+@version 1.0
+@date    2025.07.27
+@author  李源龙
+@usage
+使用Air780EGH核心板,外接GPS天线,起一个60s定位一次的定时器,模块60s一定位,然后定位成功获取到经纬度发送到服务器上面
+]]
+gnss=require("gnss")
+tcp=require("tcp")
+
+local function mode1_cb(tag)
+    log.info("TAGmode1_cb+++++++++",tag)
+    local  rmc=gnss.getRmc(0)
+    log.info("nmea", "rmc", json.encode(gnss.getRmc(0)))
+    tcp.latlngfnc(rmc.lat,rmc.lng)
+end
+local function timer1()
+    gnss.open(gnss.TIMERORSUC,{tag="MODE1",val=60,cb=mode1_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.setup(gnssotps)
+    sys.timerLoopStart(timer1,60000)
+    -- gnss.open(gnss.TIMERORSUC,{tag="MODE1",val=60,cb=mode1_cb})
+end
+
+sys.taskInit(gnss_fnc)

+ 112 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/gnss/psm.lua

@@ -0,0 +1,112 @@
+--[[
+@module  psm
+@summary gnss使用psm测试功能模块
+@version 1.0
+@date    2025.07.27
+@author  李源龙
+@usage
+使用Air780EGH核心板,外接GPS天线,开启定位,获取到定位发送到服务器上面,然后启动一个60s的定时器唤醒PSM+模式
+模块开启定位,然后定位成功获取到经纬度发送到服务器上面,然后进入PSM+模式,等待唤醒
+]]
+gnss=require("gnss")
+pm.power(pm.WORK_MODE, 0) 
+local lat,lng
+
+
+local server_ip = "112.125.89.8" 
+local server_port = 47523 -- 换成自己的
+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
+    netc = socket.create(nil, d1Name)
+    socket.debug(netc, false)
+    socket.config(netc) 
+    local retry = 0
+    while retry < 3 do
+        log.info(rtos.meminfo("sys"))
+        result = libnet.waitLink(d1Name, 0, netc)
+        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
+        libnet.close(d1Name, 5000, netc)
+        retry = retry + 1
+        if needBreak then
+            break
+        end
+    end
+
+    uart.setup(1, 9600) -- 配置uart1,外部唤醒用
+    
+    -- 配置GPIO以达到最低功耗的目的
+	-- gpio.close(23) --此脚为gnss备电脚,功能是热启动和保存星历文件,关掉会没有热启动,常开功耗会增高
+
+    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 mode1_cb(tag)
+    log.info("TAGmode1_cb+++++++++",tag)
+    local  rmc=gnss.getRmc(0)
+    log.info("nmea", "rmc", json.encode(gnss.getRmc(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.setup(gnssotps)
+    gnss.open(gnss.TIMERORSUC,{tag="MODE1",val=60,cb=mode1_cb})
+end
+
+sys.taskInit(gnss_fnc)

+ 109 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/gnss/readme.md

@@ -0,0 +1,109 @@
+
+## 演示功能概述
+
+使用Air780GH核心板,通过gnss.lua扩展库,开启GNSS定位,展示模块的三种功耗模式:正常模式,低功耗模式,PSM+模式 
+
+## 演示硬件环境
+
+1、Air780EGH核心板一块
+
+2、TYPE-C USB数据线一根
+
+3、gnss天线一根
+
+4、Air780EGH核心板和数据线的硬件接线方式为
+
+- Air780EGH核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EGH V2010版本固件](https://docs.openluat.com/air780egh/luatos/firmware/version/)
+
+## 演示核心步骤
+
+1、搭建好硬件环境
+
+2、通过Luatools将demo与固件烧录到核心板中
+
+3、烧录好后,板子开机将会在Luatools上看到如下打印:
+
+(1) 开机连接到服务器:
+
+```lua
+[2025-07-27 15:51:28.235][000000003.099] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-07-27 15:51:28.287][000000003.100] D/socket connect to 112.125.89.8,45259
+[2025-07-27 15:51:28.338][000000003.100] network_socket_connect 1573:network 0 local port auto select 50641
+[2025-07-27 15:51:28.369][000000003.137] D/mobile TIME_SYNC 0
+[2025-07-27 15:51:28.419][000000003.151] network_default_socket_callback 1103:before process socket 0,event:0xf2000009(连接成功),state:3(正在连接),wait:2(等待连接完成)
+[2025-07-27 15:51:28.471][000000003.152] network_default_socket_callback 1107:after process socket 0,state:5(在线),wait:0(无等待)
+[2025-07-27 15:51:28.523][000000003.192] network_default_socket_callback 1103:before process socket 0,event:0xf2000004(发送成功),state:5(在线),wait:3(等待发送完成)
+[2025-07-27 15:51:28.564][000000003.192] network_default_socket_callback 1107:after process socket 0,state:5(在线),wait:0(无等待)
+[2025-07-27 15:51:28.615][000000003.194] I/user.3214816 114708 116960
+[2025-07-27 15:51:43.110][000000018.196] I/user.3214816 108516 116960
+[2025-07-27 15:51:58.123][000000033.198] I/user.3214816 108552 116960
+[2025-07-27 15:52:13.118][000000048.199] I/user.3214816 108632 116960
+
+```
+
+(2) 开启GNSS定位成功后发送数据到服务器:
+
+```lua
+[2025-07-27 15:52:25.226][000000060.305] I/user.gnss.open 2 MODE1 60 function: 0C7F0448
+[2025-07-27 15:52:25.260][000000060.306] Uart_ChangeBR 1338:uart2, 115200 115203 26000000 3611
+[2025-07-27 15:52:25.293][000000060.307] I/user.全卫星开启
+[2025-07-27 15:52:25.335][000000060.307] I/user.agps开启
+[2025-07-27 15:52:25.370][000000060.308] D/sntp query ntp.aliyun.com
+[2025-07-27 15:52:25.383][000000060.308] dns_run 674:ntp.aliyun.com state 0 id 1 ipv6 0 use dns server2, try 0
+[2025-07-27 15:52:25.392][000000060.313] I/user.gnss._open
+[2025-07-27 15:52:25.402][000000060.478] dns_run 691:dns all done ,now stop
+[2025-07-27 15:52:25.452][000000060.541] D/sntp Unix timestamp: 1753602745
+[2025-07-27 15:52:25.476][000000060.545] I/user.gnss.opts 开始执行AGPS
+[2025-07-27 15:52:25.505][000000060.549] I/user.os.time 1753602745
+[2025-07-27 15:52:25.526][000000060.549] I/user.agps_time 1753602626
+[2025-07-27 15:52:25.551][000000060.553] I/user.gnss.opts 星历不需要更新 119
+[2025-07-27 15:52:25.563][000000060.570] D/user.lbsLoc2 free.bs.air32.cn 12411
+[2025-07-27 15:52:25.583][000000060.571] D/socket connect to free.bs.air32.cn,12411
+[2025-07-27 15:52:25.596][000000060.571] dns_run 674:free.bs.air32.cn state 0 id 2 ipv6 0 use dns server2, try 0
+[2025-07-27 15:52:25.624][000000060.598] dns_run 691:dns all done ,now stop
+[2025-07-27 15:52:25.661][000000060.737] D/user.lbsLoc2 rx 0030540816031183837001
+[2025-07-27 15:52:25.681][000000060.740] I/user.lbsLoc2 34.580613000000 113.83807100000
+[2025-07-27 15:52:25.706][000000060.745] I/user.gnss.opts 写入星历数据 长度 5294
+[2025-07-27 15:52:25.743][000000060.746] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:25.764][000000060.846] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:25.859][000000060.947] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:25.971][000000061.048] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:26.071][000000061.149] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:26.166][000000061.250] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:26.225][000000061.313] I/user.gnss.timerFnc@1 2 MODE1 60 60 nil
+[2025-07-27 15:52:26.267][000000061.351] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:26.364][000000061.452] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:26.465][000000061.553] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:26.576][000000061.654] I/user.gnss AGNSS write >>> 512
+[2025-07-27 15:52:26.676][000000061.755] I/user.gnss AGNSS write >>> 174
+[2025-07-27 15:52:26.783][000000061.857] I/user.gnss.opts 写入GPS坐标 3434.8367800000 11350.284260000
+[2025-07-27 15:52:26.836][000000061.857] I/user.gnss.opts 参考时间 $AIDTIME,2025,7,27,7,52,26,000
+[2025-07-27 15:52:26.852][000000061.878] I/user.gnss.opts 写入AGPS参考位置 $AIDPOS,3434.8367800,N,11350.2842600,E,1.0
+
+[2025-07-27 15:52:27.233][000000062.314] I/user.gnss.timerFnc@1 2 MODE1 60 59 nil
+[2025-07-27 15:52:28.111][000000063.201] I/user.3214816 121176 121936
+[2025-07-27 15:52:28.228][000000063.315] I/user.gnss.timerFnc@1 2 MODE1 60 58 nil
+[2025-07-27 15:52:29.237][000000064.316] I/user.gnss.timerFnc@1 2 MODE1 60 57 nil
+[2025-07-27 15:52:30.236][000000065.317] I/user.gnss.timerFnc@1 2 MODE1 60 56 nil
+[2025-07-27 15:52:30.417][000000065.496] I/gnss Fixed 343481049 1135041395
+[2025-07-27 15:52:30.487][000000065.575] I/user.gnss.statInd@1 true 2 MODE1 60 55 nil function: 0C7F0448
+[2025-07-27 15:52:31.232][000000066.318] I/user.gnss.timerFnc@1 2 MODE1 60 55 1
+[2025-07-27 15:52:31.276][000000066.319] I/user.TAGmode1_cb+++++++++ MODE1
+[2025-07-27 15:52:31.319][000000066.320] I/user.nmea rmc {"variation":0,"lat":3434.8105469,"min":52,"valid":true,"day":27,"lng":11350.4140625,"speed":0.2220000,"year":2025,"month":7,"sec":30,"hour":7,"course":302.4410095}
+[2025-07-27 15:52:31.336][000000066.321] I/user.gnss.close 2 MODE1 60 function: 0C7F0448
+[2025-07-27 15:52:31.352][000000066.328] I/user.gnss._close
+[2025-07-27 15:52:31.373][000000066.330] I/user.发送到服务器数据,长度 38
+[2025-07-27 15:52:31.394][000000066.456] network_default_socket_callback 1103:before process socket 0,event:0xf2000004(发送成功),state:5(在线),wait:3(等待发送完成)
+[2025-07-27 15:52:31.412][000000066.456] network_default_socket_callback 1107:after process socket 0,state:5(在线),wait:0(无等待)
+[2025-07-27 15:52:31.427][000000066.457] I/user.3214816 119380 122432
+[2025-07-27 15:52:46.373][000000081.459] I/user.3214816 113240 122432
+```

+ 151 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/gnss/tcp.lua

@@ -0,0 +1,151 @@
+--[[
+@module  tcp
+@summary gnss使用psm测试功能模块
+@version 1.0
+@date    2025.07.27
+@author  李源龙
+@usage
+使用Air780EGH核心板,连接TCP服务器,实现TCP通信功能
+]]
+local tcp = {}
+local taskName = "TCP_TASK"             -- sysplus库用到的任务名称,也作为任务id
+
+local uartid = 1 -- 根据实际设备选取不同的uartid
+local uart_rx_buff = zbuff.create(1024)     -- 串口接收到的数据
+local libnet = require "libnet"         -- libnet库,支持tcp、udp协议所用的同步阻塞接口
+local ip = "112.125.89.8"               -- 连接tcp服务器的ip地址
+local port = 44473                 -- 连接tcp服务器的端口
+local connect_state = false             -- 连接状态 true:已连接   false:未连接
+local protocol = false                  -- 通讯协议 true:UDP协议  false:TCP协议
+local ssl = false                       -- 加密传输 true:加密     false:不加密
+local tx_buff = zbuff.create(1024)      -- 发送至tcp服务器的数据
+local rx_buff = zbuff.create(1024)      -- 从tcp服务器接收到的数据
+
+--初始化
+uart.setup(
+    uartid,--串口id
+    9600,--波特率
+    8,--数据位
+    1--停止位
+)
+
+-- 处理未识别的消息
+local function tcp_client_main_cbfunc(msg)
+	log.info("tcp_client_main_cbfunc", msg[1], msg[2], msg[3], msg[4])
+end
+
+--
+function tcp.latlngfnc(lat,lng)
+    tx_buff:write(string.format('{"lat":%5f,"lng":%5f}', lat, lng))
+    if connect_state then
+        sys_send(taskName, socket.EVENT, 0)
+     end
+end
+
+function TCP_TASK()
+    -- 打印一下连接的目标ip和端口号
+    log.info("connect ip: ", ip, "port:", port)
+
+    while not socket.adapter() do
+        log.warn("gnss_agps", "wait IP_READY")
+        -- 在此处阻塞等待WIFI连接成功的消息"IP_READY"
+        -- 或者等待30秒超时退出阻塞等待状态
+        local result=sys.waitUntil("IP_READY", 30000)
+        if result == false then
+            log.warn("gnss_agps", "wait IP_READY timeout")
+            return
+        end
+    end
+
+    local socket_client
+    while true do
+        socket_client = socket.create(nil, taskName)     -- 创建socket对象
+        socket.debug(socket_client, true)                      -- 打开调试日志
+        socket.config(socket_client, nil, protocol, ssl)       -- 此配置为TCP连接,无SSL加密
+        -- 连接服务器,返回是否连接成功
+        result = libnet.connect(taskName, 15000, socket_client, ip, port)
+
+        -- 收取数据会触发回调, 这里的"receive" 是固定值不要修改。
+        uart.on(uartid, "receive", function(id, len)
+            while true do
+              local len = uart.rx(id, uart_rx_buff)   -- 接收串口收到的数据,并赋值到uart_rx_buff
+               if len <= 0 then    -- 接收到的字节长度为0 则退出
+                   break
+                end
+                -- 如果已经在线了,则发送socket.EVENT消息来打断任务里的阻塞等待状态,让任务循环继续
+                if connect_state then
+                   sys_send(taskName, socket.EVENT, 0)
+                end
+                break
+            end
+        end)
+
+        -- 如果连接成功,则改变连接状态参数,并且随便发一条数据到服务器,看服务器能不能收到
+        if result then
+            connect_state = true
+            libnet.tx(taskName, 0, socket_client, "TCP  CONNECT")
+        end
+
+        -- 连接上服务器后,等待处理接收服务器下行至模块的数据 和 发送串口的数据到服务器
+        while result do
+            succ, param, _, _ = socket.rx(socket_client, rx_buff)   -- 接收数据
+            if not succ then
+                log.info("服务器断开了", succ, param, ip, port)
+                break
+            end
+
+            if rx_buff:used() > 0 then
+                log.info("收到服务器数据,长度", rx_buff:used())
+
+                uart.tx(uartid, rx_buff)    -- 从服务器收到的数据转发 从串口输出
+                rx_buff:del()
+            end
+
+            tx_buff:copy(nil, uart_rx_buff)         -- 将串口数据赋值给tcp待发送数据的buff中
+            uart_rx_buff:del()                      -- 清除串口buff的数据长度
+            if tx_buff:used() > 0 then
+                log.info("发送到服务器数据,长度", tx_buff:used())
+                local result = libnet.tx(taskName, 0, socket_client, tx_buff)   -- 发送数据
+                if not result then
+                    log.info("发送失败了", result, param)
+                    break
+                end
+            end
+            tx_buff:del()
+
+            -- 如果zbuff对象长度超出,需要重新分配下空间
+            if uart_rx_buff:len() > 1024 then
+                uart_rx_buff:resize(1024)
+            end
+            if tx_buff:len() > 1024 then
+                tx_buff:resize(1024)
+            end
+            if rx_buff:len() > 1024 then
+                rx_buff:resize(1024)
+            end
+            log.info(rtos.meminfo("sys"))   -- 打印系统内存
+
+            -- 阻塞等待新的消息到来,比如服务器下发,串口接收到数据
+            result, param = libnet.wait(taskName, 15000, socket_client)
+            if not result then
+                log.info("服务器断开了", result, param)
+                break
+            end
+        end
+
+        -- 服务器断开后的行动,由于while true的影响,所以会再次重新执行进行 重新连接。
+        connect_state = false
+        libnet.close(d1Name, 5000, socket_client)
+        socket.release(socket_client)
+        tx_buff:clear(0)
+        rx_buff:clear(0)
+        socket_client=nil
+        sys.wait(1000)
+    end
+
+end
+
+-- libnet库依赖于sysplus,所以只能通过sysplus.taskInitEx创建的任务函数中运行
+sysplus.taskInitEx(TCP_TASK, taskName, tcp_client_main_cbfunc)
+
+return tcp