structwire 0.1.0__tar.gz → 0.1.0.post1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {structwire-0.1.0 → structwire-0.1.0.post1}/CONTRIBUTING.md +8 -7
  2. {structwire-0.1.0/src/structwire.egg-info → structwire-0.1.0.post1}/PKG-INFO +30 -17
  3. {structwire-0.1.0 → structwire-0.1.0.post1}/README.md +29 -16
  4. structwire-0.1.0.post1/RELEASING.md +52 -0
  5. {structwire-0.1.0 → structwire-0.1.0.post1}/docs/COMPATIBILITY.md +7 -2
  6. structwire-0.1.0.post1/docs/INTEGRATION_MODEL.md +61 -0
  7. {structwire-0.1.0 → structwire-0.1.0.post1}/docs/SCHEMA.md +7 -7
  8. structwire-0.1.0.post1/examples/bcd_record.py +14 -0
  9. structwire-0.1.0.post1/examples/ble_payload.py +21 -0
  10. structwire-0.1.0.post1/examples/catalog_demo.py +21 -0
  11. structwire-0.1.0.post1/examples/codecs.yaml +43 -0
  12. structwire-0.1.0.post1/examples/packed_fields.py +14 -0
  13. structwire-0.1.0.post1/examples/transport_bindings.yaml +35 -0
  14. structwire-0.1.0.post1/examples/uart_bindings.yaml +13 -0
  15. {structwire-0.1.0 → structwire-0.1.0.post1}/scripts/check_dist.py +3 -1
  16. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire/__init__.py +1 -1
  17. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire/model.py +1 -1
  18. {structwire-0.1.0 → structwire-0.1.0.post1/src/structwire.egg-info}/PKG-INFO +30 -17
  19. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire.egg-info/SOURCES.txt +3 -4
  20. {structwire-0.1.0 → structwire-0.1.0.post1}/tests/test_codec.py +16 -18
  21. {structwire-0.1.0 → structwire-0.1.0.post1}/tests/test_examples.py +1 -1
  22. {structwire-0.1.0 → structwire-0.1.0.post1}/tests/test_loaders.py +4 -4
  23. {structwire-0.1.0 → structwire-0.1.0.post1}/tests/test_schema.py +5 -5
  24. structwire-0.1.0/RELEASING.md +0 -31
  25. structwire-0.1.0/docs/AUTOSPAN_ADOPTION.md +0 -41
  26. structwire-0.1.0/docs/INTEGRATION_MODEL.md +0 -68
  27. structwire-0.1.0/examples/automotive_bindings.yaml +0 -36
  28. structwire-0.1.0/examples/ble_payload.py +0 -14
  29. structwire-0.1.0/examples/can_like_signal.py +0 -12
  30. structwire-0.1.0/examples/catalog_demo.py +0 -19
  31. structwire-0.1.0/examples/codecs.yaml +0 -59
  32. structwire-0.1.0/examples/diagnostic_record.py +0 -13
  33. structwire-0.1.0/examples/uart_bindings.yaml +0 -13
  34. {structwire-0.1.0 → structwire-0.1.0.post1}/LICENSE +0 -0
  35. {structwire-0.1.0 → structwire-0.1.0.post1}/MANIFEST.in +0 -0
  36. {structwire-0.1.0 → structwire-0.1.0.post1}/SECURITY.md +0 -0
  37. {structwire-0.1.0 → structwire-0.1.0.post1}/docs/API.md +0 -0
  38. {structwire-0.1.0 → structwire-0.1.0.post1}/docs/DESIGN.md +0 -0
  39. {structwire-0.1.0 → structwire-0.1.0.post1}/pyproject.toml +0 -0
  40. {structwire-0.1.0 → structwire-0.1.0.post1}/scripts/check_readme.py +0 -0
  41. {structwire-0.1.0 → structwire-0.1.0.post1}/scripts/verify_install.py +0 -0
  42. {structwire-0.1.0 → structwire-0.1.0.post1}/setup.cfg +0 -0
  43. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire/catalog.py +0 -0
  44. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire/codec.py +0 -0
  45. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire/errors.py +0 -0
  46. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire/formatting.py +0 -0
  47. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire/loaders.py +0 -0
  48. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire/py.typed +0 -0
  49. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire.egg-info/dependency_links.txt +0 -0
  50. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire.egg-info/requires.txt +0 -0
  51. {structwire-0.1.0 → structwire-0.1.0.post1}/src/structwire.egg-info/top_level.txt +0 -0
  52. {structwire-0.1.0 → structwire-0.1.0.post1}/tests/test_check_readme.py +0 -0
  53. {structwire-0.1.0 → structwire-0.1.0.post1}/tests/test_compilation.py +0 -0
  54. {structwire-0.1.0 → structwire-0.1.0.post1}/tests/test_properties.py +0 -0
@@ -30,7 +30,7 @@ coverage. Document byte order, width and failure behavior. Update the
30
30
  output, or schema validation. Add boundary and differential/property tests for
31
31
  numeric changes.
32
32
 
33
- Pytest executes the four Python examples and every Python block in the README and
33
+ Pytest executes the Python examples and every Python block in the README and
34
34
  `docs/`. YAML snippets are parsed and their StructWire schemas compiled; application
35
35
  routing examples are checked for valid nested layouts. The example routing files
36
36
  are checked against the supplied catalog. Property tests compare integer and float
@@ -81,9 +81,10 @@ See [releasing](RELEASING.md) for publication using those artifacts.
81
81
 
82
82
  ## Source material
83
83
 
84
- Contribute only source, schemas and fixtures that you have the right to publish
85
- under MIT. Prefer synthetic examples and public protocol specifications. Keep
86
- private/customer payload databases, bench configuration and device secrets out
87
- of this repository. Customer and project mappings belong in their private plugins
88
- unless publication rights are explicit. Automated checks cannot establish those
89
- rights; the domain-neutral API never requires publishing customer protocol data.
84
+ Contribute only source, schemas, and fixtures that you have the right to publish
85
+ under MIT. Use invented, vendor-neutral examples rather than proprietary
86
+ identifiers, session tables, packet definitions, logs, or internal names.
87
+ Keep private payload catalogs, bench configurations, and credentials out of
88
+ this repository. Application-specific routing and payload definitions belong
89
+ in the consuming application unless publication rights are explicit.
90
+ Automated checks cannot establish those rights.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structwire
3
- Version: 0.1.0
3
+ Version: 0.1.0.post1
4
4
  Summary: A small schema-driven binary payload codec for Python.
5
5
  Author: Nidhal Abidi
6
6
  License-Expression: MIT
@@ -49,7 +49,13 @@ Your application supplies the bytes and handles transport I/O and routing.
49
49
 
50
50
  ## Install
51
51
 
