Files
yunmei f49c0920d4 feat: 完成 issue #3 边缘采集网关模板化封装
- 点位字典 CSV schema/加载/自动校验(缺失字段/量纲/重复点号,PRD 5.1)
- 协议可插拔只读驱动:OPC UA(S7-1200 适配)/S7/Modbus/称重/能源/模拟
- 周期采集引擎:只读+背压保护+健康度指标(丢失率/P99/可用性)
- Kafka 流式上行 + 本地 spool 断点续传(丢失率≤0.02% 保障)
- 模板配置外置(gateway.yaml + 点位字典 CSV),换行业零改码
- 18 个单元测试全绿;端到端运行 SLA 达标
2026-08-04 15:32:16 +08:00

80 lines
3.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""Modbus 驱动 —— PLC / 称重仪表等 RTU/TCP 从站参数化适配。
连接参数(mode / host / port / slave_id / register_map)来自模板配置;
寄存器映射约定写入点位字典 opcNode 列,格式 `{type}:{addr}`,
type ∈ holding|input|coil|discrete,如 `holding:40001`。
依赖 `pymodbus`,未安装时给出明确提示。
"""
from __future__ import annotations
from typing import Dict, List, Optional
from point_dict.loader import Point
from .base import Driver, SampleValue
_REG_MAP = {"holding": 3, "input": 4, "coil": 1, "discrete": 2}
class ModbusDriver(Driver):
"""Modbus 只读采集驱动(参数化适配)。"""
protocol = "modbus"
def __init__(self, config: Optional[dict] = None):
super().__init__(config)
self.mode: str = self.config.get("mode", "tcp") # tcp | rtu
self.host: str = self.config.get("host", "")
self.port: int = int(self.config.get("port", 502))
self.slave_id: int = int(self.config.get("slave_id", 1))
self._client = None
def connect(self) -> None:
try:
from pymodbus.client import ModbusTcpClient, ModbusSerialClient # type: ignore
except ImportError as exc: # pragma: no cover - 依赖缺失路径
raise ConnectionError(
"Modbus 驱动依赖库未安装:请 `pip install pymodbus`"
) from exc
if self.mode == "tcp":
if not self.host:
raise ConnectionError("Modbus 驱动缺少配置: drivers.modbus.host")
self._client = ModbusTcpClient(self.host, port=self.port)
else:
self._client = ModbusSerialClient(
port=self.config.get("port", "COM1"),
baudrate=self.config.get("baudrate", 9600),
)
if not self._client.connect():
raise ConnectionError(f"Modbus 连接失败: {self.host or self.config.get('port')}")
def read_points(self, points: List[Point]) -> Dict[str, SampleValue]:
if self._client is None:
raise ConnectionError("Modbus 客户端未连接,请先 connect()")
result: Dict[str, SampleValue] = {}
for p in points:
node = (p.opc_node or "").split(":")
if len(node) != 2 or node[0] not in _REG_MAP:
continue
reg_type, addr = node[0], int(node[1])
try:
if reg_type == "coil":
resp = self._client.read_coils(addr, count=1, slave=self.slave_id)
elif reg_type == "discrete":
resp = self._client.read_discrete_inputs(addr, count=1, slave=self.slave_id)
else:
resp = self._client.read_holding_registers(addr, count=1, slave=self.slave_id) \
if reg_type == "holding" else \
self._client.read_input_registers(addr, count=1, slave=self.slave_id)
result[p.point_id] = resp.registers[0] if hasattr(resp, "registers") and resp.registers else None
except Exception:
result[p.point_id] = None
return result
def close(self) -> None:
if self._client is not None:
try:
self._client.close()
finally:
self._client = None