2 releases
Uses new Rust 2024
| 0.1.1 | Jan 15, 2026 |
|---|---|
| 0.1.0 | Jan 15, 2026 |
#1961 in Parser implementations
51KB
960 lines
a
A Rust library that implements the protocol layer of China MEE HJ 212 (ASCII messages).
This crate focuses on reusable building blocks:
- Receiving: extract frames from a TCP/serial byte stream (sticky packets) → parse a frame → get a structured
Hj212Packet - Sending: build
CP=&&...&&and payload via builders → wrap into an HJ212 frame (##{LEN}{PAYLOAD}{CRC})
Scope
Included:
- Framing:
##{LEN}{PAYLOAD}{CRC16_HEX}(HJ 212—2025 ANSI CRC16) - Parse/build:
parse_frame/build_frame - Streaming framer:
Framer - Builders:
CpBuilder/PayloadBuilder - Helpers: CRC functions, DataTime parsing
- Standard-alignment helpers:
build_frame_standard(uppercase CRC + trailing\r\n)parse_frame_strict(requires 4-digit length + CRC +\r\n)
- Appendix C helpers:
PNUM/PNOsupport and common ACK payload builders (build_qn_rtn,build_exe_rtn,build_data_ack,build_notify_ack) - Appendix A/H helpers (optional): encryption helpers under
crypto(see below)
Intentionally NOT included:
- Network I/O (Tokio/Actix/TCP server), serial port reading/writing
- Database/storage, platform-specific mapping, UI
- HTTPS upload logic (Appendix H multimedia upload)
Quick start
1) Parse a single complete frame
use a::{build_frame, parse_frame};
let payload = "QN=1;ST=22;CN=2011;PW=123456;MN=ABC;Flag=7;CP=&&DataTime=20250101010101;a21026-Rtd=12.3&&";
let frame = build_frame(payload);
assert!(frame.ends_with("\r\n"));
let pkt = parse_frame(&frame).unwrap();
assert_eq!(pkt.mn.as_deref(), Some("ABC"));
assert_eq!(pkt.cp.get("a21026-Rtd").map(String::as_str), Some("12.3"));
2) Extract frames from a byte stream
use a::{Framer, parse_frame};
let mut framer = Framer::new();
framer.push(b"##0025QN=1;ST=22;CN=2011;CP=&&&&");
while let Some(frame_bytes) = framer.next_frame() {
let frame_str = String::from_utf8(frame_bytes).expect("HJ212 is ASCII");
let _pkt = parse_frame(&frame_str)?;
}
# Ok::<(), a::Hj212Error>(())
3) Send-side: builders
use a::{CpBuilder, PayloadBuilder, parse_frame};
let mut cp = CpBuilder::new();
cp.data_time("20250101010101")
.rtd_flag("a21026", "12.3", "N")
.kv("a21026-Zs", "0.0");
let frame = PayloadBuilder::new(
"20251225123000001", // QN
"123456", // PW
"ABC", // MN
cp.build(),
)
.st("22")
.cn("2011")
.flag("7")
.frame();
let pkt = parse_frame(&frame).unwrap();
assert_eq!(pkt.mn.as_deref(), Some("ABC"));
Optional: SM4 auth encryption (Appendix H)
The standard’s multimedia upload appendix specifies:
Authorizationplaintext isusername:password- SM4-ECB + PKCS7Padding
- then Base64
Enable feature sm4:
a = { version = "0.1", features = ["sm4"] }
And use a::crypto::sm4_auth.
Notes on standard vs compat frames
build_frame(...)emits standard frames: 4-digit LEN + uppercase CRC + trailing\r\n.- If you must interop with a legacy variant without CRLF, use
build_frame_compat(...).
CRC16 and LEN (important)
LENis the ASCII byte length of the payload (the bytes between{LEN}and{CRC}), i.e.payload.as_bytes().len(). It does not include the##prefix, theLENdigits, the 4-byte CRC text, or the trailing\r\n.- CRC is computed over the payload bytes only using the HJ 212—2025 “ANSI CRC16” algorithm (poly
0xA001, init0xFFFF, and the update stepcrc = (crc >> 8) ^ byte). - For historical interop, this crate also exposes the CRC16/Modbus variant via
crc16_modbus_hex_upper/lower.
Standard example from the text (CRC = 2200):
##0087QN=20240601085857223;ST=32;CN=1011;PW=123456;MN=010000A8900016F000169DC0;Flag=9;CP=
&&&&2200\r\n
Notes on CP parsing
parse_frame*parsesCP=&&...&&intopkt.cpby splitting only on;intok=vpairs.- Some platform payloads group fields with commas (e.g.
a34006-Avg=... ,a34006-Flag=N). Those commas are intentionally left for the business layer to interpret.
Dependencies
~1.1–2.2MB
~40K SLoC