前言
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.根据命令号CN 设置数据段CP
2.设置命令号CN
3.设置报文拆分包及应答标志Flag
4.报文打包
//HJ/T212标准打包
QString UserProtocol::protocolPack(int cn)
{
setPacketCP(contentPack(cn)); //报文内容CP设置 contentPack 根据命令号CN报文内容打包
setPacketCN(cn); //报文命令号CN设置
setReplyAndSplit(needReplyCN(cn),false); //报文Flag 是否应答,是否拆包设置
setFramePacket(packetPack()); //报文字符串设置 packetPack 报文打包
return framePack();
}
1.CP内容打包
数据区内容说明
字段对照表
根据不同命令号CP数据区内容不同。
如现场机通过2021命令号上传设备运行状态数据。数据段CP内容包含DataTime和SB1-RS等。
实际数据段内容和实际项目需求有关,需要用户自定义
//报文内容打包
QString UserContent::contentPack(int cn)
{
QString content;
content.clear();
if(DeviceDir==Upper) {
switch(cn) {
case 1000 : content = overTimeAndReCountSetPack(); break;
case 1011 : content = emptyContentPack(); break;
case 1012 : content = timeSetAndQueryPack(); break;
case 1013 : content = timeCalibrationRespondPack(); break;
case 1061 : content = emptyContentPack(); break;
case 1062 : content = realCycleSetQndQueryPack(); break;
case 1063 : content = emptyContentPack(); break;
case 1064 : content = minCycleSetAndQueryPack(); break;
case 1072 : content = passwordSetPack(); break;
case 2011 : content = emptyContentPack(); break;
case 2012 : content = emptyContentPack(); break;
case 2021 : content = emptyContentPack(); break;
case 2022 : content = emptyContentPack(); break;
case 2031 : content = historyRequestPack(); break;
case 2041 : content = historyRequestPack(); break;
case 2051 : content = historyRequestPack(); break;
case 2061 : content = historyRequestPack(); break;
case 3011 : content = analyzerRequestPack(); break;
case 3012 : content = analyzerRequestPack(); break;
case 3013 : content = analyzerRequestPack(); break;
case 3014 : content = analyzerRequestPack(); break;
case 3015 : content = analyzerRequestPack(); break;
case 3016 : content = analyzerRequestPack(); break;
case 3017 : content = analyzerTimeCalibrationRequestPack(); break;
case 3018 : content = analyzerSamplingCyclePack(); break;
case 3019 : content = analyzerRequestPack(); break;
case 3020 : content = analyzerRequestPack(); break;
case 3021 : content = analyzerRequestPack(); break;
case 3022 : content = informationRequestPack(); break;
case 9014 : content = emptyContentPack(); break;
}
} else if(DeviceDir==Lower) {
switch(cn) {
case 1011 : content = timeSetAndQueryPack(); break;
case 1013 : content = emptyContentPack(); break;
case 1061 : content = realCycleSetQndQueryPack(); break;
case 1063 : content = minCycleSetAndQueryPack(); break;
case 2011 : content = realDataUploadPack(); break;
case 2021 : content = manageFacilityStatusUploadPack(); break;
case 2031 : content = dayDataUploadPack(); break;
case 2041 : content = manageFacilityTimeUploadPack(); break;
case 2051 : content = minuteDataUploadPack(); break;
case 2061 : content = hourDataUploadPack(); break;
case 2081 : content = restartTimeUploadPack(); break;
case 3019 : content = analyzerSamplingCyclePack(); break;
case 3020 : content = analyzerSamplingStopTimeRespondPack(); break;
case 3021 : content = analyzerIdentifierUploadPack(); break;
case 3022 : content = informationUploadPack(); break;
case 9011 : content = requestRespondPack(); break;
case 9012 : content = resultRespondPack(); break;
}
}
return content;
}
报文拆分包及应答标志
//是否应答,是否拆包设置
void UserPacket::setReplyAndSplit(bool reply,bool split)
{
Flag = (version<<2);//version 0为HJ212-2005协议 1为HJ212-2017协议
Flag &= 0xFC;
if(split) {Flag |= 0x02;}
if(reply) {Flag |= 0x01;}
}
报文打包 packetPack
国标HJ/212-2005与2017通讯包组成相同,区别数据段有些差异。
详细内容请参考博客:【QT+HJ212】01:协议分析
//报文打包
QString UserPacket::packetPack()
{
if(device()==NULL) {
MN = deviceMN();
PW = devicePW();
ST = deviceST();
//Flag &= 0x03;
//Flag = (version()<<2);
} else {
MN = device()->deviceMN();
PW = device()->devicePW();
ST = device()->deviceST();
Flag &= 0x03;
Flag |= (device()->version()<<2);
}
QString packet;
packet.clear();
if(version()==0) { //HJ/T212-2005标准
if(direction()==Upper) { //服务器端
if(CN<9000) {
packet += QString("QN=%1;").arg(QN);
packet += QString("ST=%1;").arg(ST);
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("PW=%1;").arg(PW);
packet += QString("MN=%1;").arg(MN);
if(CN!=2012 && CN!=2022) {
packet += QString("Flag=%1;").arg(QString::number(Flag));
}
packet += QString("CP=&&%1&&").arg(CP);
} else if(CN==9013){
packet += QString("ST=91;");
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("PW=%1;").arg(PW);
packet += QString("MN=%1;").arg(MN);
packet += QString("Flag=0;");
packet += QString("CP=&&QN=%1&&").arg(QN);
} else if(CN==9014) {
packet += QString("ST=91;");
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("CP=&&QN=%1;%2&&").arg(QN).arg(CP);
}
} else if(direction()==Lower) { //数采仪端
if(CN<9000) {
if(CN==2072) {
packet += QString("QN=%1;").arg(QN);
packet += QString("ST=%1;").arg(ST);
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("PW=%1;").arg(PW);
packet += QString("MN=%1;").arg(MN);
packet += QString("Flag=%1;").arg(QString::number(Flag));
packet += QString("CP=&&%1&&").arg(CP);
} else {
packet += QString("ST=%1;").arg(ST);
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("PW=%1;").arg(PW);
packet += QString("MN=%1;").arg(MN);
if(CN>=2000 && CN<3000) {
packet += QString("CP=&&%1&&").arg(CP);
} else {
packet += QString("CP=&&QN=%1;%2&&").arg(QN).arg(CP);
}
}
} else if(CN==9011){
packet += QString("ST=91;");
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("PW=%1;").arg(PW);
packet += QString("MN=%1;").arg(MN);
packet += QString("Flag=0;");
packet += QString("CP=&&QN=%1;%2&&").arg(QN).arg(CP);
} else if(CN==9012) {
packet += QString("ST=91;");
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("PW=%1;").arg(PW);
packet += QString("MN=%1;").arg(MN);
packet += QString("CP=&&QN=%1;%2&&").arg(QN).arg(CP);
} else if(CN==9013) {
packet += QString("ST=91;");
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("PW=%1;").arg(PW);
packet += QString("MN=%1;").arg(MN);
packet += QString("CP=&&QN=%1&&").arg(QN);
}
}
} else if(version()==1) { //HJ/T212-2016标准
packet += QString("QN=%1;").arg(QN);
if(CN < 9000) {
packet += QString("ST=%1;").arg(ST);
} else {
packet += QString("ST=91;");
}
packet += QString("CN=%1;").arg(QString::number(CN));
packet += QString("PW=%1;").arg(PW);
packet += QString("MN=%1;").arg(MN);
if(CN < 9000) {
packet += QString("Flag=%1;").arg(QString::number(Flag));
if(Flag==6 || Flag==7) {
packet += QString("PNUM=%1;").arg(QString::number(PNUM));
packet += QString("PNO=%1;").arg(QString::number(PNO));
}
} else {
packet += QString("Flag=4;");
}
packet += QString("CP=&&%1&&").arg(CP);
}
return packet;
}
发送数据、分包上传
每帧数据帧的数据区CP最大为950。当数据内容过大时需要对数据进行拆包分包发送。
如:CN2031 日数据上传 因数据量过大 分包上传
//给指定服务器发送打包数据
void UserClientDevice::sendPackMsg(const QString & id, const QString & msg,bool reply,bool split)
{
Q_UNUSED(split)
if(msg.size() <= 950) //数据量小于900 900 为一阵数据最大长度
{
if(!msg.isEmpty()) {emit sendMsg(id,msg);}
}
else
{
QString Msg = msg;
bool ok;
int cn = fieldExtract(Msg,"CN",&ok).toInt();
Msg = getCP(Msg);
QString time = fieldExtract(Msg,"DataTime",&ok);
Msg = Msg.mid(Msg.indexOf("DataTime")+QString("DataTime").size()+time.size()+1); //DataTime之后的数据
if(ok)
{
int Pnum = Msg.size()/800 + 1;
QStringList spitData = Msg.split(";");
int spitNum = 0;
for(int i=1; i<=Pnum; i++)
{
QString Data = QString("DataTime=%1;").arg(time);
for(;Data.size()<800;)
{
if(spitNum>=spitData.size())
break;
Data.append(spitData.at(spitNum));
spitNum++;
}
QString pumpMsg;
Protocol->protocolPackHandleWidthPUMPPNO(&pumpMsg,Data,cn,reply,true,Pnum,i);
if(!msg.isEmpty()) {emit sendMsg(id,pumpMsg);}
}
}
}
}