main.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. -- LuaTools需要PROJECT和VERSION这两个信息
  2. PROJECT = "JT808"
  3. VERSION = "2.0.0"
  4. --[[
  5. 本demo演示使用string.pack与unpack函数,实现JT808 终端注册协议数据生成与解析
  6. ]]
  7. --加载sys库
  8. sys = require("sys")
  9. -- Air780E的AT固件默认会为开机键防抖, 导致部分用户刷机很麻烦
  10. if rtos.bsp() == "EC618" and pm and pm.PWK_MODE then
  11. pm.power(pm.PWK_MODE, false)
  12. end
  13. local netLed = require("netLed")
  14. --GPIO27配置为输出,用作网络指示灯
  15. local LEDA= gpio.setup(27, 0, gpio.PULLUP)
  16. -- 自定义位运算函数
  17. local bit = {}
  18. --终端消息ID
  19. T_COMMON_RSP = 0x0001
  20. T_REGISTER = 0x0100
  21. function bit.bxor(a, b)
  22. return a ~ b -- 按位异或
  23. end
  24. --十六进制转二进制
  25. local function hexToBinary(hexStr)
  26. local binaryData = {}
  27. for i = 1, #hexStr, 2 do
  28. local byte = tonumber(hexStr:sub(i, i+1), 16) -- 每两个字符转换为一个字节
  29. table.insert(binaryData, string.char(byte))
  30. end
  31. return table.concat(binaryData) -- 拼接成二进制字符串
  32. end
  33. --字符串转字节数组
  34. local function stringToBytes(hexStr)
  35. local bytes = {}
  36. for i = 1, #hexStr, 2 do
  37. local byteStr = hexStr:sub(i, i+1)
  38. local byte = tonumber(byteStr, 16)
  39. table.insert(bytes, byte)
  40. end
  41. return bytes
  42. end
  43. --BCD编码
  44. local function encodeBcdNum(d, n)
  45. if d:len() < n then
  46. return (string.rep('0', n - d:len()) .. d):fromHex()
  47. else
  48. return (d:sub(1, n)):fromHex()
  49. end
  50. end
  51. --计算异或校验(十六进制字符串)
  52. local function calculateXor(data)
  53. local sum = 0
  54. for i = 1, #data, 2 do
  55. local byte = tonumber(data:sub(i, i+1), 16)
  56. -- 检查是否转换成功
  57. if not byte then
  58. error(string.format("Invalid hex character: '%s' at position %d", byteStr, i))
  59. end
  60. -- 打印当前字节的值(十六进制和十进制)
  61. --print(string.format("当前字节: %s (十进制: %d)", byteStr, byte))
  62. if not byte then
  63. error("Invalid hex character in input string!")
  64. end
  65. sum = bit.bxor(sum, byte)
  66. -- 打印异或后的中间结果
  67. --print(string.format("异或后 sum = %d (十六进制: 0x%X)", sum, sum))
  68. end
  69. return sum
  70. end
  71. --
  72. local function calCrc(hexStr)
  73. -- 解析hexStr转换为十六进制字符数组
  74. local byteArray = HexOutput(hexStr)
  75. -- 计算 CRC
  76. local crc = calculateXor(byteArray)
  77. log.info("CRCxor:",crc)
  78. -- 返回 CRC 值
  79. return crc
  80. end
  81. --将时间转换为BCD格式
  82. function timeToBCD()
  83. local t = os.date("*t")
  84. -- 转换为BCD格式
  85. local year = string.format("%02d", t.year % 100)
  86. local month = string.format("%02d", t.month)
  87. local day = string.format("%02d", t.day)
  88. local hour = string.format("%02d", t.hour)
  89. local min = string.format("%02d", t.min)
  90. local sec = string.format("%02d", t.sec)
  91. -- 组合BCD格式字符串
  92. local bcdTime = year .. month .. day .. hour .. min .. sec
  93. return bcdTime
  94. end
  95. --构建通用应答
  96. function cmnRsp(svrSeq, svrId, result)
  97. return string.pack('>HHb', svrSeq, svrId, result)
  98. -- >表示使用大端字节序;
  99. -- HH 表示两个 16 位无符号整数(svrSeq 和 svrId);
  100. -- b 表示一个 8 位有符号整数(result)
  101. end
  102. --构建设备注册
  103. function register(ProvinceId,CityId,ManufactureId,TerminalModule,TerminalId,CarColor,CarId)
  104. local termMd = TerminalModule..string.rep(string.char(0), 16 - (TerminalModule):len())..string.rep(string.char(0), 4)
  105. --log.info("HexDataByteLength:",#termMd)
  106. --log.info("HexData is: ",termMd)
  107. return string.pack('>HHc5c20c7Bc11',ProvinceId,CityId,ManufactureId,termMd,TerminalId:fromHex(),CarColor,CarId)
  108. end
  109. --构建位置信息汇报
  110. function locRpt(alarm, status, lat, lng, alt, spd, course, tm, extInfo)
  111. -- 使用 '>iiiiHHH' 格式打包数据
  112. return string.pack('>iiiiHHH', alarm, status, lat, lng, alt, spd, course)..tm:fromHex().. extInfo
  113. end
  114. function HexOutput(data)
  115. log.info("HexDataByteLength:",#data)
  116. local hex = ""
  117. for i = 1, #data do
  118. hex = hex.. string.format("%02x", string.byte(data, i))
  119. end
  120. log.info("HexData is: ",hex)
  121. return hex
  122. end
  123. --封装一个完整的JT808终端注册数据帧
  124. function encodeReg(phoneNo,tSendSeq)
  125. --消息体
  126. local msgBody = register(12,123,'10001','GT808','00000000000002',0,'41048063212')
  127. --消息头
  128. local msgHead =string.pack('>HH',T_REGISTER,msgBody:len())..encodeBcdNum(phoneNo,12)..tSendSeq
  129. local curSeq = tSendSeq
  130. tSendSeq = (tSendSeq == 0xFFFF) and 0 or (tSendSeq + 1)
  131. --校验码
  132. HexOutput(msgHead .. msgBody)
  133. local frame = msgHead .. msgBody
  134. log.info("encodeReg Frame length is: ",#frame)
  135. local crc = calCrc(frame)
  136. --转义
  137. local s = msgHead .. msgBody .. string.char(crc)
  138. s = s:gsub('\125', '\125\1') -- 7D -> 7D 01
  139. s = s:gsub('\126', '\125\2') -- 7E -> 7D 02
  140. return string.char(0x7E) .. s .. string.char(0x7E), curSeq
  141. end
  142. function decodeCmnRsp(s)
  143. log.info("hereNow.",s)
  144. --local start, tail, msg = s:find('^(7E[^7E]+7E)$')
  145. --local decPacket, msgAttr = {}
  146. -- 检查是否以 7E 开头和结尾
  147. if not s:match("^7E") or not s:match("7E$") then
  148. log.warn("prot.decode", "packet does not start or end with 7E")
  149. return nil
  150. end
  151. -- 匹配以 7E 开头和结尾的数据包
  152. local start, tail, msg = s:find('^(7E.+7E)$') -- 改为允许中间部分包含 7E
  153. log.info("start:", start)
  154. log.info("tail:", tail)
  155. log.info("msg:", msg)
  156. if not msg then
  157. -- 如果未匹配到
  158. log.warn('prot.decode', 'wait packet complete')
  159. return nil
  160. end
  161. --反转义
  162. msg = msg:gsub('\125\2', '\126') -- 7D 02 -> 7E
  163. msg = msg:gsub('\125\1', '\125') -- 7D 01 -> 7D
  164. log.info("msg:", msg,msg:len())
  165. if msg:len() < 13 then
  166. log.error('prot.decode', 'packet len is too short')
  167. return s:sub(tail + 1, -1), nil
  168. end
  169. local hexArray = stringToBytes(msg)
  170. local dataHexStr = msg:sub(3, -5)
  171. log.info("消息体和其字节长度:", dataHexStr,dataHexStr:len())
  172. local crcVal = calculateXor(dataHexStr)
  173. print("XOR校验值:", crcVal)
  174. if crcVal ~= hexArray[#hexArray-1] then
  175. log.error('prot.decode', 'crc value error!')
  176. return s:sub(tail + 1, -1), nil
  177. else
  178. log.info("XOR:校验通过")
  179. end
  180. ----------------------------解析消息id
  181. msgdata = msg:sub(3, 6)
  182. -- 转换为二进制数据
  183. local binaryData = hexToBinary(msgdata)
  184. -- 解析二进制数据
  185. msgId = string.unpack(">H", binaryData)
  186. -- 输出结果
  187. log.info("消息id十六进制:", string.format("0x%04X", msgId))
  188. ----------------------------解析应答结果
  189. msgdata = msg:sub(7, 8)
  190. -- 转换为二进制数据
  191. local binaryData = hexToBinary(msgdata)
  192. -- 解析二进制数据
  193. result = string.unpack(">B", binaryData)
  194. -- 输出结果
  195. log.info("结果十六进制:", string.format("0x%02X", result))
  196. ----------------------------解析鉴权码
  197. msgdata = msg:sub(9, 10)
  198. -- 转换为二进制数据
  199. local binaryData = hexToBinary(msgdata)
  200. -- 解析二进制数据
  201. result = string.unpack(">B", binaryData)
  202. -- 输出结果
  203. log.info("鉴权码十六进制:", string.format("0x%02X", result))
  204. ----------------------------解析电话号码
  205. msgdata = msg:sub(11, 22)
  206. -- 转换为二进制数据
  207. local binaryData = hexToBinary(msgdata)
  208. -- 解析二进制数据
  209. local b1, b2, b3, b4, b5, b6 = string.unpack(">BBBBBB", binaryData)
  210. -- 格式化输出为 6 字节的十六进制字符串
  211. local TerminalPhoneNo = string.format("0x%02X%02X%02X%02X%02X%02X", b1, b2, b3, b4, b5, b6)
  212. -- 输出电话号码
  213. log.info("电话号码十六进制:", TerminalPhoneNo)
  214. ----------------------------解析标头流水号
  215. msgdata = msg:sub(23, 26)
  216. -- 转换为二进制数据
  217. local binaryData = hexToBinary(msgdata)
  218. -- 解析二进制数据
  219. local ManualMsgNum = string.unpack(">H", binaryData)
  220. -- 输出结果
  221. log.info("标头流水号十六进制:", string.format("0x%04X", ManualMsgNum))
  222. ----------------------------解析标尾流水号
  223. msgdata = msg:sub(23, 26)
  224. -- 转换为二进制数据
  225. local binaryData = hexToBinary(msgdata)
  226. -- 解析二进制数据
  227. local MsgNum = string.unpack(">H", binaryData)
  228. -- 输出结果
  229. log.info("标尾流水号十六进制:", string.format("0x%04X", MsgNum))
  230. ----------------------------解析鉴权码
  231. msgdata = msg:sub(33, 50)
  232. -- 转换为二进制数据
  233. local binaryData = hexToBinary(msgdata)
  234. local b1, b2, b3, b4, b5, b6, b7, b8, b9 = string.unpack(">BBBBBBBBB", binaryData)
  235. -- 格式化输出为 6 字节的十六进制字符串
  236. local JianquanCode = string.format("0x%02X%02X%02X%02X%02X%02X%02X%02X%02X", b1, b2, b3, b4, b5, b6, b7, b8, b9)
  237. -- 输出结果
  238. log.info("鉴权码十六进制:", JianquanCode)
  239. end
  240. --新建任务,每休眠2000ms继续一次
  241. sys.taskInit(function()
  242. --实验1:构建立通用应答数据帧,协议可参考JT808协议8.1与8.2
  243. --输入参数:
  244. --应答流水号为123,
  245. --应答 ID为456,
  246. --结果为1,
  247. local data = cmnRsp(123, 456, 1)
  248. HexOutput(data) --HexDataByteLength:5,HexData is:007b01c801 007b为123,01c8为456,1为1
  249. --实验2:构建终端注册数据帧,协议可参考JT808协议8.5
  250. --输入参数:
  251. --12, --省域 ID WORD
  252. --123, --市县域 ID WORD
  253. --'10001', --制造商 ID BYTE[5]
  254. --'GT808' --终端型号 BYTE[20]
  255. --'00000000000002', --终端 ID BYTE[7]
  256. --0, --车牌颜色 BYTE
  257. --'41048063212') --车辆标识 STRING VIN或车牌号均为固定长度,这里假设字符串长度为11
  258. local data = register(12,123,'10001','GT808','00000000000002',0,'41048063212')
  259. HexOutput(data)--[HexDataByteLength:] 48,[HexData is: ]
  260. --输出解析:
  261. --000c,对应12, --省域 ID WORD
  262. --007b,对应123, --市县域 ID WORD
  263. --3130303031,对应'10001', --制造商 ID BYTE[5]
  264. --4754383038,对应'GT808' --终端型号 BYTE[20],不足字节补0
  265. --0000000000
  266. --0000000000
  267. --0000000000
  268. --00000000000002 对应'00000000000002', --终端 ID BYTE[7]
  269. --000, --车牌颜色 BYTE
  270. --3431303438303633323132,对应'41048063212') --车辆标识 STRING
  271. --实验3:构建位置信息汇报数据帧,协议可参考JT808协议8.18
  272. --输入参数:
  273. --0, 报警标志 DWORD
  274. --1, 状态位 DWORD
  275. --12345678, 纬度 DWORD
  276. --87654321, 经度 DWORD
  277. --100, 高程 WORD
  278. --60, 速度 WORD
  279. --180, 方向 WORD
  280. --os.time(), 时间 BCD[6] YY-MM-DD-hh-mm-ss(GMT+8 时间,本标准中之后涉及的时间均采用此时区)
  281. --"extra"
  282. local time = timeToBCD();
  283. log.info("CurrentTime: ",time)
  284. local data = locRpt(0, 1, 12345678, 87654321, 100, 60, 180, time, "extra")
  285. HexOutput(data)--[HexDataByteLength:] 33,[HexData is: ] 000000000000000100bc614e05397fb10064003c00b42411241232046578747261
  286. --输出解析:
  287. --00000000,对应 0, 报警标志 DWORD,4字节
  288. --00000001,对应 1, 状态位 DWORD
  289. --00bc614e,对应 12345678, 纬度 DWORD
  290. --05397fb1, 对应 87654321, 经度 DWORD
  291. --0064,对应 100, 高程 WORD
  292. --003c,对应60, 速度 WORD
  293. --00b4,对应180, 方向 WORD
  294. --241124123204,对应时间 BCD[6] 24-11-24-12-32-04
  295. --6578747261,对应"extra"
  296. --实验4:封闭一个完整的JT808注册数据帧
  297. --输入参数:
  298. --'018068821447',手机号不足 12位,则在前补充数字,大陆手机号补充数字 0
  299. --'101',消息流水号
  300. local data = encodeReg('018068821447','101')
  301. HexOutput(data) --[HexDataByteLength:] 64
  302. --[HexData is: ] 7e01000030018068821447313031000c007b3130303031475438303800000000000000000000000000000000000000000002003431303438303633323132627e
  303. --输出解析:
  304. --7e,标识位固定为0x7e
  305. --0100,消息 ID
  306. --0030,消息体属性
  307. --018068821447,手机号
  308. --313031,消息流水号
  309. --000c007b3130303031475438303800000000000000000000000000000000000000000002003431303438303633323132,注册消息体
  310. --62,校验
  311. --7e,标识位固定为0x7e
  312. --实验5:解析JT808终端注册应答数据帧
  313. local hexStr = '7E8100000C0404571031660030003000736875616E67776569E07E'
  314. decodeCmnRsp(hexStr)
  315. end)
  316. -- 这里演示4G模块上网后,会自动点亮网络灯,方便用户判断模块是否正常开机
  317. sys.taskInit(function()
  318. while true do
  319. sys.wait(6000)
  320. if mobile.status() == 1 then
  321. gpio.set(27, 1)
  322. else
  323. gpio.set(27, 0)
  324. mobile.reset()
  325. end
  326. end
  327. end)
  328. -- 用户代码已结束---------------------------------------------
  329. -- 运行lua task,只能调用一次,而且必须写在末尾
  330. -- 结尾总是这一句
  331. sys.run()
  332. -- sys.run()之后后面不要加任何语句!!!!!