Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docs/assets/highlighter.js -diff linguist-generated=true
33 changes: 31 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,36 @@ jobs:
run: |
cd wasm
cargo test --verbose
- name: Install wasm-pack
run: cargo install wasm-pack --locked
- name: Run WASM browser tests
run: |
cd wasm
wasm-pack test --headless --firefox
- name: Lint WASM with Clippy
run: |
rustup component add clippy
cd wasm
cargo clippy -- -D warnings

build-docs-ui:
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v6
with:
node-version: '24'
cache: npm
cache-dependency-path: docs-ui/package-lock.json
- name: Build and verify syntax highlighting
working-directory: docs-ui
run: |
npm ci
npm test
- name: Check generated highlighter
run: git diff --exit-code -- docs/assets/highlighter.js

publish-library:
needs: build-library
runs-on: ubuntu-latest
Expand Down Expand Up @@ -208,7 +232,7 @@ jobs:
cargo publish

publish-wasm:
needs: build-wasm
needs: [build-wasm, build-docs-ui]
runs-on: ubuntu-latest
permissions:
id-token: write
Expand Down Expand Up @@ -239,6 +263,11 @@ jobs:
cd wasm/pkg
npm install
npm ci
- name: Build and verify docs highlighter
working-directory: docs-ui
run: |
npm ci
npm test
- name: Publish to npm
run: |
cd wasm/pkg
Expand Down Expand Up @@ -267,6 +296,6 @@ jobs:
run: |
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add docs/m_bus_parser_wasm_pack.js docs/m_bus_parser_wasm_pack_bg.js docs/m_bus_parser_wasm_pack_bg.wasm docs/meter.png docs/package.json
git add docs/assets/highlighter.js docs/m_bus_parser_wasm_pack.js docs/m_bus_parser_wasm_pack_bg.js docs/m_bus_parser_wasm_pack_bg.wasm docs/meter.png docs/package.json
git commit -m "Update docs wasm artifacts" || echo "No changes to commit"
git push origin HEAD:main
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ Cargo.lock

# Mac
.DS_Store

# Node build dependencies
node_modules/
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.0]

### Added

- A versioned canonical output schema shared by Rust, CLI, Python, and WASM,
with exact decimal values, source provenance, partial-decode diagnostics,
stable error codes, and optional manufacturer enrichment.
- Typed `decode_*` and `render_*` Rust APIs, a native-object WASM decoder, a
custom Python exception, strict hexadecimal input, and explicit render
options.
- Responsive Unicode-aware tables that adapt between wide, compact, and
vertical layouts without exceeding the requested terminal or container
width.
- Self-hosted Shiki syntax highlighting for JSON, YAML, CSV, and XML in the
WASM documentation, with light/dark high-contrast themes, line numbers,
keyboard scrolling, forced-colors support, and automated WCAG AA checks.

### Changed

- JSON, YAML, CSV, table, Mermaid, annotated, XML, and binding outputs now use
one product-owned contract. Retained legacy serializers are available under
explicit legacy format names through this compatibility release.
- Wireless link frames preserve the raw C-field and expose the decoded
function only when the value is known.

### Fixed

- XML rendering now supports clear, encrypted, short/long transport, and ELL
wireless M-Bus frames while retaining byte-for-byte wired libmbus parity.
- Tables no longer wrap or mangle inside narrow terminals and browser output
cards.
- Syntax tokens and line-number gutters meet the WCAG 2.2 AA 4.5:1 text
contrast threshold in both supported themes.

## [0.3.0]

### Added
Expand Down
13 changes: 7 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "m-bus-parser"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
description = "A library for parsing M-Bus frames"
license = "MIT"
Expand All @@ -25,7 +25,7 @@ bindgen = "0.72.0"

