mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-18 21:41:16 +00:00
nmr
This commit is contained in:
200
unilabos/devices/Qone_nmr/QOne_NMR_User_Guide.md
Normal file
200
unilabos/devices/Qone_nmr/QOne_NMR_User_Guide.md
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
# QOne NMR 用户指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
Qone NMR 设备支持多字符串数据处理功能。该设备可以接收包含多个字符串的输入数据,并将每个字符串转换为独立的 TXT 文件,支持灵活的数据格式化和输出。
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
|
||||||
|
- **多字符串处理**: 支持逗号分隔或换行分隔的多个字符串输入
|
||||||
|
- **自动文件生成**: 每个输入字符串生成一个对应的 TXT 文件
|
||||||
|
- **文件夹监督**: 自动监督指定目录,检测新内容生成
|
||||||
|
- **错误处理**: 完善的输入验证和错误处理机制
|
||||||
|
|
||||||
|
## 参数说明
|
||||||
|
|
||||||
|
### 输入参数
|
||||||
|
|
||||||
|
- **string** (str): 包含多个字符串的输入数据,支持格式:
|
||||||
|
- **逗号分隔**: `"字符串1, 字符串2, 字符串3"`
|
||||||
|
|
||||||
|
### 输出参数
|
||||||
|
|
||||||
|
- **return_info** (str): 处理结果信息,包含监督功能的执行结果
|
||||||
|
- **success** (bool): 处理是否成功
|
||||||
|
- **files_generated** (int): 生成的 TXT 文件数量
|
||||||
|
|
||||||
|
## 输入数据格式
|
||||||
|
|
||||||
|
### 支持的输入格式
|
||||||
|
|
||||||
|
1. **逗号分隔格式**:
|
||||||
|
```
|
||||||
|
"A 1 B 1 C 1 D 1 E 1 F 1 G 1 H 1 END, A 2 B 2 C 2 D 2 E 2 F 2 G 2 H 2 END"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据项格式
|
||||||
|
|
||||||
|
每个字符串内的数据项应该用空格分隔,例如:
|
||||||
|
- `A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 END`
|
||||||
|
- `Sample 001 Method A Volume 10.5 Temp 25.0`
|
||||||
|
|
||||||
|
## 输出文件说明
|
||||||
|
|
||||||
|
### 文件命名
|
||||||
|
|
||||||
|
生成的 TXT 文件将按照row_字符串顺序命名,例如:
|
||||||
|
- `row_1.txt`
|
||||||
|
- `row_2.txt`
|
||||||
|
|
||||||
|
### 文件格式
|
||||||
|
|
||||||
|
每个 TXT 文件包含对应字符串的格式化数据,格式为:
|
||||||
|
```
|
||||||
|
A 1
|
||||||
|
B 2
|
||||||
|
C 3
|
||||||
|
D 4
|
||||||
|
E 5
|
||||||
|
F 6
|
||||||
|
G 7
|
||||||
|
H 8
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
### 输出目录
|
||||||
|
|
||||||
|
默认输出目录为 `D:/setup/txt`,可以在 `device.json` 中配置 `output_dir` 参数。
|
||||||
|
|
||||||
|
## 文件夹监督功能
|
||||||
|
|
||||||
|
### 监督机制
|
||||||
|
|
||||||
|
设备在完成字符串到TXT文件的转换后,会自动启动文件夹监督功能:
|
||||||
|
|
||||||
|
- **监督目录**: 默认监督 `D:/Data/MyPC/Automation` 目录
|
||||||
|
- **检查间隔**: 每60秒检查一次新生成的.nmr文件
|
||||||
|
- **检测内容**: 新文件生成或现有文件大小变化
|
||||||
|
- **停止条件**: 连续三次文件大小没有变化,则检测完成
|
||||||
|
|
||||||
|
## 文件夹监督功能详细说明
|
||||||
|
|
||||||
|
Oxford NMR设备驱动集成了智能文件夹监督功能,用于监测.nmr结果文件的生成完成状态。该功能通过监测文件大小变化来判断文件是否已完成写入。
|
||||||
|
|
||||||
|
### 工作机制
|
||||||
|
|
||||||
|
1. **文件大小监测**: 监督功能专门监测指定目录中新生成的.nmr文件的大小变化
|
||||||
|
2. **稳定性检测**: 当文件大小在连续多次检查中保持不变时,认为文件已完成写入
|
||||||
|
3. **批量处理支持**: 根据输入的.txt文件数量,自动确定需要监测的.nmr文件数量
|
||||||
|
4. **实时反馈**: 提供详细的监测进度和文件状态信息
|
||||||
|
5. **自动停止**: 当所有期望的.nmr文件都达到稳定状态时,监督功能自动停止,start函数执行完毕
|
||||||
|
|
||||||
|
### 配置参数
|
||||||
|
|
||||||
|
可以通过`device.json`配置文件调整监督功能的行为:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"output_dir": "D:/setup/txt",
|
||||||
|
"monitor_dir": "D:\\Data\\MyPC\\Automation",
|
||||||
|
"stability_checks": 3,
|
||||||
|
"check_interval": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `monitor_dir`: 监督的目录路径,默认为`D:\Data\MyPC\Automation`
|
||||||
|
- `stability_checks`: 文件大小稳定性检查次数,默认为3次(连续2次检查大小不变则认为文件完成)
|
||||||
|
- `check_interval`: 检查间隔时间(秒),默认为60秒
|
||||||
|
|
||||||
|
### 监测逻辑
|
||||||
|
|
||||||
|
1. **初始状态记录**: 记录监督开始时目录中已存在的.nmr文件及其大小
|
||||||
|
2. **新文件检测**: 持续检测新生成的.nmr文件
|
||||||
|
3. **大小变化跟踪**: 为每个新文件维护大小变化历史记录
|
||||||
|
4. **稳定性判断**: 当文件大小在连续`stability_checks`次检查中保持不变且大小大于0时,认为文件完成
|
||||||
|
5. **完成条件**: 当达到期望数量的.nmr文件都完成时,监督功能结束
|
||||||
|
|
||||||
|
### 配置监督目录
|
||||||
|
|
||||||
|
可以在 `device.json` 中配置 `monitor_dir` 参数来指定监督目录:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"output_dir": "D:/setup/txt",
|
||||||
|
"monitor_dir": "D:/Data/MyPC/Automation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 示例 1: 基本多字符串处理
|
||||||
|
|
||||||
|
```python
|
||||||
|
from unilabos.devices.Qone_nmr.Qone_nmr import Qone_nmr
|
||||||
|
|
||||||
|
# 创建设备实例
|
||||||
|
device = Qone_nmr(output_directory="D:/setup/txt")
|
||||||
|
|
||||||
|
# 输入多个字符串(逗号分隔)
|
||||||
|
input_data = "A 1 B 1 C 1 D 1 E 1 F 1 G 1 H 1 END, A 2 B 2 C 2 D 2 E 2 F 2 G 2 H 2 END"
|
||||||
|
|
||||||
|
# 处理数据
|
||||||
|
result = device.start(string=input_data)
|
||||||
|
|
||||||
|
print(f"处理结果: {result}")
|
||||||
|
# 输出: {'return_info': 'Oxford NMR处理完成: 已生成 3 个 txt 文件,保存在: ./output | 监督完成: 成功检测到 3 个.nmr文件已完成生成', 'success': True, 'files_generated': 3}
|
||||||
|
|
||||||
|
### 输出示例
|
||||||
|
|
||||||
|
当设备成功处理输入并完成文件监督后,会返回如下格式的结果:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"return_info": "Oxford NMR处理完成: 已生成 3 个 txt 文件,保存在: D:/setup/txt | 监督完成: 成功检测到 3 个.nmr文件已完成生成,start函数执行完毕",
|
||||||
|
"success": true,
|
||||||
|
"files_generated": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
监督过程中的日志输出示例:
|
||||||
|
```
|
||||||
|
[INFO] 开始监督目录: D:/Data/MyPC/Automation,检查间隔: 30秒,期望.nmr文件数量: 3,稳定性检查: 2次
|
||||||
|
[INFO] 初始状态: 0 个.nmr文件
|
||||||
|
[INFO] 检测到 3 个新.nmr文件,还需要 0 个...
|
||||||
|
[DEBUG] 文件大小监测中: D:/Data/MyPC/Automation/sample1.nmr (当前: 1024 字节, 检查次数: 1/3)
|
||||||
|
[DEBUG] 文件大小监测中: D:/Data/MyPC/Automation/sample2.nmr (当前: 2048 字节, 检查次数: 1/3)
|
||||||
|
[DEBUG] 文件大小监测中: D:/Data/MyPC/Automation/sample3.nmr (当前: 1536 字节, 检查次数: 1/3)
|
||||||
|
[INFO] 文件大小已稳定: D:/Data/MyPC/Automation/sample1.nmr (大小: 1024 字节)
|
||||||
|
[INFO] 文件大小已稳定: D:/Data/MyPC/Automation/sample2.nmr (大小: 2048 字节)
|
||||||
|
[INFO] 文件大小已稳定: D:/Data/MyPC/Automation/sample3.nmr (大小: 1536 字节)
|
||||||
|
[INFO] 所有期望的.nmr文件都已完成生成! 完成文件数: 3/3
|
||||||
|
[INFO] 完成的.nmr文件: D:/Data/MyPC/Automation/sample1.nmr (最终大小: 1024 字节)
|
||||||
|
[INFO] 完成的.nmr文件: D:/Data/MyPC/Automation/sample2.nmr (最终大小: 2048 字节)
|
||||||
|
[INFO] 完成的.nmr文件: D:/Data/MyPC/Automation/sample3.nmr (最终大小: 1536 字节)
|
||||||
|
[INFO] 停止文件夹监测,所有文件已完成
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
设备具有完善的错误处理机制:
|
||||||
|
|
||||||
|
- **空输入**: 如果输入为空或 None,返回错误信息
|
||||||
|
- **无效格式**: 如果输入格式不正确,返回相应错误
|
||||||
|
- **文件系统错误**: 如果输出目录不存在或无权限,返回错误信息
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **目录权限**: 确保监督目录具有读取权限,以便设备能够检测文件变化
|
||||||
|
2. **文件大小监测**: 监督功能现在基于文件大小变化来判断.nmr文件是否完成,而不是简单的文件存在性检查
|
||||||
|
3. **稳定性检查**: 文件大小需要在连续多次检查中保持不变才被认为完成,默认为3次检查
|
||||||
|
4. **自动停止**: 监督功能会在检测到期望数量的.nmr文件都达到稳定状态后自动停止,避免无限循环
|
||||||
|
5. **配置灵活性**: 可以通过`device.json`调整稳定性检查次数和检查间隔,以适应不同的使用场景
|
||||||
|
6. **文件类型**: 监督功能专门针对.nmr文件,忽略其他类型的文件变化
|
||||||
|
7. **批量处理**: 支持同时监测多个.nmr文件的完成状态,适合批量处理场景
|
||||||
382
unilabos/devices/Qone_nmr/Qone_nmr.py
Normal file
382
unilabos/devices/Qone_nmr/Qone_nmr.py
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Oxford NMR Device Driver for Uni-Lab OS
|
||||||
|
|
||||||
|
支持Oxford NMR设备的CSV字符串到TXT文件转换功能。
|
||||||
|
通过ROS2动作接口接收CSV字符串,批量生成TXT文件到指定目录。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
class UniversalDriver:
|
||||||
|
"""Fallback UniversalDriver for standalone testing"""
|
||||||
|
def __init__(self):
|
||||||
|
self.success = False
|
||||||
|
|
||||||
|
class Qone_nmr(UniversalDriver):
|
||||||
|
"""Oxford NMR Device Driver
|
||||||
|
|
||||||
|
支持CSV字符串到TXT文件的批量转换功能。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""Initialize the Oxford NMR driver
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: Device-specific configuration parameters
|
||||||
|
- config: Configuration dictionary containing output_dir
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# Device configuration
|
||||||
|
self.config = kwargs
|
||||||
|
config_dict = kwargs.get('config', {})
|
||||||
|
|
||||||
|
# 设置输出目录,优先使用配置中的output_dir,否则使用默认值
|
||||||
|
self.output_directory = "D:\\setup\\txt" # 默认输出目录
|
||||||
|
if config_dict and 'output_dir' in config_dict:
|
||||||
|
self.output_directory = config_dict['output_dir']
|
||||||
|
|
||||||
|
# 设置监督目录,优先使用配置中的monitor_dir,否则使用默认值
|
||||||
|
self.monitor_directory = "D:/Data/MyPC/Automation" # 默认监督目录
|
||||||
|
if config_dict and 'monitor_dir' in config_dict:
|
||||||
|
self.monitor_directory = config_dict['monitor_dir']
|
||||||
|
|
||||||
|
# 设置文件大小稳定性检查参数
|
||||||
|
self.stability_checks = 3 # 默认稳定性检查次数
|
||||||
|
if config_dict and 'stability_checks' in config_dict:
|
||||||
|
self.stability_checks = config_dict['stability_checks']
|
||||||
|
|
||||||
|
# 设置检查间隔时间
|
||||||
|
self.check_interval = 60 # 默认检查间隔(秒)
|
||||||
|
if config_dict and 'check_interval' in config_dict:
|
||||||
|
self.check_interval = config_dict['check_interval']
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
os.makedirs(self.output_directory, exist_ok=True)
|
||||||
|
|
||||||
|
# ROS节点引用,由框架设置
|
||||||
|
self._ros_node = None
|
||||||
|
|
||||||
|
# ROS2 action result properties
|
||||||
|
self.success = False
|
||||||
|
self.return_info = ""
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
self.logger = logging.getLogger(f"Qone_nmr-{kwargs.get('id', 'unknown')}")
|
||||||
|
self.logger.info(f"Oxford NMR driver initialized with output directory: {self.output_directory}")
|
||||||
|
self.logger.info(f"Monitor directory set to: {self.monitor_directory}")
|
||||||
|
self.logger.info(f"Stability checks: {self.stability_checks}, Check interval: {self.check_interval}s")
|
||||||
|
|
||||||
|
def post_init(self, ros_node):
|
||||||
|
"""ROS节点初始化后的回调方法
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ros_node: ROS节点实例
|
||||||
|
"""
|
||||||
|
self._ros_node = ros_node
|
||||||
|
ros_node.lab_logger().info(f"Oxford NMR设备初始化完成,输出目录: {self.output_directory}")
|
||||||
|
|
||||||
|
def get_status(self) -> str:
|
||||||
|
"""获取设备状态
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 设备状态 (Idle|Offline|Error|Busy|Unknown)
|
||||||
|
"""
|
||||||
|
return "Idle" # NMR设备始终处于空闲状态,等待处理请求
|
||||||
|
|
||||||
|
def strings_to_txt(self, string_list, output_dir=None, txt_encoding="utf-8"):
|
||||||
|
"""
|
||||||
|
将字符串列表写入多个 txt 文件
|
||||||
|
string_list: ["A 1 B 1 C 1 D 1 E 1 F 1 G 1 H 1 END", ...]
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string_list: 字符串列表
|
||||||
|
output_dir: 输出目录(如果未指定,使用self.output_directory)
|
||||||
|
txt_encoding: 文件编码
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 生成的文件数量
|
||||||
|
"""
|
||||||
|
# 使用指定的输出目录或默认目录
|
||||||
|
target_dir = output_dir if output_dir else self.output_directory
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
|
||||||
|
self.logger.info(f"开始生成文件到目录: {target_dir}")
|
||||||
|
|
||||||
|
for i, s in enumerate(string_list, start=1):
|
||||||
|
try:
|
||||||
|
# 去掉开头结尾的引号(如果有)
|
||||||
|
s = s.strip('"').strip("'")
|
||||||
|
|
||||||
|
# 拆分字符串
|
||||||
|
parts = s.split()
|
||||||
|
|
||||||
|
# 按两两一组重新排版为多行
|
||||||
|
txt_lines = []
|
||||||
|
for j in range(0, len(parts) - 1, 2):
|
||||||
|
txt_lines.append("{} {}".format(parts[j], parts[j+1]))
|
||||||
|
txt_lines.append("END")
|
||||||
|
|
||||||
|
txt_content = "\n".join(txt_lines)
|
||||||
|
|
||||||
|
# 生成文件名(row_1.txt, row_2.txt, ...)
|
||||||
|
file_name = "row_{}.txt".format(i)
|
||||||
|
out_path = os.path.join(target_dir, file_name)
|
||||||
|
|
||||||
|
with open(out_path, "w", encoding=txt_encoding) as f:
|
||||||
|
f.write(txt_content)
|
||||||
|
|
||||||
|
self.logger.info(f"成功生成文件: {file_name}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"处理第{i}个字符串时出错: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
return len(string_list) # 返回生成文件数量
|
||||||
|
|
||||||
|
def monitor_folder_for_new_content(self, monitor_dir=None, check_interval=60, expected_count=1, stability_checks=3):
|
||||||
|
"""监督指定文件夹中.nmr文件的大小变化,当文件大小稳定时认为文件完成
|
||||||
|
|
||||||
|
Args:
|
||||||
|
monitor_dir (str): 要监督的目录路径,如果未指定则使用self.monitor_directory
|
||||||
|
check_interval (int): 检查间隔时间(秒),默认60秒
|
||||||
|
expected_count (int): 期望生成的.nmr文件数量,默认1个
|
||||||
|
stability_checks (int): 文件大小稳定性检查次数,默认3次
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 如果检测到期望数量的.nmr文件且大小稳定返回True,否则返回False
|
||||||
|
"""
|
||||||
|
target_dir = monitor_dir if monitor_dir else self.monitor_directory
|
||||||
|
|
||||||
|
# 确保监督目录存在
|
||||||
|
if not os.path.exists(target_dir):
|
||||||
|
self.logger.warning(f"监督目录不存在: {target_dir}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.info(f"开始监督目录: {target_dir},检查间隔: {check_interval}秒,期望.nmr文件数量: {expected_count},稳定性检查: {stability_checks}次")
|
||||||
|
|
||||||
|
# 记录初始的.nmr文件及其大小
|
||||||
|
initial_nmr_files = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
for root, dirs, files in os.walk(target_dir):
|
||||||
|
for file in files:
|
||||||
|
if file.lower().endswith('.nmr'):
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
try:
|
||||||
|
file_size = os.path.getsize(file_path)
|
||||||
|
initial_nmr_files[file_path] = file_size
|
||||||
|
except OSError:
|
||||||
|
pass # 忽略无法访问的文件
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"读取初始目录状态失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.info(f"初始状态: {len(initial_nmr_files)} 个.nmr文件")
|
||||||
|
|
||||||
|
# 跟踪新文件的大小变化历史
|
||||||
|
new_files_size_history = {}
|
||||||
|
completed_files = set()
|
||||||
|
|
||||||
|
# 开始监督循环
|
||||||
|
while True:
|
||||||
|
time.sleep(check_interval)
|
||||||
|
|
||||||
|
current_nmr_files = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
for root, dirs, files in os.walk(target_dir):
|
||||||
|
for file in files:
|
||||||
|
if file.lower().endswith('.nmr'):
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
try:
|
||||||
|
file_size = os.path.getsize(file_path)
|
||||||
|
current_nmr_files[file_path] = file_size
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 找出新生成的.nmr文件
|
||||||
|
new_nmr_files = set(current_nmr_files.keys()) - set(initial_nmr_files.keys())
|
||||||
|
|
||||||
|
if len(new_nmr_files) < expected_count:
|
||||||
|
self.logger.info(f"检测到 {len(new_nmr_files)} 个新.nmr文件,还需要 {expected_count - len(new_nmr_files)} 个...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查新文件的大小稳定性
|
||||||
|
for file_path in new_nmr_files:
|
||||||
|
if file_path in completed_files:
|
||||||
|
continue
|
||||||
|
|
||||||
|
current_size = current_nmr_files.get(file_path, 0)
|
||||||
|
|
||||||
|
# 初始化文件大小历史记录
|
||||||
|
if file_path not in new_files_size_history:
|
||||||
|
new_files_size_history[file_path] = []
|
||||||
|
|
||||||
|
# 记录当前大小
|
||||||
|
new_files_size_history[file_path].append(current_size)
|
||||||
|
|
||||||
|
# 保持历史记录长度不超过稳定性检查次数
|
||||||
|
if len(new_files_size_history[file_path]) > stability_checks:
|
||||||
|
new_files_size_history[file_path] = new_files_size_history[file_path][-stability_checks:]
|
||||||
|
|
||||||
|
# 检查大小是否稳定
|
||||||
|
size_history = new_files_size_history[file_path]
|
||||||
|
if len(size_history) >= stability_checks:
|
||||||
|
# 检查最近几次的大小是否相同且不为0
|
||||||
|
if len(set(size_history[-stability_checks:])) == 1 and size_history[-1] > 0:
|
||||||
|
self.logger.info(f"文件大小已稳定: {file_path} (大小: {current_size} 字节)")
|
||||||
|
completed_files.add(file_path)
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"文件大小仍在变化: {file_path} (当前: {current_size} 字节, 历史: {size_history[-3:]})")
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"文件大小监测中: {file_path} (当前: {current_size} 字节, 检查次数: {len(size_history)}/{stability_checks})")
|
||||||
|
|
||||||
|
# 检查是否所有期望的文件都已完成
|
||||||
|
if len(completed_files) >= expected_count:
|
||||||
|
self.logger.info(f"所有期望的.nmr文件都已完成生成! 完成文件数: {len(completed_files)}/{expected_count}")
|
||||||
|
for completed_file in list(completed_files)[:expected_count]:
|
||||||
|
final_size = current_nmr_files.get(completed_file, 0)
|
||||||
|
self.logger.info(f"完成的.nmr文件: {completed_file} (最终大小: {final_size} 字节)")
|
||||||
|
self.logger.info("停止文件夹监测,所有文件已完成")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.logger.info(f"已完成 {len(completed_files)} 个文件,还需要 {expected_count - len(completed_files)} 个文件完成...")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"监督过程中出错: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def start(self, string: str = None) -> dict:
|
||||||
|
"""使用字符串列表启动TXT文件生成(支持ROS2动作调用)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): 包含多个字符串的输入数据,支持两种格式:
|
||||||
|
1. 逗号分隔:如 "A 1 B 2 C 3, X 10 Y 20 Z 30"
|
||||||
|
2. 换行分隔:如 "A 1 B 2 C 3\nX 10 Y 20 Z 30"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: ROS2动作结果格式 {"return_info": str, "success": bool, "files_generated": int}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if string is None or string.strip() == "":
|
||||||
|
error_msg = "未提供字符串参数或参数为空"
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
return {"return_info": error_msg, "success": False, "files_generated": 0}
|
||||||
|
|
||||||
|
self.logger.info(f"开始处理字符串数据,长度: {len(string)} 字符")
|
||||||
|
|
||||||
|
# 支持两种分隔方式:逗号分隔或换行分隔
|
||||||
|
string_list = []
|
||||||
|
|
||||||
|
# 首先尝试逗号分隔
|
||||||
|
if ',' in string:
|
||||||
|
string_list = [item.strip() for item in string.split(',') if item.strip()]
|
||||||
|
else:
|
||||||
|
# 如果没有逗号,则按换行分隔
|
||||||
|
string_list = [line.strip() for line in string.strip().split('\n') if line.strip()]
|
||||||
|
|
||||||
|
if not string_list:
|
||||||
|
error_msg = "输入字符串解析后为空"
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
return {"return_info": error_msg, "success": False, "files_generated": 0}
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
os.makedirs(self.output_directory, exist_ok=True)
|
||||||
|
|
||||||
|
# 使用strings_to_txt函数生成TXT文件
|
||||||
|
file_count = self.strings_to_txt(
|
||||||
|
string_list=string_list,
|
||||||
|
output_dir=self.output_directory,
|
||||||
|
txt_encoding='utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
|
success_msg = f"Oxford NMR处理完成: 已生成 {file_count} 个 txt 文件,保存在: {self.output_directory}"
|
||||||
|
self.logger.info(success_msg)
|
||||||
|
|
||||||
|
# 在string转txt完成后,启动文件夹监督功能
|
||||||
|
self.logger.info(f"开始启动文件夹监督功能,期望生成 {file_count} 个.nmr文件...")
|
||||||
|
monitor_result = self.monitor_folder_for_new_content(
|
||||||
|
expected_count=file_count,
|
||||||
|
check_interval=self.check_interval,
|
||||||
|
stability_checks=self.stability_checks
|
||||||
|
)
|
||||||
|
|
||||||
|
if monitor_result:
|
||||||
|
success_msg += f" | 监督完成: 成功检测到 {file_count} 个.nmr文件已完成生成,start函数执行完毕"
|
||||||
|
else:
|
||||||
|
success_msg += f" | 监督结束: 监督过程中断或失败,start函数执行完毕"
|
||||||
|
|
||||||
|
return {"return_info": success_msg, "success": True, "files_generated": file_count}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"字符串处理失败: {str(e)}"
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
return {"return_info": error_msg, "success": False, "files_generated": 0}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_qone_nmr():
|
||||||
|
"""测试Qone_nmr设备的字符串处理功能"""
|
||||||
|
try:
|
||||||
|
# 配置日志输出
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger("Qone_nmr_test")
|
||||||
|
|
||||||
|
logger.info("开始测试Qone_nmr设备...")
|
||||||
|
|
||||||
|
# 创建设备实例,使用正确的配置格式
|
||||||
|
device = Qone_nmr(config={'output_dir': "D:\\setup\\txt"})
|
||||||
|
logger.info(f"设备初始化完成,输出目录: {device.output_directory}")
|
||||||
|
|
||||||
|
# 测试数据:多个字符串,逗号分隔
|
||||||
|
test_strings = "A 1 B 1 C 1 D 1 E 1 F 1 G 1 H 1 END, A 2 B 2 C 2 D 2 E 2 F 2 G 2 H 2 END"
|
||||||
|
logger.info(f"测试输入: {test_strings}")
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
if not os.path.exists(device.output_directory):
|
||||||
|
os.makedirs(device.output_directory, exist_ok=True)
|
||||||
|
logger.info(f"创建输出目录: {device.output_directory}")
|
||||||
|
|
||||||
|
# 调用start方法
|
||||||
|
result = device.start(string=test_strings)
|
||||||
|
logger.info(f"处理结果: {result}")
|
||||||
|
|
||||||
|
# 显示生成的文件内容
|
||||||
|
if result.get('success', False):
|
||||||
|
output_dir = device.output_directory
|
||||||
|
if os.path.exists(output_dir):
|
||||||
|
txt_files = [f for f in os.listdir(output_dir) if f.endswith('.txt')]
|
||||||
|
logger.info(f"生成的文件数量: {len(txt_files)}")
|
||||||
|
for i, filename in enumerate(txt_files[:2]): # 只显示前2个文件
|
||||||
|
filepath = os.path.join(output_dir, filename)
|
||||||
|
logger.info(f"文件 {i+1}: {filename}")
|
||||||
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
logger.info(f"内容:\n{content}")
|
||||||
|
|
||||||
|
logger.info("测试完成!")
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"测试过程中出现错误: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return {"return_info": f"测试失败: {str(e)}", "success": False, "files_generated": 0}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_qone_nmr()
|
||||||
25
unilabos/devices/Qone_nmr/device.json
Normal file
25
unilabos/devices/Qone_nmr/device.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "Qone_nmr_device",
|
||||||
|
"name": "Qone_NMR_Device",
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "Qone_nmr",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"output_dir": "D:\\setup\\txt",
|
||||||
|
"monitor_dir": "D:\\Data\\MyPC\\Automation",
|
||||||
|
"stability_checks": 3,
|
||||||
|
"check_interval": 60
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
4
unilabos/devices/Qone_nmr/samples.csv
Normal file
4
unilabos/devices/Qone_nmr/samples.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
USERNAME,SLOT,EXPNAME,FILE,SOLVENT,TEMPLATE,TITLE
|
||||||
|
User,SLOT,Name,No.,SOLVENT,Experiment,TITLE
|
||||||
|
用户名,进样器孔位,实验任务的名字,保存文件的名字,溶剂(按照实验的要求),模板(按照实验的要求,指定测试的元素),标题
|
||||||
|
admin,18,11LiDFOB,LiDFOB-11B,DMSO,B11,11LiDFOB_400MHz
|
||||||
|
156
unilabos/registry/devices/Qone_nmr.yaml
Normal file
156
unilabos/registry/devices/Qone_nmr.yaml
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
Qone_nmr:
|
||||||
|
category:
|
||||||
|
- Qone_nmr
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
abort:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: EmptyIn_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
|
get_status:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: EmptyIn_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
|
start:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
string: string
|
||||||
|
goal_default:
|
||||||
|
string: ''
|
||||||
|
handles: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: StrSingleInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
title: StrSingleInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: StrSingleInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: StrSingleInput
|
||||||
|
type: object
|
||||||
|
type: StrSingleInput
|
||||||
|
module: unilabos.devices.Qone_nmr.Qone_nmr:Qone_nmr
|
||||||
|
status_types:
|
||||||
|
files_generated: int
|
||||||
|
return_info: str
|
||||||
|
success: bool
|
||||||
|
type: python
|
||||||
|
config_info: []
|
||||||
|
description: Oxford NMR设备驱动,支持CSV字符串到TXT文件的批量转换功能,并监测对应.nmr文件的大小变化以确认结果生成完成
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
check_interval:
|
||||||
|
default: 60
|
||||||
|
description: 文件监测检查间隔时间(秒)
|
||||||
|
maximum: 300
|
||||||
|
minimum: 10
|
||||||
|
type: integer
|
||||||
|
monitor_dir:
|
||||||
|
default: D:/Data/MyPC/Automation
|
||||||
|
type: string
|
||||||
|
output_dir:
|
||||||
|
default: D:/setup/txt
|
||||||
|
type: string
|
||||||
|
stability_checks:
|
||||||
|
default: 3
|
||||||
|
description: 文件大小稳定性检查次数,文件大小连续N次不变时认为文件完成
|
||||||
|
maximum: 10
|
||||||
|
minimum: 1
|
||||||
|
type: integer
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
files_generated:
|
||||||
|
type: integer
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
- files_generated
|
||||||
|
type: object
|
||||||
|
version: 1.0.0
|
||||||
Reference in New Issue
Block a user