Source code for bac_py.services.list_element
"""List element services per ASHRAE 135-2016 Clause 15.1-15.2.
AddListElement (Clause 15.1), RemoveListElement (Clause 15.2).
"""
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_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
@dataclass(frozen=True, slots=True)
class _ListElementRequest:
"""Base class for Add/RemoveListElement requests.
Both services share the same ASN.1 structure::
SEQUENCE {
objectIdentifier [0] BACnetObjectIdentifier,
propertyIdentifier [1] BACnetPropertyIdentifier,
propertyArrayIndex [2] Unsigned OPTIONAL,
listOfElements [3] ABSTRACT-SYNTAX.&TYPE
}
The listOfElements field contains raw application-tagged bytes.
"""
object_identifier: ObjectIdentifier
property_identifier: PropertyIdentifier
list_of_elements: bytes
property_array_index: int | None = None
def encode(self) -> bytes:
"""Encode Add/RemoveListElement-Request service parameters.
:returns: Encoded service request bytes.
"""
buf = bytearray()
# [0] objectIdentifier
buf.extend(encode_context_object_id(0, self.object_identifier))
# [1] propertyIdentifier
buf.extend(encode_context_tagged(1, encode_unsigned(self.property_identifier)))
# [2] propertyArrayIndex (optional)
if self.property_array_index is not None:
buf.extend(encode_context_tagged(2, encode_unsigned(self.property_array_index)))
# [3] listOfElements (opening/closing)
buf.extend(encode_opening_tag(3))
buf.extend(self.list_of_elements)
buf.extend(encode_closing_tag(3))
return bytes(buf)
@classmethod
def decode(cls, data: memoryview | bytes) -> _ListElementRequest:
"""Decode Add/RemoveListElement-Request from service request bytes.
:param data: Raw service request bytes.
:returns: Decoded :class:`_ListElementRequest` (or subclass).
"""
data = as_memoryview(data)
offset = 0
# [0] objectIdentifier
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] propertyIdentifier
tag, offset = decode_tag(data, offset)
property_identifier = PropertyIdentifier(
decode_unsigned(data[offset : offset + tag.length])
)
offset += tag.length
# [2] propertyArrayIndex (optional)
property_array_index = None
tag, new_offset = decode_tag(data, offset)
if (
tag.cls == TagClass.CONTEXT
and tag.number == 2
and not tag.is_opening
and not tag.is_closing
):
property_array_index = decode_unsigned(data[new_offset : new_offset + tag.length])
offset = new_offset + tag.length
tag, new_offset = decode_tag(data, offset)
# [3] listOfElements (opening/closing tag 3)
list_of_elements, offset = extract_context_value(data, new_offset, 3)
return cls(
object_identifier=object_identifier,
property_identifier=property_identifier,
list_of_elements=list_of_elements,
property_array_index=property_array_index,
)
[docs]
@dataclass(frozen=True, slots=True)
class AddListElementRequest(_ListElementRequest):
"""AddListElement-Request (Clause 15.1.1.1)."""
[docs]
@dataclass(frozen=True, slots=True)
class RemoveListElementRequest(_ListElementRequest):
"""RemoveListElement-Request (Clause 15.2.1.1)."""