diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index e84183f..c671332 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -2,10 +2,11 @@ name: Rust
on:
push:
- branches: [main]
+ branches: [feature/wasm-pack]
tags:
- 'v*'
- 'cli-v*'
+ - 'wasm-v*'
pull_request:
branches: [main]
@@ -61,6 +62,32 @@ jobs:
cd cli/src
cargo clippy -- -D warnings
+ build-wasm:
+ runs-on: ubuntu-latest
+ needs: build-library
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ override: true
+ - name: Build WASM
+ run: |
+ cd wasm
+ cargo build --verbose
+ - name: Run CLI Tests
+ run: |
+ cd wasm
+ cargo test --verbose
+ - name: Lint CLI with Clippy
+ run: |
+ rustup component add clippy
+ cd wasm
+ cargo clippy -- -D warnings
+
+
publish-library:
needs: build-library
runs-on: ubuntu-latest
@@ -82,7 +109,7 @@ jobs:
publish-cli:
needs: build-cli
runs-on: ubuntu-latest
- if: startsWith(github.ref_name, 'cli-v')
+ if: startsWith(github.ref, 'refs/tags/cli-v')
steps:
- uses: actions/checkout@v2
- name: Set up Rust
@@ -95,3 +122,42 @@ jobs:
run: |
cd cli/src
cargo publish --token ${{ secrets.CRATESTOKEN }}
+
+
+ publish-wasm:
+ needs: build-wasm
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ contents: read
+ if: startsWith(github.ref, 'refs/tags/wasm-v')
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ profile: minimal
+ override: true
+ - name: Install wasm-pack
+ run: cargo install wasm-pack
+ - name: Build wasm package
+ run: |
+ cd wasm
+ wasm-pack build --target bundler
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20.x'
+ registry-url: 'https://registry.npmjs.org'
+ token: ${{ secrets.NPM_TOKEN }}
+ - name: Install npm dependencies
+ run: |
+ cd wasm/pkg
+ npm install
+ npm ci
+ - name: Publish to npm
+ run: |
+ cd wasm/pkg
+ npm publish --provenance --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index dfcaa5d..b940a2b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,15 +28,20 @@ bindgen = "0.69.4"
[features]
std = []
plaintext-before-extension = []
+serde = ["dep:serde", "std", "arrayvec/serde", "bitflags/serde"]
[profile.release]
opt-level = 'z' # Optimize for size
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce codegen units to improve optimizations
+[profile.release.package."m-bus-parser-wasm-pack"]
+opt-level = "s"
+
[dependencies]
+serde = { version = "1.0", features = ["derive"], optional = true }
bitflags = "2.4.2"
-arrayvec = "0.7.4"
+arrayvec = { version = "0.7.4", optional = false, default-features = true }
[workspace]
-members = ["cli"]
+members = ["cli", "wasm"]
diff --git a/README.md b/README.md
index 40df56f..809aded 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,22 @@ Furthermore, the Open Metering System (OMS) Group has published a specification
such as a no longer maitained [ m-bus encoder and decoder by rscada](https://github.com/rscada/libmbus) written in **c**, [jMbus](https://github.com/qvest-digital/jmbus) written in **java**,[Valley.Net.Protocols.MeterBus](https://github.com/sympthom/Valley.Net.Protocols.MeterBus/) written in **C#**, [tmbus](https://dev-lab.github.io/tmbus/) written in javascript or [pyMeterBus](https://github.com/ganehag/pyMeterBus) written in python.
+## Dependants and Deployments
+
+### NPM Wasm Package
+![npm](https://img.shields.io/npm/dm/m-bus-parser-wasm-pack.svg) ![npm](https://img.shields.io/npm/v/m-bus-parser-wasm-pack.svg)
+
+The parser has been published as an npm package and can be used in the browser. An example of this can be seen under the url www.maebli.github.io/m-bus-parser/ [https://maebli.github.io/m-bus-parser/](https://maebli.github.io/m-bus-parser/).
+
+The source is in the wasm folder in this repos
+
+
+### CLI rust crate
+[![Crates.io](https://img.shields.io/crates/v/m-bus-parser-cli.svg)](https://crates.io/crates/m-bus-parser-cli) [![Downloads](https://img.shields.io/crates/d/m-bus-parser-cli.svg)](https://crates.io/crates/m-bus-parser-cli)
+
+There is a cli, the source is in the sub folder "cli" and is published on crates.io [https://crates.io/crates/m-bus-parser-cli](https://crates.io/crates/m-bus-parser-cli).
+
+
### Visualization of Library Function
Do not get confused about the different types of frame types. The most important one to understand at first is the `LongFrame` which is the most common frame type. The others are for example for searching for a slave or for setting the primary address of a slave. This is not of primary intrest for most users. Visualization was made with the help of the tool [excalidraw](https://excalidraw.com/).
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 0499778..d493a26 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "m-bus-parser-cli"
-version = "0.0.4"
+version = "0.0.3"
edition = "2021"
description = "A cli to use the library for parsing M-Bus frames"
license = "MIT"
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..75cc050
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,122 @@
+
+
+
+
+
+ Wired M-Bus Parser
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/m_bus_parser_wasm_pack.js b/docs/m_bus_parser_wasm_pack.js
new file mode 100644
index 0000000..2d3e13d
--- /dev/null
+++ b/docs/m_bus_parser_wasm_pack.js
@@ -0,0 +1,196 @@
+let wasm;
+
+let WASM_VECTOR_LEN = 0;
+
+let cachedUint8Memory0 = null;
+
+function getUint8Memory0() {
+ if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
+ cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+ }
+ return cachedUint8Memory0;
+}
+
+const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+ ? function (arg, view) {
+ return cachedTextEncoder.encodeInto(arg, view);
+}
+ : function (arg, view) {
+ const buf = cachedTextEncoder.encode(arg);
+ view.set(buf);
+ return {
+ read: arg.length,
+ written: buf.length
+ };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+ if (realloc === undefined) {
+ const buf = cachedTextEncoder.encode(arg);
+ const ptr = malloc(buf.length, 1) >>> 0;
+ getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+ WASM_VECTOR_LEN = buf.length;
+ return ptr;
+ }
+
+ let len = arg.length;
+ let ptr = malloc(len, 1) >>> 0;
+
+ const mem = getUint8Memory0();
+
+ let offset = 0;
+
+ for (; offset < len; offset++) {
+ const code = arg.charCodeAt(offset);
+ if (code > 0x7F) break;
+ mem[ptr + offset] = code;
+ }
+
+ if (offset !== len) {
+ if (offset !== 0) {
+ arg = arg.slice(offset);
+ }
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
+ const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+ const ret = encodeString(arg, view);
+
+ offset += ret.written;
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
+ }
+
+ WASM_VECTOR_LEN = offset;
+ return ptr;
+}
+
+let cachedInt32Memory0 = null;
+
+function getInt32Memory0() {
+ if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
+ cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
+ }
+ return cachedInt32Memory0;
+}
+
+const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
+
+if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
+
+function getStringFromWasm0(ptr, len) {
+ ptr = ptr >>> 0;
+ return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+/**
+* @param {string} s
+* @returns {string}
+*/
+export function m_bus_parse(s) {
+ let deferred2_0;
+ let deferred2_1;
+ try {
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+ const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ wasm.m_bus_parse(retptr, ptr0, len0);
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
+ deferred2_0 = r0;
+ deferred2_1 = r1;
+ return getStringFromWasm0(r0, r1);
+ } finally {
+ wasm.__wbindgen_add_to_stack_pointer(16);
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
+ }
+}
+
+async function __wbg_load(module, imports) {
+ if (typeof Response === 'function' && module instanceof Response) {
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
+ try {
+ return await WebAssembly.instantiateStreaming(module, imports);
+
+ } catch (e) {
+ if (module.headers.get('Content-Type') != 'application/wasm') {
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ const bytes = await module.arrayBuffer();
+ return await WebAssembly.instantiate(bytes, imports);
+
+ } else {
+ const instance = await WebAssembly.instantiate(module, imports);
+
+ if (instance instanceof WebAssembly.Instance) {
+ return { instance, module };
+
+ } else {
+ return instance;
+ }
+ }
+}
+
+function __wbg_get_imports() {
+ const imports = {};
+ imports.wbg = {};
+
+ return imports;
+}
+
+function __wbg_init_memory(imports, maybe_memory) {
+
+}
+
+function __wbg_finalize_init(instance, module) {
+ wasm = instance.exports;
+ __wbg_init.__wbindgen_wasm_module = module;
+ cachedInt32Memory0 = null;
+ cachedUint8Memory0 = null;
+
+
+ return wasm;
+}
+
+function initSync(module) {
+ if (wasm !== undefined) return wasm;
+
+ const imports = __wbg_get_imports();
+
+ __wbg_init_memory(imports);
+
+ if (!(module instanceof WebAssembly.Module)) {
+ module = new WebAssembly.Module(module);
+ }
+
+ const instance = new WebAssembly.Instance(module, imports);
+
+ return __wbg_finalize_init(instance, module);
+}
+
+async function __wbg_init(input) {
+ if (wasm !== undefined) return wasm;
+
+ if (typeof input === 'undefined') {
+ input = new URL('m_bus_parser_wasm_pack_bg.wasm', import.meta.url);
+ }
+ const imports = __wbg_get_imports();
+
+ if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
+ input = fetch(input);
+ }
+
+ __wbg_init_memory(imports);
+
+ const { instance, module } = await __wbg_load(await input, imports);
+
+ return __wbg_finalize_init(instance, module);
+}
+
+export { initSync }
+export default __wbg_init;
diff --git a/docs/m_bus_parser_wasm_pack_bg.js b/docs/m_bus_parser_wasm_pack_bg.js
new file mode 100644
index 0000000..d8c9aa2
--- /dev/null
+++ b/docs/m_bus_parser_wasm_pack_bg.js
@@ -0,0 +1,115 @@
+let wasm;
+export function __wbg_set_wasm(val) {
+ wasm = val;
+}
+
+
+let WASM_VECTOR_LEN = 0;
+
+let cachedUint8Memory0 = null;
+
+function getUint8Memory0() {
+ if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
+ cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+ }
+ return cachedUint8Memory0;
+}
+
+const lTextEncoder = typeof TextEncoder === 'undefined' ? (0, module.require)('util').TextEncoder : TextEncoder;
+
+let cachedTextEncoder = new lTextEncoder('utf-8');
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+ ? function (arg, view) {
+ return cachedTextEncoder.encodeInto(arg, view);
+}
+ : function (arg, view) {
+ const buf = cachedTextEncoder.encode(arg);
+ view.set(buf);
+ return {
+ read: arg.length,
+ written: buf.length
+ };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+ if (realloc === undefined) {
+ const buf = cachedTextEncoder.encode(arg);
+ const ptr = malloc(buf.length, 1) >>> 0;
+ getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+ WASM_VECTOR_LEN = buf.length;
+ return ptr;
+ }
+
+ let len = arg.length;
+ let ptr = malloc(len, 1) >>> 0;
+
+ const mem = getUint8Memory0();
+
+ let offset = 0;
+
+ for (; offset < len; offset++) {
+ const code = arg.charCodeAt(offset);
+ if (code > 0x7F) break;
+ mem[ptr + offset] = code;
+ }
+
+ if (offset !== len) {
+ if (offset !== 0) {
+ arg = arg.slice(offset);
+ }
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
+ const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+ const ret = encodeString(arg, view);
+
+ offset += ret.written;
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
+ }
+
+ WASM_VECTOR_LEN = offset;
+ return ptr;
+}
+
+let cachedInt32Memory0 = null;
+
+function getInt32Memory0() {
+ if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
+ cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
+ }
+ return cachedInt32Memory0;
+}
+
+const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder;
+
+let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
+
+cachedTextDecoder.decode();
+
+function getStringFromWasm0(ptr, len) {
+ ptr = ptr >>> 0;
+ return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+/**
+* @param {string} s
+* @returns {string}
+*/
+export function m_bus_parse(s) {
+ let deferred2_0;
+ let deferred2_1;
+ try {
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+ const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+ const len0 = WASM_VECTOR_LEN;
+ wasm.m_bus_parse(retptr, ptr0, len0);
+ var r0 = getInt32Memory0()[retptr / 4 + 0];
+ var r1 = getInt32Memory0()[retptr / 4 + 1];
+ deferred2_0 = r0;
+ deferred2_1 = r1;
+ return getStringFromWasm0(r0, r1);
+ } finally {
+ wasm.__wbindgen_add_to_stack_pointer(16);
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
+ }
+}
+
diff --git a/docs/m_bus_parser_wasm_pack_bg.wasm b/docs/m_bus_parser_wasm_pack_bg.wasm
new file mode 100644
index 0000000..23a6d64
Binary files /dev/null and b/docs/m_bus_parser_wasm_pack_bg.wasm differ
diff --git a/docs/meter.png b/docs/meter.png
new file mode 100644
index 0000000..daa1f54
Binary files /dev/null and b/docs/meter.png differ
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 0000000..6b234f7
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "m-bus-parser-wasm-pack",
+ "collaborators": [
+ "Michael Aebli"
+ ],
+ "description": "A wasm-pack to use the library for parsing M-Bus frames",
+ "version": "0.0.0",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/maebli/m-bus-parser"
+ },
+ "files": [
+ "m_bus_parser_wasm_pack_bg.wasm",
+ "m_bus_parser_wasm_pack.js",
+ "m_bus_parser_wasm_pack.d.ts"
+ ],
+ "module": "m_bus_parser_wasm_pack.js",
+ "homepage": "https://maebli.github.io/",
+ "types": "m_bus_parser_wasm_pack.d.ts",
+ "sideEffects": [
+ "./snippets/*"
+ ],
+ "keywords": [
+ "m-bus",
+ "parser",
+ "parse",
+ "wasm-pack"
+ ]
+}
\ No newline at end of file
diff --git a/src/frames/mod.rs b/src/frames/mod.rs
index ffcf22a..a0942c8 100644
--- a/src/frames/mod.rs
+++ b/src/frames/mod.rs
@@ -1,6 +1,6 @@
//! is part of the MBUS data link layer
//! It is used to encapsulate the application layer data
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum Frame<'a> {
SingleCharacter {
@@ -22,6 +22,7 @@ pub enum Frame<'a> {
},
}
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum Function {
SndNk,
@@ -63,7 +64,7 @@ impl TryFrom for Function {
}
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum Address {
Uninitalized,
diff --git a/src/lib.rs b/src/lib.rs
index 6dae634..9f38693 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -34,13 +34,8 @@
//! let frame = Frame::try_from(example.as_slice()).unwrap();
//!
//! if let Frame::LongFrame { function, address, data :_} = frame {
-//! assert_eq!(function, Function::RspUd{acd: false, dfc:false});
-//! assert_eq!(address, Address::Primary(1));
//! }
//!
-//! // Alternatively, parse the frame and user data in one go
-//! let mbus_data = m_bus_parser::MbusData::try_from(example.as_slice()).unwrap();
-//!
//! ```
#![cfg_attr(not(feature = "std"), no_std)]
@@ -58,9 +53,15 @@ pub mod frames;
pub mod user_data;
#[derive(Debug)]
+#[cfg_attr(
+ feature = "serde",
+ derive(serde::Serialize, serde::Deserialize),
+ serde(bound(deserialize = "'de: 'a"))
+)]
pub struct MbusData<'a> {
pub frame: frames::Frame<'a>,
pub user_data: Option>,
+ pub data_records: Option,
}
#[derive(Debug)]
@@ -77,9 +78,7 @@ impl From for MbusError {
impl From for MbusError {
fn from(error: ApplicationLayerError) -> MbusError {
- match error {
- _ => MbusError::ApplicationLayerError(error),
- }
+ MbusError::ApplicationLayerError(error)
}
}
@@ -88,15 +87,30 @@ impl<'a> TryFrom<&'a [u8]> for MbusData<'a> {
fn try_from(data: &'a [u8]) -> Result {
let frame = frames::Frame::try_from(data)?;
-
- let user_data = match &frame {
+ let mut user_data = None;
+ let mut data_records = None;
+ match &frame {
frames::Frame::LongFrame { data, .. } => {
- Some(user_data::UserDataBlock::try_from(*data)?)
+ if let Ok(x) = user_data::UserDataBlock::try_from(*data) {
+ user_data = Some(x);
+ if let Ok(user_data::UserDataBlock::VariableDataStructure {
+ fixed_data_header: _,
+ variable_data_block,
+ }) = user_data::UserDataBlock::try_from(*data)
+ {
+ data_records = user_data::DataRecords::try_from(variable_data_block).ok();
+ }
+ }
}
- frames::Frame::SingleCharacter { .. } => None,
- frames::Frame::ShortFrame { .. } | frames::Frame::ControlFrame { .. } => None,
+ frames::Frame::SingleCharacter { .. } => (),
+ frames::Frame::ShortFrame { .. } => (),
+ frames::Frame::ControlFrame { .. } => (),
};
- Ok(MbusData { frame, user_data })
+ Ok(MbusData {
+ frame,
+ user_data,
+ data_records,
+ })
}
}
diff --git a/src/user_data/data_information.rs b/src/user_data/data_information.rs
index 2578a56..a2a2761 100644
--- a/src/user_data/data_information.rs
+++ b/src/user_data/data_information.rs
@@ -3,6 +3,7 @@ use super::variable_user_data::DataRecordError;
use arrayvec::ArrayVec;
const MAX_DIFE_RECORDS: usize = 10;
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct DataInformationBlock {
pub data_information_field: DataInformationField,
@@ -19,7 +20,7 @@ impl DataInformationBlock {
size
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct DataInformationField {
pub data: u8,
@@ -42,7 +43,7 @@ impl From for DataInformationFieldExtension {
DataInformationFieldExtension { data }
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct DataInformationFieldExtension {
pub data: u8,
@@ -94,7 +95,7 @@ impl DataInformationFieldExtension {
self.data & 0x80 != 0
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct DataInformation {
pub storage_number: u64,
@@ -116,7 +117,7 @@ impl std::fmt::Display for DataInformation {
}
const MAXIMUM_DATA_INFORMATION_SIZE: usize = 11;
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct DataInformationExtensionField {}
@@ -205,11 +206,13 @@ impl TryFrom<&DataInformationBlock> for DataInformation {
}
}
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum DataType {
Text(ArrayVec),
Number(f64),
}
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Debug)]
pub struct Data {
value: Option,
@@ -509,7 +512,7 @@ impl DataInformation {
self.size
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FunctionField {
InstantaneousValue,
@@ -529,7 +532,7 @@ impl std::fmt::Display for FunctionField {
}
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SpecialFunctions {
ManufacturerSpecific,
@@ -655,7 +658,7 @@ impl DataFieldCoding {
}
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DataFieldCoding {
NoData,
diff --git a/src/user_data/data_record.rs b/src/user_data/data_record.rs
index ac3991f..53a3255 100644
--- a/src/user_data/data_record.rs
+++ b/src/user_data/data_record.rs
@@ -3,19 +3,19 @@ use super::{
value_information::{ValueInformation, ValueInformationBlock},
variable_user_data::DataRecordError,
};
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct RawDataRecordHeader {
pub data_information_block: DataInformationBlock,
pub value_information_block: ValueInformationBlock,
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct ProcessedDataRecordHeader {
pub data_information: DataInformation,
pub value_information: ValueInformation,
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct DataRecord {
pub data_record_header: DataRecordHeader,
@@ -27,7 +27,7 @@ impl DataRecord {
self.data_record_header.get_size() + self.data.get_size()
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct DataRecordHeader {
pub raw_data_record_header: RawDataRecordHeader,
diff --git a/src/user_data/mod.rs b/src/user_data/mod.rs
index 35ecc2b..3e4b3c7 100644
--- a/src/user_data/mod.rs
+++ b/src/user_data/mod.rs
@@ -15,6 +15,7 @@ pub mod variable_user_data;
// therefore the maximum number of blocks is 117, see https://m-bus.com/documentation-wired/06-application-layer
const MAXIMUM_VARIABLE_DATA_BLOCKS: usize = 117;
// Define a new struct that wraps ArrayVec
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct DataRecords {
pub inner: ArrayVec,
@@ -65,6 +66,7 @@ impl Default for DataRecords {
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+ #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StatusField: u8 {
const COUNTER_BINARY_SIGNED = 0b00000001;
const COUNTER_FIXED_DATE = 0b00000010;
@@ -239,6 +241,7 @@ impl fmt::Display for ApplicationLayerError {
#[cfg(feature = "std")]
impl std::error::Error for ApplicationLayerError {}
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum ApplicationResetSubcode {
All(u8),
@@ -297,12 +300,12 @@ fn bcd_hex_digits_to_u32(digits: [u8; 4]) -> Result
Ok(number)
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct Counter {
count: u32,
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct IdentificationNumber {
pub number: u32,
@@ -338,7 +341,7 @@ pub struct FixedDataHeder {
status: StatusField,
signature: u16,
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq)]
pub enum UserDataBlock<'a> {
@@ -358,7 +361,7 @@ pub enum UserDataBlock<'a> {
variable_data_block: &'a [u8],
},
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum Medium {
Other,
@@ -450,7 +453,7 @@ impl fmt::Display for Medium {
write!(f, "{}", medium)
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct FixedDataHeader {
pub identification_number: IdentificationNumber,
@@ -461,7 +464,7 @@ pub struct FixedDataHeader {
pub status: StatusField,
pub signature: u16,
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct ManufacturerCode {
pub code: [char; 3],
diff --git a/src/user_data/value_information.rs b/src/user_data/value_information.rs
index a497269..8e51a7f 100644
--- a/src/user_data/value_information.rs
+++ b/src/user_data/value_information.rs
@@ -70,7 +70,7 @@ fn extract_plaintext_vife(data: &[u8]) -> ArrayVec {
}
ascii
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct ValueInformationBlock {
pub value_information: ValueInformationField,
@@ -78,7 +78,7 @@ pub struct ValueInformationBlock {
Option>,
pub plaintext_vife: Option>,
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct ValueInformationField {
pub data: u8,
@@ -89,7 +89,7 @@ impl ValueInformationField {
self.data == 0x7C || self.data == 0xFC
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct ValueInformationFieldExtension {
pub data: u8,
@@ -132,7 +132,7 @@ pub enum ValueInformationCoding {
AlternateVIFExtension,
ManufacturerSpecific,
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum ValueInformationFieldExtensionCoding {
MainVIFCodeExtension,
@@ -1635,6 +1635,7 @@ impl From for ValueInformationField {
/// This is the most important type of the this file and represents
/// the whole information inside the value information block
/// value(x) = (multiplier * value + offset) * units
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct ValueInformation {
pub decimal_offset_exponent: isize,
@@ -1675,7 +1676,7 @@ impl fmt::Display for ValueInformation {
Ok(())
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum ValueLabel {
Instantaneous,
@@ -1824,6 +1825,7 @@ pub enum ValueLabel {
DisplayOutputScalingFactor,
ManufacturerSpecific,
}
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Unit {
pub name: UnitName,
@@ -1859,7 +1861,7 @@ impl fmt::Display for Unit {
}
}
}
-
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UnitName {
Watt,
diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml
new file mode 100644
index 0000000..1d27755
--- /dev/null
+++ b/wasm/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "m-bus-parser-wasm-pack"
+version = "0.0.1"
+edition = "2021"
+description = "A wasm-pack to use the library for parsing M-Bus frames"
+license = "MIT"
+homepage = "https://maebli.github.io/"
+repository = "https://github.com/maebli/m-bus-parser"
+readme = "README.md"
+authors = ["Michael Aebli"]
+keywords = ["m-bus", "parser", "parse", "wasm-pack"]
+
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[features]
+default = ["console_error_panic_hook"]
+
+[dependencies]
+wasm-bindgen = "0.2.84"
+m-bus-parser = { path = "..", version = "0.0.9", features = ["std", "serde"] }
+serde = { version = "1.0" }
+serde_json = "1.0"
+
+# The `console_error_panic_hook` crate provides better debugging of panics by
+# logging them with `console.error`. This is great for development, but requires
+# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+# code size when deploying.
+console_error_panic_hook = { version = "0.1.7", optional = true }
+
+[dev-dependencies]
+wasm-bindgen-test = "0.3.34"
diff --git a/wasm/README.md b/wasm/README.md
new file mode 100644
index 0000000..528b8a6
--- /dev/null
+++ b/wasm/README.md
@@ -0,0 +1,3 @@
+# WASM Package for m-bus-parser (wired)
+
+This package is a WebAssembly (WASM) package for the mbus parser so it may be used in the browser for example.
\ No newline at end of file
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
new file mode 100644
index 0000000..f776aca
--- /dev/null
+++ b/wasm/src/lib.rs
@@ -0,0 +1,33 @@
+use m_bus_parser::{self, MbusData};
+
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+extern "C" {
+ fn alert(s: &str);
+}
+
+fn clean_and_convert(input: &str) -> Vec {
+ let input = input.trim();
+ let cleaned_data: String = input.replace("0x", "").replace([' ', ','], "");
+
+ // Convert pairs of characters into bytes
+ cleaned_data
+ .as_bytes()
+ .chunks(2)
+ .map(|chunk| {
+ let byte_str = std::str::from_utf8(chunk).expect("Invalid UTF-8 sequence");
+ u8::from_str_radix(byte_str, 16).expect("Invalid byte value")
+ })
+ .collect()
+}
+
+#[wasm_bindgen]
+pub fn m_bus_parse(s: &str) -> String {
+ let s = clean_and_convert(s);
+ if let Ok(mbus_data) = MbusData::try_from(s.as_slice()) {
+ serde_json::to_string_pretty(&mbus_data).unwrap()
+ } else {
+ "Failed to parse".to_string()
+ }
+}
diff --git a/wasm/tests/web.rs b/wasm/tests/web.rs
new file mode 100644
index 0000000..de5c1da
--- /dev/null
+++ b/wasm/tests/web.rs
@@ -0,0 +1,13 @@
+//! Test suite for the Web and headless browsers.
+
+#![cfg(target_arch = "wasm32")]
+
+extern crate wasm_bindgen_test;
+use wasm_bindgen_test::*;
+
+wasm_bindgen_test_configure!(run_in_browser);
+
+#[wasm_bindgen_test]
+fn pass() {
+ assert_eq!(1 + 1, 2);
+}