[features]
default = []
std = ["prettytable-rs", "serde_json", "serde_yaml", "serde", "wired-mbus-link-layer/std", "wireless-mbus-link-layer/std", "m-bus-core/std", "m-bus-application-layer/std"]
std = ["prettytable-rs", "serde_json", "serde_yaml", "serde", "unicode-width", "wired-mbus-link-layer/std", "wireless-mbus-link-layer/std", "m-bus-core/std", "m-bus-application-layer/std"]
plaintext-before-extension = ["m-bus-application-layer/plaintext-before-extension"]
serde = ["dep:serde", "arrayvec/serde", "bitflags/serde", "wired-mbus-link-layer/serde", "wireless-mbus-link-layer/serde", "m-bus-core/serde", "m-bus-application-layer/serde"]
defmt = ["dep:defmt", "wired-mbus-link-layer/defmt", "wireless-mbus-link-layer/defmt", "m-bus-core/defmt", "m-bus-application-layer/defmt"]
Expand All @@ -44,13 +44,14 @@ prettytable-rs = { version = "^0.10", optional = true }
serde_json = { version = "1.0.138", optional = true }
serde_yaml = { version = "0.9", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
unicode-width = { version = "0.2", optional = true }
bitflags = "2.8.0"
arrayvec = { version = "0.7.4", default-features = false }
defmt = { version = "1.0.1", optional = true }
wired-mbus-link-layer = { version = "0.3.0", path = "crates/wired-mbus-link-layer" }
wireless-mbus-link-layer = { version = "0.3.0", path = "crates/wireless-mbus-link-layer" }
m-bus-core = { version = "0.3.0", path = "crates/m-bus-core" }
m-bus-application-layer = { version = "0.3.0", path = "crates/m-bus-application-layer" }
wired-mbus-link-layer = { version = "0.4.0", path = "crates/wired-mbus-link-layer" }
wireless-mbus-link-layer = { version = "0.4.0", path = "crates/wireless-mbus-link-layer" }
m-bus-core = { version = "0.4.0", path = "crates/m-bus-core" }
m-bus-application-layer = { version = "0.4.0", path = "crates/m-bus-application-layer" }
aes = { version = "0.9", optional = true, default-features = false }
cbc = { version = "0.2", optional = true, default-features = false }
cipher = { version = "0.5", optional = true, default-features = false, features = ["block-padding"] }
Expand Down
121 changes: 49 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ An open-source parser (decoder/deserializer) for the **wired** and **wireless**
## Features

- Parses **wired M-Bus** (EN 13757-2/-3) and **wireless M-Bus** (wMBus) frames
- **Six output formats**: `table`, `json`, `yaml`, `csv`, `mermaid`, `xml`
- **Eight harmonized output formats**: `table`, `json`, `yaml`, `csv`,
`mermaid`, `xml`, `annotated`, and `annotated-text`
- A versioned canonical schema with exact decimal values, provenance,
partial-decode diagnostics, and stable error codes
- Responsive, Unicode-aware tables for narrow terminals and browser cards
- **AES-128 decryption** for encrypted wMBus frames (mode 5 / mode 7)
- **`no_std` compatible** — runs on embedded targets (manufacturer lookup and output formats require `std`)
- Available as a **Rust library**, **CLI**, **WebAssembly (npm)** and **Python bindings**
Expand Down Expand Up @@ -69,65 +73,26 @@ m-bus-parser-cli parse [OPTIONS]
Options:
-d, --data <DATA> Raw M-Bus frame as a hex string
-f, --file <FILE> File containing a hex frame
-t, --format <FORMAT> Output format: table (default), json, yaml, csv, mermaid, xml
-t, --format <FORMAT> table, json, yaml, csv, mermaid, xml, annotated, annotated-text
-k, --key <KEY> AES-128 decryption key (32 hex characters)
--width <WIDTH> Table width (auto-detected on an interactive terminal)
--no-enrichment Omit manufacturer enrichment
```

Input hex can be in any of these forms:
Input hex is strict: use compact hexadecimal or complete byte tokens separated
by whitespace, colons, or hyphens.

```
68 3D 3D 68 ... (space-separated)
683D3D68... (plain hex)
0x68,0x3D,0x3D,... (0x-prefixed, comma-separated)
0x68:0x3D:0x3D:0x68 (prefixed byte tokens)
```

### Table output (default)

```bash
m-bus-parser-cli parse -d "68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 \
04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 \
0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16"
```

```
Long Frame
┌────────────────────────────────┬─────────────┐
│ Function │ Address │
├────────────────────────────────┼─────────────┤
│ RspUd (ACD: false, DFC: false) │ Primary (1) │
└────────────────────────────────┴─────────────┘
┌───────────────────────┬──────────────────────────────────────────┐
│ Field │ Value │
├───────────────────────┼──────────────────────────────────────────┤
│ Identification Number │ 02205100 │
├───────────────────────┼──────────────────────────────────────────┤
│ Manufacturer │ SLB │
├───────────────────────┼──────────────────────────────────────────┤
│ Manufacturer Name │ Schlumberger Industries │
├───────────────────────┼──────────────────────────────────────────┤
│ Website │ slb.com │
├───────────────────────┼──────────────────────────────────────────┤
│ Description │ Energy and water metering │
├───────────────────────┼──────────────────────────────────────────┤
│ Access Number │ 0 │
├───────────────────────┼──────────────────────────────────────────┤
│ Status │ Permanent error, Manufacturer specific 3 │
├───────────────────────┼──────────────────────────────────────────┤
│ Security Mode │ No encryption used │
├───────────────────────┼──────────────────────────────────────────┤
│ Version │ 2 │
├───────────────────────┼──────────────────────────────────────────┤
│ DeviceType │ Heat Meter (Return) │
└───────────────────────┴──────────────────────────────────────────┘
┌─────────────────────────────────────────┬───────────────────────┬────────────┬─────────────┐
│ Value │ Data Information │ Header Hex │ Data Hex │
├─────────────────────────────────────────┼───────────────────────┼────────────┼─────────────┤
│ (0)e4[Wh] │ 0,Inst,32-bit Integer │ 04 07 │ 00 00 00 00 │
├─────────────────────────────────────────┼───────────────────────┼────────────┼─────────────┤
│ (3)e-1[m³](Volume) │ 0,Inst,BCD 8-digit │ 0C 15 │ 03 00 00 00 │
├─────────────────────────────────────────┼───────────────────────┼────────────┼─────────────┤
│ (1288)e-1[°C] │ 0,Inst,BCD 4-digit │ 0A 5A │ 88 12 │
└─────────────────────────────────────────┴───────────────────────┴────────────┴─────────────┘
```
The table automatically selects a wide, compact, or vertical-card layout and
never exceeds the detected terminal width. Use `--width 44` to request an exact
maximum explicitly.

### Other formats

Expand All @@ -144,9 +109,13 @@ m-bus-parser-cli parse -d "..." -t csv
# Mermaid diagram source (renders in the web app)
m-bus-parser-cli parse -d "..." -t mermaid

# Legacy libmbus normalized XML (drop-in for rSCADA/libmbus consumers)
# Wired-compatible and wireless XML
m-bus-parser-cli parse -d "..." -t xml

# Byte annotations as JSON or human-readable text
m-bus-parser-cli parse -d "..." -t annotated
m-bus-parser-cli parse -d "..." -t annotated-text

# With AES-128 decryption key
m-bus-parser-cli parse -d "..." -k "000102030405060708090A0B0C0D0E0F"
```
Expand All @@ -159,7 +128,7 @@ Add to `Cargo.toml`:

```toml
[dependencies]
m-bus-parser = "0.1"
m-bus-parser = { version = "0.4", features = ["std", "serde"] }
```

### Parse a wired frame
Expand Down Expand Up @@ -202,32 +171,38 @@ for record in parse_data_records(&data) {
}
```

### Serialize to any format
### Decode and render with typed APIs

```rust
use m_bus_parser::serialize_mbus_data;
use m_bus_parser::{
DecodeOptions, OutputFormat, RenderOptions, decode_hex, render_hex,
};

let hex = "68 3D 3D 68 08 01 72 ...";

let table = serialize_mbus_data(hex, "table", None);
let json = serialize_mbus_data(hex, "json", None);
let yaml = serialize_mbus_data(hex, "yaml", None);
let csv = serialize_mbus_data(hex, "csv", None);
let mermaid = serialize_mbus_data(hex, "mermaid", None);
let xml = serialize_mbus_data(hex, "xml", None);

// With decryption key
let key: [u8; 16] = [0x00, 0x01, ..., 0x0F];
let decrypted = serialize_mbus_data(hex, "table", Some(&key));
let decoded = decode_hex(hex, &DecodeOptions::default())?;
println!("schema v{}: {}", decoded.schema_version, decoded.protocol);

let table = render_hex(
hex,
OutputFormat::Table,
&RenderOptions {
table_width: Some(72),
..RenderOptions::default()
},
)?;
```

`serialize_mbus_data` remains as a string compatibility wrapper. New code
should use the typed APIs so invalid input and unsupported options remain
machine-readable `OutputError` values.

### `no_std` usage

The core parsing types are `no_std` compatible. Disable default features:

```toml
[dependencies]
m-bus-parser = { version = "0.1", default-features = false }
m-bus-parser = { version = "0.4", default-features = false }
```

An embedded example (Cortex-M) is in [`examples/cortex-m/`](./examples/cortex-m).
Expand All @@ -238,12 +213,14 @@ An embedded example (Cortex-M) is in [`examples/cortex-m/`](./examples/cortex-m)

| Format | Flag | Description |
|-----------|---------|--------------------------------------------------|
| `table` | default | Human-readable ASCII table |
| `json` | `-t json` | JSON |
| `yaml` | `-t yaml` | YAML |
| `csv` | `-t csv` | Comma-separated values |
| `mermaid` | `-t mermaid` | Mermaid flowchart source (renders in web app)|
| `xml` | `-t xml` | Legacy libmbus normalized XML (rSCADA-compatible)|
| `table` | default | Width-aware human-readable table |
| `json` | `-t json` | Canonical schema as JSON |
| `yaml` | `-t yaml` | Canonical schema as YAML |
| `csv` | `-t csv` | Stable tidy-record CSV |
| `mermaid` | `-t mermaid` | Layer-oriented Mermaid flowchart |
| `xml` | `-t xml` | Wired libmbus-compatible and wireless XML |
| `annotated` | `-t annotated` | Byte-segment annotation envelope |
| `annotated-text`| `-t annotated-text` | Human-readable byte annotations |

---

Expand Down
5 changes: 3 additions & 2 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "m-bus-parser-cli"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
description = "A cli to use the library for parsing M-Bus frames"
license = "MIT"
Expand All @@ -22,6 +22,7 @@ default = ["decryption"]
decryption = ["m-bus-parser/decryption"]

[dependencies]
m-bus-parser = { path = "..", version = "0.3.0", features = ["std", "serde"] }
m-bus-parser = { path = "..", version = "0.4.0", features = ["std", "serde"] }
hex = "0.4"
clap = { version = "4.5.4", features = ["derive"] }
terminal_size = "0.4"
Loading
Loading