Files

114 lines
4.5 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 -*-
"""和利时 DCS OPC 接口驱动 —— 参数化适配(issue #25)。
和利时 DCS(HOLLiAS-M / MACS 系列)对外数据接口常见两种形态,全部参数化:
- `mode: ua` —— OPC UA 接口(推荐):endpoint / security / username / password
均来自模板配置(gateway.yaml 的 drivers.hollysys 段),委托 OpcUaDriver
(含用户认证参数化,见 opcua_driver.py);
- `mode: da` —— OPC DA 传统 COM/DCOM 接口:host(DCOM 主机)/ prog_id
(如 "Hollysys.OPCServer")/ cls_id 参数化,点位 opcNode 列写 OPC Item 路径
(如 "CLF.Temp")。
安全约束(PRD 9 章):仅暴露只读 read_points(),无任何写/控制方法。
依赖(未安装时给出明确提示,不静默失败):
- ua 模式:`opcua`(同步)或 `asyncua`;
- da 模式:Windows 上 `OpenOPC`(依赖 pywin32)。
"""
from __future__ import annotations
from typing import Dict, List, Optional
from point_dict.loader import Point
from .base import Driver, SampleValue
from .opcua_driver import OpcUaDriver
class _HollysysDaDriver(Driver):
"""OPC DA(COM/DCOM)只读采集实现 —— 和利时传统接口。"""
protocol = "hollysys-da"
def __init__(self, config: Optional[dict] = None):
super().__init__(config)
self.host: str = self.config.get("host", "")
self.prog_id: str = self.config.get("prog_id", "Hollysys.OPCServer")
self.cls_id: str = self.config.get("cls_id", "")
self._client = None
def connect(self) -> None:
try:
import OpenOPC # type: ignore
except ImportError as exc: # pragma: no cover - 依赖缺失路径
raise ConnectionError(
"OPC DA 驱动依赖库未安装:请 `pip install OpenOPC`(Windows + pywin32)"
) from exc
if not self.host:
raise ConnectionError("OPC DA 驱动缺少配置: drivers.hollysys.host")
self._client = OpenOPC.client()
self._client.connect(self.prog_id, self.host)
# 可选:按 CLSID 连接(部分和利时 OPC 服务器无 ProgID 注册时使用)
if self.cls_id:
self._client.connect(self.cls_id, self.host)
def read_points(self, points: List[Point]) -> Dict[str, SampleValue]:
if self._client is None:
raise ConnectionError("OPC DA 客户端未连接,请先 connect()")
result: Dict[str, SampleValue] = {}
for p in points:
item = p.opc_node
if not item:
continue # 无 OPC Item 的点由其它驱动采集
try:
value = self._client.read(item)
# OpenOPC.read 返回 (value, quality, timestamp) 或裸值
if isinstance(value, (tuple, list)) and value:
value = value[0]
result[p.point_id] = value
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
class HollysysDcsDriver(Driver):
"""和利时 DCS OPC 接口只读采集驱动(参数化适配,issue #25)。
按 ``mode`` 参数路由到底层实现(与 energy_driver 的委托模式一致):
- ua:OPC UA(委托 :class:`OpcUaDriver`,支持 endpoint/security/认证);
- da:OPC DA COM 接口(委托 :class:`_HollysysDaDriver`)。
"""
protocol = "hollysys"
def __init__(self, config: Optional[dict] = None):
super().__init__(config)
self.mode: str = self.config.get("mode", "ua") # ua | da
self._delegate: Optional[Driver] = None
def connect(self) -> None:
if self.mode == "da":
self._delegate = _HollysysDaDriver(self.config)
else:
# ua 默认:和利时 OPC UA 服务,支持用户认证参数化
self._delegate = OpcUaDriver(self.config)
self._delegate.connect()
def read_points(self, points: List[Point]) -> Dict[str, SampleValue]:
if self._delegate is None:
raise ConnectionError("和利时驱动未连接,请先 connect()")
return self._delegate.read_points(points)
def close(self) -> None:
if self._delegate is not None:
try:
self._delegate.close()
finally:
self._delegate = None