80 lines
3.3 KiB
Python
80 lines
3.3 KiB
Python
# -*- 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
|