CRC-protected JSON and text line framing for embedded serial links
Project description
crclink
CRC-protected JSON and text line framing for serial-style embedded links.
crclink ships both sides of the wire: a Python library for the host and a C firmware companion for the device, so the same frames build, verify, and decode on either end.
- Host (Python): this package, with no runtime dependencies. Encode, decode, and verify frames, plus a
crclinkCLI. Install withuv add crclink. - Device (C): a no-heap, no-runtime-dependency implementation under src/c/. It builds and CRC-stamps frames straight to a serial sink and verifies/reads incoming flat-JSON commands. See src/c/README.md.
Both sides compute CRC-16/XMODEM over the same coverage, so a frame built on one verifies on the other; the test suites cross-check both directions.
Features
- JSON line framing with a trailing crc key.
- Text line framing with trailing CRC suffix.
- CRC-16/XMODEM validation.
- Integer payloads: fractional values ride as scaled integers, so the bytes (and the CRC) match byte-for-byte on the host and the device.
- Matching Python (host) and C (device) implementations.
Scaled integers
A frame carries integers, strings, bools, and null. A fractional value rides as an integer with a unit both ends agree on, which keeps the bytes (and so the CRC) identical on the host and the device. For a temperature in 0.1° steps, send tenths of a degree: 23.4° goes on the wire as 234, and the receiver divides by 10.
crclink encode-json '{"temp_dC":234}' # 23.4 °C in 0.1° steps
# -> {"temp_dC":234,"crc":"03bf"}
The same idea covers millivolts not volts, cents not dollars, microseconds not seconds. The scale is a convention the two ends share; the CRC protects the integer bytes, not their meaning, so document the unit per field.
CRC engine
crclink does not reimplement CRC-16/XMODEM. It vendors a self-contained module that crcglot generates (src/crclink/crc16_xmodem.py), so the wheel installs with no runtime dependencies. crcglot owns the algorithm and its parameters and emits the code; crclink ships the generated copy and regenerates it whenever crcglot is bumped. The C firmware vendors the same crc16-xmodem the same way, so both ends stay in lockstep.
crcglot is a build-time code generator here (a dev dependency, crcglot>=0.22.0), not something the package imports at run time. See docs/crcglot-integration.md for the integration details: both the host and the firmware vendor generated code, plus the cross-end test vectors.
CLI
Installing crclink puts a crclink command on your path. It encodes and decodes single frames, and verifies a whole file of them.
# Encode (prints the framed line)
crclink encode-json '{"t":1234,"v":42}' # -> {"t":1234,"v":42,"crc":"1352"}
crclink encode-text "PING" # -> PING e0e7
crclink encode-text "PING" --prefix 0x # -> PING 0xe0e7
# Decode and verify one frame (prints a JSON result, exit 1 on a bad CRC)
crclink decode-json '{"t":1234,"v":42,"crc":"1352"}'
crclink decode-text "PING e0e7"
# Verify every frame in a file, line by line
crclink verify-file frames.jsonl --format json # a file of JSON lines
crclink verify-file log.txt --format text # a file of text lines
crclink verify-file mixed.lines # auto-detect each line
cat frames | crclink verify-file - # read from stdin
verify-file skips blank lines, reports each line as ok or FAIL with its number and reason, prints a verified X/Y summary, and exits non-zero if any line fails (so it slots into a shell pipeline). With auto (the default) a line starting with { is treated as JSON and anything else as text. Use --quiet to print only failures and the summary.
On the device (C)
The firmware speaks the same frames both ways: verify an incoming command, read its fields by key, then build and CRC-stamp the reply. Numbers are converted for you, with no heap and no runtime dependencies. Here a host sends the monitor command mem byte 0x1234 and the device returns the byte at that address (cmd: uint32 -> v: uint8):
#include "crclink_json.h" // build the reply frame
#include "crclink_json_read.h" // read the incoming command
void handle_line(const char *line) { // {"cmd":"mem byte 0x1234","crc":"5993"}
if (crclink_json_verify(line) != 0) return; // bad CRC: drop the frame
char cmd[32];
if (crclink_json_get_str(line, "cmd", cmd, sizeof cmd) < 0) return;
uint32_t addr; // pull the address out (your parser)
if (sscanf(cmd, "mem byte %" SCNx32, &addr) != 1) return;
uint8_t value = *(volatile uint8_t *)(uintptr_t)addr; // read the address
crclink_json_t j;
crclink_json_start(&j, uart_sink, NULL); // your per-byte serial sink
crclink_json_int_add(&j, "v", value);
crclink_json_end(&j); // -> {"v":42,"crc":"37c2"}
}
The reply streams out a byte at a time through your sink and decodes on the host with crclink.decode_json_frame. See src/c/README.md for the builder and reader APIs in full, failure handling, and filling a C struct from a command.
Install
uv add crclink
Development
uv sync
uv run pytest
Test across the supported Python versions with tox, which builds each env with uv (not pip):
uvx --with tox-uv tox run # every version plus the ruff/ty lint env
uvx --with tox-uv tox run -e py312 # one version
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file crclink-0.3.0.tar.gz.
File metadata
- Download URL: crclink-0.3.0.tar.gz
- Upload date:
- Size: 89.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dbbdd09eb63c4e4fd9e713eef1eb482e726b360c9ad378505e1123d0b9a8911f
|
|
| MD5 |
39c5f46ca8af79ad14c20d78ea907f0c
|
|
| BLAKE2b-256 |
016e9c00e2765ee6adfebf42457d52cb6ad34427636bbaa82b9046e57609bb5d
|
File details
Details for the file crclink-0.3.0-py3-none-any.whl.
File metadata
- Download URL: crclink-0.3.0-py3-none-any.whl
- Upload date:
- Size: 10.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e280273b7a44c52d2d146252f5784966ae34af4c1185d620241256a71126b88d
|
|
| MD5 |
d836fe7104d96ea8b32515dd1fba3d70
|
|
| BLAKE2b-256 |
7f5e31e041f8e3b98ec0289c5bbaaf168a6d7fbc1fc72918bcbae1b6a15d61bb
|