Device registry port (#49)

* Update README and MQTTClient for installation instructions and code improvements

* feat: 支持local_config启动
add: 增加对crt path的说明,为传入config.py的相对路径
move: web component

* add: registry description

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

* feat: show machine name
fix: host node registry not uploaded

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* feat: 多ProtocolNode 允许子设备ID相同
feat: 上报发现的ActionClient
feat: Host重启动,通过discover机制要求slaveNode重新注册,实现信息及时上报

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* Device visualization (#14)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: missing hostname in devices_names
fix: upload_file for model file

* fix: missing paho-mqtt package
bump version to 0.9.0

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

* add: bind_parent_ids to resource create action
fix: message convert string

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* unify liquid_handler definition

* Update virtual_device.yaml

* 更正了stir和heater的连接方式

* 区分了虚拟仪器中的八通阀和电磁阀,添加了两个阀门的驱动

* 修改了add protocol

* 修复了阀门更新版的bug

* 修复了添加protocol前缀导致的不能启动的bug

* Fix handles

* bump version to 0.9.6

* add resource edge upload

* update container registry and handles

* add virtual_separator virtual_rotavap
fix transfer_pump

* fix container value
add parent_name to edge device id

* 大图的问题都修复好了,添加了gassource和vacuum pump的驱动以及注册表

* default resource upload mode is false

* 添加了icon的文件名在注册表里面

* 修改了json图中link的格式

* fix resource and edge upload

* fix device ports

* Fix edge id

* 移除device的父节点关联

* separate registry sync and resource_add

* 默认不进行注册表报送,通过命令unilabos-register或者增加启动参数

* 完善tip

* protocol node不再嵌套显示

* bump version to 0.9.7  新增一个测试PumpTransferProtocol的teststation,亲测可以运行,将八通阀们和转移泵与pump_protocol适配

* protocol node 执行action不应携带自身device id

* 添加了一套简易双八通阀工作站JSON,亲测能跑

* 修复了很多protocol,亲测能跑

* 添加了run column和filter through的protocol,亲测能跑

* fix mock_reactor

* 修改了大图和小图的json,但是在前端上没看到改变

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
This commit is contained in:
Kongchang Feng
2025-06-22 12:59:59 +08:00
committed by GitHub
parent f7db8d17c5
commit 46cec82a51
85 changed files with 12934 additions and 1573 deletions

View File

@@ -1,5 +1,59 @@
from typing import List, Dict, Any
import networkx as nx
from .pump_protocol import generate_pump_protocol
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
"""
获取容器中的液体体积
"""
if vessel not in G.nodes():
return 0.0
vessel_data = G.nodes[vessel].get('data', {})
liquids = vessel_data.get('liquid', [])
total_volume = 0.0
for liquid in liquids:
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
total_volume += liquid['liquid_volume']
return total_volume
def find_centrifuge_device(G: nx.DiGraph) -> str:
"""
查找离心机设备
"""
centrifuge_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_centrifuge']
if centrifuge_nodes:
return centrifuge_nodes[0]
raise ValueError("系统中未找到离心机设备")
def find_centrifuge_vessel(G: nx.DiGraph) -> str:
"""
查找离心机专用容器
"""
possible_names = [
"centrifuge_tube",
"centrifuge_vessel",
"tube_centrifuge",
"vessel_centrifuge",
"centrifuge",
"tube_15ml",
"tube_50ml"
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
return vessel_name
raise ValueError(f"未找到离心机容器。尝试了以下名称: {possible_names}")
def generate_centrifuge_protocol(
G: nx.DiGraph,
@@ -9,115 +63,223 @@ def generate_centrifuge_protocol(
temp: float = 25.0
) -> List[Dict[str, Any]]:
"""
生成离心操作的协议序列
生成离心操作的协议序列,复用 pump_protocol 的成熟算法
离心流程:
1. 液体转移:将待离心溶液从源容器转移到离心机容器
2. 离心操作:执行离心分离
3. 上清液转移:将离心后的上清液转移回原容器或新容器
4. 沉淀处理:处理离心沉淀(可选)
Args:
G: 有向图,节点为设备和容器
vessel: 离心容器名称
G: 有向图,节点为设备和容器,边为流体管道
vessel: 包含待离心溶液的容器名称
speed: 离心速度 (rpm)
time: 离心时间 (秒)
temp: 温度 (摄氏度,可选)
temp: 离心温度 (°C)默认25°C
Returns:
List[Dict[str, Any]]: 离心操作的动作序列
Raises:
ValueError: 当找不到离心机设备时抛出异常
ValueError: 当找不到必要的设备时抛出异常
Examples:
centrifuge_protocol = generate_centrifuge_protocol(G, "reactor", 5000, 300, 4.0)
centrifuge_actions = generate_centrifuge_protocol(G, "reaction_mixture", 5000, 600, 4.0)
"""
action_sequence = []
# 查找离心机设备
centrifuge_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_centrifuge']
print(f"CENTRIFUGE: 开始生成离心协议")
print(f" - 源容器: {vessel}")
print(f" - 离心速度: {speed} rpm")
print(f" - 离心时间: {time}s ({time/60:.1f}分钟)")
print(f" - 离心温度: {temp}°C")
if not centrifuge_nodes:
raise ValueError("没有找到可用的离心机设备")
# 使用第一个可用的离心机
centrifuge_id = centrifuge_nodes[0]
# 验证容器是否存在
# 验证源容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于")
raise ValueError(f"容器 '{vessel}' 不存在于系统")
# 执行离心操作
action_sequence.append({
# 获取源容器中的液体体积
source_volume = get_vessel_liquid_volume(G, vessel)
print(f"CENTRIFUGE: 源容器 {vessel} 中有 {source_volume} mL 液体")
# 查找离心机设备
try:
centrifuge_id = find_centrifuge_device(G)
print(f"CENTRIFUGE: 找到离心机: {centrifuge_id}")
except ValueError as e:
raise ValueError(f"无法找到离心机: {str(e)}")
# 查找离心机容器
try:
centrifuge_vessel = find_centrifuge_vessel(G)
print(f"CENTRIFUGE: 找到离心机容器: {centrifuge_vessel}")
except ValueError as e:
raise ValueError(f"无法找到离心机容器: {str(e)}")
# === 简化的体积计算策略 ===
if source_volume > 0:
# 如果能检测到液体体积,使用实际体积的大部分
transfer_volume = min(source_volume * 0.9, 15.0) # 90%或最多15mL离心管通常较小
print(f"CENTRIFUGE: 检测到液体体积,将转移 {transfer_volume} mL")
else:
# 如果检测不到液体体积,默认转移标准量
transfer_volume = 10.0 # 标准离心管体积
print(f"CENTRIFUGE: 未检测到液体体积,默认转移 {transfer_volume} mL")
# === 第一步:将待离心溶液转移到离心机容器 ===
print(f"CENTRIFUGE: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {centrifuge_vessel}")
try:
# 使用成熟的 pump_protocol 算法进行液体转移
transfer_to_centrifuge_actions = generate_pump_protocol(
G=G,
from_vessel=vessel,
to_vessel=centrifuge_vessel,
volume=transfer_volume,
flowrate=1.0, # 离心转移用慢速,避免气泡
transfer_flowrate=1.0
)
action_sequence.extend(transfer_to_centrifuge_actions)
except Exception as e:
raise ValueError(f"无法将溶液转移到离心机: {str(e)}")
# 转移后等待
wait_action = {
"action_name": "wait",
"action_kwargs": {"time": 5}
}
action_sequence.append(wait_action)
# === 第二步:执行离心操作 ===
print(f"CENTRIFUGE: 执行离心操作")
centrifuge_action = {
"device_id": centrifuge_id,
"action_name": "centrifuge",
"action_kwargs": {
"vessel": vessel,
"vessel": centrifuge_vessel,
"speed": speed,
"time": time,
"temp": temp
}
})
}
action_sequence.append(centrifuge_action)
# 离心后等待系统稳定
wait_action = {
"action_name": "wait",
"action_kwargs": {"time": 10} # 离心后等待稍长,让沉淀稳定
}
action_sequence.append(wait_action)
# === 第三步:将上清液转移回原容器 ===
print(f"CENTRIFUGE: 将上清液从离心机转移回 {vessel}")
try:
# 估算上清液体积约为转移体积的80% - 假设20%成为沉淀)
supernatant_volume = transfer_volume * 0.8
print(f"CENTRIFUGE: 预计上清液体积 {supernatant_volume} mL")
transfer_back_actions = generate_pump_protocol(
G=G,
from_vessel=centrifuge_vessel,
to_vessel=vessel,
volume=supernatant_volume,
flowrate=0.5, # 上清液转移更慢,避免扰动沉淀
transfer_flowrate=0.5
)
action_sequence.extend(transfer_back_actions)
except Exception as e:
print(f"CENTRIFUGE: 将上清液转移回容器失败: {str(e)}")
# === 第四步:清洗离心机容器 ===
print(f"CENTRIFUGE: 清洗离心机容器")
try:
# 查找清洗溶剂
cleaning_solvent = None
for solvent in ["flask_water", "flask_ethanol", "flask_acetone"]:
if solvent in G.nodes():
cleaning_solvent = solvent
break
if cleaning_solvent:
# 用少量溶剂清洗离心管
cleaning_volume = 5.0 # 5mL清洗
print(f"CENTRIFUGE: 用 {cleaning_volume} mL {cleaning_solvent} 清洗")
# 清洗溶剂加入
cleaning_actions = generate_pump_protocol(
G=G,
from_vessel=cleaning_solvent,
to_vessel=centrifuge_vessel,
volume=cleaning_volume,
flowrate=2.0,
transfer_flowrate=2.0
)
action_sequence.extend(cleaning_actions)
# 将清洗液转移到废液
if "waste_workup" in G.nodes():
waste_actions = generate_pump_protocol(
G=G,
from_vessel=centrifuge_vessel,
to_vessel="waste_workup",
volume=cleaning_volume,
flowrate=2.0,
transfer_flowrate=2.0
)
action_sequence.extend(waste_actions)
except Exception as e:
print(f"CENTRIFUGE: 清洗步骤失败: {str(e)}")
print(f"CENTRIFUGE: 生成了 {len(action_sequence)} 个动作")
print(f"CENTRIFUGE: 离心协议生成完成")
print(f"CENTRIFUGE: 总处理体积: {transfer_volume} mL")
return action_sequence
def generate_multi_step_centrifuge_protocol(
# 便捷函数:常用离心方案
def generate_low_speed_centrifuge_protocol(
G: nx.DiGraph,
vessel: str,
steps: List[Dict[str, Any]]
time: float = 300.0 # 5分钟
) -> List[Dict[str, Any]]:
"""
生成多步骤离心操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 离心容器名称
steps: 离心步骤列表,每个步骤包含 speed, time, temp 参数
Returns:
List[Dict[str, Any]]: 多步骤离心操作的动作序列
Examples:
steps = [
{"speed": 1000, "time": 60, "temp": 4.0}, # 低速预离心
{"speed": 12000, "time": 600, "temp": 4.0} # 高速离心
]
protocol = generate_multi_step_centrifuge_protocol(G, "reactor", steps)
"""
action_sequence = []
# 查找离心机设备
centrifuge_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_centrifuge']
if not centrifuge_nodes:
raise ValueError("没有找到可用的离心机设备")
centrifuge_id = centrifuge_nodes[0]
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
# 执行每个离心步骤
for i, step in enumerate(steps):
speed = step.get('speed', 5000)
time = step.get('time', 300)
temp = step.get('temp', 25.0)
action_sequence.append({
"device_id": centrifuge_id,
"action_name": "centrifuge",
"action_kwargs": {
"vessel": vessel,
"speed": speed,
"time": time,
"temp": temp
}
})
# 步骤间等待时间(除了最后一步)
if i < len(steps) - 1:
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 3}
})
return action_sequence
"""低速离心:细胞分离或大颗粒沉淀"""
return generate_centrifuge_protocol(G, vessel, 1000.0, time, 4.0)
def generate_high_speed_centrifuge_protocol(
G: nx.DiGraph,
vessel: str,
time: float = 600.0 # 10分钟
) -> List[Dict[str, Any]]:
"""高速离心:蛋白质沉淀或小颗粒分离"""
return generate_centrifuge_protocol(G, vessel, 12000.0, time, 4.0)
def generate_standard_centrifuge_protocol(
G: nx.DiGraph,
vessel: str,
time: float = 600.0 # 10分钟
) -> List[Dict[str, Any]]:
"""标准离心:常规样品处理"""
return generate_centrifuge_protocol(G, vessel, 5000.0, time, 25.0)
def generate_cold_centrifuge_protocol(
G: nx.DiGraph,
vessel: str,
speed: float = 5000.0,
time: float = 600.0
) -> List[Dict[str, Any]]:
"""冷冻离心:热敏感样品处理"""
return generate_centrifuge_protocol(G, vessel, speed, time, 4.0)
def generate_ultra_centrifuge_protocol(
G: nx.DiGraph,
vessel: str,
time: float = 1800.0 # 30分钟
) -> List[Dict[str, Any]]:
"""超高速离心:超细颗粒分离"""
return generate_centrifuge_protocol(G, vessel, 15000.0, time, 4.0)