|
1 | 1 | # configure logging output |
| 2 | +import asyncio |
2 | 3 | import logging |
3 | 4 | import warnings |
4 | 5 | from functools import cached_property |
5 | 6 | from typing import Any, Dict, List, Optional, Tuple |
6 | 7 |
|
7 | 8 | try: |
8 | 9 | import BAC0 |
9 | | - from BAC0.core.devices.Device import Device as BACnetDevice |
10 | 10 | except ImportError: |
11 | 11 | logging.critical( |
12 | 12 | "Install the 'bacnet-ingress' module, e.g. 'pip install buildingmotif[bacnet-ingress]'" |
|
23 | 23 |
|
24 | 24 |
|
25 | 25 | class BACnetNetwork(RecordIngressHandler): |
26 | | - def __init__(self, ip: Optional[str] = None): |
| 26 | + def __init__( |
| 27 | + self, |
| 28 | + ip: Optional[str] = None, |
| 29 | + *, |
| 30 | + discover_kwargs: Optional[Dict[str, Any]] = None, |
| 31 | + ping: bool = False, |
| 32 | + device_kwargs: Optional[Dict[str, Any]] = None, |
| 33 | + ): |
27 | 34 | """ |
28 | 35 | Reads a BACnet network to discover the devices and objects therein |
29 | 36 |
|
30 | | - :param ip: IP/mask for the host which is canning the networks, |
| 37 | + :param ip: IP/mask for the host which is scanning the network, |
31 | 38 | defaults to None |
32 | 39 | :type ip: Optional[str], optional |
| 40 | + :param discover_kwargs: Optional kwargs forwarded to BAC0._discover. |
| 41 | + :type discover_kwargs: Optional[Dict[str, Any]] |
| 42 | + :param ping: Whether to ping devices during connect; defaults to False. |
| 43 | + :type ping: bool |
| 44 | + :param device_kwargs: Optional kwargs forwarded to BAC0.device. |
| 45 | + :type device_kwargs: Optional[Dict[str, Any]] |
33 | 46 | """ |
34 | | - # create the network object; this will handle scans |
35 | | - # Be a good net citizen: do not ping BACnet devices |
36 | | - self.network = BAC0.connect(ip=ip, ping=False) |
37 | | - # initiate discovery of BACnet networks |
38 | | - self.network.discover() |
39 | | - |
40 | | - self.devices: List[BACnetDevice] = [] |
41 | | - self.objects: Dict[Tuple[str, int], List[dict]] = {} |
42 | | - |
43 | | - # for each discovered Device, create a BAC0.device object |
44 | | - # This will read the BACnet objects off of the Device. |
45 | | - # Save the BACnet objects in the objects dictionary |
| 47 | + self.objects: Dict[Tuple[str, int], List[Dict[str, Any]]] = {} |
| 48 | + self._run_async( |
| 49 | + self._collect_objects( |
| 50 | + ip=ip, |
| 51 | + discover_kwargs=discover_kwargs or {}, |
| 52 | + ping=ping, |
| 53 | + device_kwargs=device_kwargs or {}, |
| 54 | + ) |
| 55 | + ) |
| 56 | + |
| 57 | + def _run_async(self, coro): |
| 58 | + loop = asyncio.new_event_loop() |
46 | 59 | try: |
47 | | - if self.network.discoveredDevices is None: |
| 60 | + asyncio.set_event_loop(loop) |
| 61 | + loop.run_until_complete(coro) |
| 62 | + loop.run_until_complete(loop.shutdown_asyncgens()) |
| 63 | + finally: |
| 64 | + asyncio.set_event_loop(None) |
| 65 | + loop.close() |
| 66 | + |
| 67 | + async def _collect_objects( |
| 68 | + self, |
| 69 | + *, |
| 70 | + ip: Optional[str], |
| 71 | + discover_kwargs: Dict[str, Any], |
| 72 | + ping: bool, |
| 73 | + device_kwargs: Dict[str, Any], |
| 74 | + ): |
| 75 | + device_kwargs.setdefault("poll", 0) |
| 76 | + |
| 77 | + async with BAC0.start(ip=ip, ping=ping) as bacnet: |
| 78 | + await bacnet._discover(**discover_kwargs) |
| 79 | + |
| 80 | + discovered = getattr(bacnet, "discoveredDevices", None) |
| 81 | + if not discovered: |
48 | 82 | warnings.warn("BACnet ingress could not find any BACnet devices") |
49 | | - for (address, device_id) in self.network.discoveredDevices: # type: ignore |
50 | | - # set poll to 0 to avoid reading the points regularly |
51 | | - dev = BAC0.device(address, device_id, self.network, poll=0) |
52 | | - self.devices.append(dev) |
53 | | - self.objects[(address, device_id)] = [] |
| 83 | + return |
| 84 | + |
| 85 | + for (address, device_id) in discovered: |
| 86 | + device = await BAC0.device(address, device_id, bacnet, **device_kwargs) |
| 87 | + objects: List[Dict[str, Any]] = [] |
54 | 88 |
|
55 | | - for bobj in dev.points: |
| 89 | + for bobj in device.points: |
56 | 90 | obj = bobj.properties.asdict |
57 | 91 | self._clean_object(obj) |
58 | | - self.objects[(address, device_id)].append(obj) |
59 | | - finally: |
60 | | - for dev in self.devices: |
61 | | - self.network.unregister_device(dev) |
62 | | - self.network.disconnect() |
| 92 | + objects.append(obj) |
| 93 | + |
| 94 | + self.objects[(address, device_id)] = objects |
63 | 95 |
|
64 | 96 | def _clean_object(self, obj: Dict[str, Any]): |
65 | 97 | if "name" in obj: |
|
0 commit comments