Skip to content

Commit 7a2f115

Browse files
committed
bacnet handling tasks
1 parent 76b5ead commit 7a2f115

File tree

1 file changed

+36
-15
lines changed

1 file changed

+36
-15
lines changed

buildingmotif/ingresses/bacnet.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from functools import cached_property
66
from typing import Any, Dict, List, Optional, Tuple
77

8+
from buildingmotif.ingresses.base import Record, RecordIngressHandler
9+
810
try:
911
import BAC0
1012
except ImportError:
@@ -13,15 +15,6 @@
1315
)
1416

1517

16-
from buildingmotif.ingresses.base import Record, RecordIngressHandler
17-
18-
# We do this little rigamarole to avoid BAC0 spitting out a million
19-
# logging messages warning us that we changed the log level, which
20-
# happens when we go through the normal BAC0 log level procedure
21-
logger = logging.getLogger("BAC0_Root.BAC0.scripts.Base.Base")
22-
logger.setLevel(logging.ERROR)
23-
24-
2518
class BACnetNetwork(RecordIngressHandler):
2619
def __init__(
2720
self,
@@ -87,7 +80,6 @@ async def _collect_objects(
8780
device_kwargs.setdefault("poll", -1)
8881
device_kwargs.setdefault("auto_save", False)
8982

90-
logger.error(f"starting with {ip=} {ping=}")
9183
async with BAC0.start(ip=ip, ping=ping) as bacnet:
9284
await asyncio.sleep(2)
9385
await bacnet._discover(**discover_kwargs)
@@ -110,7 +102,7 @@ async def _collect_objects(
110102
device_id = info.get("device_id")
111103

112104
if address is None or device_id is None:
113-
logger.warning(
105+
logging.warning(
114106
"Skipping discovered device with missing address/device_id: %s",
115107
info,
116108
)
@@ -128,6 +120,13 @@ async def _collect_objects(
128120
for (address, device_id, _) in discovered_entries:
129121
device = await BAC0.device(address, device_id, bacnet, **device_kwargs)
130122
try:
123+
# keep persistence disabled and quiet for one-shot scans
124+
setattr(device.properties, "auto_save", False)
125+
setattr(device.properties, "clear_history_on_save", False)
126+
setattr(device.properties, "history_size", None)
127+
if hasattr(device, "_log"):
128+
device._log.setLevel(logging.ERROR) # type: ignore[attr-defined]
129+
131130
objects: List[Dict[str, Any]] = []
132131

133132
for bobj in device.points:
@@ -137,14 +136,36 @@ async def _collect_objects(
137136

138137
self.objects[(address, device_id)] = objects
139138
finally:
140-
disconnect_task = device.disconnect(save_on_disconnect=False)
141-
if disconnect_task is not None:
142-
await disconnect_task
139+
disconnect = getattr(
140+
device, "_disconnect", None # type: ignore[attr-defined]
141+
)
142+
if callable(disconnect):
143+
await disconnect(save_on_disconnect=False, unregister=True)
143144

144145
def _clean_object(self, obj: Dict[str, Any]):
145-
if "name" in obj:
146+
def _normalize(value: Any, path: Tuple[Any, ...]) -> Any:
147+
if isinstance(value, (str, int, float, bool)) or value is None:
148+
return value
149+
if isinstance(value, dict):
150+
normalized: Dict[Any, Any] = {}
151+
for key, nested in value.items():
152+
normalized[key] = _normalize(nested, (*path, key))
153+
return normalized
154+
if isinstance(value, (list, tuple, set)):
155+
return [_normalize(v, (*path, idx)) for idx, v in enumerate(value)]
156+
157+
logging.error(
158+
"Ignoring non-serializable BACnet value %r at %s",
159+
value,
160+
" -> ".join(str(p) for p in path),
161+
)
162+
return None
163+
164+
if "name" in obj and isinstance(obj["name"], str):
146165
# remove trailing/leading whitespace from names
147166
obj["name"] = obj["name"].strip()
167+
for key, value in list(obj.items()):
168+
obj[key] = _normalize(value, (obj.get("device"), key))
148169

149170
@cached_property
150171
def records(self) -> List[Record]:

0 commit comments

Comments
 (0)