Forráskód Böngészése

支持S7-Smart200

Lu Xianghui 1 hónapja
szülő
commit
88c69347e8

+ 17 - 6
data_collector_mcp/gateway_api.py

@@ -52,9 +52,9 @@ def s7_raw_read(
     rock: int,
     slot: int,
     read: dict[str, Any],
-    device_type: str = "S7TCP",
+    device_type: str = "S7-1200",
     port: int = 102,
-    tsap_conn_type: str = "PG",
+    tsap_conn_type: str | None = None,
 ) -> dict[str, Any]:
     payload = {
         "device_type": device_type,
@@ -62,7 +62,7 @@ def s7_raw_read(
         "port": port,
         "rock": rock,
         "slot": slot,
-        "tsap_conn_type": tsap_conn_type,
+        "tsap_conn_type": _resolve_s7_tsap_conn_type(device_type, tsap_conn_type),
         "read": read,
     }
     return _request_gateway(project_key, S7_SPEC.raw_read_path, payload, "s7 raw read")
@@ -75,9 +75,9 @@ def s7_point_collect_test(
     rock: int,
     slot: int,
     points: list[dict[str, Any]],
-    device_type: str = "S7TCP",
+    device_type: str = "S7-1200",
     port: int = 102,
-    tsap_conn_type: str = "PG",
+    tsap_conn_type: str | None = None,
 ) -> dict[str, Any]:
     payload = {
         "device_type": device_type,
@@ -85,7 +85,7 @@ def s7_point_collect_test(
         "port": port,
         "rock": rock,
         "slot": slot,
-        "tsap_conn_type": tsap_conn_type,
+        "tsap_conn_type": _resolve_s7_tsap_conn_type(device_type, tsap_conn_type),
         "points": points,
     }
     return _request_gateway(project_key, S7_SPEC.point_test_path, payload, "s7 point test")
@@ -93,3 +93,14 @@ def s7_point_collect_test(
 
 def s7_connect_scan(project_key: str, *, ip: str) -> dict[str, Any]:
     return _request_gateway(project_key, S7_SPEC.connect_scan_path, {"ip": ip}, "s7 connect scan")
+
+
+def _resolve_s7_tsap_conn_type(device_type: str, tsap_conn_type: str | None) -> str:
+    normalized_tsap = str(tsap_conn_type or "").strip().upper()
+    if normalized_tsap:
+        return normalized_tsap
+
+    normalized_device_type = str(device_type or "").strip().lower().replace(" ", "")
+    if normalized_device_type in {"s7-smart200", "s7smart200", "smart200"}:
+        return "OP"
+    return "PG"

+ 32 - 16
data_collector_mcp/s7_server.py

@@ -22,8 +22,10 @@ from .mcp_app import mcp
         "通过采集网关对 Siemens S7 TCP 设备执行原始字节读取。调用 "
         "{base_url}/api/dc-gateway/s7/read。请求字段使用网关/汇采一致命名:"
         "ip 为 PLC 地址,port 默认 102,rock 为机架号,slot 为槽号,"
-        "tsap_conn_type 默认 PG,可选 PG、OP、BASIC。read 必须包含 area、start、size;"
-        "area 可用 DB、M、I、Q;area=DB 时 read.db 必须大于 0。"
+        "device_type 为设备类型,默认 S7-1200,可传 S7-1200、S7-Smart200。"
+        "tsap_conn_type 可选 PG、OP、BASIC;未传时 S7-1200 使用 PG,"
+        "device_type=S7-Smart200 时使用 OP。read 必须包含 area、start、size;"
+        "area 可用 DB、M、I、Q,S7-Smart200 还支持 V;area=DB 时 read.db 必须大于 0。"
         "该接口不解析业务值,只返回语义化 communication:S7_CONNECT、S7_CONNECTED、"
         "S7_READ、原始十六进制字节、S7_DISCONNECT 或 S7_ERROR。"
         "响应透传上游 JSON,code=0 表示业务成功,code!=0 时 msg 为失败原因。"
@@ -35,9 +37,9 @@ def s7_raw_read(
     read: dict[str, Any],
     rock: int = 0,
     slot: int = 1,
-    device_type: str = "S7TCP",
+    device_type: str = "S7-1200",
     port: int = 102,
-    tsap_conn_type: str = "PG",
+    tsap_conn_type: str | None = None,
 ) -> dict[str, Any]:
     return api_s7_raw_read(
         project_key,
@@ -57,8 +59,11 @@ def s7_raw_read(
         "通过采集网关读取 Siemens S7 TCP 点位并转换为业务值。调用 "
         "{base_url}/api/dc-gateway/s7/read_points。请求字段使用网关/汇采一致命名:"
         "ip 为 PLC 地址,port 默认 102,rock 为机架号,slot 为槽号,"
-        "tsap_conn_type 默认 PG,可选 PG、OP、BASIC。points 为点位数组,每个点位必须包含 "
-        "area、start、type;area 可用 DB、M、I、Q;area=DB 时 db 必须大于 0。"
+        "device_type 为设备类型,默认 S7-1200,可传 S7-1200、S7-Smart200。"
+        "tsap_conn_type 可选 PG、OP、BASIC;未传时 S7-1200 使用 PG,"
+        "device_type=S7-Smart200 时使用 OP。points 为点位数组,每个点位必须包含 "
+        "area、start、type;area 可用 DB、M、I、Q,S7-Smart200 还支持 V;"
+        "area=DB 时 db 必须大于 0。"
         "type 可用 bool、byte、int8、int16、uint16、int32、uint32、int64、uint64、"
         "float32、float64;bool 点可传 bit 读取指定 0..7 位,非 bool 点不能传 bit。"
         "S7 多字节数值按大端解析,响应 data.points[].value 为转换后的业务值,"
@@ -71,9 +76,9 @@ def s7_point_collect_test(
     rock: int,
     slot: int,
     points: list[dict[str, Any]],
-    device_type: str = "S7TCP",
+    device_type: str = "S7-1200",
     port: int = 102,
-    tsap_conn_type: str = "PG",
+    tsap_conn_type: str | None = None,
 ) -> dict[str, Any]:
     return api_s7_point_collect_test(
         project_key,
@@ -101,16 +106,26 @@ def s7_connect_scan(project_key: str, ip: str) -> dict[str, Any]:
     return api_s7_connect_scan(project_key, ip=ip)
 
 
+def _resolve_collector_s7_tsap_conn_type(device_type: int, tsap_conn_type: str | None) -> str:
+    normalized_tsap = str(tsap_conn_type or "").strip().upper()
+    if normalized_tsap:
+        return normalized_tsap
+    if int(device_type) == 3:
+        return "OP"
+    return "PG"
+
+
 @mcp.tool(
     name="collector.s7_device_create",
     description=(
         "汇采-创建 S7 设备。调用 {data_collector_base_url}/api/collector/device,"
         "与 Modbus 创建设备共用地址,但请求体固定 type=s7,入参使用 S7 字段。"
         "创建设备必须传 name 名称、ip IP 地址、rock 机架号(轨道号)、slot 槽号;"
-        "port 默认 102,device_type 默认 1,tsap_conn_type 默认 PG。"
+        "port 默认 102,device_type 默认 1。tsap_conn_type 可选 PG、OP、BASIC;"
+        "未传时 device_type=1 使用 PG,device_type=3 时使用 OP。"
         "默认参数: is_persistent=false, "
         "device_group_id=0, timeout=3, alarm_interval=90, collect_interval=5。"
-        "device_type: 1=S7-1200, 2=S7-1500, 3=S7 Smart 200。"
+        "device_type: 1=S7-1200, 2=S7-1500, 3=S7-Smart200。"
         "tsap_conn_type 可用 PG、OP、BASIC。响应透传上游 JSON,state=0 表示业务成功。"
     ),
 )
@@ -122,7 +137,7 @@ def collector_s7_device_create(
     slot: int,
     port: int = 102,
     device_type: int = 1,
-    tsap_conn_type: str = "PG",
+    tsap_conn_type: str | None = None,
     is_persistent: bool = False,
     device_group_id: int = 0,
     timeout: int = 3,
@@ -138,7 +153,7 @@ def collector_s7_device_create(
             "slot": slot,
             "port": port,
             "device_type": device_type,
-            "tsap_conn_type": tsap_conn_type,
+            "tsap_conn_type": _resolve_collector_s7_tsap_conn_type(device_type, tsap_conn_type),
             "is_persistent": is_persistent,
             "device_group_id": device_group_id,
             "timeout": timeout,
@@ -153,10 +168,11 @@ def collector_s7_device_create(
     description=(
         "汇采-编辑 S7 设备。调用 {data_collector_base_url}/api/collector/s7/device/update。"
         "必须传 ori_id 原设备 id、name、ip、rock、slot;port 默认 102,device_type 默认 1,"
-        "tsap_conn_type 默认 PG。编辑前设备不能处于已连接状态;若已连接,请先调用 "
+        "tsap_conn_type 可选 PG、OP、BASIC;未传时 device_type=1 使用 PG,device_type=3 时使用 OP。"
+        "编辑前设备不能处于已连接状态;若已连接,请先调用 "
         "collector.device_disconnect,并传 device_type=s7。该接口是全量更新语义。"
         "默认参数: is_persistent=false, device_group_id=0, timeout=3, alarm_interval=90, "
-        "collect_interval=5。device_type: 1=S7-1200, 2=S7-1500, 3=S7 Smart 200。"
+        "collect_interval=5。device_type: 1=S7-1200, 2=S7-1500, 3=S7-Smart200。"
         "tsap_conn_type 可用 PG、OP、BASIC。响应透传上游 JSON,state=0 表示业务成功。"
     ),
 )
@@ -169,7 +185,7 @@ def collector_s7_device_edit(
     slot: int,
     port: int = 102,
     device_type: int = 1,
-    tsap_conn_type: str = "PG",
+    tsap_conn_type: str | None = None,
     is_persistent: bool = False,
     device_group_id: int = 0,
     timeout: int = 3,
@@ -186,7 +202,7 @@ def collector_s7_device_edit(
             "slot": slot,
             "port": port,
             "device_type": device_type,
-            "tsap_conn_type": tsap_conn_type,
+            "tsap_conn_type": _resolve_collector_s7_tsap_conn_type(device_type, tsap_conn_type),
             "is_persistent": is_persistent,
             "device_group_id": device_group_id,
             "timeout": timeout,

+ 24 - 20
docs/接口汇总.md

@@ -575,12 +575,14 @@ S7 网关接口通过 HTTP 连接 Siemens S7 TCP 设备,支持连接组合扫
 
 | 字段 | 类型 | 必填 | 默认值 | 允许值或范围 | 含义 |
 |---|---|---:|---|---|---|
-| `device_type` | string | 否 | `S7TCP` | 只能是 `S7TCP` | 设备协议类型。 |
+| `device_type` | string | 否 | `S7-1200` | `S7-1200`,`S7-Smart200` | 设备协议类型。 |
 | `ip` | string | 是 | 无 | 合法 IPv4 或 IPv6 地址 | S7 设备 IP 地址。 |
 | `port` | integer | 否 | `102` | `1..65535` | S7 TCP 端口,通常固定为 `102`。 |
 | `rock` | integer | 是 | 无 | `0..31` | PLC 机架号。字段名沿用汇采 S7 设备接口。 |
 | `slot` | integer | 是 | 无 | `0..31` | PLC 槽号。 |
-| `tsap_conn_type` | string | 否 | `PG` | `PG`、`OP`、`BASIC` | Snap7 连接类型。大小写不敏感,服务内部统一转为大写。 |
+| `tsap_conn_type` | string | 否 | `S7-1200` 使用 `PG`;`S7-Smart200` 使用 `OP` | `PG`、`OP`、`BASIC` | Snap7 连接类型。大小写不敏感,服务内部统一转为大写。 |
+
+约定:当 `device_type=S7-Smart200` 时,`tsap_conn_type` 使用 `OP`。如果请求中显式传入 `tsap_conn_type`,以请求值为准;未传时按 `device_type` 选择默认值。
 
 连接类型映射:
 
@@ -594,7 +596,7 @@ S7 网关接口通过 HTTP 连接 Siemens S7 TCP 设备,支持连接组合扫
 
 | 字段 | 适用位置 | 必填 | 默认值 | 允许值或范围 | 含义 |
 |---|---|---:|---|---|---|
-| `area` | `read`、`points[]` | 是 | 无 | `DB`、`M`、`I`、`Q` | 读取区域。 |
+| `area` | `read`、`points[]` | 是 | 无 | `DB`、`M`、`I`、`Q`、`V(S7-Smart200)` | 读取区域。 |
 | `db` | `read`、`points[]` | `area=DB` 时必填 | `0` | `>=0` | DB 块号。`area=DB` 时必须大于 `0`。 |
 | `start` | `read`、`points[]` | 是 | 无 | `>=0` | 起始字节偏移。 |
 | `size` | `read` | 是 | 无 | `1..65535` | 原始读取的字节数。 |
@@ -609,6 +611,7 @@ S7 网关接口通过 HTTP 连接 Siemens S7 TCP 设备,支持连接组合扫
 | `M` | Merker 标志位区。 |
 | `I` | 输入区 Inputs。 |
 | `Q` | 输出区 Outputs。 |
+| `V` | V 存储区,仅 `S7-Smart200` 支持。 |
 
 ### S7 communication 格式
 
@@ -653,7 +656,7 @@ Rx:S7_DISCONNECTED
 
 ```json
 {
-  "device_type": "S7TCP",
+  "device_type": "S7-1200",
   "ip": "192.168.1.10",
   "port": 102,
   "rock": 0,
@@ -678,7 +681,7 @@ Rx:S7_DISCONNECTED
   "msg": "success",
   "data": {
     "device": {
-      "device_type": "S7TCP",
+      "device_type": "S7-1200",
       "ip": "192.168.1.10",
       "port": 102,
       "rock": 0,
@@ -705,7 +708,7 @@ Rx:S7_DISCONNECTED
   "msg": "TCP : Unreachable peer",
   "data": {
     "device": {
-      "device_type": "S7TCP",
+      "device_type": "S7-1200",
       "ip": "192.168.1.10",
       "port": 102,
       "rock": 0,
@@ -726,7 +729,7 @@ Rx:S7_DISCONNECTED
 curl -X POST http://127.0.0.1:8000/api/dc-gateway/s7/read \
   -H "Content-Type: application/json" \
   -d '{
-    "device_type": "S7TCP",
+    "device_type": "S7-1200",
     "ip": "192.168.1.10",
     "port": 102,
     "rock": 0,
@@ -757,7 +760,7 @@ curl -X POST http://127.0.0.1:8000/api/dc-gateway/s7/read \
 
 ```json
 {
-  "device_type": "S7TCP",
+  "device_type": "S7-1200",
   "ip": "192.168.1.10",
   "port": 102,
   "rock": 0,
@@ -805,7 +808,7 @@ S7 多字节数值按大端字节序解析。
 
 | 规则 | 说明 |
 |---|---|
-| `area` 必须合法 | 只能是 `DB`、`M`、`I`、`Q`,输入会转为大写。 |
+| `area` 必须合法 | 支持 `DB`、`M`、`I`、`Q`,`S7-Smart200` 还支持 `V`,输入会转为大写。 |
 | DB 区必须传有效 DB 号 | 当 `area=DB` 时,`db` 必须大于 `0`。 |
 | `type` 必须合法 | 只能使用上表列出的 S7 网关点位类型。 |
 | `bit` 只支持布尔点 | `type=bool` 时可传 `bit`,非布尔点传 `bit` 会校验失败。 |
@@ -819,7 +822,7 @@ S7 多字节数值按大端字节序解析。
   "msg": "success",
   "data": {
     "device": {
-      "device_type": "S7TCP",
+      "device_type": "S7-1200",
       "ip": "192.168.1.10",
       "port": 102,
       "rock": 0,
@@ -872,7 +875,7 @@ S7 多字节数值按大端字节序解析。
 curl -X POST http://127.0.0.1:8000/api/dc-gateway/s7/read_points \
   -H "Content-Type: application/json" \
   -d '{
-    "device_type": "S7TCP",
+    "device_type": "S7-1200",
     "ip": "192.168.1.10",
     "port": 102,
     "rock": 0,
@@ -936,7 +939,7 @@ curl -X POST http://127.0.0.1:8000/api/dc-gateway/s7/read_points \
   "msg": "success",
   "data": {
     "device": {
-      "device_type": "S7TCP",
+      "device_type": "S7-1200",
       "ip": "192.168.1.10",
       "port": 102
     },
@@ -964,7 +967,7 @@ curl -X POST http://127.0.0.1:8000/api/dc-gateway/s7/read_points \
   "msg": "success",
   "data": {
     "device": {
-      "device_type": "S7TCP",
+      "device_type": "S7-1200",
       "ip": "192.168.1.10",
       "port": 102
     },
@@ -1015,12 +1018,13 @@ AI 构造 `/modbus/read_points` 点位时应按以下步骤:
 
 AI 构造 `/s7/read_points` 点位时应按以下步骤:
 
-1. 如果不确定 S7 连接参数,先调用 `/s7/connect_scan` 获取可用的 `rock`、`slot`、`tsap_conn_type`。
-2. 根据设备点表选择 `area`,DB 区传 `area=DB` 且填写大于 `0` 的 `db`。
-3. 将 S7 地址换算为字节偏移,填入 `start`。
-4. 根据点位数据类型设置 `type`,S7 网关类型与汇采 S7 点位类型不完全一致:网关 8 位无符号类型为 `byte`,汇采创建点位常用 `uint8`。
-5. 如果读取布尔位,设置 `type=bool` 并填写 `bit=0..7`;非布尔点不要传 `bit`。
-6. 发送请求后检查响应体 `code`,不要只检查 HTTP 状态码。
+1. 先确定 `device_type`,默认使用 `S7-1200`;如果是 `S7-Smart200`,传 `device_type=S7-Smart200` 且 `tsap_conn_type=OP`。
+2. 如果不确定 S7 连接参数,先调用 `/s7/connect_scan` 获取可用的 `rock`、`slot`、`tsap_conn_type`。
+3. 根据设备点表选择 `area`,DB 区传 `area=DB` 且填写大于 `0` 的 `db`;`S7-Smart200` 的 V 区传 `area=V`。
+4. 将 S7 地址换算为字节偏移,填入 `start`。
+5. 根据点位数据类型设置 `type`,S7 网关类型与汇采 S7 点位类型不完全一致:网关 8 位无符号类型为 `byte`,汇采创建点位常用 `uint8`。
+6. 如果读取布尔位,设置 `type=bool` 并填写 `bit=0..7`;非布尔点不要传 `bit`。
+7. 发送请求后检查响应体 `code`,不要只检查 HTTP 状态码。
 
 
 
@@ -1657,7 +1661,7 @@ http://192.168.75.110:32080
 |---:|---|
 | `1` | S7-1200 |
 | `2` | S7-1500 |
-| `3` | S7 Smart 200 |
+| `3` | S7-Smart200 |
 
 - S7 TSAP 连接类型 `tsap_conn_type`
 

+ 23 - 2
tests/test_gateway_api.py

@@ -83,7 +83,7 @@ class GatewayApiTests(unittest.TestCase):
             "POST",
             "http://gateway.test/api/dc-gateway/s7/read",
             json_payload={
-                "device_type": "S7TCP",
+                "device_type": "S7-1200",
                 "ip": "192.168.1.10",
                 "port": 102,
                 "rock": 0,
@@ -117,7 +117,7 @@ class GatewayApiTests(unittest.TestCase):
             "POST",
             "http://gateway.test/api/dc-gateway/s7/read_points",
             json_payload={
-                "device_type": "S7TCP",
+                "device_type": "S7-1200",
                 "ip": "192.168.1.10",
                 "port": 1102,
                 "rock": 0,
@@ -127,6 +127,27 @@ class GatewayApiTests(unittest.TestCase):
             },
         )
 
+    def test_s7_point_collect_test_defaults_smart200_tsap_to_op(self) -> None:
+        with patch(
+            "data_collector_mcp.gateway_api.find_project_config",
+            return_value={"project_key": "dev-01", "base_url": "http://gateway.test"},
+        ), patch(
+            "data_collector_mcp.gateway_api.request_json",
+            return_value={"code": 0},
+        ) as request_json:
+            gateway_api.s7_point_collect_test(
+                "dev-01",
+                ip="192.168.1.10",
+                rock=0,
+                slot=1,
+                device_type="S7-Smart200",
+                points=[{"area": "V", "start": 0, "type": "int16"}],
+            )
+
+        payload = request_json.call_args.kwargs["json_payload"]
+        self.assertEqual(payload["device_type"], "S7-Smart200")
+        self.assertEqual(payload["tsap_conn_type"], "OP")
+
     def test_s7_connect_scan_posts_ip_only(self) -> None:
         response = {"code": 0, "msg": "success", "data": {"available": []}}
         with patch(

+ 3 - 3
tests/test_server_tools.py

@@ -366,9 +366,9 @@ class ServerToolTests(unittest.TestCase):
             rock=0,
             slot=1,
             read={"area": "DB", "db": 1, "start": 0, "size": 4},
-            device_type="S7TCP",
+            device_type="S7-1200",
             port=102,
-            tsap_conn_type="PG",
+            tsap_conn_type=None,
         )
 
         with patch("data_collector_mcp.s7_server.api_s7_point_collect_test", return_value={"code": 0}) as point_test:
@@ -390,7 +390,7 @@ class ServerToolTests(unittest.TestCase):
             rock=0,
             slot=1,
             points=[{"area": "M", "start": 0, "type": "bool"}],
-            device_type="S7TCP",
+            device_type="S7-1200",
             port=1102,
             tsap_conn_type="BASIC",
         )