HJ212-2017 对接与环境在线监测平台设计:报文、CRC、应答、预警和传输有效率

环保在线监测平台的数据来源是现场数采仪,接口协议是 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 可能半帧、多帧,也可能夹着垃圾字节。切帧的规则:

  1. 找包头 ##,前面的字节丢掉;
  2. 读 4 位长度,不是数字就丢掉这个 ##,从下一个字节重新找;
  3. 等够 6 + 长度 + 4 + 2 字节再处理,不够就等下一次 read;
  4. 检查包尾 \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 接入、超标预警、派单审核、钉钉通知和设备模拟器,拉下来就能跑完整流程:

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(自带数采仪模拟器)。后续文章首发在同名公众号「小物学长」。

相关推荐
171320330计算机毕设编程1 小时前
计算机毕业设计 基于springboot+vue的在线观鸟视频播放网站 LW PPT
java·大数据·vue.js·spring boot·算法·课程设计
IT枫斗者枫哥1 小时前
JDBC事务实测:操作日志写入失败时,让客户修改一起回滚
java
OxYGC1 小时前
Spring 面经系列 · 第 1 篇:Bean 生命周期、单例语义、依赖注入与线程安全
java·安全·spring
小物学长1 小时前
Java 程序员的 IoT 协议入门(一):物联网平台到底在做什么
java
const_qiu1 小时前
第四章 07:4.7 本章小结
java·运维·服务器·人工智能
鶴哥只手遮天1 小时前
Spring AI 2.0.1 深度解析-第 22 章 Spring Boot 集成:自动配置、Properties 与条件装配
java·人工智能·开源
WJ.Polar2 小时前
final、单例类、枚举类、抽象类、接口
java·开发语言
江湖有缘2 小时前
Docker实战 | 使用Docker部署Imagor Studio图片编辑工具
java·docker·eureka