HJ/T212是由国家环保行业制定的数据传输标准协议。
目前广泛使用的是HJ/T212-2005和HJ/T212-2017通信协议。
通信方式包括RS232、RS485、GPRS、TCP/IP等。
资料下载地址:https://download.csdn.net/download/qq_37373742/85747016
QT+HJ212专题学习地址
程序支持HJ212-2017协议/HJ212-2005协议
程序一:通过串口通讯方式实现上位机和现场机的数据交互,下图左边上位机右边是现场机
程序二:通过TCP通讯方式实现上位机和现场机的数据交互,下图左边上位机右边是现场机
数据帧解包最外层接口
1.判断是否数据帧完整
2.判断CRC校验是否通过、是否包含QN命令号字段
3.判断本次命令请求是否有错误 返回QnRtn
4.判断本次命令执行是否有错误 返回ExeRtn
//HJ/T212标准解包
int UserProtocol::protocolUnpack(const QString &buffer)
{
bool ok;
Frame.clear();
Frame = frameCheck(buffer,ok); //判断是否数据帧完整
if(ok) {
if(frameUnpack(Frame)) { //判断CRC校验是否通过、是否包含QN命令号字段
if(packetUnpack(framePacket())) { //判断请求结果
if(contentUnpack(packetCP(),packetCN())) { //判断执行结果
ok = true;
}
}
}
}
if(ok) {
return packetCN();
} else {
return -1;
}
}
1.数据帧检测 判断是否为完整数据
QString UserFrame::frameCheck(const QString &buffer,bool &ok)
{
QString frame;
ok = false;
frame.clear();
if(!buffer.isEmpty()) {
int head = buffer.indexOf("##",0,Qt::CaseSensitive);
int tail = buffer.indexOf("\r\n",0,Qt::CaseSensitive);
if((head != -1)&&(tail != -1)) {
frame = buffer.mid(head,tail+2-head);
ok = true;
}
}
return frame;
}
1.1字段解析 从数据帧中找出对应字段的内容
QString UserPacket::fieldExtract(const QString &packet,const QString &field,bool *ok)
{
QString value;
*ok = false;
int length = field.length() + 1;
int ps = packet.indexOf(field+"=",0);
int pe = packet.indexOf(";",ps+length);
if(field == "QN" && packet.contains("CN=9013"))
pe = packet.indexOf("&&",ps+length);
if((ps != -1) && (pe != -1)) {
ps += length;
value = packet.mid(ps,pe-ps);
*ok = true;
}
return value;
}
1.1.1使用方法:如查找SystemTime字段的内容
数据帧:
QString frame = "##0095ST=31;CN=1011;PW=123456;MN=399435XDLXG028;CP=&&QN=20220414010426069;SystemTime=20220414010003&&0780";
bool ok;
QString systemTime = fieldExtract(frame,"SystemTime",&ok);
2.数据帧检测 判断CRC校验是否通过、是否包含QN命令号字段
下面的req即9011命令号的QnRtn应答错误码 可以参考文章
【QT+HJ212】01:协议分析
bool UserFrame::frameUnpack(const QString &frame)
{
bool ok = false;
int req = -1;
if(!frame.isEmpty()) {
bool tmp;
bool QNOK;
Length = frame.mid(2,4).toInt(&tmp,10);
setPacketQN(fieldExtract(frame,"QN",&QNOK));
if(tmp && (Length+12 == frame.length())) {
Parity = frame.mid(Length+6,4).toInt(&tmp,16); //CRC16
Packet = frame.mid(6,Length); //数据帧
if(tmp && (Parity == packetVerifyCalculate(Packet))) {
ok = true;
} else {
req = 9;
}
}
else {
req = 9;
}
}
if(device()==NULL) {setRequest(req);} else {setRequest(req); device()->setRequest(req);}
return ok;
}
3.判断本次命令请求是否有错误 返回QnRtn
HJ212-2017的9011指令通过QnRtn返回应答结果对应的错误码
bool UserPacket::packetUnpack(QString packet)
{
QString mn,pw,st;
int ver,request;
bool dir;
//获取报文解包所需基本信息
if(device()==NULL) {
mn = deviceMN();
pw = devicePW();
st = deviceST();
ver = version();
CN = -1;
request = -1;
if(direction()==Upper) {dir = true;} else {dir = false;}
} else {
mn = device()->deviceMN();
pw = device()->devicePW();
st = device()->deviceST();
ver = device()->version();
device()->setCommand(-1);
request = -1;
if(device()->direction()==UserDevice::Upper) {dir = true;} else {dir = false;}
}
Flag = 0;
bool state = false;
//报文解包
if(!packet.isEmpty()) {
bool ok;
if(ver==0) { //HJ/T212-2005标准
if(dir) { //服务器端
//ST提取
if(request == -1) {
ST = fieldExtract(packet,"ST",&ok);
if(!ok) {request = 5;}
}
//CN提取
if(request == -1) {
CN = fieldExtract(packet,"CN",&ok).toInt();
if(!ok) {request = 9;}
}
if(request == -1) {
if(CN < 9000) {
if(ST != st) {request = 5;}
} else {
if(ST != "91") {request = 5;}
}
}
if(CN<2000 || CN>=3000) {
//QN提取
if(request == -1) {
QN = fieldExtract(packet,"QN",&ok);
if(!ok || (ok && QN.length()!=17)) {request = 8;}
}
}
//PW提取
if(request == -1) {
PW = fieldExtract(packet,"PW",&ok);
if(!ok || (ok && PW.length()!=6) || (ok && PW.length()==6 && PW!=pw)) {request = 3;}
}
//MN提取
if(request == -1) {
MN = fieldExtract(packet,"MN",&ok);
//if(!ok || (ok && MN.length()!=14) || (ok && MN.length()==14 && MN!=mn)) {request = 4;}
if(!ok || MN!=mn) {request = 4;}
}
if(CN==2072 || CN==9011) {
//Flag提取
if(request == -1) {
Flag = fieldExtract(packet,"Flag",&ok).toInt();
if(!ok || (ok && (Flag>>2)!=ver)) {request = 6;}
}
}
//CP提取
if(request == -1) {
int ps = packet.indexOf("CP=&&",0);
int pe = packet.indexOf("&&",ps+5);
if((ps != -1) && (pe != -1)) {
pe += 2;
CP = packet.mid(ps,pe-ps);
} else {request = 100;}
}
if(request == -1 && (Flag&0x01)==0x01) {request =1;}
//qDebug() << "request" << request << Flag << ((Flag&0x01)==0x01);
if(device()!=NULL) {
setRequest(request);
device()->setRequest(request);
}
if(request==-1 || request==1) {state = true;}else {state = false;}
} else { //数采仪端
//QN提取
if(request == -1) {
QN = fieldExtract(packet,"QN",&ok);
if(!ok || (ok && QN.length()!=17)) {request = 8;}
}
//ST提取
if(request == -1) {
ST = fieldExtract(packet,"ST",&ok);
if(!ok) {request = 5;}
}
//CN提取
if(request == -1) {
CN = fieldExtract(packet,"CN",&ok).toInt();
if(!ok) {request = 9;}
}
if(request == -1) {
if(CN < 9000) {
if(ST != st) {request = 5;}
} else {
if(ST != "91") {request = 5;}
}
}
if(CN!=9014) {
//PW提取
if(request == -1) {
PW = fieldExtract(packet,"PW",&ok);
if(!ok || (ok && PW.length()!=6) || (ok && PW.length()==6 && PW!=pw)) {request = 3;}
}
//MN提取
if(request == -1) {
MN = fieldExtract(packet,"MN",&ok);
//if(!ok || (ok && MN.length()!=14) || (ok && MN.length()==14 && MN!=mn)) {request = 4;}
if(!ok || MN!=mn) {request = 4;}
}
}
if(CN!=9014 && CN!=2012 && CN!=2022) {
//Flag提取
if(request == -1) {
Flag = fieldExtract(packet,"Flag",&ok).toInt();
if(!ok || ((Flag>>2)!=ver)) {request = 6;}
}
}
//CP提取
if(request == -1) {
int ps = packet.indexOf("CP=&&",0);
int pe = packet.indexOf("&&",ps+5);
if((ps != -1) && (pe != -1)) {
pe += 2;
CP = packet.mid(ps,pe-ps);
} else {request = 100;}
}
if(request == -1 && (Flag&0x01)==0x01) {request =1;}
//qDebug() << "request" << request << Flag << ((Flag&0x01)==0x01);
if(device()!=NULL) {
setRequest(request);
device()->setRequest(request);
}
if(request==-1 || request==1) {state = true;}else {state = false;}
}
} else if(ver==1) { //HJ/T212-2016标准
//QN提取
if(request == -1) {
QN = fieldExtract(packet,"QN",&ok);
if(!ok || (ok && QN.length()!=17)) {request = 8;}
}
//ST提取
if(request == -1) {
ST = fieldExtract(packet,"ST",&ok);
if(!ok) {request = 5;}
}
//CN提取
if(request == -1) {
CN = fieldExtract(packet,"CN",&ok).toInt();
if(!ok) {request = 8;}
}
if(request == -1) {
if(CN < 9000) {
if(ST != deviceST()) {request = 5;}
} else {
if(ST != "91") {request = 5;}
}
}
//PW提取
if(request == -1) {
PW = fieldExtract(packet,"PW",&ok);
if(!ok || (ok && PW.length()!=6) || (ok && PW.length()==6 && PW!=pw)) {request = 3;}
}
//MN提取
if(request == -1) {
MN = fieldExtract(packet,"MN",&ok);
//if(!ok || (ok && MN.length()!=24) || (ok && MN.length()==24 && MN!=mn)) {request = 4;}
if(!ok || MN!=mn) {request = 4;}
}
//Flag提取
if(request == -1) {
Flag = fieldExtract(packet,"Flag",&ok).toInt();
if(!ok || (ok && (Flag>>2)!=ver)) {request = 6;}
}
//PNUM提取
if((request == -1)&&((Flag&0x02)==0x02)) {
PNUM = fieldExtract(packet,"PNUM",&ok).toInt();
if(!ok) {request = 100;}
}
//PNO提取
if((request == -1)&&((Flag&0x02)==0x02)) {
PNO = fieldExtract(packet,"PNO",&ok).toInt();
if(!ok) {request = 100;}
}
if((request == -1) && (PNUM < PNO)) {request = 100;}
//CP提取
if(request == -1) {
int ps = packet.indexOf("CP=&&",0);
int pe = packet.indexOf("&&",ps+5);
if((ps != -1) && (pe != -1)) {
pe += 2;
CP = packet.mid(ps,pe-ps);
} else {request = 100;}
}
if(request == -1 && (Flag&0x01)==0x01) {request =1;}
//qDebug() << "request" << request << Flag << ((Flag&0x01)==0x01);
if(device()!=NULL) {
setRequest(request);
device()->setRequest(request);
}
if(request==-1 || request==1) {state = true;}else {state = false;}
}
}
return state;
}
4.判断本次命令执行是否有错误 返回ExeRtn
上位机和现场机有不同的解包方式
HJ212-2017的9012指令通过ExeRtn返回执行结果对应的错误码
报文内容解包
bool UserContent::contentUnpack(const QString &content,int cn)
{
//qDebug() << "contentUnpack " << content;
bool ok = false;
if(DeviceDir==Upper) { //上位机解包
switch(cn) {
case 1011 : ok = timeSetAndQueryUnpack(content); break;
case 1013 : ok = emptyContentUnpack(content); break;
case 1061 : ok = realCycleSetAndQueryUnpack(content); break;
case 1063 : ok = minCycleSetAndQueryUnpack(content); break;
case 2011 : ok = realDataUploadUnpack(content); break;
case 2021 : ok = manageFacilityStatusUploadUnpack(content); break;
case 2031 : ok = dayDataUploadUnpack(content); break;
case 2041 : ok = manageFacilityTimeUploadUnpack(content); break;
case 2051 : ok = minuteDataUploadUnpack(content); break;
case 2061 : ok = hourDataUploadUnpack(content); break;
case 2081 : ok = restartTimeUploadUnpack(content); break;
case 3019 : ok = analyzerSamplingCycleUnpack(content); break;
case 3020 : ok = analyzerSamplingStopTimeRespondUnpack(content); break;
case 3021 : ok = analyzerIdentifierUploadUnpack(content); break;
case 3022 : ok = informationUploadUnpack(content); break;
case 9011 : ok = requestRespondUnpack(content); break;
case 9012 : ok = resultRespondUnpack(content); break;
}
} else if(DeviceDir==Lower) { //现场机解包
switch(cn) {
case 1000 : ok = overTimeAndReCountSetUnpack(content); break;
case 1011 : ok = emptyContentUnpack(content); break;
case 1012 : ok = timeSetAndQueryUnpack(content); break;
case 1013 : ok = timeCalibrationRespondUnpack(content); break;
case 1061 : ok = emptyContentUnpack(content); break;
case 1062 : ok = realCycleSetAndQueryUnpack(content); break;
case 1063 : ok = emptyContentUnpack(content); break;
case 1064 : ok = minCycleSetAndQueryUnpack(content); break;
case 1072 : ok = passwordSetUnpack(content); break;
case 2011 : ok = emptyContentUnpack(content); if(ok) Device->setRealUpload(true); break;
case 2012 : ok = emptyContentUnpack(content); if(ok) Device->setRealUpload(false); break;
case 2021 : ok = emptyContentUnpack(content); if(ok) Device->setPollutionControlUpload(true);break;
case 2022 : ok = emptyContentUnpack(content); if(ok) Device->setPollutionControlUpload(false);break;
case 2031 : ok = historyRequestUnpack(content); break;
case 2041 : ok = historyRequestUnpack(content); break;
case 2051 : ok = historyRequestUnpack(content); break;
case 2061 : ok = historyRequestUnpack(content); break;
case 3011 : ok = analyzerRequestUnpack(content); break;
case 3012 : ok = analyzerRequestUnpack(content); break;
case 3013 : ok = analyzerRequestUnpack(content); break;
case 3014 : ok = analyzerRequestUnpack(content); break;
case 3015 : ok = analyzerRequestUnpack(content); break;
case 3016 : ok = analyzerRequestUnpack(content); break;
case 3017 : ok = analyzerTimeCalibrationRequestUnpack(content); break;
case 3018 : ok = analyzerSamplingCycleUnpack(content); break;
case 3019 : ok = analyzerRequestUnpack(content); break;
case 3020 : ok = analyzerRequestUnpack(content); break;
case 3021 : ok = analyzerRequestUnpack(content); break;
case 3022 : ok = informationRequestUnpack(content); break;
case 9013 : ok = true; Result = 0; break;
case 9014 : ok = emptyContentUnpack(content); break;
}
}
return ok;
}
4.1举例说明:
上位机接受到现场机1011命令回复时,会进入函数timeSetAndQueryUnpack
执行成功返回1,执行失败返回3
//数采仪时间设置/查询解包(1012/1011)
bool UserContent::timeSetAndQueryUnpack(const QString &content)
{
int version,result;
QDateTime systemTime;
if(Device==NULL) {
version = Version;
} else {
version = Device->version();
}
bool state = false;
if(!content.isEmpty()) {
if(version==0 || version==1) { //HJ/212-2005或HJ/212-2016标准
result = 1;
bool ok;
QString time = fieldExtract(content,"SystemTime",&ok);
if(ok) {
systemTime = datetimeExtract(time);
} else {
result = 3;
}
state = true;
}
}
Result = result;
SystemTime = systemTime;
if(Device!=NULL) {
Device->setResult(result);
Device->setSystemTime(systemTime);
}
return state;
}
当接受数据帧通过校验并得到请求结果和执行结果后,程序需要对命令进行相应回复
上位机响应现场机指令
根据Request和Result的值来执行不同的响应
当Result为1,表示成功执行现场机请求
当Request为1,表示需要响应现场机
void UserDevice::receiveMessage(const QString &msg)
{
if(DeviceDir==Upper) {
emit protocolUnpack(msg); //命令解包 获取result
qDebug() << "服务器解包: Request" << Request << "Result" << Result << "Command" << Command;
//请求指令执行
//qDebug() << __FUNCTION__ << Command << Result;
if(Result==1) {
if(Request == 1) { //判断是否需要回应请求
bool PNUMOk;
bool PNOOk;
bool FlagOk;
QString PNUM = fieldExtract(msg,"PNUM",&PNUMOk);
QString PNO = fieldExtract(msg,"PNO",&PNOOk);
int Flag = getbit(fieldExtract(msg,"Flag",&FlagOk).toInt(),0);
if((!PNUMOk && !PNOOk) || (PNUM == PNO && PNUMOk && PNOOk ))
{
//if(FlagOk && Flag == 1)
//{
QString frame;
if(Command == 1013 || Command == 2012 || Command == 2022)
DevicePackAndSend("",&frame,9013,false,false); //回复应答ST=91 CN=9014
else
DevicePackAndSend("",&frame,9014,false,false); //回复应答ST=91 CN=9014
//}
}
}
if(ReceiveCommandHandle()) { //判断是否需要回应请求
QString frame;
DevicePackAndSend("",&frame,Command,needReplyCN(Command),false); //回复不同命令
}
} else {
emit runningLog(QString("接收帧错误,ErrorCode=%1").arg(Result));
}
}
}
现场机响应上位机指令
根据Request和Result的值来执行不同的响应
当Result为1,表示成功执行上位机请求
当Request大于0,表示需要响应上位机
//数采仪接收消息
void UserDevice::receiveMessage(const QString &id,const QString &msg)
{
if(DeviceDir==Lower) {
emit protocolUnpack(msg); //解析数据帧
//不需要回复服务器的应答信号
if(Command == 9014)
return;
qDebug() << "客户端解包: Request" << Request << "Result" << Result << "Command" << Command;
//发送请求响应
if(Request>0 || (Version == 0 && (Command == 2012 || Command == 2022))) {
QString frame;
if(Command == 1013 || Command == 2012 || Command == 2022)
emit DevicePackAndSend(id,&frame,9013,false,false); //回复应答ST=91 CN=9014
else
emit DevicePackAndSend(id,&frame,9011,false,false);
}
//请求指令执行
if(Result==0) {
if(ReceiveCommandHandle()) {
QString frame;
emit DevicePackAndSend(id,&frame,Command,false,false); //目前默认回复的数据 需要应答
}
}
//发送执行结果
if(Result == 1) {
QString frame;
emit DevicePackAndSend(id,&frame,9012,false,false);
}
}
}