a

Crates.ioa
lib.rsa
version0.1.1
created_at2026-01-15 12:53:12.890307+00
updated_at2026-01-15 12:59:46.557244+00
descriptionHJ 212 ASCII protocol: framing, parsing, CRC, and builders.
homepagehttps://git.sr.ht/~hkl/a
repositoryhttps://git.sr.ht/~hkl/a
max_upload_size
id2045499
size72,768
(hklo)

documentation

https://docs.rs/a

README

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/PNO support 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:

  • Authorization plaintext is username: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)

  • 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.
  • CRC is computed over the payload bytes only using the HJ 212—2025 “ANSI CRC16” algorithm (poly 0xA001, init 0xFFFF, and the update step crc = (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* parses CP=&&...&& into pkt.cp by splitting only on ; into k=v pairs.
  • 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.
Commit count: 0

cargo fmt