| Crates.io | a |
| lib.rs | a |
| version | 0.1.1 |
| created_at | 2026-01-15 12:53:12.890307+00 |
| updated_at | 2026-01-15 12:59:46.557244+00 |
| description | HJ 212 ASCII protocol: framing, parsing, CRC, and builders. |
| homepage | https://git.sr.ht/~hkl/a |
| repository | https://git.sr.ht/~hkl/a |
| max_upload_size | |
| id | 2045499 |
| size | 72,768 |
A Rust library that implements the protocol layer of China MEE HJ 212 (ASCII messages).
This crate focuses on reusable building blocks:
Hj212PacketCP=&&...&& and payload via builders → wrap into an HJ212 frame (##{LEN}{PAYLOAD}{CRC})Included:
##{LEN}{PAYLOAD}{CRC16_HEX} (HJ 212—2025 ANSI CRC16)parse_frame / build_frameFramerCpBuilder / PayloadBuilderbuild_frame_standard (uppercase CRC + trailing \r\n)parse_frame_strict (requires 4-digit length + CRC + \r\n)PNUM/PNO support and common ACK payload builders (build_qn_rtn, build_exe_rtn, build_data_ack, build_notify_ack)crypto (see below)Intentionally NOT included:
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"));
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>(())
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"));
The standard’s multimedia upload appendix specifies:
Authorization plaintext is username:passwordEnable feature sm4:
a = { version = "0.1", features = ["sm4"] }
And use a::crypto::sm4_auth.
build_frame(...) emits standard frames: 4-digit LEN + uppercase CRC + trailing \r\n.build_frame_compat(...).LEN is 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, the LEN digits, the 4-byte CRC text, or the trailing \r\n.0xA001, init 0xFFFF, and the update step crc = (crc >> 8) ^ byte).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
parse_frame* parses CP=&&...&& into pkt.cp by splitting only on ; into k=v pairs.a34006-Avg=... ,a34006-Flag=N). Those commas are intentionally left for the business layer to interpret.