modbus_server.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. from __future__ import annotations
  2. from typing import Any, NotRequired, TypedDict
  3. from pydantic import BaseModel
  4. from .collector_api import (
  5. create_modbus_devices as api_create_modbus_devices,
  6. create_modbus_points as api_create_modbus_points,
  7. edit_modbus_device as api_edit_modbus_device,
  8. edit_modbus_point as api_edit_modbus_point,
  9. )
  10. from .gateway_api import (
  11. modbus_point_collect_test as api_modbus_point_collect_test,
  12. modbus_raw_read as api_modbus_raw_read,
  13. )
  14. from .mcp_app import mcp
  15. class ModbusDeviceCreateItem(BaseModel):
  16. name: str
  17. ip: str
  18. port: int
  19. slave_id: int
  20. byte_order: int
  21. word_order: int
  22. address_base: int
  23. serial_port: str = ""
  24. device_type: int = 1
  25. timeout: int = 3
  26. is_persistent: bool = False
  27. baud_rate: int = 0
  28. data_bit: int = 0
  29. parity: int = 0
  30. stop_bit: int = 0
  31. mode: int = 0
  32. retry_times: int = 0
  33. group_id: int = 0
  34. alarm_interval: int = 90
  35. collect_interval: int = 5
  36. class ModbusPointCreateItem(BaseModel):
  37. device_id: int
  38. name: str
  39. address: int
  40. type: str
  41. func_code: int
  42. register_type: str = ""
  43. point_id: str = ""
  44. scale_ratio: float = 1
  45. value_offset: float = 0
  46. group_id: int = 0
  47. invalid_values: str = ""
  48. valid_range_start: float | None = None
  49. valid_range_end: float | None = None
  50. bit: int = 0
  51. describe: str = ""
  52. class ModbusRawReadSpec(TypedDict):
  53. function_code: int
  54. address: int
  55. quantity: int
  56. @mcp.tool(
  57. name="modbus.raw_read",
  58. description=(
  59. "采集网关-通过Modbus TCP 读取原始数据。"
  60. "device_type 设备类型,默认 ModbusTCP;word_byte_order 默认 ABCD,可选 ABCD、BADC、CDAB、DCBA;"
  61. "address_base 地址偏移,默认 0。"
  62. "read 必须包含 function_code、address、quantity;function_code: "
  63. "1=Read Coils/线圈,2=Read Discrete Inputs/离散输入,"
  64. "3=Read Holding Registers/保持寄存器,4=Read Input Registers/输入寄存器;"
  65. "quantity 范围 1..125。"
  66. "不要传name字段"
  67. ),
  68. )
  69. def modbus_raw_read(
  70. project_key: str,
  71. ip: str,
  72. port: int,
  73. slave_id: int,
  74. read: ModbusRawReadSpec,
  75. device_type: str = "ModbusTCP",
  76. word_byte_order: str = "ABCD",
  77. address_base: int = 0,
  78. ) -> dict[str, Any]:
  79. return api_modbus_raw_read(
  80. project_key,
  81. ip=ip,
  82. port=port,
  83. slave_id=slave_id,
  84. read=dict(read),
  85. device_type=device_type,
  86. word_byte_order=word_byte_order,
  87. address_base=address_base,
  88. )
  89. @mcp.tool(
  90. name="modbus.point_collect_test",
  91. description=(
  92. "通过采集网关读取 Modbus TCP 点位的数据"
  93. "寄存器类型可选:1=Read Coils/线圈/0x,2=Read Discrete Inputs/离散输入/1x,"
  94. "3=Read Holding Registers/保持寄存器/4x,4=Read Input Registers/输入寄存器/3x。"
  95. "word_byte_order 可选 ABCD、BADC、CDAB、DCBA"
  96. ),
  97. )
  98. def modbus_point_collect_test(
  99. project_key: str,
  100. ip: str,
  101. port: int,
  102. slave_id: int,
  103. points: list[dict[str, Any]],
  104. device_type: str = "ModbusTCP",
  105. word_byte_order: str = "ABCD",
  106. address_base: int = 0,
  107. ) -> dict[str, Any]:
  108. return api_modbus_point_collect_test(
  109. project_key,
  110. ip=ip,
  111. port=port,
  112. slave_id=slave_id,
  113. points=points,
  114. device_type=device_type,
  115. word_byte_order=word_byte_order,
  116. address_base=address_base,
  117. )
  118. @mcp.tool(
  119. name="collector.modbus_device_create",
  120. description=(
  121. "汇采-批量创建 Modbus 设备。"
  122. "默认参数: type=modbus, timeout=3, is_persistent=false, group_id=0, "
  123. "alarm_interval=90, collect_interval=5, retry_times=0。"
  124. "address_base 地址偏移/address_offset。"
  125. "byte_order: 1=Big Endian, 2=Small Endian。word_order: 1=Big Endian, 2=Small Endian。"
  126. "word_byte_order 映射: ABCD=>1/1, BADC=>2/1, CDAB=>1/2, DCBA=>2/2。"
  127. ),
  128. )
  129. def collector_modbus_device_create(
  130. project_key: str,
  131. devices: list[ModbusDeviceCreateItem],
  132. ) -> dict[str, Any]:
  133. return api_create_modbus_devices(project_key, [_dump_model_or_dict(item) for item in devices])
  134. @mcp.tool(
  135. name="collector.modbus_device_edit",
  136. description=(
  137. "汇采-编辑 Modbus 设备。必须传 ori_id 原设备 id、name、slave_id、"
  138. "word_order、byte_order、ip 和 port;"
  139. "编辑前设备不能处于已连接状态;若已连接,请先调用 collector.device_disconnect。"
  140. "默认参数: ip='', port=0, serial_port='', timeout=3, is_persistent=false, "
  141. "baud_rate=0, data_bit=0, parity=0, stop_bit=0, mode=0, address_offset=0, "
  142. "retry_times=0, device_group_id=0, alarm_interval=90, collect_interval=5。"
  143. "byte_order: 1=Big Endian, 2=Small Endian;word_order: 1=Big Endian, 2=Small Endian。"
  144. ),
  145. )
  146. def collector_modbus_device_edit(
  147. project_key: str,
  148. ori_id: int,
  149. name: str,
  150. device_type: int,
  151. slave_id: int,
  152. byte_order: int,
  153. word_order: int,
  154. ip: str = "",
  155. port: int = 0,
  156. serial_port: str = "",
  157. timeout: int = 3,
  158. is_persistent: bool = False,
  159. baud_rate: int = 0,
  160. data_bit: int = 0,
  161. parity: int = 0,
  162. stop_bit: int = 0,
  163. mode: int = 0,
  164. address_offset: int = 0,
  165. retry_times: int = 0,
  166. device_group_id: int = 0,
  167. alarm_interval: int = 90,
  168. collect_interval: int = 5,
  169. ) -> dict[str, Any]:
  170. return api_edit_modbus_device(
  171. project_key,
  172. {
  173. "ori_id": ori_id,
  174. "name": name,
  175. "device_type": device_type,
  176. "ip": ip,
  177. "port": port,
  178. "slave_id": slave_id,
  179. "byte_order": byte_order,
  180. "word_order": word_order,
  181. "serial_port": serial_port,
  182. "timeout": timeout,
  183. "is_persistent": is_persistent,
  184. "baud_rate": baud_rate,
  185. "data_bit": data_bit,
  186. "parity": parity,
  187. "stop_bit": stop_bit,
  188. "mode": mode,
  189. "address_offset": address_offset,
  190. "retry_times": retry_times,
  191. "device_group_id": device_group_id,
  192. "alarm_interval": alarm_interval,
  193. "collect_interval": collect_interval,
  194. },
  195. )
  196. @mcp.tool(
  197. name="collector.modbus_point_create",
  198. description=(
  199. "汇采-批量创建 Modbus 采集点位。"
  200. "points 每项必须传 device_id、name 名称、address 寄存器地址、"
  201. "type 数据类型,以及 func_code 寄存器类型。"
  202. "func_code: 1=Read Coils/线圈/0x,2=Read Discrete Inputs/离散输入/1x,"
  203. "3=Read Holding Registers/保持寄存器/4x,4=Read Input Registers/输入寄存器/3x。"
  204. "数据类型应使用汇采类型: bool, int16, uint16, int32, uint32, int64, uint64, float32, float64。"
  205. "常见点表类型映射: BOOL=>bool, SHORT=>int16, WORD=>uint16, LONG=>int32, "
  206. "DWORD=>uint32, FLOAT/REAL=>float32, DOUBLE=>float64, LONGLONG=>int64, QWORD=>uint64。"
  207. ),
  208. )
  209. def collector_modbus_point_create(
  210. project_key: str,
  211. points: list[ModbusPointCreateItem],
  212. ) -> dict[str, Any]:
  213. return api_create_modbus_points(project_key, [_dump_model_or_dict(item) for item in points])
  214. def _dump_model_or_dict(item: Any) -> dict[str, Any]:
  215. if isinstance(item, BaseModel):
  216. return item.model_dump(exclude_none=True)
  217. return dict(item)
  218. @mcp.tool(
  219. name="collector.modbus_point_edit",
  220. description=(
  221. "汇采-编辑 Modbus 采集点位。调用 "
  222. "必须传 ori_id 原点位 id、name 名称、address 寄存器地址、type 数据类型,"
  223. "以及 func_code 寄存器类型。"
  224. "ori_id 对应 collector.device_points 返回的 data.point[].id。"
  225. "默认参数: point_id='', scale_ratio=1, value_offset=0, group_id=0, "
  226. "invalid_values='', valid_range_start=null, valid_range_end=null, bit=0。"
  227. "func_code: 1=Read Coils/线圈/0x,2=Read Discrete Inputs/离散输入/1x,"
  228. "3=Read Holding Registers/保持寄存器/4x,4=Read Input Registers/输入寄存器/3x。"
  229. "register_type 可用 coil、discrete_input、holding_register、input_register。"
  230. "数据类型应使用汇采类型: bool, int16, uint16, int32, uint32, int64, uint64, float32, float64。"
  231. "常见点表类型映射: BOOL=>bool, SHORT=>int16, WORD=>uint16, LONG=>int32, "
  232. "DWORD=>uint32, FLOAT/REAL=>float32, DOUBLE=>float64, LONGLONG=>int64, QWORD=>uint64。"
  233. ),
  234. )
  235. def collector_modbus_point_edit(
  236. project_key: str,
  237. ori_id: int,
  238. name: str,
  239. address: int,
  240. data_type: str,
  241. func_code: int = 0,
  242. register_type: str = "",
  243. point_id: str = "",
  244. scale_ratio: float = 1,
  245. value_offset: float = 0,
  246. group_id: int = 0,
  247. invalid_values: str = "",
  248. valid_range_start: float | None = None,
  249. valid_range_end: float | None = None,
  250. bit: int = 0,
  251. describe: str = "",
  252. ) -> dict[str, Any]:
  253. payload: dict[str, Any] = {
  254. "ori_id": ori_id,
  255. "name": name,
  256. "address": address,
  257. "type": data_type,
  258. "point_id": point_id,
  259. "scale_ratio": scale_ratio,
  260. "value_offset": value_offset,
  261. "group_id": group_id,
  262. "invalid_values": invalid_values,
  263. "valid_range_start": valid_range_start,
  264. "valid_range_end": valid_range_end,
  265. "bit": bit,
  266. "describe": describe,
  267. }
  268. if func_code:
  269. payload["func_code"] = func_code
  270. else:
  271. payload["register_type"] = register_type
  272. return api_edit_modbus_point(project_key, payload)