modbus_server.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. from __future__ import annotations
  2. from typing import Any
  3. from .collector_api import (
  4. create_modbus_device as api_create_modbus_device,
  5. create_modbus_point as api_create_modbus_point,
  6. edit_modbus_device as api_edit_modbus_device,
  7. edit_modbus_point as api_edit_modbus_point,
  8. )
  9. from .gateway_api import modbus_point_collect_test as api_modbus_point_collect_test
  10. from .mcp_app import mcp
  11. @mcp.tool(
  12. name="modbus.point_collect_test",
  13. description=(
  14. "通过采集网关读取 Modbus TCP 点位并转换为业务值。调用 "
  15. "{base_url}/api/dc-gateway/modbus/read_points。function_code: "
  16. "1=Read Coils/线圈,2=Read Discrete Inputs/离散输入,"
  17. "3=Read Holding Registers/保持寄存器,4=Read Input Registers/输入寄存器。"
  18. "word_byte_order 可选 ABCD、BADC、CDAB、DCBA。若读取成功后要创建汇采设备,"
  19. "映射为 byte_order/word_order: ABCD=>1/1, BADC=>2/1, CDAB=>1/2, DCBA=>2/2。"
  20. "响应透传上游 JSON,code=0 表示业务成功。"
  21. ),
  22. )
  23. def modbus_point_collect_test(
  24. project_key: str,
  25. ip: str,
  26. port: int,
  27. slave_id: int,
  28. points: list[dict[str, Any]],
  29. device_type: str = "ModbusTCP",
  30. word_byte_order: str = "ABCD",
  31. address_base: int = 0,
  32. ) -> dict[str, Any]:
  33. return api_modbus_point_collect_test(
  34. project_key,
  35. ip=ip,
  36. port=port,
  37. slave_id=slave_id,
  38. points=points,
  39. device_type=device_type,
  40. word_byte_order=word_byte_order,
  41. address_base=address_base,
  42. )
  43. @mcp.tool(
  44. name="collector.modbus_device_create",
  45. description=(
  46. "汇采-创建 Modbus 设备。调用 {data_collector_base_url}/api/collector/device,"
  47. "需要登录后使用 Authorization。创建设备必须传 device_type 协议类型、ip IP 地址、"
  48. "port 端口号、name 名称、slave_id、word_order 字顺序、byte_order 字节顺序、"
  49. "address_base 地址基准。address_base 会转换为汇采接口的 address_offset。"
  50. "默认参数: type=modbus, timeout=3, is_persistent=false, group_id=0, "
  51. "alarm_interval=90, collect_interval=5, retry_times=0。"
  52. "注意 byte_order/word_order 是汇采枚举,不是网关 word_byte_order。"
  53. "byte_order: 1=Big Endian, 2=Small Endian。word_order: 1=Big Endian, 2=Small Endian。"
  54. "device_type: 1=TCP, 2=RTU, 3=UDP, 4=RTU OVER TCP, 5=RTU OVER UDP。"
  55. "采集网关 word_byte_order 映射: ABCD=>1/1, BADC=>2/1, CDAB=>1/2, DCBA=>2/2。"
  56. "响应透传上游 JSON,state=0 表示业务成功。"
  57. ),
  58. )
  59. def collector_modbus_device_create(
  60. project_key: str,
  61. name: str,
  62. device_type: int,
  63. ip: str,
  64. port: int,
  65. slave_id: int,
  66. byte_order: int,
  67. word_order: int,
  68. address_base: int,
  69. serial_port: str = "",
  70. timeout: int = 3,
  71. is_persistent: bool = False,
  72. baud_rate: int = 0,
  73. data_bit: int = 0,
  74. parity: int = 0,
  75. stop_bit: int = 0,
  76. mode: int = 0,
  77. retry_times: int = 0,
  78. group_id: int = 0,
  79. alarm_interval: int = 90,
  80. collect_interval: int = 5,
  81. ) -> dict[str, Any]:
  82. return api_create_modbus_device(
  83. project_key,
  84. {
  85. "name": name,
  86. "device_type": device_type,
  87. "ip": ip,
  88. "port": port,
  89. "slave_id": slave_id,
  90. "byte_order": byte_order,
  91. "word_order": word_order,
  92. "address_base": address_base,
  93. "serial_port": serial_port,
  94. "timeout": timeout,
  95. "is_persistent": is_persistent,
  96. "baud_rate": baud_rate,
  97. "data_bit": data_bit,
  98. "parity": parity,
  99. "stop_bit": stop_bit,
  100. "mode": mode,
  101. "retry_times": retry_times,
  102. "group_id": group_id,
  103. "alarm_interval": alarm_interval,
  104. "collect_interval": collect_interval,
  105. },
  106. )
  107. @mcp.tool(
  108. name="collector.modbus_device_edit",
  109. description=(
  110. "汇采-编辑 Modbus 设备。调用 "
  111. "{data_collector_base_url}/api/collector/modbus/device/edit。"
  112. "这是 Modbus 专用旧编辑接口,必须传 ori_id 原设备 id、name、slave_id、"
  113. "word_order、byte_order、device_type 连接类型。TCP/UDP 类设备还必须传 ip 和 port;"
  114. "RTU 设备必须传 serial_port。"
  115. "编辑前设备不能处于已连接状态;若已连接,请先调用 collector.device_disconnect。"
  116. "该接口是全量更新语义,未传字段可能被默认值覆盖。"
  117. "默认参数: ip='', port=0, serial_port='', timeout=3, is_persistent=false, "
  118. "baud_rate=0, data_bit=0, parity=0, stop_bit=0, mode=0, address_offset=0, "
  119. "retry_times=0, device_group_id=0, alarm_interval=90, collect_interval=5。"
  120. "连接类型: 1=TCP, 2=RTU, 3=UDP, 4=RTU OVER TCP, 5=RTU OVER UDP。"
  121. "byte_order: 1=Big Endian, 2=Small Endian;word_order: 1=Big Endian, 2=Small Endian。"
  122. "响应透传上游 JSON,state=0 表示业务成功。"
  123. ),
  124. )
  125. def collector_modbus_device_edit(
  126. project_key: str,
  127. ori_id: int,
  128. name: str,
  129. device_type: int,
  130. slave_id: int,
  131. byte_order: int,
  132. word_order: int,
  133. ip: str = "",
  134. port: int = 0,
  135. serial_port: str = "",
  136. timeout: int = 3,
  137. is_persistent: bool = False,
  138. baud_rate: int = 0,
  139. data_bit: int = 0,
  140. parity: int = 0,
  141. stop_bit: int = 0,
  142. mode: int = 0,
  143. address_offset: int = 0,
  144. retry_times: int = 0,
  145. device_group_id: int = 0,
  146. alarm_interval: int = 90,
  147. collect_interval: int = 5,
  148. ) -> dict[str, Any]:
  149. return api_edit_modbus_device(
  150. project_key,
  151. {
  152. "ori_id": ori_id,
  153. "name": name,
  154. "device_type": device_type,
  155. "ip": ip,
  156. "port": port,
  157. "slave_id": slave_id,
  158. "byte_order": byte_order,
  159. "word_order": word_order,
  160. "serial_port": serial_port,
  161. "timeout": timeout,
  162. "is_persistent": is_persistent,
  163. "baud_rate": baud_rate,
  164. "data_bit": data_bit,
  165. "parity": parity,
  166. "stop_bit": stop_bit,
  167. "mode": mode,
  168. "address_offset": address_offset,
  169. "retry_times": retry_times,
  170. "device_group_id": device_group_id,
  171. "alarm_interval": alarm_interval,
  172. "collect_interval": collect_interval,
  173. },
  174. )
  175. @mcp.tool(
  176. name="collector.modbus_point_create",
  177. description=(
  178. "汇采-创建 Modbus 采集点位。调用 "
  179. "{data_collector_base_url}/api/collector/modbus/point/add_collect_point。"
  180. "创建点位必须传 payload.name 名称、payload.address 寄存器地址、"
  181. "payload.type 数据类型,以及 payload.func_code 或 payload.register_type 寄存器类型。"
  182. "payload 会补齐默认值: point_id='', scale_ratio=1, value_offset=0, group_id=0, "
  183. "invalid_values='', valid_range_start=null, valid_range_end=null, bit=0。"
  184. "func_code: 1=Read Coils/线圈,2=Read Discrete Inputs/离散输入,"
  185. "3=Read Holding Registers/保持寄存器,4=Read Input Registers/输入寄存器。"
  186. "register_type 可用 coil、discrete_input、holding_register、input_register。"
  187. "数据类型应使用汇采类型: bool, int16, uint16, int32, uint32, int64, uint64, float32, float64。"
  188. "常见点表类型映射: BOOL=>bool, SHORT=>int16, WORD=>uint16, LONG=>int32, "
  189. "DWORD=>uint32, FLOAT/REAL=>float32, DOUBLE=>float64, LONGLONG=>int64, QWORD=>uint64。"
  190. "响应透传上游 JSON,state=0 表示业务成功。"
  191. ),
  192. )
  193. def collector_modbus_point_create(
  194. project_key: str,
  195. payload: dict[str, Any],
  196. ) -> dict[str, Any]:
  197. return api_create_modbus_point(project_key, payload)
  198. @mcp.tool(
  199. name="collector.modbus_point_edit",
  200. description=(
  201. "汇采-编辑 Modbus 采集点位。调用 "
  202. "{data_collector_base_url}/api/collector/modbus/point/edit_collect_point。"
  203. "必须传 ori_id 原点位 id、name 名称、address 寄存器地址、data_type 数据类型,"
  204. "以及 func_code 或 register_type 寄存器类型。"
  205. "ori_id 对应 collector.device_points 返回的 data.point[].id。"
  206. "编辑点位不会迁移所属设备。"
  207. "该接口是全量更新语义,未传字段可能被默认值覆盖。"
  208. "默认参数: point_id='', scale_ratio=1, value_offset=0, group_id=0, "
  209. "invalid_values='', valid_range_start=null, valid_range_end=null, bit=0。"
  210. "func_code: 1=Read Coils/线圈,2=Read Discrete Inputs/离散输入,"
  211. "3=Read Holding Registers/保持寄存器,4=Read Input Registers/输入寄存器。"
  212. "register_type 可用 coil、discrete_input、holding_register、input_register。"
  213. "数据类型应使用汇采类型: bool, int16, uint16, int32, uint32, int64, uint64, float32, float64。"
  214. "常见点表类型映射: BOOL=>bool, SHORT=>int16, WORD=>uint16, LONG=>int32, "
  215. "DWORD=>uint32, FLOAT/REAL=>float32, DOUBLE=>float64, LONGLONG=>int64, QWORD=>uint64。"
  216. "响应透传上游 JSON,state=0 表示业务成功。"
  217. ),
  218. )
  219. def collector_modbus_point_edit(
  220. project_key: str,
  221. ori_id: int,
  222. name: str,
  223. address: int,
  224. data_type: str,
  225. func_code: int = 0,
  226. register_type: str = "",
  227. point_id: str = "",
  228. scale_ratio: float = 1,
  229. value_offset: float = 0,
  230. group_id: int = 0,
  231. invalid_values: str = "",
  232. valid_range_start: float | None = None,
  233. valid_range_end: float | None = None,
  234. bit: int = 0,
  235. describe: str = "",
  236. ) -> dict[str, Any]:
  237. payload: dict[str, Any] = {
  238. "ori_id": ori_id,
  239. "name": name,
  240. "address": address,
  241. "type": data_type,
  242. "point_id": point_id,
  243. "scale_ratio": scale_ratio,
  244. "value_offset": value_offset,
  245. "group_id": group_id,
  246. "invalid_values": invalid_values,
  247. "valid_range_start": valid_range_start,
  248. "valid_range_end": valid_range_end,
  249. "bit": bit,
  250. "describe": describe,
  251. }
  252. if func_code:
  253. payload["func_code"] = func_code
  254. else:
  255. payload["register_type"] = register_type
  256. return api_edit_modbus_point(project_key, payload)