|
|
@@ -0,0 +1,403 @@
|
|
|
+-- LuaTools需要PROJECT和VERSION这两个信息
|
|
|
+PROJECT = "JT808"
|
|
|
+VERSION = "2.0.0"
|
|
|
+
|
|
|
+--[[
|
|
|
+本demo演示使用string.pack与unpack函数,实现JT808 终端注册协议数据生成与解析
|
|
|
+]]
|
|
|
+
|
|
|
+--加载sys库
|
|
|
+sys = require("sys")
|
|
|
+
|
|
|
+
|
|
|
+local netLed = require("netLed")
|
|
|
+--GPIO27配置为输出,用作网络指示灯
|
|
|
+local LEDA= gpio.setup(27, 0, gpio.PULLUP)
|
|
|
+
|
|
|
+-- 自定义位运算函数
|
|
|
+local bit = {}
|
|
|
+
|
|
|
+--终端消息ID
|
|
|
+T_COMMON_RSP = 0x0001
|
|
|
+T_REGISTER = 0x0100
|
|
|
+
|
|
|
+function bit.bxor(a, b)
|
|
|
+ return a ~ b -- 按位异或
|
|
|
+end
|
|
|
+
|
|
|
+--十六进制转二进制
|
|
|
+local function hexToBinary(hexStr)
|
|
|
+ local binaryData = {}
|
|
|
+ for i = 1, #hexStr, 2 do
|
|
|
+ local byte = tonumber(hexStr:sub(i, i+1), 16) -- 每两个字符转换为一个字节
|
|
|
+ table.insert(binaryData, string.char(byte))
|
|
|
+ end
|
|
|
+ return table.concat(binaryData) -- 拼接成二进制字符串
|
|
|
+end
|
|
|
+--字符串转字节数组
|
|
|
+local function stringToBytes(hexStr)
|
|
|
+ local bytes = {}
|
|
|
+ for i = 1, #hexStr, 2 do
|
|
|
+ local byteStr = hexStr:sub(i, i+1)
|
|
|
+ local byte = tonumber(byteStr, 16)
|
|
|
+ table.insert(bytes, byte)
|
|
|
+ end
|
|
|
+ return bytes
|
|
|
+end
|
|
|
+--BCD编码
|
|
|
+local function encodeBcdNum(d, n)
|
|
|
+ if d:len() < n then
|
|
|
+ return (string.rep('0', n - d:len()) .. d):fromHex()
|
|
|
+ else
|
|
|
+ return (d:sub(1, n)):fromHex()
|
|
|
+ end
|
|
|
+end
|
|
|
+--计算异或校验(十六进制字符串)
|
|
|
+local function calculateXor(data)
|
|
|
+ local sum = 0
|
|
|
+ for i = 1, #data, 2 do
|
|
|
+ local byte = tonumber(data:sub(i, i+1), 16)
|
|
|
+
|
|
|
+ -- 检查是否转换成功
|
|
|
+ if not byte then
|
|
|
+ error(string.format("Invalid hex character: '%s' at position %d", byteStr, i))
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 打印当前字节的值(十六进制和十进制)
|
|
|
+ --print(string.format("当前字节: %s (十进制: %d)", byteStr, byte))
|
|
|
+
|
|
|
+ if not byte then
|
|
|
+ error("Invalid hex character in input string!")
|
|
|
+ end
|
|
|
+ sum = bit.bxor(sum, byte)
|
|
|
+ -- 打印异或后的中间结果
|
|
|
+ --print(string.format("异或后 sum = %d (十六进制: 0x%X)", sum, sum))
|
|
|
+ end
|
|
|
+ return sum
|
|
|
+end
|
|
|
+--
|
|
|
+local function calCrc(hexStr)
|
|
|
+
|
|
|
+ -- 解析hexStr转换为十六进制字符数组
|
|
|
+ local byteArray = HexOutput(hexStr)
|
|
|
+
|
|
|
+ -- 计算 CRC
|
|
|
+ local crc = calculateXor(byteArray)
|
|
|
+ log.info("CRCxor:",crc)
|
|
|
+
|
|
|
+ -- 返回 CRC 值
|
|
|
+ return crc
|
|
|
+end
|
|
|
+
|
|
|
+--将时间转换为BCD格式
|
|
|
+function timeToBCD()
|
|
|
+ local t = os.date("*t")
|
|
|
+
|
|
|
+ -- 转换为BCD格式
|
|
|
+ local year = string.format("%02d", t.year % 100)
|
|
|
+ local month = string.format("%02d", t.month)
|
|
|
+ local day = string.format("%02d", t.day)
|
|
|
+ local hour = string.format("%02d", t.hour)
|
|
|
+ local min = string.format("%02d", t.min)
|
|
|
+ local sec = string.format("%02d", t.sec)
|
|
|
+
|
|
|
+ -- 组合BCD格式字符串
|
|
|
+ local bcdTime = year .. month .. day .. hour .. min .. sec
|
|
|
+
|
|
|
+ return bcdTime
|
|
|
+end
|
|
|
+
|
|
|
+
|
|
|
+--构建通用应答
|
|
|
+function cmnRsp(svrSeq, svrId, result)
|
|
|
+ return string.pack('>HHb', svrSeq, svrId, result)
|
|
|
+ -- >表示使用大端字节序;
|
|
|
+ -- HH 表示两个 16 位无符号整数(svrSeq 和 svrId);
|
|
|
+ -- b 表示一个 8 位有符号整数(result)
|
|
|
+end
|
|
|
+--构建设备注册
|
|
|
+function register(ProvinceId,CityId,ManufactureId,TerminalModule,TerminalId,CarColor,CarId)
|
|
|
+ local termMd = TerminalModule..string.rep(string.char(0), 16 - (TerminalModule):len())..string.rep(string.char(0), 4)
|
|
|
+ --log.info("HexDataByteLength:",#termMd)
|
|
|
+ --log.info("HexData is: ",termMd)
|
|
|
+ return string.pack('>HHc5c20c7Bc11',ProvinceId,CityId,ManufactureId,termMd,TerminalId:fromHex(),CarColor,CarId)
|
|
|
+end
|
|
|
+--构建位置信息汇报
|
|
|
+function locRpt(alarm, status, lat, lng, alt, spd, course, tm, extInfo)
|
|
|
+ -- 使用 '>iiiiHHH' 格式打包数据
|
|
|
+ return string.pack('>iiiiHHH', alarm, status, lat, lng, alt, spd, course)..tm:fromHex().. extInfo
|
|
|
+end
|
|
|
+
|
|
|
+function HexOutput(data)
|
|
|
+ log.info("HexDataByteLength:",#data)
|
|
|
+ local hex = ""
|
|
|
+ for i = 1, #data do
|
|
|
+ hex = hex.. string.format("%02x", string.byte(data, i))
|
|
|
+ end
|
|
|
+ log.info("HexData is: ",hex)
|
|
|
+ return hex
|
|
|
+end
|
|
|
+--封装一个完整的JT808终端注册数据帧
|
|
|
+function encodeReg(phoneNo,tSendSeq)
|
|
|
+ --消息体
|
|
|
+ local msgBody = register(12,123,'10001','GT808','00000000000002',0,'41048063212')
|
|
|
+ --消息头
|
|
|
+ local msgHead =string.pack('>HH',T_REGISTER,msgBody:len())..encodeBcdNum(phoneNo,12)..tSendSeq
|
|
|
+ local curSeq = tSendSeq
|
|
|
+ tSendSeq = (tSendSeq == 0xFFFF) and 0 or (tSendSeq + 1)
|
|
|
+ --校验码
|
|
|
+ HexOutput(msgHead .. msgBody)
|
|
|
+ local frame = msgHead .. msgBody
|
|
|
+ log.info("encodeReg Frame length is: ",#frame)
|
|
|
+ local crc = calCrc(frame)
|
|
|
+
|
|
|
+ --转义
|
|
|
+ local s = msgHead .. msgBody .. string.char(crc)
|
|
|
+ s = s:gsub('\125', '\125\1') -- 7D -> 7D 01
|
|
|
+ s = s:gsub('\126', '\125\2') -- 7E -> 7D 02
|
|
|
+ return string.char(0x7E) .. s .. string.char(0x7E), curSeq
|
|
|
+
|
|
|
+end
|
|
|
+
|
|
|
+function decodeCmnRsp(s)
|
|
|
+
|
|
|
+ log.info("hereNow.",s)
|
|
|
+ --local start, tail, msg = s:find('^(7E[^7E]+7E)$')
|
|
|
+ --local decPacket, msgAttr = {}
|
|
|
+-- 检查是否以 7E 开头和结尾
|
|
|
+ if not s:match("^7E") or not s:match("7E$") then
|
|
|
+ log.warn("prot.decode", "packet does not start or end with 7E")
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 匹配以 7E 开头和结尾的数据包
|
|
|
+ local start, tail, msg = s:find('^(7E.+7E)$') -- 改为允许中间部分包含 7E
|
|
|
+ log.info("start:", start)
|
|
|
+ log.info("tail:", tail)
|
|
|
+ log.info("msg:", msg)
|
|
|
+
|
|
|
+ if not msg then
|
|
|
+ -- 如果未匹配到
|
|
|
+ log.warn('prot.decode', 'wait packet complete')
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+
|
|
|
+ --反转义
|
|
|
+ msg = msg:gsub('\125\2', '\126') -- 7D 02 -> 7E
|
|
|
+ msg = msg:gsub('\125\1', '\125') -- 7D 01 -> 7D
|
|
|
+
|
|
|
+ log.info("msg:", msg,msg:len())
|
|
|
+
|
|
|
+ if msg:len() < 13 then
|
|
|
+ log.error('prot.decode', 'packet len is too short')
|
|
|
+ return s:sub(tail + 1, -1), nil
|
|
|
+ end
|
|
|
+
|
|
|
+ local hexArray = stringToBytes(msg)
|
|
|
+
|
|
|
+ local dataHexStr = msg:sub(3, -5)
|
|
|
+ log.info("消息体和其字节长度:", dataHexStr,dataHexStr:len())
|
|
|
+
|
|
|
+ local crcVal = calculateXor(dataHexStr)
|
|
|
+ print("XOR校验值:", crcVal)
|
|
|
+
|
|
|
+ if crcVal ~= hexArray[#hexArray-1] then
|
|
|
+ log.error('prot.decode', 'crc value error!')
|
|
|
+ return s:sub(tail + 1, -1), nil
|
|
|
+ else
|
|
|
+ log.info("XOR:校验通过")
|
|
|
+ end
|
|
|
+
|
|
|
+ ----------------------------解析消息id
|
|
|
+ msgdata = msg:sub(3, 6)
|
|
|
+
|
|
|
+ -- 转换为二进制数据
|
|
|
+ local binaryData = hexToBinary(msgdata)
|
|
|
+
|
|
|
+ -- 解析二进制数据
|
|
|
+ msgId = string.unpack(">H", binaryData)
|
|
|
+
|
|
|
+ -- 输出结果
|
|
|
+ log.info("消息id十六进制:", string.format("0x%04X", msgId))
|
|
|
+
|
|
|
+ ----------------------------解析应答结果
|
|
|
+ msgdata = msg:sub(7, 8)
|
|
|
+
|
|
|
+ -- 转换为二进制数据
|
|
|
+ local binaryData = hexToBinary(msgdata)
|
|
|
+
|
|
|
+ -- 解析二进制数据
|
|
|
+ result = string.unpack(">B", binaryData)
|
|
|
+
|
|
|
+ -- 输出结果
|
|
|
+ log.info("结果十六进制:", string.format("0x%02X", result))
|
|
|
+
|
|
|
+ ----------------------------解析鉴权码
|
|
|
+ msgdata = msg:sub(9, 10)
|
|
|
+
|
|
|
+ -- 转换为二进制数据
|
|
|
+ local binaryData = hexToBinary(msgdata)
|
|
|
+
|
|
|
+ -- 解析二进制数据
|
|
|
+ result = string.unpack(">B", binaryData)
|
|
|
+
|
|
|
+ -- 输出结果
|
|
|
+ log.info("鉴权码十六进制:", string.format("0x%02X", result))
|
|
|
+
|
|
|
+ ----------------------------解析电话号码
|
|
|
+ msgdata = msg:sub(11, 22)
|
|
|
+
|
|
|
+ -- 转换为二进制数据
|
|
|
+ local binaryData = hexToBinary(msgdata)
|
|
|
+
|
|
|
+ -- 解析二进制数据
|
|
|
+ local b1, b2, b3, b4, b5, b6 = string.unpack(">BBBBBB", binaryData)
|
|
|
+
|
|
|
+ -- 格式化输出为 6 字节的十六进制字符串
|
|
|
+ local TerminalPhoneNo = string.format("0x%02X%02X%02X%02X%02X%02X", b1, b2, b3, b4, b5, b6)
|
|
|
+
|
|
|
+ -- 输出电话号码
|
|
|
+ log.info("电话号码十六进制:", TerminalPhoneNo)
|
|
|
+
|
|
|
+ ----------------------------解析标头流水号
|
|
|
+ msgdata = msg:sub(23, 26)
|
|
|
+
|
|
|
+ -- 转换为二进制数据
|
|
|
+ local binaryData = hexToBinary(msgdata)
|
|
|
+
|
|
|
+ -- 解析二进制数据
|
|
|
+ local ManualMsgNum = string.unpack(">H", binaryData)
|
|
|
+
|
|
|
+ -- 输出结果
|
|
|
+ log.info("标头流水号十六进制:", string.format("0x%04X", ManualMsgNum))
|
|
|
+
|
|
|
+ ----------------------------解析标尾流水号
|
|
|
+ msgdata = msg:sub(23, 26)
|
|
|
+
|
|
|
+ -- 转换为二进制数据
|
|
|
+ local binaryData = hexToBinary(msgdata)
|
|
|
+
|
|
|
+ -- 解析二进制数据
|
|
|
+ local MsgNum = string.unpack(">H", binaryData)
|
|
|
+
|
|
|
+ -- 输出结果
|
|
|
+ log.info("标尾流水号十六进制:", string.format("0x%04X", MsgNum))
|
|
|
+
|
|
|
+ ----------------------------解析鉴权码
|
|
|
+ msgdata = msg:sub(33, 50)
|
|
|
+
|
|
|
+ -- 转换为二进制数据
|
|
|
+ local binaryData = hexToBinary(msgdata)
|
|
|
+
|
|
|
+ local b1, b2, b3, b4, b5, b6, b7, b8, b9 = string.unpack(">BBBBBBBBB", binaryData)
|
|
|
+
|
|
|
+ -- 格式化输出为 6 字节的十六进制字符串
|
|
|
+ local JianquanCode = string.format("0x%02X%02X%02X%02X%02X%02X%02X%02X%02X", b1, b2, b3, b4, b5, b6, b7, b8, b9)
|
|
|
+
|
|
|
+ -- 输出结果
|
|
|
+ log.info("鉴权码十六进制:", JianquanCode)
|
|
|
+end
|
|
|
+
|
|
|
+--新建任务,每休眠2000ms继续一次
|
|
|
+sys.taskInit(function()
|
|
|
+
|
|
|
+ --实验1:构建立通用应答数据帧,协议可参考JT808协议8.1与8.2
|
|
|
+ --输入参数:
|
|
|
+ --应答流水号为123,
|
|
|
+ --应答 ID为456,
|
|
|
+ --结果为1,
|
|
|
+ local data = cmnRsp(123, 456, 1)
|
|
|
+ HexOutput(data) --HexDataByteLength:5,HexData is:007b01c801 007b为123,01c8为456,1为1
|
|
|
+
|
|
|
+ --实验2:构建终端注册数据帧,协议可参考JT808协议8.5
|
|
|
+ --输入参数:
|
|
|
+ --12, --省域 ID WORD
|
|
|
+ --123, --市县域 ID WORD
|
|
|
+ --'10001', --制造商 ID BYTE[5]
|
|
|
+ --'GT808' --终端型号 BYTE[20]
|
|
|
+ --'00000000000002', --终端 ID BYTE[7]
|
|
|
+ --0, --车牌颜色 BYTE
|
|
|
+ --'41048063212') --车辆标识 STRING VIN或车牌号均为固定长度,这里假设字符串长度为11
|
|
|
+ local data = register(12,123,'10001','GT808','00000000000002',0,'41048063212')
|
|
|
+ HexOutput(data)--[HexDataByteLength:] 48,[HexData is: ]
|
|
|
+
|
|
|
+ --输出解析:
|
|
|
+ --000c,对应12, --省域 ID WORD
|
|
|
+ --007b,对应123, --市县域 ID WORD
|
|
|
+ --3130303031,对应'10001', --制造商 ID BYTE[5]
|
|
|
+ --4754383038,对应'GT808' --终端型号 BYTE[20],不足字节补0
|
|
|
+ --0000000000
|
|
|
+ --0000000000
|
|
|
+ --0000000000
|
|
|
+ --00000000000002 对应'00000000000002', --终端 ID BYTE[7]
|
|
|
+ --000, --车牌颜色 BYTE
|
|
|
+ --3431303438303633323132,对应'41048063212') --车辆标识 STRING
|
|
|
+
|
|
|
+
|
|
|
+ --实验3:构建位置信息汇报数据帧,协议可参考JT808协议8.18
|
|
|
+ --输入参数:
|
|
|
+ --0, 报警标志 DWORD
|
|
|
+ --1, 状态位 DWORD
|
|
|
+ --12345678, 纬度 DWORD
|
|
|
+ --87654321, 经度 DWORD
|
|
|
+ --100, 高程 WORD
|
|
|
+ --60, 速度 WORD
|
|
|
+ --180, 方向 WORD
|
|
|
+ --os.time(), 时间 BCD[6] YY-MM-DD-hh-mm-ss(GMT+8 时间,本标准中之后涉及的时间均采用此时区)
|
|
|
+ --"extra"
|
|
|
+
|
|
|
+ local time = timeToBCD();
|
|
|
+ log.info("CurrentTime: ",time)
|
|
|
+
|
|
|
+ local data = locRpt(0, 1, 12345678, 87654321, 100, 60, 180, time, "extra")
|
|
|
+ HexOutput(data)--[HexDataByteLength:] 33,[HexData is: ] 000000000000000100bc614e05397fb10064003c00b42411241232046578747261
|
|
|
+ --输出解析:
|
|
|
+ --00000000,对应 0, 报警标志 DWORD,4字节
|
|
|
+ --00000001,对应 1, 状态位 DWORD
|
|
|
+ --00bc614e,对应 12345678, 纬度 DWORD
|
|
|
+ --05397fb1, 对应 87654321, 经度 DWORD
|
|
|
+ --0064,对应 100, 高程 WORD
|
|
|
+ --003c,对应60, 速度 WORD
|
|
|
+ --00b4,对应180, 方向 WORD
|
|
|
+ --241124123204,对应时间 BCD[6] 24-11-24-12-32-04
|
|
|
+ --6578747261,对应"extra"
|
|
|
+
|
|
|
+ --实验4:封闭一个完整的JT808注册数据帧
|
|
|
+ --输入参数:
|
|
|
+ --'018068821447',手机号不足 12位,则在前补充数字,大陆手机号补充数字 0
|
|
|
+ --'101',消息流水号
|
|
|
+ local data = encodeReg('018068821447','101')
|
|
|
+ HexOutput(data) --[HexDataByteLength:] 64
|
|
|
+ --[HexData is: ] 7e01000030018068821447313031000c007b3130303031475438303800000000000000000000000000000000000000000002003431303438303633323132627e
|
|
|
+
|
|
|
+ --输出解析:
|
|
|
+ --7e,标识位固定为0x7e
|
|
|
+ --0100,消息 ID
|
|
|
+ --0030,消息体属性
|
|
|
+ --018068821447,手机号
|
|
|
+ --313031,消息流水号
|
|
|
+ --000c007b3130303031475438303800000000000000000000000000000000000000000002003431303438303633323132,注册消息体
|
|
|
+ --62,校验
|
|
|
+ --7e,标识位固定为0x7e
|
|
|
+
|
|
|
+ --实验5:解析JT808终端注册应答数据帧
|
|
|
+ local hexStr = '7E8100000C0404571031660030003000736875616E67776569E07E'
|
|
|
+ decodeCmnRsp(hexStr)
|
|
|
+end)
|
|
|
+-- 这里演示4G模块上网后,会自动点亮网络灯,方便用户判断模块是否正常开机
|
|
|
+sys.taskInit(function()
|
|
|
+ while true do
|
|
|
+ sys.wait(6000)
|
|
|
+ if mobile.status() == 1 then
|
|
|
+ gpio.set(27, 1)
|
|
|
+ else
|
|
|
+ gpio.set(27, 0)
|
|
|
+ mobile.reset()
|
|
|
+ end
|
|
|
+ end
|
|
|
+end)
|
|
|
+-- 用户代码已结束---------------------------------------------
|
|
|
+-- 运行lua task,只能调用一次,而且必须写在末尾
|
|
|
+-- 结尾总是这一句
|
|
|
+sys.run()
|
|
|
+-- sys.run()之后后面不要加任何语句!!!!!
|