bacnet_server.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. from __future__ import annotations
  2. from typing import Any, NotRequired, TypedDict
  3. from pydantic import BaseModel
  4. from .collector_api import (
  5. create_bacnet_devices as api_create_bacnet_devices,
  6. create_bacnet_points as api_create_bacnet_points,
  7. edit_bacnet_device as api_edit_bacnet_device,
  8. edit_bacnet_point as api_edit_bacnet_point,
  9. )
  10. from .gateway_api import (
  11. bacnet_bbmd_whois as api_bacnet_bbmd_whois,
  12. bacnet_point_collect_test as api_bacnet_point_collect_test,
  13. bacnet_point_search as api_bacnet_point_search,
  14. )
  15. from .mcp_app import mcp
  16. class BacnetDeviceCreateItem(BaseModel):
  17. name: str
  18. ip: str
  19. bacnet_device_id: int
  20. port: int = 47808
  21. bacnet_net: int = 0
  22. asp_ip: str = ""
  23. timeout: int = 3
  24. is_persistent: bool = False
  25. group_id: int = 0
  26. alarm_interval: int = 90
  27. collect_interval: int = 5
  28. class BacnetPointSpec(TypedDict):
  29. object_type: str
  30. object_id: int
  31. name: NotRequired[str]
  32. description: NotRequired[str]
  33. class BacnetPointCreateItem(BaseModel):
  34. device_id: int
  35. object_type: str
  36. object_id: int
  37. name: str = ""
  38. object_name: str = ""
  39. point_id: str = ""
  40. priority: int | None = None
  41. units: str = ""
  42. value_type: int = 0
  43. scale_ratio: float = 1
  44. value_offset: float = 0
  45. group_id: int = 0
  46. invalid_values: str = ""
  47. valid_range_start: float | None = None
  48. valid_range_end: float | None = None
  49. describe: str = ""
  50. @mcp.tool(
  51. name="bacnet.point_collect_test",
  52. description=(
  53. "采集网关-读取 BACnet/IP 设备点位的值,只需传 project_key、ip、"
  54. "bacnet_device_id 和 points;port 默认 47808。points 每项包含 object_type、object_id;"
  55. "object_type 支持 AnalogInput、analogInput、analog-input"
  56. "不需要传name字段"
  57. "响应 data.points[].present_value 为当前值,code=0 表示成功。"
  58. ),
  59. )
  60. def bacnet_point_collect_test(
  61. project_key: str,
  62. ip: str,
  63. bacnet_device_id: int,
  64. points: list[BacnetPointSpec],
  65. port: int = 47808,
  66. ) -> dict[str, Any]:
  67. return api_bacnet_point_collect_test(
  68. project_key,
  69. ip=ip,
  70. bacnet_device_id=bacnet_device_id,
  71. port=port,
  72. points=points,
  73. )
  74. @mcp.tool(
  75. name="bacnet.point_search",
  76. description=(
  77. "采集网关-搜索 BACnet/IP 设备点位。必须传 project_key、ip、bacnet_device_id;port 默认 47808。"
  78. ),
  79. )
  80. def bacnet_point_search(
  81. project_key: str,
  82. ip: str,
  83. bacnet_device_id: int,
  84. port: int = 47808,
  85. ) -> dict[str, Any]:
  86. return api_bacnet_point_search(
  87. project_key,
  88. ip=ip,
  89. bacnet_device_id=bacnet_device_id,
  90. port=port,
  91. )
  92. @mcp.tool(
  93. name="bacnet.bbmd_whois",
  94. description=(
  95. "采集网关-查询设备列表,通过 BBMD 执行 BACnet/IP Who-Is 设备发现。只需传 project_key。"
  96. "响应 data.devices 为发现的 BACnet 设备,"
  97. ),
  98. )
  99. def bacnet_bbmd_whois(project_key: str) -> dict[str, Any]:
  100. return api_bacnet_bbmd_whois(project_key)
  101. @mcp.tool(
  102. name="collector.bacnet_device_create",
  103. description=(
  104. "汇采-批量创建 BACnet 设备。devices 每项必须传 name、ip、bacnet_device_id;"
  105. "返回批量创建结果和对应的设备 id"
  106. ),
  107. )
  108. def collector_bacnet_device_create(
  109. project_key: str,
  110. devices: list[BacnetDeviceCreateItem],
  111. ) -> dict[str, Any]:
  112. return api_create_bacnet_devices(project_key, [_dump_model_or_dict(item) for item in devices])
  113. @mcp.tool(
  114. name="collector.bacnet_device_edit",
  115. description=(
  116. "汇采-编辑 BACnet 设备。必须传 ori_id、name、ip、bacnet_device_id;"
  117. "ori_id是设备id,bacnet_device_id是设备号"
  118. "编辑前设备不能处于已连接状态,"
  119. "若已连接请先调用 collector.device_disconnect 且 device_type=bacnet。"
  120. ),
  121. )
  122. def collector_bacnet_device_edit(
  123. project_key: str,
  124. ori_id: int,
  125. name: str,
  126. ip: str,
  127. bacnet_device_id: int,
  128. port: int = 47808,
  129. bacnet_net: int = 0,
  130. asp_ip: str = "",
  131. is_persistent: bool = False,
  132. device_group_id: int = 0,
  133. timeout: int = 3,
  134. alarm_interval: int = 90,
  135. collect_interval: int = 5,
  136. ) -> dict[str, Any]:
  137. return api_edit_bacnet_device(
  138. project_key,
  139. {
  140. "ori_id": ori_id,
  141. "name": name,
  142. "ip": ip,
  143. "bacnet_device_id": bacnet_device_id,
  144. "port": port,
  145. "bacnet_net": bacnet_net,
  146. "asp_ip": asp_ip,
  147. "is_persistent": is_persistent,
  148. "device_group_id": device_group_id,
  149. "timeout": timeout,
  150. "alarm_interval": alarm_interval,
  151. "collect_interval": collect_interval,
  152. },
  153. )
  154. @mcp.tool(
  155. name="collector.bacnet_point_create",
  156. description=(
  157. "汇采-批量创建 BACnet 采集点位。points 每项必须传 device_id、object_type、object_id, name,"
  158. "object_type 支持 AnalogInput、analogInput、analog-input"
  159. ),
  160. )
  161. def collector_bacnet_point_create(
  162. project_key: str,
  163. points: list[BacnetPointCreateItem],
  164. ) -> dict[str, Any]:
  165. return api_create_bacnet_points(project_key, [_dump_model_or_dict(item) for item in points])
  166. @mcp.tool(
  167. name="collector.bacnet_point_edit",
  168. description=(
  169. "汇采-编辑 BACnet 采集点位。必须传 ori_id、object_type、object_id,name"
  170. "ori_id 是设备id,object_type 支持 AnalogInput、analogInput、analog-input"
  171. ),
  172. )
  173. def collector_bacnet_point_edit(
  174. project_key: str,
  175. ori_id: int,
  176. object_type: str,
  177. object_id: int,
  178. name: str = "",
  179. object_name: str = "",
  180. point_id: str = "",
  181. priority: int | None = None,
  182. units: str = "",
  183. value_type: int = 0,
  184. scale_ratio: float = 1,
  185. value_offset: float = 0,
  186. group_id: int = 0,
  187. invalid_values: str = "",
  188. valid_range_start: float | None = None,
  189. valid_range_end: float | None = None,
  190. describe: str = "",
  191. ) -> dict[str, Any]:
  192. return api_edit_bacnet_point(
  193. project_key,
  194. {
  195. "ori_id": ori_id,
  196. "name": name,
  197. "object_type": object_type,
  198. "object_id": object_id,
  199. "object_name": object_name,
  200. "point_id": point_id,
  201. "priority": priority,
  202. "units": units,
  203. "value_type": value_type,
  204. "scale_ratio": scale_ratio,
  205. "value_offset": value_offset,
  206. "group_id": group_id,
  207. "invalid_values": invalid_values,
  208. "valid_range_start": valid_range_start,
  209. "valid_range_end": valid_range_end,
  210. "describe": describe,
  211. },
  212. )
  213. def _dump_model_or_dict(item: Any) -> dict[str, Any]:
  214. if isinstance(item, BaseModel):
  215. return item.model_dump(exclude_none=True)
  216. return dict(item)