pyinsts 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyinsts-0.0.3/LICENSE +21 -0
- pyinsts-0.0.3/PKG-INFO +43 -0
- pyinsts-0.0.3/README.md +29 -0
- pyinsts-0.0.3/pyproject.toml +21 -0
- pyinsts-0.0.3/setup.cfg +4 -0
- pyinsts-0.0.3/src/__init__.py +0 -0
- pyinsts-0.0.3/src/_version.py +15 -0
- pyinsts-0.0.3/src/pyinsts/__init__.py +0 -0
- pyinsts-0.0.3/src/pyinsts/common/__init__.py +3 -0
- pyinsts-0.0.3/src/pyinsts/common/log_data.py +96 -0
- pyinsts-0.0.3/src/pyinsts/libs/__init__.py +11 -0
- pyinsts-0.0.3/src/pyinsts/libs/baseinstrument.py +232 -0
- pyinsts-0.0.3/src/pyinsts/libs/data/__init__.py +3 -0
- pyinsts-0.0.3/src/pyinsts/libs/data/load_config.py +47 -0
- pyinsts-0.0.3/src/pyinsts/libs/keysight/N9020B.py +359 -0
- pyinsts-0.0.3/src/pyinsts/libs/keysight/__init__.py +3 -0
- pyinsts-0.0.3/src/pyinsts/libs/rs/Fswp.py +494 -0
- pyinsts-0.0.3/src/pyinsts/libs/rs/__init__.py +3 -0
- pyinsts-0.0.3/src/pyinsts.egg-info/PKG-INFO +43 -0
- pyinsts-0.0.3/src/pyinsts.egg-info/SOURCES.txt +21 -0
- pyinsts-0.0.3/src/pyinsts.egg-info/dependency_links.txt +1 -0
- pyinsts-0.0.3/src/pyinsts.egg-info/requires.txt +4 -0
- pyinsts-0.0.3/src/pyinsts.egg-info/top_level.txt +3 -0
pyinsts-0.0.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2024] [zhangxp93]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyinsts-0.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyinsts
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: PyVISA仪器控制库,支持Keysight和Rohde&Schwarz仪器
|
|
5
|
+
Author-email: zhangxp93 <937097345@qq.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: colorlog>=6.9.0
|
|
10
|
+
Requires-Dist: pyvisa>=1.14.1
|
|
11
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
12
|
+
Requires-Dist: versioningit>=3.3.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
[**简体中文**](README.md) | [**English**](README_en.md)
|
|
16
|
+
|
|
17
|
+
<div align="center">
|
|
18
|
+
<h1>
|
|
19
|
+
<strong>PyVISA仪器控制</strong>
|
|
20
|
+
</h1>
|
|
21
|
+
<h5>
|
|
22
|
+
<strong>适用于 Windows、MacOs、Linux</strong><br>
|
|
23
|
+
</h5>
|
|
24
|
+
</div><br>
|
|
25
|
+
|
|
26
|
+
___
|
|
27
|
+
|
|
28
|
+
- **基于pyvisa库对仪器进行控制,支持USB、GPIB、TCP/IP等连接方式。**
|
|
29
|
+
- **Rohde & Schwarz SCPI**
|
|
30
|
+
- **KeySight SCPI**
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## 必要依赖库安装
|
|
34
|
+
PyVISA 是一个用于控制和通信仪器的 Python 库,使用下面的代码安装:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
pip install pyvisa
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## 🏷[更新日志](CHANGELOG.md)
|
|
43
|
+
|
pyinsts-0.0.3/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[**简体中文**](README.md) | [**English**](README_en.md)
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<h1>
|
|
5
|
+
<strong>PyVISA仪器控制</strong>
|
|
6
|
+
</h1>
|
|
7
|
+
<h5>
|
|
8
|
+
<strong>适用于 Windows、MacOs、Linux</strong><br>
|
|
9
|
+
</h5>
|
|
10
|
+
</div><br>
|
|
11
|
+
|
|
12
|
+
___
|
|
13
|
+
|
|
14
|
+
- **基于pyvisa库对仪器进行控制,支持USB、GPIB、TCP/IP等连接方式。**
|
|
15
|
+
- **Rohde & Schwarz SCPI**
|
|
16
|
+
- **KeySight SCPI**
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## 必要依赖库安装
|
|
20
|
+
PyVISA 是一个用于控制和通信仪器的 Python 库,使用下面的代码安装:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
pip install pyvisa
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## 🏷[更新日志](CHANGELOG.md)
|
|
29
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyinsts"
|
|
3
|
+
version = "0.0.3"
|
|
4
|
+
description = "PyVISA仪器控制库,支持Keysight和Rohde&Schwarz仪器"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "zhangxp93", email = "937097345@qq.com" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"colorlog>=6.9.0",
|
|
12
|
+
"pyvisa>=1.14.1",
|
|
13
|
+
"pyyaml>=6.0.3",
|
|
14
|
+
"versioningit>=3.3.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
[tool.uv.workspace]
|
|
19
|
+
members = [
|
|
20
|
+
"test_app",
|
|
21
|
+
]
|
pyinsts-0.0.3/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
def _get_version() -> str:
|
|
2
|
+
|
|
3
|
+
from importlib.resources import files
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import versioningit
|
|
7
|
+
|
|
8
|
+
module_path = files("pyinsts")
|
|
9
|
+
if isinstance(module_path, Path):
|
|
10
|
+
return versioningit.get_version(project_dir=Path(module_path).parent.parent)
|
|
11
|
+
else:
|
|
12
|
+
return "0.0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__version__ = "0.0.3"
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
日志处理
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from logging.handlers import RotatingFileHandler
|
|
7
|
+
import colorlog # 导入 colorlog
|
|
8
|
+
def setup_logging(log_file=None, log_level=logging.INFO, max_bytes=1 * 1024 * 1024, backup_count=5):
|
|
9
|
+
"""
|
|
10
|
+
配置日志记录,包括日志轮换和控制台输出,支持彩色日志
|
|
11
|
+
:param log_file: 日志文件路径,如果为None则使用默认路径
|
|
12
|
+
:param log_level: 日志等级
|
|
13
|
+
:param max_bytes: 单个日志文件的最大字节数,默认为1MB
|
|
14
|
+
:param backup_count: 保留的日志文件备份数
|
|
15
|
+
"""
|
|
16
|
+
if log_file is None:
|
|
17
|
+
log_file = os.getenv('LOG_FILE_PATH', 'docs/log/test.log')
|
|
18
|
+
|
|
19
|
+
# 确保日志文件夹存在
|
|
20
|
+
log_dir = os.path.dirname(log_file)
|
|
21
|
+
if not os.path.exists(log_dir):
|
|
22
|
+
os.makedirs(log_dir)
|
|
23
|
+
|
|
24
|
+
# 创建根记录器
|
|
25
|
+
logger = logging.getLogger()
|
|
26
|
+
logger.setLevel(log_level)
|
|
27
|
+
|
|
28
|
+
# 清除已有的处理器,避免重复添加
|
|
29
|
+
if logger.hasHandlers():
|
|
30
|
+
logger.handlers.clear()
|
|
31
|
+
|
|
32
|
+
# 创建文件处理器,支持日志轮换
|
|
33
|
+
file_handler = RotatingFileHandler(log_file, mode='w', maxBytes=max_bytes, backupCount=backup_count)
|
|
34
|
+
file_handler.setLevel(log_level)
|
|
35
|
+
|
|
36
|
+
# 创建控制台处理器,日志输出到控制台
|
|
37
|
+
console_handler = logging.StreamHandler()
|
|
38
|
+
console_handler.setLevel(log_level)
|
|
39
|
+
|
|
40
|
+
# 定义日志格式
|
|
41
|
+
log_format = '%(asctime)s - %(levelname)-8s - %(message)-60s - [%(filename)-15s:%(lineno)-4d]'
|
|
42
|
+
color_format = "%(log_color)s" + log_format
|
|
43
|
+
|
|
44
|
+
# 设置文件处理器的格式
|
|
45
|
+
file_formatter = logging.Formatter(log_format)
|
|
46
|
+
file_handler.setFormatter(file_formatter)
|
|
47
|
+
|
|
48
|
+
# 设置控制台处理器的彩色格式
|
|
49
|
+
color_formatter = colorlog.ColoredFormatter(
|
|
50
|
+
color_format,
|
|
51
|
+
log_colors={
|
|
52
|
+
'DEBUG': 'cyan',
|
|
53
|
+
'INFO': 'green',
|
|
54
|
+
'WARNING': 'yellow',
|
|
55
|
+
'ERROR': 'red',
|
|
56
|
+
'CRITICAL': 'bold_red',
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
console_handler.setFormatter(color_formatter)
|
|
60
|
+
|
|
61
|
+
# 将处理器添加到记录器中
|
|
62
|
+
logger.addHandler(file_handler)
|
|
63
|
+
logger.addHandler(console_handler)
|
|
64
|
+
|
|
65
|
+
# 添加日志分隔符
|
|
66
|
+
logger.info("=" * 60)
|
|
67
|
+
logger.info("新日志开始".center(60, ' '))
|
|
68
|
+
logger.info("=" * 60)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def log_data(data):
|
|
72
|
+
"""
|
|
73
|
+
记录数据的日志
|
|
74
|
+
"""
|
|
75
|
+
logging.info(f"数据记录: {data}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# 通用异常处理装饰器
|
|
79
|
+
def log_exceptions(func):
|
|
80
|
+
def wrapper(*args, **kwargs):
|
|
81
|
+
try:
|
|
82
|
+
return func(*args, **kwargs)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logging.error(f"{func.__name__} 执行时发生错误", exc_info=True)
|
|
85
|
+
raise e
|
|
86
|
+
|
|
87
|
+
return wrapper
|
|
88
|
+
|
|
89
|
+
# 示例:调用日志设置
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
setup_logging(log_file='/Users/zhangxianping/Desktop/pythonProject/SiCore_ChipTest/docs/log//test.log', log_level=logging.DEBUG)
|
|
92
|
+
log_data("这是一个测试数据记录")
|
|
93
|
+
logging.debug("这是一条调试信息")
|
|
94
|
+
logging.warning("这是一条警告信息")
|
|
95
|
+
logging.error("这是一条错误信息")
|
|
96
|
+
logging.critical("这是一条严重错误信息")
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
import pyvisa
|
|
5
|
+
from typing import Callable, Optional
|
|
6
|
+
import os
|
|
7
|
+
import inspect
|
|
8
|
+
from functools import wraps
|
|
9
|
+
|
|
10
|
+
from pyvisa import VisaIOError
|
|
11
|
+
|
|
12
|
+
from src.pyinsts.libs.data import load_config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_instrument_error(func: Callable) -> Callable:
|
|
16
|
+
"""仪器操作错误处理装饰器"""
|
|
17
|
+
@wraps(func)
|
|
18
|
+
def wrapper(*args, **kwargs):
|
|
19
|
+
try:
|
|
20
|
+
return func(*args, **kwargs)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
logging.error(f"{args[0].model} {func.__name__}失败: {e}")
|
|
23
|
+
raise
|
|
24
|
+
|
|
25
|
+
return wrapper
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BaseInstrument:
|
|
29
|
+
"""基础仪器控制类"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, address: Optional[str] = None, config_path: str = "config.yaml",
|
|
32
|
+
model: str = "E5052B"):
|
|
33
|
+
"""初始化仪器连接
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
address: 仪器地址,例如:"USB0::0x0957::0x0E09::MY47402409::0::INSTR"
|
|
37
|
+
config_path: 配置文件路径
|
|
38
|
+
model: 仪器型号
|
|
39
|
+
"""
|
|
40
|
+
# 设置基本属性
|
|
41
|
+
self.model = model
|
|
42
|
+
self.address = self._get_instrument_address(address, config_path)
|
|
43
|
+
|
|
44
|
+
# 设置默认超时参数
|
|
45
|
+
|
|
46
|
+
self.opc_timeout = 100 # 默认OPC超时时间100秒
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
self.opc_poll_interval = 0.01 # 默认OPC轮询间隔0.01秒
|
|
50
|
+
|
|
51
|
+
# 连接仪器
|
|
52
|
+
self.idn = self._connect_instrument()
|
|
53
|
+
|
|
54
|
+
# def __del__(self):
|
|
55
|
+
# """
|
|
56
|
+
# del的调用时机不确定,依赖于Python的垃圾回收机制。
|
|
57
|
+
# :return:
|
|
58
|
+
# """
|
|
59
|
+
# self.close()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_instrument_address(self, address: str, config_path: str) -> str:
|
|
63
|
+
"""获取仪器地址
|
|
64
|
+
|
|
65
|
+
优先使用传入的地址,否则从配置文件读取
|
|
66
|
+
支持.yaml和.json格式的配置文件
|
|
67
|
+
"""
|
|
68
|
+
if address:
|
|
69
|
+
return address
|
|
70
|
+
|
|
71
|
+
# 处理配置文件路径
|
|
72
|
+
if not os.path.isabs(config_path):
|
|
73
|
+
# 获取继承类的文件所在目录
|
|
74
|
+
derived_class_file = inspect.getmodule(self.__class__).__file__
|
|
75
|
+
derived_class_dir = os.path.dirname(
|
|
76
|
+
os.path.abspath(derived_class_file))
|
|
77
|
+
config_path = os.path.join(derived_class_dir, config_path)
|
|
78
|
+
|
|
79
|
+
# 如果没有指定扩展名,先尝试.yaml,再尝试.json
|
|
80
|
+
if not os.path.splitext(config_path)[1]:
|
|
81
|
+
yaml_path = config_path + '.yaml'
|
|
82
|
+
json_path = config_path + '.json'
|
|
83
|
+
|
|
84
|
+
if os.path.exists(yaml_path):
|
|
85
|
+
config_path = yaml_path
|
|
86
|
+
elif os.path.exists(json_path):
|
|
87
|
+
config_path = json_path
|
|
88
|
+
|
|
89
|
+
config = load_config(config_path)
|
|
90
|
+
address = config.get("instruments", {}).get(self.model)
|
|
91
|
+
if not address:
|
|
92
|
+
raise ValueError(f"未找到{self.model}的仪器地址配置")
|
|
93
|
+
|
|
94
|
+
return address
|
|
95
|
+
|
|
96
|
+
@handle_instrument_error
|
|
97
|
+
def _connect_instrument(self) -> None:
|
|
98
|
+
"""建立仪器连接,返回仪器标识"""
|
|
99
|
+
max_retries = 3 # 最大重试次数
|
|
100
|
+
|
|
101
|
+
retry_count = 0
|
|
102
|
+
|
|
103
|
+
while retry_count < max_retries:
|
|
104
|
+
try:
|
|
105
|
+
print(f"型号:{self.model}, 地址:{self.address}")
|
|
106
|
+
self.rm = pyvisa.ResourceManager()
|
|
107
|
+
self.instrument = self.rm.open_resource(self.address)
|
|
108
|
+
|
|
109
|
+
self.instrument.write('*CLS')
|
|
110
|
+
logging.info('清除仪器寄存器信息')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# 验证连接
|
|
114
|
+
idn = self.instrument.query("*IDN?")
|
|
115
|
+
logging.info(f'仪器标识:{idn}, 成功连接{self.model}')
|
|
116
|
+
return idn
|
|
117
|
+
except(pyvisa.VisaIOError, pyvisa.VisaTypeError, TimeoutError):
|
|
118
|
+
retry_count += 1
|
|
119
|
+
if retry_count < max_retries:
|
|
120
|
+
logging.info(f"等待1秒后重试...")
|
|
121
|
+
time.sleep(1)
|
|
122
|
+
# 确保之前的连接被关闭
|
|
123
|
+
if hasattr(self, 'instrument'):
|
|
124
|
+
self.instrument.close()
|
|
125
|
+
if hasattr(self, 'rm'):
|
|
126
|
+
self.rm.close()
|
|
127
|
+
else:
|
|
128
|
+
logging.error(f"连接失败次数超过最大重试次数({max_retries})")
|
|
129
|
+
raise
|
|
130
|
+
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@handle_instrument_error
|
|
135
|
+
def write(self, command: str,
|
|
136
|
+
check_complete: bool = False) -> None:
|
|
137
|
+
"""写入命令
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
command: SCPI命令
|
|
141
|
+
check_complete: 是否等待命令完成
|
|
142
|
+
|
|
143
|
+
example:
|
|
144
|
+
self.write(f'SENSE1:FREQ:CENT 1GHz')
|
|
145
|
+
"""
|
|
146
|
+
self.instrument.timeout = 100000
|
|
147
|
+
self.instrument.write(command)
|
|
148
|
+
if check_complete:
|
|
149
|
+
self.wait_opc()
|
|
150
|
+
logging.debug(f"{self.model} 写入: {command}")
|
|
151
|
+
|
|
152
|
+
@handle_instrument_error
|
|
153
|
+
def query(self, command: str,
|
|
154
|
+
check_complete: bool = False) -> str:
|
|
155
|
+
"""查询命令
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
command: SCPI命令
|
|
159
|
+
check_complete: 是否等待命令完成
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
查询结果
|
|
163
|
+
|
|
164
|
+
example:
|
|
165
|
+
result = self.query("*IDN?")
|
|
166
|
+
print(result)
|
|
167
|
+
"""
|
|
168
|
+
self.instrument.timeout = 20000
|
|
169
|
+
try:
|
|
170
|
+
result = self.instrument.query(command)
|
|
171
|
+
if check_complete:
|
|
172
|
+
self.wait_opc()
|
|
173
|
+
logging.debug(f"{self.model} 查询: {command}, 结果: {result}")
|
|
174
|
+
return result
|
|
175
|
+
except VisaIOError as e:
|
|
176
|
+
logging.error(f"{self.model} 查询失败: {e}")
|
|
177
|
+
raise
|
|
178
|
+
|
|
179
|
+
@handle_instrument_error
|
|
180
|
+
def set_opc_timeout(self, timeout: float=0.1, poll_interval: float = None):
|
|
181
|
+
"""临时设置OPC超时时间
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
timeout: 新的超时时间(秒)
|
|
185
|
+
poll_interval: 新的轮询间隔(秒),可选
|
|
186
|
+
"""
|
|
187
|
+
self.opc_timeout = timeout
|
|
188
|
+
if poll_interval is not None:
|
|
189
|
+
self.opc_poll_interval = poll_interval
|
|
190
|
+
logging.info(f"{self.model} 设置超时时间: {timeout}秒, 轮询间隔: {poll_interval or self.opc_poll_interval}秒")
|
|
191
|
+
|
|
192
|
+
@handle_instrument_error
|
|
193
|
+
def wait_opc(self, timeout=None, opc_poll_interval=None):
|
|
194
|
+
"""等待OPC完成
|
|
195
|
+
Args:
|
|
196
|
+
timeout: 超时时间(秒),默认使用self.timeout
|
|
197
|
+
opc_poll_interval: 轮询间隔(秒),默认使用self.poll_interval
|
|
198
|
+
"""
|
|
199
|
+
self.opc_timeout = timeout or self.opc_timeout
|
|
200
|
+
try:
|
|
201
|
+
start_time = time.time()
|
|
202
|
+
logging.info(f"开始等待opc?")
|
|
203
|
+
|
|
204
|
+
while True:
|
|
205
|
+
try:
|
|
206
|
+
opc = self.instrument.query("*OPC?")
|
|
207
|
+
if opc.strip() == '1':
|
|
208
|
+
logging.info("opc运行完成")
|
|
209
|
+
break
|
|
210
|
+
except VisaIOError as e:
|
|
211
|
+
logging.warning(f"OPC查询失败: {str(e)}")
|
|
212
|
+
# break # 通信异常时终止
|
|
213
|
+
|
|
214
|
+
elapsed = time.time() - start_time
|
|
215
|
+
if elapsed >= self.opc_timeout:
|
|
216
|
+
logging.warning(f"运行超时(已耗时:{elapsed:.1f}秒)")
|
|
217
|
+
break
|
|
218
|
+
time.sleep(opc_poll_interval or self.opc_poll_interval)
|
|
219
|
+
end_time = time.time()
|
|
220
|
+
total_time = end_time - start_time
|
|
221
|
+
logging.info(f"总运行时间: {total_time:.2f}秒")
|
|
222
|
+
return total_time # 返回时间供外部使用
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logging.error(f"发生错误: {str(e)}")
|
|
225
|
+
return str(e)
|
|
226
|
+
|
|
227
|
+
@handle_instrument_error
|
|
228
|
+
def close(self) -> None:
|
|
229
|
+
"""关闭仪器连接"""
|
|
230
|
+
self.instrument.close()
|
|
231
|
+
logging.info(f"{self.model} 连接已关闭")
|
|
232
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import yaml
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_config(config_path="config.yaml"):
|
|
8
|
+
"""加载配置文件,支持YAML和JSON格式
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
config_path: 配置文件路径(.yaml或.json)
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
配置字典
|
|
15
|
+
"""
|
|
16
|
+
if not os.path.exists(config_path):
|
|
17
|
+
logging.error(f"配置文件未找到: {config_path}")
|
|
18
|
+
raise FileNotFoundError(f"配置文件未找到: {config_path}")
|
|
19
|
+
|
|
20
|
+
file_ext = os.path.splitext(config_path)[1].lower()
|
|
21
|
+
|
|
22
|
+
with open(config_path, "r", encoding="utf-8") as file:
|
|
23
|
+
try:
|
|
24
|
+
if file_ext in ['.yaml', '.yml']:
|
|
25
|
+
return yaml.safe_load(file)
|
|
26
|
+
elif file_ext == '.json':
|
|
27
|
+
return json.load(file)
|
|
28
|
+
else:
|
|
29
|
+
error_msg = f"不支持的配置文件格式: {file_ext}"
|
|
30
|
+
logging.error(error_msg)
|
|
31
|
+
raise ValueError(error_msg)
|
|
32
|
+
except (yaml.YAMLError, json.JSONDecodeError) as e:
|
|
33
|
+
error_msg = f"配置文件格式错误: {e}"
|
|
34
|
+
logging.error(error_msg)
|
|
35
|
+
raise ValueError(error_msg)
|
|
36
|
+
|
|
37
|
+
def get_config_path(config_path):
|
|
38
|
+
"""获取当前工作目录"""
|
|
39
|
+
# 获取当前工作目录
|
|
40
|
+
current_dir = os.getcwd()
|
|
41
|
+
# 拼接配置文件的路径
|
|
42
|
+
config_path = os.path.join(current_dir, config_path)
|
|
43
|
+
return config_path
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == '__main__':
|
|
47
|
+
config = get_config_path("config.yaml")
|