|
|
@@ -45,6 +45,11 @@ except Exception:
|
|
|
pass
|
|
|
|
|
|
|
|
|
+class BacnetCommunicationError(RuntimeError):
|
|
|
+ def __init__(self, message: str) -> None:
|
|
|
+ super().__init__(message)
|
|
|
+
|
|
|
+
|
|
|
def device_payload(request: BacnetPointReadRequest | BacnetPointSearchRequest) -> dict[str, Any]:
|
|
|
return {
|
|
|
"device_type": "BACnet/IP",
|
|
|
@@ -107,7 +112,38 @@ async def read_property_or_none(bacnet: Any, address: str, object_type: str, obj
|
|
|
|
|
|
|
|
|
async def read_present_value(bacnet: Any, address: str, point: BacnetPointSpec) -> Any:
|
|
|
- return json_value(await bacnet.read(property_read_args(address, point.object_type, point.object_id, "presentValue")))
|
|
|
+ try:
|
|
|
+ value = await bacnet.read(property_read_args(address, point.object_type, point.object_id, "presentValue"))
|
|
|
+ except Exception as exc:
|
|
|
+ raise BacnetCommunicationError(
|
|
|
+ f"no response from BACnet device {address} while reading "
|
|
|
+ f"{point.object_type} {point.object_id} presentValue"
|
|
|
+ ) from exc
|
|
|
+ return json_value(value)
|
|
|
+
|
|
|
+
|
|
|
+def ignore_cancelled_bacnet_broadcast_endpoint(loop: asyncio.AbstractEventLoop, context: dict[str, Any]) -> None:
|
|
|
+ message = str(context.get("message", ""))
|
|
|
+ exception = context.get("exception")
|
|
|
+ if isinstance(exception, asyncio.CancelledError) and "set_broadcast_transport_protocol" in message:
|
|
|
+ return
|
|
|
+ loop.default_exception_handler(context)
|
|
|
+
|
|
|
+
|
|
|
+def run_bacnet(coro: Any) -> Any:
|
|
|
+ loop = asyncio.new_event_loop()
|
|
|
+ loop.set_exception_handler(ignore_cancelled_bacnet_broadcast_endpoint)
|
|
|
+ try:
|
|
|
+ asyncio.set_event_loop(loop)
|
|
|
+ return loop.run_until_complete(coro)
|
|
|
+ finally:
|
|
|
+ pending = asyncio.all_tasks(loop)
|
|
|
+ for task in pending:
|
|
|
+ task.cancel()
|
|
|
+ if pending:
|
|
|
+ loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
|
|
+ loop.close()
|
|
|
+ asyncio.set_event_loop(None)
|
|
|
|
|
|
|
|
|
def split_object_identifier(value: Any) -> tuple[str, int] | None:
|
|
|
@@ -163,9 +199,15 @@ async def search_points_async(request: BacnetPointSearchRequest) -> list[dict[st
|
|
|
with warnings.catch_warnings():
|
|
|
warnings.filterwarnings("ignore", message=BAC0_REGISTERED_WARNING)
|
|
|
async with BAC0.start(ip=local_ip, port=local_port, ping=False) as bacnet:
|
|
|
- object_list = await bacnet.read(
|
|
|
- property_read_args(address, "Device", request.bacnet_device_id, "objectList")
|
|
|
- )
|
|
|
+ try:
|
|
|
+ object_list = await bacnet.read(
|
|
|
+ property_read_args(address, "Device", request.bacnet_device_id, "objectList")
|
|
|
+ )
|
|
|
+ except Exception as exc:
|
|
|
+ raise BacnetCommunicationError(
|
|
|
+ f"no response from BACnet device {address} while reading "
|
|
|
+ f"Device {request.bacnet_device_id} objectList"
|
|
|
+ ) from exc
|
|
|
for item in object_list or []:
|
|
|
parsed = split_object_identifier(item)
|
|
|
if parsed is None:
|
|
|
@@ -188,7 +230,9 @@ async def search_points_async(request: BacnetPointSearchRequest) -> list[dict[st
|
|
|
def read_points(request: BacnetPointReadRequest) -> dict[str, Any]:
|
|
|
try:
|
|
|
with _BACNET_LOCK:
|
|
|
- points = asyncio.run(read_points_async(request))
|
|
|
+ points = run_bacnet(read_points_async(request))
|
|
|
+ except BacnetCommunicationError as exc:
|
|
|
+ return response_payload(1, str(exc), {"device": device_payload(request), "points": []})
|
|
|
except Exception as exc:
|
|
|
return response_payload(1, str(exc), {"device": device_payload(request), "points": []})
|
|
|
return response_payload(0, "success", {"device": device_payload(request), "points": points})
|
|
|
@@ -197,7 +241,9 @@ def read_points(request: BacnetPointReadRequest) -> dict[str, Any]:
|
|
|
def search_points(request: BacnetPointSearchRequest) -> dict[str, Any]:
|
|
|
try:
|
|
|
with _BACNET_LOCK:
|
|
|
- points = asyncio.run(search_points_async(request))
|
|
|
+ points = run_bacnet(search_points_async(request))
|
|
|
+ except BacnetCommunicationError as exc:
|
|
|
+ return response_payload(1, str(exc), {"device": device_payload(request), "points": []})
|
|
|
except Exception as exc:
|
|
|
return response_payload(1, str(exc), {"device": device_payload(request), "points": []})
|
|
|
return response_payload(0, "success", {"device": device_payload(request), "points": points})
|