将硬件规定的通信协议用Lua实现(涉及到很多Lua通信的数据转换)
1:这次处理的是大唐的gps通信协议,先简单介绍一下他规定的通信规则:
信息结构:
传输说明:
信息结构中的各个字节书写时都是以十六进制标识,两位数组成。传输时,SOI和EOI(SOI=7EH,EOI=0DH)各按一个字节传输,但其余各项每个字节都是拆成两个字节,每个字节用两个ASCII码标识,即高4位用一个ASCII码表示,低4位用一个ASCII码标识,传输时先发送高4位的ASCII码,后发送低4位的ASCII码。
示例:CID2=4BH,4的ASCII码是34H,B的ASCII码是42H,传送时顺序发送34H和42H两个字节。
因此,实际传输的字节数应是表1以及下面各表中字节数乘以2。
2:要达到的目的:
例如要处理一段这样的数据:(需要计算的LCHkSUM和CHKSUM由46代替)
info="7E 32 31 30 31 44 30 30 30
46 30 31 45 ( length段(1个LCHKSUM+3个LENTGTHID))
30 30 30 36 33 32 36 31 42 37 30 31 44 34 42 42 42 39 30 31 45 41 30 30 30 30 30 30 30 30(数据段)
46 46 46 46 (CHKSUM)
0D"
我们不知道LCHKSUM是多少,也不知道后面的CHKSUM是多少但是知道其他,总不能手动去算吧,于是写了如下程序用来自动计算这两个CHKSUM
3:关于LENGTH段的解释:
LENGTH共2个字节,由LENID和LCHKSUM组成,LENID表示INFO项的ASCII码字节数,当LENID=0时,INFO为空,即无该项。LENGTH拆分4个ASCII码传送,先高字节,后低字节。校验码的计算:D11D10D9D8+D7D6D5D4+D3D2D1D0,求和后模16余数取反加1。
示例:
INFO项的ASCII码字节数为18,即LENID=0000 0001 0010B。
D11D10D9D8+D7D6D5D4+D3D2D1D0=0000B+0001B+0010B=0011B,模16余数为0011B,0011B取反加1就是1101B,即LCHKSUM为1101B。
可以得出:LENGTH为1101 0000 0001 0010B,即D012H。
代码实现:(利用length段的后面234字节算出chksum得到第一个字节)
function LengthID(ch1, ch2, ch3) local tmp = ""; tmp = tmp .. string.char(0x30) .. string.char(ch1) .. string.char(ch2) .. string.char(ch3); print("tmp==>",tmp); local HI = Parse2btye(string.sub(tmp,1,2)); local LO = Parse2btye(string.sub(tmp,3,4)); print(LO)--两个十六进制数1e转化为十进制30 print("lo==>",string.format("%x",LO)); print("hi==>",string.format("%x",HI)); local hh = bits.band(HI, 0x0F); local mid = bits.rshift(bits.band(LO, 0xF0),4);--z:加法时一定别带十六进制权(比如01e:并非10+0e,而是01+0e) local ll = bits.band(LO, 0x0F); print(string.format("%x",hh+mid+ll)) local cs = bits.bnot(hh+mid+ll, 0xFF)+1; local rh = bits.bor(bits.lshift(cs, 4),hh); return Hex2Ascii(rh);--返回rh的高位和低位,高位就是我们需要的LCHKSUM end
经过这个计算:上面给出例子中的info变成了:
info="7E 32 31 30 31 44 30 30 30 31 30 31 45 30 30 30 36 33 32 36 31 42 37 30 31 44 34 42 42 42 39 30 31 45 41 30 30 30 30 30 30 30 3046 46 46 460D"
4:关于CHKSUM的解释:
CHKSUM的计算是除SOI、EOI和CHKSUM外,其他字符按ASCII码值累加求和,所得结果模65536余数取反加1。CHKSUM拆分4个ASCII码传送,先高字节,后低字节。
示例:
收到或发送的字符序列为:“~20014043E00200FD3B\R”(“~”为SOI,\R为EOI),则最后5个字符FD3B\R中的FD3B为CHKSUM,计算方法是:
‘2’+‘0’+‘0’+……+‘E’+‘0’+‘0’+‘2’+‘0’+‘0’
=32H+30H+30H+……+45H+30H+30H+32H+30H+30H
=02C5H
将由16进制字符组成的字符串转化为该十六进制相对应的ascii字符串
function CheckSum(strlen,buffer)--z:特别注意传输时一个数据字节直接使用了两个ascii码表示,一个ascii码占一个字节,用两位十六进制数表示 --计算lengthchk并填充 x,y=LengthID(buffer[11],buffer[12],buffer[13]); print("lengthchk:",string.format("%x",x)) print("lengthchk:",string.format("%x",y)) buffer[10]=x print("uncheck table:") for key,value in pairs(buffer) do io.write(string.format("%x",value)," ") end print() local sum = 0.0; for i = 2,strlen-5 do sum = sum + buffer[i]; end print("checksum==>",string.format("%x",sum)); --取得sum的高位和低位 local hh = bits.rshift(bits.band(sum, 0xff00), 8); local ll = bits.band(sum, 0x00ff); --高位低位分别取反 local nhh = bit32.band(bit32.bnot(hh),0x000000ff); local nll = bit32.band(bit32.bnot(ll),0x000000ff); --低位+1不进位的话就直接加,要进位的话就加高位 if nll+1 <= 0xFF then chkh = nhh; chkl = nll+1; elseif nhh+1 <= 0xFF then chkh = nhh+1; chkl = 0; else chkh = 0; chkl = 0; end print("zzyh:",string.format("%x",chkh)); h1,h2=Hex2Ascii(chkh) print(string.format("%x",h1)) print(string.format("%x",h2)) print("zzyl:",string.format("%x",chkl)); l1,l2=Hex2Ascii(chkl); print(string.format("%x",l1)) print(string.format("%x",l2)) buffer[strlen-4]=h1 buffer[strlen-3]=h2 buffer[strlen-2]=l1 buffer[strlen-1]=l2 print("checked table:") for k,v in pairs(buffer) do io.write(string.format("%x",v)," ") end print() end
经过这个计算:上面给出例子中的info变成了:
info="7E 32 31 30 31 44 30 30 30 31 30 31 45 30 30 30 36 33 32 36 31 42 37 30 31 44 34 42 42 42 39 30 31 45 41 30 30 30 30 30 30 30 30 46 37 34 37 0D"
完了!哈哈
5:上面的两个处理函数涉及到的自定义函数
!(函数在这里没有分包,实际中是在不同的包中)
function utils.str2chr(str)--将由16进制字符组成的字符串转化为该十六进制相对应的ascii字符串 local ret=""; local tmp; print("undostring:",str); for w in string.gmatch(str,"%x+") do--循环的读取该串中的十六进制数据并且转化为字符,例如:0x32->2 tmp=string.sub(w,0); ret=ret..string.char(tonumber(tmp,16)); end print("done string:",ret); strlen=string.len(retStr); return strlen,retStr; end; function utils.str2table(str)--z:表格里面存的是每个字符对应的ascii数字编码 local RetTable = {}; if string.len(str) <= 0 then return nil; end; strlen,str = utils.str2chr(str); for i = 1, strlen do RetTable[i] = string.byte(string.sub(str, i, i + 1));--将ascii字符串每一个字符变为表的每一项,一个字符对应一个ascii数字编码 end; return strlen,RetTable; end; function Parse2btye(szStr) if string.byte(szStr,1) == 0x20 and string.byte(szStr,2) == 0x20 then return VALUE_INVALID; end local buf = ''; buf = commutils.Hex2Dec(string.sub(szStr, 1)); return string.byte(buf,1); end function Hex2Ascii(hex)--把两位十六进制表示的一个数据的高位和低位分别转为adcii码表示,高位对应一个adcii,低位对应一个ascii,该ascii又由一个两位十六进制数表示() local chartable = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; local low = bits.band(hex, 0x0f); local high = bits.rshift(bits.band(hex, 0xf0), 4);--z:与oxfo与运算并且右移四位去除低四位 local lowAsc = string.byte(chartable[low+1]); local highAsc = string.byte(chartable[high+1]); --print(string.format("%x: %x", lowAsc, highAsc)); return highAsc,lowAsc end
local function Byte2Hex(szByte) if szByte >= string.byte('0') and szByte <= string.byte('9') then return szByte - string.byte('0'); elseif szByte >= string.byte('a') and szByte <= string.byte('f') then return szByte - string.byte('a')+10; elseif szByte >= string.byte('A') and szByte <= string.byte('F') then return szByte - string.byte('A')+10; else print("ParseData error,szByte = ",string.format("%02x",string.byte(szByte))); return 0; end end local function Hex2Dec(szStr)--把两个十六进制数转化为一个long型数 local tmp1,tmp2; --if string.byte(szStr,1)==nil or string.byte(szStr,2)==nil then -- return 0; --else print("tmp1udo==>",string.byte(szStr,1)) tmp1 = Byte2Hex(string.byte(szStr,1)); print("tmp1==>",tmp1) tmp2 = Byte2Hex(string.byte(szStr,2)); print("tmp2udo==>",string.byte(szStr,2)) print("tmp2==>",tmp2) print("dec==>",tmp1*16 + tmp2); return string.char(tmp1*16 + tmp2); --end --return string.char(tmp1*16 + tmp2); end