diff --git a/pyproject.toml b/pyproject.toml index 28d6af1..25d63bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "python_thingset" -version = "0.5.1" +version = "0.5.2" description = "A Python library for ThingSet functionality" authors = [ { name = "Adam Mitchell", email = "adam.mitchell@brillpower.com" } diff --git a/python_thingset/encoders/binary.py b/python_thingset/encoders/binary.py index 540b7bc..057a6ad 100644 --- a/python_thingset/encoders/binary.py +++ b/python_thingset/encoders/binary.py @@ -14,6 +14,7 @@ class ThingSetBinaryEncoder(object): NULL_BYTE = 0xF6 + ROOT_ID = 0x00 def __init__(self): pass @@ -62,7 +63,17 @@ def encode_exec(self, value_id: int, args: List[Union[Any, None]]) -> bytes: + list(cbor2.dumps(p_args, canonical=True)) ) - def encode_update(self, parent_id: int, value_id: int, value: Any) -> bytes: + def encode_update( + self, parent_id: Union[int, None], value_id: int, value: Any + ) -> bytes: + """Update request: ``07 {value_id: value}``. + + ``parent_id`` is the containing group; ``None`` means the object sits + at the root and is addressed via the root node (ID 0). A CBOR null is + never a valid endpoint — the device rejects it with BAD_REQUEST before + looking at the map — so it must not be emitted for a missing parent.""" + if parent_id is None: + parent_id = self.ROOT_ID value = self._coerce_value(value) return bytes( diff --git a/tests/encoders/binary/test_enc_bin_update.py b/tests/encoders/binary/test_enc_bin_update.py index ba2a9aa..dad2c17 100644 --- a/tests/encoders/binary/test_enc_bin_update.py +++ b/tests/encoders/binary/test_enc_bin_update.py @@ -62,3 +62,16 @@ def test_update_list_of_strings(): encoded = encoder.encode_update(0x0, 0x4F, ["a", "b"]) expected = bytes([0x07]) + cbor2.dumps(0x0) + cbor2.dumps({0x4F: ["a", "b"]}, canonical=True) assert encoded == expected + + +def test_update_no_parent_targets_root(): + # A root-level object has no containing group: the request must address + # the root node (ID 0), never a CBOR null endpoint. + encoded = encoder.encode_update(None, 0x4F, 1) + assert encoded == encoder.encode_update(0x0, 0x4F, 1) + assert encoded[1] == 0x00 + + +def test_update_native_bool_false(): + encoded = encoder.encode_update(0x0, 0x4F, False) + assert encoded == b"\x07\x00\xa1\x18\x4f\xf4" diff --git a/uv.lock b/uv.lock index ce4e3b5..af8c26d 100644 --- a/uv.lock +++ b/uv.lock @@ -311,7 +311,7 @@ wheels = [ [[package]] name = "python-thingset" -version = "0.5.0" +version = "0.5.2" source = { editable = "." } dependencies = [ { name = "can-isotp" },