Source code for buildingmotif.ingresses.bacnet

# configure logging output
import logging
import warnings
from functools import cached_property
from typing import Any, Dict, List, Optional, Tuple

try:
    import BAC0
    from BAC0.core.devices.Device import Device as BACnetDevice
except ImportError:
    logging.critical(
        "Install the 'bacnet-ingress' module, e.g. 'pip install buildingmotif[bacnet-ingress]'"
    )


from buildingmotif.ingresses.base import Record, RecordIngressHandler

# We do this little rigamarole to avoid BAC0 spitting out a million
# logging messages warning us that we changed the log level, which
# happens when we go through the normal BAC0 log level procedure
logger = logging.getLogger("BAC0_Root.BAC0.scripts.Base.Base")
logger.setLevel(logging.ERROR)


[docs]class BACnetNetwork(RecordIngressHandler): def __init__(self, ip: Optional[str] = None): """ Reads a BACnet network to discover the devices and objects therein :param ip: IP/mask for the host which is canning the networks, defaults to None :type ip: Optional[str], optional """ # create the network object; this will handle scans # Be a good net citizen: do not ping BACnet devices self.network = BAC0.connect(ip=ip, ping=False) # initiate discovery of BACnet networks self.network.discover() self.devices: List[BACnetDevice] = [] self.objects: Dict[Tuple[str, int], List[dict]] = {} # for each discovered Device, create a BAC0.device object # This will read the BACnet objects off of the Device. # Save the BACnet objects in the objects dictionary try: if self.network.discoveredDevices is None: warnings.warn("BACnet ingress could not find any BACnet devices") for (address, device_id) in self.network.discoveredDevices: # type: ignore # set poll to 0 to avoid reading the points regularly dev = BAC0.device(address, device_id, self.network, poll=0) self.devices.append(dev) self.objects[(address, device_id)] = [] for bobj in dev.points: obj = bobj.properties.asdict self._clean_object(obj) self.objects[(address, device_id)].append(obj) finally: for dev in self.devices: self.network.unregister_device(dev) self.network.disconnect() def _clean_object(self, obj: Dict[str, Any]): if "name" in obj: # remove trailing/leading whitespace from names obj["name"] = obj["name"].strip() @cached_property def records(self) -> List[Record]: """ Returns a list of the BACnet devices and objects discovered in the BACnet network. The 'rtype' field of each Record is either "Device" for a BACnet Device or "Object" for a BACnet Object. The 'fields' field contains the key-value BACnet properties associated with that device or object. :return: list of BACnet devices and objects, each expressed as a Record :rtype: List[Record] """ records = [] # make devices for (address, device_id) in self.objects.keys(): records.append( Record( rtype="Device", fields={"address": address, "device_id": device_id}, ) ) for (address, device_id), objs in self.objects.items(): for obj in objs: fields = obj.copy() del fields["device"] fields["device_id"] = device_id records.append( Record( rtype="Object", fields=fields, ) ) return records