From 2fed44e92b82acef0ff9ad98df9f0b3a23cf0ce2 Mon Sep 17 00:00:00 2001 From: Hua Haiming <840473807@qq.com> Date: Tue, 6 May 2025 16:09:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=94=B5=E5=8C=96=E5=AD=A6?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E7=AB=99=E4=B8=8E=E6=8B=89=E6=9B=BC=E5=85=89?= =?UTF-8?q?=E8=B0=B1=E7=9A=84=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/dh_7000_eis.py | 34 ++ examples/opsky_30007_raman.py | 33 ++ unilabos/devices/dh_electrochem/__init__.py | 0 unilabos/devices/dh_electrochem/dh_7000.py | 552 ++++++++++++++++++ unilabos/devices/opsky_Raman/__init__.py | 0 .../devices/opsky_Raman/opsky_ATR30007.py | 114 ++++ 6 files changed, 733 insertions(+) create mode 100644 examples/dh_7000_eis.py create mode 100644 examples/opsky_30007_raman.py create mode 100644 unilabos/devices/dh_electrochem/__init__.py create mode 100644 unilabos/devices/dh_electrochem/dh_7000.py create mode 100644 unilabos/devices/opsky_Raman/__init__.py create mode 100644 unilabos/devices/opsky_Raman/opsky_ATR30007.py diff --git a/examples/dh_7000_eis.py b/examples/dh_7000_eis.py new file mode 100644 index 0000000..4c53bdb --- /dev/null +++ b/examples/dh_7000_eis.py @@ -0,0 +1,34 @@ +import rclpy + +# Initialize ROS communications for a given context +if(rclpy.ok() == False): + rclpy.init() +else: + print("rclpy already initiated") + +from unilabos.devices.dh_electrochem.dh_7000 import DH7000 +from unilabos_msgs.action import SendCmd +from std_msgs.msg import Float64, String, Int16 +from unilabos.ros.device_node_wrapper import ros2_device_node +import clr +import os + +# dll_path = r'D:\UniLab\code\DHElecChem\release64' +# eccore_dll_path = os.path.join(dll_path, 'ECCore.dll') +# os.environ["PATH"] = dll_path + os.pathsep + os.environ["PATH"] +# clr.AddReference(eccore_dll_path) +# from ECCore import ElecMachines + +ROS2_DH7000 = ros2_device_node( + DH7000, + status_types={'machine_id': Int16, 'status': String}, + action_value_mappings={'dh_cmd': { + 'type': SendCmd, + 'goal': {'command': 'command'}, + 'feedback': {}, + 'result': {'success': 'success'}} + } +) + +device = ROS2_DH7000(device_id='DH7000_1') +rclpy.spin(device.ros_node_instance) \ No newline at end of file diff --git a/examples/opsky_30007_raman.py b/examples/opsky_30007_raman.py new file mode 100644 index 0000000..fc2499f --- /dev/null +++ b/examples/opsky_30007_raman.py @@ -0,0 +1,33 @@ +import rclpy + +# Initialize ROS communications for a given context +if(rclpy.ok() == False): + rclpy.init() +else: + print("rclpy already initiated") + +from unilabos.devices.opsky_Raman.opsky_ATR30007 import ATR30007 +from unilabos_msgs.action import SendCmd +from std_msgs.msg import Float64, String, Int16 +from unilabos.ros.device_node_wrapper import ros2_device_node +import clr +import os +# dll_path = r'D:\UniLab\code\DHElecChem\release64' +# eccore_dll_path = os.path.join(dll_path, 'ECCore.dll') +# os.environ["PATH"] = dll_path + os.pathsep + os.environ["PATH"] +# clr.AddReference(eccore_dll_path) +# from ECCore import ElecMachines + +ROS2_ATR30007 = ros2_device_node( + ATR30007, + #status_types={'machine_id': Int16, 'status': String}, + action_value_mappings={'opsky_cmd': { + 'type': SendCmd, + 'goal': {'command': 'command'}, + 'feedback': {}, + 'result': {'success': 'success'}} + } +) + +device = ROS2_ATR30007(device_id='ATR30007_1') +rclpy.spin(device.ros_node_instance) \ No newline at end of file diff --git a/unilabos/devices/dh_electrochem/__init__.py b/unilabos/devices/dh_electrochem/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/dh_electrochem/dh_7000.py b/unilabos/devices/dh_electrochem/dh_7000.py new file mode 100644 index 0000000..dee71e7 --- /dev/null +++ b/unilabos/devices/dh_electrochem/dh_7000.py @@ -0,0 +1,552 @@ +import clr +import os +import time +import pandas as pd +import json +import shutil +import sys +import time +from System import Environment + + +# define class DH7000 +# this class is used to control the DH7000 electrochemical workstation +class DH7000: + def __init__(self, dll_path: str = r'D:\DH_release64'): + self.dll_path = dll_path + self.machine = None + self._load_dll() + self._initialize_machine() + self._status = "PowerOff" # otherwise it will be "Running", "Overload", "Idle" + self._machine_id = None + + @property + def status(self) -> str: + return self._status + + @property + def machine_id(self) -> int: + return self._machine_id + + def _load_dll(self): + # Check if ECCore.dll exists + eccore_dll_path = os.path.join(self.dll_path, 'ECCore.dll') + if not os.path.exists(eccore_dll_path): + raise FileNotFoundError(f"ECCore.dll不存在于路径 {eccore_dll_path}") + # Prepend DLL path to system PATH + os.environ["PATH"] = self.dll_path + os.pathsep + os.environ["PATH"] + + # Add reference + try: + clr.AddReference(eccore_dll_path) + from ECCore import ElecMachines + self.machine = ElecMachines() + print("成功添加ECCore.dll引用,导入ElecMachines类,并创建实例") + except Exception as e: + raise RuntimeError(f"添加DLL引用或导入ElecMachines类时出错: {e}") + + def _initialize_machine(self): + interface_dir = os.path.join(self.dll_path, 'DHInterface') + if not os.path.exists(interface_dir): + raise FileNotFoundError(f"DHInterface文件夹不存在于路径 {interface_dir}") + + init_success = self.machine.Init(interface_dir) + if not init_success: + raise RuntimeError("工作站初始化失败") + print("工作站初始化成功") + self.check_status() + + def check_status(self): + Flag_isexp = self.machine.IsExperimenting() + if Flag_isexp: + Flag_isover = self.machine.IsOverLoad() + if Flag_isover: + self._status = "Overload" + else: + self._status = "Running" + else: + self._status = "Idle" + + def set_current_machineid(self, machine_id: int): + machineid_list = list(self.machine.GetMachineId()) + if machine_id not in machineid_list: + raise ValueError(f"工作站ID {machine_id} 不在可用ID列表中: {machineid_list}") + self.machine.SetCurrentMachineId(machine_id) + self._machine_id = machine_id + print(f"选择的工作站ID: {self._machine_id}") + + def start_eis( + self, + isVolEIS: bool = True, + StartFreq: float = 1E5, + EndFreq: float = 1E-1, + Amplitude: float = 10, + IntervalType: int = 1, + PointCount: int = 10, + Voltage: float = 0.0, + VoltageVSType: float = 1.0, + isVoltageRandAuto: int = 1, + VoltageRand: str = "10", + isCurrentRandAuto: int = 1, + CurrentRand: str = "10", + file_name: str = 'test_eis', + save_root: str = r"D:\UniLab\results" + ): + """ + EIS test + """ + self.machine.Start_EIS( + isVolEIS, + StartFreq, + EndFreq, + Amplitude, + IntervalType, + PointCount, + Voltage, + VoltageVSType, + isVoltageRandAuto, + VoltageRand, + isCurrentRandAuto, + CurrentRand + ) + self._status = "Running" + + # Wait for experiment to finish + while self._status == "Running": + time.sleep(5) + self.check_status() + print(f"当前状态: {self._status}") + + # Retrieve data + datatype = self.machine.GetResultDataType() + data_result = self.machine.GetData(datatype) + splitCount = 6 + + timeData = list(self.machine.SplitData(data_result, splitCount, 0)) + ZreData = list(self.machine.SplitData(data_result, splitCount, 1)) + ZimData = list(self.machine.SplitData(data_result, splitCount, 2)) + ZData = list(self.machine.SplitData(data_result, splitCount, 3)) + FreqData = list(self.machine.SplitData(data_result, splitCount, 4)) + PhaseData = list(self.machine.SplitData(data_result, splitCount, 5)) + + data_to_dataframe = { + "TimeData": timeData, + "ZreData": ZreData, + "ZimData": ZimData, + "ZData": ZData, + "FreqData": FreqData, + "PhaseData": PhaseData, + } + + # Convert to DataFrame + df = pd.DataFrame(data_to_dataframe) + self.save_data(df, file_name, save_root) + self.check_status() + + def start_lsv( + self, + InitialPotential: float = 0.0, + InitialPotentialVSType: int = 1, + FinallyPotential: float = 1.0, + FinallyPotentialVSType: int = 1, + ScanRate: float = 0.05, + isVoltageRandAuto: int = 1, + VoltageRand: str = "10", + isCurrentRandAuto: int = 1, + CurrentRand: str = "10", + file_name: str = 'lsv_test', + save_root: str = r"D:\UniLab\results" + ): + """ + LSV test + """ + self.machine.Start_Linear_Scan_Voltammetry( + InitialPotential, + InitialPotentialVSType, + FinallyPotential, + FinallyPotentialVSType, + ScanRate, + isVoltageRandAuto, + VoltageRand, + isCurrentRandAuto, + CurrentRand + ) + self._status = "Running" + + # Wait for experiment to finish + while self._status == "Running": + time.sleep(5) + self.check_status() + print(f"当前状态: {self._status}") + + # Retrieve data + datatype = self.machine.GetResultDataType() + data_result = self.machine.GetData(datatype) + splitCount = 3 + + timeData = list(self.machine.SplitData(data_result, splitCount, 0)) + VolData = list(self.machine.SplitData(data_result, splitCount, 1)) + CurData = list(self.machine.SplitData(data_result, splitCount, 2)) + + data_to_dataframe = { + "TimeData": timeData, + "VolData": VolData, + "CurData": CurData, + } + # Convert to DataFrame + df = pd.DataFrame(data_to_dataframe) + self.save_data(df, file_name, save_root) + self.check_status() + + def start_cv_single( + self, + InitialPotential: float = 0.0, + InitialPotentialVSType: int = 0, + TopPotential: float = 1.0, + TopPotentialVSType: int = 0, + FinallyPotential: float = 0.0, + FinallyPotentialVSType: int = 0, + ScanRate: float = 0.1, + isVoltageRandAuto: int = 1, + VoltageRand: str = "10", + isCurrentRandAuto: int = 1, + CurrentRand: str = "10", + isVoltageFilterAuto: int = 1, + VoltageFilter: str = "", + isCurrentFilterAuto: int = 1, + currentFilter: str = "", + machineId: int = 0, + delayTime: float = 0.0, + file_name: str = 'cv_single_test', + save_root: str = r"D:\UniLab\results" + ): + """ + Single cyclic voltammetry (Single CV) + """ + + # Call underlying DLL function + self.machine.Start_Circle_Voltammetry_Single( + InitialPotential, + InitialPotentialVSType, + TopPotential, + TopPotentialVSType, + FinallyPotential, + FinallyPotentialVSType, + ScanRate, + isVoltageRandAuto, + VoltageRand, + isCurrentRandAuto, + CurrentRand, + isVoltageFilterAuto, + VoltageFilter, + isCurrentFilterAuto, + currentFilter, + machineId, + delayTime + ) + + # Wait for experiment to finish + self._status = "Running" + while self._status == "Running": + time.sleep(5) + self.check_status() + print(f"当前状态: {self._status}") + + # Retrieve and parse data + datatype = self.machine.GetResultDataType() + data_result = self.machine.GetData(datatype) + # Same as LSV, usually three columns: Time, Voltage, Current + splitCount = 3 + + timeData = list(self.machine.SplitData(data_result, splitCount, 0)) + volData = list(self.machine.SplitData(data_result, splitCount, 1)) + curData = list(self.machine.SplitData(data_result, splitCount, 2)) + + data_to_dataframe = { + "TimeData": timeData, + "VolData": volData, + "CurData": curData, + } + df = pd.DataFrame(data_to_dataframe) + + # Save data + self.save_data(df, file_name, save_root) + self.check_status() + + def start_cv_multi( + self, + IsUseInitialPotential: bool = True, + InitialPotential: float = 0.0, + InitialPotentialVSType: int = 0, + TopPotential1: float = 1.0, + TopPotential1VSType: int = 0, + TopPotential2: float = -1.0, + TopPotential2VSType: int = 0, + IsUseFinallyPotential: bool = True, + FinallyPotential: float = 0.0, + FinallyPotentialVSType: int = 0, + ScanRate: float = 0.1, + cycleCount: int = 3, + isVoltageRandAuto: int = 1, + VoltageRand: str = "10", + isCurrentRandAuto: int = 1, + CurrentRand: str = "10", + isVoltageFilterAuto: int = 1, + VoltageFilter: str = "", + isCurrentFilterAuto: int = 1, + currentFilter: str = "", + machineId: int = 0, + delayTime: float = 0.0, + file_name: str = "cv_multi_test", + save_root: str = r"D:\UniLab\results", + ): + """ + Multiple cyclic voltammetry (Multiple CV). + """ + # Call underlying DLL + self.machine.Start_Circle_Voltammetry_Multi( + IsUseInitialPotential, + InitialPotential, + InitialPotentialVSType, + TopPotential1, + TopPotential1VSType, + TopPotential2, + TopPotential2VSType, + IsUseFinallyPotential, + FinallyPotential, + FinallyPotentialVSType, + ScanRate, + cycleCount, + isVoltageRandAuto, + VoltageRand, + isCurrentRandAuto, + CurrentRand, + isVoltageFilterAuto, + VoltageFilter, + isCurrentFilterAuto, + currentFilter, + machineId, + delayTime, + ) + + # Wait for experiment to finish + self._status = "Running" + while self._status == "Running": + time.sleep(5) + self.check_status() + print(f"当前状态: {self._status}") + + # Fetch data and split + datatype = self.machine.GetResultDataType() + data_result = self.machine.GetData(datatype) + + splitCount = 3 # Assume still three columns: Time/Voltage/Current + timeData = list(self.machine.SplitData(data_result, splitCount, 0)) + volData = list(self.machine.SplitData(data_result, splitCount, 1)) + curData = list(self.machine.SplitData(data_result, splitCount, 2)) + + df = pd.DataFrame( + { + "TimeData": timeData, + "VolData": volData, + "CurData": curData, + } + ) + + # Save + self.save_data(df, file_name, save_root) + self.check_status() + + def start_ca( + self, + timePerPoint: float = 0.1, + continueTime: float = 100.0, + InitialPotential: float = 0.0, + InitialPotentialVSType: int = 0, + isVoltageRandAuto: int = 1, + VoltageRand: str = "10", + isCurrentRandAuto: int = 1, + CurrentRand: str = "10", + isVoltageFilterAuto: int = 1, + VoltageFilter: str = "", + isCurrentFilterAuto: int = 1, + currentFilter: str = "", + machineId: int = 0, + file_name: str = "ca_test", + save_root: str = r"D:\UniLab\results", + ): + """ + Chrono‑Amperometry (CA) — constant potential + """ + # Call DLL + self.machine.Start_ChronoamperonetryParam( + timePerPoint, + continueTime, + InitialPotential, + InitialPotentialVSType, + isVoltageRandAuto, + VoltageRand, + isCurrentRandAuto, + CurrentRand, + isVoltageFilterAuto, + VoltageFilter, + isCurrentFilterAuto, + currentFilter, + machineId, + ) + + # Wait for experiment to finish + self._status = "Running" + while self._status == "Running": + time.sleep(5) + self.check_status() + print(f"当前状态: {self._status}") + + # Fetch and split data (commonly three columns: Time / Voltage / Current) + datatype = self.machine.GetResultDataType() + data_result = self.machine.GetData(datatype) + + splitCount = 3 + timeData = list(self.machine.SplitData(data_result, splitCount, 0)) + volData = list(self.machine.SplitData(data_result, splitCount, 1)) + curData = list(self.machine.SplitData(data_result, splitCount, 2)) + + df = pd.DataFrame( + { + "TimeData": timeData, + "VolData": volData, + "CurData": curData, + } + ) + + # Save + self.save_data(df, file_name, save_root) + self.check_status() + + def start_cp( + self, + timePerPoint: float = 0.1, + continueTime: float = 100.0, + current: float = 1.0, + VoltageRand: str = "100", + isCurrentRandAuto: int = 1, + CurrentRand: str = "10", + isVoltageFilterAuto: int = 1, + VoltageFilter: str = "", + isCurrentFilterAuto: int = 1, + currentFilter: str = "", + machineId: int = 0, + file_name: str = "cp_test", + save_root: str = r"D:\UniLab\results", + ): + """ + Chrono‑Potentiometry (CP) — constant current + """ + # Call DLL + self.machine.Start_ChronopotentiometryParam( + timePerPoint, + continueTime, + current, + VoltageRand, + isCurrentRandAuto, + CurrentRand, + isVoltageFilterAuto, + VoltageFilter, + isCurrentFilterAuto, + currentFilter, + machineId, + ) + + # Wait for experiment to finish + self._status = "Running" + while self._status == "Running": + time.sleep(5) + self.check_status() + print(f"当前状态: {self._status}") + + # Fetch and split data (Time / Voltage / Current) + datatype = self.machine.GetResultDataType() + data_result = self.machine.GetData(datatype) + + splitCount = 3 + timeData = list(self.machine.SplitData(data_result, splitCount, 0)) + volData = list(self.machine.SplitData(data_result, splitCount, 1)) + curData = list(self.machine.SplitData(data_result, splitCount, 2)) + + df = pd.DataFrame( + { + "TimeData": timeData, + "VolData": volData, + "CurData": curData, + } + ) + + # Save + self.save_data(df, file_name, save_root) + self.check_status() + + + def stop_experiment(self): + self.machine.StopExperiment() + self.check_status() + print("实验已停止") + + def save_data(self, data: pd.DataFrame, file_name: str, save_root: str): + if not os.path.exists(save_root): + os.makedirs(save_root) + data.to_csv(os.path.join(save_root, f"{file_name}.csv"), index=False) + print(f"数据已保存到 {save_root}") + + # === Core: a unified dh_cmd method that calls start_eis or start_lsv according to methods === + def dh_cmd(self, command: str): + """ + Unified handler for different commands such as EIS / LSV / CV / CA. + In the incoming command JSON, use the key "methods" to specify the measurement type: "eis", "lsv", etc. + """ + self.success = False + print(f"接收到命令: {command}") + + # Replace "!=!" with quotes and fix True/False and path separators + command = command.replace("!=!", "\"") + command = command.replace('\\', '\\\\') + command = command.replace("True", "true").replace("False", "false") + + try: + cmd_dict = json.loads(command) + print(f"命令参数: {cmd_dict}") + + # Extract measurement method, default to "EIS" if not specified + method = cmd_dict.pop("methods", "eis").lower() + # Extract file save path (may not be provided) + save_root = cmd_dict.get("save_root", r"D:\UniLab\results\test") + + if method in ("cv_single", "cvs"): + # CV (cyclic voltammetry, single cycle) + print("执行 EIS 测试...") + self.start_cv_single(**cmd_dict) + elif method in ("cv_multi", "cvm"): + # CV (cyclic voltammetry, multiple cycles) + print("执行多循环伏安测试...") + self.start_cv_multi(**cmd_dict) + elif method == "lsv": + # LSV + print("执行 LSV 测试...") + self.start_lsv(**cmd_dict) + elif method == "ca": + # CA + self.start_ca(**cmd_dict) + elif method == "cp": + # CP + self.start_cp(**cmd_dict) + else: + # Default to EIS + print("执行 EIS 测试...") + self.start_eis(**cmd_dict) + + + print(f"实验完成,数据已保存到 {save_root}") + self.success = True + + except Exception as e: + print(f"命令执行失败: {e}") + raise RuntimeError(f"error: {e}") \ No newline at end of file diff --git a/unilabos/devices/opsky_Raman/__init__.py b/unilabos/devices/opsky_Raman/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/opsky_Raman/opsky_ATR30007.py b/unilabos/devices/opsky_Raman/opsky_ATR30007.py new file mode 100644 index 0000000..7a31e04 --- /dev/null +++ b/unilabos/devices/opsky_Raman/opsky_ATR30007.py @@ -0,0 +1,114 @@ +import clr +import os +import time +import pandas as pd +import json +import shutil +import sys +import time +from System import Environment + +# define class ATR30007 +# this class is used to control the ATR30007 Raman workstation + +class ATR30007: + def __init__(self, dll_path: str = r'D:\Raman_RS'): + self.dll_path = dll_path + self.machine = None + self._load_dll() + + def _load_dll(self): + # 检查ECCore.dll是否存在 + eccore_dll_path = os.path.join(self.dll_path, 'ATRWrapper.dll') + if not os.path.exists(eccore_dll_path): + raise FileNotFoundError(f"ATRWrapper.dll不存在于路径 {eccore_dll_path}") + + # 将DLL路径添加到系统PATH的最前面 + os.environ["PATH"] = self.dll_path + os.pathsep + os.environ["PATH"] + + # 添加引用 + try: + clr.AddReference(eccore_dll_path) + from Optosky.Wrapper import ATRWrapper + self.machine = ATRWrapper() + print("成功添加ATRWrapper.dll引用,导入ATRWrapper类,并创建实例") + except Exception as e: + raise RuntimeError(f"添加DLL引用或导入ATRWrapper类时出错: {e}") + + + def get_machineSn(self): + machineSn = self.machine.GetSn() + print(f"选择的工作站ID: {machineSn}") + + def start_Raman(self, IntegTime: int = 5000, LdPower: int = 200, + Ldwave: int = 1, CCDTemp: int = -5, + file_name: str = 'test_raman', save_root: str = None): + #打开仪器 + On_flag = self.machine.OpenDevice() + print(f"On_flag: {On_flag}") + #获取仪器SN + wrapper_Sn = self.machine.GetSn() + print(f"wrapper_Sn: {wrapper_Sn}") + #设置当前设备的积分时间, 单位为毫秒 + Integ_flag = self.machine.SetIntegrationTime(IntegTime) + print(f"Integ_flag:{Integ_flag}") + #设置激光功率, 单位mW + LdP_flag = self.machine.SetLdPower(LdPower,Ldwave) + print(f"LdP_flag:{LdP_flag}") + #设置 CCD 制冷温度 + SetC_flag = self.machine.SetCool(CCDTemp) + print(f"SetC_flag:{SetC_flag}") + #开始采集光谱 + Spect = self.machine.AcquireSpectrum() + #开始采集光谱谱图数据转换 + Spect_data = list(Spect.get_Data()) + Spect_suss_flag = Spect.get_Success() + print(f"Spect_suss_flag:{Spect_suss_flag}") + #获取波数 + WaveNum = list(self.machine.GetWaveNum()) + #光谱数据基线校正 + Spect_bLC = list(self.machine.BaseLineCorrect(Spect_data)) + #对数据进行boxcar 平滑 + Spect_StB = list(self.machine.SmoothBoxcar(Spect_bLC, 10)) + #关闭仪器 + OFF_flag = wrapper.CloseDevice() + print(f"OFF_flag: {OFF_flag}") + + data_to_dataframe = { + "WaveNum": WaveNum, + "Spect_data": Spect_data, + "Spect_bLC": Spect_bLC, + "Spect_StB": Spect_StB + } + # 将数据转换为DataFrame格式 + df = pd.DataFrame(data_to_dataframe) + self.save_data(df, file_name, save_root) + self.check_status() + + + def save_data(self, data: pd.DataFrame, file_name: str, save_root: str): + if not os.path.exists(save_root): + os.makedirs(save_root) + data.to_csv(os.path.join(save_root, f"{file_name}.csv"), index=False) + print(f"数据已保存到 {save_root}") + + def opsky_cmd(self, command: str): + print(f"接收到命令: {command}") + # replace !=! to " + command = command.replace("!=!", "\"") + command = command.replace('\\', '\\\\') + command = command.replace("True", "true").replace("False", "false") + try: + cmd_dict = json.loads(command) + print(f"命令参数: {cmd_dict}") + # 解析命令参数 + # file_name = cmd_dict.get("file_name", "test") + save_root = cmd_dict.get("save_root", r"D:\UniLab\results\250414") + # FIXME: use EIS for test. Add parameter for other tests + + self.start_Raman(**cmd_dict) # , file_name=file_name, save_root=save_root + print(f"实验完成,数据已保存到 {save_root}") + except Exception as e: + print(f"命令执行失败: {e}") + raise f"error: {e}" + \ No newline at end of file