Browse Source

:sparkles: 更换基站定位方式, 删除开机先基站定位

Mizore 2 years ago
parent
commit
8e09b93ad8
3 changed files with 165 additions and 404 deletions
  1. 0 356
      script/lbsLoc.lua
  2. 4 13
      script/main.lua
  3. 161 35
      script/util_location.lua

+ 0 - 356
script/lbsLoc.lua

@@ -1,356 +0,0 @@
---[[
-@module lbsLoc
-@summary lbsLoc 发送基站定位请求
-@version 1.0
-@date    2022.12.16
-@author  luatos
-@usage
---注意:因使用了sys.wait()所有api需要在协程中使用
---用法实例
-PRODUCT_KEY = "VmhtOb81EgZau6YyuuZJzwF6oUNGCbXi"
-local lbsLoc = require("lbsLoc")
-local function reqLbsLoc()
-    lbsLoc.request(getLocCb)
-end
--- 功能:获取基站对应的经纬度后的回调函数
--- 参数:-- result:number类型,0表示成功,1表示网络环境尚未就绪,2表示连接服务器失败,3表示发送数据失败,4表示接收服务器应答超时,5表示服务器返回查询失败;为0时,后面的5个参数才有意义
-		-- lat:string类型,纬度,整数部分3位,小数部分7位,例如031.2425864
-		-- lng:string类型,经度,整数部分3位,小数部分7位,例如121.4736522
-        -- addr:目前无意义
-        -- time:string类型或者nil,服务器返回的时间,6个字节,年月日时分秒,需要转为十六进制读取
-            -- 第一个字节:年减去2000,例如2017年,则为0x11
-            -- 第二个字节:月,例如7月则为0x07,12月则为0x0C
-            -- 第三个字节:日,例如11日则为0x0B
-            -- 第四个字节:时,例如18时则为0x12
-            -- 第五个字节:分,例如59分则为0x3B
-            -- 第六个字节:秒,例如48秒则为0x30
-        -- locType:numble类型或者nil,定位类型,0表示基站定位成功,255表示WIFI定位成功
-function getLocCb(result, lat, lng, addr, time, locType)
-    log.info("testLbsLoc.getLocCb", result, lat, lng)
-    -- 获取经纬度成功
-    if result == 0 then
-        log.info("服务器返回的时间", time:toHex())
-        log.info("定位类型,基站定位成功返回0", locType)
-    end
-    sys.timerStart(lbsLoc,20000)
-end
-reqLbsLoc()
-]]
-
-local lbsLoc = {}
-local d1Name = "D1_TASKL"
---- 阻塞等待网卡的网络连接上,只能用于任务函数中
--- @string 任务标志
--- @int 超时时间,如果==0或者空,则没有超时一致等待
--- @... 其他参数和socket.linkup一致
--- @return 失败或者超时返回false 成功返回true
-local function waitLink(taskName, timeout, ...)
-	local is_err, result = socket.linkup(...)
-	if is_err then
-		return false
-	end
-	if not result then
-		result = sys_wait(taskName, socket.LINK, timeout)
-	else
-		return true
-	end
-	if type(result) == 'table' and result[2] == 0 then
-		return true
-	else
-		return false
-	end
-end
-
---- 阻塞等待IP或者域名连接上,如果加密连接还要等握手完成,只能用于任务函数中
--- @string 任务标志
--- @int 超时时间,如果==0或者空,则没有超时一致等待
--- @... 其他参数和socket.connect一致
--- @return 失败或者超时返回false 成功返回true
-local function connect(taskName,timeout, ... )
-	local is_err, result = socket.connect(...)
-	if is_err then
-		return false
-	end
-	if not result then
-		result = sys_wait(taskName, socket.ON_LINE, timeout)
-	else
-		return true
-	end
-	if type(result) == 'table' and result[2] == 0 then
-		return true
-	else
-		return false
-	end
-end
-
---- 阻塞等待数据发送完成,只能用于任务函数中
--- @string 任务标志
--- @int 超时时间,如果==0或者空,则没有超时一致等待
--- @... 其他参数和socket.tx一致
--- @return
--- @boolean 失败或者超时返回false,缓冲区满了或者成功返回true
--- @boolean 缓存区是否满了
-local function tx(taskName,timeout, ...)
-	local is_err, is_full, result = socket.tx(...)
-	if is_err then
-		return false, is_full
-	end
-	if is_full then
-		return true, true
-	end
-	if not result then
-		result = sys_wait(taskName, socket.TX_OK, timeout)
-	else
-		return true, is_full
-	end
-	if type(result) == 'table' and result[2] == 0 then
-		return true, false
-	else
-		return false, is_full
-	end
-end
-
---- 阻塞等待新的网络事件或者特定事件,只能用于任务函数中
--- @string 任务标志
--- @int 超时时间,如果==0或者空,则没有超时一致等待
--- @... 其他参数和socket.wait一致
--- @return 
--- @boolean 网络异常返回false,其他返回true
--- @table or boolean 超时返回false,有新的数据到返回true,被其他事件退出的,返回接收到的事件
-local function wait(taskName,timeout, netc)
-	local is_err, result = socket.wait(netc)
-	if is_err then
-		return false,false
-	end
-	if not result then
-		result = sys_wait(taskName, socket.EVENT, timeout)
-	else
-		return true,true
-	end
-	if type(result) == 'table' then
-		if result[2] == 0 then
-			return true, true
-		else
-			return false, false
-		end
-	else
-		return true, false
-	end
-end
-
---- ASCII字符串 转化为 BCD编码格式字符串(仅支持数字)
--- @string inStr 待转换字符串
--- @number destLen 转换后的字符串期望长度,如果实际不足,则填充F
--- @return string data,转换后的字符串
--- @usage
-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
-
-
-local function netCB(msg)
-	log.info("未处理消息", msg[1], msg[2], msg[3], msg[4])
-end
-
-
-local function enCellInfo(s)
-    local ret,t,mcc,mnc,lac,ci,rssi,k,v,m,n,cntrssi = "",{}
-        for k,v in pairs(s) do
-            mcc,mnc,lac,ci,rssi = v.mcc,v.mnc,v.tac,v.cid,((v.rsrq + 144) >31) and 31 or (v.rsrq + 144)
-            local handle = nil
-            for k,v in pairs(t) do
-                if v.lac == lac and v.mcc == mcc and v.mnc == mnc then
-                    if #v.rssici < 8 then
-                        table.insert(v.rssici,{rssi=rssi,ci=ci})
-                    end
-                    handle = true
-                break
-                end
-            end
-            if not handle then
-                table.insert(t,{mcc=mcc,mnc=mnc,lac=lac,rssici={{rssi=rssi,ci=ci}}})
-            end
-            log.info("rssi、mcc、mnc、lac、ci", rssi,mcc,mnc,lac,ci)
-        end
-        for k,v in pairs(t) do
-            ret = ret .. pack.pack(">HHb",v.lac,v.mcc,v.mnc)
-            for m,n in pairs(v.rssici) do
-                cntrssi = bit.bor(bit.lshift(((m == 1) and (#v.rssici-1) or 0),5),n.rssi)
-                ret = ret .. pack.pack(">bi",cntrssi,n.ci)
-            end
-        end
-        return string.char(#t)..ret
-end
-
-local function enWifiInfo(tWifi)
-    local ret,cnt,k,v = "",0
-    if tWifi then
-        for k,v in pairs(tWifi) do
-            log.info("lbsLoc.enWifiInfo",k,v)
-            ret = ret..pack.pack("Ab",(k:gsub(":","")):fromHex(),(v<0) and (v+255) or v)
-            cnt = cnt+1
-        end
-    end
-    return string.char(cnt)..ret
-end
-
-local function enMuid()   --获取模块MUID
-    local muid = mobile.muid()
-    return string.char(muid:len())..muid
-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
-
-
-local function taskClient(cbFnc, reqAddr, timeout, productKey, host, port,reqTime, reqWifi)
-    while mobile.status() == 0 do
-        if not sys.waitUntil("IP_READY", timeout) then return cbFnc(1) end
-    end
-    local retryCnt  = 0
-    local reqStr = pack.pack("bAbAAAAA", productKey:len(), productKey,
-                             (reqAddr and 2 or 0) + (reqTime and 4 or 0) + 8 +(reqWifi and 16 or 0) + 32, "",
-                             numToBcdNum(mobile.imei()), enMuid(),
-                             enCellInfo(mobile.getCellInfo()),
-                             enWifiInfo(reqWifi))
-    log.info("reqStr", reqStr:toHex())
-    local rx_buff = zbuff.create(17)
-    -- sys.wait(5000)
-    while true do
-        netc = socket.create(nil, d1Name) -- 创建socket对象
-        if not netc then cbFnc(6) return end -- 创建socket失败
-        socket.debug(netc, false)
-        socket.config(netc, nil, true, nil)
-        waitLink(d1Name, 0, netc)
-        local result = connect(d1Name, 15000, netc, host, port)
-        if result then
-            while true do
-                log.info(" lbsloc socket_service connect true")
-                local result, _ = tx(d1Name, 0, netc, reqStr) ---发送数据
-                if result then
-                    wait(d1Name, timeout - 100, netc)
-                    local is_err, param, _, _ = socket.rx(netc, rx_buff) -- 接收数据
-                    log.info("是否接收和数据长度", not is_err, param)
-                    if not is_err then -- 如果接收成功
-                        socket.close(netc) -- 关闭连接
-                        socket.release(netc)
-                        local read_buff = rx_buff:toStr(0, param)
-                        rx_buff:clear()
-                        log.info("lbsLoc receive", read_buff:toHex())
-                        if read_buff:len() >= 11 and(read_buff:byte(1) == 0 or read_buff:byte(1) == 0xFF) then
-                            local locType = read_buff:byte(1)
-                            cbFnc(0, trans(bcdNumToNum(read_buff:sub(2, 6))),
-                                trans(bcdNumToNum(read_buff:sub(7, 11))), reqAddr and
-                                read_buff:sub(13, 12 + read_buff:byte(12)) or nil,
-                                read_buff:sub(reqAddr and (13 + read_buff:byte(12)) or 12, -1),
-                                locType)
-                        else
-                            log.warn("lbsLoc.query", "根据基站查询经纬度失败")
-                            if read_buff:byte(1) == 2 then
-                                log.warn("lbsLoc.query","main.lua中的PRODUCT_KEY和此设备在iot.openluat.com中所属项目的ProductKey必须一致,请去检查")
-                            else
-                                log.warn("lbsLoc.query","基站数据库查询不到所有小区的位置信息")
-                                log.warn("lbsLoc.query","在trace中向上搜索encellinfo,然后在电脑浏览器中打开http://bs.openluat.com/,手动查找encellinfo后的所有小区位置")
-                                log.warn("lbsLoc.query","如果手动可以查到位置,则服务器存在BUG,直接向技术人员反映问题")
-                                log.warn("lbsLoc.query","如果手动无法查到位置,则基站数据库还没有收录当前设备的小区位置信息,向技术人员反馈,我们会尽快收录")
-                            end
-                            cbFnc(5)
-                        end
-                        return
-                    else
-                        socket.close(netc)
-                        socket.release(netc)
-                        retryCnt = retryCnt+1
-                        if retryCnt>=3 then return cbFnc(4) end
-                        break
-                    end
-                else
-                    socket.close(netc)
-                    socket.release(netc)
-                    retryCnt = retryCnt+1
-                    if retryCnt>=3 then return cbFnc(3) end
-                    break
-                end
-            end
-        else
-            socket.close(netc)
-            socket.release(netc)
-            retryCnt = retryCnt + 1
-            if retryCnt >= 3 then return cbFnc(2) end
-        end
-    end
-end
-
-
---[[
-发送基站/WIFI定位请求(仅支持中国区域的位置查询)
-@api lbsLoc.request(cbFnc,reqAddr,timeout,productKey,host,port,reqTime,reqWifi)
-@function cbFnc 用户回调函数,回调函数的调用形式为:cbFnc(result,lat,lng,addr,time,locType)
-@bool reqAddr 是否请求服务器返回具体的位置字符串信息,目前此功能不完善,参数可以传nil
-@number timeout 请求超时时间,单位毫秒,默认20000毫秒
-@string productKey IOT网站上的产品证书,如果在main.lua中定义了PRODUCT_KEY变量,则此参数可以传nil
-@string host 服务器域名,此参数可选,目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil
-@string port 服务器端口,此参数可选,目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil
-@bool reqTime 是否需要服务器返回时间信息,true返回,false或者nil不返回,此参数可选,目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil
-@table reqWifi 搜索到的WIFI热点信息(MAC地址和信号强度),如果传入了此参数,后台会查询WIFI热点对应的经纬度,此参数格式如下:
-{["1a:fe:34:9e:a1:77"] = -63,["8c:be:be:2d:cd:e9"] = -81,["20:4e:7f:82:c2:c4"] = -70,}
-@return nil
-]]
-function lbsLoc.request(cbFnc,reqAddr,timeout,productKey,host,port,reqTime,reqWifi)
-    sysplus.taskInitEx(taskClient, d1Name, netCB, cbFnc,reqAddr or nil,timeout or 20000,productKey or _G.PRODUCT_KEY,host or "bs.openluat.com",port or "12411",reqTime,reqWifi)
-end
-
-return lbsLoc

+ 4 - 13
script/main.lua

@@ -20,8 +20,8 @@ end
 socket.setDNS(nil, 1, "119.29.29.29")
 socket.setDNS(nil, 2, "223.5.5.5")
 
--- 设置 SIM 自动恢复, 搜索小区信息间隔, 最大搜索时间
-mobile.setAuto(1000 * 10, 1000 * 60, 1000 * 5)
+-- 设置 SIM 自动恢复(单位: 毫秒), 搜索小区信息间隔(单位: 毫秒), 最大搜索时间(单位: 秒)
+mobile.setAuto(1000 * 10)
 
 -- POWERKEY
 local button_last_press_time, button_last_release_time = 0, 0
@@ -78,15 +78,6 @@ sys.taskInit(
 
         util_netled.init()
 
-        -- 开机基站定位
-        util_location.getCoord(
-            function()
-                log.info("publish", "COORD_INIT_DONE")
-                sys.publish("COORD_INIT_DONE")
-            end
-        )
-        sys.waitUntil("COORD_INIT_DONE", 1000 * 20)
-
         -- 开机通知
         if config.BOOT_NOTIFY then
             util_notify.send("#BOOT")
@@ -98,8 +89,8 @@ sys.taskInit(
         end
 
         -- 定时基站定位
-        if config.LOCATION_INTERVAL and config.LOCATION_INTERVAL >= 1000 * 10 then
-            sys.timerLoopStart(util_location.getCoord, config.LOCATION_INTERVAL)
+        if config.LOCATION_INTERVAL and config.LOCATION_INTERVAL >= 1000 * 30 then
+            sys.timerLoopStart(util_location.refresh, config.LOCATION_INTERVAL, 30)
         end
 
         -- 电源键短按发送测试通知

+ 161 - 35
script/util_location.lua

@@ -1,50 +1,176 @@
-local lbsLoc = require "lbsLoc"
-
 local util_location = {}
 
-local last_lat, last_lng = 0, 0
-local last_time = 0
+-- 基站定位接口类型, 支持 openluat 和 cellocation
+local api_type = "openluat"
+
+local cache = {
+    cell_info_raw = {},
+    cell_info_formatted = "",
+    lbs_data = {
+        lat = 0,
+        lng = 0
+    }
+}
+
+--- 格式化经纬度 (保留小数点后 6 位, 去除末尾的 0)
+-- @param value 经纬度
+-- @return 格式化后的经纬度
+local function formatCoord(value)
+    local str = string.format("%.6f", tonumber(value) or 0)
+    str = str:gsub("%.?0+$", "")
+    return tonumber(str)
+end
+
+--- 生成地图链接
+-- @param lat 纬度
+-- @param lng 经度
+-- @return 地图链接 or ""
+local function getMapLink(lat, lng)
+    lat, lng = lat or 0, lng or 0
+    local map_link = ""
+    if lat ~= 0 and lng ~= 0 then
+        map_link = "http://apis.map.qq.com/uri/v1/marker?coord_type=1&marker=title:+;coord:" .. lat .. "," .. lng
+    end
+    log.debug("util_location.getMapLink", map_link)
+    return map_link
+end
+
+--- 格式化基站信息
+-- @param cell_info_raw 基站信息
+-- @return 格式化后的基站信息
+local function formatCellInfo(cell_info_raw)
+    if api_type == "openluat" then
+        local cell_info_arr = {}
+        for i, v in ipairs(cell_info_raw) do
+            table.insert(cell_info_arr, {mcc = v.mcc, mnc = v.mnc, lac = v.tac, ci = v.cid, rxlevel = v.rsrp, hex = 10})
+        end
+        local cell_info_json = json.encode(cell_info_arr)
+        log.debug("util_location.formatCellInfo", api_type .. ":", cell_info_json)
+        return cell_info_json
+    end
+
+    if api_type == "cellocation" then
+        local str = ""
+        for i, v in ipairs(cell_info_raw) do
+            str = str .. (i == 1 and "" or ";")
+            str = str .. v.mcc .. "," .. v.mnc .. "," .. v.tac .. "," .. v.cid .. "," .. v.rsrp
+        end
+        log.debug("util_location.formatCellInfo", api_type .. ":", str)
+        return str
+    end
+end
+
+--- 获取基站信息
+-- @return 基站信息 or ""
+local function getCellInfo()
+    local cell_info_formatted = formatCellInfo(mobile.getCellInfo())
+    cache.cell_info_formatted = cell_info_formatted
+    return cell_info_formatted
+end
+
+--- 刷新基站信息
+-- @param timeout 超时时间(单位: 秒)
+function util_location.refreshCellInfo(timeout)
+    log.info("util_location.refreshCellInfo", "start")
+    if cache.is_req_cell_info_running then
+        log.info("util_location.refreshCellInfo", "running, wait...")
+    else
+        cache.is_req_cell_info_running = true
+        mobile.reqCellInfo(timeout or 30) -- 单位: 秒
+    end
+    sys.waitUntil("CELL_INFO_UPDATE")
+    cache.is_req_cell_info_running = false
+    log.info("util_location.refreshCellInfo", "end")
+end
+
+--- 刷新基站定位信息
+-- @param timeout 超时时间(单位: 秒)
+-- @return 刷新成功返回 true
+function util_location.refresh(timeout, is_refresh_cell_info_disabled)
+    timeout = type(timeout) == "number" and timeout * 1000 or nil
 
--- 获取坐标
-function util_location.getCoord(callback, type, wifi, timeout)
-    local is_callback = callback ~= nil
-    if callback == nil then
-        callback = function()
+    local openluat = function(cell_info_formatted)
+        local lbs_api = "http://bs.openluat.com/get_gpss"
+        local header = {
+            ["Content-Type"] = "application/x-www-form-urlencoded"
+        }
+        local body = "data=" .. cell_info_formatted
+        local code, headers, body = util_http.fetch(timeout, "POST", lbs_api, header, body)
+        log.info("util_location.refresh", api_type .. ":", "code:", code, "body:", body)
+
+        if code ~= 200 or body == nil or body == "" then
+            return
+        end
+
+        local lbs_data = json.decode(body) or {}
+        local status, lat, lng = lbs_data.status, lbs_data.lat, lbs_data.lng
+
+        if status ~= 0 or lat == nil or lng == nil or lat == "" or lng == "" then
+            return
+        end
+
+        return lat, lng
+    end
+
+    local cellocation = function(cell_info_formatted)
+        local lbs_api = "http://api.cellocation.com:83/loc/?output=json&cl=" .. cell_info_formatted
+        local code, headers, body = util_http.fetch(timeout, "GET", lbs_api)
+        log.info("util_location.refresh", api_type .. ":", "code:", code, "body:", body)
+
+        if code ~= 200 or body == nil or body == "" then
+            return
+        end
+
+        local lbs_data = json.decode(body) or {}
+        local errcode, lat, lng = lbs_data.errcode, lbs_data.lat, lbs_data.lon
+        if errcode ~= 0 or lat == nil or lng == nil or lat == "0.0" or lng == "0.0" then
+            return
         end
+
+        return lat, lng
     end
 
     sys.taskInit(
         function()
-            local current_time = os.time()
-            if not is_callback then
-                if current_time - last_time < 30 then
-                    log.info("util_location.getCoord", "距离上次定位时间太短", current_time - last_time)
-                    return
-                end
-                sys.wait(2000)
+            if not is_refresh_cell_info_disabled then
+                util_location.refreshCellInfo(timeout)
+            end
+            local old_cell_info_formatted = cache.cell_info_formatted
+            local cell_info_formatted = getCellInfo()
+
+            if cell_info_formatted == old_cell_info_formatted then
+                log.info("util_location.refresh", api_type .. ":", "cell_info 无变化, 不重新请求")
+                return
+            end
+
+            local lat, lng
+            if api_type == "openluat" then
+                lat, lng = openluat(cell_info_formatted)
+            elseif api_type == "cellocation" then
+                lat, lng = cellocation(cell_info_formatted)
+            end
+            if lat and lng then
+                cache.lbs_data = {lat, lng}
             end
-            last_time = current_time
-            lbsLoc.request(
-                function(result, lat, lng, addr, time, locType)
-                    log.info("util_location.getCoord", result, lat, lng, locType)
-                    if result == 0 and lat and lng then
-                        last_lat, last_lng = lat, lng
-                        return callback(lat, lng)
-                    end
-                    return callback(last_lat, last_lng)
-                end,
-                nil,
-                timeout,
-                "v32xEAKsGTIEQxtqgwCldp5aPlcnPs3K",
-                nil,
-                nil,
-                nil,
-                wifi
-            )
         end
     )
+end
 
-    return last_lat, last_lng
+--- 获取位置信息
+-- @return lat
+-- @return lng
+-- @return map_link
+function util_location.get()
+    local lat, lng = unpack(cache.lbs_data)
+    lat, lng = formatCoord(lat), formatCoord(lng)
+    return lat, lng, getMapLink(lat, lng)
 end
 
+sys.subscribe(
+    "CELL_INFO_UPDATE",
+    function()
+        log.debug("EVENT.CELL_INFO_UPDATE")
+    end
+)
+
 return util_location