52
- Python 3.11 or newer is required. Install from the source checkout:
52
+ Python 3.11 or newer is required. Install from PyPI:
53
+
54
+ ```bash
55
+ python -m pip install "structwire>=0.1.0.post1,<0.2"
56
+ ```
57
+
58
+ Or install from the source checkout:
53
59
 
54
60
  ```bash
55
61
  git clone https://github.com/nidops/structwire.git
@@ -57,7 +63,7 @@ cd structwire
57
63
  python -m pip install .
58
64
  ```
59
65
 
60
- Version `0.1.0` is an alpha API. PyYAML is installed automatically for YAML loading.
66
+ Version `0.1.0.post1` is an alpha API. PyYAML is installed automatically for YAML loading.
61
67
 
62
68
  ## Quick start
63
69
 
@@ -65,13 +71,13 @@ Version `0.1.0` is an alpha API. PyYAML is installed automatically for YAML load
65
71
  from structwire import Field, Layout
66
72
 
67
73
  codec = Layout(
68
- Field("temperature_c", "i16le", scale=0.1),
69
- Field("state", "u8"),
74
+ Field("level", "i16le", scale=0.1),
75
+ Field("quality", "u8"),
70
76
  ).compile()
71
77
 
72
- payload = codec.encode({"temperature_c": 23.5, "state": 1})
78
+ payload = codec.encode({"level": 23.5, "quality": 1})
73
79
  assert payload == bytes.fromhex("eb 00 01")
74
- assert codec.decode(payload) == {"temperature_c": 23.5, "state": 1}
80
+ assert codec.decode(payload) == {"level": 23.5, "quality": 1}
75
81
  ```
76
82
 
77
83
  Compile a layout once and reuse its codec.
@@ -83,27 +89,27 @@ Save this as `codecs.yaml`:
83
89
  ```yaml
84
90
  version: 1
85
91
  codecs:
86
- node_status:
92
+ frame_status:
87
93
  size: 4
88
94
  fields:
89
- - name: mode
95
+ - name: phase
90
96
  type: enum
91
- values: {0: asleep, 1: awake, 2: fault}
92
- - name: supply_voltage
97
+ values: {0: idle, 1: running, 2: paused}
98
+ - name: value
93
99
  type: uint16_le
94
100
  offset: 2
95
- scale: 0.001
96
- unit: V
101
+ scale: 0.01
102
+ unit: arbitrary
97
103
  ```
98
104
 
99
105
  ```python
100
106
  from structwire import catalog_from_yaml
101
107
 
102
108
  catalog = catalog_from_yaml("codecs.yaml")
103
- payload = bytes.fromhex("01 00 88 13")
104
- values = catalog.decode("node_status", payload)
105
- assert values == {"mode": "awake", "supply_voltage": 5.0}
106
- assert catalog.encode("node_status", values) == payload
109
+ payload = bytes.fromhex("01 00 64 00")
110
+ values = catalog.decode("frame_status", payload)
111
+ assert values == {"phase": "running", "value": 1.0}
112
+ assert catalog.encode("frame_status", values) == payload
107
113
  ```
108
114
 
109
115
  Offsets and sizes are in bytes. Here, byte 1 is unused and encodes as zero.
@@ -140,6 +146,13 @@ limits; applications must bound untrusted schema and payload sizes. See
140
146
 
141
147
  ## Documentation and examples
142
148
 
149
+ All examples use fictional values and are independent of specific products.
150
+
151
+ - [BCD records](https://github.com/nidops/structwire/blob/master/examples/bcd_record.py)
152
+ - [Packed bit fields](https://github.com/nidops/structwire/blob/master/examples/packed_fields.py)
153
+ - [YAML codec catalog](https://github.com/nidops/structwire/blob/master/examples/codecs.yaml)
154
+ - [BLE-style payload](https://github.com/nidops/structwire/blob/master/examples/ble_payload.py)
155
+ - [Generic message routing](https://github.com/nidops/structwire/blob/master/examples/transport_bindings.yaml)
143
156
  - [Python API](https://github.com/nidops/structwire/blob/master/docs/API.md)
144
157
  - [Schema reference](https://github.com/nidops/structwire/blob/master/docs/SCHEMA.md)
145
158
  - [Examples](https://github.com/nidops/structwire/tree/master/examples)
@@ -7,7 +7,13 @@ Your application supplies the bytes and handles transport I/O and routing.
7
7
 
8
8
  ## Install
9
9
 
10
- Python 3.11 or newer is required. Install from the source checkout:
10
+ Python 3.11 or newer is required. Install from PyPI:
11
+
12
+ ```bash
13
+ python -m pip install "structwire>=0.1.0.post1,<0.2"
14
+ ```
15
+
16
+ Or install from the source checkout:
11
17
 
12
18
  ```bash
13
19
  git clone https://github.com/nidops/structwire.git
@@ -15,7 +21,7 @@ cd structwire
15
21
  python -m pip install .
16
22
  ```
17
23
 
18
- Version `0.1.0` is an alpha API. PyYAML is installed automatically for YAML loading.
24
+ Version `0.1.0.post1` is an alpha API. PyYAML is installed automatically for YAML loading.
19
25
 
20
26
  ## Quick start
21
27
 
@@ -23,13 +29,13 @@ Version `0.1.0` is an alpha API. PyYAML is installed automatically for YAML load
23
29
  from structwire import Field, Layout
24
30
 
25
31
  codec = Layout(
26
- Field("temperature_c", "i16le", scale=0.1),
27
- Field("state", "u8"),
32
+ Field("level", "i16le", scale=0.1),
33
+ Field("quality", "u8"),
28
34
  ).compile()
29
35
 
30
- payload = codec.encode({"temperature_c": 23.5, "state": 1})
36
+ payload = codec.encode({"level": 23.5, "quality": 1})
31
37
  assert payload == bytes.fromhex("eb 00 01")
32
- assert codec.decode(payload) == {"temperature_c": 23.5, "state": 1}
38
+ assert codec.decode(payload) == {"level": 23.5, "quality": 1}
33
39
  ```
34
40
 
35
41
  Compile a layout once and reuse its codec.
@@ -41,27 +47,27 @@ Save this as `codecs.yaml`:
41
47
  ```yaml
42
48
  version: 1
43
49
  codecs:
44
- node_status:
50
+ frame_status:
45
51
  size: 4
46
52
  fields:
47
- - name: mode
53
+ - name: phase
48
54
  type: enum
49
- values: {0: asleep, 1: awake, 2: fault}
50
- - name: supply_voltage
55
+ values: {0: idle, 1: running, 2: paused}
56
+ - name: value
51
57
  type: uint16_le
52
58
  offset: 2
53
- scale: 0.001
54
- unit: V
59
+ scale: 0.01
60
+ unit: arbitrary
55
61
  ```
