Administrator
发布于 2024-06-15 / 6 阅读
0
0

HJ 212-2017 协议精讲:从报文结构到工程落地

环保在线监控数据接入的事实标准。这篇笔记把 HJ 212-2017 的报文结构、交互机制、编码体系,以及实际对接中最容易踩的坑,一次性梳理清楚。

一、这份标准解决什么问题

HJ 212-2017《污染物在线监控(监测)系统数据传输标准》由国家环境保护部于 2017 年 4 月 24 日发布、2017 年 5 月 1 日实施,代替 HJ/T 212-2005。它规定了污染物在线监控(监测)系统、污染物排放过程(工况)自动监控系统与监控中心之间的数据传输过程,明确了参数命令、交互命令、数据命令、控制命令的格式与代码定义,同时也规定了在线监控仪器仪表与数据采集传输仪(数采仪)之间的传输格式。

相比 2005 版,这次修订有几个关键变化,做兼容时必须注意:

  • 适用范围扩展,新增污染物排放过程(工况)自动监控设备与监控中心之间的数据交换;
  • 新增数采仪与在线监控仪器仪表之间的通讯协议;
  • 新增现场机与上位机之间的数据传输网络;
  • 修改了设备唯一标识 MN 的描述;
  • 修改了是否拆分包及应答标志 Flag 的描述;
  • 删除了报警相关的字段、命令字与通讯示例;
  • 增加工况监控监测因子编码规则,明确系统编码与命令的分类并扩充;
  • 扩充执行结果定义表、请求返回表,新增实时数据标记定义表;
  • 单独列出《声环境监测因子编码表》,完善污水、烟气排放量及均值计算方法。

标准允许扩展,但扩展内容不得与标准中已使用或保留的控制命令冲突。

二、系统结构与协议层次

系统自下而上分三层:现场机 → 传输网络 → 上位机。有两种典型构成方式:

  1. 一台(套)现场机集监控(监测)、存储、通讯传输为一体,直接经传输网络与上位机交互;
  2. 现场有多套仪器仪表,经数字输出接口连接到独立的数采仪,上位机与数采仪通讯。

协议层次上有一个关键定位:HJ 212 对应 ISO/OSI 模型中的应用层,基础传输层建构在 TCP/IP 之上,整个应用层协议与具体通讯介质无关。也就是说 GPRS、ADSL、CDMA、WCDMA、TD-SCDMA、CDMA2000、PLC、TD-LTE、FDD-LTE、WiMAX 都可以承载。现网设备大多已换成 4G 或专线,但协议报文本身一字不用改——这也是这套标准能沿用至今的原因。

三、报文结构:外层五段,数据段九个字段

所有通讯包由 ASCII 字符组成(汉字除外,采用 UTF-8 编码,8 位 1 字节)。

3.1 外层结构

名称 类型 长度 说明
包头 字符 2 固定为 ##
数据段长度 十进制整数 4 数据段的 ASCII 字符数,如长度 255 写作 0255
数据段 字符 0 ≤ n ≤ 1024 变长,结构见下表
CRC 校验 十六进制整数 4 对数据段计算的结果,算法见附录 A
包尾 字符 2 固定为 <CR><LF>(回车、换行)

3.2 数据段结构

注意表里的"长度"包含字段名称 + = + 字段内容三部分。

名称 长度 说明
请求编码 QN 20 精确到毫秒的时间戳 YYYYMMDDhhmmsszzz,用来唯一标识一次命令交互
系统编码 ST 5 取值见《系统编码表》
命令编码 CN 7 取值见《命令编码表》
访问密码 PW 9 设备访问密码
设备唯一标识 MN 27 固化在设备中,由 EPC-96 编码转化而来,即 24 位 0~9、A~F 字符
拆分包及应答标志 Flag 8 位域,见下
总包数 PNUM 9 分包时才有,与 Flag 有关
包号 PNO 8 分包时才有,与 Flag 有关
指令参数 CP 0 ≤ n ≤ 950 CP=&&数据区&&

3.3 Flag 位域:一个字节塞了三件事

Flag 是 0~255 的整数,位定义如下:

位 含义
V5~V0 标准版本号。000000 表示 HJ/T 212-2005,000001 表示本次修订版本
A 命令是否应答:1 应答,0 不应答
D 是否包含数据包序号:1 数据段中含包号和总包数,0 不含

