环保在线监测平台的数据来源是现场数采仪,接口协议是 HJ212(环保行业标准,2017 版)。这篇按"一帧报文进来之后平台要做的事"往下讲:报文结构、CRC、应答和重发、同一台设备的处理顺序、预警判断、传输有效率怎么算。代码来自开源项目 env-monitor(Spring Boot 3.5 + Netty 4.1),文末有地址。
一、报文结构
标准附录 A 的示例:
shell
##0101QN=20160801085857223;ST=32;CN=1062;PW=100000;MN=010000A8900016F000169DC0;Flag=5;CP=&&RtdInterval=30&&1C80\r\n
| 部分 | 示例 | 说明 |
|---|---|---|
| 包头 | ## |
固定 |
| 数据段长度 | 0101 |
4 位十进制,单位字节 |
| 数据段 | QN=...&& |
分号分隔的键值对,CP 用 && 包住 |
| CRC | 1C80 |
只算数据段,4 位大写十六进制 |
| 包尾 | \r\n |
固定 |
数据段的关键字段:
| 字段 | 含义 | 平台用来做什么 |
|---|---|---|
| QN | 请求编码,yyyyMMddHHmmssSSS | 应答原样带回,匹配请求和应答、重发去重 |
| ST | 系统编码(32 地表水体环境污染源、31 大气环境污染源、91 系统交互) | 区分监测类型 |
| CN | 命令编码(2011 实时、2051 分钟、2061 小时、2031 日、9014 数据应答...) | 分发到不同处理逻辑 |
| PW | 访问密码 | 设备鉴权 |
| MN | 设备唯一标识 | 找设备、找会话 |
| Flag | bit0 是否应答,bit1 是否分包,bit2~7 协议版本(2017 版为 1) | 决定回不回 9014、要不要拼包 |
Flag=5 是二进制 101:bit0=1 要求应答,bit1=0 不分包,bit2=1 表示 2017 版。
CP 里的监测数据,组之间用分号,组内用逗号:
ini
CP=&&DataTime=20261017083000;w01018-Rtd=28.6,w01018-Flag=N;w21003-Rtd=1.25,w21003-Flag=N&&
w01018 是因子编码(化学需氧量),Rtd 实时值,Flag=N 数据标记正常。分钟、小时数据用 Avg、Min、Max、Cou(累计值,流量用)。
二、CRC:和 MODBUS 只差一处
标准给的算法:
java
// env-hj212/.../Hj212Crc.java
public static int crc16(byte[] bytes) {
int crc = 0xFFFF;
for (byte b : bytes) {
crc = (crc >>> 8) ^ (b & 0xFF); // 和 crc >> 8 异或
crc = shift8(crc);
}
return crc;
}
private static int shift8(int crc) {
for (int j = 0; j < 8; j++) {
if ((crc & 1) != 0) {
crc = (crc >>> 1) ^ 0xA001;
} else {
crc >>>= 1;
}
}
return crc;
}
CRC-16/MODBUS 每个字节是和 crc 本身异或;HJ212 是和 crc >>> 8 异或。多项式、初值都一样,只这一处不同,所以直接套 MODBUS 的实现,算出来怎么都对不上。用上面的示例报文验证:数据段 101 字节,结果 1C80。
还有一个特点:每一步异或之前高 8 位都被移掉了,状态只剩 8 位有效,所以这个 CRC 的结果最多只有 256 种。它能发现传输中的随机错误,但别指望它区分内容,CRC 通过后仍要做格式校验。
三、切帧:半包、粘包和脏数据
TCP 是字节流,一次 read 可能半帧、多帧,也可能夹着垃圾字节。切帧的规则:
- 找包头
##,前面的字节丢掉; - 读 4 位长度,不是数字就丢掉这个
##,从下一个字节重新找; - 等够
6 + 长度 + 4 + 2字节再处理,不够就等下一次 read; - 检查包尾
\r\n和 CRC,不对就记录错误报文,回 9011(QnRtn=9 CRC 错误)或丢弃,不断开连接,继续找下一个包头。
长度字段只有 4 位,数据段最多 9999 字节;超过就要分包,Flag 的 bit1 置 1,数据段带 PNUM(总包数)、PNO(包号)。
四、应答和重发
| 方向 | 场景 | 应答 |
|---|---|---|
| 设备 → 平台 | 上报数据,Flag bit0=1 | 平台回 9014,QN 原样带回 |
| 平台 → 设备 | 请求类命令(取数据、设置参数) | 设备先回 9011(QnRtn 是否接受),执行完回 9012(ExeRtn 结果) |
| 平台 → 设备 | 通知类命令 | 设备回 9013 |
设备收不到 9014,会按 OverTime(超时秒数)、ReCount(重发次数)重发,平台要按 QN 去重,不然同一条数据会入库两次。平台下发命令同理:记下 QN,按 QN 匹配 9011/9012,超时没应答就重发,次数用完标记失败。
五、同一台设备的报文要按顺序处理
Netty 的 I/O 线程只负责切帧解码,入库、预警判断放到业务线程。但不能随便丢进一个线程池:同一台设备先后两帧如果被两个线程同时处理,"最新值"会被旧数据覆盖,"连续 3 次超标"也会数错。
做法是按 MN 分片,同一个 MN 永远进同一个单线程执行器:
java
// env-server/.../ingest/IngestService.java(节选)
private final ExecutorService[] workers = new ExecutorService[WORKERS];
// ......
workers[Math.floorMod(mn == null ? 0 : mn.hashCode(), WORKERS)].execute(() -> {
// 解析、鉴权、入库、预警判断、回 9014
});
不同设备之间并行,同一台设备内部串行,不需要加锁。
六、预警判断
| 类型 | 判断方式 |
|---|---|
| 超标 | 超过上限或低于下限,连续 N 次才报(排除单点毛刺) |
| 离线 | 超过规则设定的分钟数没有任何报文 |
| 数据恒值 | 连续 N 个值完全一样(探头卡死、数据造假的常见特征) |
| 数据突变 | 相邻两个值变化超过设定百分比 |
| 设备状态异常 | 数据标记不是 N(D 故障、M 维护、C 校准等) |
两个细节决定了预警能不能用:
- 合并:同一台设备同一个因子的同类预警,没关闭之前只累加次数,不重复派单;
- 停运报备:企业停产、设备检修、校准前在平台报备,报备期间不产生预警。不做这一条,每次检修都会产生一串离线和超标预警,误派单,处理人很快就不看通知了。
七、传输有效率怎么算
在线监测考核的核心指标之一。按小时判断:
| 状态 | 判断 |
|---|---|
| 有效 | 这一小时收到了分钟或小时数据,且数据标记全是 N |
| 无效 | 收到了,但有标记不是 N |
| 缺数 | 应该收到但没收到 |
| 不考核 | 停运报备的小时、设备登记之前、当前还没过完的小时 |
- 传输率 = 已传输小时 ÷ 应传输小时
- 有效率 = 有效小时 ÷ 已传输小时
- 传输有效率 = 传输率 × 有效率 = 有效小时 ÷ 应传输小时
例如一天应传 24 小时,收到 20 小时、其中 1 小时标记为 D:传输率 83.3%,有效率 95.0%,传输有效率 79.2%。
缺数的小时,设备在线时可以给它下发"提取分钟数据"(CN=2051,带 BeginTime、EndTime),设备补传后自动计入。这一步做成按钮,运维每天花几秒就能把缺口补上,不用等月底发现。
八、调试工具
对接新厂家的数采仪,最费时间的是来回抓包核对。平台里做了两个小工具:
- 报文解析:贴进一帧报文,校验长度和 CRC(错了会给出正确值),逐字段拆开,因子编码对照名称;
- 虚拟设备:自定义 MN、密码、因子和上报间隔,模拟一台数采仪连到 HJ212 服务端,实时显示收发的原始报文,可以固定某个因子的值、手动发自定义报文。
开源地址
项目 env-monitor 基础版已开源(AGPL-3.0),包含 HJ212 编解码(半包粘包、CRC、分包)、TCP 接入、超标预警、派单审核、钉钉通知和设备模拟器,拉下来就能跑完整流程:
- GitHub:github.com/xiaowu-iot/...
- Gitee(国内访问快):gitee.com/xiaowu-iot/...
- 在线演示:env.xiaowuiot.com(登录页一键登录体验账号,密码 demo123)
bash
git clone https://github.com/xiaowu-iot/env-monitor.git
cd env-monitor
mvn clean package -DskipTests
java -jar env-server/target/env-monitor.jar --env.simulator.auto-start=true
对接数采仪时,你遇到最头疼的是哪一类问题?欢迎在评论区聊聊。
示例代码已开源:GitHub / Gitee 搜索 env-monitor(自带数采仪模拟器)。后续文章首发在同名公众号「小物学长」。