class ReadAttributeRecord(t.Struct): """Read Attribute Record.""" attrid: t.uint16_t = t.StructField(repr=_hex_uint16_repr) status: Status value: TypeValue = t.StructField( requires=lambda s: s.status == Status.SUCCESS)
class OTAImageHeader(t.Struct): MAGIC_VALUE = 0x0BEEF11E OTA_HEADER = MAGIC_VALUE.to_bytes(4, "little") upgrade_file_id: t.uint32_t header_version: t.uint16_t header_length: t.uint16_t field_control: FieldControl manufacturer_id: t.uint16_t image_type: t.uint16_t file_version: t.uint32_t stack_version: t.uint16_t header_string: HeaderString image_size: t.uint32_t security_credential_version: t.uint8_t = t.StructField( requires=lambda s: s.security_credential_version_present) upgrade_file_destination: t.EUI64 = t.StructField( requires=lambda s: s.device_specific_file) minimum_hardware_version: HWVersion = t.StructField( requires=lambda s: s.hardware_versions_present) maximum_hardware_version: HWVersion = t.StructField( requires=lambda s: s.hardware_versions_present) @property def security_credential_version_present(self) -> bool: if self.field_control is None: return None return bool(self.field_control & FieldControl.SECURITY_CREDENTIAL_VERSION_PRESENT) @property def device_specific_file(self) -> bool: if self.field_control is None: return None return bool(self.field_control & FieldControl.DEVICE_SPECIFIC_FILE_PRESENT) @property def hardware_versions_present(self) -> bool: if self.field_control is None: return None return bool(self.field_control & FieldControl.HARDWARE_VERSIONS_PRESENT) @property def key(self): return ImageKey(self.manufacturer_id, self.image_type) @classmethod def deserialize(cls, data) -> tuple: hdr, data = super().deserialize(data) if hdr.upgrade_file_id != cls.MAGIC_VALUE: raise ValueError( f"Wrong magic number for OTA Image: {hdr.upgrade_file_id!r}") return hdr, data
class TestStruct(t.Struct): foo: t.uint8_t status1: StrictStatus value1: t.uint8_t = t.StructField( requires=lambda s: s.status1 == StrictStatus.SUCCESS) status2: StrictStatus value2: t.uint8_t = t.StructField( requires=lambda s: s.status2 == StrictStatus.SUCCESS)
class TestStruct(t.Struct): foo: t.uint8_t # The first pair of (status, value) is not optional status1: StrictStatus value1: t.uint8_t = t.StructField( requires=lambda s: s.status1 == StrictStatus.SUCCESS) # The second one is status2: typing.Optional[StrictStatus] value2: typing.Optional[t.uint8_t] = t.StructField( requires=lambda s: s.status2 == StrictStatus.SUCCESS)
class NwkUpdate(t.Struct): CHANNEL_CHANGE_REQ = 0xFE CHANNEL_MASK_MANAGER_ADDR_CHANGE_REQ = 0xFF ScanChannels: t.Channels ScanDuration: t.uint8_t ScanCount: t.uint8_t = t.StructField(requires=lambda s: s.ScanDuration <= 0x05) nwkUpdateId: t.uint8_t = t.StructField( requires=lambda s: s.ScanDuration in (s.CHANNEL_CHANGE_REQ, s.CHANNEL_MASK_MANAGER_ADDR_CHANGE_REQ) ) nwkManagerAddr: t.NWK = t.StructField( requires=lambda s: s.ScanDuration == s.CHANNEL_MASK_MANAGER_ADDR_CHANGE_REQ )
class TestStruct(t.Struct): foo: t.uint8_t status: Status bar: t.uint8_t = t.StructField( requires=lambda s: s.status == Status.SUCCESS) baz1: t.uint8_t baz2: typing.Optional[t.uint8_t]
class TestStruct1(t.Struct): field1: t.uint8_t # Python does not provide any simple way to get the order of both defined # class attributes and annotations. This is bad. field2 = t.StructField(type=t.uint16_t) field3: t.uint32_t
class ConfigureReportingResponseRecord(t.Struct): status: Status direction: ReportingDirection attrid: t.uint16_t = t.StructField(repr=_hex_uint16_repr) @classmethod def deserialize(cls, data): r = cls() r.status, data = Status.deserialize(data) if r.status == Status.SUCCESS: r.direction, data = t.Optional(t.uint8_t).deserialize(data) if r.direction is not None: r.direction = ReportingDirection(r.direction) r.attrid, data = t.Optional(t.uint16_t).deserialize(data) return r, data r.direction, data = ReportingDirection.deserialize(data) r.attrid, data = t.uint16_t.deserialize(data) return r, data def serialize(self): r = Status(self.status).serialize() if self.status != Status.SUCCESS: r += ReportingDirection(self.direction).serialize() r += t.uint16_t(self.attrid).serialize() return r def __repr__(self): r = f"{self.__class__.__name__}(status={self.status}" if self.status != Status.SUCCESS: r += f", direction={self.direction}, attrid={self.attrid}" r += ")" return r
class OuterStruct(t.Struct): class InnerStruct(t.Struct): b: t.uint8_t c: t.uint8_t a: t.uint8_t inner: None = t.StructField(type=InnerStruct) d: t.uint8_t
class TestStruct(t.Struct): foo: t.uint8_t # the first parameter is really an instance of TestStruct bar: t.uint8_t = t.StructField(requires=lambda s: s.test) @property def test(self): assert isinstance(self, TestStruct) return self.foo == 0x01
class MultiAddress(t.Struct): """Used for binds, represents an IEEE+endpoint or NWK address""" addrmode: t.uint8_t nwk: t.uint16_t = t.StructField(requires=lambda s: s.addrmode == 0x01) ieee: t.EUI64 = t.StructField(requires=lambda s: s.addrmode == 0x03) endpoint: t.uint8_t = t.StructField(requires=lambda s: s.addrmode == 0x03) @classmethod def deserialize(cls, data): r, data = super().deserialize(data) if r.addrmode not in (0x01, 0x03): raise ValueError("Invalid MultiAddress - unknown address mode") return r, data def serialize(self): if self.addrmode not in (0x01, 0x03): raise ValueError("Invalid MultiAddress - unknown address mode") return super().serialize()
def test_non_annotated_field(): with pytest.raises(TypeError): class TestStruct1(t.Struct): field1: t.uint8_t # Python does not provide any simple way to get the order of both defined # class attributes and annotations. This is bad. field2 = t.StructField(type=t.uint16_t) field3: t.uint32_t class TestStruct2(t.Struct): field1: t.uint8_t field2: None = t.StructField(type=t.uint16_t) field3: t.uint32_t assert len(TestStruct2.fields) == 3 assert TestStruct2.fields[0] == t.StructField(name="field1", type=t.uint8_t) assert TestStruct2.fields[1] == t.StructField(name="field2", type=t.uint16_t) assert TestStruct2.fields[2] == t.StructField(name="field3", type=t.uint32_t)
def with_compiled_schema(self): """ Return a copy of the ZCL command definition object with its dictionary command schema converted into a `CommandSchema` subclass. """ # If the schema is already a struct, do nothing if not isinstance(self.schema, dict): return self assert self.id is not None assert self.name is not None cls_attrs = { "__annotations__": {}, "command": self, } for name, param_type in self.schema.items(): plain_name = name.rstrip("?") # Make sure parameters with names like "foo bar" and "class" can't exist if not plain_name.isidentifier() or keyword.iskeyword(plain_name): raise ValueError( f"Schema parameter {name} must be a valid Python identifier" ) cls_attrs["__annotations__"][plain_name] = "None" cls_attrs[plain_name] = t.StructField( type=param_type, optional=name.endswith("?"), ) schema = type(self.name, (CommandSchema, ), cls_attrs) return self.replace(schema=schema)
class WriteAttributesStatusRecord(t.Struct): status: Status attrid: t.uint16_t = t.StructField( requires=lambda s: s.status != Status.SUCCESS)
class BadStruct(t.Struct): foo: t.uint8_t = t.StructField(type=t.uint16_t)
class GoodStruct(t.Struct): foo: t.uint8_t = t.StructField(type=t.uint8_t)
class TestStructSubclass(TestStruct): bar: t.uint8_t = t.StructField(requires=lambda s: s.foo == 0x01)
class TestStruct(t.Struct): foo: t.uint8_t = t.StructField(repr=lambda v: v + 1) bar: t.uint16_t = t.StructField(repr=lambda v: "bar") baz: t.CharacterString = t.StructField(repr=lambda v: "baz")
class TestBadStruct(t.Struct): field: None = t.StructField()
class TestStruct2(t.Struct): field1: t.uint8_t field2: None = t.StructField(type=t.uint16_t) field3: t.uint32_t
class ZCLHeader(t.Struct): NO_MANUFACTURER_ID = -1 # type: typing.Literal frame_control: FrameControl manufacturer: t.uint16_t = t.StructField( requires=lambda hdr: hdr.frame_control.is_manufacturer_specific) tsn: t.uint8_t command_id: t.uint8_t def __new__(cls, frame_control=None, manufacturer=None, tsn=None, command_id=None) -> ZCLHeader: # Allow "auto manufacturer ID" to be disabled in higher layers if manufacturer is cls.NO_MANUFACTURER_ID: manufacturer = None if frame_control is not None and manufacturer is not None: frame_control.is_manufacturer_specific = True return super().__new__(cls, frame_control, manufacturer, tsn, command_id) @property def is_reply(self) -> bool: """Return direction of Frame Control.""" return self.frame_control.is_reply == 1 def __setattr__(self, name, value) -> None: if name == "manufacturer" and value is self.NO_MANUFACTURER_ID: value = None super().__setattr__(name, value) if name == "manufacturer" and self.frame_control is not None: self.frame_control.is_manufacturer_specific = value is not None @classmethod def general( cls, tsn: int | t.uint8_t, command_id: int | t.uint8_t, manufacturer: int | t.uint16_t | None = None, is_reply: bool = False, ) -> ZCLHeader: return cls( frame_control=FrameControl.general( is_reply=is_reply, is_manufacturer_specific=(manufacturer is not None)), manufacturer=manufacturer, tsn=tsn, command_id=command_id, ) @classmethod def cluster( cls, tsn: int | t.uint8_t, command_id: int | t.uint8_t, manufacturer: int | t.uint16_t | None = None, is_reply: bool = False, ) -> ZCLHeader: return cls( frame_control=FrameControl.cluster( is_reply=is_reply, is_manufacturer_specific=(manufacturer is not None)), manufacturer=manufacturer, tsn=tsn, command_id=command_id, )
class Attribute(t.Struct): attrid: t.uint16_t = t.StructField(repr=_hex_uint16_repr) value: TypeValue
class TestStruct(t.Struct): foo: t.uint8_t bar: t.uint8_t = t.StructField(requires=lambda s: s.foo == 0x01)
class OuterStruct(t.Struct): InnerStruct = InnerStruct a: t.uint8_t inner: None = t.StructField(type=InnerStruct) d: t.uint8_t
class TestStruct6(t.Struct): a: t.uint8_t = t.StructField()
class TestStruct(t.Struct): foo: t.uint8_t status: Status bar: t.uint8_t = t.StructField( requires=lambda s: s.status == Status.SUCCESS) baz: t.uint8_t
class TestStruct(t.Struct): status: t.uint8_t value: t.uint8_t = t.StructField(requires=lambda s: s.status == 0x00)
class TestStruct(t.Struct): foo: t.uint8_t bar: t.uint16_t baz: t.uint8_t = t.StructField(requires=lambda s: s.bar == 2, optional=True)