标准给的例子是 Flag=7:标准版本为本次修订版、数据段需要拆分、命令需要应答。实际对接中 Flag=5(本次修订版 + 不拆分 + 需要应答)最常见。

3.4 数据区语法

  • 字段与其值用 = 连接;
  • 同一项目的不同分类值之间用 , 分隔;
  • 不同项目之间用 ; 分隔;
  • 字段名区分大小写,单词首字符大写,其余小写。

数据类型记法需要先建立概念:C4 表示最多 4 位字符型字符串;N5 表示最多 5 位数字型字符串;N14.2 表示 14 位整数 + 2 位小数、带符号、最大长度 18 的可变长数字型;此外还有 YYYY/MM/DD/hh/mm/ss/zzz 分别表示日期的年、月、日与时间的小时、分、秒、毫秒。

四、交互机制:请求—应答 + 超时重发

4.1 应答模式

一条完整的命令由请求方发起、响应方应答组成,共六步:

  1. 请求方发送请求命令给响应方;
  2. 响应方接到请求后,向请求方发送请求应答(握手完成);
  3. 请求方收到请求应答后,等待响应方回应执行结果;未收到请求应答则按请求回应超时处理;
  4. 响应方执行请求操作;
  5. 响应方发送执行结果给请求方;
  6. 请求方收到执行结果,命令完成;没有接收到则按执行超时处理。

这里要划重点:"请求应答"和"执行结果"是两条独立消息。收到应答只代表对方愿意干活,业务是否成功要看执行结果里的 ExeRtn。

4.2 超时与重发

  • 请求命令发出后,在规定时间内未收到回应视为超时;
  • 超时后重发,重发超过规定次数仍未收到回应,视为通讯不可用,通讯结束;
  • 超时时间与重发次数均可根据通讯方式和任务性质自定义。

标准给出的缺省值:

通讯类型 缺省超时(秒) 重发次数
GPRS 10 3
CDMA 10 3
ADSL 5 3
WCDMA 10 3
TD-SCDMA 10 3
CDMA2000 10 3
PLC 10 3
TD-LTE 10 3
FDD-LTE 10 3
WIMAX 10 3

五、编码体系:系统、命令、返回码、数据标记

5.1 系统编码 ST

分四类:10~29 环境质量、30~49 环境污染源、50~69 工况、91~99 系统交互,A0~Z9 用于扩展。

系统名称 编码 系统名称 编码
地表水质量监测 21 大气环境污染源 31
空气质量监测 22 地表水体环境污染源 32
声环境质量监测 23 地下水体环境污染源 33
地下水质量监测 24 海洋环境污染源 34
土壤质量监测 25 土壤环境污染源 35
海水质量监测 26 声环境污染源 36
挥发性有机物监测 27 振动环境污染源 37
放射性环境污染源 38 工地扬尘污染源 39
电磁环境污染源 41 烟气排放过程监控 51
污水排放过程监控 52 系统交互(现场机与上位机交互) 91

5.2 命令编码 CN

共四类命令(请求、上传、通知、交互),编码分四组:

  • 1000~1999 初始化命令和参数命令;
  • 2000~2999 数据命令;
  • 3000~3999 控制命令;
  • 9000~9999 交互命令。

常用的几条:

命令名称 编码 方向
设置超时时间及重发次数 1000 上位 → 现场(请求)
提取现场机时间 1011 上位 → 现场(请求)
上传现场机时间 1011 现场 → 上位(上传)
设置现场机时间 1012 上位 → 现场(请求)
现场机时间校准请求 1013 现场 → 上位(通知)
提取 / 上传实时数据间隔 1061 请求 / 上传
设置实时数据间隔 1062 上位 → 现场(请求)
提取 / 上传分钟数据间隔 1063 请求 / 上传
设置分钟数据间隔 1064 上位 → 现场(请求)
设置现场机密码 1072 上位 → 现场(请求)
取 / 上传污染物实时数据 2011 请求 / 上传
停止察看污染物实时数据 2012 通知
请求应答 9011 响应方 → 请求方
执行结果应答 9012 响应方 → 请求方

5.3 请求命令返回 QnRtn

1 准备执行请求、2 请求被拒绝、3 PW 错误、4 MN 错误、5 ST 错误、6 Flag 错误、7 QN 错误、8 CN 错误、9 CRC 校验错误、100 未知错误。

排查现场问题时这张表非常好用——对方返回 3 就是密码不对,返回 9 就是你的 CRC 算错了,几乎可以做到秒级定位。

