mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-05 14:05:12 +00:00
* 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>
256 lines
11 KiB
Python
256 lines
11 KiB
Python
import numpy as np
|
||
import networkx as nx
|
||
|
||
|
||
def is_integrated_pump(node_name):
|
||
return "pump" in node_name and "valve" in node_name
|
||
|
||
|
||
def find_connected_pump(G, valve_node):
|
||
for neighbor in G.neighbors(valve_node):
|
||
node_class = G.nodes[neighbor].get("class") or "" # 防止 None
|
||
if "pump" in node_class:
|
||
return neighbor
|
||
raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点")
|
||
|
||
|
||
def build_pump_valve_maps(G, pump_backbone):
|
||
pumps_from_node = {}
|
||
valve_from_node = {}
|
||
for node in pump_backbone:
|
||
if is_integrated_pump(node):
|
||
pumps_from_node[node] = node
|
||
valve_from_node[node] = node
|
||
else:
|
||
pump_node = find_connected_pump(G, node)
|
||
pumps_from_node[node] = pump_node
|
||
valve_from_node[node] = node
|
||
return pumps_from_node, valve_from_node
|
||
|
||
|
||
def generate_pump_protocol(
|
||
G: nx.DiGraph,
|
||
from_vessel: str,
|
||
to_vessel: str,
|
||
volume: float,
|
||
flowrate: float = 0.5,
|
||
transfer_flowrate: float = 0,
|
||
) -> list[dict]:
|
||
"""
|
||
生成泵操作的动作序列。
|
||
|
||
:param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
||
:param from_vessel: 容器A
|
||
:param to_vessel: 容器B
|
||
:param volume: 转移的体积
|
||
:param flowrate: 最终注入容器B时的流速
|
||
:param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
||
:return: 泵操作的动作序列
|
||
"""
|
||
|
||
# 生成泵操作的动作序列
|
||
pump_action_sequence = []
|
||
nodes = G.nodes(data=True)
|
||
# 从from_vessel到to_vessel的最短路径
|
||
shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
|
||
print(shortest_path)
|
||
|
||
pump_backbone = shortest_path
|
||
if not from_vessel.startswith("pump"):
|
||
pump_backbone = pump_backbone[1:]
|
||
if not to_vessel.startswith("pump"):
|
||
pump_backbone = pump_backbone[:-1]
|
||
|
||
if transfer_flowrate == 0:
|
||
transfer_flowrate = flowrate
|
||
|
||
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
|
||
|
||
min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
|
||
repeats = int(np.ceil(volume / min_transfer_volume))
|
||
if repeats > 1 and (from_vessel.startswith("pump") or to_vessel.startswith("pump")):
|
||
raise ValueError("Cannot transfer volume larger than min_transfer_volume between two pumps.")
|
||
|
||
volume_left = volume
|
||
|
||
# 生成泵操作的动作序列
|
||
for i in range(repeats):
|
||
# 单泵依次执行阀指令、活塞指令,将液体吸入与之相连的第一台泵
|
||
if not from_vessel.startswith("pump"):
|
||
pump_action_sequence.extend([
|
||
{
|
||
"device_id": valve_from_node[pump_backbone[0]],
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {
|
||
"command": G.get_edge_data(pump_backbone[0], from_vessel)["port"][pump_backbone[0]]
|
||
}
|
||
},
|
||
{
|
||
"device_id": pumps_from_node[pump_backbone[0]],
|
||
"action_name": "set_position",
|
||
"action_kwargs": {
|
||
"position": float(min(volume_left, min_transfer_volume)),
|
||
"max_velocity": transfer_flowrate
|
||
}
|
||
}
|
||
])
|
||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
|
||
for nodeA, nodeB in zip(pump_backbone[:-1], pump_backbone[1:]):
|
||
# 相邻两泵同时切换阀门至连通位置
|
||
pump_action_sequence.append([
|
||
{
|
||
"device_id": valve_from_node[nodeA],
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {
|
||
"command": G.get_edge_data(nodeA, nodeB)["port"][nodeA]
|
||
}
|
||
},
|
||
{
|
||
"device_id": valve_from_node[nodeB],
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {
|
||
"command": G.get_edge_data(nodeB, nodeA)["port"][nodeB],
|
||
}
|
||
}
|
||
])
|
||
# 相邻两泵液体转移:泵A排出液体,泵B吸入液体
|
||
pump_action_sequence.append([
|
||
{
|
||
"device_id": pumps_from_node[nodeA],
|
||
"action_name": "set_position",
|
||
"action_kwargs": {
|
||
"position": 0.0,
|
||
"max_velocity": transfer_flowrate
|
||
}
|
||
},
|
||
{
|
||
"device_id": pumps_from_node[nodeB],
|
||
"action_name": "set_position",
|
||
"action_kwargs": {
|
||
"position": float(min(volume_left, min_transfer_volume)),
|
||
"max_velocity": transfer_flowrate
|
||
}
|
||
}
|
||
])
|
||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
|
||
|
||
if not to_vessel.startswith("pump"):
|
||
# 单泵依次执行阀指令、活塞指令,将最后一台泵液体缓慢加入容器B
|
||
pump_action_sequence.extend([
|
||
{
|
||
"device_id": valve_from_node[pump_backbone[-1]],
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {
|
||
"command": G.get_edge_data(pump_backbone[-1], to_vessel)["port"][pump_backbone[-1]]
|
||
}
|
||
},
|
||
{
|
||
"device_id": pumps_from_node[pump_backbone[-1]],
|
||
"action_name": "set_position",
|
||
"action_kwargs": {
|
||
"position": 0.0,
|
||
"max_velocity": flowrate
|
||
}
|
||
}
|
||
])
|
||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
|
||
|
||
volume_left -= min_transfer_volume
|
||
return pump_action_sequence
|
||
|
||
|
||
# Pump protocol compilation
|
||
def generate_pump_protocol_with_rinsing(
|
||
G: nx.DiGraph,
|
||
from_vessel: str,
|
||
to_vessel: str,
|
||
volume: float,
|
||
amount: str = "",
|
||
time: float = 0,
|
||
viscous: bool = False,
|
||
rinsing_solvent: str = "air",
|
||
rinsing_volume: float = 5.0,
|
||
rinsing_repeats: int = 2,
|
||
solid: bool = False,
|
||
flowrate: float = 2.5,
|
||
transfer_flowrate: float = 0.5,
|
||
) -> list[dict]:
|
||
"""
|
||
Generates a pump protocol for transferring a specified volume between vessels, including rinsing steps with a chosen solvent. This function constructs a sequence of pump actions based on the provided parameters and the shortest path in a directed graph.
|
||
|
||
Args:
|
||
G (nx.DiGraph): The directed graph representing the vessels and connections. 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
||
from_vessel (str): The name of the vessel to transfer from.
|
||
to_vessel (str): The name of the vessel to transfer to.
|
||
volume (float): The volume to transfer.
|
||
amount (str, optional): Additional amount specification (default is "").
|
||
time (float, optional): Time over which to perform the transfer (default is 0).
|
||
viscous (bool, optional): Indicates if the fluid is viscous (default is False).
|
||
rinsing_solvent (str, optional): The solvent to use for rinsing (default is "air").
|
||
rinsing_volume (float, optional): The volume of rinsing solvent to use (default is 5.0).
|
||
rinsing_repeats (int, optional): The number of times to repeat rinsing (default is 2).
|
||
solid (bool, optional): Indicates if the transfer involves a solid (default is False).
|
||
flowrate (float, optional): The flow rate for the transfer (default is 2.5). 最终注入容器B时的流速
|
||
transfer_flowrate (float, optional): The flow rate for the transfer action (default is 0.5). 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
||
|
||
Returns:
|
||
list[dict]: A sequence of pump actions to be executed for the transfer and rinsing process. 泵操作的动作序列.
|
||
|
||
Raises:
|
||
AssertionError: If the number of rinsing solvents does not match the number of rinsing repeats.
|
||
|
||
Examples:
|
||
pump_protocol = generate_pump_protocol_with_rinsing(G, "vessel_A", "vessel_B", 0.1, rinsing_solvent="water")
|
||
"""
|
||
air_vessel = "flask_air"
|
||
waste_vessel = f"waste_workup"
|
||
|
||
shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
|
||
pump_backbone = shortest_path[1: -1]
|
||
nodes = G.nodes(data=True)
|
||
|
||
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
|
||
|
||
min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
|
||
if time != 0:
|
||
flowrate = transfer_flowrate = volume / time
|
||
|
||
pump_action_sequence = generate_pump_protocol(G, from_vessel, to_vessel, float(volume), flowrate, transfer_flowrate)
|
||
if rinsing_solvent != "air" and rinsing_solvent != "":
|
||
if "," in rinsing_solvent:
|
||
rinsing_solvents = rinsing_solvent.split(",")
|
||
assert len(
|
||
rinsing_solvents) == rinsing_repeats, "Number of rinsing solvents must match number of rinsing repeats."
|
||
else:
|
||
rinsing_solvents = [rinsing_solvent] * rinsing_repeats
|
||
|
||
for rinsing_solvent in rinsing_solvents:
|
||
solvent_vessel = f"flask_{rinsing_solvent}"
|
||
# 清洗泵
|
||
pump_action_sequence.extend(
|
||
generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate,
|
||
transfer_flowrate) +
|
||
generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate,
|
||
transfer_flowrate) +
|
||
generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate,
|
||
transfer_flowrate)
|
||
)
|
||
# 如果转移的是溶液,第一种冲洗溶剂请选用溶液的溶剂,稀释泵内、转移管道内的溶液。后续冲洗溶剂不需要此操作。
|
||
if rinsing_solvent == rinsing_solvents[0]:
|
||
pump_action_sequence.extend(
|
||
generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
||
pump_action_sequence.extend(
|
||
generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
||
pump_action_sequence.extend(
|
||
generate_pump_protocol(G, air_vessel, solvent_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
||
pump_action_sequence.extend(
|
||
generate_pump_protocol(G, air_vessel, waste_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
||
if rinsing_solvent != "":
|
||
pump_action_sequence.extend(
|
||
generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
|
||
pump_action_sequence.extend(
|
||
generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
|
||
|
||
return pump_action_sequence
|
||
# End Protocols
|