Source code for bac_py.services.device_discovery
"""Device discovery services per ASHRAE 135-2020 Clause 16.11.
Who-Am-I (new in 2020) and You-Are for device identity assignment.
"""
from __future__ import annotations
from dataclasses import dataclass
from bac_py.encoding.primitives import (
decode_character_string,
decode_object_identifier,
decode_unsigned,
encode_application_character_string,
encode_application_unsigned,
encode_context_object_id,
encode_context_octet_string,
encode_context_tagged,
encode_unsigned,
)
from bac_py.encoding.tags import TagClass, as_memoryview, decode_tag
from bac_py.types.enums import ObjectType
from bac_py.types.primitives import ObjectIdentifier
[docs]
@dataclass(frozen=True, slots=True)
class WhoAmIRequest:
"""Who-Am-I-Request (Clause 16.11, new in 2020).
Sent by an unconfigured device to request identity assignment.
All fields use APPLICATION tags.
::
Who-Am-I-Request ::= SEQUENCE {
vendorID Unsigned16,
modelName CharacterString,
serialNumber CharacterString
}
"""
vendor_id: int
model_name: str
serial_number: str
[docs]
def encode(self) -> bytes:
"""Encode Who-Am-I-Request service parameters."""
buf = bytearray()
buf.extend(encode_application_unsigned(self.vendor_id))
buf.extend(encode_application_character_string(self.model_name))
buf.extend(encode_application_character_string(self.serial_number))
return bytes(buf)
[docs]
@classmethod
def decode(cls, data: memoryview | bytes) -> WhoAmIRequest:
"""Decode Who-Am-I-Request from service request bytes."""
data = as_memoryview(data)
offset = 0
# vendorID (application tagged unsigned)
tag, offset = decode_tag(data, offset)
vendor_id = decode_unsigned(data[offset : offset + tag.length])
offset += tag.length
# modelName (application tagged character string)
tag, offset = decode_tag(data, offset)
model_name = decode_character_string(data[offset : offset + tag.length])
offset += tag.length
# serialNumber (application tagged character string)
tag, offset = decode_tag(data, offset)
serial_number = decode_character_string(data[offset : offset + tag.length])
return cls(
vendor_id=vendor_id,
model_name=model_name,
serial_number=serial_number,
)
[docs]
@dataclass(frozen=True, slots=True)
class YouAreRequest:
"""You-Are-Request (Clause 16.11, new in 2020).
Sent by a supervisor to assign identity to a device.
::
You-Are-Request ::= SEQUENCE {
deviceIdentifier [0] BACnetObjectIdentifier,
deviceMACAddress [1] OCTET STRING,
deviceNetworkNumber [2] Unsigned16 OPTIONAL
}
"""
device_identifier: ObjectIdentifier
device_mac_address: bytes
device_network_number: int | None = None
[docs]
def encode(self) -> bytes:
"""Encode You-Are-Request service parameters."""
buf = bytearray()
# [0] deviceIdentifier
buf.extend(encode_context_object_id(0, self.device_identifier))
# [1] deviceMACAddress
buf.extend(encode_context_octet_string(1, self.device_mac_address))
# [2] deviceNetworkNumber (optional)
if self.device_network_number is not None:
buf.extend(encode_context_tagged(2, encode_unsigned(self.device_network_number)))
return bytes(buf)
[docs]
@classmethod
def decode(cls, data: memoryview | bytes) -> YouAreRequest:
"""Decode You-Are-Request from service request bytes."""
data = as_memoryview(data)
offset = 0
# [0] deviceIdentifier
tag, offset = decode_tag(data, offset)
obj_type, instance = decode_object_identifier(data[offset : offset + tag.length])
offset += tag.length
device_identifier = ObjectIdentifier(ObjectType(obj_type), instance)
# [1] deviceMACAddress
tag, offset = decode_tag(data, offset)
device_mac_address = bytes(data[offset : offset + tag.length])
offset += tag.length
# [2] deviceNetworkNumber (optional)
device_network_number = None
if offset < len(data):
tag, new_offset = decode_tag(data, offset)
if tag.cls == TagClass.CONTEXT and tag.number == 2:
device_network_number = decode_unsigned(data[new_offset : new_offset + tag.length])
return cls(
device_identifier=device_identifier,
device_mac_address=device_mac_address,
device_network_number=device_network_number,
)