产品名称: | 环保HJ212-2017协议转Modbus方案 |
---|---|
规 格: | |
产品备注: | |
产品类别: | 环保HJ212-2017协议转Modbus |
点击量: | 1802 |
此方案符合污染物在线监控(监测)系统数据传输标准HJ212-2017规范。HJ212环保协议用于用于向数据中心上传锅炉、水质等各类环保数据,采用TCP/IP Client传输字符串的方式进行通讯。
此方案下,AiMaster网关作为Server,接受HJ212的客户端连接,并接收其上传的数据。然后利用自身强大字符串处理功能,解析出HJ212数据,并放入Modbus寄存器提供给上位机读取。上位机可以是各类组态软件、PLC、触摸屏等等。
也可以将解析出的HJ212数据转换为任何其他协议,例如:通过mqtt协议传输环保数据至云服务器等。
具体代码示例如下,此代码经过实际项目验证,可以直接下载到AiMaster网关运行。
--print("此例子演示启动可支持10个客户端的TCP/IP Server,并通过消息模式读取客户端string数据")
--启动TCP/IP Server,监听端口6003,消息模式,支持10个客户端。
--hj212_3020命令对应的Modbus寄存器地址
mb3020addr=0000
--hj212_2051命令对应的Modbus寄存器地址
mb2051addr=1000
--hj212_2061命令对应的Modbus寄存器地址
mb2061addr=2000
--启动智能Server,监听端口6003,超时20秒,支持10个客户端,消息方式处理数据。
res=lns.startserver(0,6003,20,10,1,1);
local itemlst={}
local maindata={}
local hj212value={}
local keyvallst={}
local itemdata=""
local headdata=""
local rawstr=""
local alldata=""
local rechj212state=0
local sendback=0
local arraydata={0,0}
local mbdatacnt=0
recconn=0
while(true) do
itemlst={}
maindata={}
hj212value={}
keyvallst={}
itemdata=""
headdata=""
rawstr=""
alldata=""
rechj212state=0
sendback=0
recconn=0
arraydata={0,0}
mbdatacnt=0
while(true) do
--网络处理循环
sendback=0
recconn=lns.srvwaitrec(0);
-- print(string.format("\r\n收到数据的HJ212客户端连接索引:%d",recconn));
rawstr=lns.srvreadrecstr(0,recconn)
-- print(string.format("\r\n收到HJ212数据:%s",rawstr));
while(true) do
--HJ212协议处理循环
--收到字符串为nil,超时错误。
if (rawstr==nil) then
rechj212state=0
break
end
--分割主数据,报文头之后的&&...&&之间为主数据
itemdata,headdata,crcdata,alldata=splitmaindata(rawstr)
---数据错误退出循环,重新接收。
if (itemdata==nil) or (headdata==nil) then
rechj212state=0
break
end
--判断报文头212标识,取命令码。
cncode=getheadinfo(headdata)
if cncode==nil then
rechj212state=0
break;
end;
print(cncode)
rechj212state=1
break;
end
if (rechj212state==0) then
--如果接收的数据错误,继续网络循环,
lns.srvresp(0)
else
--协议接收正常进入处理阶段
break
end
end
while(true) do
itemdata=itemdata..";"
--分割项目数据列表,数据存储在itemlist中
itemlst=splititemdata(itemdata)
if (itemlst==nil) then break end
--分割出HJ212的数据,格式为key=value,例如01-Avg=2.065
keyvallst=split212value(itemlst)
if (keyvallst==nil) then break end
if (cncode==2061) then
--处理2061命令
-- print("conv 2061")
convres,hj212datatime=convtovmb_2061(keyvallst)
-- print(hj212datatime)
print(convres)
sendback=1
end
if (cncode==2051) then
--处理2051命令
-- print("conv 2051")
convres,hj212datatime=convtovmb_2051(keyvallst)
-- print(hj212datatime)
print(convres)
sendback=1
end
if (cncode==3020) then
--处理3020命令
-- print("conv 3020")
convres,hj212datatime=convtovmb_3020(keyvallst)
print(convres)
sendback=1
end
break;
end
if (sendback==1) then
--返回HJ212应答报文
responstr=buildsenddata(headdata,hj212datatime)
-- print(responstr)
if (responstr~=nil) then
--向客户端返回数据,客户端索引reconn也是可以写数据的。
res=lns.srvwritestr(0,recconn,responstr)
end
end
--此客户端处理完毕
lns.srvresp(0);
end
函数定义:
local function getdelistrdata(rawstr,deli)
odata={}
zzbds="(.-)"..deli
if (rawstr==nil) then return nil end
--string.gmatch是支持正则表达式的字符串分割函数,更详细的使用方法请参考互联网上的相关教程。
--正则表达式"(.-),"中的.表示匹配任何字符,-表示短匹配,,为匹配分割符。
for w in string.gmatch(rawstr,zzbds) do
--w输出由,分割的字符序列。
table.insert(odata,w);
end
return odata
end
local function splitmaindata(rawstr)
--分割主数据,报文头之后的&&...&&之间为主数据
if ((string.byte(string.sub(rawstr,string.len(rawstr)-1)))~=13) or
((string.byte(string.sub(rawstr,string.len(rawstr))))~=10) then
return nil
end
s,e=string.find(rawstr, "&&")
if (s==nil) or (e==nil) then return nil end
--项目数据
itemdata=string.sub(rawstr,e+1,string.len(rawstr)-8)
--报文头数据
headdata=string.sub(rawstr,1,s-1)
--crc字节
crcdata=string.sub(rawstr,string.len(rawstr)-5,string.len(rawstr)-2)
--所有有效数据
alldata=string.sub(rawstr,7,string.len(rawstr)-6)
return itemdata,headdata,crcdata,alldata
end
local function getheadinfo(rawheaddata)
--判断报文头212标识
if string.sub(rawheaddata,1,2)~="##" then return nil end
headvalue=getdelistrdata(rawheaddata,";")
--取命令码CN
key,value = string.match(headvalue[2],"(.-)=(.+)")
if (value==nil) or (key~="CN") then
return nil
end
--口令
key,pwd = string.match(headvalue[3],"(.-)=(.+)")
if (value==nil) or (key~="PW") then
return nil
end
--设备号号码
key,mn = string.match(headvalue[4],"(.-)=(.+)")
if (value==nil) or (key~="MN") then
return nil
end
--返回命令码CN
return tonumber(value)
end
local function splititemdata(rawstr)
--分割项目数据列表
return getdelistrdata(rawstr,";")
end
local function split212value(itemlst)
--分割项目数据
--输入数据
--DataTime=20190704190046
--B02-Cou=81219.430
--01-Min=1.063, 01-Max=16.938, 01-Avg=2.065, 01-Cou=0.168, 01-ZsMin=0.771, 01-ZsMax=13.231, 01-ZsAvg=1.564, 01-ZsCou=0.127
local odata={}
for i=1,#itemlst do
--分割以,分割的key=value数据
itemlst[i]=itemlst[i]..","
keyvalelst=getdelistrdata(itemlst[i],",")
for i2=1,#keyvalelst do
-- print(keyvalelst[i2])
table.insert(odata,keyvalelst[i2]);
end
end
-- print(#odata)
return odata
end
local function convhj212tovmb(keyvaluelst,convmbregaddr)
--转换HJ212数据至Modbus
local fdata
local keyandvalue
local lhj212value={}
local i2=0
local lhj212datatime=""
--DataTime=20190704190046
--B02-Cou=81219.430
--01-Min=1.063, 01-Max=16.938, 01-Avg=2.065, 01-Cou=0.168, 01-ZsMin=0.771, 01-ZsMax=13.231, 01-ZsAvg=1.564, 01-ZsCou=0.127
--转换从第二个元素开始,第一个元素是时间和日期
key,lhj212datatime = string.match(keyvaluelst[1],"(.-)=(.+)")
for i=2,#keyvaluelst do
-- print(keyvaluelst[i])
--用正则表达式解析出key=value数据。
key,value = string.match(keyvaluelst[i],"(.-)=(.+)")
if (value==nil) then
return nil
end
--将字符串转换为float数据
fdata=tonumber(value)
if (fdata==nil) then
return nil
end
--输出至数组
table.insert(lhj212value,fdata);
end
for i=1,#lhj212value do
-- print(lhj212value[i])
-- print(convmbregaddr+((i-1)*2))
--将数组的值,放入Modbus寄存器,以供上位机读取。
lib_vmb.setvaluefc3(convmbregaddr+((i-1)*2),1,4,lhj212value[i]);
end
return #lhj212value,lhj212datatime
end
local function convtovmb_2051(keyvaluelst)
return convhj212tovmb(keyvaluelst,mb2051addr)
end
local function convtovmb_2061(keyvaluelst)
return convhj212tovmb(keyvaluelst,mb2061addr)
end
local function convtovmb_3020(keyvaluelst)
return convhj212tovmb(keyvaluelst,mb3020addr)
end
local function strtoarray (idata,odata)
for i=1,#idata do
table.insert(odata,string.byte(string.sub(idata,i,i)));
end
return #odata
end
--****************************************************************************************
--函 数: CRC16_Checkout
--描 述: CRC16 循环冗余校验算法。
--参 数 一: *puchMsg:需要校验的字符串指针
--参 数 二: usDataLen:要校验的字符串长度
--返 回 值: 返回CRC16 校验码
--****************************************************************************************/
local function calhj212crc(idata)
crc_reg = 0xFFFF
larraydata={0,0}
for i=1,#idata do
-- crc_reg = (crc_reg>>8) ^ puchMsg[i];
--右移8位
crc_reg =lib_bit.shr(crc_reg,8,1)
crc_reg =lib_bit.lxor(crc_reg,idata[i],1)
-- syslib.osresetwdog()
for j=0,7 do
-- check = crc_reg & 0x0001;
check=lib_bit.land(crc_reg ,0x0001,1)
-- crc_reg >>= 1;
crc_reg=lib_bit.shr(crc_reg,1,1)
--crc_reg=lib_bit.lxor(crc_reg,0xA001,1)
if(check==0x0001) then
-- crc_reg ^= 0xA001;
crc_reg=lib_bit.lxor(crc_reg,0xA001,1)
end
end
end
calc.wordtoarray(larraydata,crc_reg,1,0);
crcstr=string.format("%2x%2x",larraydata[1],larraydata[2])
return crcstr,crc_reg;
end
local function checkhj212crc(idata,icrcdata)
--检查212协议的crc校验是否正确
--输入的是212
--转换字符串为ANSI数组
checkarray={}
--idata="QN=20160801085000001;ST=91;CN=9014;PW=123456;MN=010000A8900016F000169DC0;Flag=4;CP=&&&&"
--idata="ST=31;CN=3020;PW=123456;MN=34080331WNZK03;Flag=1;CP=&&DataTime=20190704195800;T10-Info=894.427;T11-Info=966.605;T12-Info=900.502;T20-Info=1011.820;T21-Info=1008.292;T22-Info=999.574;T30-Info=1031.684;T31-Info=1052.480;T32-Info=1035.924&&"
strtoarray(idata,checkarray);
crcstr,crcword=calhj212crc(checkarray)
--print(crcstr)
--if (crcstr==icrcdata) then return 1 end
return 1
end
local function buildsenddata(headdata,idatetime)
--生成返回的应答数据
sendtobackdatafmt="QN=%s;ST=91;CN=9014;PW=%s;MN=%s;Flag=4;CP=&&&&"
headlst={}
headlst=getdelistrdata(headdata,";")
--口令
key,pwd = string.match(headlst[3],"(.-)=(.+)")
--设备号号码
key,mn = string.match(headlst[4],"(.-)=(.+)")
if (idatetime==nil) or (pwd==nil) or (mn==nil) then return nil end
respdata=string.format(sendtobackdatafmt,idatetime,pwd,mn);
resplen=string.len(respdata)
senddata=string.format("##%04d%s\r\n",resplen,respdata)
return senddata
end