由于是做环保相关的,有时需要对212协议进行拆包和解包。HJ212协议是一种字符串协议,数据传输通讯包主要由包头、数据段长度、数据段、CRC校验、包尾组成,其中“数据段”内容包括请求编码、系统编码、命令编码、密码、设备唯一标识、总包数、包号、指令参数。请求编码为请求的时间戳,系统编码ST统一规定为22,命令编码CN为该数据包的时间类型,访问密码、设备唯一标识在对接时由平台提供,指令参数为数据内容。通讯协议的数据结构如图4所示。
通讯协议的数据结构
图4 通讯协议的数据结构
6.1.1通讯包结构组成
名称
类型
长度
描述
包头
字符
2
固定为##
数据段长度
十进制整数
4
数据段的ASCII字符数。例如数据段的字符数为128,则写为“0128”
数据段
字符
0<=n<=9999
变长的数据
CRC校验
十六进制
4
数据段的校验结果,例如C901,如果CRC错,即执行超时
包尾
字符
2
回车换行(\r\n)
下面是一个使用C++写的212解析类 GB212:
GB212.h
#pragma once
#include
#include
// 国标 212
// 所有的通讯包都是由ASCII码
class GB212
{
public:
typedef std::vector GB212Array;
typedef std::vector DataItemArray;
// 切分数据
static void split_kv(DataItemArray& _return, const String& cp) {
auto arr1 = Math::Tools::split(cp, ";", true);
for (auto& i : arr1) {
StringMap item;
auto arr2 = Math::Tools::split(i, ",", true);
for (auto& j : arr2) {
auto arrkv = Math::Tools::split(j, "=", false);
if (arrkv.size() == 2) {
item.insert(std::make_pair(arrkv[0], arrkv[1]));
}
}
_return.emplace_back(item);
}
}
// 组合数据
static String join_kv(const DataItemArray& arr) {
StringArray item;
for (auto& i : arr) {
StringArray arrkv;
for (auto& j : i) {
arrkv.emplace_back(j.first + "=" + j.second);
}
item.emplace_back(Math::Tools::join(arrkv, ","));
}
return Math::Tools::join(item, ";");
}
// 数据区
struct DataCP {
DataCP() {}
DataCP(const String& s) {
DataItemArray arr;
split_kv(arr, s);
for (auto& i : arr) {
auto kvlen = i.size();
String key;
for (auto& j : i) {
// 对指定监测因子的项,统一使用因子代表
if (j.first == "PolId") {
key = j.second;
this->Value[key] = StringMap();
continue;
}
String name, field;
// 查询是否包含标准协议中的设备状态
auto f1 = j.first.find("SB");
auto f2 = j.first.find("-");
if (f2 != String::npos && f1 != 0) {
// a20004-Rtd name-field
name = j.first.substr(0, f2);
field = j.first.substr(f2 + 1);
// i11001-Info field
if (field == "Info") {
field = name;
}
else {
key = name;
}
}
else {
if (j.first == "DataTime") {
this->DataTime = j.second;
}
name = j.first;
key = name;
field = "value";
}
this->Value[key].insert(std::make_pair(field, j.second));
}
}
// 如果不包含DataTime字段,则将当前时间作为数据时间
if (this->Value.find("DataTime") == this->Value.end()) {
this->DataTime = Math::Date::getnow("%04d%02d%02d%02d%02d%02d");
}
}
void clear() {
this->Value.clear();
}
std::map Value;
String DataTime;
};
// 数据段
struct Data {
String QN; // 请求编码 20字符 QN=YYYYMMDDHHmmssZZZ
String ST; // 系统编码 5字符 ST=21
String CN; // 命令编码 7字符 CN=2011
String PW; // 访问密码 9字符 PW=123456
String MN; // 设备标识 27字符 MN=[0-9A-F]
String Flag = "4"; // 标志位 8整数 Flag=7 bit:000001(协议版本)0(是否有包号)0(是否需应答)
String PNUM; // 总包数 9字符 PNUM=0000 [不分包则没有本字段]
String PNO; // 包号 8字符 PNO=0000 [不分包则没有本字段]
String CP; // 指令参数 <=950字符 CP=&&数据区&&
DataCP CPs;
// 设置flag, bit从1开始
void set_flag(uint32 bit, bool enable) {
int32 flag = 4;
Math::Tools::to_int(flag, Flag);
enable ? SETBIT(flag, bit) : CLRBIT(flag, bit);
this->Flag = std::to_string(flag);
}
// 获取flag, bit从1开始
bool get_flag(uint32 bit) const {
int32 flag = 4;
Math::Tools::to_int(flag, Flag);
return GETBIT(flag, bit);
}
// 有效性
bool valid() const {
return bvalid;
}
// 长度
size_t size() const {
#define ADDDataItem(name, subnum) result += name.size() + (name.empty() ? 0 : subnum);
size_t result = 0;
ADDDataItem(QN, 3);
ADDDataItem(ST, 3);
ADDDataItem(CN, 3);
ADDDataItem(PW, 3);
ADDDataItem(MN, 3);
ADDDataItem(Flag, 5);
ADDDataItem(PNUM, 5);
ADDDataItem(PNO, 4);
ADDDataItem(CP, 3);
#undef ADDDataItem
return QN.size() + ST.size() + CN.size() + PW.size() + MN.size() + Flag.size() +
PNUM.size() + PNO.size() + CP.size();
}
// 构造函数
Data() {}
Data(const String& s){
CopyStr(s);
}
// 赋值构造
Data& operator=(const String& str) {
CopyStr(str);
return *this;
}
void CopyStr(const String& s) {
auto d1 = s.find("CP=&&");
auto d2 = s.find("&&", d1 + 5);
String tmp;
if (d1 != String::npos && d2 != String::npos) {
CP = s.substr(d1 + 5, d2 - d1 - 5);
CPs = CP;
tmp = s.substr(0, d1) + s.substr(d2 + 2);
}
else {
tmp = s;
}
StringMap it;
auto arr1 = Math::Tools::split(tmp, ";&