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
Metadata
Release files for crclink 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| crclink-0.3.0.tar.gz | 89.4 kB | Without provenance | Details for crclink-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| crclink-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for crclink-0.3.0-py3-none-any.whl |
Total release size: 100.3 kB
Release files / crclink-0.3.0.tar.gz
| Download URL | crclink-0.3.0.tar.gz |
|---|---|
| Size | 89.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dbbdd09eb63c4e4fd9e713eef1eb482e726b360c9ad378505e1123d0b9a8911f
|
|
BLAKE2b-256 checksum How to use checksums |
016e9c00e2765ee6adfebf42457d52cb6ad34427636bbaa82b9046e57609bb5d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / crclink-0.3.0-py3-none-any.whl
| Download URL | crclink-0.3.0-py3-none-any.whl |
|---|---|
| Size | 10.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e280273b7a44c52d2d146252f5784966ae34af4c1185d620241256a71126b88d
|
|
BLAKE2b-256 checksum How to use checksums |
7f5e31e041f8e3b98ec0289c5bbaaf168a6d7fbc1fc72918bcbae1b6a15d61bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Security for crclink 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.