"""ReadProperty service per ASHRAE 135-2016 Clause 15.5."""
from __future__ import annotations
from dataclasses import dataclass
from bac_py.encoding.primitives import (
decode_object_identifier,
decode_unsigned,
encode_context_object_id,
encode_context_tagged,
encode_unsigned,
)
from bac_py.encoding.tags import (
TagClass,
as_memoryview,
decode_optional_context,
decode_tag,
encode_closing_tag,
encode_opening_tag,
extract_context_value,
)
from bac_py.types.enums import ObjectType, PropertyIdentifier
from bac_py.types.primitives import ObjectIdentifier
[docs]
@dataclass(frozen=True, slots=True)
class ReadPropertyRequest:
"""ReadProperty-Request service parameters (Clause 15.5.1.1).
::
ReadProperty-Request ::= SEQUENCE {
objectIdentifier [0] BACnetObjectIdentifier,
propertyIdentifier [1] BACnetPropertyIdentifier,
propertyArrayIndex [2] Unsigned OPTIONAL
}
"""
object_identifier: ObjectIdentifier
property_identifier: PropertyIdentifier
property_array_index: int | None = None
[docs]
def encode(self) -> bytes:
"""Encode ReadProperty-Request service parameters.
:returns: Encoded service request bytes.
"""
buf = bytearray()
# [0] object-identifier
buf.extend(encode_context_object_id(0, self.object_identifier))
# [1] property-identifier
buf.extend(encode_context_tagged(1, encode_unsigned(self.property_identifier)))
# [2] property-array-index (optional)
if self.property_array_index is not None:
buf.extend(encode_context_tagged(2, encode_unsigned(self.property_array_index)))
return bytes(buf)
[docs]
@classmethod
def decode(cls, data: memoryview | bytes) -> ReadPropertyRequest:
"""Decode ReadProperty-Request from service request bytes.
:param data: Raw service request bytes.
:returns: Decoded :class:`ReadPropertyRequest`.
"""
data = as_memoryview(data)
offset = 0
# [0] object-identifier
tag, offset = decode_tag(data, offset)
obj_type, instance = decode_object_identifier(data[offset : offset + tag.length])
offset += tag.length
object_identifier = ObjectIdentifier(ObjectType(obj_type), instance)
# [1] property-identifier
tag, offset = decode_tag(data, offset)
property_identifier = PropertyIdentifier(
decode_unsigned(data[offset : offset + tag.length])
)
offset += tag.length
# [2] property-array-index (optional)
property_array_index, _ = decode_optional_context(data, offset, 2, decode_unsigned)
return cls(
object_identifier=object_identifier,
property_identifier=property_identifier,
property_array_index=property_array_index,
)
[docs]
@dataclass(frozen=True, slots=True)
class ReadPropertyACK:
"""ReadProperty-ACK service parameters (Clause 15.5.1.2).
::
ReadProperty-ACK ::= SEQUENCE {
objectIdentifier [0] BACnetObjectIdentifier,
propertyIdentifier [1] BACnetPropertyIdentifier,
propertyArrayIndex [2] Unsigned OPTIONAL,
propertyValue [3] ABSTRACT-SYNTAX.&TYPE
}
The property_value field contains raw encoded bytes wrapped
in context tag 3 (opening/closing). The application layer is
responsible for interpreting the value based on the property type.
"""
object_identifier: ObjectIdentifier
property_identifier: PropertyIdentifier
property_array_index: int | None = None
property_value: bytes = b""
[docs]
def encode(self) -> bytes:
"""Encode ReadProperty-ACK service parameters.
:returns: Encoded service ACK bytes.
"""
buf = bytearray()
# [0] object-identifier
buf.extend(encode_context_object_id(0, self.object_identifier))
# [1] property-identifier
buf.extend(encode_context_tagged(1, encode_unsigned(self.property_identifier)))
# [2] property-array-index (optional)
if self.property_array_index is not None:
buf.extend(encode_context_tagged(2, encode_unsigned(self.property_array_index)))
# [3] property-value (opening tag 3, data, closing tag 3)
buf.extend(encode_opening_tag(3))
buf.extend(self.property_value)
buf.extend(encode_closing_tag(3))
return bytes(buf)
[docs]
@classmethod
def decode(cls, data: memoryview | bytes) -> ReadPropertyACK:
"""Decode ReadProperty-ACK from service ACK bytes.
:param data: Raw service ACK bytes.
:returns: Decoded :class:`ReadPropertyACK`.
"""
data = as_memoryview(data)
offset = 0
# [0] object-identifier
tag, offset = decode_tag(data, offset)
obj_type, instance = decode_object_identifier(data[offset : offset + tag.length])
offset += tag.length
object_identifier = ObjectIdentifier(ObjectType(obj_type), instance)
# [1] property-identifier
tag, offset = decode_tag(data, offset)
property_identifier = PropertyIdentifier(
decode_unsigned(data[offset : offset + tag.length])
)
offset += tag.length
# [2] property-array-index (optional) or [3] opening tag
property_array_index = None
tag, offset = decode_tag(data, offset)
if tag.cls == TagClass.CONTEXT and tag.number == 2 and not tag.is_opening:
property_array_index = decode_unsigned(data[offset : offset + tag.length])
offset += tag.length
# Now read opening tag 3
tag, offset = decode_tag(data, offset)
# At this point tag should be opening tag 3
property_value, offset = extract_context_value(data, offset, 3)
return cls(
object_identifier=object_identifier,
property_identifier=property_identifier,
property_array_index=property_array_index,
property_value=property_value,
)