diff --git a/brewblox-proto b/brewblox-proto index 06247728..c97eca6c 160000 --- a/brewblox-proto +++ b/brewblox-proto @@ -1 +1 @@ -Subproject commit 06247728d07ae71ac3aceb7480101b82e85c1e3f +Subproject commit c97eca6c8c3f70e89a34b4af74c7e4add8b583ed diff --git a/brewblox_devcon_spark/codec/pb2.py b/brewblox_devcon_spark/codec/pb2.py index dae08a6f..fbc58c33 100644 --- a/brewblox_devcon_spark/codec/pb2.py +++ b/brewblox_devcon_spark/codec/pb2.py @@ -43,6 +43,7 @@ import TempSensorMock_pb2 import TempSensorOneWire_pb2 import TouchSettings_pb2 + import Variables_pb2 import WiFiSettings_pb2 __all__ = [ @@ -78,5 +79,6 @@ 'TempSensorMock_pb2', 'TempSensorOneWire_pb2', 'TouchSettings_pb2', + 'Variables_pb2', 'WiFiSettings_pb2', ] diff --git a/brewblox_devcon_spark/codec/processor.py b/brewblox_devcon_spark/codec/processor.py index 45927a51..02644601 100644 --- a/brewblox_devcon_spark/codec/processor.py +++ b/brewblox_devcon_spark/codec/processor.py @@ -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) @@ -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 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) @@ -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)) diff --git a/brewblox_devcon_spark/codec/proto-compiled/Variables_pb2.py b/brewblox_devcon_spark/codec/proto-compiled/Variables_pb2.py new file mode 100644 index 00000000..6694caf9 --- /dev/null +++ b/brewblox_devcon_spark/codec/proto-compiled/Variables_pb2.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: Variables.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +import brewblox_pb2 as brewblox__pb2 +import nanopb_pb2 as nanopb__pb2 +import IoArray_pb2 as IoArray__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fVariables.proto\x12\x0e\x62lox.Variables\x1a\x0e\x62rewblox.proto\x1a\x0cnanopb.proto\x1a\rIoArray.proto\"\x9b\x02\n\x0cVarContainer\x12\x0f\n\x05\x65mpty\x18\x01 \x01(\x08H\x00\x12-\n\x07\x64igital\x18\n \x01(\x0e\x32\x1a.blox.IoArray.DigitalStateH\x00\x12\x1e\n\x06\x61nalog\x18\x0b \x01(\x11\x42\x0c\x92?\x02\x38 \x8a\xb5\x18\x03\x10\x80 H\x00\x12\x1e\n\x04temp\x18\x14 \x01(\x11\x42\x0e\x92?\x02\x38 \x8a\xb5\x18\x05\x08\x01\x10\x80 H\x00\x12#\n\tdeltaTemp\x18\x15 \x01(\x11\x42\x0e\x92?\x02\x38 \x8a\xb5\x18\x05\x08\x06\x10\x80 H\x00\x12 \n\ttimestamp\x18\x1e \x01(\rB\x0b\x92?\x02\x38 \x8a\xb5\x18\x02X\x01H\x00\x12\x1f\n\x08\x64uration\x18\x1f \x01(\rB\x0b\x92?\x02\x38 \x8a\xb5\x18\x02\x08\x03H\x00\x12\x1c\n\x04link\x18( \x01(\rB\x0c\x92?\x02\x38\x10\x8a\xb5\x18\x03\x18\xff\x01H\x00\x42\x05\n\x03var\"\x99\x01\n\x05\x42lock\x12\x37\n\tvariables\x18\x01 \x03(\x0b\x32$.blox.Variables.Block.VariablesEntry\x1aN\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05value\x18\x02 \x01(\x0b\x32\x1c.blox.Variables.VarContainer:\x02\x38\x01:\x07\x8a\xb5\x18\x03\x18\xcb\x02\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'Variables_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _VARCONTAINER.fields_by_name['analog']._options = None + _VARCONTAINER.fields_by_name['analog']._serialized_options = b'\222?\0028 \212\265\030\003\020\200 ' + _VARCONTAINER.fields_by_name['temp']._options = None + _VARCONTAINER.fields_by_name['temp']._serialized_options = b'\222?\0028 \212\265\030\005\010\001\020\200 ' + _VARCONTAINER.fields_by_name['deltaTemp']._options = None + _VARCONTAINER.fields_by_name['deltaTemp']._serialized_options = b'\222?\0028 \212\265\030\005\010\006\020\200 ' + _VARCONTAINER.fields_by_name['timestamp']._options = None + _VARCONTAINER.fields_by_name['timestamp']._serialized_options = b'\222?\0028 \212\265\030\002X\001' + _VARCONTAINER.fields_by_name['duration']._options = None + _VARCONTAINER.fields_by_name['duration']._serialized_options = b'\222?\0028 \212\265\030\002\010\003' + _VARCONTAINER.fields_by_name['link']._options = None + _VARCONTAINER.fields_by_name['link']._serialized_options = b'\222?\0028\020\212\265\030\003\030\377\001' + _BLOCK_VARIABLESENTRY._options = None + _BLOCK_VARIABLESENTRY._serialized_options = b'8\001' + _BLOCK._options = None + _BLOCK._serialized_options = b'\212\265\030\003\030\313\002' + _globals['_VARCONTAINER']._serialized_start=81 + _globals['_VARCONTAINER']._serialized_end=364 + _globals['_BLOCK']._serialized_start=367 + _globals['_BLOCK']._serialized_end=520 + _globals['_BLOCK_VARIABLESENTRY']._serialized_start=433 + _globals['_BLOCK_VARIABLESENTRY']._serialized_end=511 +# @@protoc_insertion_point(module_scope) diff --git a/brewblox_devcon_spark/codec/proto-compiled/brewblox_pb2.py b/brewblox_devcon_spark/codec/proto-compiled/brewblox_pb2.py index 1521c4c6..1497291f 100644 --- a/brewblox_devcon_spark/codec/proto-compiled/brewblox_pb2.py +++ b/brewblox_devcon_spark/codec/proto-compiled/brewblox_pb2.py @@ -15,7 +15,7 @@ import nanopb_pb2 as nanopb__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x62rewblox.proto\x12\x08\x62rewblox\x1a google/protobuf/descriptor.proto\x1a\x0cnanopb.proto\"|\n\x0bMessageOpts\x12$\n\x07objtype\x18\x03 \x01(\x0e\x32\x13.brewblox.BlockType\x12(\n\x04impl\x18\t \x03(\x0e\x32\x13.brewblox.BlockTypeB\x05\x92?\x02\x10\x05\x12\x16\n\x07subtype\x18\x0b \x01(\rB\x05\x92?\x02\x38\x10:\x05\x92?\x02\x30\x01\"\xa0\x02\n\tFieldOpts\x12 \n\x04unit\x18\x01 \x01(\x0e\x32\x12.brewblox.UnitType\x12\r\n\x05scale\x18\x02 \x01(\r\x12$\n\x07objtype\x18\x03 \x01(\x0e\x32\x13.brewblox.BlockType\x12\r\n\x05hexed\x18\x04 \x01(\x08\x12\x10\n\x08readonly\x18\x05 \x01(\x08\x12\x0e\n\x06logged\x18\x06 \x01(\x08\x12\x0e\n\x06hexstr\x18\x07 \x01(\x08\x12\x0f\n\x07ignored\x18\t \x01(\x08\x12\x10\n\x08\x62itfield\x18\n \x01(\x08\x12\x10\n\x08\x64\x61tetime\x18\x0b \x01(\x08\x12\x13\n\x0bipv4address\x18\x0c \x01(\x08\x12\x14\n\x0comit_if_zero\x18\r \x01(\x08\x12\x14\n\x0cnull_if_zero\x18\x0e \x01(\x08:\x05\x92?\x02\x30\x01*\x8c\x02\n\x08UnitType\x12\n\n\x06NotSet\x10\x00\x12\x0b\n\x07\x43\x65lsius\x10\x01\x12\x12\n\x0eInverseCelsius\x10\x02\x12\n\n\x06Second\x10\x03\x12\n\n\x06Minute\x10\x04\x12\x08\n\x04Hour\x10\x05\x12\x10\n\x0c\x44\x65ltaCelsius\x10\x06\x12\x19\n\x15\x44\x65ltaCelsiusPerSecond\x10\x07\x12\x19\n\x15\x44\x65ltaCelsiusPerMinute\x10\x08\x12\x17\n\x13\x44\x65ltaCelsiusPerHour\x10\t\x12\x1a\n\x16\x44\x65ltaCelsiusMultSecond\x10\n\x12\x1a\n\x16\x44\x65ltaCelsiusMultMinute\x10\x0b\x12\x18\n\x14\x44\x65ltaCelsiusMultHour\x10\x0c*\xb8\n\n\tBlockType\x12\x0b\n\x07Invalid\x10\x00\x12\x19\n\x15ProcessValueInterface\x10\x01\x12\x17\n\x13TempSensorInterface\x10\x02\x12\x1f\n\x1bSetpointSensorPairInterface\x10\x04\x12\x1b\n\x17\x41\x63tuatorAnalogInterface\x10\x05\x12\x1c\n\x18\x41\x63tuatorDigitalInterface\x10\x06\x12\x15\n\x11\x42\x61lancerInterface\x10\x07\x12\x12\n\x0eMutexInterface\x10\x08\x12\x1a\n\x16OneWireDeviceInterface\x10\t\x12\x14\n\x10IoArrayInterface\x10\n\x12\x13\n\x0f\x44S2408Interface\x10\x0b\x12\x17\n\x13OneWireBusInterface\x10\x0c\x12\x15\n\x11IoModuleInterface\x10\r\x12\x1f\n\x1bOneWireDeviceBlockInterface\x10\x0e\x12\x14\n\x10\x45nablerInterface\x10\x0f\x12\x16\n\x12\x43laimableInterface\x10\x10\x12\x15\n\x11IoDriverInterface\x10\x11\x12\x15\n\x11SetpointInterface\x10\x12\x12\x19\n\x15StoredAnalogInterface\x10\x13\x12\x1b\n\x17StoredSetpointInterface\x10\x14\x12\x1a\n\x16StoredDigitalInterface\x10\x15\x12\x1e\n\x1a\x43onstrainedAnalogInterface\x10\x16\x12 \n\x1c\x43onstrainedSetpointInterface\x10\x17\x12\x1f\n\x1b\x43onstrainedDigitalInterface\x10\x18\x12\x1c\n\x18ScanningFactoryInterface\x10\x19\x12\x1c\n\x18I2CDiscoverableInterface\x10\x1a\x12\x14\n\x10\x44igitalInterface\x10\x1b\x12\x08\n\x03\x41ny\x10\xff\x01\x12\x0c\n\x07SysInfo\x10\x80\x02\x12\n\n\x05Ticks\x10\x81\x02\x12\x0f\n\nOneWireBus\x10\x82\x02\x12\x0e\n\tBoardPins\x10\x83\x02\x12\x13\n\x0eTempSensorMock\x10\xad\x02\x12\x16\n\x11TempSensorOneWire\x10\xae\x02\x12\x17\n\x12SetpointSensorPair\x10\xaf\x02\x12\x08\n\x03Pid\x10\xb0\x02\x12\x17\n\x12\x41\x63tuatorAnalogMock\x10\xb1\x02\x12\x10\n\x0b\x41\x63tuatorPin\x10\xb2\x02\x12\x10\n\x0b\x41\x63tuatorPwm\x10\xb3\x02\x12\x13\n\x0e\x41\x63tuatorOffset\x10\xb4\x02\x12\r\n\x08\x42\x61lancer\x10\xb5\x02\x12\n\n\x05Mutex\x10\xb6\x02\x12\x14\n\x0fSetpointProfile\x10\xb7\x02\x12\x11\n\x0cWiFiSettings\x10\xb8\x02\x12\x12\n\rTouchSettings\x10\xb9\x02\x12\x14\n\x0f\x44isplaySettings\x10\xba\x02\x12\x0b\n\x06\x44S2413\x10\xbb\x02\x12\x14\n\x0f\x41\x63tuatorOneWire\x10\xbc\x02\x12\x0b\n\x06\x44S2408\x10\xbd\x02\x12\x14\n\x0f\x44igitalActuator\x10\xbe\x02\x12\x0f\n\nSpark3Pins\x10\xbf\x02\x12\x0f\n\nSpark2Pins\x10\xc0\x02\x12\x0f\n\nMotorValve\x10\xc1\x02\x12\x12\n\rActuatorLogic\x10\xc2\x02\x12\r\n\x08MockPins\x10\xc3\x02\x12\x14\n\x0fTempSensorCombi\x10\xc4\x02\x12\x16\n\x11OneWireGpioModule\x10\xc5\x02\x12\r\n\x08Sequence\x10\xc6\x02\x12\x17\n\x12TempSensorExternal\x10\xc8\x02\x12\x0c\n\x07\x46\x61stPwm\x10\xc9\x02\x12\x11\n\x0c\x44igitalInput\x10\xca\x02:J\n\x05\x66ield\x12\x1d.google.protobuf.FieldOptions\x18\xd1\x86\x03 \x01(\x0b\x32\x13.brewblox.FieldOptsB\x05\x92?\x02\x18\x03:L\n\x03msg\x12\x1f.google.protobuf.MessageOptions\x18\xd1\x86\x03 \x01(\x0b\x32\x15.brewblox.MessageOptsB\x05\x92?\x02\x18\x03\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x62rewblox.proto\x12\x08\x62rewblox\x1a google/protobuf/descriptor.proto\x1a\x0cnanopb.proto\"|\n\x0bMessageOpts\x12$\n\x07objtype\x18\x03 \x01(\x0e\x32\x13.brewblox.BlockType\x12(\n\x04impl\x18\t \x03(\x0e\x32\x13.brewblox.BlockTypeB\x05\x92?\x02\x10\x05\x12\x16\n\x07subtype\x18\x0b \x01(\rB\x05\x92?\x02\x38\x10:\x05\x92?\x02\x30\x01\"\xa0\x02\n\tFieldOpts\x12 \n\x04unit\x18\x01 \x01(\x0e\x32\x12.brewblox.UnitType\x12\r\n\x05scale\x18\x02 \x01(\r\x12$\n\x07objtype\x18\x03 \x01(\x0e\x32\x13.brewblox.BlockType\x12\r\n\x05hexed\x18\x04 \x01(\x08\x12\x10\n\x08readonly\x18\x05 \x01(\x08\x12\x0e\n\x06logged\x18\x06 \x01(\x08\x12\x0e\n\x06hexstr\x18\x07 \x01(\x08\x12\x0f\n\x07ignored\x18\t \x01(\x08\x12\x10\n\x08\x62itfield\x18\n \x01(\x08\x12\x10\n\x08\x64\x61tetime\x18\x0b \x01(\x08\x12\x13\n\x0bipv4address\x18\x0c \x01(\x08\x12\x14\n\x0comit_if_zero\x18\r \x01(\x08\x12\x14\n\x0cnull_if_zero\x18\x0e \x01(\x08:\x05\x92?\x02\x30\x01*\x8c\x02\n\x08UnitType\x12\n\n\x06NotSet\x10\x00\x12\x0b\n\x07\x43\x65lsius\x10\x01\x12\x12\n\x0eInverseCelsius\x10\x02\x12\n\n\x06Second\x10\x03\x12\n\n\x06Minute\x10\x04\x12\x08\n\x04Hour\x10\x05\x12\x10\n\x0c\x44\x65ltaCelsius\x10\x06\x12\x19\n\x15\x44\x65ltaCelsiusPerSecond\x10\x07\x12\x19\n\x15\x44\x65ltaCelsiusPerMinute\x10\x08\x12\x17\n\x13\x44\x65ltaCelsiusPerHour\x10\t\x12\x1a\n\x16\x44\x65ltaCelsiusMultSecond\x10\n\x12\x1a\n\x16\x44\x65ltaCelsiusMultMinute\x10\x0b\x12\x18\n\x14\x44\x65ltaCelsiusMultHour\x10\x0c*\xc8\n\n\tBlockType\x12\x0b\n\x07Invalid\x10\x00\x12\x19\n\x15ProcessValueInterface\x10\x01\x12\x17\n\x13TempSensorInterface\x10\x02\x12\x1f\n\x1bSetpointSensorPairInterface\x10\x04\x12\x1b\n\x17\x41\x63tuatorAnalogInterface\x10\x05\x12\x1c\n\x18\x41\x63tuatorDigitalInterface\x10\x06\x12\x15\n\x11\x42\x61lancerInterface\x10\x07\x12\x12\n\x0eMutexInterface\x10\x08\x12\x1a\n\x16OneWireDeviceInterface\x10\t\x12\x14\n\x10IoArrayInterface\x10\n\x12\x13\n\x0f\x44S2408Interface\x10\x0b\x12\x17\n\x13OneWireBusInterface\x10\x0c\x12\x15\n\x11IoModuleInterface\x10\r\x12\x1f\n\x1bOneWireDeviceBlockInterface\x10\x0e\x12\x14\n\x10\x45nablerInterface\x10\x0f\x12\x16\n\x12\x43laimableInterface\x10\x10\x12\x15\n\x11IoDriverInterface\x10\x11\x12\x15\n\x11SetpointInterface\x10\x12\x12\x19\n\x15StoredAnalogInterface\x10\x13\x12\x1b\n\x17StoredSetpointInterface\x10\x14\x12\x1a\n\x16StoredDigitalInterface\x10\x15\x12\x1e\n\x1a\x43onstrainedAnalogInterface\x10\x16\x12 \n\x1c\x43onstrainedSetpointInterface\x10\x17\x12\x1f\n\x1b\x43onstrainedDigitalInterface\x10\x18\x12\x1c\n\x18ScanningFactoryInterface\x10\x19\x12\x1c\n\x18I2CDiscoverableInterface\x10\x1a\x12\x14\n\x10\x44igitalInterface\x10\x1b\x12\x08\n\x03\x41ny\x10\xff\x01\x12\x0c\n\x07SysInfo\x10\x80\x02\x12\n\n\x05Ticks\x10\x81\x02\x12\x0f\n\nOneWireBus\x10\x82\x02\x12\x0e\n\tBoardPins\x10\x83\x02\x12\x13\n\x0eTempSensorMock\x10\xad\x02\x12\x16\n\x11TempSensorOneWire\x10\xae\x02\x12\x17\n\x12SetpointSensorPair\x10\xaf\x02\x12\x08\n\x03Pid\x10\xb0\x02\x12\x17\n\x12\x41\x63tuatorAnalogMock\x10\xb1\x02\x12\x10\n\x0b\x41\x63tuatorPin\x10\xb2\x02\x12\x10\n\x0b\x41\x63tuatorPwm\x10\xb3\x02\x12\x13\n\x0e\x41\x63tuatorOffset\x10\xb4\x02\x12\r\n\x08\x42\x61lancer\x10\xb5\x02\x12\n\n\x05Mutex\x10\xb6\x02\x12\x14\n\x0fSetpointProfile\x10\xb7\x02\x12\x11\n\x0cWiFiSettings\x10\xb8\x02\x12\x12\n\rTouchSettings\x10\xb9\x02\x12\x14\n\x0f\x44isplaySettings\x10\xba\x02\x12\x0b\n\x06\x44S2413\x10\xbb\x02\x12\x14\n\x0f\x41\x63tuatorOneWire\x10\xbc\x02\x12\x0b\n\x06\x44S2408\x10\xbd\x02\x12\x14\n\x0f\x44igitalActuator\x10\xbe\x02\x12\x0f\n\nSpark3Pins\x10\xbf\x02\x12\x0f\n\nSpark2Pins\x10\xc0\x02\x12\x0f\n\nMotorValve\x10\xc1\x02\x12\x12\n\rActuatorLogic\x10\xc2\x02\x12\r\n\x08MockPins\x10\xc3\x02\x12\x14\n\x0fTempSensorCombi\x10\xc4\x02\x12\x16\n\x11OneWireGpioModule\x10\xc5\x02\x12\r\n\x08Sequence\x10\xc6\x02\x12\x17\n\x12TempSensorExternal\x10\xc8\x02\x12\x0c\n\x07\x46\x61stPwm\x10\xc9\x02\x12\x11\n\x0c\x44igitalInput\x10\xca\x02\x12\x0e\n\tVariables\x10\xcb\x02:J\n\x05\x66ield\x12\x1d.google.protobuf.FieldOptions\x18\xd1\x86\x03 \x01(\x0b\x32\x13.brewblox.FieldOptsB\x05\x92?\x02\x18\x03:L\n\x03msg\x12\x1f.google.protobuf.MessageOptions\x18\xd1\x86\x03 \x01(\x0b\x32\x15.brewblox.MessageOptsB\x05\x92?\x02\x18\x03\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -37,7 +37,7 @@ _globals['_UNITTYPE']._serialized_start=494 _globals['_UNITTYPE']._serialized_end=762 _globals['_BLOCKTYPE']._serialized_start=765 - _globals['_BLOCKTYPE']._serialized_end=2101 + _globals['_BLOCKTYPE']._serialized_end=2117 _globals['_MESSAGEOPTS']._serialized_start=76 _globals['_MESSAGEOPTS']._serialized_end=200 _globals['_FIELDOPTS']._serialized_start=203 diff --git a/firmware.ini b/firmware.ini index e60c4dc1..ee963d01 100644 --- a/firmware.ini +++ b/firmware.ini @@ -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 diff --git a/test/test_codec.py b/test/test_codec.py index 7b05f85c..74746740 100644 --- a/test/test_codec.py +++ b/test/test_codec.py @@ -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 + }, + }, + } + }