Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add variables block; support map types in codec processor #602

Merged
merged 1 commit into from
Feb 29, 2024
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
2 changes: 2 additions & 0 deletions brewblox_devcon_spark/codec/pb2.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import TempSensorMock_pb2
import TempSensorOneWire_pb2
import TouchSettings_pb2
import Variables_pb2
import WiFiSettings_pb2

__all__ = [
Expand Down Expand Up @@ -78,5 +79,6 @@
'TempSensorMock_pb2',
'TempSensorOneWire_pb2',
'TouchSettings_pb2',
'Variables_pb2',
'WiFiSettings_pb2',
]
31 changes: 22 additions & 9 deletions brewblox_devcon_spark/codec/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def _walk_elements(self,
This makes it safe for calling code to modify or delete the value relevant to them.
Any entries added to the parent object after an element is yielded will not be considered.
"""
for key in set(obj.keys()):
for key, value in list(obj.items()):
base_key, postfix = self._postfix_pattern.findall(key)[0]
field: FieldDescriptor = desc.fields_by_name[base_key]
address: tuple[int | None] = (*parent_address, field.number)
Expand All @@ -176,16 +176,29 @@ def _walk_elements(self,
# Stop recursion
# obj is { key: None }
# Because we stop here, this field is a leaf node
elif obj[key] is None:
elif value is None:
yield OptionElement(field, obj, key, base_key, postfix, address)

# Repeated field
# traverse all members
# obj is { key: [{...},{...}] }
# Because we can't patch list items, the repeated field itself is a leaf node
# Repeated fields are generic collections, expressed in json as list or dict
# Because the list/map index is not a tag, we can't patch inside the repeated field
# The repeated field itself is a leaf node
elif field.label == FieldDescriptor.LABEL_REPEATED:
for childobj in obj[key]:
yield from self._walk_elements(field.message_type, childobj, (*address, None))

# map<K, V> field
# traverse all values
# The content is serialized as repeated `{ key: K, value: V }` entries
# obj is { key: {...} }
if isinstance(value, dict):
message_type = field.message_type.fields_by_name['value'].message_type
for childobj in value.values():
yield from self._walk_elements(message_type, childobj, (*address, None))

# Generic repeated field
# traverse all values
# obj is { key: [{...},{...}] }
else:
for childobj in value:
yield from self._walk_elements(field.message_type, childobj, (*address, None))

yield OptionElement(field, obj, key, base_key, postfix, address)

Expand All @@ -194,7 +207,7 @@ def _walk_elements(self,
# obj is { key: {...} }
# The field itself is not a leaf node
else:
yield from self._walk_elements(field.message_type, obj[key], address)
yield from self._walk_elements(field.message_type, value, address)
# This is not a leaf node. Its address should not be included in the mask
yield OptionElement(field, obj, key, base_key, postfix, (*address, None))

Expand Down
48 changes: 48 additions & 0 deletions brewblox_devcon_spark/codec/proto-compiled/Variables_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions brewblox_devcon_spark/codec/proto-compiled/brewblox_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions firmware.ini
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[FIRMWARE]
firmware_version=db387ac1
firmware_date=2024-02-23
firmware_sha=db387ac1b6bc9ce2907202d8b113e9e1a1929795
proto_version=06247728
proto_date=2024-02-02
proto_sha=06247728d07ae71ac3aceb7480101b82e85c1e3f
firmware_version=caeba1c0
firmware_date=2024-02-26
firmware_sha=caeba1c0060885fe13f1566d8c9b7beae23bf0e7
proto_version=c97eca6c
proto_date=2024-02-26
proto_sha=c97eca6c8c3f70e89a34b4af74c7e4add8b583ed
system_version=3.2.0
36 changes: 36 additions & 0 deletions test/test_codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,39 @@ async def test_invalid_if_decoding():
assert payload.content['listValues'][0]['value'] == pytest.approx(10)
assert payload.content['deltaV']['value'] is None
assert 'logged' not in payload.content


async def test_map_fields():
cdc = codec.CV.get()

encoded_payload = cdc.encode_payload(DecodedPayload(
blockId=1,
blockType='Variables',
content={
'variables': {
'k1': {'digital': 'STATE_ACTIVE'},
'k2': {'temp[degC]': 20},
'k3': {'duration[s]': 10},
},
},
))
payload = cdc.decode_payload(encoded_payload)
assert payload.content == {
'variables': {
'k1': {'digital': 'STATE_ACTIVE'},
'k2': {
'temp': {
'__bloxtype': 'Quantity',
'unit': 'degC',
'value': pytest.approx(20),
},
},
'k3': {
'duration': {
'__bloxtype': 'Quantity',
'unit': 'second',
'value': 10
},
},
}
}