5.4 执行结果 ExeRtn

1 执行成功、2 执行失败但不知道原因、3 命令请求条件错误、4 通讯超时、5 系统繁忙不能执行、6 系统故障、100 没有数据。

5.5 数据标记:最容易被忽略、却最重要的一张表

标记 含义
N 在线监控(监测)仪器仪表工作正常
F 在线监控(监测)仪器仪表停运
M 仪器仪表处于维护期间产生的数据
S 手工输入的设定值
D 在线监控(监测)仪器仪表故障
C 仪器仪表处于校准状态
T 仪器仪表采样数值超过测量上限
B 仪器仪表与数采仪通讯异常

做数据接入时,绝不能只存数值、丢掉标记。同一个 -Rtd 值,标记是 N 和标记是 C,在有效性判别和报表统计里完全是两回事。

六、CRC16:算错一个字节,整包作废

标准采用 ANSI CRC16,简称 CRC16。生成步骤:

  1. CRC16 校验寄存器赋值为 0xFFFF;
  2. 取被校验串的第一个字节赋给临时寄存器;
  3. 临时寄存器与 CRC16 校验寄存器的高位字节做"异或"运算,结果赋给 CRC16 校验寄存器;
  4. 取 CRC16 校验寄存器最后一位赋给检测寄存器;
  5. 把 CRC16 校验寄存器右移一位;
  6. 若检测寄存器值为 1,则 CRC16 校验寄存器与多项式 0xA001 做"异或"运算,结果赋给 CRC16 校验寄存器;
  7. 重复步骤 4~6,直至移出 8 位;
  8. 取被校验串的下一个字节赋给临时寄存器;
  9. 重复步骤 3~8,直至所有字节均被校验;
  10. 返回 CRC16 校验寄存器的值。

校验码按照先高字节后低字节的顺序存放。

标准附录 A 给出的参考实现:

unsigned int CRC16_Checkout(unsigned char *puchMsg, unsigned int usDataLen)
{
    unsigned int i, j, crc_reg, check;

    crc_reg = 0xFFFF;
    for (i = 0; i < usDataLen; i++) {
        crc_reg = (crc_reg >> 8) ^ puchMsg[i];
        for (j = 0; j < 8; j++) {
            check = crc_reg & 0x0001;
            crc_reg >>= 1;
            if (check == 0x0001) {
                crc_reg ^= 0xA001;
            }
        }
    }
    return crc_reg;
}

生产代码里建议替换为查表法,避免在实时上报链路上产生不必要的 CPU 开销。

七、拆一条完整报文

标准附录 A 里那条经典示例:

##0101QN=20160801085857223;ST=32;CN=1062;PW=100000;MN=010000A8900016F000169DC0;Flag=5;CP=&&RtdInterval=30&&1C80\r\n

逐段拆开看:

  • ## —— 包头;
  • 0101 —— 数据段长度为 101 个 ASCII 字符(实测数据段确实是 101 字节,与字段一致);
  • QN=20160801085857223 —— 2016 年 8 月 1 日 8 时 58 分 57 秒 223 毫秒触发的一次命令交互;
  • ST=32 —— 地表水体环境污染源;
  • CN=1062 —— 设置实时数据间隔;
  • PW=100000 —— 访问密码;
  • MN=010000A8900016F000169DC0 —— 设备唯一标识;
  • Flag=5 —— 本次修订版本号、不分包、需要应答;
  • CP=&&RtdInterval=30&& —— 实时采样数据上报间隔 30 秒(标准规定取值 30 ≤ n ≤ 3600);
  • 1C80 —— 数据段的 CRC16 校验码;
  • \r\n —— 包尾。

顺便说个细节:标准正文在解释这条示例时写的是"1C08 为 CRC16 校验码",但按附录 A 的算法对数据段实际计算,结果应为 0x1C80,报文示例中给出的也是 1C80。以算法和报文示例为准——这种正文与示例不一致的地方,实现时一定要用实测数据兜底,别照抄文本。

再来看一条实时数据上传(CN=2011)的数据区:

