什么是环保HJ212协议?
HJ212是由国家环保行业制定的数据传输标准协议,通常是通过TCP/P通讯方式进行数据传输的,数据传输报文主要由包头、数据段长度、数据段、CRC校验、包尾组成。重点解析的就是”数据段“的内容,内容包括请求编码(QN)、系统编码(ST)、设备唯一标识(MN)、密码(PW)、命令编码(CN)、指令参数(CP)。请求编码为请求的时间戳,系统编码ST统一规定为22,命令编码CN为该数据包的时间类型,访问密码、设备唯一标识在对接时由平台提供,指令参数为数据内容。
基本报文示例:
##0192QN=20230703220100923;ST=22:CN=2011:PW=123456;MN=BF0F022021090077:Flag=5:CP=&&DataTime=20230703220100:a34001-Rtd=48.5,a34001-Flag=N:a34004-Rtd=31.2,a34004-Flag=N:a34002-Rtd=48.5,a34002-FIaQ=N&&A881
协议包组成:
名称 类型 长度 描述
包头 字符 2 同定为##
数据段长度 十进制整数 A 数据段的ASCII字符数,例如:长192,则写为“0192
数据段 字符 0<=n<=1024 变长的数据
CRC校验 十六进制 4 数据段的校验结果
包尾 字符 2 回车换行()
标准化json:
{
“ST “: “22“,
“MN“:“BFOF022021090077“,
“PW“ : “123456“,
“CN“: “2011“,
“QN“:“20230703220100923“,
“Flag“ : “5“,
“CP“:{
“DataTime": “20230703220100“,
“a34001-Flag :“ N“,
“a34001-Rtd : “48.5“,
“a34002-Flag :“N“,
“a34002-Rtd : “48.5“,
“a34004-Flag :“N“,
“a34004-Rtd : “31.2“,
}
}
常用的标准码说明
1.后面都会加Rtd或Flag,一般是a01001-Rtd或a01001-Flag,其中Rtd是实际的值,Flag为状态一般为N
2.2.单位只是大部分的一个标准,可作为参考
编码(Rtd->数值,Flag->状态) 实际值 单位(参考)
a01001 温度 咖
a01002 湿度 Kpa
a01006 气压 m/s
a01007 风速 0
a01008 风向 ug/m
a34001 TSP扬尘 ug/ms
a34002 PM10 ug/m
a34004 PM2.5 mg/m
a21004 NOz mg/m*
a21005 NO 葱mg/m*
a21026 S0z mg/m*
a21026 S0z dB
LA 噪声 单位(参考)
php接收包解包(没有crc验证)到redis 序列化
<?php
// 设置 Redis 连接
$redisHost = '127.0.0.1';
$redisPort = 6379;
$redis = new Redis();
$redis->connect($redisHost, $redisPort);
// 创建一个 TCP 服务器
$host = '0.0.0.0'; // 监听所有网卡
$port = 21212; // 自定义端口(与212协议无直接关系,仅供示例)
$socket = stream_socket_server("tcp://$host:$port", $errno, $errstr);
if (!$socket) {
die("无法创建服务器: $errstr ($errno)\n");
}
echo "服务器已启动,正在监听 $host:$port...\n";
while ($conn = stream_socket_accept($socket)) {
$data = fread($conn, 2048); // 每次读取2048字节
if ($data) {
echo "接收到数据: $data\n";
// 数据解析(根据212协议对数据结构的定义)
$parsedData = parse212Data($data);
// 获取设备唯一标识(MN)
$MN = $parsedData['MN'] ?? 'unknown_device';
$parsedData = serialize($parsedData);
// 将解析后的数据存入 Redis,设置过期时间为300秒
$redis->setex('tsp_data_' . $MN, 300,$parsedData);
// 发送确认或回复
fwrite($conn, "数据接收成功,^_^,谢谢测试帅哥!\n");
}
fclose($conn);
}
fclose($socket);
function parse212Data($data) {
// 分离 CP 部分
// $data = substr($data, 6, strpos($data, '&&') - 6); // 提取数据段
$data= substr($data, 6);
$parts = explode('CP=&&', $data, 2);
$metadata = trim($parts[0]); // CP 之前的数据部分
$cpContent = isset($parts[1]) ? trim($parts[1]) : '';
// 解析 CP 之前的数据部分
$fieldsBeforeCP = explode(';', $metadata);
$result = [];
foreach ($fieldsBeforeCP as $field) {
if (strpos($field, '=') !== false) {
list($key, $value) = explode('=', $field, 2);
$result[$key] = $value;
}
}
// 解析 CP 内部的数据
if (!empty($cpContent)) {
$cpEndPos = strpos($cpContent, '&&');
if ($cpEndPos !== false) {
$cpData = substr($cpContent, 0, $cpEndPos);
// 用正则表达式精准分割 CP 内容
$fieldsInCP = preg_split('/[;,]/', $cpData);
foreach ($fieldsInCP as $field) {
if (strpos($field, '=') !== false) {
list($key, $value) = explode('=', $field, 2);
$result[$key] = $value;
}
}
}
}
return $result;
}
?>
python 发包测试
import socket
host = 'IP地址或者域名'
port = 21212
# 数据包
data = "##0530QN=20241119164503506;ST=39;CN=2051;PW=123456;MN=ZR118220915203;Flag=5;CP=&&DataTime=20241119163500;a01001-Min=12.54,a01001-Avg=12.87,a01001-Max=13.25,a01001-Flag=N;a01002-Min=51.30,a01002-Avg=52.49,a01002-Max=53.52,a01002-Flag=N;a01006-Min=102.301,a01006-Avg=102.301,a01006-Max=102.303,a01006-Flag=N;a01007-Min=0.0,a01007-Avg=0.0,a01007-Max=0.0,a01007-Flag=N;a01008-Min=165,a01008-Avg=165,a01008-Max=166,a01008-Flag=N;a34001-Min=100.9,a34001-Avg=100.9,a34001-Max=100.9,a34001-Flag=N;LA-Min=38.4,LA-Avg=39.8,LA-Max=44.1,LA-Flag=N&&6240"
##0530QN=20241119170103194;ST=39;CN=2061;PW=123456;MN=ZR118220915203;Flag=5;CP=&&DataTime=20241119160000;a01001-Min=11.14,a01001-Avg=13.20,a01001-Max=15.22,a01001-Flag=N;a01002-Min=45.44,a01002-Avg=51.41,a01002-Max=57.99,a01002-Flag=N;a01006-Min=102.299,a01006-Avg=102.315,a01006-Max=102.349,a01006-Flag=N;a01007-Min=0.0,a01007-Avg=0.0,a01007-Max=0.0,a01007-Flag=N;a01008-Min=130,a01008-Avg=165,a01008-Max=197,a01008-Flag=N;LA-Min=37.3,LA-Avg=40.7,LA-Max=53.5,LA-Flag=N;a34001-Min=103.1,a34001-Avg=103.1,a34001-Max=103.1,a34001-Flag=N&&EA41
# 创建 socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
s.sendall(data.encode())
# 接收服务器的响应
response = s.recv(1024)
print('服务器响应:', response.decode())