56
62
 
57
63
  ```python
58
64
  from structwire import catalog_from_yaml
59
65
 
60
66
  catalog = catalog_from_yaml("codecs.yaml")
61
- payload = bytes.fromhex("01 00 88 13")
62
- values = catalog.decode("node_status", payload)
63
- assert values == {"mode": "awake", "supply_voltage": 5.0}
64
- assert catalog.encode("node_status", values) == payload
67
+ payload = bytes.fromhex("01 00 64 00")
68
+ values = catalog.decode("frame_status", payload)
69
+ assert values == {"phase": "running", "value": 1.0}
70
+ assert catalog.encode("frame_status", values) == payload
65
71
  ```
66
72
 
67
73
  Offsets and sizes are in bytes. Here, byte 1 is unused and encodes as zero.
@@ -98,6 +104,13 @@ limits; applications must bound untrusted schema and payload sizes. See
98
104
 
99
105
  ## Documentation and examples
100
106
 
107
+ All examples use fictional values and are independent of specific products.
108
+
109
+ - [BCD records](https://github.com/nidops/structwire/blob/master/examples/bcd_record.py)
110
+ - [Packed bit fields](https://github.com/nidops/structwire/blob/master/examples/packed_fields.py)
111
+ - [YAML codec catalog](https://github.com/nidops/structwire/blob/master/examples/codecs.yaml)
112
+ - [BLE-style payload](https://github.com/nidops/structwire/blob/master/examples/ble_payload.py)
113
+ - [Generic message routing](https://github.com/nidops/structwire/blob/master/examples/transport_bindings.yaml)
101
114
  - [Python API](https://github.com/nidops/structwire/blob/master/docs/API.md)
102
115
  - [Schema reference](https://github.com/nidops/structwire/blob/master/docs/SCHEMA.md)
103
116
  - [Examples](https://github.com/nidops/structwire/tree/master/examples)
@@ -0,0 +1,52 @@
1
+ # Releasing StructWire
2
+
3
+ Package version lives in `src/structwire/__init__.py`; setuptools derives metadata
4
+ from it. Keep this as the single version source.
5
+
6
+ ## Setup
7
+
8
+ Configure [trusted publishing](https://docs.pypi.org/trusted-publishers/)
9
+ separately on TestPyPI and PyPI for project `structwire`, repository
10
+ `nidops/structwire`, and workflow `publish.yml`. Use GitHub environments `testpypi`
11
+ and `pypi` respectively, restricted to release tags. For a new project, register
12
+ a pending publisher; this does not reserve the package name. Confirm account
13
+ ownership and public documentation links.
14
+
15
+ ## Release
16
+
17
+ Before tagging, perform a publication-content review across source, examples,
18
+ tests, documentation, and configuration. Check the actual contents of both built
19
+ archives, not just their filenames. Ensure example schemas, identifiers, byte
20
+ sequences, and names are invented or explicitly cleared for public release.
21
+ Automated checks and passing CI do not prove publication rights.
22
+
23
+ 1. Update the package version and documentation.
24
+ 2. Run the [development checks](CONTRIBUTING.md#development) and
25
+ [distribution checks](CONTRIBUTING.md#distribution-checks).
26
+ 3. From a clean checkout, tag the reviewed `master` commit as `v<package version>`
27
+ after all supported CI jobs pass. For `0.1.0.post1`, use `v0.1.0.post1` and verify
28
+ it with `python scripts/check_dist.py dist --tag v0.1.0.post1`.
29
+ 4. Push the tag, then manually run **Publish** in GitHub Actions with that tag selected.
30
+
31
+ The workflow reruns CI, uploads verified artifacts to TestPyPI, checks the downloaded
32
+ wheel's hash and installation, then publishes the same artifacts to PyPI and verifies
33
+ again. Pushes alone do not publish. Check the package page after completion.
34
+
35
+ If publication fails, rerun failed jobs in the same run while its `release-dist`
36
+ artifact is available. Never overwrite tags or uploaded files, or hide mismatches
37
+ with `skip-existing`. Changed release contents require a new version.
38
+
39
+ ## Replacing an existing release
40
+
41
+ `0.1.0.post1` replaces the examples, documentation and test fixtures shipped in
42
+ `0.1.0` while preserving library behavior. Publish and verify the replacement on
43
+ both indexes before retiring the earlier release. Consumers with exact `==0.1.0`
44
+ pins must update them; a post-release cannot satisfy that exact pin.
45
+
46
+ [Yanking](https://docs.pypi.org/project-management/yanking/) leaves files available
47
+ to exact pins and direct downloads. If the old files must be removed, delete the
48
+ old release separately on PyPI and TestPyPI after verifying the replacement.
49
+ [Deleted filenames cannot be reused](https://pypi.org/help/#file-name-reuse).
50
+ Deleting an index release does not remove Git tags, branches, source archives,
51
+ or copies already downloaded. Keep recovery branches local when retiring public
52
+ history, and review old public refs separately.
@@ -2,8 +2,13 @@
2
2
 
3
3
  ## API and versions
4
4
 
5
- StructWire `0.1.0` is an alpha API. Consumers that depend on this series can use
6
- `structwire>=0.1,<0.2`.
5
+ StructWire `0.1.0.post1` is an alpha API. Consumers that depend on this series can use
6
+ `structwire>=0.1.0.post1,<0.2`.
7
+
8
+ This post-release updates documentation, examples and test fixtures. The public
9
+ API, schema version and encoding/decoding behavior are unchanged from `0.1.0`.
10
+ The example files are teaching material, outside the library's compatibility
11
+ contract. Exact pins to `structwire==0.1.0` must be updated to select the post-release.
7
12
 
8
13
  Schema documents have a separate `version: 1`. Unsupported schema versions,
9
14
  unknown keys, conflicting aliases, and duplicate YAML mapping keys are rejected
@@ -0,0 +1,61 @@
1
+ # Integration examples
2
+
3
+ These teaching examples use invented identifiers and payloads, not real-world
4
+ message definitions.
5
+
6
+ The application selects a codec and passes it payload bytes. It handles
7
+ identifiers, framing, checksums and device I/O. The YAML below is illustrative
8
+ **application configuration**; StructWire does not load the outer mappings.
9
+
10
+ ## Named codec reference
11
+
12
+ A fictional message type can be mapped to a named catalog entry:
13
+
14
+ ```yaml
15
+ messages:
16
+ by_kind:
17
+ status:
18
+ codec: frame_status
19
+ ```
20
+
21
+ The application looks up `frame_status` and calls
22
+ `catalog.decode("frame_status", payload)`.
23
+ See [catalog_demo.py](../examples/catalog_demo.py) and
24
+ [codecs.yaml](../examples/codecs.yaml) for runnable examples.
25
+
26
+ ## Inline layout
27
+
28
+ Application configuration may also contain a complete synthetic layout:
29
+
30
+ ```yaml
31
+ messages:
32
+ by_kind:
33
+ custom:
34
+ layout:
35
+ size: 2
36
+ fields:
37
+ - name: category
38
+ type: enum
39
+ values: {0: basic, 1: extended}
40
+ - name: count
41
+ type: uint8
42
+ ```
43
+
44
+ The application extracts the nested `layout` mapping and passes it to
45
+ `layout_from_mapping(...).compile()`. All routing remains outside StructWire.
46
+
47
+ ## Framed byte stream
48
+
49
+ For example, an imaginary stream might contain:
50
+
51
+ ```text
52
+ C7 21 | <6 payload bytes> | checksum
53
+ ```
54
+
55
+ The application checks the packet envelope, extracts six bytes and uses
56
+ `catalog.decode("environment_sample", payload)`. This library does **not**
57
+ implement the fictional envelope or checksum.
58
+
59
+ The [transport bindings](../examples/transport_bindings.yaml) and
60
+ [UART bindings](../examples/uart_bindings.yaml) illustrate application-owned
61
+ routing independently of any particular framework.
@@ -52,11 +52,11 @@ Encoding fills unused bytes with zero; decoding does not preserve their values.
52
52
  - `unit`: display/documentation metadata only
53
53
  - `description`: display/documentation metadata only
54
54
 
55
- Numeric `bias` also accepts the compatibility alias `physical_offset`. If both
55
+ Numeric `bias` also accepts the alias `physical_offset`. If both
56
56
  spellings of an alias are supplied, their values must agree. Unknown catalog,
57
57
  layout and field keys are rejected.
58
58
 
59
- For compatibility, a valid `byteorder` is accepted on every kind. Integer/float
59
+ A valid `byteorder` is accepted on every kind. Integer/float
60
60
  kind names take precedence even when the supplied order disagrees (including a
61
61
  little-endian kind with the default `big`). BCD, ASCII and bytes ignore it and
62
62
  retain wire order. `describe()` reports the kind-defined order for integers and
@@ -90,7 +90,7 @@ labels must be strings; field, flag and catalog names must be non-empty. Enum
90
90
  labels must be unique. Quote YAML labels such as
91
91
  `"on"`, `"off"`, `"yes"` and `"no"`: PyYAML otherwise resolves these as booleans.
92
92
 
93
- ### Compatibility kind aliases
93
+ ### Kind aliases
94
94
 
95
95
  `uint8`/`int8` map to `u8`/`i8`. For widths 16, 24, 32 and 64,
96
96
  `uint<width>_be`/`uint<width>_le` and `int<width>_be`/`int<width>_le` map to their
@@ -137,12 +137,12 @@ overlapping fields unless their masks are disjoint:
137
137
  from structwire import Field, Layout
138
138
 
139
139
  status = Layout(
140
- Field("error", "u8", mask=0xF0, shift=4),
141
- Field("alive", "u8", offset=0, mask=0x0F),
140
+ Field("upper_nibble", "u8", mask=0xF0, shift=4),
141
+ Field("lower_nibble", "u8", offset=0, mask=0x0F),
142
142
  ).compile()
143
143
 
144
- assert status.decode(b"\x53") == {"error": 5, "alive": 3}
145
- assert status.encode({"error": 5, "alive": 3}) == b"\x53"
144
+ assert status.decode(b"\x53") == {"upper_nibble": 5, "lower_nibble": 3}
145
+ assert status.encode({"upper_nibble": 5, "lower_nibble": 3}) == b"\x53"
146
146
  ```
147
147
 
148
148
  Routing keys belong in project configuration, outside this schema.
@@ -0,0 +1,14 @@
1
+ from structwire import Field, Layout
2
+
3
+ # Invented numeric record demonstrating one-byte and multi-byte packed BCD.
4
+ record = Layout(
5
+ Field("batch", "bcd", length=2),
6
+ Field("quantity", "bcd"),
7
+ name="example-bcd-record",
8
+ ).compile()
9
+
10
+ values = {"batch": 1234, "quantity": 25}
11
+ encoded = record.encode(values)
12
+
13
+ assert encoded == bytes.fromhex("12 34 25")
14
+ assert record.decode(encoded) == values
@@ -0,0 +1,21 @@
1
+ from structwire import Field, Layout
2
+
3
+ # Invented BLE application payload. Not based on any product's GATT specification.
4
+ measurement = Layout(
5
+ Field("format", "u8"),
6
+ Field("channel", "u8"),
7
+ Field("temperature_c", "i16le", scale=0.1),
8
+ Field("humidity_pct", "u8"),
9
+ Field("battery_pct", "u8"),
10
+ name="example-ble-measurement",
11
+ ).compile()
12
+
13
+ values = {
14
+ "format": 1,
15
+ "channel": 3,
16
+ "temperature_c": 21.5,
17
+ "humidity_pct": 48,
18
+ "battery_pct": 82,
19
+ }
20
+ raw = measurement.encode(values)
21
+ assert measurement.decode(raw) == values
@@ -0,0 +1,21 @@
1
+ from pathlib import Path
2
+
3
+ from structwire import catalog_from_yaml
4
+
5
+ catalog = catalog_from_yaml(Path(__file__).with_name("codecs.yaml"))
6
+
7
+ # Purely synthetic example bytes: phase=running, both flags set, value=100.
8
+ payload = bytes.fromhex("01 03 10 27")
9
+ decoded = catalog.decode("frame_status", payload)
10
+ assert decoded == {"phase": "running", "ready": True, "flagged": True, "value": 100.0}
11
+ assert catalog.encode("frame_status", decoded) == payload
12
+
13
+ values = {
14
+ "channel": 7,
15
+ "state": "sampling",
16
+ "temperature_c": 21.5,
17
+ "humidity_pct": 48.0,
18
+ }
19
+ encoded = catalog.encode("environment_sample", values)
20
+ assert catalog.decode("environment_sample", encoded) == values
21
+ print(encoded.hex(" ").upper())
@@ -0,0 +1,43 @@
1
+ # All layouts and values are fictional teaching examples, not production formats.
2
+ version: 1
3
+
4
+ codecs:
5
+ frame_status:
6
+ description: Synthetic packed record demonstrating enums, bits and scaling.
7
+ size: 4
8
+ fields:
9
+ - name: phase
10
+ type: enum
11
+ values: {0: idle, 1: running, 2: paused}
12
+ - name: ready
13
+ type: bool
14
+ offset: 1
15
+ bitmask: 0x01
16
+ - name: flagged
17
+ type: bool
18
+ offset: 1
19
+ bitmask: 0x02
20
+ bitshift: 1
21
+ - name: value
22
+ type: uint16_le
23
+ offset: 2
24
+ scale: 0.01
25
+ unit: arbitrary
26
+
27
+ environment_sample:
28
+ description: Synthetic six-byte environmental measurement.
29
+ size: 6
30
+ fields:
31
+ - name: channel
32
+ type: uint8
33
+ - name: state
34
+ type: enum
35
+ values: {0: idle, 1: sampling, 2: paused}
36
+ - name: temperature_c
37
+ type: int16_le
38
+ scale: 0.01
39
+ unit: degC
40
+ - name: humidity_pct
41
+ type: uint16_le
42
+ scale: 0.01
43
+ unit: pct
@@ -0,0 +1,14 @@
1
+ from structwire import Field, Layout
2
+
3
+ # Invented record demonstrating two disjoint nibble masks in the same byte.
4
+ record = Layout(
5
+ Field("upper_code", "u8", mask=0xF0, shift=4),
6
+ Field("lower_code", "u8", offset=0, mask=0x0F),
7
+ Field("reading", "u16le", scale=0.1),
8
+ name="example-packed-fields",
9
+ ).compile()
10
+
11
+ values = {"upper_code": 2, "lower_code": 7, "reading": 12.5}
12
+ payload = record.encode(values)
13
+ assert record.decode(payload) == values
14
+ print(payload.hex(" ").upper())
@@ -0,0 +1,35 @@
1
+ # Invented routing keys, not values from any real device or protocol.
2
+ # This is application configuration, not a StructWire schema.
3
+ messages:
4
+ by_kind:
5
+ sample:
6
+ codec: environment_sample
7
+ status:
8
+ codec: frame_status
9
+ by_address:
10
+ 0x10:
11
+ codec: frame_status
12
+ 0x20:
13
+ codec: environment_sample
14
+ address_ranges:
15
+ - base: 0x40
16
+ count: 4
17
+ codec: environment_sample
18
+ index_name: channel
19
+
20
+ # Only the nested layout is passed to layout_from_mapping().
21
+ inline_example:
22
+ kinds:
23
+ custom:
24
+ layout:
25
+ size: 4
26
+ fields:
27
+ - name: category
28
+ type: enum
29
+ values: {0: basic, 1: extended}
30
+ - name: count
31
+ type: uint8
32
+ - name: ratio
33
+ type: uint16_le
34
+ scale: 0.01
35
+ unit: pct
@@ -0,0 +1,13 @@
1
+ # Completely fictional packet envelope used to illustrate application-side routing.
2
+ # The application owns framing, checksums and I/O; StructWire sees only payload bytes.
3
+ # Example: [marker=0xC7][kind=0x21][6-byte payload][checksum]
4
+ uart:
5
+ messages:
6
+ sample:
7
+ match:
8
+ marker: 0xC7
9
+ kind: 0x21
10
+ payload:
11
+ offset: 2
12
+ length: 6
13
+ codec: environment_sample
@@ -135,7 +135,9 @@ def inspect_distributions(directory: Path, tag: str | None = None) -> dict:
135
135
  def main() -> None:
136
136
  parser = argparse.ArgumentParser(description=__doc__)
137
137
  parser.add_argument("directory", type=Path, nargs="?", default=Path("dist"))
138
- parser.add_argument("--tag", help="Require a vX.Y.Z tag matching the built version")
138
+ parser.add_argument(
139
+ "--tag", help="Require a version tag matching the built version (e.g. v0.1.0.post1)"
140
+ )
139
141
  args = parser.parse_args()
140
142
  print(json.dumps(inspect_distributions(args.directory, args.tag), indent=2))
141
143
 
@@ -30,4 +30,4 @@ __all__ = [
30
30
  "layout_from_yaml",
31
31
  ]
32
32
 
33
- __version__ = "0.1.0"
33
+ __version__ = "0.1.0.post1"
@@ -78,7 +78,7 @@ def _schema_integer(value: Any, where: str) -> int:
78
78
 
79
79
 
80
80
  def canonical_kind(kind: str) -> str:
81
- """Return a stable kind name, accepting compatibility aliases."""
81
+ """Return a stable kind name, accepting kind aliases."""
82
82
  if not isinstance(kind, str):
83
83
  raise SchemaError("Field kind must be a string")
84
84
  key = kind.strip().lower().replace("-", "_")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structwire
3
- Version: 0.1.0
3
+ Version: 0.1.0.post1
4
4
  Summary: A small schema-driven binary payload codec for Python.
5
5
  Author: Nidhal Abidi
6
6
  License-Expression: MIT
@@ -49,7 +49,13 @@ Your application supplies the bytes and handles transport I/O and routing.
49
49
 
50
50
  ## Install
51
51
 
52
- Python 3.11 or newer is required. Install from the source checkout:
52
+ Python 3.11 or newer is required. Install from PyPI:
53
+
54
+ ```bash
55
+ python -m pip install "structwire>=0.1.0.post1,<0.2"
56
+ ```
57
+
58
+ Or install from the source checkout:
53
59
 
54
60
  ```bash
55
61
  git clone https://github.com/nidops/structwire.git
@@ -57,7 +63,7 @@ cd structwire
57
63
  python -m pip install .
58
64
  ```
59
65
 
60
- Version `0.1.0` is an alpha API. PyYAML is installed automatically for YAML loading.
66
+ Version `0.1.0.post1` is an alpha API. PyYAML is installed automatically for YAML loading.
61
67
 
62
68
  ## Quick start
63
69
 
@@ -65,13 +71,13 @@ Version `0.1.0` is an alpha API. PyYAML is installed automatically for YAML load
65
71
  from structwire import Field, Layout
66
72
 
67
73
  codec = Layout(
68
- Field("temperature_c", "i16le", scale=0.1),
69
- Field("state", "u8"),
74
+ Field("level", "i16le", scale=0.1),
75
+ Field("quality", "u8"),
70
76
  ).compile()
71
77
 
72
- payload = codec.encode({"temperature_c": 23.5, "state": 1})
78
+ payload = codec.encode({"level": 23.5, "quality": 1})
73
79
  assert payload == bytes.fromhex("eb 00 01")
74
- assert codec.decode(payload) == {"temperature_c": 23.5, "state": 1}
80
+ assert codec.decode(payload) == {"level": 23.5, "quality": 1}
75
81
  ```
76
82
 
77
83
  Compile a layout once and reuse its codec.
@@ -83,27 +89,27 @@ Save this as `codecs.yaml`:
83
89
  ```yaml
84
90
  version: 1
85
91
  codecs:
86
- node_status:
92
+ frame_status:
87
93
  size: 4
88
94
  fields:
89
- - name: mode
95
+ - name: phase
90
96
  type: enum
91
- values: {0: asleep, 1: awake, 2: fault}
92
- - name: supply_voltage
97
+ values: {0: idle, 1: running, 2: paused}
98
+ - name: value
93
99
  type: uint16_le
94
100
  offset: 2
95
- scale: 0.001
96
- unit: V
101
+ scale: 0.01
102
+ unit: arbitrary
97
103
  ```
98
104
 
99
105
  ```python
100
106
  from structwire import catalog_from_yaml
101
107
 
102
108
  catalog = catalog_from_yaml("codecs.yaml")
103
- payload = bytes.fromhex("01 00 88 13")
104
- values = catalog.decode("node_status", payload)
105
- assert values == {"mode": "awake", "supply_voltage": 5.0}
106
- assert catalog.encode("node_status", values) == payload
109
+ payload = bytes.fromhex("01 00 64 00")
110
+ values = catalog.decode("frame_status", payload)
111
+ assert values == {"phase": "running", "value": 1.0}
112
+ assert catalog.encode("frame_status", values) == payload
107
113
  ```
108
114
 
109
115
  Offsets and sizes are in bytes. Here, byte 1 is unused and encodes as zero.
@@ -140,6 +146,13 @@ limits; applications must bound untrusted schema and payload sizes. See
140
146
 
141
147
  ## Documentation and examples
142
148
 
149
+ All examples use fictional values and are independent of specific products.
150
+
151
+ - [BCD records](https://github.com/nidops/structwire/blob/master/examples/bcd_record.py)
152
+ - [Packed bit fields](https://github.com/nidops/structwire/blob/master/examples/packed_fields.py)
153
+ - [YAML codec catalog](https://github.com/nidops/structwire/blob/master/examples/codecs.yaml)
154
+ - [BLE-style payload](https://github.com/nidops/structwire/blob/master/examples/ble_payload.py)
155
+ - [Generic message routing](https://github.com/nidops/structwire/blob/master/examples/transport_bindings.yaml)
143
156
  - [Python API](https://github.com/nidops/structwire/blob/master/docs/API.md)
144
157
  - [Schema reference](https://github.com/nidops/structwire/blob/master/docs/SCHEMA.md)
145
158
  - [Examples](https://github.com/nidops/structwire/tree/master/examples)
@@ -6,17 +6,16 @@ RELEASING.md
6
6
  SECURITY.md
7
7
  pyproject.toml
8
8
  docs/API.md
9
- docs/AUTOSPAN_ADOPTION.md
10
9
  docs/COMPATIBILITY.md
11
10
  docs/DESIGN.md
12
11
  docs/INTEGRATION_MODEL.md
13
12
  docs/SCHEMA.md
14
- examples/automotive_bindings.yaml
13
+ examples/bcd_record.py
15
14
  examples/ble_payload.py
16
- examples/can_like_signal.py
17
15
  examples/catalog_demo.py
18
16
  examples/codecs.yaml
19
- examples/diagnostic_record.py
17
+ examples/packed_fields.py
18
+ examples/transport_bindings.yaml
20
19
  examples/uart_bindings.yaml
21
20
  scripts/check_dist.py
22
21
  scripts/check_readme.py
@@ -42,21 +42,19 @@ def test_nonfinite_integer_values_raise_encode_error(value: float) -> None:
42
42
  Layout(Field("count", "u8")).encode({"count": value})
43
43
 
44
44
 
45
- def test_sequential_bcd_and_integers_compatibility() -> None:
45
+ def test_sequential_bcd_and_integers_round_trip() -> None:
46
46
  codec = Layout(
47
- Field("version_major", "bcd"),
48
- Field("version_minor", "bcd"),
49
- Field("version_patch", "bcd"),
50
- Field("compile_hour", "uint8"),
51
- Field("compile_minute", "bcd"),
47
+ Field("batch", "bcd", length=2),
48
+ Field("quantity", "uint8"),
49
+ Field("group", "bcd"),
50
+ Field("sequence", "bcd"),
52
51
  ).compile()
53
- payload = bytes.fromhex("26 29 01 08 36")
52
+ payload = bytes.fromhex("12 34 07 25 05")
54
53
  assert codec.decode(payload) == {
55
- "version_major": 26,
56
- "version_minor": 29,
57
- "version_patch": 1,
58
- "compile_hour": 8,
59
- "compile_minute": 36,
54
+ "batch": 1234,
55
+ "quantity": 7,
56
+ "group": 25,
57
+ "sequence": 5,
60
58
  }
61
59
  assert codec.encode(codec.decode(payload)) == payload
62
60
 
@@ -96,9 +94,9 @@ def test_raw_bytes_exact_width() -> None:
96
94
 
97
95
  def test_mac_roundtrip() -> None:
98
96
  codec = Layout(Field("address", "mac_address")).compile()
99
- payload = bytes.fromhex("7B 7C FA 58 1A A6")
100
- assert codec.decode(payload)["address"] == "7B:7C:FA:58:1A:A6"
101
- assert codec.encode({"address": "7B:7C:FA:58:1A:A6"}) == payload
97
+ payload = bytes.fromhex("02 00 00 00 00 01")
98
+ assert codec.decode(payload)["address"] == "02:00:00:00:00:01"
99
+ assert codec.encode({"address": "02:00:00:00:00:01"}) == payload
102
100
 
103
101
 
104
102
  @pytest.mark.parametrize("kind,raw", [("u64be", 2**53 + 1), ("i64be", -(2**53 + 1))])
@@ -368,9 +366,9 @@ def test_raw_integer_is_not_treated_as_zero_bytes() -> None:
368
366
 
369
367
  def test_little_endian_mac_roundtrip() -> None:
370
368
  codec = Layout(Field("address", "mac", byteorder="little")).compile()
371
- payload = bytes.fromhex("A6 1A 58 FA 7C 7B")
372
- assert codec.decode(payload) == {"address": "7B:7C:FA:58:1A:A6"}
373
- assert codec.encode({"address": "7B:7C:FA:58:1A:A6"}) == payload
369
+ payload = bytes.fromhex("01 00 00 00 00 02")
370
+ assert codec.decode(payload) == {"address": "02:00:00:00:00:01"}
371
+ assert codec.encode({"address": "02:00:00:00:00:01"}) == payload
374
372
 
375
373
 
376
374
  @pytest.mark.parametrize("payload", [1, True, "x", [1], None])
@@ -119,5 +119,5 @@ def test_example_bindings_reference_existing_codecs() -> None:
119
119
  for nested in value:
120
120
  validate_bindings(nested)
121
121
 
122
- for name in ("automotive_bindings.yaml", "uart_bindings.yaml"):
122
+ for name in ("transport_bindings.yaml", "uart_bindings.yaml"):
123
123
  validate_bindings(_yaml_load(ROOT / "examples" / name))
@@ -15,18 +15,18 @@ from structwire import (
15
15
  )
16
16
 
17
17
 
18
- def test_mapping_loader_accepts_compatibility_names() -> None:
18
+ def test_mapping_loader_accepts_schema_aliases() -> None:
19
19
  layout = layout_from_mapping(
20
20
  {
21
21
  "name": "status",
22
22
  "fields": [
23
- {"name": "error", "type": "uint8", "bitmask": "0xF0", "bitshift": 4},
24
- {"name": "alive", "type": "uint8", "offset": 0, "bitmask": "0x0F"},
23
+ {"name": "upper_nibble", "type": "uint8", "bitmask": "0xF0", "bitshift": 4},
24
+ {"name": "lower_nibble", "type": "uint8", "offset": 0, "bitmask": "0x0F"},
25
25
  ],
26
26
  }
27
27
  )
28
28
  codec = layout.compile()
29
- assert codec.decode(b"\x53") == {"error": 5, "alive": 3}
29
+ assert codec.decode(b"\x53") == {"upper_nibble": 5, "lower_nibble": 3}
30
30
 
31
31
 
32
32
  def test_mapping_loader_parses_enum_hex_keys() -> None:
@@ -45,13 +45,13 @@ def test_describe_reports_resolved_offsets() -> None:
45
45
  assert description["fields"][1]["offset"] == 1
46
46
 
47
47
 
48
- def test_formatting_helpers_preserve_legacy_human_readability() -> None:
49
- assert format_mac_address(bytes.fromhex("7B 7C FA 58 1A A6")) == "7B:7C:FA:58:1A:A6"
48
+ def test_formatting_helpers_render_readable_values() -> None:
49
+ assert format_mac_address(bytes.fromhex("02 00 00 00 00 01")) == "02:00:00:00:00:01"
50
50
  assert (
51
- format_mac_address(bytes.fromhex("A6 1A 58 FA 7C 7B"), byteorder="little")
52
- == "7B:7C:FA:58:1A:A6"
51
+ format_mac_address(bytes.fromhex("01 00 00 00 00 02"), byteorder="little")
52
+ == "02:00:00:00:00:01"
53
53
  )
54
- assert format_value(b"\x7b\x7c\xfa") == "7B 7C FA"
54
+ assert format_value(b"\x12\x34\x56") == "12 34 56"
55
55
  assert format_value(b"") == "<empty>"
56
56
  assert format_record({"raw": b"\x01", "nested": {"x": b"\x02"}}) == {
57
57
  "raw": "01",
@@ -1,31 +0,0 @@
1
- # Releasing StructWire
2
-
3
- Package version lives in `src/structwire/__init__.py`; setuptools derives metadata
4
- from it. Keep this as the single version source.
5
-
6
- ## Setup
7
-
8
- Configure [trusted publishing](https://docs.pypi.org/trusted-publishers/)
9
- separately on TestPyPI and PyPI for project `structwire`, repository
10
- `nidops/structwire`, and workflow `publish.yml`. Use GitHub environments `testpypi`
11
- and `pypi` respectively, restricted to release tags. For a new project, register
12
- a pending publisher; this does not reserve the package name. Confirm account
13
- ownership and public documentation links.
14
-
15
- ## Release
16
-
17
- 1. Update the package version and documentation.
18
- 2. Run the [development checks](CONTRIBUTING.md#development) and
19
- [distribution checks](CONTRIBUTING.md#distribution-checks).
20
- 3. From a clean checkout, tag the reviewed `master` commit as `vX.Y.Z` after all
21
- supported CI jobs pass. Verify the version with
22
- `python scripts/check_dist.py dist --tag vX.Y.Z`, substituting the actual version.
23
- 4. Push the tag, then manually run **Publish** in GitHub Actions with that tag selected.
24
-
25
- The workflow reruns CI, uploads verified artifacts to TestPyPI, checks the downloaded
26
- wheel's hash and installation, then publishes the same artifacts to PyPI and verifies
27
- again. Pushes alone do not publish. Check the package page after completion.
28
-
29
- If publication fails, rerun failed jobs in the same run while its `release-dist`
30
- artifact is available. Never overwrite tags or uploaded files, or hide mismatches
31
- with `skip-existing`. Changed release contents require a new version.
@@ -1,41 +0,0 @@
1
- # Optional AutoSpan adoption
2
-
3
- An AutoSpan project plugin can use StructWire to encode and decode payloads.
4
- StructWire has no AutoSpan dependency, and using it does not require AutoSpan core
5
- to depend on it.
6
-
7
- Suggested responsibilities for this optional integration:
8
-
9
- | Component | Responsibility |
10
- |---|---|
11
- | AutoSpan core | Test planning, execution and evidence |
12
- | StructWire | Payload layouts, encoding, decoding and value formatting |
13
- | Driver or adapter | Transport, protocol and device operations |
14
- | Project plugin | Device configuration, routing and payload definitions |
15
-
16
- ## Project payload catalog
17
-
18
- A project-specific diagnostic or signal catalog should own the mapping from its identifier to a
19
- StructWire `Codec`:
20
-
21
- ```python
22
- from structwire import Field, Layout
23
-
24
- SOFTWARE_VERSION = Layout(
25
- Field("major", "bcd"),
26
- Field("minor", "bcd"),
27
- Field("patch", "bcd"),
28
- ).compile()
29
-
30
- PAYLOADS = {
31
- 0x1001: SOFTWARE_VERSION,
32
- }
33
- ```
34
-
35
- The protocol layer remains responsible for extracting or adding service identifiers and headers.
36
- StructWire receives only the payload region whose binary layout it owns.
37
-
38
- ## Integration rule
39
-
40
- Declare the StructWire dependency in the plugin that uses it. For named catalogs
41
- and inline layouts, see the [integration examples](INTEGRATION_MODEL.md).
@@ -1,68 +0,0 @@
1
- # Integration examples
2
-
3
- The application selects a codec and passes it payload bytes. It handles protocol
4
- identifiers, headers, framing, checksums and device I/O.
5
-
6
- The routing YAML below is illustrative application configuration. StructWire does
7
- not load these outer mappings; application code must extract the codec name or
8
- nested layout.
9
-
10
- ## Named codec reference
11
-
12
- Use a named catalog when several messages or tests share a layout:
13
-
14
- ```yaml
15
- can:
16
- frames:
17
- 0x321:
18
- codec: node_status
19
- ```
20
-
21
- The application reads the frame identifier, looks up `node_status` in its
22
- configuration, then calls `catalog.decode("node_status", payload)`.
23
- See [catalog_demo.py](../examples/catalog_demo.py) and
24
- [codecs.yaml](../examples/codecs.yaml) for a working catalog example.
25
-
26
- ## Inline layout
27
-
28
- An application can keep a layout alongside a single routing entry:
29
-
30
- ```yaml
31
- diagnostics:
32
- dids:
33
- 0xF186:
34
- layout:
35
- size: 1
36
- fields:
37
- - name: session
38
- type: enum
39
- values: {0x01: default, 0x02: programming, 0x03: extended}
40
- ```
41
-
42
- The diagnostic application passes the nested `layout` mapping to
43
- `layout_from_mapping(...).compile()`. The diagnostic identifier (`0xF186`) stays
44
- in the application.
45
-
46
- ## UART example
47
-
48
- A small device may use a packet envelope such as:
49
-
50
- ```text
51
- A5 10 | <6 payload bytes> | CRC16
52
- ```
53
-
54
- The application validates the framing and CRC, then extracts the payload.
55
- Its configuration could map the message type to a codec:
56
-
57
- ```yaml
58
- uart:
59
- messages:
60
- status:
61
- match: {sync: 0xA5, type: 0x10}
62
- payload: {offset: 2, length: 6, codec: sensor_status}
63
- ```
64
-
65
- The application passes exactly six bytes to `catalog.decode("sensor_status", payload)`.
66
- The illustrative [automotive](../examples/automotive_bindings.yaml) and
67
- [UART](../examples/uart_bindings.yaml) configuration files reference codecs from
68
- the example catalog. For AutoSpan projects, see [optional adoption](AUTOSPAN_ADOPTION.md).
@@ -1,36 +0,0 @@
1
- # Illustrative application configuration, not a StructWire schema.
2
- # Application code maps these protocol identifiers to codecs from codecs.yaml.
3
-
4
- diagnostics:
5
- dids:
6
- 0xF186:
7
- codec: diagnostic_session
8
-
9
- can:
10
- frames:
11
- 0x321:
12
- codec: node_status
13
-
14
- # Application-defined range routing.
15
- ranges:
16
- - base_id: 0x300
17
- count: 16
18
- codec: node_status
19
- index_name: node_id
20
-
21
- # Application code passes only the nested layout to layout_from_mapping().
22
- diagnostics_inline_example:
23
- dids:
24
- 0xF187:
25
- layout:
26
- size: 4
27
- fields:
28
- - name: state
29
- type: enum
30
- values: {0x00: inactive, 0x01: active}
31
- - name: counter
32
- type: uint8
33
- - name: voltage
34
- type: uint16_le
35
- scale: 0.001
36
- unit: V
@@ -1,14 +0,0 @@
1
- from structwire import Field, Layout
2
-
3
- advertisement = Layout(
4
- Field("version", "u8"),
5
- Field("sensor_id", "u16le"),
6
- Field("temperature_c", "i16le", scale=0.01),
7
- Field("battery_pct", "u8"),
8
- name="sensor-advertisement",
9
- ).compile()
10
-
11
- raw = advertisement.encode(
12
- {"version": 1, "sensor_id": 42, "temperature_c": 23.75, "battery_pct": 91}
13
- )
14
- assert advertisement.decode(raw)["sensor_id"] == 42
@@ -1,12 +0,0 @@
1
- from structwire import Field, Layout
2
-
3
- status = Layout(
4
- Field("error_state", "u8", mask=0xF0, shift=4),
5
- Field("alive_state", "u8", offset=0, mask=0x0F),
6
- Field("voltage", "u16le", scale=0.01),
7
- name="status-frame",
8
- ).compile()
9
-
10
- payload = status.encode({"error_state": 2, "alive_state": 7, "voltage": 13.42})
11
- print(payload.hex(" ").upper())
12
- print(status.decode(payload))
@@ -1,19 +0,0 @@
1
- from pathlib import Path
2
-
3
- from structwire import catalog_from_yaml
4
-
5
- catalog = catalog_from_yaml(Path(__file__).with_name("codecs.yaml"))
6
-
7
- payload = bytes.fromhex("01 03 34 30")
8
- print(catalog.decode("node_status", payload))
9
-
10
- encoded = catalog.encode(
11
- "sensor_status",
12
- {
13
- "sensor_id": 7,
14
- "state": "measuring",
15
- "temperature": 23.45,
16
- "battery_voltage": 3.721,
17
- },
18
- )
19
- print(encoded.hex(" ").upper())
@@ -1,59 +0,0 @@
1
- version: 1
2
-
3
- codecs:
4
- diagnostic_session:
5
- description: One-byte diagnostic session state.
6
- size: 1
7
- fields:
8
- - name: session
9
- type: enum
10
- values:
11
- 0x01: default
12
- 0x02: programming
13
- 0x03: extended
14
-
15
- node_status:
16
- description: Synthetic automotive cyclic status payload.
17
- size: 4
18
- fields:
19
- - name: mode
20
- type: enum
21
- values:
22
- 0x00: asleep
23
- 0x01: awake
24
- 0x02: fault
25
- - name: communication_ok
26
- type: bool
27
- offset: 1
28
- bitmask: 0x01
29
- - name: sensor_ok
30
- type: bool
31
- offset: 1
32
- bitmask: 0x02
33
- bitshift: 1
34
- - name: supply_voltage
35
- type: uint16_le
36
- offset: 2
37
- scale: 0.001
38
- unit: V
39
-
40
- sensor_status:
41
- description: Small device payload suitable for UART, BLE, or another transport.
42
- size: 6
43
- fields:
44
- - name: sensor_id
45
- type: uint8
46
- - name: state
47
- type: enum
48
- values:
49
- 0x00: idle
50
- 0x01: measuring
51
- 0x02: error
52
- - name: temperature
53
- type: int16_le
54
- scale: 0.01
55
- unit: degC
56
- - name: battery_voltage
57
- type: uint16_le
58
- scale: 0.001
59
- unit: V
@@ -1,13 +0,0 @@
1
- from structwire import Field, Layout
2
-
3
- software_version = Layout(
4
- Field("major", "bcd"),
5
- Field("minor", "bcd"),
6
- Field("patch", "bcd"),
7
- Field("build", "u16be"),
8
- name="software-version",
9
- ).compile()
10
-
11
- raw = bytes.fromhex("26 10 03 01 F4")
12
- assert software_version.decode(raw) == {"major": 26, "minor": 10, "patch": 3, "build": 500}
13
- assert software_version.encode(software_version.decode(raw)) == raw
@@ -1,13 +0,0 @@
1
- # Illustrative application configuration; the application handles framing and routing.
2
- # Example wire packet: [sync=0xA5][type=0x10][6-byte payload][crc16]
3
-
4
- uart:
5
- messages:
6
- status:
7
- match:
8
- sync: 0xA5
9
- type: 0x10
10
- payload:
11
- offset: 2
12
- length: 6
13
- codec: sensor_status
File without changes
File without changes
File without changes
File without changes
File without changes