CP=&&DataTime=20160801085857;w01001-Rtd=7.1,w01001-Flag=N;w01018-SampleTime=20160801070000,01018-Rtd=2.2,w01018-Flag=N,w01018-EFlag=A01;…&&
  • DataTime —— 本组数据的数据时间;
  • w01001-Rtd —— 某监测因子的实时值,w 前缀表示水类因子;
  • w01001-Flag=N —— 该因子的仪器数据标记,N 表示仪器工作正常;
  • w01018-SampleTime —— 采样时间(非连续测量仪器才有意义);
  • w01018-Flag=N —— 该因子的仪器数据标记;
  • w01018-EFlag=A01 —— 该因子的扩充数据标记,由仪器仪表厂商自行定义。

7.1 顺带说清一个极易混淆的点:-Flag 与 -EFlag 不是一回事

《字段对照表》里这两个是并列的独立字段,不是同一个东西的两种写法:

字段 含义 类型 取值说明
xxxxxx-Flag 监测仪器数据标记 A-Z/0-9,C1 见表 8,即 N/F/M/S/D/C/T/B
xxxxxx-EFlag 监测仪器扩充数据标记 A-Z/0-9,C4 由在线监控(监测)仪器仪表设备自行定义

区别很关键:

  • -Flag 的语义由标准统一规定,解析时必须按表 8 翻译,全行业一致;
  • -EFlag 是留给厂商的扩展位,标准不定义含义,同一个值在 A 厂和 B 厂可能完全是两回事,且"可以没有此项,根据实际情况确定"。

所以对接不同厂家的设备时,-EFlag 一定要逐家问清定义,绝不能想当然地当成"超标标记"来用——这是现场很常见的一个误读。

可以看到,同一因子通过 -Rtd、-Flag、-SampleTime、-EFlag 成组出现,用 , 分隔;不同因子之间用 ; 分隔,正好对应第 3.4 节的语法规则。

八、工程落地:七个必须处理好的点

  1. 分包重组。Flag 的 D 位决定数据段里是否带 PNUM/PNO。接收端必须先按 QN + 包序号完成重组,再交给业务层,否则长报文(比如历史数据补传)一定解析失败。
  2. QN 的复用与配对。一次命令交互的请求、请求应答、执行结果必须共用同一个 QN,它是配对的唯一依据。重发时不要重新生成 QN,否则对端会当成新命令。
  3. 区分应答与执行结果。CN=9011 只代表"我收到并准备执行",CN=9012 的 ExeRtn 才是业务结果。把它们混为一谈是极常见的实现错误。
  4. 编码规范。MN 固定 24 位十六进制;汉字用 UTF-8;数字型字段注意 N14.2 这类"带符号、带小数点、最大 18 位"的表述,不要按定长截断。
  5. CRC 校验失败的包直接丢弃。不要试图"修复"或跳过校验,否则脏数据会污染整个数据链。
  6. 数据标记必须入库。N/F/M/S/D/C/T/B 是后续解释异常值、做有效数据判别的唯一依据。
  7. 落库要做幂等。重发机制天然决定了同一份数据可能重复到达,建议按(MN + CN + DataTime + 因子编码)建唯一约束,把去重交给数据库。

九、小结

HJ 212-2017 的设计其实非常克制:外层只用一个 ## 包头 + 长度 + 数据段 + CRC + 包尾,数据段用 字段=值 的扁平结构,靠 QN 串联交互、靠 Flag 表达控制语义、靠编码表承载业务含义。理解了这四件事——报文骨架、QN 配对、Flag 位域、编码表——整套协议就没有黑盒了。

真正决定对接效率的往往不是协议本身,而是边角:分包重组有没有做、CRC 是不是严格校验、数据标记有没有保留、重发幂等有没有考虑。这几点做扎实,现场调试基本就是"通一遍就过"。


关于作者

我长期从事环保在线监控(监测)数据接入与上位机平台研发,熟练使用 HJ 212-2017 数据传输标准,对该标准的报文结构、应答与超时重发机制、系统编码与命令编码体系、分包与重组逻辑、CRC16 校验实现,以及水、气、声各类监测因子编码都有完整的实战经验;同时具备 HJ/T 212-2005 历史报文的兼容处理能力。

在工程实现上,我能独立完成从 TCP 长连接接入、报文解析与校验、分包重组、上下行命令交互(含 2011/2051 等数据命令与 1000/1011/1061 等参数命令)、异常数据标记判别,到数据落库与幂等设计的全链路开发,技术栈以 Java / Spring Boot 为主,并熟悉 SQL Server、Kingbase 等数据库。如果你正在做数采接入、协议对接或环保数据平台,欢迎交流。


评论