--- 模块功能:GPS TRACKERE 主逻辑 -- @author openLuat -- @module default -- @license MIT -- @copyright openLuat -- @release 2018.03.27 require "net" require "misc" require "mqtt" require "gpsv2" require "utils" require "update" require "funlib" require "pins" require"nvm" module(..., package.seeall) -- 无网络重启时间,飞行模式启动时间 local rstTim, flyTim = 600000, 300000 -- 解除报警的等待时间秒,GPS打开的起始时间utc秒 local clearTime, startTime = 300, 0 -- 轨迹消息缓冲区 local trackFile = {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}} -- 传感器数据 local sens = { vib = false, -- 震动检测 acc = false, -- 开锁检测 act = false, -- 启动检测 chg = false, -- 充电检测 und = false, -- 剪线检测 wup = false, -- 唤醒检测 vcc = 0, -- 电池电压 } local frameSn = 0 --是否发送报警位置 local isSendAlarm = false -- 配置文件 local dtu = { host = "", -- 自定义参数服务器 passon = 0, --透传标志位 plate = 0, --识别码标志位 convert = 0, --hex转换标志位 reg = 0, -- 登陆注册包 param_ver = 0, -- 参数版本 flow = 0, -- 流量监控 fota = 0, -- 远程升级 uartReadTime = 500, -- 串口读超时 netReadTime = 50, -- 网络读超时 pwrmod = "normal", password = "", upprot = {}, -- 上行自定义协议 dwprot = {}, -- 下行自定义协议 apn = {nil, nil, nil}, -- 用户自定义APN cmds = {{}, {}}, -- 自动采集任务参数 pins = {"", "", ""}, -- 用户自定义IO: netled,netready,rstcnf, conf = {{ -- 数组下标代表通道ID号 "tcp", -- 协议 TCP | UDP | MQTT | "ping", -- 心跳包内容 "180", -- 心跳包间隔 "116.62.220.88", -- HOST 地址 "21890", -- HOST 端口 1, -- 通道透传捆绑的串口ID "", -- 保持为"" "", -- 保持为"" "", -- 自动采集任务间隔 "" -- SSL 标志位 可选"ssl" }, {}, {}, {}, {}, {}, {}}, -- 用户通道参数 preset = {number = "", delay = 1, smsword = "SMS_UPDATE"}, -- 用户预定义的来电电话,延时时间,短信关键字 uconf = { {1, 115200, 8, uart.PAR_NONE, uart.STOP_1}, {2, 115200, 8, uart.PAR_NONE, uart.STOP_1}, {3, 115200, 8, uart.PAR_NONE, uart.STOP_1}, }, -- 串口配置表 gps = { fun = {"1", "9600", "0", "1", "1", "json", "0", ";", "60"}, -- 用户捆绑GPS的串口,波特率,功耗模式,采集间隔,采集方式支持触发和持续, 报文数据格式支持 json 和 hex,缓冲条数,分隔符,状态报文间隔 pio = {"", "", "", "", "0", "16"}, -- 配置GPS用到的IO: led脚,vib震动输入脚,ACC输入脚,内置电池充电状态监视脚,adc通道,分压比 }, warn = { gpio = {}, adc0 = {}, adc1 = {}, vbatt = {} }, task = {}, -- 用户自定义任务列表 } --停止时间阀值 local stopTimeThreshold = 10 --定位上报间隔 local locateReportInterval = 30 ----------------------------------------------------------传感器部分---------------------------------------------------------- -- 配置GPS用到的IO: led脚,vib震动输入脚,ACC输入脚,内置电池充电状态监视脚,adc通道,分压比 function sensMonitor(ledio, vibio, accio, chgio, adcid, ratio) -- 点火监测采样队列 local powerVolt, adcQue, acc, chg = 0, {0, 0, 0, 0, 0} -- GPS 定位成功指示灯 if ledio and default.pios[ledio] then default.pios[ledio] = nil local led = pins.setup(tonumber(ledio:sub(4, -1)), 0) sys.subscribe("GPS_MSG_REPORT", led) end -- 震动传感器检测 if vibio and default.pios[vibio] then pins.setup(tonumber(vibio:sub(4, -1)), function(msg) if msg == cpu.INT_GPIO_NEGEDGE then sens.vib = true end end, pio.PULLUP) default.pios[vibio] = nil end -- ACC开锁检测 if accio and default.pios[accio] then acc = pins.setup(tonumber(accio:sub(4, -1)), nil, pio.PULLUP) default.pios[accio] = nil end -- 内置锂电池充电状态监控脚 if chgio and default.pios[chgio] then chg = pins.setup(tonumber(chgio:sub(4, -1)), nil, pio.PULLUP) default.pios[chgio] = nil end adc.open(tonumber(adcid) or 0) while true do local adcValue, voltValue = adc.read(tonumber(adcid) or 0) if adcValue ~= 0xFFFF or voltValue ~= 0xFFFF then voltValue = voltValue * (tonumber(ratio)) / 3 -- 点火检测部分 powerVolt = (adcQue[1] + adcQue[2] + adcQue[3] + adcQue[4] + adcQue[5]) / 5 table.remove(adcQue, 1) table.insert(adcQue, voltValue) if voltValue + 1500 < powerVolt or voltValue - 1500 > powerVolt then sens.act = true else sens.act = false end end sens.acc, sens.chg = acc and acc() == 0, chg and chg() == 0 sens.vcc, sens.und = voltValue, voltValue < 4000 sys.wait(1000) sens.vib = false end adc.close(tonumber(adcid) or 0) end ----------------------------------------------------------设备逻辑任务---------------------------------------------------------- -- 上报设备状态,这里是用户自定义上报报文的顺序的 -- sta = {"isopen", "vib", "acc", "act", "chg", "und", "volt", "vbat", "csq"} function deviceMessage(format) if format:lower() ~= "hex" then return json.encode({ sta = {gpsv2.isOpen(), sens.vib, sens.acc, sens.act, sens.chg, sens.und, sens.vcc, misc.getVbatt(), net.getRssi()} }) else return pack.pack(">b7IHb", 0x55, gpsv2.isOpen() and 1 or 0, sens.vib and 1 or 0, sens.acc and 1 or 0, sens.act and 1 or 0, sens.chg and 1 or 0, sens.und and 1 or 0, sens.vcc, misc.getVbatt(), net.getRssi()) end end -- 上传定位信息 -- [是否有效,经度,纬度,海拔,方位角,速度,载噪比,定位卫星,时间戳] -- 用户自定义上报GPS数据的报文顺序 -- msg = {"isfix", "stamp", "lng", "lat", "altitude", "azimuth", "speed", "sateCno", "sateCnt"}, function locateMessage(format) local isFix = gpsv2.isFix() and 1 or 0 local lng, lat = gpsv2.getDegLocation() local altitude = gpsv2.getAltitude() local azimuth = gpsv2.getAzimuth() local speed = gpsv2.getSpeed() local sateCnt = gpsv2.getUsedSateCnt() local sateCno = gpsv2.getCno() table.sort(sateCno) sateCno = table.remove(sateCno) or 0 if format:lower() ~= "hex" then local msg = {["isFix"] = isFix, ["time"] = os.time(), ["lng"] = lng*60*30000, ["lat"] = lat*60*30000, ["altitude"] = altitude, ["azimuth"] = azimuth, ["speed"] = speed, ["sateCno"] = sateCno, ["sateCnt"] = sateCnt} log.info("tracker.locateMessage", "------->locate data:", json.encode(msg)) return msg else return pack.pack(">b2i3H2b3", 0xAA, isFix and 1 or 0, os.time(), lng, lat, altitude, azimuth, speed, sateCno, sateCnt) end end -- 用户捆绑GPS的串口,波特率,功耗模式,采集间隔,采集方式支持触发和持续, 报文数据格式支持 json 和 hex,缓冲条数,数据分隔符(不包含,),状态报文间隔分钟 function alert(uid, baud, pwmode, sleep, guard, format, num, sep, interval, cid) while true do if sys.waitUntil("NTP_SUCCEED", 3600*1000) then log.info("tracker.alert", "------->ntp succeed, unpack(dtu.gps.fun):", unpack(dtu.gps.fun)) break end end uid, baud, pwmode, sleep, num = tonumber(uid), tonumber(baud), tonumber(pwmode), tonumber(sleep), tonumber(num) or 0 guard, interval = tonumber(guard) == 0, (tonumber(interval) or 0) * 60000 local cnt, report = 0, function(format) log.info("tracker.alert","---------> 0") sys.publish("NET_SENT_RDY_" .. (tonumber(cid) or uid), deviceMessage(format)) end --log.info("tracker.alert","------------>", uid, baud, pwmode, sleep, num) while true do -- 布防判断 --if not gpsv2.isOpen() and (not guard or sens.vib or sens.acc or sens.act or sens.und or sens.wup) then if not gpsv2.isOpen() then --sens.wup = false startTime = os.time() -- GPS TRACKER 模式 gpsv2.open(uid, baud, pwmode, sleep) -- 布防上报 --report(format) --if interval ~= 0 then sys.timerLoopStart(report, interval, format) end end while gpsv2.isOpen() do --[[ -- 撤防判断 if os.difftime(os.time(), startTime) > clearTime then if guard and sens.vib and sens.acc and sens.act and sens.und and gpsv2.getSpeed() == 0 then sys.timerStopAll(report) gpsv2.close(uid) else startTime = os.time() end end ]] -- 上报消息 if sys.waitUntil("GPS_MSG_REPORT") then if num == 0 then sys.publish("NET_SENT_RDY_" .. (tonumber(cid) or uid), locateMessage(format)) else cnt = cnt < num and cnt + 1 or 0 table.insert(trackFile, locateMessage(format)) if cnt == 0 then sys.publish("NET_SENT_RDY_" .. (tonumber(cid) or uid), table.concat(trackFile, sep)) end end end sys.wait(100) end sys.wait(100) end end --传感器监测任务 --sys.taskInit(sensMonitor, unpack(dtu.gps.pio)) --GPS串口任务 sys.taskInit(alert, unpack(dtu.gps.fun)) ---------------------------------------------------------- DTU的网络任务部分 ---------------------------------------------------------- --生成请求报文 local function createReqData(protoType) if tonumber(protoType) == 0x01 then --登录包 frameSn = 1 imei = misc.getImei() if not imei then log.error("tracker.loginMsg","getImei failed!") return false end local binData = funlib.hex2bin("676701000b"..string.format("%04x",frameSn)..string.format("%016s",imei).."00") return binData elseif tonumber(protoType) == 0x03 then --心跳包 frameSn = (frameSn + 1 > 65535) and 1 or (frameSn + 1) local status = gpsv2.isFix() and 1 or 0 local binData = funlib.hex2bin("6767030004"..string.format("%04x",frameSn)..string.format("%04x",status)) return binData elseif tonumber(protoType) == 0x84 then --告警包 local data = locateMessage("json") frameSn = (frameSn + 1 > 65535) and 1 or (frameSn + 1) binData = funlib.hex2bin("676784001F"..string.format("%04x",frameSn)..string.format("%08x",data.time)..string.format("%08x",data.lat) ..string.format("%08x",data.lng)..string.format("%02x",data.speed)..string.format("%04x",data.azimuth).."00000000000000000000" ..string.format("%02x",data.isFix).."010000") return binData else return false end end --解析响应报文 local function parseRespData(packet) if not packet then log.error("tracker.parseRespData", "packet not existed!") return false end if #packet < 7 then log.error("tracker.parseRespData", "packet length < 7!") return false end local parse = {} local header = packet:sub(1,2):toHex() if header ~= "6767" then log.error("tracker.parseRespData", "packet header invalid! packet:", packet:toHex()) return false end parse.heaser = header local protoType = packet:sub(3,3):toHex() parse.protoType = protoType local packetLen = tonumber(packet:sub(4,5):toHex(),16) if packetLen + 5 ~= #packet then log.error("tracker.parseRespData", "packet length error! packet:", packet:toHex()) return false end parse.packetLen = packetLen --登录响应包 if protoType == "01" then local respSn = tonumber(packet:sub(6,7):toHex(),16) parse.respSn = respSn return true,parse end return false end ---------------------------------------------------------- SOKCET 服务 ---------------------------------------------------------- local function tcpTask(cid, pios, reg, convert, passon, upprot, dwprot, prot, ping, timeout, addr, port, uid, gap, report, intervalTime, ssl) cid, prot, timeout, uid = tonumber(cid) or 1, prot:upper(), tonumber(timeout) or 120, tonumber(uid) or 1 if not ping or ping == "" then ping = "0x00" end local login = false local heartbeatTimerId = false while true do local idx = 0 if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then sys.restart("网络初始化失败!") end local c = prot == "TCP" and socket.tcp(ssl and ssl:lower() == "ssl") or socket.udp() if c:connect(addr, port) then -- 登陆报文 datalink = true local retryCount = 0 while not login do local loginData = createReqData(0x01) if loginData then if not c:send(loginData) then retryCount = retryCount + 1 log.error("tracker.tcpTask","send loginData failed! loginData:", loginData:toHex(), "retryCount:", retryCount) else local result, data = c:recv(2 * 1000) if not result then retryCount = retryCount + 1 log.error("tracker.tcpTask","wait login response timeout! loginData:", loginData:toHex(), "retryCount:", retryCount) else local reqSn = tonumber(loginData:sub(6,7):toHex(),16) local res,parseData = parseRespData(data) if res and parseData.respSn == reqSn then login = true retryCount = 0 break else retryCount = retryCount + 1 log.error("tracker.tcpTask","parseRespData failed! respData:", data:toHex(), "retryCount:", retryCount) end end end else log.error("tracker.tcpTask","loginMsg failed!") break end if retryCount > 3 then retryCount = 0 break end sys.wait(10*1000) end --上报定位/心跳数据报文 while login do local result, data, param = c:recv(timeout * 1000, "GPS_SENT_RDY_" .. (cid or uid)) --log.info("tracker.tcpTask", "result, data, param", result, data, param and param:toHex()) if result then if #data>=7 and data:sub(1, 3):toHex() == "676703" then log.info("tracker.tcpTask","recv heartbeat resp data, data:", data:toHex()) elseif #data>=7 and data:sub(1, 3):toHex() == "676782" then log.info("tracker.tcpTask","recv locate resp data, data:", data:toHex()) elseif #data>=7 and data:sub(1, 3):toHex() == "676784" then log.info("tracker.tcpTask","recv alarm resp data, data:", data:toHex()) end elseif data == "timeout" then local heartbeatData = createReqData(0x03) if heartbeatData then if not c:send(heartbeatData) then log.error("tracker.tcpTask","send heartbeatData failed! heartbeatData:", heartbeatData:toHex()) break else log.info("tracker.tcpTask","send heartbeatData success, heartbeatData:", heartbeatData:toHex()) end else log.error("tracker.tcpTask","create heartbeatData failed!") break end elseif data == ("GPS_SENT_RDY_" .. (cid or uid)) then if not c:send(param) then log.error("tracker.tcpTask","send locate data failed! locateData:", param:toHex()) break else log.info("tracker.tcpTask","send locate data success, locateData:", param:toHex()) end else log.error("tracker.tcpTask","recv data failed!") break end end end c:close() log.error("tracker.tcpTask","datalink failed!") datalink = false login = false sys.wait((2 ^ idx) * 1000) idx = idx > 9 and 0 or idx + 1 end end --定位数据预处理 local function locateDataFilterTask( cid ) cid = tonumber(cid) or 1 local startTime = os.time() local locateData = "" local points = {} local stopStartTime = 0 local isStopStart = false local isTurning = false while true do local res,data = sys.waitUntil("NET_SENT_RDY_" .. cid, 60*1000) if res then --静止超过时间阀值,停止上报位置 if data.speed == 0 and not isStopStart then isStopStart = true stopStartTime = os.time() elseif data.speed == 0 and isStopStart and stopStartTime>0 and os.time()-stopStartTime > stopTimeThreshold then log.info("tracker.locateDataFilterTask","stop > 10s,stop gps send!") elseif data.speed > 0 then --缓存点位 if #points == 0 then table.insert(points,data) else if #points >= 3 then table.remove(points,1) end table.insert(points,data) --过滤过期的点 local tmp_points = {} for i=1,#points do if data.time - points[i].time <= 4 then table.insert(tmp_points,points[i]) end end points = tmp_points --达到三个点时,进行拐点检测 if #points>=3 then if math.abs(points[2].azimuth-points[1].azimuth) >= 10 and math.abs(points[3].azimuth-points[2].azimuth) >= 10 then isTurning = true else isTurning = false end else isTurning = false end end isStopStart = false stopStartTime = 0 --拐弯时,收到定位数据就上报 if isTurning then log.info("tracker.locateDataFilterTask","device is turning, start fast report locate!") frameSn = (frameSn + 1 > 65535) and 1 or (frameSn + 1) locateData = funlib.hex2bin("676782001C"..string.format("%04x",frameSn)..string.format("%08x",data.time)..string.format("%08x",data.lat) ..string.format("%08x",data.lng)..string.format("%02x",data.speed)..string.format("%04x",data.azimuth).."00000000000000000000"..string.format("%02x",data.isFix)) sys.publish("GPS_SENT_RDY_"..cid, locateData) locateData = "" startTime = os.time() else --如果达到间隔时间上报定位包 if os.time()-startTime >= locateReportInterval then log.info("tracker.locateDataFilterTask","report interval locate data") frameSn = (frameSn + 1 > 65535) and 1 or (frameSn + 1) locateData = funlib.hex2bin("676782001C"..string.format("%04x",frameSn)..string.format("%08x",data.time)..string.format("%08x",data.lat) ..string.format("%08x",data.lng)..string.format("%02x",data.speed)..string.format("%04x",data.azimuth).."00000000000000000000"..string.format("%02x",data.isFix)) sys.publish("GPS_SENT_RDY_"..cid, locateData) locateData = "" startTime = os.time() else locateData = funlib.hex2bin("676702001C"..string.format("%04x",frameSn)..string.format("%08x",data.time)..string.format("%08x",data.lat) ..string.format("%08x",data.lng)..string.format("%02x",data.speed)..string.format("%04x",data.azimuth).."00000000000000000000"..string.format("%02x",data.isFix)) end end else log.warn("tracker.locateDataFilterTask","stop start ", os.time()-stopStartTime) end else if locateData ~= "" then log.info("tracker.locateDataFilterTask", "wait ".."NET_SENT_RDY_" .. cid .." timeout, upload locateData") sys.publish("GPS_SENT_RDY_"..cid, locateData) locateData = "" startTime = os.time() else log.warn("tracker.locateDataFilterTask", "wait NET_SENT_RDY_" .. cid .." timeout!" ) end end end end ---------------------------------------------------------- 参数配置,任务转发,线程守护主进程---------------------------------------------------------- function connect(pios, conf, reg, convert, passon, upprot, dwprot) local flyTag = false if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then sys.restart("网络初始化失败!") end --sys.waitUntil("DTU_PARAM_READY", 120000) -- 自动创建透传任务并填入参数 for k, v in pairs(conf or {}) do -- log.info("Task parameter information:", k, pios, reg, convert, passon, upprot, dwprot, unpack(v)) if v[1] and (v[1]:upper() == "TCP" or v[1]:upper() == "UDP") then log.warn("----------------------- TCP/UDP is start! --------------------------------------") sys.taskInit(tcpTask, k, pios, reg, convert, passon, upprot, dwprot, unpack(v)) sys.taskInit(locateDataFilterTask, k) end end -- 守护进程 datalink = true while true do -- 这里是网络正常,但是链接服务器失败重启 if datalink then sys.timerStart(sys.restart, rstTim, "Server connection failed") end sys.wait(1000) end end sys.taskInit(connect, pios, dtu.conf, dtu.reg, tonumber(dtu.convert) or 0, (tonumber(dtu.passon) == 0), dtu.upprot, dtu.dwprot) --gpio中断 function gpio13IntFnc(msg) log.info("tracker.gpio13IntFnc",msg,getGpio13Fnc()) --上升沿中断 if msg==cpu.INT_GPIO_POSEDGE then log.info("tracker.gpio13IntFnc","up edge") locateData = createReqData(0x84) sys.publish("GPS_SENT_RDY_1", locateData) --下降沿中断 else log.info("tracker.gpio13IntFnc","down edge") end end --GPIO16配置为中断,可通过getGpio16Fnc()获取输入电平,产生中断时,自动执行gpio16IntFnc函数 getGpio13Fnc = pins.setup(pio.P0_13,gpio13IntFnc) -- NTP同步后清零一次startTime,避免第一次开机的时候utc时间跳变 sys.subscribe("NTP_SUCCEED", function() log.info("tracker.subscribe.NTP_SUCCEED","recv NTP_SUCCEED") startTime = os.time() end) -- 订阅服务器远程唤醒指令 --sys.subscribe("REMOTE_WAKEUP", function()sens.wup = true end)