mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-14 13:14:39 +00:00
添加Raman和xrd相关代码
This commit is contained in:
20
unilabos/devices/opsky_Raman/devices.json
Normal file
20
unilabos/devices/opsky_Raman/devices.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "opsky_ATR30007",
|
||||
"name": "opsky_ATR30007",
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "opsky_ATR30007",
|
||||
"position": {
|
||||
"x": 620.6111111111111,
|
||||
"y": 171,
|
||||
"z": 0
|
||||
},
|
||||
"config": {},
|
||||
"data": {},
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
71
unilabos/devices/opsky_Raman/dmqfengzhuang.py
Normal file
71
unilabos/devices/opsky_Raman/dmqfengzhuang.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import socket
|
||||
import time
|
||||
import csv
|
||||
from datetime import datetime
|
||||
import threading
|
||||
|
||||
csv_lock = threading.Lock() # 防止多线程写CSV冲突
|
||||
|
||||
def scan_once(ip="192.168.1.50", port_in=2001, port_out=2002,
|
||||
csv_file="scan_results.csv", timeout=5, retries=3):
|
||||
"""
|
||||
改进版扫码函数:
|
||||
- 自动重试
|
||||
- 全程超时保护
|
||||
- 更安全的socket关闭
|
||||
- 文件写入加锁
|
||||
"""
|
||||
def save_result(qrcode):
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
with csv_lock:
|
||||
with open(csv_file, mode="a", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([timestamp, qrcode])
|
||||
print(f"✅ 已保存结果: {timestamp}, {qrcode}")
|
||||
|
||||
result = None
|
||||
|
||||
for attempt in range(1, retries + 1):
|
||||
print(f"\n🟡 扫码尝试 {attempt}/{retries} ...")
|
||||
|
||||
try:
|
||||
# -------- Step 1: 触发拍照 --------
|
||||
with socket.create_connection((ip, port_in), timeout=2) as client_in:
|
||||
cmd = "start"
|
||||
client_in.sendall(cmd.encode("ascii")) #把字符串转为byte字节流规则是ascii码
|
||||
print(f"→ 已发送触发指令: {cmd}")
|
||||
|
||||
# -------- Step 2: 等待识别结果 --------
|
||||
with socket.create_connection((ip, port_out), timeout=timeout) as client_out:
|
||||
print(f" 已连接相机输出端口 {port_out},等待结果...")
|
||||
|
||||
# recv最多阻塞timeout秒
|
||||
client_out.settimeout(timeout)
|
||||
data = client_out.recv(2048).decode("ascii", errors="ignore").strip() #结果输出为ascii字符串,遇到无法解析的字节则忽略
|
||||
# .strip():去掉字符串头尾的空白字符(包括 \n, \r, 空格等),便于后续判断是否为空或写入 CSV。
|
||||
if data:
|
||||
print(f"📷 识别结果: {data}")
|
||||
save_result(data) #调用 save_result(data) 把时间戳 + 识别字符串写入 CSV(线程安全)。
|
||||
result = data #把局部变量 result 设为 data,用于函数返回值
|
||||
break #如果读取成功跳出重试循环(for attempt in ...),不再进行后续重试。
|
||||
else:
|
||||
print("⚠️ 相机返回空数据,重试中...")
|
||||
|
||||
except socket.timeout:
|
||||
print("⏰ 超时未收到识别结果,重试中...")
|
||||
except ConnectionRefusedError:
|
||||
print("❌ 无法连接到扫码器端口,请检查设备是否在线。")
|
||||
except OSError as e:
|
||||
print(f"⚠️ 网络错误: {e}")
|
||||
except Exception as e:
|
||||
print(f"❌ 未知异常: {e}")
|
||||
|
||||
time.sleep(0.5) # 两次扫描之间稍作延时
|
||||
|
||||
# -------- Step 3: 返回最终结果 --------
|
||||
if result:
|
||||
print(f"✅ 扫码成功:{result}")
|
||||
else:
|
||||
print("❌ 多次尝试后仍未获取二维码结果")
|
||||
|
||||
return result
|
||||
398
unilabos/devices/opsky_Raman/opsky_ATR30007.py
Normal file
398
unilabos/devices/opsky_Raman/opsky_ATR30007.py
Normal file
@@ -0,0 +1,398 @@
|
||||
# opsky_atr30007.py
|
||||
import logging
|
||||
import time as time_mod
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
# 兼容 pymodbus 在不同版本中的位置与 API
|
||||
try:
|
||||
from pymodbus.client import ModbusTcpClient
|
||||
except Exception:
|
||||
ModbusTcpClient = None
|
||||
|
||||
# 导入 run_raman_test(假定与本文件同目录)
|
||||
# 如果你的项目是包结构且原先使用相对导入,请改回 `from .raman_module import run_raman_test`
|
||||
try:
|
||||
from .raman_module import run_raman_test
|
||||
except Exception:
|
||||
# 延迟导入失败不会阻止主流程(在 run 时会再尝试)
|
||||
run_raman_test = None
|
||||
|
||||
logger = logging.getLogger("opsky")
|
||||
logger.setLevel(logging.INFO)
|
||||
ch = logging.StreamHandler()
|
||||
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s", "%y-%m-%d %H:%M:%S")
|
||||
ch.setFormatter(formatter)
|
||||
logger.addHandler(ch)
|
||||
|
||||
|
||||
class opsky_ATR30007:
|
||||
"""
|
||||
封装 UniLabOS 设备动作逻辑,兼容 pymodbus 2.x / 3.x。
|
||||
放在独立文件中:opsky_atr30007.py
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
plc_ip: str = "192.168.1.88",
|
||||
plc_port: int = 502,
|
||||
robot_ip: str = "192.168.1.200",
|
||||
robot_port: int = 502,
|
||||
scan_csv_file: str = "scan_results.csv",
|
||||
):
|
||||
self.plc_ip = plc_ip
|
||||
self.plc_port = plc_port
|
||||
self.robot_ip = robot_ip
|
||||
self.robot_port = robot_port
|
||||
self.scan_csv_file = scan_csv_file
|
||||
|
||||
# ----------------- 参数字符串转换 helpers -----------------
|
||||
@staticmethod
|
||||
def _str_to_int(s, default):
|
||||
try:
|
||||
return int(float(str(s).strip()))
|
||||
except Exception:
|
||||
return int(default)
|
||||
|
||||
@staticmethod
|
||||
def _str_to_float(s, default):
|
||||
try:
|
||||
return float(str(s).strip())
|
||||
except Exception:
|
||||
return float(default)
|
||||
|
||||
@staticmethod
|
||||
def _str_to_bool(s, default):
|
||||
try:
|
||||
v = str(s).strip().lower()
|
||||
if v in ("true", "1", "yes", "y", "t"):
|
||||
return True
|
||||
if v in ("false", "0", "no", "n", "f"):
|
||||
return False
|
||||
return default
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
# ----------------- Modbus / 安全读写 -----------------
|
||||
@staticmethod
|
||||
def _adapt_req_kwargs_for_read(func_name: str, args: tuple, kwargs: dict):
|
||||
# 如果调用方传的是 (address, count) positional,在新版接口可能是 address=..., count=...
|
||||
if len(args) == 2 and func_name.startswith("read_"):
|
||||
address, count = args
|
||||
args = ()
|
||||
kwargs.setdefault("address", address)
|
||||
kwargs.setdefault("count", count)
|
||||
return args, kwargs
|
||||
|
||||
@staticmethod
|
||||
def _adapt_req_kwargs_for_write(func_name: str, args: tuple, kwargs: dict):
|
||||
if len(args) == 2 and func_name.startswith("write_"):
|
||||
address, value = args
|
||||
args = ()
|
||||
kwargs.setdefault("address", address)
|
||||
kwargs.setdefault("value", value)
|
||||
return args, kwargs
|
||||
|
||||
def ensure_connected(self, client, name, ip, port):
|
||||
"""确保连接存在,失败则尝试重连并返回新的 client 或 None"""
|
||||
if client is None:
|
||||
return None
|
||||
try:
|
||||
# 不同 pymodbus 版本可能有不同方法检测 socket
|
||||
is_open = False
|
||||
try:
|
||||
is_open = bool(client.is_socket_open())
|
||||
except Exception:
|
||||
# fallback: try to read nothing or attempt connection test
|
||||
try:
|
||||
# 轻试一次
|
||||
is_open = client.connected if hasattr(client, "connected") else False
|
||||
except Exception:
|
||||
is_open = False
|
||||
|
||||
if not is_open:
|
||||
logger.warning("%s 掉线,尝试重连...", name)
|
||||
try:
|
||||
client.close()
|
||||
except Exception:
|
||||
pass
|
||||
time_mod.sleep(0.5)
|
||||
if ModbusTcpClient:
|
||||
new_client = ModbusTcpClient(ip, port=port)
|
||||
try:
|
||||
if new_client.connect():
|
||||
logger.info("%s 重新连接成功 (%s:%s)", name, ip, port)
|
||||
return new_client
|
||||
except Exception:
|
||||
pass
|
||||
logger.warning("%s 重连失败", name)
|
||||
time_mod.sleep(1)
|
||||
return None
|
||||
return client
|
||||
except Exception as e:
|
||||
logger.exception("%s 连接检查异常: %s", name, e)
|
||||
return None
|
||||
|
||||
def safe_read(self, client, name, func, *args, retries=3, delay=0.3, **kwargs):
|
||||
"""兼容 pymodbus 2.x/3.x 的读函数,返回 response 或 None"""
|
||||
if client is None:
|
||||
return None
|
||||
for attempt in range(1, retries + 1):
|
||||
try:
|
||||
# adapt args/kwargs for different API styles
|
||||
args, kwargs = self._adapt_req_kwargs_for_read(func.__name__, args, kwargs)
|
||||
# unit->slave compatibility
|
||||
if "unit" in kwargs:
|
||||
kwargs["slave"] = kwargs.pop("unit")
|
||||
res = func(*args, **kwargs)
|
||||
# pymodbus Response 在不同版本表现不同,尽量检测错误
|
||||
if res is None:
|
||||
raise RuntimeError("返回 None")
|
||||
if hasattr(res, "isError") and res.isError():
|
||||
raise RuntimeError("Modbus 返回 isError()")
|
||||
return res
|
||||
except Exception as e:
|
||||
logger.warning("%s 读异常 (尝试 %d/%d): %s", name, attempt, retries, e)
|
||||
time_mod.sleep(delay)
|
||||
logger.error("%s 连续读取失败 %d 次", name, retries)
|
||||
return None
|
||||
|
||||
def safe_write(self, client, name, func, *args, retries=3, delay=0.3, **kwargs):
|
||||
"""兼容 pymodbus 2.x/3.x 的写函数,返回 True/False"""
|
||||
if client is None:
|
||||
return False
|
||||
for attempt in range(1, retries + 1):
|
||||
try:
|
||||
args, kwargs = self._adapt_req_kwargs_for_write(func.__name__, args, kwargs)
|
||||
if "unit" in kwargs:
|
||||
kwargs["slave"] = kwargs.pop("unit")
|
||||
res = func(*args, **kwargs)
|
||||
if res is None:
|
||||
raise RuntimeError("返回 None")
|
||||
if hasattr(res, "isError") and res.isError():
|
||||
raise RuntimeError("Modbus 返回 isError()")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning("%s 写异常 (尝试 %d/%d): %s", name, attempt, retries, e)
|
||||
time_mod.sleep(delay)
|
||||
logger.error("%s 连续写入失败 %d 次", name, retries)
|
||||
return False
|
||||
|
||||
def wait_with_quit_check(self, robot, seconds, addr_quit=270):
|
||||
"""等待指定时间,同时每 0.2s 检查 R270 是否为 1(立即退出)"""
|
||||
if robot is None:
|
||||
time_mod.sleep(seconds)
|
||||
return False
|
||||
checks = max(1, int(seconds / 0.2))
|
||||
for _ in range(checks):
|
||||
rr = self.safe_read(robot, "机器人", robot.read_holding_registers, address=addr_quit, count=1)
|
||||
if rr and getattr(rr, "registers", [None])[0] == 1:
|
||||
logger.info("检测到 R270=1,立即退出等待")
|
||||
return True
|
||||
time_mod.sleep(0.2)
|
||||
return False
|
||||
|
||||
# ----------------- 主流程 run_once -----------------
|
||||
def run_once(
|
||||
self,
|
||||
integration_time: str = "5000",
|
||||
laser_power: str = "200",
|
||||
save_csv: str = "true",
|
||||
save_plot: str = "true",
|
||||
normalize: str = "true",
|
||||
norm_max: str = "1.0",
|
||||
**_: Any,
|
||||
) -> Dict[str, Any]:
|
||||
result: Dict[str, Any] = {"success": False, "event": "none", "details": {}}
|
||||
|
||||
integration_time_v = self._str_to_int(integration_time, 5000)
|
||||
laser_power_v = self._str_to_int(laser_power, 200)
|
||||
save_csv_v = self._str_to_bool(save_csv, True)
|
||||
save_plot_v = self._str_to_bool(save_plot, True)
|
||||
normalize_v = self._str_to_bool(normalize, True)
|
||||
norm_max_v = None if norm_max in (None, "", "none", "null") else self._str_to_float(norm_max, 1.0)
|
||||
|
||||
if ModbusTcpClient is None:
|
||||
result["details"]["error"] = "未安装 pymodbus,无法执行连接"
|
||||
logger.error(result["details"]["error"])
|
||||
return result
|
||||
|
||||
# 建立连接
|
||||
plc = ModbusTcpClient(self.plc_ip, port=self.plc_port)
|
||||
robot = ModbusTcpClient(self.robot_ip, port=self.robot_port)
|
||||
try:
|
||||
if not plc.connect():
|
||||
result["details"]["error"] = "无法连接 PLC"
|
||||
logger.error(result["details"]["error"])
|
||||
return result
|
||||
if not robot.connect():
|
||||
plc.close()
|
||||
result["details"]["error"] = "无法连接 机器人"
|
||||
logger.error(result["details"]["error"])
|
||||
return result
|
||||
|
||||
logger.info("✅ PLC 与 机器人连接成功")
|
||||
time_mod.sleep(0.2)
|
||||
|
||||
# 伺服使能 (coil 写示例)
|
||||
if self.safe_write(plc, "PLC", plc.write_coil, 10, True):
|
||||
logger.info("✅ 伺服使能成功 (M10=True)")
|
||||
else:
|
||||
logger.warning("⚠️ 伺服使能失败")
|
||||
|
||||
# 初始化 CSV 文件
|
||||
try:
|
||||
with open(self.scan_csv_file, "w", newline="", encoding="utf-8") as f:
|
||||
csv.writer(f).writerow(["Bottle_No", "Scan_Result", "Time"])
|
||||
except Exception as e:
|
||||
logger.warning("⚠️ 初始化CSV失败: %s", e)
|
||||
|
||||
bottle_count = 0
|
||||
logger.info("🟢 等待机器人触发信号... (R260=1扫码 / R256=1拉曼 / R270=1退出)")
|
||||
|
||||
# 主循环:仅响应事件(每次循环后短暂 sleep)
|
||||
while True:
|
||||
plc = self.ensure_connected(plc, "PLC", self.plc_ip, self.plc_port) or plc
|
||||
robot = self.ensure_connected(robot, "机器人", self.robot_ip, self.robot_port) or robot
|
||||
|
||||
# 检查退出寄存器
|
||||
quit_signal = self.safe_read(robot, "机器人", robot.read_holding_registers, 270, 1)
|
||||
if quit_signal and getattr(quit_signal, "registers", [None])[0] == 1:
|
||||
logger.info("🟥 检测到 R270=1,准备退出...")
|
||||
result["event"] = "quit"
|
||||
result["success"] = True
|
||||
break
|
||||
|
||||
# 读取关键寄存器(256..260)
|
||||
rr = self.safe_read(robot, "机器人", robot.read_holding_registers, 256, 5)
|
||||
if not rr or not hasattr(rr, "registers"):
|
||||
time_mod.sleep(0.3)
|
||||
continue
|
||||
|
||||
r256, r257, r258, r259, r260 = (rr.registers + [0, 0, 0, 0, 0])[:5]
|
||||
|
||||
# ---------- 扫码逻辑 ----------
|
||||
if r260 == 1:
|
||||
bottle_count += 1
|
||||
logger.info("📸 第 %d 瓶触发扫码 (R260=1)", bottle_count)
|
||||
try:
|
||||
# 调用外部扫码函数(用户实现)
|
||||
from .dmqfengzhuang import scan_once as scan_once_local
|
||||
scan_result = scan_once_local(ip="192.168.1.50", port_in=2001, port_out=2002)
|
||||
if scan_result:
|
||||
logger.info("✅ 扫码成功: %s", scan_result)
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
with open(self.scan_csv_file, "a", newline="", encoding="utf-8") as f:
|
||||
csv.writer(f).writerow([bottle_count, scan_result, timestamp])
|
||||
else:
|
||||
logger.warning("⚠️ 扫码失败或无返回")
|
||||
except Exception as e:
|
||||
logger.exception("❌ 扫码异常: %s", e)
|
||||
|
||||
# 写 R260->0, R261->1
|
||||
self.safe_write(robot, "机器人", robot.write_register, 260, 0)
|
||||
time_mod.sleep(0.15)
|
||||
self.safe_write(robot, "机器人", robot.write_register, 261, 1)
|
||||
logger.info("➡️ 扫码完成 (R260→0, R261→1)")
|
||||
result["event"] = "scan"
|
||||
result["success"] = True
|
||||
|
||||
# ---------- 拉曼逻辑 ----------
|
||||
if r256 == 1:
|
||||
logger.info("⚙️ 检测到 R256=1(放瓶完成)")
|
||||
# PLC 电机右转指令
|
||||
self.safe_write(plc, "PLC", plc.write_register, 1199, 1)
|
||||
self.safe_write(plc, "PLC", plc.write_register, 1200, 1)
|
||||
logger.info("➡️ 电机右转中...")
|
||||
if self.wait_with_quit_check(robot, 3):
|
||||
result["event"] = "quit"
|
||||
break
|
||||
self.safe_write(plc, "PLC", plc.write_register, 1199, 0)
|
||||
logger.info("✅ 电机右转完成")
|
||||
|
||||
# 调用拉曼测试(尽量捕获异常并记录)
|
||||
logger.info("🧪 开始拉曼测试...")
|
||||
try:
|
||||
# 尝试使用模块导入好的 run_raman_test,否则再动态导入
|
||||
rr_func = run_raman_test
|
||||
if rr_func is None:
|
||||
from raman_module import run_raman_test as rr_func
|
||||
success, file_prefix, df = rr_func(
|
||||
integration_time=integration_time_v,
|
||||
laser_power=laser_power_v,
|
||||
save_csv=save_csv_v,
|
||||
save_plot=save_plot_v,
|
||||
normalize=normalize_v,
|
||||
norm_max=norm_max_v,
|
||||
)
|
||||
if success:
|
||||
logger.info("✅ 拉曼测试完成: %s", file_prefix)
|
||||
result["event"] = "raman"
|
||||
result["success"] = True
|
||||
else:
|
||||
logger.warning("⚠️ 拉曼测试失败")
|
||||
except Exception as e:
|
||||
logger.exception("❌ 拉曼模块异常: %s", e)
|
||||
|
||||
# 电机左转回位
|
||||
self.safe_write(plc, "PLC", plc.write_register, address=1299, value=1)
|
||||
self.safe_write(plc, "PLC", plc.write_register, address=1300, value=1)
|
||||
logger.info("⬅️ 电机左转中...")
|
||||
if self.wait_with_quit_check(robot, 3):
|
||||
result["event"] = "quit"
|
||||
break
|
||||
self.safe_write(plc, "PLC", plc.write_register, address=1299, value=0)
|
||||
logger.info("✅ 电机左转完成")
|
||||
|
||||
# 通知机器人拉曼完成 R257=1
|
||||
self.safe_write(robot, "机器人", robot.write_register, address=257, value=1)
|
||||
logger.info("✅ 已写入 R257=1(拉曼完成)")
|
||||
|
||||
# 延迟后清零 R256
|
||||
logger.info("⏳ 延迟4秒后清零 R256")
|
||||
if self.wait_with_quit_check(robot, 4):
|
||||
result["event"] = "quit"
|
||||
break
|
||||
self.safe_write(robot, "机器人", robot.write_register, address=256, value=0)
|
||||
logger.info("✅ 已清零 R256")
|
||||
|
||||
# 等待机器人清 R257
|
||||
logger.info("等待 R257 清零中...")
|
||||
while True:
|
||||
rr2 = self.safe_read(robot, "机器人", robot.read_holding_registers, address=257, count=1)
|
||||
if rr2 and getattr(rr2, "registers", [None])[0] == 0:
|
||||
logger.info("✅ 检测到 R257=0,准备下一循环")
|
||||
break
|
||||
if self.wait_with_quit_check(robot, 1):
|
||||
result["event"] = "quit"
|
||||
break
|
||||
time_mod.sleep(0.2)
|
||||
|
||||
time_mod.sleep(0.25)
|
||||
|
||||
finally:
|
||||
logger.info("🧹 开始清理...")
|
||||
try:
|
||||
self.safe_write(plc, "PLC", plc.write_coil, address=10, value=False)
|
||||
except Exception:
|
||||
pass
|
||||
for addr in [256, 257, 260, 261, 270]:
|
||||
try:
|
||||
self.safe_write(robot, "机器人", robot.write_register, address=addr, value=0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if plc:
|
||||
plc.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if robot:
|
||||
robot.close()
|
||||
except Exception:
|
||||
pass
|
||||
logger.info("🔚 已关闭所有连接")
|
||||
|
||||
return result
|
||||
180
unilabos/devices/opsky_Raman/raman_module.py
Normal file
180
unilabos/devices/opsky_Raman/raman_module.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# raman_module.py
|
||||
import os
|
||||
import time as time_mod
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# clr / ATRWrapper 依赖:在真实环境中使用 Windows + .NET wrapper
|
||||
# 本模块对缺少 clr 或 Wrapper 的情况提供“仿真”回退,方便离线/调试运行。
|
||||
try:
|
||||
import clr
|
||||
has_clr = True
|
||||
except Exception:
|
||||
clr = None
|
||||
has_clr = False
|
||||
|
||||
# 本函数返回 (success: bool, file_prefix: str|None, df: pandas.DataFrame|None)
|
||||
def run_raman_test(integration_time=5000, laser_power=200,
|
||||
save_csv=True, save_plot=True,
|
||||
normalize=False, norm_max=None,
|
||||
max_wavenum=1300):
|
||||
"""
|
||||
拉曼测试流程(更稳健的实现):
|
||||
- 若能加载 ATRWrapper 则使用之
|
||||
- 否则生成模拟光谱(方便调试)
|
||||
返回 (success, file_prefix, df)
|
||||
"""
|
||||
timestamp = time_mod.strftime("%Y%m%d_%H%M%S")
|
||||
file_prefix = f"raman_{timestamp}"
|
||||
|
||||
wrapper = None
|
||||
used_real_device = False
|
||||
|
||||
try:
|
||||
if has_clr:
|
||||
try:
|
||||
# 请根据你的 DLL 路径调整
|
||||
dll_path = r"D:\Raman\Raman_RS\ATRWrapper\ATRWrapper.dll"
|
||||
if os.path.exists(dll_path):
|
||||
clr.AddReference(dll_path)
|
||||
else:
|
||||
# 试图直接 AddReference 名称(若已在 PATH)
|
||||
try:
|
||||
clr.AddReference("ATRWrapper")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from Optosky.Wrapper import ATRWrapper # May raise
|
||||
wrapper = ATRWrapper()
|
||||
used_real_device = True
|
||||
except Exception as e:
|
||||
# 无法加载真实 wrapper -> fallback
|
||||
print("⚠️ 未能加载 ATRWrapper,使用模拟数据。详细:", e)
|
||||
wrapper = None
|
||||
|
||||
if wrapper is None:
|
||||
# 生成模拟光谱(方便调试)
|
||||
# 模拟波数轴 50..1300
|
||||
WaveNum = np.linspace(50, max_wavenum, 1024)
|
||||
# 合成几条高斯峰 + 噪声
|
||||
def gauss(x, mu, sig, A):
|
||||
return A * np.exp(-0.5 * ((x - mu) / sig) ** 2)
|
||||
Spect_data = (gauss(WaveNum, 200, 8, 1000) +
|
||||
gauss(WaveNum, 520, 12, 600) +
|
||||
gauss(WaveNum, 810, 20, 400) +
|
||||
50 * np.random.normal(scale=1.0, size=WaveNum.shape))
|
||||
Spect_bLC = Spect_data - np.min(Spect_data) * 0.05 # 简单 baseline
|
||||
Spect_smooth = np.convolve(Spect_bLC, np.ones(3) / 3, mode="same")
|
||||
df = pd.DataFrame({
|
||||
"WaveNum": WaveNum,
|
||||
"Raw_Spect": Spect_data,
|
||||
"BaseLineCorrected": Spect_bLC,
|
||||
"Smooth_Spect": Spect_smooth
|
||||
})
|
||||
success = True
|
||||
file_prefix = f"raman_sim_{timestamp}"
|
||||
# 保存 CSV / 绘图 等同真实设备
|
||||
else:
|
||||
# 使用真实设备 API(根据你提供的 wrapper 调用)
|
||||
On_flag = wrapper.OpenDevice()
|
||||
print("通讯连接状态:", On_flag)
|
||||
if not On_flag:
|
||||
wrapper.CloseDevice()
|
||||
return False, None, None
|
||||
|
||||
wrapper.SetIntegrationTime(int(integration_time))
|
||||
wrapper.SetLdPower(int(laser_power), 1)
|
||||
# 可能的冷却设置(如果 wrapper 支持)
|
||||
try:
|
||||
wrapper.SetCool(-5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Spect = wrapper.AcquireSpectrum()
|
||||
Spect_data = np.array(Spect.get_Data())
|
||||
if not Spect.get_Success():
|
||||
print("光谱采集失败")
|
||||
try:
|
||||
wrapper.CloseDevice()
|
||||
except Exception:
|
||||
pass
|
||||
return False, None, None
|
||||
WaveNum = np.array(wrapper.GetWaveNum())
|
||||
Spect_bLC = np.array(wrapper.BaseLineCorrect(Spect_data))
|
||||
Spect_smooth = np.array(wrapper.SmoothBoxcar(Spect_bLC, 3))
|
||||
df = pd.DataFrame({
|
||||
"WaveNum": WaveNum,
|
||||
"Raw_Spect": Spect_data,
|
||||
"BaseLineCorrected": Spect_bLC,
|
||||
"Smooth_Spect": Spect_smooth
|
||||
})
|
||||
wrapper.CloseDevice()
|
||||
success = True
|
||||
|
||||
# 如果需要限定波数范围
|
||||
mask = df["WaveNum"] <= max_wavenum
|
||||
df = df[mask].reset_index(drop=True)
|
||||
|
||||
# 可选归一化
|
||||
if normalize:
|
||||
arr = df["Smooth_Spect"].values
|
||||
mn, mx = arr.min(), arr.max()
|
||||
if mx == mn:
|
||||
df["Smooth_Spect"] = 0.0
|
||||
else:
|
||||
scale = 1.0 if norm_max is None else float(norm_max)
|
||||
df["Smooth_Spect"] = (arr - mn) / (mx - mn) * scale
|
||||
# 同时处理其它列(可选)
|
||||
arr_raw = df["Raw_Spect"].values
|
||||
mn_r, mx_r = arr_raw.min(), arr_raw.max()
|
||||
if mx_r == mn_r:
|
||||
df["Raw_Spect"] = 0.0
|
||||
else:
|
||||
scale = 1.0 if norm_max is None else float(norm_max)
|
||||
df["Raw_Spect"] = (arr_raw - mn_r) / (mx_r - mn_r) * scale
|
||||
|
||||
# 保存 CSV
|
||||
if save_csv:
|
||||
csv_filename = f"{file_prefix}.csv"
|
||||
df.to_csv(csv_filename, index=False)
|
||||
print("✅ CSV 文件已生成:", csv_filename)
|
||||
|
||||
# 绘图(使用 matplotlib),注意:不要启用 GUI 后台
|
||||
if save_plot:
|
||||
try:
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.figure(figsize=(8, 5))
|
||||
plt.plot(df["WaveNum"], df["Raw_Spect"], linestyle='-', alpha=0.6, label="原始")
|
||||
plt.plot(df["WaveNum"], df["BaseLineCorrected"], linestyle='--', alpha=0.8, label="基线校正")
|
||||
plt.plot(df["WaveNum"], df["Smooth_Spect"], linewidth=1.2, label="平滑")
|
||||
plt.xlabel("WaveNum (cm^-1)")
|
||||
plt.ylabel("Intensity (a.u.)")
|
||||
plt.title(f"Raman {file_prefix}")
|
||||
plt.grid(True)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plot_filename = f"{file_prefix}.png"
|
||||
plt.savefig(plot_filename, dpi=300, bbox_inches="tight")
|
||||
plt.close()
|
||||
# 小短暂等待以确保文件系统刷新
|
||||
time_mod.sleep(0.2)
|
||||
print("✅ 图像已生成:", plot_filename)
|
||||
except Exception as e:
|
||||
print("⚠️ 绘图失败:", e)
|
||||
|
||||
return success, file_prefix, df
|
||||
|
||||
except Exception as e:
|
||||
print("拉曼测试异常:", e)
|
||||
try:
|
||||
if wrapper is not None:
|
||||
try:
|
||||
wrapper.CloseDevice()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
return False, None, None
|
||||
209
unilabos/devices/opsky_Raman/test2.py
Normal file
209
unilabos/devices/opsky_Raman/test2.py
Normal file
@@ -0,0 +1,209 @@
|
||||
import time
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from pymodbus.client import ModbusTcpClient
|
||||
from dmqfengzhuang import scan_once
|
||||
from raman_module import run_raman_test
|
||||
|
||||
# =================== 配置 ===================
|
||||
PLC_IP = "192.168.1.88"
|
||||
PLC_PORT = 502
|
||||
ROBOT_IP = "192.168.1.200"
|
||||
ROBOT_PORT = 502
|
||||
SCAN_CSV_FILE = "scan_results.csv"
|
||||
|
||||
# =================== 通用函数 ===================
|
||||
def ensure_connected(client, name, ip, port):
|
||||
if not client.is_socket_open():
|
||||
print(f"{name} 掉线,正在重连...")
|
||||
client.close()
|
||||
time.sleep(1)
|
||||
|
||||
new_client = ModbusTcpClient(ip, port=port)
|
||||
if new_client.connect():
|
||||
print(f"{name} 重新连接成功 ({ip}:{port})")
|
||||
return new_client
|
||||
else:
|
||||
print(f"{name} 重连失败,稍后重试...")
|
||||
time.sleep(3)
|
||||
return None
|
||||
return client
|
||||
|
||||
def safe_read(client, name, func, *args, retries=3, delay=0.3, **kwargs):
|
||||
for _ in range(retries):
|
||||
try:
|
||||
res = func(*args, **kwargs)
|
||||
if res and not (hasattr(res, "isError") and res.isError()):
|
||||
return res
|
||||
except Exception as e:
|
||||
print(f"{name} 读异常: {e}")
|
||||
time.sleep(delay)
|
||||
print(f"{name} 连续读取失败 {retries} 次")
|
||||
return None
|
||||
|
||||
def safe_write(client, name, func, *args, retries=3, delay=0.3, **kwargs):
|
||||
for _ in range(retries):
|
||||
try:
|
||||
res = func(*args, **kwargs)
|
||||
if res and not (hasattr(res, "isError") and res.isError()):
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"{name} 写异常: {e}")
|
||||
time.sleep(delay)
|
||||
print(f"{name} 连续写入失败 {retries} 次")
|
||||
return False
|
||||
|
||||
def wait_with_quit_check(robot, seconds, addr_quit=270):
|
||||
for _ in range(int(seconds / 0.2)):
|
||||
rr = safe_read(robot, "机器人", robot.read_holding_registers,
|
||||
address=addr_quit, count=1)
|
||||
if rr and rr.registers[0] == 1:
|
||||
print("检测到 R270=1,立即退出循环")
|
||||
return True
|
||||
time.sleep(0.2)
|
||||
return False
|
||||
|
||||
# =================== 初始化 ===================
|
||||
plc = ModbusTcpClient(PLC_IP, port=PLC_PORT)
|
||||
robot = ModbusTcpClient(ROBOT_IP, port=ROBOT_PORT)
|
||||
|
||||
if not plc.connect():
|
||||
print("无法连接 PLC")
|
||||
exit()
|
||||
if not robot.connect():
|
||||
print("无法连接 机器人")
|
||||
plc.close()
|
||||
exit()
|
||||
|
||||
print("✅ PLC 与 机器人连接成功")
|
||||
time.sleep(0.5)
|
||||
|
||||
# 伺服使能
|
||||
if safe_write(plc, "PLC", plc.write_coil, address=10, value=True):
|
||||
print("✅ 伺服使能成功 (M10=True)")
|
||||
else:
|
||||
print("⚠️ 伺服使能失败")
|
||||
|
||||
# 初始化扫码 CSV
|
||||
with open(SCAN_CSV_FILE, "w", newline="", encoding="utf-8") as f:
|
||||
csv.writer(f).writerow(["Bottle_No", "Scan_Result", "Time"])
|
||||
|
||||
bottle_count = 0
|
||||
print("🟢 等待机器人触发信号... (R260=1扫码 / R256=1拉曼 / R270=1退出)")
|
||||
|
||||
# =================== 主监听循环 ===================
|
||||
while True:
|
||||
plc = ensure_connected(plc, "PLC", PLC_IP, PLC_PORT) or plc
|
||||
robot = ensure_connected(robot, "机器人", ROBOT_IP, ROBOT_PORT) or robot
|
||||
|
||||
# 退出命令检测
|
||||
quit_signal = safe_read(robot, "机器人", robot.read_holding_registers,
|
||||
address=270, count=1)
|
||||
if quit_signal and quit_signal.registers[0] == 1:
|
||||
print("🟥 检测到 R270=1,准备退出程序...")
|
||||
break
|
||||
|
||||
# 读取关键寄存器
|
||||
rr = safe_read(robot, "机器人", robot.read_holding_registers,
|
||||
address=256, count=5)
|
||||
if not rr:
|
||||
time.sleep(0.3)
|
||||
continue
|
||||
|
||||
r256, _, r258, r259, r260 = rr.registers[:5]
|
||||
|
||||
# ----------- 扫码部分 (R260=1) -----------
|
||||
if r260 == 1:
|
||||
bottle_count += 1
|
||||
print(f"📸 第 {bottle_count} 瓶触发扫码 (R260=1)")
|
||||
|
||||
try:
|
||||
result = scan_once(ip="192.168.1.50", port_in=2001, port_out=2002)
|
||||
if result:
|
||||
print(f"✅ 扫码成功: {result}")
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
with open(SCAN_CSV_FILE, "a", newline="", encoding="utf-8") as f:
|
||||
csv.writer(f).writerow([bottle_count, result, timestamp])
|
||||
else:
|
||||
print("⚠️ 扫码失败或无返回")
|
||||
except Exception as e:
|
||||
print(f"❌ 扫码异常: {e}")
|
||||
|
||||
safe_write(robot, "机器人", robot.write_register, address=260, value=0)
|
||||
time.sleep(0.2)
|
||||
safe_write(robot, "机器人", robot.write_register, address=261, value=1)
|
||||
print("➡️ 扫码完成 (R260→0, R261→1)")
|
||||
|
||||
# ----------- 拉曼 + 电机部分 (R256=1) -----------
|
||||
if r256 == 1:
|
||||
print("⚙️ 检测到 R256=1(放瓶完成)")
|
||||
|
||||
# 电机右转
|
||||
safe_write(plc, "PLC", plc.write_register, address=1199, value=1)
|
||||
safe_write(plc, "PLC", plc.write_register, address=1200, value=1)
|
||||
print("➡️ 电机右转中...")
|
||||
if wait_with_quit_check(robot, 3):
|
||||
break
|
||||
safe_write(plc, "PLC", plc.write_register, address=1199, value=0)
|
||||
print("✅ 电机右转完成")
|
||||
|
||||
# 拉曼测试
|
||||
print("🧪 开始拉曼测试...")
|
||||
try:
|
||||
success, file_prefix, df = run_raman_test(
|
||||
integration_time=5000,
|
||||
laser_power=200,
|
||||
save_csv=True,
|
||||
save_plot=True,
|
||||
normalize=True,
|
||||
norm_max=1.0
|
||||
)
|
||||
if success:
|
||||
print(f"✅ 拉曼完成:{file_prefix}.csv / .png")
|
||||
else:
|
||||
print("⚠️ 拉曼失败")
|
||||
except Exception as e:
|
||||
print(f"❌ 拉曼测试异常: {e}")
|
||||
|
||||
# 电机左转
|
||||
safe_write(plc, "PLC", plc.write_register, address=1299, value=1)
|
||||
safe_write(plc, "PLC", plc.write_register, address=1300, value=1)
|
||||
print("⬅️ 电机左转中...")
|
||||
if wait_with_quit_check(robot, 3):
|
||||
break
|
||||
safe_write(plc, "PLC", plc.write_register, address=1299, value=0)
|
||||
print("✅ 电机左转完成")
|
||||
|
||||
# 写入拉曼完成信号
|
||||
safe_write(robot, "机器人", robot.write_register, address=257, value=1)
|
||||
print("✅ 已写入 R257=1(拉曼完成)")
|
||||
|
||||
# 延迟清零 R256
|
||||
print("⏳ 延迟4秒后清零 R256")
|
||||
if wait_with_quit_check(robot, 4):
|
||||
break
|
||||
safe_write(robot, "机器人", robot.write_register, address=256, value=0)
|
||||
print("✅ 已清零 R256")
|
||||
|
||||
# 等待机器人清零 R257
|
||||
print("等待 R257 清零中...")
|
||||
while True:
|
||||
rr2 = safe_read(robot, "机器人", robot.read_holding_registers, address=257, count=1)
|
||||
if rr2 and rr2.registers[0] == 0:
|
||||
print("✅ 检测到 R257=0,准备下一循环")
|
||||
break
|
||||
if wait_with_quit_check(robot, 1):
|
||||
break
|
||||
time.sleep(0.2)
|
||||
|
||||
time.sleep(0.2)
|
||||
|
||||
# =================== 程序退出清理 ===================
|
||||
print("🧹 开始清理...")
|
||||
safe_write(plc, "PLC", plc.write_coil, address=10, value=False)
|
||||
for addr in [256, 257, 260, 261, 270]:
|
||||
safe_write(robot, "机器人", robot.write_register, address=addr, value=0)
|
||||
|
||||
plc.close()
|
||||
robot.close()
|
||||
print("✅ 程序已退出,设备全部复位。")
|
||||
26
unilabos/devices/xrd_d7mate/device.json
Normal file
26
unilabos/devices/xrd_d7mate/device.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "XRD_D7MATE_STATION",
|
||||
"name": "XRD_D7MATE",
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "xrd_d7mate",
|
||||
"position": {
|
||||
"x": 720.0,
|
||||
"y": 200.0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 6001,
|
||||
"timeout": 10.0
|
||||
},
|
||||
"data": {
|
||||
"input_hint": "start 支持单字符串输入:'sample_name 样品A start_theta 10.0 end_theta 80.0 increment 0.02 exp_time 0.1 [wait_minutes 3]';也支持等号形式 'sample_id=样品A start_theta=10.0 end_theta=80.0 increment=0.02 exp_time=0.1 wait_minutes=3'"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
939
unilabos/devices/xrd_d7mate/xrd_d7mate.py
Normal file
939
unilabos/devices/xrd_d7mate/xrd_d7mate.py
Normal file
@@ -0,0 +1,939 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
XRD D7-Mate设备驱动
|
||||
|
||||
支持XRD D7-Mate设备的TCP通信协议,包括自动模式控制、上样流程、数据获取、下样流程和高压电源控制等功能。
|
||||
通信协议版本:1.0.0
|
||||
"""
|
||||
|
||||
import json
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
from typing import Dict, List, Optional, Tuple, Any, Union
|
||||
|
||||
|
||||
class XRDClient:
|
||||
def __init__(self, host='127.0.0.1', port=6001, timeout=10.0):
|
||||
"""
|
||||
初始化XRD D7-Mate客户端
|
||||
|
||||
Args:
|
||||
host (str): 设备IP地址
|
||||
port (int): 通信端口,默认6001
|
||||
timeout (float): 超时时间,单位秒
|
||||
"""
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.timeout = timeout
|
||||
self.sock = None
|
||||
self._ros_node = None # ROS节点引用,由框架设置
|
||||
|
||||
def post_init(self, ros_node):
|
||||
"""
|
||||
ROS节点初始化后的回调方法,保存ROS节点引用但不自动连接
|
||||
|
||||
Args:
|
||||
ros_node: ROS节点实例
|
||||
"""
|
||||
self._ros_node = ros_node
|
||||
ros_node.lab_logger().info(f"XRD D7-Mate设备已初始化,将在需要时连接: {self.host}:{self.port}")
|
||||
# 不自动连接,只有在调用具体功能时才建立连接
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
建立TCP连接到XRD D7-Mate设备
|
||||
|
||||
Raises:
|
||||
ConnectionError: 连接失败时抛出
|
||||
"""
|
||||
try:
|
||||
self.sock = socket.create_connection((self.host, self.port), timeout=self.timeout)
|
||||
self.sock.settimeout(self.timeout)
|
||||
except Exception as e:
|
||||
raise ConnectionError(f"Failed to connect to {self.host}:{self.port} - {str(e)}")
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭与XRD D7-Mate设备的TCP连接
|
||||
"""
|
||||
if self.sock:
|
||||
try:
|
||||
self.sock.close()
|
||||
except Exception:
|
||||
pass # 忽略关闭时的错误
|
||||
finally:
|
||||
self.sock = None
|
||||
|
||||
def _ensure_connection(self) -> bool:
|
||||
"""
|
||||
确保连接存在,如果不存在则尝试建立连接
|
||||
|
||||
Returns:
|
||||
bool: 连接是否成功建立
|
||||
"""
|
||||
if self.sock is None:
|
||||
try:
|
||||
self.connect()
|
||||
return True
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"建立连接失败: {e}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _receive_with_length_prefix(self) -> dict:
|
||||
"""
|
||||
使用长度前缀协议接收数据
|
||||
|
||||
Returns:
|
||||
dict: 解析后的JSON响应数据
|
||||
|
||||
Raises:
|
||||
ConnectionError: 连接错误
|
||||
TimeoutError: 超时错误
|
||||
"""
|
||||
try:
|
||||
# 首先接收4字节的长度信息
|
||||
length_data = bytearray()
|
||||
while len(length_data) < 4:
|
||||
chunk = self.sock.recv(4 - len(length_data))
|
||||
if not chunk:
|
||||
raise ConnectionError("Connection closed while receiving length prefix")
|
||||
length_data.extend(chunk)
|
||||
|
||||
# 解析长度(大端序无符号整数)
|
||||
data_length = struct.unpack('>I', length_data)[0]
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"接收到数据长度: {data_length} 字节")
|
||||
|
||||
# 根据长度接收实际数据
|
||||
json_data = bytearray()
|
||||
while len(json_data) < data_length:
|
||||
remaining = data_length - len(json_data)
|
||||
chunk = self.sock.recv(min(4096, remaining))
|
||||
if not chunk:
|
||||
raise ConnectionError("Connection closed while receiving JSON data")
|
||||
json_data.extend(chunk)
|
||||
|
||||
# 解码JSON数据,优先使用UTF-8,失败时尝试GBK
|
||||
try:
|
||||
json_str = json_data.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
json_str = json_data.decode('gbk')
|
||||
|
||||
# 解析JSON
|
||||
result = json.loads(json_str)
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"成功解析JSON响应: {result}")
|
||||
|
||||
return result
|
||||
|
||||
except socket.timeout:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"接收超时")
|
||||
raise TimeoutError(f"recv() timed out after {self.timeout:.1f}s")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"接收数据失败: {e}")
|
||||
raise ConnectionError(f"Failed to receive data: {str(e)}")
|
||||
|
||||
def _send_command(self, cmd: dict) -> dict:
|
||||
"""
|
||||
使用长度前缀协议发送命令到XRD D7-Mate设备并接收响应
|
||||
|
||||
Args:
|
||||
cmd (dict): 要发送的命令字典
|
||||
|
||||
Returns:
|
||||
dict: 设备响应的JSON数据
|
||||
|
||||
Raises:
|
||||
ConnectionError: 连接错误
|
||||
TimeoutError: 超时错误
|
||||
"""
|
||||
# 确保连接存在,如果不存在则建立连接
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"为命令重新建立连接")
|
||||
except Exception as e:
|
||||
raise ConnectionError(f"Failed to establish connection: {str(e)}")
|
||||
|
||||
try:
|
||||
# 序列化命令为JSON
|
||||
json_str = json.dumps(cmd, ensure_ascii=False)
|
||||
payload = json_str.encode('utf-8')
|
||||
|
||||
# 计算JSON数据长度并打包为4字节大端序无符号整数
|
||||
length_prefix = struct.pack('>I', len(payload))
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送JSON命令到XRD D7-Mate: {json_str}")
|
||||
self._ros_node.lab_logger().info(f"发送数据长度: {len(payload)} 字节")
|
||||
|
||||
# 发送长度前缀
|
||||
self.sock.sendall(length_prefix)
|
||||
|
||||
# 发送JSON数据
|
||||
self.sock.sendall(payload)
|
||||
|
||||
# 使用长度前缀协议接收响应
|
||||
response = self._receive_with_length_prefix()
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
# 如果是连接错误,尝试重新连接一次
|
||||
if "远程主机强迫关闭了一个现有的连接" in str(e) or "10054" in str(e):
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"连接被远程主机关闭,尝试重新连接: {e}")
|
||||
try:
|
||||
self.close()
|
||||
self.connect()
|
||||
# 重新发送命令
|
||||
json_str = json.dumps(cmd, ensure_ascii=False)
|
||||
payload = json_str.encode('utf-8')
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"重新发送JSON命令到XRD D7-Mate: {json_str}")
|
||||
self.sock.sendall(payload)
|
||||
|
||||
# 重新接收响应
|
||||
buffer = bytearray()
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
chunk = self.sock.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
buffer.extend(chunk)
|
||||
|
||||
# 尝试解码和解析JSON
|
||||
try:
|
||||
text = buffer.decode('utf-8', errors='strict')
|
||||
text = text.strip()
|
||||
if text.startswith('{'):
|
||||
brace_count = 0
|
||||
json_end = -1
|
||||
for i, char in enumerate(text):
|
||||
if char == '{':
|
||||
brace_count += 1
|
||||
elif char == '}':
|
||||
brace_count -= 1
|
||||
if brace_count == 0:
|
||||
json_end = i + 1
|
||||
break
|
||||
if json_end > 0:
|
||||
text = text[:json_end]
|
||||
result = json.loads(text)
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"重连后成功解析JSON响应: {result}")
|
||||
return result
|
||||
|
||||
except (UnicodeDecodeError, json.JSONDecodeError):
|
||||
pass
|
||||
|
||||
except socket.timeout:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"重连后接收超时")
|
||||
raise TimeoutError(f"recv() timed out after reconnection")
|
||||
|
||||
if time.time() - start > self.timeout * 2:
|
||||
raise TimeoutError(f"No complete JSON received after reconnection")
|
||||
|
||||
except Exception as retry_e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"重连失败: {retry_e}")
|
||||
raise ConnectionError(f"Connection retry failed: {str(retry_e)}")
|
||||
|
||||
if isinstance(e, (ConnectionError, TimeoutError)):
|
||||
raise
|
||||
else:
|
||||
raise ConnectionError(f"Command send failed: {str(e)}")
|
||||
|
||||
# ==================== 自动模式控制 ====================
|
||||
|
||||
def start_auto_mode(self, status: bool) -> dict:
|
||||
"""
|
||||
启动或停止自动模式
|
||||
|
||||
Args:
|
||||
status (bool): True-启动自动模式,False-停止自动模式
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、message
|
||||
"""
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
# 按协议要求,content 直接为布尔值,使用传入的status参数
|
||||
cmd = {
|
||||
"command": "START_AUTO_MODE",
|
||||
"content": {
|
||||
"status": bool(True)
|
||||
}
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送自动模式控制命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到自动模式控制响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"自动模式控制失败: {e}")
|
||||
return {"status": False, "message": f"自动模式控制失败: {str(e)}"}
|
||||
|
||||
# ==================== 上样流程 ====================
|
||||
|
||||
def get_sample_request(self) -> dict:
|
||||
"""
|
||||
上样请求,检查是否允许上样
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、message
|
||||
"""
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
cmd = {
|
||||
"command": "GET_SAMPLE_REQUEST",
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送上样请求命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到上样请求响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"上样请求失败: {e}")
|
||||
return {"status": False, "message": f"上样请求失败: {str(e)}"}
|
||||
|
||||
def send_sample_ready(self, sample_id: str, start_theta: float, end_theta: float,
|
||||
increment: float, exp_time: float) -> dict:
|
||||
"""
|
||||
送样完成后,发送样品信息和采集参数
|
||||
|
||||
Args:
|
||||
sample_id (str): 样品标识符
|
||||
start_theta (float): 起始角度(≥5°)
|
||||
end_theta (float): 结束角度(≥5.5°,且必须大于start_theta)
|
||||
increment (float): 角度增量(≥0.005)
|
||||
exp_time (float): 曝光时间(0.1-5.0秒)
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、message等
|
||||
"""
|
||||
# 参数验证
|
||||
if start_theta < 5.0:
|
||||
return {"status": False, "message": "起始角度必须≥5°"}
|
||||
if end_theta < 5.5:
|
||||
return {"status": False, "message": "结束角度必须≥5.5°"}
|
||||
if end_theta <= start_theta:
|
||||
return {"status": False, "message": "结束角度必须大于起始角度"}
|
||||
if increment < 0.005:
|
||||
return {"status": False, "message": "角度增量必须≥0.005"}
|
||||
if not (0.1 <= exp_time <= 5.0):
|
||||
return {"status": False, "message": "曝光时间必须在0.1-5.0秒之间"}
|
||||
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
cmd = {
|
||||
"command": "SEND_SAMPLE_READY",
|
||||
"content": {
|
||||
"sample_id": sample_id,
|
||||
"start_theta": start_theta,
|
||||
"end_theta": end_theta,
|
||||
"increment": increment,
|
||||
"exp_time": exp_time
|
||||
}
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送样品准备完成命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到样品准备完成响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"样品准备完成失败: {e}")
|
||||
return {"status": False, "message": f"样品准备完成失败: {str(e)}"}
|
||||
|
||||
# ==================== 数据获取 ====================
|
||||
|
||||
def get_current_acquire_data(self) -> dict:
|
||||
"""
|
||||
获取当前正在采集的样品数据
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、sample_id、Energy、Intensity等
|
||||
"""
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
cmd = {
|
||||
"command": "GET_CURRENT_ACQUIRE_DATA",
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送获取采集数据命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到获取采集数据响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"获取采集数据失败: {e}")
|
||||
return {"status": False, "message": f"获取采集数据失败: {str(e)}"}
|
||||
|
||||
def get_sample_status(self) -> dict:
|
||||
"""
|
||||
获取工位样品状态及设备状态
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、Station等
|
||||
"""
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
cmd = {
|
||||
"command": "GET_SAMPLE_STATUS",
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送获取样品状态命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到获取样品状态响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"获取样品状态失败: {e}")
|
||||
return {"status": False, "message": f"获取样品状态失败: {str(e)}"}
|
||||
|
||||
# ==================== 下样流程 ====================
|
||||
|
||||
def get_sample_down(self, sample_station: int) -> dict:
|
||||
"""
|
||||
下样请求
|
||||
|
||||
Args:
|
||||
sample_station (int): 下样工位(1, 2, 3)
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、sample_info等
|
||||
"""
|
||||
# 参数验证
|
||||
if sample_station not in [1, 2, 3]:
|
||||
return {"status": False, "message": "下样工位必须是1、2或3"}
|
||||
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
# 按协议要求,content 直接为整数工位号
|
||||
cmd = {
|
||||
"command": "GET_SAMPLE_DOWN",
|
||||
"content": {
|
||||
"Sample station":int(3)
|
||||
}
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送下样请求命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到下样请求响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"下样请求失败: {e}")
|
||||
return {"status": False, "message": f"下样请求失败: {str(e)}"}
|
||||
|
||||
def send_sample_down_ready(self) -> dict:
|
||||
"""
|
||||
下样完成命令
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、message
|
||||
"""
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
cmd = {
|
||||
"command": "SEND_SAMPLE_DOWN_READY",
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送下样完成命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到下样完成响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"下样完成失败: {e}")
|
||||
return {"status": False, "message": f"下样完成失败: {str(e)}"}
|
||||
|
||||
# ==================== 高压电源控制 ====================
|
||||
|
||||
def set_power_on(self) -> dict:
|
||||
"""
|
||||
高压电源开启
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、message
|
||||
"""
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
cmd = {
|
||||
"command": "SET_POWER_ON",
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送高压电源开启命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到高压开启响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"高压开启失败: {e}")
|
||||
return {"status": False, "message": f"高压开启失败: {str(e)}"}
|
||||
|
||||
def set_power_off(self) -> dict:
|
||||
"""
|
||||
高压电源关闭
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、message
|
||||
"""
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
cmd = {
|
||||
"command": "SET_POWER_OFF",
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送高压电源关闭命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到高压关闭响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"高压关闭失败: {e}")
|
||||
return {"status": False, "message": f"高压关闭失败: {str(e)}"}
|
||||
|
||||
def set_voltage_current(self, voltage: float, current: float) -> dict:
|
||||
"""
|
||||
设置高压电源电压和电流
|
||||
|
||||
Args:
|
||||
voltage (float): 电压值(kV)
|
||||
current (float): 电流值(mA)
|
||||
|
||||
Returns:
|
||||
dict: 响应结果,包含status、timestamp、message
|
||||
"""
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备重新连接成功")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"status": False, "message": "设备连接异常"}
|
||||
|
||||
try:
|
||||
cmd = {
|
||||
"command": "SET_VOLTAGE_CURRENT",
|
||||
"content": {
|
||||
"voltage": voltage,
|
||||
"current": current
|
||||
}
|
||||
}
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"发送设置电压电流命令: {cmd}")
|
||||
|
||||
response = self._send_command(cmd)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"收到设置电压电流响应: {response}")
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"设置电压电流失败: {e}")
|
||||
return {"status": False, "message": f"设置电压电流失败: {str(e)}"}
|
||||
|
||||
def start(self, sample_id: str = "", start_theta: float = 10.0, end_theta: float = 80.0,
|
||||
increment: float = 0.05, exp_time: float = 0.1, wait_minutes: float = 3.0,
|
||||
string: str = "") -> dict:
|
||||
"""
|
||||
Start 主流程:
|
||||
1) 启动自动模式;
|
||||
2) 发送上样请求并等待允许;
|
||||
3) 等待指定分钟后发送样品准备完成(携带采集参数);
|
||||
4) 周期性轮询采集数据与工位状态;
|
||||
5) 一旦任一下样位变为 True,执行下样流程(GET_SAMPLE_DOWN + SEND_SAMPLE_DOWN_READY)。
|
||||
|
||||
Args:
|
||||
sample_id: 样品名称
|
||||
start_theta: 起始角度(≥5°)
|
||||
end_theta: 结束角度(≥5.5°,且必须大于 start_theta)
|
||||
increment: 角度增量(≥0.005)
|
||||
exp_time: 曝光时间(0.1-5.0 秒)
|
||||
wait_minutes: 在允许上样后、发送样品准备完成前的等待分钟数(默认 3 分钟)
|
||||
string: 字符串格式的参数输入,如果提供则优先解析使用
|
||||
|
||||
Returns:
|
||||
dict: {"return_info": str, "success": bool}
|
||||
"""
|
||||
try:
|
||||
# 强制类型转换:除 sample_id 外的所有输入均转换为 float(若为字符串)
|
||||
def _to_float(v, default):
|
||||
try:
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
return float(default)
|
||||
|
||||
if not isinstance(sample_id, str):
|
||||
sample_id = str(sample_id)
|
||||
if isinstance(start_theta, str):
|
||||
start_theta = _to_float(start_theta, 10.0)
|
||||
if isinstance(end_theta, str):
|
||||
end_theta = _to_float(end_theta, 80.0)
|
||||
if isinstance(increment, str):
|
||||
increment = _to_float(increment, 0.05)
|
||||
if isinstance(exp_time, str):
|
||||
exp_time = _to_float(exp_time, 0.1)
|
||||
if isinstance(wait_minutes, str):
|
||||
wait_minutes = _to_float(wait_minutes, 3.0)
|
||||
|
||||
# 不再从 string 参数解析覆盖;保留参数但忽略字符串解析,统一使用结构化输入
|
||||
|
||||
# 确保设备连接
|
||||
if not self.sock:
|
||||
try:
|
||||
self.connect()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info("XRD D7-Mate设备连接成功,开始执行start流程")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"XRD D7-Mate设备连接失败: {e}")
|
||||
return {"return_info": f"设备连接失败: {str(e)}", "success": False}
|
||||
|
||||
# 1) 启动自动模式
|
||||
r_auto = self.start_auto_mode(True)
|
||||
if not r_auto.get("status", False):
|
||||
return {"return_info": f"启动自动模式失败: {r_auto.get('message', '未知')}", "success": False}
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"自动模式已启动: {r_auto}")
|
||||
|
||||
# 2) 上样请求
|
||||
r_req = self.get_sample_request()
|
||||
if not r_req.get("status", False):
|
||||
return {"return_info": f"上样请求未允许: {r_req.get('message', '未知')}", "success": False}
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"上样已允许: {r_req}")
|
||||
|
||||
# 3) 等待指定分钟后发送样品准备完成
|
||||
wait_seconds = max(0.0, float(wait_minutes)) * 60.0
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"等待 {wait_minutes} 分钟后发送样品准备完成")
|
||||
time.sleep(wait_seconds)
|
||||
|
||||
r_ready = self.send_sample_ready(sample_id=sample_id,
|
||||
start_theta=start_theta,
|
||||
end_theta=end_theta,
|
||||
increment=increment,
|
||||
exp_time=exp_time)
|
||||
if not r_ready.get("status", False):
|
||||
return {"return_info": f"样品准备完成失败: {r_ready.get('message', '未知')}", "success": False}
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"样品准备完成已发送: {r_ready}")
|
||||
|
||||
# 4) 轮询采集数据与工位状态
|
||||
polling_interval = 5.0 # 秒
|
||||
down_station_idx: Optional[int] = None
|
||||
while True:
|
||||
try:
|
||||
r_data = self.get_current_acquire_data()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"采集中数据: {r_data}")
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"获取采集数据失败: {e}")
|
||||
|
||||
try:
|
||||
r_status = self.get_sample_status()
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"工位状态: {r_status}")
|
||||
|
||||
station = r_status.get("Station", {})
|
||||
if isinstance(station, dict):
|
||||
for idx in (1, 2, 3):
|
||||
key = f"DownStation{idx}"
|
||||
val = station.get(key)
|
||||
if isinstance(val, bool) and val:
|
||||
down_station_idx = idx
|
||||
break
|
||||
if down_station_idx is not None:
|
||||
break
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(f"获取工位状态失败: {e}")
|
||||
|
||||
time.sleep(polling_interval)
|
||||
|
||||
if down_station_idx is None:
|
||||
return {"return_info": "未检测到任一下样位 True,流程未完成", "success": False}
|
||||
|
||||
# 5) 下样流程
|
||||
r_down = self.get_sample_down(down_station_idx)
|
||||
if not r_down.get("status", False):
|
||||
return {"return_info": f"下样请求失败(工位 {down_station_idx}): {r_down.get('message', '未知')}", "success": False}
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"下样请求成功(工位 {down_station_idx}): {r_down}")
|
||||
|
||||
r_ready_down = self.send_sample_down_ready()
|
||||
if not r_ready_down.get("status", False):
|
||||
return {"return_info": f"下样完成发送失败: {r_ready_down.get('message', '未知')}", "success": False}
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"下样完成已发送: {r_ready_down}")
|
||||
|
||||
return {"return_info": f"Start流程完成,工位 {down_station_idx} 已下样", "success": True}
|
||||
|
||||
except Exception as e:
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(f"Start流程异常: {e}")
|
||||
return {"return_info": f"Start流程异常: {str(e)}", "success": False}
|
||||
|
||||
def _parse_start_params(self, params: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""
|
||||
解析UI输入参数为 Start 流程参数。
|
||||
- 从UI字典中读取各个字段的字符串值
|
||||
- 将数值字段从字符串转换为 float 类型
|
||||
- 保留 sample_id 为字符串类型
|
||||
|
||||
返回:
|
||||
dict: {sample_id, start_theta, end_theta, increment, exp_time, wait_minutes}
|
||||
"""
|
||||
# 如果传入为字典,则直接按键读取;否则给出警告并使用空字典
|
||||
if isinstance(params, dict):
|
||||
p = params
|
||||
else:
|
||||
p = {}
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning("start 参数应为结构化字典")
|
||||
|
||||
def _to_float(v, default):
|
||||
"""将UI输入的字符串值转换为float,处理空值和无效值"""
|
||||
if v is None or v == '':
|
||||
return float(default)
|
||||
try:
|
||||
# 处理字符串输入(来自UI)
|
||||
if isinstance(v, str):
|
||||
v = v.strip()
|
||||
if v == '':
|
||||
return float(default)
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
return float(default)
|
||||
|
||||
# 从UI输入字典中读取参数
|
||||
sample_id = p.get('sample_id') or p.get('sample_name') or '样品名称'
|
||||
if not isinstance(sample_id, str):
|
||||
sample_id = str(sample_id)
|
||||
|
||||
# 将UI字符串输入转换为float
|
||||
result: Dict[str, Any] = {
|
||||
'sample_id': sample_id,
|
||||
'start_theta': _to_float(p.get('start_theta'), 10.0),
|
||||
'end_theta': _to_float(p.get('end_theta'), 80.0),
|
||||
'increment': _to_float(p.get('increment'), 0.05),
|
||||
'exp_time': _to_float(p.get('exp_time'), 0.1),
|
||||
'wait_minutes': _to_float(p.get('wait_minutes'), 3.0),
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def start_from_string(self, params: Union[str, Dict[str, Any]]) -> dict:
|
||||
"""
|
||||
从UI输入参数执行 Start 主流程。
|
||||
接收来自用户界面的参数字典,其中数值字段为字符串格式,自动转换为正确的类型。
|
||||
|
||||
参数:
|
||||
params: UI输入参数字典,例如:
|
||||
{
|
||||
'sample_id': 'teste',
|
||||
'start_theta': '10.0', # UI字符串输入
|
||||
'end_theta': '25.0', # UI字符串输入
|
||||
'increment': '0.05', # UI字符串输入
|
||||
'exp_time': '0.10', # UI字符串输入
|
||||
'wait_minutes': '0.5' # UI字符串输入
|
||||
}
|
||||
|
||||
返回:
|
||||
dict: 执行结果
|
||||
"""
|
||||
parsed = self._parse_start_params(params)
|
||||
|
||||
sample_id = parsed.get('sample_id', '样品名称')
|
||||
start_theta = float(parsed.get('start_theta', 10.0))
|
||||
end_theta = float(parsed.get('end_theta', 80.0))
|
||||
increment = float(parsed.get('increment', 0.05))
|
||||
exp_time = float(parsed.get('exp_time', 0.1))
|
||||
wait_minutes = float(parsed.get('wait_minutes', 3.0))
|
||||
|
||||
return self.start(
|
||||
sample_id=sample_id,
|
||||
start_theta=start_theta,
|
||||
end_theta=end_theta,
|
||||
increment=increment,
|
||||
exp_time=exp_time,
|
||||
wait_minutes=wait_minutes,
|
||||
)
|
||||
|
||||
# 测试函数
|
||||
def test_xrd_client():
|
||||
"""
|
||||
测试XRD客户端功能
|
||||
"""
|
||||
client = XRDClient(host='127.0.0.1', port=6001)
|
||||
|
||||
try:
|
||||
# 测试连接
|
||||
client.connect()
|
||||
print("连接成功")
|
||||
|
||||
# 测试启动自动模式
|
||||
result = client.start_auto_mode(True)
|
||||
print(f"启动自动模式: {result}")
|
||||
|
||||
# 测试上样请求
|
||||
result = client.get_sample_request()
|
||||
print(f"上样请求: {result}")
|
||||
|
||||
# 测试获取样品状态
|
||||
result = client.get_sample_status()
|
||||
print(f"样品状态: {result}")
|
||||
|
||||
# 测试高压开启
|
||||
result = client.set_power_on()
|
||||
print(f"高压开启: {result}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"测试失败: {e}")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_xrd_client()
|
||||
|
||||
# 为了兼容性,提供别名
|
||||
XRD_D7Mate = XRDClient
|
||||
86
unilabos/registry/devices/opsky_ATR30007.yaml
Normal file
86
unilabos/registry/devices/opsky_ATR30007.yaml
Normal file
@@ -0,0 +1,86 @@
|
||||
opsky_ATR30007:
|
||||
category:
|
||||
- characterization_optic
|
||||
- opsky_ATR30007
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-run_once:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
integration_time: '5000'
|
||||
laser_power: '200'
|
||||
norm_max: '1.0'
|
||||
normalize: 'true'
|
||||
save_csv: 'true'
|
||||
save_plot: 'true'
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 执行一次站控-扫码-拉曼流程的大函数入口,参数以字符串形式传入。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
integration_time:
|
||||
default: '5000'
|
||||
type: string
|
||||
laser_power:
|
||||
default: '200'
|
||||
type: string
|
||||
norm_max:
|
||||
default: '1.0'
|
||||
type: string
|
||||
normalize:
|
||||
default: 'true'
|
||||
type: string
|
||||
save_csv:
|
||||
default: 'true'
|
||||
type: string
|
||||
save_plot:
|
||||
default: 'true'
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required: []
|
||||
title: run_once 参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.opsky_Raman.opsky_ATR30007:opsky_ATR30007
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: OPSKY ATR30007 光纤拉曼模块,提供单一入口大函数以执行一次完整流程。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
plc_ip:
|
||||
default: 192.168.1.88
|
||||
type: string
|
||||
plc_port:
|
||||
default: 502
|
||||
type: integer
|
||||
robot_ip:
|
||||
default: 192.168.1.200
|
||||
type: string
|
||||
robot_port:
|
||||
default: 502
|
||||
type: integer
|
||||
scan_csv_file:
|
||||
default: scan_results.csv
|
||||
type: string
|
||||
required:
|
||||
- plc_ip
|
||||
- plc_port
|
||||
- robot_ip
|
||||
- robot_port
|
||||
- scan_csv_file
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
557
unilabos/registry/devices/xrd_d7mate.yaml
Normal file
557
unilabos/registry/devices/xrd_d7mate.yaml
Normal file
@@ -0,0 +1,557 @@
|
||||
xrd_d7mate:
|
||||
category:
|
||||
- xrd_d7mate
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-close:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 安全关闭与XRD D7-Mate设备的TCP连接,释放网络资源。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: close参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-connect:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 与XRD D7-Mate设备建立TCP连接,配置超时参数。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: connect参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-post_init:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
ros_node: null
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
ros_node:
|
||||
type: string
|
||||
required:
|
||||
- ros_node
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: post_init参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
get_current_acquire_data:
|
||||
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_sample_down:
|
||||
feedback: {}
|
||||
goal:
|
||||
sample_station: 1
|
||||
goal_default:
|
||||
int_input: 0
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: IntSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
int_input:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
required:
|
||||
- int_input
|
||||
title: IntSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: IntSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: IntSingleInput
|
||||
type: object
|
||||
type: IntSingleInput
|
||||
get_sample_request:
|
||||
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_sample_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
|
||||
send_sample_down_ready:
|
||||
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
|
||||
send_sample_ready:
|
||||
feedback: {}
|
||||
goal:
|
||||
end_theta: 80.0
|
||||
exp_time: 0.5
|
||||
increment: 0.02
|
||||
sample_id: ''
|
||||
start_theta: 10.0
|
||||
goal_default:
|
||||
end_theta: 80.0
|
||||
exp_time: 0.5
|
||||
increment: 0.02
|
||||
sample_id: Sample001
|
||||
start_theta: 10.0
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 送样完成后,发送样品信息和采集参数
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: SampleReadyInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
end_theta:
|
||||
description: 结束角度(≥5.5°,且必须大于start_theta)
|
||||
minimum: 5.5
|
||||
type: number
|
||||
exp_time:
|
||||
description: 曝光时间(0.1-5.0秒)
|
||||
maximum: 5.0
|
||||
minimum: 0.1
|
||||
type: number
|
||||
increment:
|
||||
description: 角度增量(≥0.005)
|
||||
minimum: 0.005
|
||||
type: number
|
||||
sample_id:
|
||||
description: 样品标识符
|
||||
type: string
|
||||
start_theta:
|
||||
description: 起始角度(≥5°)
|
||||
minimum: 5.0
|
||||
type: number
|
||||
required:
|
||||
- sample_id
|
||||
- start_theta
|
||||
- end_theta
|
||||
- increment
|
||||
- exp_time
|
||||
title: SampleReadyInput_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SampleReadyInput_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SampleReadyInput
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
set_power_off:
|
||||
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
|
||||
set_power_on:
|
||||
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
|
||||
set_voltage_current:
|
||||
feedback: {}
|
||||
goal:
|
||||
current: 30.0
|
||||
voltage: 40.0
|
||||
goal_default:
|
||||
current: 30.0
|
||||
voltage: 40.0
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 设置高压电源电压和电流
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: VoltageCurrentInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
current:
|
||||
description: 电流值(mA)
|
||||
type: number
|
||||
voltage:
|
||||
description: 电压值(kV)
|
||||
type: number
|
||||
required:
|
||||
- voltage
|
||||
- current
|
||||
title: VoltageCurrentInput_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: VoltageCurrentInput_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: VoltageCurrentInput
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
start:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
end_theta: 80.0
|
||||
exp_time: 0.1
|
||||
increment: 0.05
|
||||
sample_id: 样品名称
|
||||
start_theta: 10.0
|
||||
string: ''
|
||||
wait_minutes: 3.0
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动自动模式→上样→等待→样品准备→监控→检测下样位→执行下样流程。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
end_theta:
|
||||
description: 结束角度(≥5.5°,且必须大于start_theta)
|
||||
minimum: 5.5
|
||||
type: string
|
||||
exp_time:
|
||||
description: 曝光时间(0.1-5.0秒)
|
||||
maximum: 5.0
|
||||
minimum: 0.1
|
||||
type: string
|
||||
increment:
|
||||
description: 角度增量(≥0.005)
|
||||
minimum: 0.005
|
||||
type: string
|
||||
sample_id:
|
||||
description: 样品标识符
|
||||
type: string
|
||||
start_theta:
|
||||
description: 起始角度(≥5°)
|
||||
minimum: 5.0
|
||||
type: string
|
||||
string:
|
||||
description: 字符串格式的参数输入,如果提供则优先解析使用
|
||||
type: string
|
||||
wait_minutes:
|
||||
description: 允许上样后等待分钟数
|
||||
minimum: 0.0
|
||||
type: number
|
||||
required:
|
||||
- sample_id
|
||||
- start_theta
|
||||
- end_theta
|
||||
- increment
|
||||
- exp_time
|
||||
title: StartWorkflow_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StartWorkflow_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: StartWorkflow
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
start_auto_mode:
|
||||
feedback: {}
|
||||
goal:
|
||||
status: true
|
||||
goal_default:
|
||||
status: true
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动或停止自动模式
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: BoolSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
status:
|
||||
description: True-启动自动模式,False-停止自动模式
|
||||
type: boolean
|
||||
required:
|
||||
- status
|
||||
title: BoolSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: BoolSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: BoolSingleInput
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.xrd_d7mate.xrd_d7mate:XRDClient
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: XRD D7-Mate X射线衍射分析设备,通过TCP通信实现远程控制与状态监控,支持自动模式控制、上样流程、数据获取、下样流程和高压电源控制等功能。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
host:
|
||||
default: 127.0.0.1
|
||||
type: string
|
||||
port:
|
||||
default: 6001
|
||||
type: string
|
||||
timeout:
|
||||
default: 10.0
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
Reference in New Issue
Block a user