# -*- coding: utf-8 -*- """西门子 S7-1200 驱动 —— 参数化适配(issue #24 落点)。 连接参数(ip / rack / slot / db / offset 映射)全部来自模板配置, 点位到 DB 地址的映射通过点位字典 CSV 的 opcNode 列携带 (S7 场景下约定格式 `DB{db}.{byte}.{bit}`,如 DB100.0.0)。 依赖 `python-snap7`,未安装时给出明确提示。 """ from __future__ import annotations import re from typing import Dict, List, Optional from point_dict.loader import Point from .base import Driver, SampleValue _S7_NODE_RE = re.compile(r"^DB(\d+)\.(\d+)(?:\.(\d+))?$") class S7Driver(Driver): """西门子 S7-1200 只读采集驱动(参数化适配)。""" protocol = "s7" def __init__(self, config: Optional[dict] = None): super().__init__(config) self.ip: str = self.config.get("ip", "") self.rack: int = int(self.config.get("rack", 0)) self.slot: int = int(self.config.get("slot", 1)) self._client = None def connect(self) -> None: try: import snap7 # type: ignore except ImportError as exc: # pragma: no cover - 依赖缺失路径 raise ConnectionError( "S7 驱动依赖库未安装:请 `pip install python-snap7`" ) from exc if not self.ip: raise ConnectionError("S7 驱动缺少配置: drivers.s7.ip") self._client = snap7.client.Client() self._client.connect(self.ip, self.rack, self.slot) def read_points(self, points: List[Point]) -> Dict[str, SampleValue]: if self._client is None: raise ConnectionError("S7 客户端未连接,请先 connect()") result: Dict[str, SampleValue] = {} for p in points: match = _S7_NODE_RE.match(p.opc_node or "") if not match: continue # 非 S7 点位由其它驱动采集 db, byte_, bit = int(match.group(1)), int(match.group(2)), match.group(3) try: if bit is not None: result[p.point_id] = self._client.read_area( 0x84, db, byte_ * 8 + int(bit), 1 )[0] else: result[p.point_id] = self._client.read_area(0x84, db, byte_, 4)[0] except Exception: result[p.point_id] = None # 单点失败不中断整批 return result def close(self) -> None: if self._client is not None: try: self._client.disconnect() finally: self._client = None