Compare commits

..

160 Commits

Author SHA1 Message Date
Guangxin Zhang
41c130ef54 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-07-17 23:03:01 +08:00
Guangxin Zhang
2d30fb79c1 update 2025-07-17 23:02:08 +08:00
Xuwznln
743ec8839d get_well_container&get_tip_rack 2025-07-17 23:01:30 +08:00
Guangxin Zhang
3f7b991dd9 Update 9320 2025-07-17 22:36:45 +08:00
Xuwznln
0466b57e0a 更新axis等参数 2025-07-17 21:53:25 +08:00
Guangxin Zhang
aaf33a8878 Update PRCXI 2025-07-17 20:54:11 +08:00
Guangxin Zhang
31f59dc2aa Update prcxi.py 2025-07-17 14:38:50 +08:00
Guangxin Zhang
46b7ce292b Update for discard tips 2025-07-16 23:28:46 +08:00
Guangxin Zhang
3584e92a1e Update prcxi.py 2025-07-16 22:39:02 +08:00
Guangxin Zhang
e793ff6aa2 Update prcxi.py 2025-07-16 22:38:59 +08:00
Guangxin Zhang
f90be18926 Update trash 2025-07-16 21:34:25 +08:00
Guangxin Zhang
604d82140d Update prcxi.py 2025-07-16 21:04:58 +08:00
Xuwznln
9c4fdd8001 新增simulator 2025-07-16 20:07:23 +08:00
Guangxin Zhang
71f6deda6b Update prcxi.py 2025-07-16 18:22:02 +08:00
Guangxin Zhang
d81297d699 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-07-16 12:53:06 +08:00
Guangxin Zhang
23cf713a80 Update prcxi.py 2025-07-16 12:52:35 +08:00
Xuwznln
c5efa5aa26 修复action移除时的报错,更新注册表 2025-07-16 11:16:26 +08:00
Kongchang Feng
acf7b6d3f7 修复了部分的protocol因为XDL更新导致的问题 (#61)
* 修复了部分的protocol因为XDL更新导致的问题

但是pumptransfer,add,dissolve,separate还没修,后续还需要写virtual固体加料器

* 补充了四个action

* 添加了固体加样器,丰富了json,修改了add protocol

* bump version to 0.9.9

* fix bugs from new actions

* protocol完整修复版本& bump version to 0.9.10

* 修补了一些单位处理,bump version to 0.9.11

* 优化了全protocol的运行时间,除了pumptransfer相关的还没

* 补充了剩下的几个protocol

---------

Co-authored-by: Junhan Chang <changjh@dp.tech>
Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
2025-07-16 11:12:02 +08:00
Guangxin Zhang
0593f98f81 Update prcxi.py 2025-07-16 10:16:22 +08:00
Xuwznln
540c5e94b7 增加注册表版本参数,支持将auto-指令人工检查后非auto,不生成人工已检查的指令,取消不必要的description生成 2025-07-16 09:46:32 +08:00
Xuwznln
f9aae44174 增加注册表版本参数,支持将auto-指令人工检查后非auto,不生成人工已检查的指令,取消不必要的description生成 2025-07-16 01:05:16 +08:00
Xuwznln
10cb645191 不生成已配置action的动作,增加prcxi的debug模式 2025-07-15 13:56:34 +08:00
Guangxin Zhang
4456529cfb Update prcxi.py to fit the function in unilabos. 2025-07-14 15:23:31 +08:00
Xuwznln
694a779c66 update prcxi registry 2025-07-11 18:43:11 +08:00
Xuwznln
5d214ebcd8 update prcxi 2025-07-11 18:20:50 +08:00
Xuwznln
0e11dacead assert blending_times > 0 2025-07-11 18:15:41 +08:00
Xuwznln
7b68545db3 prcxi blending 2025-07-11 18:11:44 +08:00
Guangxin Zhang
25960c2ed5 Add plateT6 to PRCXI configuration and enhance error handling in liquid handling 2025-07-11 18:10:21 +08:00
Xuwznln
72c67ba25c 任意执行错误都应该返回failed 2025-07-11 16:33:55 +08:00
Xuwznln
cd9e7ef12c 任意执行错误都应该返回failed 2025-07-11 16:30:56 +08:00
Xuwznln
b85722f44d add pickup tips for prcxi 2025-07-11 16:09:53 +08:00
Guangxin Zhang
5a2cc2d709 更新PRCXI配置,修改主机地址和设置状态,并添加示例用法 2025-07-11 16:00:45 +08:00
Xuwznln
644feced55 修正prcxi启动 2025-07-11 15:50:30 +08:00
Xuwznln
61ee446542 更新实例 2025-07-11 15:42:01 +08:00
Xuwznln
18f6685e18 更新实例 2025-07-11 15:36:46 +08:00
Xuwznln
e2052d4a2c 更新实例 2025-07-11 15:11:44 +08:00
q434343
50282664e0 修改moveit_interface,并在mqtt上报时发送一个时间戳,方便网页端对数据的筛选 (#62)
* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

* 修改moveit_interface,并在mqtt上报时发送一个时间戳

---------

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: Junhan Chang <changjh@pku.edu.cn>
2025-07-08 15:49:35 +08:00
Xuwznln
ce8667f937 更新中析仪器,以及启动示例 2025-07-06 18:39:40 +08:00
Guangxin Zhang
bef44b2293 Update Prcxi 2025-07-05 22:03:49 +08:00
Guangxin Zhang
b78c6c6ba9 Update prcxi.py 2025-07-05 18:02:58 +08:00
Guangxin Zhang
0d512c9e38 Create prcxi.py 2025-07-05 17:16:59 +08:00
Kongchang Feng
c8c755057c Update work_station.yaml (#60)
* Update work_station.yaml

* Checklist里面有XDL跟protocol之间没对齐的问题,工作量有点大找时间写完
2025-07-05 15:13:14 +08:00
Xuwznln
a6ec20e279 转换到ros消息时,要进行基础类型转换 2025-07-05 12:43:27 +08:00
Xuwznln
b69aceaff3 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-07-04 21:23:38 +08:00
Kongchang Feng
21afdb62bc Create 5 new protocols & bump version 0.9.8 (#59)
* 添加了5个缺失的protocol,验证了可以运行

* bump version to 0.9.8

* 修复新增的Action的字段缺失

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
2025-07-04 13:58:27 +08:00
Xuwznln
d7d43af40a 修复任务执行传参 2025-07-04 13:52:23 +08:00
ZiWei
132955617d Add channel_sources config in conda_build_config.yaml (#58) 2025-07-03 23:59:08 +08:00
Xuwznln
e7521972e4 匹配init param schema格式 2025-06-30 12:29:30 +08:00
Xuwznln
f2753fc69a Merge branch 'main' into dev 2025-06-29 19:18:17 +08:00
Xuwznln
09ad905280 修复auto-的Action在protocol node下错误注册 2025-06-29 19:12:32 +08:00
Junhan Chang
7714c71cd2 add camera and dependency (#56) 2025-06-29 17:35:32 +08:00
Junhan Chang
64832718be Fix handle names (#55)
* fix handle names

* improve evacuateAndRefill gas source finding
2025-06-29 17:31:44 +08:00
Xuwznln
68871358c2 成功动态导入的不再需要使用静态导入 2025-06-29 11:52:59 +08:00
Xuwznln
498b3cad6a 支持通过list[int],list[float]进行Int64MultiArray,Float64MultiArray的替换 2025-06-29 11:52:24 +08:00
Xuwznln
157da1759d status types对于嵌套类型返回的对象,暂时处理成字符串,无法直接进行转换 2025-06-29 11:26:35 +08:00
Xuwznln
be0a73eb19 修复静态方法识别get status,注册表支持python类型 2025-06-28 12:18:30 +08:00
Xuwznln
9be6e1069a 修复部分识别error 2025-06-28 10:52:34 +08:00
Xuwznln
817e88cfc4 修复不启用注册表补充就无法启动的bug 2025-06-28 01:32:10 +08:00
Xuwznln
15f3f8518b 支持通过导入方式补全注册表,新增工作流unilabos_device_id字段 2025-06-28 01:19:54 +08:00
Xuwznln
bbc49e9aab 新增注册表补全功能,修复Protocol执行失败 2025-06-27 23:45:05 +08:00
Xuwznln
4139e079f4 Dev (#52)
* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#39)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

---------

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: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#41)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

* 增加modbus支持
调整protocol node以更好支持多种类型的read和write

* 调整protocol node以更好支持多种类型的read和write

* 补充日志

* Device visualization (#42)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>

* Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

* 规范模拟设备代码与注册表信息

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

* Add Virtual Device, Action, YAML, Protocol for Organic Syn

* 单独分类测试的protocol文件夹

* 更名Action

---------

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

* bump version & protocol fix

* hotfix: Add macos_sdk_config (#46)

Co-authored-by: quehh <scienceol@outlook.com>

* include device_mesh when pip install

* 测试自动构建

* try build fix

* try build

* test artifacts

* hotfix: Add .certs in .gitignore

* create container

* container 添加和更新完成

* 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>

* 更新workstation注册表

* 添加了两个protocol的检索功能 (#51)

* 添加了两个protocol的检索liquid type功能

* fix workstation registry

* 修复了没连接的几个仪器的link,添加了container的icon

* 修改了json和注册表,现在大图全部的device都链接上了

* 修复了小图的json图,线全部连上了

* add work_station protocol handles (ports)

* fix workstation action handle

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@dp.tech>

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
Co-authored-by: hh. <103566763+Mile-Away@users.noreply.github.com>
Co-authored-by: quehh <scienceol@outlook.com>
Co-authored-by: Harvey Que <quehaohui@dp.tech>
Co-authored-by: Junhan Chang <changjh@dp.tech>
2025-06-22 18:33:08 +08:00
Xuwznln
f9a9e91d56 Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	test/experiments/Protocol_Test_Station/clean_vessel_protocol_test_station.json
#	test/experiments/comprehensive_protocol/comprehensive_station.json
#	unilabos/compile/__init__.py
#	unilabos/compile/add_protocol.py
#	unilabos/compile/clean_vessel_protocol.py
#	unilabos/registry/devices/virtual_device.yaml
#	unilabos/registry/resources/organic/container.yaml
2025-06-22 18:32:46 +08:00
Kongchang Feng
96e9c76709 添加了两个protocol的检索功能 (#51)
* 添加了两个protocol的检索liquid type功能

* fix workstation registry

* 修复了没连接的几个仪器的link,添加了container的icon

* 修改了json和注册表,现在大图全部的device都链接上了

* 修复了小图的json图,线全部连上了

* add work_station protocol handles (ports)

* fix workstation action handle

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@dp.tech>
2025-06-22 18:30:09 +08:00
Xuwznln
06b7962ef9 更新workstation注册表 2025-06-22 14:52:40 +08:00
Xuwznln
efc0a9fbbc v0.9.7 (#50)
注册表单独上传、新增大量模拟节点与Protocol、新增container管理、修复pip install出现的文件缺失问题


* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#39)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

---------

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: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#41)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

* 增加modbus支持
调整protocol node以更好支持多种类型的read和write

* 调整protocol node以更好支持多种类型的read和write

* 补充日志

* Device visualization (#42)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>

* Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

* 规范模拟设备代码与注册表信息

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

* Add Virtual Device, Action, YAML, Protocol for Organic Syn

* 单独分类测试的protocol文件夹

* 更名Action

---------

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

* bump version & protocol fix

* hotfix: Add macos_sdk_config (#46)

Co-authored-by: quehh <scienceol@outlook.com>

* include device_mesh when pip install

* 测试自动构建

* try build fix

* try build

* test artifacts

* hotfix: Add .certs in .gitignore

* create container

* container 添加和更新完成

* 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>

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
Co-authored-by: hh. <103566763+Mile-Away@users.noreply.github.com>
Co-authored-by: quehh <scienceol@outlook.com>
Co-authored-by: Harvey Que <quehaohui@dp.tech>
2025-06-22 13:02:51 +08:00
Xuwznln
6faa19a250 Merge branch 'main' into dev 2025-06-22 13:01:37 +08:00
Kongchang Feng
46cec82a51 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>
2025-06-22 12:59:59 +08:00
Xuwznln
f7db8d17c5 container 添加和更新完成 2025-06-15 17:37:38 +08:00
Xuwznln
a354965f8e Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-06-15 12:51:48 +08:00
Xuwznln
934276d2f7 create container 2025-06-15 12:51:37 +08:00
Harvey Que
803809480b hotfix: Add .certs in .gitignore 2025-06-15 09:09:06 +08:00
Xuwznln
5478ba3237 test artifacts 2025-06-13 14:13:41 +08:00
Xuwznln
49f1aa9c28 try build 2025-06-13 14:05:58 +08:00
Xuwznln
d5d516f0ef try build fix 2025-06-13 13:52:45 +08:00
Xuwznln
4471fed4b8 测试自动构建 2025-06-13 13:47:08 +08:00
Xuwznln
7db3123547 0.9.5 Candidate (#47)
* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#39)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

---------

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: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#41)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

* 增加modbus支持
调整protocol node以更好支持多种类型的read和write

* 调整protocol node以更好支持多种类型的read和write

* 补充日志

* Device visualization (#42)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>

* Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

* 规范模拟设备代码与注册表信息

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

* Add Virtual Device, Action, YAML, Protocol for Organic Syn

* 单独分类测试的protocol文件夹

* 更名Action

---------

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

* bump version & protocol fix

* hotfix: Add macos_sdk_config (#46)

Co-authored-by: quehh <scienceol@outlook.com>

* include device_mesh when pip install

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
Co-authored-by: hh. <103566763+Mile-Away@users.noreply.github.com>
Co-authored-by: quehh <scienceol@outlook.com>
2025-06-13 13:35:53 +08:00
Xuwznln
30d143e1a5 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-06-13 13:12:46 +08:00
Xuwznln
75ea45f21e include device_mesh when pip install 2025-06-13 00:32:15 +08:00
hh.
66af337d6c hotfix: Add macos_sdk_config (#46)
Co-authored-by: quehh <scienceol@outlook.com>
2025-06-12 22:46:44 +08:00
Xuwznln
6da7a20a7a bump version & protocol fix (#45)
* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#39)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

---------

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: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#41)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

* 增加modbus支持
调整protocol node以更好支持多种类型的read和write

* 调整protocol node以更好支持多种类型的read和write

* 补充日志

* Device visualization (#42)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>

* Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

* 规范模拟设备代码与注册表信息

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

* Add Virtual Device, Action, YAML, Protocol for Organic Syn

* 单独分类测试的protocol文件夹

* 更名Action

---------

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

* bump version & protocol fix

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
2025-06-12 22:10:30 +08:00
Xuwznln
ae3c65c1d3 Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	README.md
#	README_zh.md
#	recipes/ros-humble-unilabos-msgs/recipe.yaml
#	recipes/unilabos/recipe.yaml
#	setup.py
#	unilabos/compile/pump_protocol.py
#	unilabos/registry/devices/pump_and_valve.yaml
#	unilabos/ros/nodes/presets/protocol_node.py
2025-06-12 21:27:07 +08:00
Xuwznln
11e4f053f1 bump version & protocol fix 2025-06-12 21:21:25 +08:00
Xuwznln
aa1c67de29 Device Visualization & Mock Devices (#44) [37-biomek-i5i7 (#40), Device visualization (#39), Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)]
* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#39)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

---------

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: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#41)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

* 增加modbus支持
调整protocol node以更好支持多种类型的read和write

* 调整protocol node以更好支持多种类型的read和write

* 补充日志

* Device visualization (#42)

* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>

* Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

* 规范模拟设备代码与注册表信息

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

* Add Virtual Device, Action, YAML, Protocol for Organic Syn

* 单独分类测试的protocol文件夹

* 更名Action

---------

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

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
2025-06-12 21:01:04 +08:00
Kongchang Feng
96f37b3b0d Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)
* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

* 规范模拟设备代码与注册表信息

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

* Add Virtual Device, Action, YAML, Protocol for Organic Syn

* 单独分类测试的protocol文件夹

* 更名Action

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
2025-06-12 20:58:39 +08:00
Xuwznln
d7d0a27976 Device visualization (#42)
* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>
2025-06-12 20:58:18 +08:00
Xuwznln
34151f5cb2 补充日志 2025-06-10 22:13:35 +08:00
Xuwznln
369a21b904 调整protocol node以更好支持多种类型的read和write 2025-06-10 21:54:23 +08:00
Xuwznln
90169981c1 增加modbus支持
调整protocol node以更好支持多种类型的read和write
2025-06-10 21:46:49 +08:00
Xuwznln
d297abfd19 bump ver
modify slot type
2025-06-10 03:46:28 +08:00
Xuwznln
9c515a252a create_resource 2025-06-10 02:55:29 +08:00
Xuwznln
ea5e7a5ce2 Merge branch '37-biomek-i5i7' into dev
# Conflicts:
#	README.md
#	README_zh.md
#	recipes/ros-humble-unilabos-msgs/recipe.yaml
#	recipes/unilabos/recipe.yaml
#	setup.py
#	unilabos/devices/liquid_handling/biomek.py
#	unilabos/devices/liquid_handling/biomek_test.py
#	unilabos/registry/devices/liquid_handler.yaml
#	unilabos/registry/registry.py
#	unilabos/ros/msgs/message_converter.py
#	unilabos_msgs/action/LiquidHandlerMoveBiomek.action
#	unilabos_msgs/action/LiquidHandlerTransferBiomek.action
2025-06-10 02:00:43 +08:00
Xuwznln
2e9a0a4677 fix move it 2025-06-10 01:55:39 +08:00
Xuwznln
4c7aa8a89a fix move it 2025-06-10 01:53:58 +08:00
Xuwznln
d8a0c5e715 Device visualization (#41)
* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.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>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
2025-06-10 01:28:09 +08:00
q434343
133ffaac17 Device visualization (#39)
* 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

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

---------

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: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
2025-06-09 17:06:04 +08:00
Xuwznln
729a0fcf0c 37-biomek-i5i7 (#40)
* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
2025-06-09 16:57:42 +08:00
Xuwznln
6ae77e0408 temp disable initialize resource 2025-06-08 17:07:48 +08:00
Xuwznln
bab4b1d67a biomek switch back to non-test 2025-06-08 17:05:48 +08:00
Guangxin Zhang
12c17ec26e 同步了Biomek.py 现在应可用 2025-06-08 16:58:19 +08:00
Guangxin Zhang
6577fe12eb 0608 DONE 2025-06-08 16:49:11 +08:00
qxw138
f1fee5fad9 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-08 15:52:31 +08:00
qxw138
9b3377aedb Update biomek_test.py 2025-06-08 15:52:20 +08:00
Xuwznln
526327727d 取消raiseValueError提示 2025-06-08 15:34:56 +08:00
Xuwznln
aaa86314e3 同步执行状态信息 2025-06-08 15:34:16 +08:00
Xuwznln
6a14104e6b 正确发送return_info结果 2025-06-08 15:06:38 +08:00
Xuwznln
ab0c4b708b 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传
2025-06-08 14:43:07 +08:00
Xuwznln
c0b7f2decd host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2
2025-06-08 13:23:55 +08:00
Junhan Chang
b6c9530c61 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-07 18:52:23 +08:00
Junhan Chang
8698821c52 fix liquid_handler.biomek handles 2025-06-07 18:52:20 +08:00
qxw138
3f53f88390 biomek_test.py 2025-06-07 15:21:20 +08:00
qxw138
e840516ba4 Update biomek.py 2025-06-06 22:50:11 +08:00
qxw138
146d8c5296 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 22:49:35 +08:00
qxw138
6573c9e02e biomek_test.py
biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json
2025-06-06 22:42:06 +08:00
Xuwznln
c7b9c6a825 fix handles not as default entry 2025-06-06 18:13:53 +08:00
Xuwznln
48c43d3303 fix biomek startup
add action handles
2025-06-06 17:45:54 +08:00
Xuwznln
55be5e8188 registry 2025-06-06 17:21:19 +08:00
qxw138
1b9f3c666d 1 2025-06-06 14:44:17 +08:00
qxw138
097114d38c new actions 2025-06-06 14:31:10 +08:00
qxw138
5bec899479 new actions 2025-06-06 13:56:39 +08:00
Xuwznln
3470a1cb69 更新比赛链接 2025-06-06 13:26:25 +08:00
Guangxin Zhang
5e86112ebf Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 13:25:34 +08:00
Guangxin Zhang
24ecb13b79 Update 2025-06-06 13:22:15 +08:00
qxw138
2573d34713 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 13:18:42 +08:00
Guangxin Zhang
106d71e1db Refine 2025-06-06 11:11:17 +08:00
Guangxin Zhang
3c2a4a64ac Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 11:11:10 +08:00
Guangxin Zhang
1e00a66a65 New parameter for biomek to run. 2025-06-06 11:05:36 +08:00
qxw138
46da42deef Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 00:13:11 +08:00
Xuwznln
101c1bc3cc fix key name typo 2025-06-05 22:15:57 +08:00
qxw138
a62112ae26 new action 2025-06-05 17:26:36 +08:00
Xuwznln
dd5a7cab75 支持Biomek创建 2025-06-05 16:04:44 +08:00
Xuwznln
39de3ac58e 更新transfer_biomek的msg 2025-06-05 15:41:16 +08:00
Xuwznln
b99969278c 更新transfer_biomek的msg 2025-06-05 15:30:51 +08:00
Guangxin Zhang
b957ad2f71 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-04 21:49:27 +08:00
Guangxin Zhang
e1a7c3a103 Updated transfer_biomek 2025-06-04 21:49:22 +08:00
Guangxin Zhang
e63c15997c New transfer_biomek 2025-06-04 21:29:54 +08:00
Xuwznln
c5a495f409 新增transfer_biomek的msg 2025-06-04 19:03:00 +08:00
Guangxin Zhang
5b240cb0ea Update biomek.py 2025-06-04 17:30:53 +08:00
Guangxin Zhang
147b8f47c0 Biomek test 2025-06-04 16:38:18 +08:00
Guangxin Zhang
6d2489af5f Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-04 13:27:11 +08:00
Guangxin Zhang
807dcdd226 Update biomek.py 2025-06-04 13:27:05 +08:00
Guangxin Zhang
8a29bc5597 Remove warnings 2025-06-04 13:20:12 +08:00
Guangxin Zhang
6f6c70ee57 delete 's' 2025-06-04 13:11:45 +08:00
Xuwznln
478a85951c 修复biomek缺少的字段 2025-05-31 00:00:55 +08:00
Xuwznln
0f2555c90c 注册表上报handle和schema (param input) 2025-05-31 00:00:39 +08:00
Guangxin Zhang
d2dda6ee03 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-05-30 17:11:23 +08:00
Guangxin Zhang
208540b307 Update biomek.py 2025-05-30 17:08:19 +08:00
Guangxin Zhang
cb7c56a1d9 Convert LH action to biomek. 2025-05-30 17:00:06 +08:00
Xuwznln
ea2e9c3e3a fix biomek success type 2025-05-30 16:50:13 +08:00
Guangxin Zhang
0452a68180 Test 2025-05-30 16:03:49 +08:00
Xuwznln
90a0f3db9b merge 2025-05-30 15:40:14 +08:00
Junhan Chang
055d120ba8 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。 2025-05-30 15:38:23 +08:00
Junhan Chang
a948f09f60 add biomek.py demo implementation 2025-05-30 13:33:10 +08:00
Xuwznln
6f69df440c 修复linux 64构建问题 2025-05-30 01:27:34 +08:00
Xuwznln
b420d1fa8e bump version to 0.9.1 2025-05-29 22:17:50 +08:00
Xuwznln
767e0fcdee bump version to 0.9.1 2025-05-29 20:44:30 +08:00
Xuwznln
84944396e9 34 icon support online (#35)
* unify liquid_handler definition

* remove default values

* Dev Sync (#25)

* 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

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>

* remove default behavior for visualization

* change liquidhandler name

* resource create from outer easy

* add easy resource creation

* easy resource creation logic

* remove wrongly debug msg from others

* remove wrongly debug msg from others

* add missing action clients

* fix device_id

* fix slot_on_deck

* fix registry typo

* complete require packages
msg converter support array string
implements create resource logic

* 修复port输入

* 修复必须两次启动edge后端才有节点生成的bug
新增resources报送

* 新增延迟统计

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
2025-05-29 20:43:01 +08:00
Xuwznln
bfcb214b53 24 high level liquidhandler (#28)
* unify liquid_handler definition

* remove default values

* Dev Sync (#25)

* 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

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>

* remove default behavior for visualization

* change liquidhandler name

* resource create from outer easy

* add easy resource creation

* easy resource creation logic

* remove wrongly debug msg from others

* remove wrongly debug msg from others

* add missing action clients

* fix device_id

* fix slot_on_deck

* fix registry typo

* complete require packages
msg converter support array string
implements create resource logic

* 修复port输入

* fix: remove dirty actions

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
2025-05-29 20:40:16 +08:00
Xuwznln
ec4e6c6cfd 增加英文readme描述 (#33) 2025-05-23 10:06:30 +08:00
Xuwznln
53b6457a88 修复property类型的Action执行失败 (#30) 2025-05-17 17:57:49 +08:00
Xuwznln
133dbf77bb 嵌套节点上报云端出现ID错误 (#27)
* 修复嵌套节点,mq发送任务id出错的问题
修正一处注册表命名错误

* 修复本地看板Action显示不全
修复本地子设备没有机器名称的bug

* 补全vacuum_pump.mock注册信息
2025-05-16 19:12:59 +08:00
349 changed files with 103609 additions and 4970 deletions

View File

@@ -0,0 +1,132 @@
name: Multi-Platform Conda Build
on:
push:
branches: [ main, dev ]
tags: [ 'v*' ]
pull_request:
branches: [ main, dev ]
workflow_dispatch:
inputs:
platforms:
description: '选择构建平台 (逗号分隔): linux-64, osx-64, osx-arm64, win-64'
required: false
default: 'osx-arm64'
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux-64
env_file: unilabos-linux-64.yaml
- os: macos-13 # Intel
platform: osx-64
env_file: unilabos-osx-64.yaml
- os: macos-latest # ARM64
platform: osx-arm64
env_file: unilabos-osx-arm64.yaml
- os: windows-latest
platform: win-64
env_file: unilabos-win64.yaml
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash -l {0}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if platform should be built
id: should_build
run: |
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
else
echo "should_build=false" >> $GITHUB_OUTPUT
fi
- name: Setup Miniconda
if: steps.should_build.outputs.should_build == 'true'
uses: conda-incubator/setup-miniconda@v3
with:
miniconda-version: "latest"
channels: conda-forge,robostack-staging,defaults
channel-priority: strict
activate-environment: build-env
auto-activate-base: false
auto-update-conda: false
show-channel-urls: true
- name: Install boa and build tools
if: steps.should_build.outputs.should_build == 'true'
run: |
conda install -c conda-forge boa conda-build
- name: Show environment info
if: steps.should_build.outputs.should_build == 'true'
run: |
conda info
conda list | grep -E "(boa|conda-build)"
echo "Platform: ${{ matrix.platform }}"
echo "OS: ${{ matrix.os }}"
- name: Build conda package
if: steps.should_build.outputs.should_build == 'true'
run: |
if [[ "${{ matrix.platform }}" == "osx-arm64" ]]; then
boa build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/ros-humble-unilabos-msgs
else
boa build -m ./recipes/conda_build_config.yaml ./recipes/ros-humble-unilabos-msgs
fi
- name: List built packages
if: steps.should_build.outputs.should_build == 'true'
run: |
echo "Built packages in conda-bld:"
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" | head -10
ls -la $CONDA_PREFIX/conda-bld/${{ matrix.platform }}/ || echo "${{ matrix.platform }} directory not found"
ls -la $CONDA_PREFIX/conda-bld/noarch/ || echo "noarch directory not found"
echo "CONDA_PREFIX: $CONDA_PREFIX"
echo "Full path would be: $CONDA_PREFIX/conda-bld/**/*.tar.bz2"
- name: Prepare artifacts for upload
if: steps.should_build.outputs.should_build == 'true'
run: |
mkdir -p ${{ runner.temp }}/conda-packages
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} ${{ runner.temp }}/conda-packages/ \;
echo "Copied files to temp directory:"
ls -la ${{ runner.temp }}/conda-packages/
- name: Upload conda package artifacts
if: steps.should_build.outputs.should_build == 'true'
uses: actions/upload-artifact@v4
with:
name: conda-package-${{ matrix.platform }}
path: ${{ runner.temp }}/conda-packages
if-no-files-found: warn
retention-days: 30
- name: Create release assets (on tags)
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
run: |
mkdir -p release-assets
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} release-assets/ \;
- name: Upload to release
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: release-assets/*
draft: false
prerelease: false

10
.gitignore vendored
View File

@@ -1,3 +1,5 @@
configs/
temp/
## Python
# Byte-compiled / optimized / DLL files
@@ -6,6 +8,7 @@ __pycache__/
.vscode
*.py[cod]
*$py.class
service
# C extensions
*.so
@@ -231,4 +234,9 @@ CATKIN_IGNORE
/**/local_config.py
*.graphml
*.graphml
unilabos/device_mesh/view_robot.rviz
# Certs
**/.certs

View File

@@ -1,3 +1,5 @@
recursive-include unilabos/registry *.yaml
recursive-include unilabos/app/web *.html
recursive-include unilabos/app/web *.css
recursive-include unilabos/device_mesh/devices *
recursive-include unilabos/device_mesh/resources *

View File

@@ -4,83 +4,90 @@
# Uni-Lab-OS
<!-- Language switcher -->
**English** | [中文](README_zh.md)
[![GitHub Stars](https://img.shields.io/github/stars/dptech-corp/Uni-Lab-OS.svg)](https://github.com/dptech-corp/Uni-Lab-OS/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/dptech-corp/Uni-Lab-OS.svg)](https://github.com/dptech-corp/Uni-Lab-OS/network/members)
[![GitHub Issues](https://img.shields.io/github/issues/dptech-corp/Uni-Lab-OS.svg)](https://github.com/dptech-corp/Uni-Lab-OS/issues)
[![GitHub License](https://img.shields.io/github/license/dptech-corp/Uni-Lab-OS.svg)](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
Uni-Lab 操作系统是一个用于实验室自动化的综合平台,旨在连接和控制各种实验设备,实现实验流程的自动化和标准化。
Uni-Lab-OS is a platform for laboratory automation, designed to connect and control various experimental equipment, enabling automation and standardization of experimental workflows.
## 核心特点
## 🏆 Competition
- 多设备集成管理
- 自动化实验流程
- 云端连接能力
- 灵活的配置系统
- 支持多种实验协议
Join the [Intelligent Organic Chemistry Synthesis Competition](https://bohrium.dp.tech/competitions/1451645258) to explore automated synthesis with Uni-Lab-OS!
## 文档
## Key Features
详细文档可在以下位置找到:
- Multi-device integration management
- Automated experimental workflows
- Cloud connectivity capabilities
- Flexible configuration system
- Support for multiple experimental protocols
- [在线文档](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
## Documentation
## 快速开始
Detailed documentation can be found at:
1. 配置Conda环境
- [Online Documentation](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适当的环境文件:
## Quick Start
1. Configure Conda Environment
Uni-Lab-OS recommends using `mamba` for environment management. Choose the appropriate environment file for your operating system:
```bash
# 创建新环境
# Create new environment
mamba env create -f unilabos-[YOUR_OS].yaml
mamba activate unilab
# 或更新现有环境
# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, `osx-arm64`
conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# Or update existing environment
# Where `[YOUR_OS]` can be `win64`, `linux-64`, `osx-64`, or `osx-arm64`.
conda env update --file unilabos-[YOUR_OS].yml -n environment_name
# 现阶段,需要安装 `unilabos_msgs`
# 可以前往 Release 页面下载系统对应的包进行安装
conda install ros-humble-unilabos-msgs-0.9.0-xxxxx.tar.bz2
# Currently, you need to install the `unilabos_msgs` package
# You can download the system-specific package from the Release page
conda install ros-humble-unilabos-msgs-0.9.10-xxxxx.tar.bz2
# 安装PyLabRobot等前置
# Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
cd plr_repo
pip install .[opentrons]
```
2. 安装 Uni-Lab-OS:
2. Install Uni-Lab-OS:
```bash
# 克隆仓库
# Clone the repository
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
cd Uni-Lab-OS
# 安装 Uni-Lab-OS
# Install Uni-Lab-OS
pip install .
```
3. 启动 Uni-Lab 系统:
3. Start Uni-Lab System:
请见[文档-启动样例](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
Please refer to [Documentation - Boot Examples](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
## 消息格式
## Message Format
Uni-Lab-OS 使用预构建的 `unilabos_msgs` 进行系统通信。您可以在 [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) 页面找到已构建的版本。
Uni-Lab-OS uses pre-built `unilabos_msgs` for system communication. You can find the built versions on the [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) page.
## 许可证
## License
此项目采用 GPL-3.0 许可 - 详情请参阅 [LICENSE](LICENSE) 文件。
This project is licensed under GPL-3.0 - see the [LICENSE](LICENSE) file for details.
## 项目统计
## Project Statistics
### Stars 趋势
### Stars Trend
<a href="https://star-history.com/#dptech-corp/Uni-Lab-OS&Date">
<img src="https://api.star-history.com/svg?repos=dptech-corp/Uni-Lab-OS&type=Date" alt="Star History Chart" width="600">
</a>
## 联系我们
## Contact Us
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)

93
README_zh.md Normal file
View File

@@ -0,0 +1,93 @@
<div align="center">
<img src="docs/logo.png" alt="Uni-Lab Logo" width="200"/>
</div>
# Uni-Lab-OS
<!-- Language switcher -->
[English](README.md) | **中文**
[![GitHub Stars](https://img.shields.io/github/stars/dptech-corp/Uni-Lab-OS.svg)](https://github.com/dptech-corp/Uni-Lab-OS/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/dptech-corp/Uni-Lab-OS.svg)](https://github.com/dptech-corp/Uni-Lab-OS/network/members)
[![GitHub Issues](https://img.shields.io/github/issues/dptech-corp/Uni-Lab-OS.svg)](https://github.com/dptech-corp/Uni-Lab-OS/issues)
[![GitHub License](https://img.shields.io/github/license/dptech-corp/Uni-Lab-OS.svg)](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
Uni-Lab-OS是一个用于实验室自动化的综合平台旨在连接和控制各种实验设备实现实验流程的自动化和标准化。
## 🏆 比赛
欢迎参加[有机化学合成智能实验大赛](https://bohrium.dp.tech/competitions/1451645258),使用 Uni-Lab-OS 探索自动化合成!
## 核心特点
- 多设备集成管理
- 自动化实验流程
- 云端连接能力
- 灵活的配置系统
- 支持多种实验协议
## 文档
详细文档可在以下位置找到:
- [在线文档](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
## 快速开始
1. 配置Conda环境
Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适当的环境文件:
```bash
# 创建新环境
mamba env create -f unilabos-[YOUR_OS].yaml
mamba activate unilab
# 或更新现有环境
# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。
conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装
conda install ros-humble-unilabos-msgs-0.9.11-xxxxx.tar.bz2
# 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
cd plr_repo
pip install .[opentrons]
```
2. 安装 Uni-Lab-OS:
```bash
# 克隆仓库
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
cd Uni-Lab-OS
# 安装 Uni-Lab-OS
pip install .
```
3. 启动 Uni-Lab 系统:
请见[文档-启动样例](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
## 消息格式
Uni-Lab-OS 使用预构建的 `unilabos_msgs` 进行系统通信。您可以在 [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) 页面找到已构建的版本。
## 许可证
此项目采用 GPL-3.0 许可 - 详情请参阅 [LICENSE](LICENSE) 文件。
## 项目统计
### Stars 趋势
<a href="https://star-history.com/#dptech-corp/Uni-Lab-OS&Date">
<img src="https://api.star-history.com/svg?repos=dptech-corp/Uni-Lab-OS&type=Date" alt="Star History Chart" width="600">
</a>
## 联系我们
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)

View File

@@ -1,3 +1,6 @@
channel_sources:
- robostack,robostack-staging,conda-forge,defaults
gazebo:
- '11'
libpqxx:

View File

@@ -0,0 +1,7 @@
CONDA_BUILD_SYSROOT:
- /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
MACOSX_DEPLOYMENT_TARGET:
- "11.0"
CONDA_SUBDIR:
- osx-arm64
# boa build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/ros-humble-unilabos-msgs

View File

@@ -1,6 +1,6 @@
package:
name: ros-humble-unilabos-msgs
version: 0.9.0
version: 0.9.11
source:
path: ../../unilabos_msgs
folder: ros-humble-unilabos-msgs/src/work
@@ -50,12 +50,12 @@ requirements:
- robostack-staging::ros-humble-rosidl-default-generators
- robostack-staging::ros-humble-std-msgs
- robostack-staging::ros-humble-geometry-msgs
- robostack-staging::ros2-distro-mutex=0.6.*
- robostack-staging::ros2-distro-mutex=0.5.*
run:
- robostack-staging::ros-humble-action-msgs
- robostack-staging::ros-humble-ros-workspace
- robostack-staging::ros-humble-rosidl-default-runtime
- robostack-staging::ros-humble-std-msgs
- robostack-staging::ros-humble-geometry-msgs
- robostack-staging::ros2-distro-mutex=0.6.*
# - robostack-staging::ros2-distro-mutex=0.6.*
- sel(osx and x86_64): __osx >={{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}

View File

@@ -1,6 +1,6 @@
package:
name: unilabos
version: "0.9.0"
version: "0.9.11"
source:
path: ../..

View File

@@ -1,4 +1,2 @@
[develop]
script_dir=$base/lib/unilabos
[install]
install_scripts=$base/lib/unilabos

View File

@@ -4,7 +4,7 @@ package_name = 'unilabos'
setup(
name=package_name,
version='0.9.0',
version='0.9.11',
packages=find_packages(),
include_package_data=True,
install_requires=['setuptools'],
@@ -17,6 +17,7 @@ setup(
entry_points={
'console_scripts': [
"unilab = unilabos.app.main:main",
"unilab-register = unilabos.app.register:main"
],
},
)

View File

@@ -1,5 +1,11 @@
使用plr_test.json启动将Well加入Plate中
```bash
ros2 action send_goal /devices/host_node/add_resource_from_outer unilabos_msgs/action/_resource_create_from_outer/ResourceCreateFromOuter "{ resources: [ { 'category': '', 'children': [], 'config': { 'type': 'Well', 'size_x': 6.86, 'size_y': 6.86, 'size_z': 10.67, 'rotation': { 'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation' }, 'category': 'well', 'model': null, 'max_volume': 360, 'material_z_thickness': 0.5, 'compute_volume_from_height': null, 'compute_height_from_volume': null, 'bottom_type': 'flat', 'cross_section_type': 'circle' }, 'data': { 'liquids': [], 'pending_liquids': [], 'liquid_history': [] }, 'id': 'plate_well_11_7', 'name': 'plate_well_11_7', 'pose': { 'orientation': { 'w': 1.0, 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'position': { 'x': 0.0, 'y': 0.0, 'z': 0.0 } }, 'sample_id': '', 'parent': 'plate', 'type': 'device' } ], device_ids: [ 'PLR_STATION' ], bind_parent_ids: [ 'plate' ], bind_locations: [ { 'x': 0.0, 'y': 0.0, 'z': 0.0 } ], other_calling_params: [ '{}' ] }"
ros2 action send_goal /devices/host_node/create_resource_detailed unilabos_msgs/action/_resource_create_from_outer/ResourceCreateFromOuter "{ resources: [ { 'category': '', 'children': [], 'config': { 'type': 'Well', 'size_x': 6.86, 'size_y': 6.86, 'size_z': 10.67, 'rotation': { 'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation' }, 'category': 'well', 'model': null, 'max_volume': 360, 'material_z_thickness': 0.5, 'compute_volume_from_height': null, 'compute_height_from_volume': null, 'bottom_type': 'flat', 'cross_section_type': 'circle' }, 'data': { 'liquids': [], 'pending_liquids': [], 'liquid_history': [] }, 'id': 'plate_well_11_7', 'name': 'plate_well_11_7', 'pose': { 'orientation': { 'w': 1.0, 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'position': { 'x': 0.0, 'y': 0.0, 'z': 0.0 } }, 'sample_id': '', 'parent': 'plate', 'type': 'device' } ], device_ids: [ 'PLR_STATION' ], bind_parent_ids: [ 'plate' ], bind_locations: [ { 'x': 0.0, 'y': 0.0, 'z': 0.0 } ], other_calling_params: [ '{}' ] }"
```
使用mock_all.json启动重新捕获MockContainerForChiller1
```bash
ros2 action send_goal /devices/host_node/create_resource unilabos_msgs/action/_resource_create_from_outer_easy/ResourceCreateFromOuterEasy "{ 'res_id': 'MockContainerForChiller1', 'device_id': 'MockChiller1', 'class_name': 'container', 'parent': 'MockChiller1', 'bind_locations': { 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'liquid_input_slot': [ -1 ], 'liquid_type': [ 'CuCl2' ], 'liquid_volume': [ 100.0 ], 'slot_on_deck': '' }"
```

View File

@@ -0,0 +1,563 @@
{
"nodes": [
{
"id": "AddProtocolTestStation",
"name": "添加协议测试站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"stirrer_1",
"stirrer_2",
"flask_DMF",
"flask_ethyl_acetate",
"flask_methanol",
"flask_acetone",
"flask_water",
"flask_air",
"main_reactor",
"secondary_reactor",
"waste_workup",
"collection_bottle_1",
"collection_bottle_2"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": ["PumpTransferProtocol", "AddProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵1",
"children": [],
"parent": "AddProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 250,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "转移泵2",
"children": [],
"parent": "AddProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 750,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "试剂分配阀",
"children": [],
"parent": "AddProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 250,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "反应器分配阀",
"children": [],
"parent": "AddProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 750,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "stirrer_1",
"name": "主反应器搅拌器",
"children": [],
"parent": "AddProtocolTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 600,
"y": 450,
"z": 0
},
"config": {
"port": "VIRTUAL_STIRRER1",
"max_speed": 1500.0,
"default_speed": 300.0
},
"data": {
"speed": 0.0,
"status": "Stopped"
}
},
{
"id": "stirrer_2",
"name": "副反应器搅拌器",
"children": [],
"parent": "AddProtocolTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 900,
"y": 450,
"z": 0
},
"config": {
"port": "VIRTUAL_STIRRER2",
"max_speed": 1500.0,
"default_speed": 300.0
},
"data": {
"speed": 0.0,
"status": "Stopped"
}
},
{
"id": "flask_DMF",
"name": "DMF试剂瓶",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 50,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "DMF",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯试剂瓶",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 150,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethyl_acetate",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇试剂瓶",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "methanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮试剂瓶",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 350,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "acetone",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_water",
"name": "蒸馏水瓶",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 450,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "water",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "main_reactor",
"name": "主反应器",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "secondary_reactor",
"name": "副反应器",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_workup",
"name": "废液处理瓶",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 600,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_1",
"name": "收集瓶1",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_2",
"name": "收集瓶2",
"children": [],
"parent": "AddProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_valve2",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "8",
"multiway_valve_2": "1"
}
},
{
"id": "link_valve1_DMF",
"source": "multiway_valve_1",
"target": "flask_DMF",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_DMF": "outlet"
}
},
{
"id": "link_valve1_ethyl_acetate",
"source": "multiway_valve_1",
"target": "flask_ethyl_acetate",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_ethyl_acetate": "outlet"
}
},
{
"id": "link_valve1_methanol",
"source": "multiway_valve_1",
"target": "flask_methanol",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_methanol": "outlet"
}
},
{
"id": "link_valve1_acetone",
"source": "multiway_valve_1",
"target": "flask_acetone",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_acetone": "outlet"
}
},
{
"id": "link_valve1_water",
"source": "multiway_valve_1",
"target": "flask_water",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"flask_water": "outlet"
}
},
{
"id": "link_valve1_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "6",
"flask_air": "top"
}
},
{
"id": "link_valve2_main_reactor",
"source": "multiway_valve_2",
"target": "main_reactor",
"type": "fluid",
"port": {
"multiway_valve_2": "2",
"main_reactor": "inlet"
}
},
{
"id": "link_valve2_secondary_reactor",
"source": "multiway_valve_2",
"target": "secondary_reactor",
"type": "fluid",
"port": {
"multiway_valve_2": "3",
"secondary_reactor": "inlet"
}
},
{
"id": "link_valve2_waste",
"source": "multiway_valve_2",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_2": "6",
"waste_workup": "inlet"
}
},
{
"id": "link_valve2_collection1",
"source": "multiway_valve_2",
"target": "collection_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_2": "7",
"collection_bottle_1": "inlet"
}
},
{
"id": "link_valve2_collection2",
"source": "multiway_valve_2",
"target": "collection_bottle_2",
"type": "fluid",
"port": {
"multiway_valve_2": "8",
"collection_bottle_2": "inlet"
}
},
{
"id": "link_stirrer1_main_reactor",
"source": "stirrer_1",
"target": "main_reactor",
"type": "mechanical",
"port": {
"stirrer_1": "stirrer_head",
"main_reactor": "stirrer_port"
}
},
{
"id": "link_stirrer2_secondary_reactor",
"source": "stirrer_2",
"target": "secondary_reactor",
"type": "mechanical",
"port": {
"stirrer_2": "stirrer_head",
"secondary_reactor": "stirrer_port"
}
}
]
}

View File

@@ -0,0 +1,438 @@
{
"nodes": [
{
"id": "CentrifugeProtocolTestStation",
"name": "离心协议测试站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"centrifuge_1",
"reaction_mixture",
"centrifuge_tube",
"collection_bottle_1",
"flask_water",
"flask_ethanol",
"flask_acetone",
"flask_air",
"waste_workup"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": [
"CentrifugeProtocol",
"PumpTransferProtocol"
]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "主转移泵",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 200,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 2.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "副转移泵",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 400,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 2.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "溶剂分配阀",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 200,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "样品分配阀",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 400,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "centrifuge_1",
"name": "离心机",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "device",
"class": "virtual_centrifuge",
"position": {
"x": 600,
"y": 350,
"z": 0
},
"config": {
"port": "VIRTUAL_CENTRIFUGE1",
"max_speed": 15000.0,
"max_temp": 40.0,
"min_temp": 4.0
},
"data": {
"status": "Idle"
}
},
{
"id": "reaction_mixture",
"name": "反应混合物",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 500,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"liquid_type": "cell_suspension",
"liquid_volume": 200.0
}
]
}
},
{
"id": "centrifuge_tube",
"name": "离心管",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 450,
"z": 0
},
"config": {
"max_volume": 15.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_1",
"name": "上清液收集瓶",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 500,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_water",
"name": "蒸馏水瓶",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 200,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "water",
"liquid_volume": 900.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇清洗瓶",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮清洗瓶",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "acetone",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_workup",
"name": "废液瓶",
"children": [],
"parent": "CentrifugeProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 550,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_air": "top"
}
},
{
"id": "link_valve1_water",
"source": "multiway_valve_1",
"target": "flask_water",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_water": "outlet"
}
},
{
"id": "link_valve1_ethanol",
"source": "multiway_valve_1",
"target": "flask_ethanol",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_ethanol": "outlet"
}
},
{
"id": "link_valve1_acetone",
"source": "multiway_valve_1",
"target": "flask_acetone",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_acetone": "outlet"
}
},
{
"id": "link_valve1_valve2",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"multiway_valve_2": "1"
}
},
{
"id": "link_valve2_reaction_mixture",
"source": "multiway_valve_2",
"target": "reaction_mixture",
"type": "fluid",
"port": {
"multiway_valve_2": "2",
"reaction_mixture": "inlet"
}
},
{
"id": "link_valve2_centrifuge_tube",
"source": "multiway_valve_2",
"target": "centrifuge_tube",
"type": "fluid",
"port": {
"multiway_valve_2": "3",
"centrifuge_tube": "inlet"
}
},
{
"id": "link_valve2_collection",
"source": "multiway_valve_2",
"target": "collection_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_2": "4",
"collection_bottle_1": "inlet"
}
},
{
"id": "link_valve2_waste",
"source": "multiway_valve_2",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_2": "5",
"waste_workup": "inlet"
}
},
{
"id": "link_centrifuge1_centrifuge_tube",
"source": "centrifuge_1",
"target": "centrifuge_tube",
"type": "transport",
"port": {
"centrifuge_1": "centrifuge",
"centrifuge_tube": "centrifuge_port"
}
}
]
}

View File

@@ -0,0 +1,446 @@
{
"nodes": [
{
"id": "CleanVesselProtocolTestStation",
"name": "容器清洗协议测试站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"heatchill_1",
"flask_water",
"flask_acetone",
"flask_ethanol",
"flask_air",
"main_reactor",
"secondary_reactor",
"waste_workup"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": [
"CleanVesselProtocol",
"PumpTransferProtocol",
"HeatChillProtocol",
"HeatChillStartProtocol",
"HeatChillStopProtocol"
]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "主清洗泵",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 250,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 2.5
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "副清洗泵",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 450,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 2.5
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "溶剂分配阀",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 250,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "容器分配阀",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 450,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "heatchill_1",
"name": "加热清洗器",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 600,
"y": 350,
"z": 0
},
"config": {
"port": "VIRTUAL_HEATCHILL1",
"max_temp": 100.0,
"min_temp": 10.0,
"max_stir_speed": 500.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_water",
"name": "蒸馏水瓶",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "container",
"class": "container",
"position": {
"x": 50,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "water",
"liquid_volume": 900.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮清洗瓶",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "container",
"class": "container",
"position": {
"x": 150,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "acetone",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇清洗瓶",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "container",
"class": "container",
"position": {
"x": 250,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "container",
"class": "container",
"position": {
"x": 350,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "main_reactor",
"name": "主反应器",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "container",
"class": "container",
"position": {
"x": 600,
"y": 450,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"liquid_type": "residue",
"liquid_volume": 50.0
}
]
}
},
{
"id": "secondary_reactor",
"name": "副反应器",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "container",
"class": "container",
"position": {
"x": 800,
"y": 450,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "organic_residue",
"liquid_volume": 30.0
}
]
}
},
{
"id": "waste_workup",
"name": "清洗废液瓶",
"children": [],
"parent": "CleanVesselProtocolTestStation",
"type": "container",
"class": "container",
"position": {
"x": 700,
"y": 550,
"z": 0
},
"config": {
"max_volume": 3000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_to_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_to_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_to_water",
"source": "multiway_valve_1",
"target": "flask_water",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_water": "top"
}
},
{
"id": "link_valve1_to_acetone",
"source": "multiway_valve_1",
"target": "flask_acetone",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_acetone": "top"
}
},
{
"id": "link_valve1_to_ethanol",
"source": "multiway_valve_1",
"target": "flask_ethanol",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_ethanol": "top"
}
},
{
"id": "link_valve1_to_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "6",
"flask_air": "top"
}
},
{
"id": "link_valve1_to_valve2_for_cleaning",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"multiway_valve_2": "8"
}
},
{
"id": "link_valve2_to_main_reactor_in",
"source": "multiway_valve_2",
"target": "main_reactor",
"type": "fluid",
"port": {
"multiway_valve_2": "2",
"main_reactor": "top"
}
},
{
"id": "link_valve2_to_secondary_reactor_in",
"source": "multiway_valve_2",
"target": "secondary_reactor",
"type": "fluid",
"port": {
"multiway_valve_2": "3",
"secondary_reactor": "top"
}
},
{
"id": "link_main_reactor_out_to_valve2",
"source": "main_reactor",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"main_reactor": "bottom",
"multiway_valve_2": "6"
}
},
{
"id": "link_secondary_reactor_out_to_valve2",
"source": "secondary_reactor",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"secondary_reactor": "bottom",
"multiway_valve_2": "7"
}
},
{
"id": "link_valve2_to_waste",
"source": "multiway_valve_2",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_2": "4",
"waste_workup": "top"
}
},
{
"id": "link_heatchill1_to_main_reactor",
"source": "heatchill_1",
"target": "main_reactor",
"type": "mechanical",
"port": {
"heatchill_1": "heatchill",
"main_reactor": "bind"
}
},
{
"id": "link_heatchill1_to_secondary_reactor",
"source": "heatchill_1",
"target": "secondary_reactor",
"type": "mechanical",
"port": {
"heatchill_1": "heatchill",
"secondary_reactor": "bind"
}
}
]
}

View File

@@ -0,0 +1,367 @@
{
"nodes": [
{
"id": "DualValvePumpStation",
"name": "双阀门泵站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"flask_DMF",
"flask_ethyl_acetate",
"flask_methanol",
"flask_air",
"main_reactor",
"waste_workup",
"collection_bottle_1"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": ["PumpTransferProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵1",
"children": [],
"parent": "DualValvePumpStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 300,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "转移泵2",
"children": [],
"parent": "DualValvePumpStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 700,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "第一个八通阀",
"children": [],
"parent": "DualValvePumpStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 300,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "第二个八通阀",
"children": [],
"parent": "DualValvePumpStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 700,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "flask_DMF",
"name": "DMF试剂瓶",
"children": [],
"parent": "DualValvePumpStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "DMF",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯试剂瓶",
"children": [],
"parent": "DualValvePumpStation",
"type": "container",
"class": null,
"position": {
"x": 200,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethyl_acetate",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇试剂瓶",
"children": [],
"parent": "DualValvePumpStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "methanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "DualValvePumpStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "main_reactor",
"name": "主反应器",
"children": [],
"parent": "DualValvePumpStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_workup",
"name": "废液处理瓶",
"children": [],
"parent": "DualValvePumpStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_1",
"name": "收集瓶1",
"children": [],
"parent": "DualValvePumpStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_valve2",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "8",
"multiway_valve_2": "1"
}
},
{
"id": "link_valve1_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_air": "top"
}
},
{
"id": "link_valve1_DMF",
"source": "multiway_valve_1",
"target": "flask_DMF",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_DMF": "outlet"
}
},
{
"id": "link_valve1_ethyl_acetate",
"source": "multiway_valve_1",
"target": "flask_ethyl_acetate",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_ethyl_acetate": "outlet"
}
},
{
"id": "link_valve1_methanol",
"source": "multiway_valve_1",
"target": "flask_methanol",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_methanol": "outlet"
}
},
{
"id": "link_valve2_reactor",
"source": "multiway_valve_2",
"target": "main_reactor",
"type": "fluid",
"port": {
"multiway_valve_2": "5",
"main_reactor": "inlet"
}
},
{
"id": "link_valve2_waste",
"source": "multiway_valve_2",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_2": "6",
"waste_workup": "inlet"
}
},
{
"id": "link_valve2_collection",
"source": "multiway_valve_2",
"target": "collection_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_2": "7",
"collection_bottle_1": "inlet"
}
}
]
}

View File

@@ -0,0 +1,557 @@
{
"nodes": [
{
"id": "EvacuateRefillTestStation",
"name": "抽真空充气测试站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"flask_DMF",
"flask_ethyl_acetate",
"flask_methanol",
"flask_air",
"vacuum_pump_1",
"gas_source_nitrogen",
"gas_source_air",
"solenoid_valve_vacuum",
"solenoid_valve_gas",
"main_reactor",
"stirrer_1",
"waste_workup",
"collection_bottle_1"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": ["PumpTransferProtocol", "EvacuateAndRefillProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵1",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 300,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "转移泵2",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 700,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "第一个八通阀",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 300,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "第二个八通阀",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 700,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "vacuum_pump_1",
"name": "真空泵1",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_vacuum_pump",
"position": {
"x": 150,
"y": 200,
"z": 0
},
"config": {
"port": "VIRTUAL_VACUUM1",
"max_pressure": -0.9
},
"data": {
"status": "OFF",
"pressure": 0.0
}
},
{
"id": "gas_source_nitrogen",
"name": "氮气源",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_gas_source",
"position": {
"x": 850,
"y": 200,
"z": 0
},
"config": {
"port": "VIRTUAL_GAS_N2",
"gas_type": "nitrogen",
"max_pressure": 5.0
},
"data": {
"status": "OFF",
"flow_rate": 0.0
}
},
{
"id": "gas_source_air",
"name": "空气源",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_gas_source",
"position": {
"x": 950,
"y": 200,
"z": 0
},
"config": {
"port": "VIRTUAL_GAS_AIR",
"gas_type": "air",
"max_pressure": 3.0
},
"data": {
"status": "OFF",
"flow_rate": 0.0
}
},
{
"id": "solenoid_valve_vacuum",
"name": "真空电磁阀",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_solenoid_valve",
"position": {
"x": 225,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_SOLENOID_VACUUM"
},
"data": {
"valve_position": "CLOSED"
}
},
{
"id": "solenoid_valve_gas",
"name": "气源电磁阀",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_solenoid_valve",
"position": {
"x": 775,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_SOLENOID_GAS"
},
"data": {
"valve_position": "CLOSED"
}
},
{
"id": "flask_DMF",
"name": "DMF试剂瓶",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "DMF",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯试剂瓶",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "container",
"class": null,
"position": {
"x": 200,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethyl_acetate",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇试剂瓶",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "methanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "main_reactor",
"name": "主反应器",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "stirrer_1",
"name": "搅拌器1",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 600,
"y": 450,
"z": 0
},
"config": {
"port": "VIRTUAL_STIRRER1",
"max_speed": 1500.0
},
"data": {
"speed": 0.0,
"status": "OFF"
}
},
{
"id": "waste_workup",
"name": "废液处理瓶",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_1",
"name": "收集瓶1",
"children": [],
"parent": "EvacuateRefillTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_valve2",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "8",
"multiway_valve_2": "1"
}
},
{
"id": "link_vacuum_solenoid",
"source": "vacuum_pump_1",
"target": "solenoid_valve_vacuum",
"type": "fluid",
"port": {
"vacuum_pump_1": "outlet",
"solenoid_valve_vacuum": "inlet"
}
},
{
"id": "link_solenoid_vacuum_valve1",
"source": "solenoid_valve_vacuum",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"solenoid_valve_vacuum": "outlet",
"multiway_valve_1": "7"
}
},
{
"id": "link_gas_solenoid",
"source": "gas_source_nitrogen",
"target": "solenoid_valve_gas",
"type": "fluid",
"port": {
"gas_source_nitrogen": "outlet",
"solenoid_valve_gas": "inlet"
}
},
{
"id": "link_solenoid_gas_valve2",
"source": "solenoid_valve_gas",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"solenoid_valve_gas": "outlet",
"multiway_valve_2": "8"
}
},
{
"id": "link_air_source_valve2",
"source": "gas_source_air",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"gas_source_air": "outlet",
"multiway_valve_2": "2"
}
},
{
"id": "link_valve1_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_air": "top"
}
},
{
"id": "link_valve1_DMF",
"source": "multiway_valve_1",
"target": "flask_DMF",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_DMF": "outlet"
}
},
{
"id": "link_valve1_ethyl_acetate",
"source": "multiway_valve_1",
"target": "flask_ethyl_acetate",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_ethyl_acetate": "outlet"
}
},
{
"id": "link_valve1_methanol",
"source": "multiway_valve_1",
"target": "flask_methanol",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_methanol": "outlet"
}
},
{
"id": "link_valve2_reactor",
"source": "multiway_valve_2",
"target": "main_reactor",
"type": "fluid",
"port": {
"multiway_valve_2": "5",
"main_reactor": "inlet"
}
},
{
"id": "link_valve2_waste",
"source": "multiway_valve_2",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_2": "6",
"waste_workup": "inlet"
}
},
{
"id": "link_valve2_collection",
"source": "multiway_valve_2",
"target": "collection_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_2": "7",
"collection_bottle_1": "inlet"
}
},
{
"id": "link_stirrer_reactor",
"source": "stirrer_1",
"target": "main_reactor",
"type": "mechanical",
"port": {
"stirrer_1": "stirrer",
"main_reactor": "stirrer"
}
}
]
}

View File

@@ -0,0 +1,503 @@
{
"nodes": [
{
"id": "EvaporateProtocolTestStation",
"name": "蒸发协议测试站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"rotavap_1",
"heatchill_1",
"reaction_mixture",
"rotavap_flask",
"rotavap_condenser",
"flask_distillate",
"flask_ethanol",
"flask_acetone",
"flask_water",
"flask_air",
"waste_workup"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": [
"EvaporateProtocol",
"PumpTransferProtocol",
"HeatChillProtocol",
"HeatChillStartProtocol",
"HeatChillStopProtocol"
]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "主转移泵",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 200,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 2.5
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "副转移泵",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 400,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 2.5
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "溶剂分配阀",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 200,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "容器分配阀",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 400,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "rotavap_1",
"name": "旋转蒸发仪",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "device",
"class": "virtual_rotavap",
"position": {
"x": 700,
"y": 350,
"z": 0
},
"config": {
"port": "VIRTUAL_ROTAVAP1",
"max_temp": 180.0,
"max_rotation_speed": 280.0
},
"data": {
"status": "Ready"
}
},
{
"id": "heatchill_1",
"name": "预加热器",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 100,
"y": 550,
"z": 0
},
"config": {
"port": "VIRTUAL_HEATCHILL1",
"max_temp": 100.0,
"min_temp": 10.0,
"max_stir_speed": 500.0
},
"data": {
"status": "Idle"
}
},
{
"id": "reaction_mixture",
"name": "反应混合物",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 450,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "reaction_mixture",
"liquid_volume": 600.0
}
]
}
},
{
"id": "rotavap_flask",
"name": "旋蒸样品瓶",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 450,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "rotavap_condenser",
"name": "旋蒸冷凝器",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 350,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_distillate",
"name": "溶剂回收瓶",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 450,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_ethanol",
"name": "乙醇清洗瓶",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 50,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮清洗瓶",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 150,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "acetone",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_water",
"name": "蒸馏水瓶",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "water",
"liquid_volume": 900.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 350,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_workup",
"name": "废液瓶",
"children": [],
"parent": "EvaporateProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 550,
"z": 0
},
"config": {
"max_volume": 3000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_air": "top"
}
},
{
"id": "link_valve1_ethanol",
"source": "multiway_valve_1",
"target": "flask_ethanol",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_ethanol": "outlet"
}
},
{
"id": "link_valve1_acetone",
"source": "multiway_valve_1",
"target": "flask_acetone",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_acetone": "outlet"
}
},
{
"id": "link_valve1_water",
"source": "multiway_valve_1",
"target": "flask_water",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_water": "outlet"
}
},
{
"id": "link_valve1_valve2",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"multiway_valve_2": "1"
}
},
{
"id": "link_valve2_reaction_mixture",
"source": "multiway_valve_2",
"target": "reaction_mixture",
"type": "fluid",
"port": {
"multiway_valve_2": "2",
"reaction_mixture": "inlet"
}
},
{
"id": "link_valve2_rotavap_flask",
"source": "multiway_valve_2",
"target": "rotavap_flask",
"type": "fluid",
"port": {
"multiway_valve_2": "3",
"rotavap_flask": "inlet"
}
},
{
"id": "link_valve2_rotavap_condenser",
"source": "multiway_valve_2",
"target": "rotavap_condenser",
"type": "fluid",
"port": {
"multiway_valve_2": "4",
"rotavap_condenser": "inlet"
}
},
{
"id": "link_valve2_distillate",
"source": "multiway_valve_2",
"target": "flask_distillate",
"type": "fluid",
"port": {
"multiway_valve_2": "5",
"flask_distillate": "inlet"
}
},
{
"id": "link_valve2_waste",
"source": "multiway_valve_2",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_2": "6",
"waste_workup": "inlet"
}
},
{
"id": "link_rotavap1_rotavap_flask",
"source": "rotavap_1",
"target": "rotavap_flask",
"type": "fluid",
"port": {
"rotavap_1": "rotavap-sample",
"rotavap_flask": "rotavap_port"
}
},
{
"id": "link_heatchill1_reaction_mixture",
"source": "heatchill_1",
"target": "reaction_mixture",
"type": "mechanical",
"port": {
"heatchill_1": "heatchill",
"reaction_mixture": "heating_jacket"
}
}
]
}

View File

@@ -0,0 +1,534 @@
{
"nodes": [
{
"id": "FilterProtocolTestStation",
"name": "过滤协议测试站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"filter_1",
"heatchill_1",
"reaction_mixture",
"filter_vessel",
"filtrate_vessel",
"collection_bottle_1",
"collection_bottle_2",
"flask_water",
"flask_ethanol",
"flask_acetone",
"flask_air",
"waste_workup"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": [
"FilterProtocol",
"PumpTransferProtocol",
"HeatChillProtocol",
"HeatChillStartProtocol",
"HeatChillStopProtocol"
]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "主转移泵",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 200,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 2.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "副转移泵",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 400,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 2.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "溶剂分配阀",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 200,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "样品分配阀",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 400,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "filter_1",
"name": "过滤器",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "device",
"class": "virtual_filter",
"position": {
"x": 600,
"y": 350,
"z": 0
},
"config": {
"port": "VIRTUAL_FILTER1",
"max_temp": 100.0,
"max_stir_speed": 1000.0,
"max_volume": 500.0
},
"data": {
"status": "Idle"
}
},
{
"id": "heatchill_1",
"name": "加热搅拌器",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 600,
"y": 450,
"z": 0
},
"config": {
"port": "VIRTUAL_HEATCHILL1",
"max_temp": 100.0,
"min_temp": 4.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "reaction_mixture",
"name": "反应混合物",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "cell_suspension",
"liquid_volume": 200.0
}
]
}
},
{
"id": "filter_vessel",
"name": "过滤器容器",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 550,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "filtrate_vessel",
"name": "滤液收集容器",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 500,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_1",
"name": "收集瓶1",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_2",
"name": "收集瓶2",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_water",
"name": "蒸馏水瓶",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 200,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "water",
"liquid_volume": 900.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇清洗瓶",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮清洗瓶",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "acetone",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_workup",
"name": "废液瓶",
"children": [],
"parent": "FilterProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 600,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_air": "top"
}
},
{
"id": "link_valve1_water",
"source": "multiway_valve_1",
"target": "flask_water",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_water": "outlet"
}
},
{
"id": "link_valve1_ethanol",
"source": "multiway_valve_1",
"target": "flask_ethanol",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_ethanol": "outlet"
}
},
{
"id": "link_valve1_acetone",
"source": "multiway_valve_1",
"target": "flask_acetone",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_acetone": "outlet"
}
},
{
"id": "link_valve1_valve2",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"multiway_valve_2": "1"
}
},
{
"id": "link_valve2_reaction_mixture",
"source": "multiway_valve_2",
"target": "reaction_mixture",
"type": "fluid",
"port": {
"multiway_valve_2": "2",
"reaction_mixture": "inlet"
}
},
{
"id": "link_valve2_filter_vessel",
"source": "multiway_valve_2",
"target": "filter_vessel",
"type": "fluid",
"port": {
"multiway_valve_2": "3",
"filter_vessel": "inlet"
}
},
{
"id": "link_valve2_filtrate_vessel",
"source": "multiway_valve_2",
"target": "filtrate_vessel",
"type": "fluid",
"port": {
"multiway_valve_2": "4",
"filtrate_vessel": "inlet"
}
},
{
"id": "link_valve2_collection1",
"source": "multiway_valve_2",
"target": "collection_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_2": "5",
"collection_bottle_1": "inlet"
}
},
{
"id": "link_valve2_collection2",
"source": "multiway_valve_2",
"target": "collection_bottle_2",
"type": "fluid",
"port": {
"multiway_valve_2": "6",
"collection_bottle_2": "inlet"
}
},
{
"id": "link_valve2_waste",
"source": "multiway_valve_2",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_2": "7",
"waste_workup": "inlet"
}
},
{
"id": "link_filter1_filter_vessel",
"source": "filter_1",
"target": "filter_vessel",
"type": "transport",
"port": {
"filter_1": "filter",
"filter_vessel": "filter_port"
}
},
{
"id": "link_heatchill1_filter_vessel",
"source": "heatchill_1",
"target": "filter_vessel",
"type": "mechanical",
"port": {
"heatchill_1": "heatchill",
"filter_vessel": "heating_jacket"
}
}
]
}

View File

@@ -0,0 +1,671 @@
{
"nodes": [
{
"id": "HeatChillProtocolTestStation",
"name": "加热冷却协议测试站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"stirrer_1",
"stirrer_2",
"heatchill_1",
"heatchill_2",
"flask_DMF",
"flask_ethyl_acetate",
"flask_methanol",
"flask_acetone",
"flask_water",
"flask_ethanol",
"flask_air",
"main_reactor",
"secondary_reactor",
"waste_workup",
"collection_bottle_1",
"collection_bottle_2"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": [
"PumpTransferProtocol",
"AddProtocol",
"HeatChillProtocol",
"HeatChillStartProtocol",
"HeatChillStopProtocol",
"DissolveProtocol"
]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵1",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 250,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "转移泵2",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 750,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "试剂分配阀",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 250,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "反应器分配阀",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 750,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "stirrer_1",
"name": "主反应器搅拌器",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 600,
"y": 450,
"z": 0
},
"config": {
"port": "VIRTUAL_STIRRER1",
"max_speed": 1500.0,
"default_speed": 300.0
},
"data": {
"speed": 0.0,
"status": "Stopped"
}
},
{
"id": "stirrer_2",
"name": "副反应器搅拌器",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 900,
"y": 450,
"z": 0
},
"config": {
"port": "VIRTUAL_STIRRER2",
"max_speed": 1500.0,
"default_speed": 300.0
},
"data": {
"speed": 0.0,
"status": "Stopped"
}
},
{
"id": "heatchill_1",
"name": "主反应器加热冷却器",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 550,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_HEATCHILL1",
"max_temp": 200.0,
"min_temp": -80.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "heatchill_2",
"name": "副反应器加热冷却器",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 850,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_HEATCHILL2",
"max_temp": 200.0,
"min_temp": -80.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_DMF",
"name": "DMF试剂瓶",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 50,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "DMF",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯试剂瓶",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 150,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethyl_acetate",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇试剂瓶",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "methanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇试剂瓶",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 650,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮试剂瓶",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 350,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "acetone",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_water",
"name": "蒸馏水瓶",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 450,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "water",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "main_reactor",
"name": "主反应器",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "secondary_reactor",
"name": "副反应器",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_workup",
"name": "废液处理瓶",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 600,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_1",
"name": "收集瓶1",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_2",
"name": "收集瓶2",
"children": [],
"parent": "HeatChillProtocolTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_valve2",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "8",
"multiway_valve_2": "1"
}
},
{
"id": "link_valve1_DMF",
"source": "multiway_valve_1",
"target": "flask_DMF",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_DMF": "outlet"
}
},
{
"id": "link_valve1_ethyl_acetate",
"source": "multiway_valve_1",
"target": "flask_ethyl_acetate",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_ethyl_acetate": "outlet"
}
},
{
"id": "link_valve1_methanol",
"source": "multiway_valve_1",
"target": "flask_methanol",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_methanol": "outlet"
}
},
{
"id": "link_valve1_acetone",
"source": "multiway_valve_1",
"target": "flask_acetone",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_acetone": "outlet"
}
},
{
"id": "link_valve1_water",
"source": "multiway_valve_1",
"target": "flask_water",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"flask_water": "outlet"
}
},
{
"id": "link_valve1_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "6",
"flask_air": "top"
}
},
{
"id": "link_valve2_main_reactor",
"source": "multiway_valve_2",
"target": "main_reactor",
"type": "fluid",
"port": {
"multiway_valve_2": "2",
"main_reactor": "inlet"
}
},
{
"id": "link_valve2_secondary_reactor",
"source": "multiway_valve_2",
"target": "secondary_reactor",
"type": "fluid",
"port": {
"multiway_valve_2": "3",
"secondary_reactor": "inlet"
}
},
{
"id": "link_valve2_waste",
"source": "multiway_valve_2",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_2": "6",
"waste_workup": "inlet"
}
},
{
"id": "link_valve2_collection1",
"source": "multiway_valve_2",
"target": "collection_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_2": "7",
"collection_bottle_1": "inlet"
}
},
{
"id": "link_valve2_collection2",
"source": "multiway_valve_2",
"target": "collection_bottle_2",
"type": "fluid",
"port": {
"multiway_valve_2": "8",
"collection_bottle_2": "inlet"
}
},
{
"id": "link_stirrer1_main_reactor",
"source": "stirrer_1",
"target": "main_reactor",
"type": "mechanical",
"port": {
"stirrer_1": "stirrer_head",
"main_reactor": "stirrer_port"
}
},
{
"id": "link_stirrer2_secondary_reactor",
"source": "stirrer_2",
"target": "secondary_reactor",
"type": "mechanical",
"port": {
"stirrer_2": "stirrer_head",
"secondary_reactor": "stirrer_port"
}
},
{
"id": "link_heatchill1_main_reactor",
"source": "heatchill_1",
"target": "main_reactor",
"type": "thermal",
"port": {
"heatchill_1": "heating_surface",
"main_reactor": "heating_jacket"
}
},
{
"id": "link_heatchill2_secondary_reactor",
"source": "heatchill_2",
"target": "secondary_reactor",
"type": "thermal",
"port": {
"heatchill_2": "heating_surface",
"secondary_reactor": "heating_jacket"
}
},
{
"id": "link_valve1_ethanol",
"source": "multiway_valve_1",
"target": "flask_ethanol",
"type": "fluid",
"port": {
"multiway_valve_1": "7",
"flask_ethanol": "outlet"
}
}
]
}

View File

@@ -0,0 +1,778 @@
{
"nodes": [
{
"id": "PumpTransferFilterThroughTestStation",
"name": "泵转移+过滤介质测试站",
"children": [
"transfer_pump_1",
"transfer_pump_2",
"multiway_valve_1",
"multiway_valve_2",
"reaction_mixture",
"crude_product",
"filter_celite",
"column_silica_gel",
"filter_C18",
"pure_product",
"collection_bottle_1",
"collection_bottle_2",
"collection_bottle_3",
"intermediate_vessel_1",
"intermediate_vessel_2",
"flask_water",
"flask_ethanol",
"flask_methanol",
"flask_ethyl_acetate",
"flask_acetone",
"flask_hexane",
"flask_air",
"waste_workup"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": [
"PumpTransferProtocol",
"FilterThroughProtocol"
]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "主转移泵",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 200,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 25.0,
"transfer_rate": 2.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "transfer_pump_2",
"name": "副转移泵",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 400,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP2",
"max_volume": 25.0,
"transfer_rate": 2.0
},
"data": {
"position": 0.0,
"status": "Idle"
}
},
{
"id": "multiway_valve_1",
"name": "溶剂分配阀",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 200,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "样品分配阀",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 400,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE2",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "reaction_mixture",
"name": "反应混合物",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "organic_reaction_mixture",
"liquid_volume": 250.0
}
]
}
},
{
"id": "crude_product",
"name": "粗产品",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 200,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "crude_organic_compound",
"liquid_volume": 150.0
}
]
}
},
{
"id": "filter_celite",
"name": "硅藻土过滤器",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 450,
"z": 0
},
"config": {
"max_volume": 300.0,
"filter_type": "celite_pad"
},
"data": {
"liquid": []
}
},
{
"id": "column_silica_gel",
"name": "硅胶柱",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 450,
"z": 0
},
"config": {
"max_volume": 200.0,
"filter_type": "silica_gel_column"
},
"data": {
"liquid": []
}
},
{
"id": "filter_C18",
"name": "C18固相萃取柱",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 450,
"z": 0
},
"config": {
"max_volume": 100.0,
"filter_type": "C18_cartridge"
},
"data": {
"liquid": []
}
},
{
"id": "pure_product",
"name": "纯产品",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_1",
"name": "收集瓶1",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_2",
"name": "收集瓶2",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_3",
"name": "收集瓶3",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 550,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "intermediate_vessel_1",
"name": "中间容器1",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "intermediate_vessel_2",
"name": "中间容器2",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_water",
"name": "蒸馏水瓶",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "water",
"liquid_volume": 900.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇瓶",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 200,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇瓶",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "methanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯瓶",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethyl_acetate",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮瓶",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 500,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "acetone",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_hexane",
"name": "正己烷瓶",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "hexane",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 600,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_workup",
"name": "废液瓶",
"children": [],
"parent": "PumpTransferFilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 600,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump1_valve1",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve1_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_air": "top"
}
},
{
"id": "link_valve1_water",
"source": "multiway_valve_1",
"target": "flask_water",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_water": "outlet"
}
},
{
"id": "link_valve1_ethanol",
"source": "multiway_valve_1",
"target": "flask_ethanol",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_ethanol": "outlet"
}
},
{
"id": "link_valve1_methanol",
"source": "multiway_valve_1",
"target": "flask_methanol",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_methanol": "outlet"
}
},
{
"id": "link_valve1_ethyl_acetate",
"source": "multiway_valve_1",
"target": "flask_ethyl_acetate",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"flask_ethyl_acetate": "outlet"
}
},
{
"id": "link_valve1_acetone",
"source": "multiway_valve_1",
"target": "flask_acetone",
"type": "fluid",
"port": {
"multiway_valve_1": "6",
"flask_acetone": "outlet"
}
},
{
"id": "link_valve1_hexane",
"source": "multiway_valve_1",
"target": "flask_hexane",
"type": "fluid",
"port": {
"multiway_valve_1": "7",
"flask_hexane": "outlet"
}
},
{
"id": "link_valve1_valve2",
"source": "multiway_valve_1",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"multiway_valve_1": "8",
"multiway_valve_2": "1"
}
},
{
"id": "link_valve2_reaction_mixture",
"source": "multiway_valve_2",
"target": "reaction_mixture",
"type": "fluid",
"port": {
"multiway_valve_2": "2",
"reaction_mixture": "inlet"
}
},
{
"id": "link_valve2_crude_product",
"source": "multiway_valve_2",
"target": "crude_product",
"type": "fluid",
"port": {
"multiway_valve_2": "3",
"crude_product": "inlet"
}
},
{
"id": "link_valve2_intermediate1",
"source": "multiway_valve_2",
"target": "intermediate_vessel_1",
"type": "fluid",
"port": {
"multiway_valve_2": "4",
"intermediate_vessel_1": "inlet"
}
},
{
"id": "link_valve2_intermediate2",
"source": "multiway_valve_2",
"target": "intermediate_vessel_2",
"type": "fluid",
"port": {
"multiway_valve_2": "5",
"intermediate_vessel_2": "inlet"
}
},
{
"id": "link_valve2_celite",
"source": "multiway_valve_2",
"target": "filter_celite",
"type": "fluid",
"port": {
"multiway_valve_2": "6",
"filter_celite": "inlet"
}
},
{
"id": "link_valve2_silica_gel",
"source": "multiway_valve_2",
"target": "column_silica_gel",
"type": "fluid",
"port": {
"multiway_valve_2": "7",
"column_silica_gel": "inlet"
}
},
{
"id": "link_valve2_C18",
"source": "multiway_valve_2",
"target": "filter_C18",
"type": "fluid",
"port": {
"multiway_valve_2": "8",
"filter_C18": "inlet"
}
},
{
"id": "link_celite_collection1",
"source": "filter_celite",
"target": "collection_bottle_1",
"type": "fluid",
"port": {
"filter_celite": "outlet",
"collection_bottle_1": "inlet"
}
},
{
"id": "link_silica_gel_collection2",
"source": "column_silica_gel",
"target": "collection_bottle_2",
"type": "fluid",
"port": {
"column_silica_gel": "outlet",
"collection_bottle_2": "inlet"
}
},
{
"id": "link_C18_collection3",
"source": "filter_C18",
"target": "collection_bottle_3",
"type": "fluid",
"port": {
"filter_C18": "outlet",
"collection_bottle_3": "inlet"
}
},
{
"id": "link_collection1_pure_product",
"source": "collection_bottle_1",
"target": "pure_product",
"type": "fluid",
"port": {
"collection_bottle_1": "outlet",
"pure_product": "inlet"
}
},
{
"id": "link_collection2_pure_product",
"source": "collection_bottle_2",
"target": "pure_product",
"type": "fluid",
"port": {
"collection_bottle_2": "outlet",
"pure_product": "inlet"
}
},
{
"id": "link_collection3_pure_product",
"source": "collection_bottle_3",
"target": "pure_product",
"type": "fluid",
"port": {
"collection_bottle_3": "outlet",
"pure_product": "inlet"
}
},
{
"id": "link_waste_connection",
"source": "pure_product",
"target": "waste_workup",
"type": "fluid",
"port": {
"pure_product": "waste_outlet",
"waste_workup": "inlet"
}
}
]
}

View File

@@ -0,0 +1,304 @@
{
"nodes": [
{
"id": "SimpleProtocolStation",
"name": "简单协议工作站",
"children": [
"transfer_pump_1",
"multiway_valve_1",
"flask_DMF",
"flask_ethyl_acetate",
"flask_methanol",
"main_reactor",
"waste_workup",
"collection_bottle_1",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": ["PumpTransferProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵1",
"children": [],
"parent": "SimpleProtocolStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 500,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0,
"transfer_rate": 5.0
},
"data": {
"position": 0.0,
"status": "Idle",
"valve_position": "0"
}
},
{
"id": "multiway_valve_1",
"name": "八通阀1",
"children": [],
"parent": "SimpleProtocolStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 500,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "flask_DMF",
"name": "DMF试剂瓶",
"children": [],
"parent": "SimpleProtocolStation",
"type": "container",
"class": null,
"position": {
"x": 200,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "DMF",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯试剂瓶",
"children": [],
"parent": "SimpleProtocolStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "ethyl_acetate",
"liquid_volume": 800.0
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇试剂瓶",
"children": [],
"parent": "SimpleProtocolStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"liquid_type": "methanol",
"liquid_volume": 800.0
}
]
}
},
{
"id": "main_reactor",
"name": "主反应器",
"children": [],
"parent": "SimpleProtocolStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_workup",
"name": "废液处理瓶",
"children": [],
"parent": "SimpleProtocolStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_bottle_1",
"name": "收集瓶1",
"children": [],
"parent": "SimpleProtocolStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "SimpleProtocolStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_pump_valve",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_valve_air",
"source": "multiway_valve_1",
"target": "flask_air",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_air": "top"
}
},
{
"id": "link_valve_DMF",
"source": "multiway_valve_1",
"target": "flask_DMF",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_DMF": "outlet"
}
},
{
"id": "link_valve_ethyl_acetate",
"source": "multiway_valve_1",
"target": "flask_ethyl_acetate",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_ethyl_acetate": "outlet"
}
},
{
"id": "link_valve_methanol",
"source": "multiway_valve_1",
"target": "flask_methanol",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_methanol": "outlet"
}
},
{
"id": "link_valve_reactor",
"source": "multiway_valve_1",
"target": "main_reactor",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"main_reactor": "inlet"
}
},
{
"id": "link_valve_waste",
"source": "multiway_valve_1",
"target": "waste_workup",
"type": "fluid",
"port": {
"multiway_valve_1": "6",
"waste_workup": "inlet"
}
},
{
"id": "link_valve_collection",
"source": "multiway_valve_1",
"target": "collection_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_1": "7",
"collection_bottle_1": "inlet"
}
}
]
}

View File

@@ -0,0 +1,432 @@
{
"nodes": [
{
"id": "RunColumnTestStation",
"name": "柱层析测试工作站",
"children": [
"transfer_pump_1",
"multiway_valve_1",
"column_1",
"flask_sample",
"flask_hexane",
"flask_ethyl_acetate",
"flask_methanol",
"column_vessel",
"collection_flask_1",
"collection_flask_2",
"collection_flask_3",
"waste_flask",
"main_reactor"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": ["RunColumnProtocol", "PumpTransferProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "RunColumnTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 300,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL_PUMP1",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"position": 0.0
}
},
{
"id": "multiway_valve_1",
"name": "八通阀门",
"children": [],
"parent": "RunColumnTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 300,
"y": 400,
"z": 0
},
"config": {
"port": "VIRTUAL_VALVE1",
"positions": 8
},
"data": {
"current_position": 1
}
},
{
"id": "column_1",
"name": "柱层析设备",
"children": [],
"parent": "RunColumnTestStation",
"type": "device",
"class": "virtual_column",
"position": {
"x": 600,
"y": 350,
"z": 0
},
"config": {
"port": "VIRTUAL_COLUMN1",
"max_flow_rate": 5.0,
"column_length": 30.0,
"column_diameter": 2.5
},
"data": {
"status": "Idle",
"column_state": "Ready"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 500,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "crude_mixture",
"volume": 200.0,
"concentration": 70.0
}
]
}
},
{
"id": "flask_hexane",
"name": "正己烷洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 200,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "hexane",
"volume": 1500.0,
"concentration": 99.8
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethyl_acetate",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "methanol",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "column_vessel",
"name": "柱容器",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 450,
"z": 0
},
"config": {
"max_volume": 300.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_flask_1",
"name": "收集瓶1",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_flask_2",
"name": "收集瓶2",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_flask_3",
"name": "收集瓶3",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 500,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_flask",
"name": "废液瓶",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 500,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "main_reactor",
"name": "反应器",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 600,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "reaction_mixture",
"volume": 300.0,
"concentration": 85.0
}
]
}
}
],
"links": [
{
"id": "link_pump_valve",
"source": "transfer_pump_1",
"target": "multiway_valve_1",
"type": "fluid",
"port": {
"transfer_pump_1": "transferpump",
"multiway_valve_1": "transferpump"
}
},
{
"id": "link_valve_sample",
"source": "multiway_valve_1",
"target": "flask_sample",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"flask_sample": "outlet"
}
},
{
"id": "link_valve_hexane",
"source": "multiway_valve_1",
"target": "flask_hexane",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"flask_hexane": "outlet"
}
},
{
"id": "link_valve_ethyl_acetate",
"source": "multiway_valve_1",
"target": "flask_ethyl_acetate",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"flask_ethyl_acetate": "outlet"
}
},
{
"id": "link_valve_methanol",
"source": "multiway_valve_1",
"target": "flask_methanol",
"type": "fluid",
"port": {
"multiway_valve_1": "4",
"flask_methanol": "outlet"
}
},
{
"id": "link_valve_column_vessel",
"source": "multiway_valve_1",
"target": "column_vessel",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"column_vessel": "inlet"
}
},
{
"id": "link_valve_collection1",
"source": "multiway_valve_1",
"target": "collection_flask_1",
"type": "fluid",
"port": {
"multiway_valve_1": "6",
"collection_flask_1": "inlet"
}
},
{
"id": "link_valve_collection2",
"source": "multiway_valve_1",
"target": "collection_flask_2",
"type": "fluid",
"port": {
"multiway_valve_1": "7",
"collection_flask_2": "inlet"
}
},
{
"id": "link_valve_waste",
"source": "multiway_valve_1",
"target": "waste_flask",
"type": "fluid",
"port": {
"multiway_valve_1": "8",
"waste_flask": "inlet"
}
},
{
"id": "link_column_device_vessel",
"source": "column_1",
"target": "column_vessel",
"type": "transport",
"port": {
"column_1": "columnin",
"column_vessel": "column_port"
}
},
{
"id": "link_column_collection3",
"source": "column_1",
"target": "collection_flask_3",
"type": "transport",
"port": {
"column_1": "columnout",
"collection_flask_3": "column_outlet"
}
}
]
}

View File

@@ -0,0 +1,141 @@
{
"nodes": [
{
"id": "SimpleStirHeatChillTestStation",
"name": "搅拌加热测试站",
"children": [
"stirrer_1",
"heatchill_1",
"main_reactor",
"secondary_reactor"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 500,
"y": 200,
"z": 0
},
"config": {
"protocol_type": [
"StirProtocol",
"StartStirProtocol",
"StopStirProtocol",
"HeatChillProtocol",
"HeatChillStartProtocol",
"HeatChillStopProtocol"
]
},
"data": {}
},
{
"id": "stirrer_1",
"name": "主搅拌器",
"children": [],
"parent": "SimpleStirHeatChillTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 400,
"y": 350,
"z": 0
},
"config": {
"port": "VIRTUAL_STIRRER1",
"max_speed": 1500.0,
"min_speed": 50.0
},
"data": {
"status": "Idle"
}
},
{
"id": "heatchill_1",
"name": "主加热冷却器",
"children": [],
"parent": "SimpleStirHeatChillTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 600,
"y": 350,
"z": 0
},
"config": {
"port": "VIRTUAL_HEATCHILL1",
"max_temp": 200.0,
"min_temp": -80.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "main_reactor",
"name": "主反应器",
"children": [],
"parent": "SimpleStirHeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 500,
"y": 450,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"liquid_type": "water",
"liquid_volume": 500.0
}
]
}
},
{
"id": "secondary_reactor",
"name": "副反应器",
"children": [],
"parent": "SimpleStirHeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 700,
"y": 450,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"id": "link_stirrer1_main_reactor",
"source": "stirrer_1",
"target": "main_reactor",
"type": "mechanical",
"port": {
"stirrer_1": "stirrer",
"main_reactor": "stirrer_port"
}
},
{
"id": "link_heatchill1_main_reactor",
"source": "heatchill_1",
"target": "main_reactor",
"type": "mechanical",
"port": {
"heatchill_1": "heatchill",
"main_reactor": "heating_jacket"
}
}
]
}

View File

@@ -0,0 +1,22 @@
{
"nodes": [
{
"id": "BIOMEK",
"name": "BIOMEK",
"parent": null,
"type": "device",
"class": "liquid_handler.biomek",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
},
"data": {},
"children": [
]
}
],
"links": []
}

View File

@@ -0,0 +1,45 @@
{
"nodes": [
{
"id": "Camera",
"name": "摄像头",
"children": [
],
"parent": null,
"type": "device",
"class": "camera",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"camera_index": 0,
"period": 0.05
},
"data": {
}
},
{
"id": "Gripper1",
"name": "假夹爪",
"children": [
],
"parent": null,
"type": "device",
"class": "gripper.mock",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
},
"data": {
}
}
],
"links": [
]
}

View File

@@ -0,0 +1,258 @@
1. 用到的仪器
virtual_multiway_valve(√) 八通阀门
virtual_transfer_pump(√) 转移泵
virtual_centrifuge() 离心机
virtual_rotavap() 旋蒸仪
virtual_heatchill() 加热器
virtual_stirrer() 搅拌器
virtual_solenoid_valve() 电磁阀
virtual_vacuum_pump(√) vacuum_pump.mock 真空泵
virtual_gas_source(√) 气源
virtual_filter() 过滤器
virtual_column(√) 层析柱
separator() homemade_grbl_conductivity 分液漏斗
2. 用到的protocol
PumpTransferProtocol: generate_pump_protocol_with_rinsing, (√)
这个重复了删掉CleanProtocol: generate_clean_protocol,
SeparateProtocol: generate_separate_protocol, (×)
EvaporateProtocol: generate_evaporate_protocol, (√)
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol, (√)
CentrifugeProtocol: generate_centrifuge_protocol, (√)
AddProtocol: generate_add_protocol, (√)
FilterProtocol: generate_filter_protocol, (√)
HeatChillProtocol: generate_heat_chill_protocol, (√)
HeatChillStartProtocol: generate_heat_chill_start_protocol, (√)
HeatChillStopProtocol: generate_heat_chill_stop_protocol, (√)
HeatChillToTempProtocol:
StirProtocol: generate_stir_protocol, (√)
StartStirProtocol: generate_start_stir_protocol, (√)
StopStirProtocol: generate_stop_stir_protocol, (√)
这个重复了删掉TransferProtocol: generate_transfer_protocol,
CleanVesselProtocol: generate_clean_vessel_protocol, (√)
DissolveProtocol: generate_dissolve_protocol, (√)
FilterThroughProtocol: generate_filter_through_protocol, (√)
RunColumnProtocol: generate_run_column_protocol, (√)<RunColumn Rf="?" column="column" from_vessel="rotavap" ratio="5:95" solvent1="methanol" solvent2="chloroform" to_vessel="rotavap"/>
上下文体积搜索
3. 还没创建的protocol
ResetHandling 写完了 <ResetHandling solvent="methanol"/>
Dry 写完了 <Dry compound="product" vessel="filter"/>
AdjustPH 写完了 <AdjustPH pH="8.0" reagent="hydrochloric acid" vessel="main_reactor"/>
Recrystallize 写完了 <Recrystallize ratio="?" solvent1="dichloromethane" solvent2="methanol" vessel="filter" volume="?"/>
TakeSample <TakeSample id="a" vessel="rotavap"/>
Hydrogenate <Hydrogenate temp="45 °C" time="?" vessel="main_reactor"/>
4. 参数对齐
class PumpTransferProtocol(BaseModel):
from_vessel: str
to_vessel: str
volume: float
amount: str = ""
time: float = 0
viscous: bool = False
rinsing_solvent: str = "air" <Transfer from_vessel="main_reactor" to_vessel="rotavap"/>
rinsing_volume: float = 5000 <Transfer event="A" from_vessel="reactor" rate_spec="dropwise" to_vessel="main_reactor"/>
rinsing_repeats: int = 2 <Transfer from_vessel="separator" through="cartridge" to_vessel="rotavap"/>
solid: bool = False 测完了三个都能跑✅
flowrate: float = 500
transfer_flowrate: float = 2500
class SeparateProtocol(BaseModel):
purpose: str
product_phase: str
from_vessel: str
separation_vessel: str
to_vessel: str
waste_phase_to_vessel: str
solvent: str
solvent_volume: float <Separate product_phase="bottom" purpose="wash" solvent="water" vessel="separator" volume="?"/>
through: str <Separate product_phase="top" purpose="separate" vessel="separator"/>
repeats: int <Separate product_phase="bottom" purpose="extract" repeats="3" solvent="CH2Cl2" vessel="separator" volume="?"/>
stir_time: float <Separate product_phase="top" product_vessel="flask" purpose="separate" vessel="separator" waste_vessel="separator"/>
stir_speed: float
settling_time: float 测完了能跑✅
class EvaporateProtocol(BaseModel):
vessel: str
pressure: float
temp: float <Evaporate solvent="ethanol" vessel="rotavap"/>
time: float 测完了能跑✅
stir_speed: float
class EvacuateAndRefillProtocol(BaseModel):
vessel: str
gas: str <EvacuateAndRefill gas="nitrogen" vessel="main_reactor"/>
repeats: int 测完了能跑✅
class AddProtocol(BaseModel):
vessel: str
reagent: str
volume: float
mass: float
amount: str
time: float
stir: bool
stir_speed: float <Add reagent="ethanol" vessel="main_reactor" volume="2.7 mL"/>
<Add event="A" mass="19.3 g" mol="0.28 mol" rate_spec="portionwise" reagent="sodium nitrite" time="1 h" vessel="main_reactor"/>
<Add mass="4.5 g" mol="16.2 mmol" reagent="(S)-2-phthalimido-6-hydroxyhexanoic acid" vessel="main_reactor"/>
<Add purpose="dilute" reagent="hydrochloric acid" vessel="main_reactor" volume="?"/>
<Add equiv="1.1" event="B" mol="25.2 mmol" rate_spec="dropwise" reagent="1-fluoro-2-nitrobenzene" time="20 min"
vessel="main_reactor" volume="2.67 mL"/>
<Add ratio="?" reagent="tetrahydrofuran|tert-butanol" vessel="main_reactor" volume="?"/>
viscous: bool
purpose: str 测完了能跑✅
class CentrifugeProtocol(BaseModel):
vessel: str
speed: float
time: float 没毛病
temp: float
class FilterProtocol(BaseModel):
vessel: str
filtrate_vessel: str
stir: bool <Filter vessel="filter"/>
stir_speed: float <Filter filtrate_vessel="rotavap" vessel="filter"/>
temp: float 测完了能跑✅
continue_heatchill: bool
volume: float
class HeatChillProtocol(BaseModel):
vessel: str
temp: float
time: float <HeatChill pressure="1 mbar" temp_spec="room temperature" time="?" vessel="main_reactor"/>
<HeatChill temp_spec="room temperature" time_spec="overnight" vessel="main_reactor"/>
<HeatChill temp="256 °C" time="?" vessel="main_reactor"/>
<HeatChill reflux_solvent="methanol" temp_spec="reflux" time="2 h" vessel="main_reactor"/>
<HeatChillToTemp temp_spec="room temperature" vessel="main_reactor"/>
stir: bool 测完了能跑✅
stir_speed: float
purpose: str
class HeatChillStartProtocol(BaseModel):
vessel: str
temp: float 疑似没有
purpose: str
class HeatChillStopProtocol(BaseModel):
vessel: str 疑似没有
class StirProtocol(BaseModel):
stir_time: float
stir_speed: float <Stir time="0.5 h" vessel="main_reactor"/>
<Stir event="A" time="30 min" vessel="main_reactor"/>
<Stir time_spec="several minutes" vessel="filter"/>
settling_time: float 测完了能跑✅
class StartStirProtocol(BaseModel):
vessel: str
stir_speed: float 疑似没有
purpose: str
class StopStirProtocol(BaseModel):
vessel: str 疑似没有
class TransferProtocol(BaseModel):
from_vessel: str
to_vessel: str
volume: float
amount: str = ""
time: float = 0
viscous: bool = False
rinsing_solvent: str = ""
rinsing_volume: float = 0.0
rinsing_repeats: int = 0
solid: bool = False 这个protocol早该删掉了
class CleanVesselProtocol(BaseModel):
vessel: str
solvent: str
volume: float
temp: float
repeats: int = 1 <CleanVessel vessel="centrifuge"/>
class DissolveProtocol(BaseModel):
vessel: str
solvent: str
volume: float <Dissolve mass="2.9 g" mol="0.12 mol" reagent="magnesium" vessel="main_reactor"/>
amount: str = "" <Dissolve mass="12.9 g" reagent="4-tert-butylbenzyl bromide" vessel="main_reactor"/>
temp: float = 25.0 <Dissolve solvent="diisopropyl ether" vessel="rotavap" volume="?"/>
time: float = 0.0 <Dissolve event="A" mass="?" reagent="pyridinone" vessel="main_reactor"/>
stir_speed: float = 0.0 测完了能跑✅
class FilterThroughProtocol(BaseModel):
from_vessel: str
to_vessel: str
filter_through: str
eluting_solvent: str = ""
eluting_volume: float = 0.0 疑似没有
eluting_repeats: int = 0
residence_time: float = 0.0
class RunColumnProtocol(BaseModel):
from_vessel: str
to_vessel: str <RunColumn Rf="?" column="column" from_vessel="rotavap" pct1="40 %" pct2="50 %" solvent1="ethyl acetate" solvent2="hexane" to_vessel="rotavap"/>
column: str 测完了能跑✅
class WashSolidProtocol(BaseModel):
vessel: str
solvent: str
volume: float
filtrate_vessel: str = "" <WashSolid repeats="4" solvent="water" vessel="main_reactor" volume="400 mL"/>
temp: float = 25.0 <WashSolid filtrate_vessel="rotavap" solvent="formic acid" vessel="main_reactor" volume="?"/>
stir: bool = False <WashSolid solvent="acetone" vessel="rotavap" volume="5 mL"/>
<WashSolid solvent="ethyl alcohol" vessel="main_reactor" volume_spec="small volume"/>
<WashSolid filtrate_vessel="rotavap" mass="10 g" solvent="toluene" vessel="separator"/>
<WashSolid repeats_spec="several" solvent="water" vessel="main_reactor" volume="?"/>
stir_speed: float = 0.0 测完了能跑✅
time: float = 0.0
repeats: int = 1
class AdjustPHProtocol(BaseModel):
vessel: str = Field(..., description="目标容器")
ph_value: float = Field(..., description="目标pH值") # 改为 ph_value
reagent: str = Field(..., description="酸碱试剂名称")
# 移除其他可选参数,使用默认值 <新写的没问题>
class ResetHandlingProtocol(BaseModel):
solvent: str = Field(..., description="溶剂名称") <新写的没问题>
class DryProtocol(BaseModel):
compound: str = Field(..., description="化合物名称") <新写的没问题>
vessel: str = Field(..., description="目标容器")
class RecrystallizeProtocol(BaseModel):
ratio: str = Field(..., description="溶剂比例(如 '1:1', '3:7'")
solvent1: str = Field(..., description="第一种溶剂名称") <新写的没问题>
solvent2: str = Field(..., description="第二种溶剂名称")
vessel: str = Field(..., description="目标容器")
volume: float = Field(..., description="总体积 (mL)")
class HydrogenateProtocol(BaseModel):
temp: str = Field(..., description="反应温度(如 '45 °C'")
time: str = Field(..., description="反应时间(如 '2 h'") <新写的没问题>
vessel: str = Field(..., description="反应容器")
还差
<dissolve>
<separate>
<CleanVessel vessel="centrifuge"/>
单位修复:
evaporate
heatchill
recrysitallize
stir
wash solid

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,314 @@
{
"nodes": [
{
"id": "MockChiller1",
"name": "模拟冷却器",
"children": [
"MockContainerForChiller1"
],
"parent": null,
"type": "device",
"class": "mock_chiller",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"current_temperature": 25.0,
"target_temperature": 25.0,
"status": "Idle",
"is_cooling": false,
"is_heating": false,
"vessel": "",
"purpose": ""
}
},
{
"id": "MockContainerForChiller1",
"name": "模拟容器",
"type": "container",
"parent": "MockChiller1",
"position": {
"x": 5,
"y": 0,
"z": 0
},
"data": {
"liquid_type": "CuCl2",
"liquid_volume": "100"
},
"children": []
},
{
"id": "MockFilter1",
"name": "模拟过滤器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_filter",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"is_filtering": false,
"flow_rate": 0.0,
"filter_life": 100.0,
"vessel": "",
"filtrate_vessel": "",
"filtered_volume": 0.0,
"target_volume": 0.0,
"progress": 0.0,
"stir": false,
"stir_speed": 0.0,
"temperature": 25.0,
"continue_heatchill": false
}
},
{
"id": "MockHeater1",
"name": "模拟加热器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_heater",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"current_temperature": 25.0,
"target_temperature": 25.0,
"status": "Idle",
"is_heating": false,
"heating_power": 0.0,
"max_temperature": 300.0,
"vessel": "Unknown",
"purpose": "Unknown",
"stir": false,
"stir_speed": 0.0
}
},
{
"id": "MockPump1",
"name": "模拟泵设备",
"children": [],
"parent": null,
"type": "device",
"class": "mock_pump",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"current_device": "MockPump1",
"pump_state": "Stopped",
"flow_rate": 0.0,
"target_flow_rate": 0.0,
"pressure": 0.0,
"total_volume": 0.0,
"max_flow_rate": 100.0,
"max_pressure": 10.0,
"from_vessel": "",
"to_vessel": "",
"transfer_volume": 0.0,
"amount": "",
"transfer_time": 0.0,
"is_viscous": false,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"is_solid": false,
"time_spent": 0.0,
"time_remaining": 0.0
}
},
{
"id": "MockRotavap1",
"name": "模拟旋转蒸发器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_rotavap",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"rotate_state": "Stopped",
"rotate_time": 0.0,
"rotate_speed": 0.0,
"pump_state": "Stopped",
"pump_time": 0.0,
"vacuum_level": 1013.25,
"temperature": 25.0,
"target_temperature": 25.0,
"success": "True"
}
},
{
"id": "MockSeparator1",
"name": "模拟分离器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_separator",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"settling_time": 0.0,
"valve_state": "Closed",
"shake_time": 0.0,
"shake_status": "Not Shaking",
"current_device": "MockSeparator1",
"purpose": "",
"product_phase": "",
"from_vessel": "",
"separation_vessel": "",
"to_vessel": "",
"waste_phase_to_vessel": "",
"solvent": "",
"solvent_volume": 0.0,
"through": "",
"repeats": 1,
"stir_time": 0.0,
"stir_speed": 0.0,
"time_spent": 0.0,
"time_remaining": 0.0
}
},
{
"id": "MockSolenoidValve1",
"name": "模拟电磁阀",
"children": [],
"parent": null,
"type": "device",
"class": "mock_solenoid_valve",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"valve_status": "Closed"
}
},
{
"id": "MockStirrer1NEW",
"name": "模拟搅拌器(new)",
"children": [],
"parent": null,
"type": "device",
"class": "mock_stirrer_new",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"vessel": "",
"purpose": "",
"stir_speed": 0.0,
"target_stir_speed": 0.0,
"stir_state": "Stopped",
"stir_time": 0.0,
"settling_time": 0.0,
"progress": 0.0,
"max_stir_speed": 2000.0
}
},
{
"id": "MockStirrer1",
"name": "模拟搅拌器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_stirrer",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"stir_speed": 0.0,
"target_stir_speed": 0.0,
"stir_state": "Stopped",
"temperature": 25.0,
"target_temperature": 25.0,
"heating_state": "Off",
"heating_power": 0.0,
"max_stir_speed": 2000.0,
"max_temperature": 300.0
}
},
{
"id": "MockVacuum1",
"name": "模拟真空泵",
"children": [],
"parent": null,
"type": "device",
"class": "mock_vacuum",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"power_state": "Off",
"pump_state": "Stopped",
"vacuum_level": 1013.25,
"target_vacuum": 50.0,
"pump_speed": 0.0,
"pump_efficiency": 95.0,
"max_pump_speed": 100.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,30 @@
{
"nodes": [
{
"id": "MockChiller1",
"name": "模拟冷却器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_chiller",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"current_temperature": 25.0,
"target_temperature": 25.0,
"status": "Idle",
"is_cooling": false,
"is_heating": false,
"vessel": "",
"purpose": ""
}
}
],
"links": []
}

View File

@@ -0,0 +1,36 @@
{
"nodes": [
{
"id": "MockFilter1",
"name": "模拟过滤器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_filter",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"is_filtering": false,
"flow_rate": 0.0,
"filter_life": 100.0,
"vessel": "",
"filtrate_vessel": "",
"filtered_volume": 0.0,
"target_volume": 0.0,
"progress": 0.0,
"stir": false,
"stir_speed": 0.0,
"temperature": 25.0,
"continue_heatchill": false
}
}
],
"links": []
}

View File

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "MockHeater1",
"name": "模拟加热器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_heater",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"current_temperature": 25.0,
"target_temperature": 25.0,
"status": "Idle",
"is_heating": false,
"heating_power": 0.0,
"max_temperature": 300.0,
"vessel": "Unknown",
"purpose": "Unknown",
"stir": false,
"stir_speed": 0.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,44 @@
{
"nodes": [
{
"id": "MockPump1",
"name": "模拟泵设备",
"children": [],
"parent": null,
"type": "device",
"class": "mock_pump",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"current_device": "MockPump1",
"pump_state": "Stopped",
"flow_rate": 0.0,
"target_flow_rate": 0.0,
"pressure": 0.0,
"total_volume": 0.0,
"max_flow_rate": 100.0,
"max_pressure": 10.0,
"from_vessel": "",
"to_vessel": "",
"transfer_volume": 0.0,
"amount": "",
"transfer_time": 0.0,
"is_viscous": false,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"is_solid": false,
"time_spent": 0.0,
"time_remaining": 0.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "MockRotavap1",
"name": "模拟旋转蒸发器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_rotavap",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"rotate_state": "Stopped",
"rotate_time": 0.0,
"rotate_speed": 0.0,
"pump_state": "Stopped",
"pump_time": 0.0,
"vacuum_level": 1013.25,
"temperature": 25.0,
"target_temperature": 25.0,
"success": "True"
}
}
],
"links": []
}

View File

@@ -0,0 +1,43 @@
{
"nodes": [
{
"id": "MockSeparator1",
"name": "模拟分离器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_separator",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"settling_time": 0.0,
"valve_state": "Closed",
"shake_time": 0.0,
"shake_status": "Not Shaking",
"current_device": "MockSeparator1",
"purpose": "",
"product_phase": "",
"from_vessel": "",
"separation_vessel": "",
"to_vessel": "",
"waste_phase_to_vessel": "",
"solvent": "",
"solvent_volume": 0.0,
"through": "",
"repeats": 1,
"stir_time": 0.0,
"stir_speed": 0.0,
"time_spent": 0.0,
"time_remaining": 0.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,25 @@
{
"nodes": [
{
"id": "MockSolenoidValve1",
"name": "模拟电磁阀",
"children": [],
"parent": null,
"type": "device",
"class": "mock_solenoid_valve",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"valve_status": "Closed"
}
}
],
"links": []
}

View File

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "MockStirrer1",
"name": "模拟搅拌器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_stirrer",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"stir_speed": 0.0,
"target_stir_speed": 0.0,
"stir_state": "Stopped",
"temperature": 25.0,
"target_temperature": 25.0,
"heating_state": "Off",
"heating_power": 0.0,
"max_stir_speed": 2000.0,
"max_temperature": 300.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "MockStirrer1COPY",
"name": "模拟搅拌器(Copy)",
"children": [],
"parent": null,
"type": "device",
"class": "mock_stirrer_new",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"vessel": "",
"purpose": "",
"stir_speed": 0.0,
"target_stir_speed": 0.0,
"stir_state": "Stopped",
"stir_time": 0.0,
"settling_time": 0.0,
"progress": 0.0,
"max_stir_speed": 2000.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,31 @@
{
"nodes": [
{
"id": "MockVacuum1",
"name": "模拟真空泵",
"children": [],
"parent": null,
"type": "device",
"class": "mock_vacuum",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"power_state": "Off",
"pump_state": "Stopped",
"vacuum_level": 1013.25,
"target_vacuum": 50.0,
"pump_speed": 0.0,
"pump_efficiency": 95.0,
"max_pump_speed": 100.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,376 @@
{
"nodes": [
{
"id": "AddTestStation",
"name": "添加试剂测试工作站",
"children": [
"transfer_pump",
"multiway_valve",
"stirrer",
"flask_reagent1",
"flask_reagent2",
"flask_reagent3",
"flask_reagent4",
"reactor",
"flask_waste",
"flask_rinsing",
"flask_buffer"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "TransferProtocol", "StartStirProtocol", "StopStirProtocol"]
},
"data": {}
},
{
"id": "transfer_pump",
"name": "注射器泵",
"children": [],
"parent": "AddTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 5.0
},
"data": {
"status": "Idle"
}
},
{
"id": "multiway_valve",
"name": "八通阀门",
"children": [],
"parent": "AddTestStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 420,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"positions": 8
},
"data": {
"status": "Idle",
"current_position": 1
}
},
{
"id": "stirrer",
"name": "搅拌器",
"children": [],
"parent": "AddTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 720,
"y": 450,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_reagent1",
"name": "试剂瓶1 (甲醇)",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 400,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "甲醇",
"volume": 800.0,
"concentration": "99.9%"
}
]
}
},
{
"id": "flask_reagent2",
"name": "试剂瓶2 (乙醇)",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 180,
"y": 400,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "乙醇",
"volume": 750.0,
"concentration": "95%"
}
]
}
},
{
"id": "flask_reagent3",
"name": "试剂瓶3 (丙酮)",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 260,
"y": 400,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "丙酮",
"volume": 900.0,
"concentration": "99.5%"
}
]
}
},
{
"id": "flask_reagent4",
"name": "试剂瓶4 (二氯甲烷)",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 340,
"y": 400,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "二氯甲烷",
"volume": 850.0,
"concentration": "99.8%"
}
]
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 720,
"y": 400,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_waste",
"name": "废液瓶",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 400,
"z": 0
},
"config": {
"max_volume": 3000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_rinsing",
"name": "冲洗液瓶",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "去离子水",
"volume": 800.0,
"concentration": "纯净"
}
]
}
},
{
"id": "flask_buffer",
"name": "缓冲液瓶",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 400,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "磷酸盐缓冲液",
"volume": 700.0,
"concentration": "0.1M, pH 7.4"
}
]
}
}
],
"links": [
{
"source": "transfer_pump",
"target": "multiway_valve",
"type": "physical",
"port": {
"transfer_pump": "syringe-port",
"multiway_valve": "multiway-valve-inlet"
}
},
{
"source": "multiway_valve",
"target": "flask_reagent1",
"type": "physical",
"port": {
"multiway_valve": "multiway-valve-port-1",
"flask_reagent1": "top"
}
},
{
"source": "multiway_valve",
"target": "flask_reagent2",
"type": "physical",
"port": {
"multiway_valve": "multiway-valve-port-2",
"flask_reagent2": "top"
}
},
{
"source": "multiway_valve",
"target": "flask_reagent3",
"type": "physical",
"port": {
"multiway_valve": "multiway-valve-port-3",
"flask_reagent3": "top"
}
},
{
"source": "multiway_valve",
"target": "flask_reagent4",
"type": "physical",
"port": {
"multiway_valve": "multiway-valve-port-4",
"flask_reagent4": "top"
}
},
{
"source": "multiway_valve",
"target": "reactor",
"type": "physical",
"port": {
"multiway_valve": "multiway-valve-port-5",
"reactor": "top"
}
},
{
"source": "multiway_valve",
"target": "flask_waste",
"type": "physical",
"port": {
"multiway_valve": "multiway-valve-port-6",
"flask_waste": "top"
}
},
{
"source": "multiway_valve",
"target": "flask_rinsing",
"type": "physical",
"port": {
"multiway_valve": "multiway-valve-port-7",
"flask_rinsing": "top"
}
},
{
"source": "multiway_valve",
"target": "flask_buffer",
"type": "physical",
"port": {
"multiway_valve": "multiway-valve-port-8",
"flask_buffer": "top"
}
},
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "stirrer-vessel",
"reactor": "bottom"
}
}
]
}

View File

@@ -0,0 +1,271 @@
{
"nodes": [
{
"id": "CentrifugeTestStation",
"name": "离心机测试工作站",
"children": [
"pump_add",
"flask_1",
"flask_2",
"flask_3",
"reactor",
"stirrer",
"centrifuge_1",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "CentrifugeProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "pump_add",
"children": [],
"parent": "CentrifugeTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "stirrer",
"children": [],
"parent": "CentrifugeTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "centrifuge_1",
"name": "离心机",
"children": [],
"parent": "CentrifugeTestStation",
"type": "device",
"class": "virtual_centrifuge",
"position": {
"x": 800,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_speed": 15000.0,
"max_temp": 40.0,
"min_temp": 4.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_1",
"name": "样品瓶1",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_2",
"name": "样品瓶2",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_3",
"name": "缓冲液瓶",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_1",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_1": "top"
}
},
{
"source": "pump_add",
"target": "flask_2",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_2": "top"
}
},
{
"source": "pump_add",
"target": "flask_3",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_3": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
},
{
"source": "centrifuge_1",
"target": "reactor",
"type": "logical",
"port": {
"centrifuge_1": "chamber",
"reactor": "vessel"
}
},
{
"source": "centrifuge_1",
"target": "flask_1",
"type": "logical",
"port": {
"centrifuge_1": "chamber",
"flask_1": "vessel"
}
},
{
"source": "centrifuge_1",
"target": "flask_2",
"type": "logical",
"port": {
"centrifuge_1": "chamber",
"flask_2": "vessel"
}
}
]
}

View File

@@ -0,0 +1,362 @@
{
"nodes": [
{
"id": "CleanVesselTestStation",
"name": "容器清洗测试工作站",
"children": [
"transfer_pump_cleaner",
"heatchill_1",
"flask_water",
"flask_ethanol",
"flask_acetone",
"flask_waste",
"reactor",
"flask_buffer",
"flask_sample",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["CleanVesselProtocol", "TransferProtocol", "AddProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_cleaner",
"name": "清洗转移泵",
"children": [],
"parent": "CleanVesselTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "heatchill_1",
"name": "加热冷却器",
"children": [],
"parent": "CleanVesselTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 150.0,
"min_temp": -20.0
},
"data": {
"status": "Idle",
"current_temp": 25.0,
"target_temp": 25.0,
"vessel": "",
"purpose": "",
"progress": 0.0,
"current_status": "Ready"
}
},
{
"id": "flask_water",
"name": "水溶剂瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "water",
"volume": 1500.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇溶剂瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethanol",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮溶剂瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "acetone",
"volume": 1800.0,
"concentration": 99.9
}
]
}
},
{
"id": "flask_waste",
"name": "废液瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "residue",
"volume": 50.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_buffer",
"name": "缓冲液瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "buffer",
"volume": 1000.0,
"concentration": 10.0
}
]
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 428,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "transfer_pump_cleaner",
"target": "flask_water",
"type": "physical",
"port": {
"transfer_pump_cleaner": "1",
"flask_water": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_ethanol",
"type": "physical",
"port": {
"transfer_pump_cleaner": "2",
"flask_ethanol": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_acetone",
"type": "physical",
"port": {
"transfer_pump_cleaner": "3",
"flask_acetone": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_waste",
"type": "physical",
"port": {
"transfer_pump_cleaner": "4",
"flask_waste": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_cleaner": "5",
"reactor": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_buffer",
"type": "physical",
"port": {
"transfer_pump_cleaner": "6",
"flask_buffer": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_cleaner": "7",
"flask_sample": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_air",
"type": "physical",
"port": {
"transfer_pump_cleaner": "8",
"flask_air": "top"
}
},
{
"source": "heatchill_1",
"target": "reactor",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"reactor": "bottom"
}
},
{
"source": "heatchill_1",
"target": "flask_sample",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"flask_sample": "bottom"
}
}
]
}

View File

@@ -0,0 +1,343 @@
{
"nodes": [
{
"id": "DissolveTestStation",
"name": "溶解测试工作站",
"children": [
"transfer_pump_1",
"heatchill_1",
"stirrer_1",
"flask_water",
"flask_ethanol",
"flask_dmso",
"reactor",
"flask_sample",
"flask_buffer"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["DissolveProtocol", "TransferProtocol", "HeatChillProtocol", "StirProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "DissolveTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "heatchill_1",
"name": "加热冷却器",
"children": [],
"parent": "DissolveTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 150.0,
"min_temp": -20.0
},
"data": {
"status": "Idle",
"current_temp": 25.0,
"target_temp": 25.0,
"vessel": "",
"purpose": "",
"progress": 0.0,
"current_status": "Ready"
}
},
{
"id": "stirrer_1",
"name": "搅拌器",
"children": [],
"parent": "DissolveTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 750.1111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_water",
"name": "水溶剂瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "water",
"volume": 1500.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇溶剂瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethanol",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_dmso",
"name": "DMSO溶剂瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "dmso",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "solid_sample",
"volume": 10.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 428,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer",
"name": "缓冲液瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "buffer",
"volume": 1000.0,
"concentration": 10.0
}
]
}
}
],
"links": [
{
"source": "transfer_pump_1",
"target": "flask_water",
"type": "physical",
"port": {
"transfer_pump_1": "1",
"flask_water": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_ethanol",
"type": "physical",
"port": {
"transfer_pump_1": "2",
"flask_ethanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_dmso",
"type": "physical",
"port": {
"transfer_pump_1": "3",
"flask_dmso": "top"
}
},
{
"source": "transfer_pump_1",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_1": "4",
"reactor": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_1": "5",
"flask_sample": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_buffer",
"type": "physical",
"port": {
"transfer_pump_1": "6",
"flask_buffer": "top"
}
},
{
"source": "heatchill_1",
"target": "reactor",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"reactor": "bottom"
}
},
{
"source": "heatchill_1",
"target": "flask_sample",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"flask_sample": "bottom"
}
},
{
"source": "stirrer_1",
"target": "reactor",
"type": "physical",
"port": {
"stirrer_1": "stir_rod",
"reactor": "center"
}
},
{
"source": "stirrer_1",
"target": "flask_sample",
"type": "physical",
"port": {
"stirrer_1": "stir_rod",
"flask_sample": "center"
}
}
]
}

View File

@@ -0,0 +1,270 @@
{
"nodes": [
{
"id": "FilterTestStation",
"name": "过滤器测试工作站",
"children": [
"pump_add",
"flask_sample",
"flask_filtrate",
"flask_buffer",
"reactor",
"stirrer",
"filter_1",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "FilterProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "pump_add",
"children": [],
"parent": "FilterTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "stirrer",
"children": [],
"parent": "FilterTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "filter_1",
"name": "过滤器",
"children": [],
"parent": "FilterTestStation",
"type": "device",
"class": "virtual_filter",
"position": {
"x": 800,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_filtrate",
"name": "滤液瓶",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer",
"name": "缓冲液瓶",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_sample",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_sample": "top"
}
},
{
"source": "pump_add",
"target": "flask_filtrate",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_filtrate": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
},
{
"source": "filter_1",
"target": "reactor",
"type": "logical",
"port": {
"filter_1": "input",
"reactor": "vessel"
}
},
{
"source": "filter_1",
"target": "flask_sample",
"type": "logical",
"port": {
"filter_1": "input",
"flask_sample": "vessel"
}
},
{
"source": "filter_1",
"target": "flask_filtrate",
"type": "logical",
"port": {
"filter_1": "output",
"flask_filtrate": "vessel"
}
}
]
}

View File

@@ -0,0 +1,388 @@
{
"nodes": [
{
"id": "FilterThroughTestStation",
"name": "过滤通过测试工作站",
"children": [
"transfer_pump_1",
"filter_1",
"flask_ethanol",
"flask_water",
"flask_methanol",
"reactor",
"collection_flask",
"waste_flask",
"flask_sample",
"flask_celite",
"flask_silica"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["FilterThroughProtocol", "TransferProtocol", "FilterProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "FilterThroughTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "filter_1",
"name": "过滤器",
"children": [],
"parent": "FilterThroughTestStation",
"type": "device",
"class": "virtual_filter",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle",
"filter_state": "Ready",
"current_temp": 25.0,
"target_temp": 25.0,
"max_temp": 100.0,
"stir_speed": 0.0,
"max_stir_speed": 1000.0,
"filtered_volume": 0.0,
"progress": 0.0,
"message": ""
}
},
{
"id": "flask_ethanol",
"name": "乙醇溶剂瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethanol",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_water",
"name": "水溶剂瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "water",
"volume": 1800.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇溶剂瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "methanol",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "crude_product",
"volume": 200.0,
"concentration": 80.0
}
]
}
},
{
"id": "collection_flask",
"name": "收集瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_flask",
"name": "废液瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 300,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "sample_mixture",
"volume": 100.0,
"concentration": 50.0
}
]
}
},
{
"id": "flask_celite",
"name": "硅藻土容器",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 150,
"y": 300,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "celite",
"volume": 50.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_silica",
"name": "硅胶容器",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 300,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "silica",
"volume": 30.0,
"concentration": 100.0
}
]
}
}
],
"links": [
{
"source": "transfer_pump_1",
"target": "flask_ethanol",
"type": "physical",
"port": {
"transfer_pump_1": "1",
"flask_ethanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_water",
"type": "physical",
"port": {
"transfer_pump_1": "2",
"flask_water": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_methanol",
"type": "physical",
"port": {
"transfer_pump_1": "3",
"flask_methanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_1": "4",
"reactor": "top"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask",
"type": "physical",
"port": {
"transfer_pump_1": "5",
"collection_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "waste_flask",
"type": "physical",
"port": {
"transfer_pump_1": "6",
"waste_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_1": "7",
"flask_sample": "top"
}
},
{
"source": "filter_1",
"target": "collection_flask",
"type": "physical",
"port": {
"filter_1": "filter_element",
"collection_flask": "top"
}
},
{
"source": "filter_1",
"target": "reactor",
"type": "physical",
"port": {
"filter_1": "filter_element",
"reactor": "top"
}
}
]
}

View File

@@ -0,0 +1,262 @@
{
"nodes": [
{
"id": "HeatChillTestStation",
"name": "加热冷却测试工作站",
"children": [
"pump_add",
"flask_sample",
"flask_buffer1",
"flask_buffer2",
"reactor",
"stirrer",
"heatchill_1",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "pump_add",
"children": [],
"parent": "HeatChillTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "stirrer",
"children": [],
"parent": "HeatChillTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "heatchill_1",
"name": "加热冷却器",
"children": [],
"parent": "HeatChillTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 800,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 200.0,
"min_temp": -80.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer1",
"name": "缓冲液瓶1",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer2",
"name": "缓冲液瓶2",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_sample",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_sample": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer1",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer1": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer2",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer2": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
},
{
"source": "heatchill_1",
"target": "reactor",
"type": "logical",
"port": {
"heatchill_1": "heating_element",
"reactor": "vessel"
}
},
{
"source": "heatchill_1",
"target": "flask_sample",
"type": "logical",
"port": {
"heatchill_1": "heating_element",
"flask_sample": "vessel"
}
}
]
}

View File

@@ -0,0 +1,412 @@
{
"nodes": [
{
"id": "RunColumnTestStation",
"name": "柱层析测试工作站",
"children": [
"transfer_pump_1",
"column_1",
"flask_sample",
"flask_hexane",
"flask_ethyl_acetate",
"flask_methanol",
"collection_flask_1",
"collection_flask_2",
"collection_flask_3",
"waste_flask",
"reactor"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["RunColumnProtocol", "TransferProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "RunColumnTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "column_1",
"name": "柱层析设备",
"children": [],
"parent": "RunColumnTestStation",
"type": "device",
"class": "virtual_column",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_flow_rate": 5.0,
"column_length": 30.0,
"column_diameter": 2.5
},
"data": {
"status": "Idle",
"column_state": "Ready",
"current_flow_rate": 0.0,
"max_flow_rate": 5.0,
"column_length": 30.0,
"column_diameter": 2.5,
"processed_volume": 0.0,
"progress": 0.0,
"current_status": "Ready"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "crude_mixture",
"volume": 200.0,
"concentration": 70.0
}
]
}
},
{
"id": "flask_hexane",
"name": "正己烷洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "hexane",
"volume": 1500.0,
"concentration": 99.8
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethyl_acetate",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "methanol",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "collection_flask_1",
"name": "收集瓶1",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 750,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_flask_2",
"name": "收集瓶2",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_flask_3",
"name": "收集瓶3",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 1050,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_flask",
"name": "废液瓶",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 1200,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "reaction_mixture",
"volume": 300.0,
"concentration": 85.0
}
]
}
}
],
"links": [
{
"source": "transfer_pump_1",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_1": "1",
"flask_sample": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_hexane",
"type": "physical",
"port": {
"transfer_pump_1": "2",
"flask_hexane": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_ethyl_acetate",
"type": "physical",
"port": {
"transfer_pump_1": "3",
"flask_ethyl_acetate": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_methanol",
"type": "physical",
"port": {
"transfer_pump_1": "4",
"flask_methanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "column_1",
"type": "physical",
"port": {
"transfer_pump_1": "5",
"column_1": "inlet"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask_1",
"type": "physical",
"port": {
"transfer_pump_1": "6",
"collection_flask_1": "top"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask_2",
"type": "physical",
"port": {
"transfer_pump_1": "7",
"collection_flask_2": "top"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask_3",
"type": "physical",
"port": {
"transfer_pump_1": "8",
"collection_flask_3": "top"
}
},
{
"source": "transfer_pump_1",
"target": "waste_flask",
"type": "physical",
"port": {
"transfer_pump_1": "9",
"waste_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_1": "10",
"reactor": "top"
}
},
{
"source": "column_1",
"target": "collection_flask_1",
"type": "physical",
"port": {
"column_1": "outlet",
"collection_flask_1": "top"
}
},
{
"source": "column_1",
"target": "collection_flask_2",
"type": "physical",
"port": {
"column_1": "outlet",
"collection_flask_2": "top"
}
},
{
"source": "column_1",
"target": "collection_flask_3",
"type": "physical",
"port": {
"column_1": "outlet",
"collection_flask_3": "top"
}
}
]
}

View File

@@ -0,0 +1,250 @@
{
"nodes": [
{
"id": "StirTestStation",
"name": "搅拌测试工作站",
"children": [
"pump_add",
"flask_sample",
"flask_buffer1",
"flask_buffer2",
"reactor",
"stirrer",
"flask_waste",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "添加泵",
"children": [],
"parent": "StirTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "搅拌器",
"children": [],
"parent": "StirTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer1",
"name": "缓冲液瓶1",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer2",
"name": "缓冲液瓶2",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_waste",
"name": "废液瓶",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 3000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_sample",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_sample": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer1",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer1": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer2",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer2": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_waste",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_waste": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
}
]
}

View File

@@ -0,0 +1,249 @@
{
"nodes": [
{
"id": "TransferTestStation",
"name": "液体转移测试工作站",
"children": [
"transfer_pump",
"flask_source1",
"flask_source2",
"flask_target1",
"flask_target2",
"reactor",
"flask_waste",
"flask_rinsing"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["TransferProtocol"]
},
"data": {}
},
{
"id": "transfer_pump",
"name": "转移泵",
"children": [],
"parent": "TransferTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 5.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_source1",
"name": "源容器1",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_source2",
"name": "源容器2",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_target1",
"name": "目标容器1",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_target2",
"name": "目标容器2",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_waste",
"name": "废液瓶",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_rinsing",
"name": "冲洗液瓶",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "transfer_pump",
"target": "flask_source1",
"type": "physical",
"port": {
"transfer_pump": "inlet",
"flask_source1": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_source2",
"type": "physical",
"port": {
"transfer_pump": "inlet",
"flask_source2": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_target1",
"type": "physical",
"port": {
"transfer_pump": "outlet",
"flask_target1": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_target2",
"type": "physical",
"port": {
"transfer_pump": "outlet",
"flask_target2": "top"
}
},
{
"source": "transfer_pump",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump": "outlet",
"reactor": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_waste",
"type": "physical",
"port": {
"transfer_pump": "outlet",
"flask_waste": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_rinsing",
"type": "physical",
"port": {
"transfer_pump": "inlet",
"flask_rinsing": "top"
}
}
]
}

View File

@@ -0,0 +1,494 @@
{
"nodes": [
{
"id": "WashSolidTestStation",
"name": "固体清洗测试工作站",
"children": [
"transfer_pump_1",
"heatchill_1",
"stirrer_1",
"filter_1",
"flask_ethanol",
"flask_water",
"flask_acetone",
"flask_methanol",
"reactor",
"collection_flask",
"waste_flask",
"flask_sample",
"filtrate_flask"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["WashSolidProtocol", "TransferProtocol", "FilterProtocol", "HeatChillProtocol", "StirProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "WashSolidTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "heatchill_1",
"name": "加热冷却器",
"children": [],
"parent": "WashSolidTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 150.0,
"min_temp": -20.0
},
"data": {
"status": "Idle",
"current_temp": 25.0,
"target_temp": 25.0,
"vessel": "",
"purpose": "",
"progress": 0.0,
"current_status": "Ready"
}
},
{
"id": "stirrer_1",
"name": "搅拌器",
"children": [],
"parent": "WashSolidTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 750.1111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "filter_1",
"name": "过滤器",
"children": [],
"parent": "WashSolidTestStation",
"type": "device",
"class": "virtual_filter",
"position": {
"x": 850.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle",
"filter_state": "Ready",
"current_temp": 25.0,
"target_temp": 25.0,
"max_temp": 100.0,
"stir_speed": 0.0,
"max_stir_speed": 1000.0,
"filtered_volume": 0.0,
"progress": 0.0,
"message": ""
}
},
{
"id": "flask_ethanol",
"name": "乙醇清洗剂",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethanol",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_water",
"name": "水清洗剂",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "water",
"volume": 1800.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮清洗剂",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "acetone",
"volume": 800.0,
"concentration": 99.8
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇清洗剂",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "methanol",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "solid_product",
"volume": 50.0,
"concentration": 100.0
}
]
}
},
{
"id": "collection_flask",
"name": "收集瓶",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_flask",
"name": "废液瓶",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 1150,
"y": 300,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "crude_solid",
"volume": 30.0,
"concentration": 80.0
}
]
}
},
{
"id": "filtrate_flask",
"name": "滤液收集瓶",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1500.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "transfer_pump_1",
"target": "flask_ethanol",
"type": "physical",
"port": {
"transfer_pump_1": "1",
"flask_ethanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_water",
"type": "physical",
"port": {
"transfer_pump_1": "2",
"flask_water": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_acetone",
"type": "physical",
"port": {
"transfer_pump_1": "3",
"flask_acetone": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_methanol",
"type": "physical",
"port": {
"transfer_pump_1": "4",
"flask_methanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_1": "5",
"reactor": "top"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask",
"type": "physical",
"port": {
"transfer_pump_1": "6",
"collection_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "waste_flask",
"type": "physical",
"port": {
"transfer_pump_1": "7",
"waste_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_1": "8",
"flask_sample": "top"
}
},
{
"source": "transfer_pump_1",
"target": "filtrate_flask",
"type": "physical",
"port": {
"transfer_pump_1": "9",
"filtrate_flask": "top"
}
},
{
"source": "heatchill_1",
"target": "reactor",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"reactor": "bottom"
}
},
{
"source": "heatchill_1",
"target": "flask_sample",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"flask_sample": "bottom"
}
},
{
"source": "stirrer_1",
"target": "reactor",
"type": "physical",
"port": {
"stirrer_1": "stir_rod",
"reactor": "center"
}
},
{
"source": "stirrer_1",
"target": "flask_sample",
"type": "physical",
"port": {
"stirrer_1": "stir_rod",
"flask_sample": "center"
}
},
{
"source": "filter_1",
"target": "reactor",
"type": "physical",
"port": {
"filter_1": "filter_element",
"reactor": "top"
}
},
{
"source": "filter_1",
"target": "flask_sample",
"type": "physical",
"port": {
"filter_1": "filter_element",
"flask_sample": "top"
}
},
{
"source": "filter_1",
"target": "filtrate_flask",
"type": "physical",
"port": {
"filter_1": "filter_element",
"filtrate_flask": "top"
}
}
]
}

View File

@@ -30,14 +30,17 @@
"children": [],
"parent": "ReactorX",
"type": "container",
"class": null,
"class": "container",
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
"max_volume": 5000.0,
"size_x": 200.0,
"size_y": 200.0,
"size_z": 200.0
},
"data": {
"liquid": [
@@ -71,7 +74,7 @@
"type": "device",
"class": "solenoid_valve.mock",
"position": {
"x": 620.6111111111111,
"x": 780,
"y": 171,
"z": 0
},
@@ -89,7 +92,7 @@
"type": "device",
"class": "vacuum_pump.mock",
"position": {
"x": 620.6111111111111,
"x": 500,
"y": 171,
"z": 0
},
@@ -107,7 +110,7 @@
"type": "device",
"class": "gas_source.mock",
"position": {
"x": 620.6111111111111,
"x": 900,
"y": 171,
"z": 0
},
@@ -119,39 +122,39 @@
],
"links": [
{
"source": "reactor",
"target": "vacuum_valve",
"type": "physical",
"source": "vacuum_valve",
"target": "reactor",
"type": "fluid",
"port": {
"reactor": "top",
"vacuum_valve": "1"
"vacuum_valve": "out"
}
},
{
"source": "reactor",
"target": "gas_valve",
"type": "physical",
"source": "gas_valve",
"target": "reactor",
"type": "fluid",
"port": {
"reactor": "top",
"gas_valve": "1"
"gas_valve": "out"
}
},
{
"source": "vacuum_pump",
"target": "vacuum_valve",
"type": "physical",
"type": "fluid",
"port": {
"vacuum_pump": "out",
"vacuum_valve": "0"
"vacuum_valve": "in"
}
},
{
"source": "gas_source",
"target": "gas_valve",
"type": "physical",
"type": "fluid",
"port": {
"gas_source": "out",
"gas_valve": "0"
"gas_valve": "in"
}
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

10063
test/experiments/prcxi.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
{
"nodes": [
{
"id": "benyao",
"name": "benyao",
"children": [
],
"parent": null,
"type": "device",
"class": "moveit.arm_slider",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"moveit_type": "arm_slider",
"joint_poses": {
"arm": {
"home": [0.0, 0.2, 0.0, 0.0, 0.0],
"pick": [1.2, 0.0, 0.0, 0.0, 0.0]
}
},
"device_config": {
}
},
"data": {
}
}
],
"links": [
]
}

View File

@@ -48,16 +48,20 @@ dependencies:
- ros-humble-ros2-control
- ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher
# web
# web and visualization
- ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning
- ros-humble-tf2
- ros-humble-moveit
- ros-humble-moveit-servo
# simulation
- ros-humble-simulation
- ros-humble-tf-transformations
- transforms3d
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments
# - ros-humble-unilabos-msgs
- pip:
- paho-mqtt
- paho-mqtt
- opentrons_shared_data

View File

@@ -56,8 +56,11 @@ dependencies:
# - ros-humble-moveit-servo
# simulation
- ros-humble-simulation
- ros-humble-tf-transformations
- transforms3d
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments
# - ros-humble-unilabos-msgs
- pip:
- paho-mqtt
- paho-mqtt
- opentrons_shared_data

View File

@@ -50,16 +50,20 @@ dependencies:
- ros-humble-ros2-control
- ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher
# web
# web and visualization
- ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning
- ros-humble-tf2
- ros-humble-moveit
- ros-humble-moveit-servo
# simulation
- ros-humble-simulation
- ros-humble-tf-transformations
- transforms3d
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments
# - ros-humble-unilabos-msgs
- pip:
- paho-mqtt
- paho-mqtt
- opentrons_shared_data

View File

@@ -6,12 +6,12 @@ channels:
dependencies:
# Basics
- python=3.11.11
- compilers
- cmake
- make
- ninja
- sphinx
- sphinx_rtd_theme
# - compilers
# - cmake
# - make
# - ninja
# - sphinx
# - sphinx_rtd_theme
# Data Visualization
- numpy
- scipy
@@ -23,7 +23,7 @@ dependencies:
- pyserial
- pyusb
- pylibftdi
- pymodbus
- pymodbus==3.6.9
- python-can
- pyvisa
- opencv
@@ -48,16 +48,27 @@ dependencies:
- ros-humble-ros2-control
- ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher
# web
# web and visualization
- ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning
- ros-humble-tf2
- ros-humble-moveit
- ros-humble-moveit-servo
# simulation
- ros-humble-simulation # ignored because of NO python3.11 package in WIN64
- ros-humble-tf-transformations
- transforms3d
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments
# ros-humble-unilabos-msgs
# driver
#- crcmod
- pip:
- paho-mqtt
- paho-mqtt
- opentrons_shared_data
# driver
#- ur-rtde # set PYTHONUTF8=1
#- pyautogui
#- pywinauto
#- pywinauto_recorder

View File

@@ -8,6 +8,7 @@ def start_backend(
backend: str,
devices_config: dict = {},
resources_config: list = [],
resources_edge_config: list = [],
graph=None,
controllers_config: dict = {},
bridges=[],
@@ -31,7 +32,7 @@ def start_backend(
backend_thread = threading.Thread(
target=main if not without_host else slave,
args=(devices_config, resources_config, graph, controllers_config, bridges, visual, resources_mesh_config),
args=(devices_config, resources_config, resources_edge_config, graph, controllers_config, bridges, visual, resources_mesh_config),
name="backend_thread",
daemon=True,
)

View File

@@ -1,8 +1,10 @@
import json
import traceback
import uuid
from unilabos.app.model import JobAddReq, JobData
from unilabos.ros.nodes.presets.host_node import HostNode
from unilabos.utils.type_check import serialize_result_info
def get_resources() -> tuple:
@@ -25,12 +27,19 @@ def job_add(req: JobAddReq) -> JobData:
if req.job_id is None:
req.job_id = str(uuid.uuid4())
action_name = req.data["action"]
action_kwargs = req.data["action_kwargs"]
req.data['action'] = action_name
if action_name == "execute_command_from_outer":
action_kwargs = {"command": json.dumps(action_kwargs)}
elif "command" in action_kwargs:
action_kwargs = action_kwargs["command"]
print(f"job_add:{req.device_id} {action_name} {action_kwargs}")
HostNode.get_instance().send_goal(req.device_id, action_name=action_name, action_kwargs=action_kwargs, goal_uuid=req.job_id)
action_type = req.data.get("action_type", "LocalUnknown")
action_args = req.data.get("action_kwargs", None) # 兼容老版本,后续删除
if action_args is None:
action_args = req.data.get("action_args")
else:
if "command" in action_args:
action_args = action_args["command"]
# print(f"job_add:{req.device_id} {action_name} {action_kwargs}")
try:
HostNode.get_instance().send_goal(req.device_id, action_type=action_type, action_name=action_name, action_kwargs=action_args, goal_uuid=req.job_id, server_info=req.server_info)
except Exception as e:
for bridge in HostNode.get_instance().bridges:
traceback.print_exc()
if hasattr(bridge, "publish_job_status"):
bridge.publish_job_status({}, req.job_id, "failed", serialize_result_info(traceback.format_exc(), False, {}))
return JobData(jobId=req.job_id)

View File

@@ -10,6 +10,8 @@ from copy import deepcopy
import yaml
from unilabos.resources.graphio import tree_to_list, modify_to_backend_format
# 首先添加项目根目录到路径
current_dir = os.path.dirname(os.path.abspath(__file__))
unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
@@ -18,7 +20,21 @@ if unilabos_dir not in sys.path:
from unilabos.config.config import load_config, BasicConfig, _update_config_from_env
from unilabos.utils.banner_print import print_status, print_unilab_banner
from unilabos.device_mesh.resource_visalization import ResourceVisualization
def load_config_from_file(config_path):
if config_path is None:
config_path = os.environ.get("UNILABOS.BASICCONFIG.CONFIG_PATH", None)
if config_path:
if not os.path.exists(config_path):
print_status(f"配置文件 {config_path} 不存在", "error")
elif not config_path.endswith(".py"):
print_status(f"配置文件 {config_path} 不是Python文件必须以.py结尾", "error")
else:
load_config(config_path)
else:
print_status(f"启动 UniLab-OS时配置文件参数未正确传入 --config '{config_path}' 尝试本地配置...", "warning")
load_config(config_path)
def parse_args():
@@ -57,6 +73,11 @@ def parse_args():
action="store_true",
help="Slave模式下跳过等待host服务",
)
parser.add_argument(
"--upload_registry",
action="store_true",
help="启动unilab时同时报送注册表信息",
)
parser.add_argument(
"--config",
type=str,
@@ -96,22 +117,12 @@ def main():
# 加载配置文件优先加载config然后从env读取
config_path = args_dict.get("config")
if config_path is None:
config_path = os.environ.get("UNILABOS.BASICCONFIG.CONFIG_PATH", None)
if config_path:
if not os.path.exists(config_path):
print_status(f"配置文件 {config_path} 不存在", "error")
elif not config_path.endswith(".py"):
print_status(f"配置文件 {config_path} 不是Python文件必须以.py结尾", "error")
else:
load_config(config_path)
else:
print_status(f"启动 UniLab-OS时配置文件参数未正确传入 --config '{config_path}' 尝试本地配置...", "warning")
load_config(config_path)
load_config_from_file(config_path)
# 设置BasicConfig参数
BasicConfig.is_host_mode = not args_dict.get("without_host", False)
BasicConfig.slave_no_host = args_dict.get("slave_no_host", False)
BasicConfig.upload_registry = args_dict.get("upload_registry", False)
machine_name = os.popen("hostname").read().strip()
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
BasicConfig.machine_name = machine_name
@@ -135,29 +146,30 @@ def main():
# 注册表
build_registry(args_dict["registry_path"])
resource_edge_info = []
devices_and_resources = None
if args_dict["graph"] is not None:
import unilabos.resources.graphio as graph_res
graph_res.physical_setup_graph = (
read_node_link_json(args_dict["graph"])
if args_dict["graph"].endswith(".json")
else read_graphml(args_dict["graph"])
)
if args_dict["graph"].endswith(".json"):
graph, data = read_node_link_json(args_dict["graph"])
else:
graph, data = read_graphml(args_dict["graph"])
graph_res.physical_setup_graph = graph
resource_edge_info = modify_to_backend_format(data["links"])
devices_and_resources = dict_from_graph(graph_res.physical_setup_graph)
args_dict["resources_config"] = initialize_resources(list(deepcopy(devices_and_resources).values()))
# args_dict["resources_config"] = initialize_resources(list(deepcopy(devices_and_resources).values()))
args_dict["resources_config"] = list(devices_and_resources.values())
args_dict["devices_config"] = dict_to_nested_dict(deepcopy(devices_and_resources), devices_only=False)
# args_dict["resources_config"] = dict_to_tree(devices_and_resources, devices_only=False)
args_dict["graph"] = graph_res.physical_setup_graph
else:
if args_dict["devices"] is None or args_dict["resources"] is None:
print_status("Either graph or devices and resources must be provided.", "error")
sys.exit(1)
args_dict["devices_config"] = json.load(open(args_dict["devices"], encoding="utf-8"))
args_dict["resources_config"] = initialize_resources(
list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
)
# args_dict["resources_config"] = initialize_resources(
# list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
# )
args_dict["resources_config"] = list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
print_status(f"{len(args_dict['resources_config'])} Resources loaded:", "info")
for i in args_dict["resources_config"]:
@@ -184,15 +196,17 @@ def main():
signal.signal(signal.SIGTERM, _exit)
mqtt_client.start()
args_dict["resources_mesh_config"] = {}
args_dict["resources_edge_config"] = resource_edge_info
# web visiualize 2D
if args_dict["visual"] != "disable":
enable_rviz = args_dict["visual"] == "rviz"
if devices_and_resources is not None:
from unilabos.device_mesh.resource_visalization import ResourceVisualization # 此处开启后logger会变更为INFO有需要请调整
resource_visualization = ResourceVisualization(devices_and_resources, args_dict["resources_config"] ,enable_rviz=enable_rviz)
args_dict["resources_mesh_config"] = resource_visualization.resource_model
start_backend(**args_dict)
server_thread = threading.Thread(target=start_server, kwargs=dict(
open_browser=not args_dict["disable_browser"]
open_browser=not args_dict["disable_browser"], port=args_dict["port"],
))
server_thread.start()
asyncio.set_event_loop(asyncio.new_event_loop())
@@ -201,10 +215,10 @@ def main():
time.sleep(1)
else:
start_backend(**args_dict)
start_server(open_browser=not args_dict["disable_browser"])
start_server(open_browser=not args_dict["disable_browser"], port=args_dict["port"],)
else:
start_backend(**args_dict)
start_server(open_browser=not args_dict["disable_browser"])
start_server(open_browser=not args_dict["disable_browser"], port=args_dict["port"],)
if __name__ == "__main__":

View File

@@ -51,8 +51,9 @@ class Resp(BaseModel):
class JobAddReq(BaseModel):
device_id: str = Field(examples=["Gripper"], description="device id")
data: dict = Field(examples=[{"position": 30, "torque": 5, "action": "push_to"}])
job_id: str = Field(examples=["sfsfsfeq"], description="goal uuid")
node_id: str = Field(examples=["sfsfsfeq"], description="node uuid")
job_id: str = Field(examples=["job_id"], description="goal uuid")
node_id: str = Field(examples=["node_id"], description="node uuid")
server_info: dict = Field(examples=[{"send_timestamp": 1717000000.0}], description="server info")
class JobStepFinishReq(BaseModel):

View File

@@ -1,6 +1,7 @@
import json
import time
import traceback
from typing import Optional
import uuid
import paho.mqtt.client as mqtt
@@ -12,7 +13,7 @@ import tempfile
import os
from unilabos.config.config import MQConfig
from unilabos.app.controler import devices, job_add
from unilabos.app.controler import job_add
from unilabos.app.model import JobAddReq
from unilabos.utils import logger
from unilabos.utils.type_check import TypeEncoder
@@ -26,6 +27,7 @@ class MQTTClient:
def __init__(self):
self.mqtt_disable = not MQConfig.lab_id
self.client_id = f"{MQConfig.group_id}@@@{MQConfig.lab_id}{uuid.uuid4()}"
logger.info("[MQTT] Client_id: " + self.client_id)
self.client = mqtt.Client(CallbackAPIVersion.VERSION2, client_id=self.client_id, protocol=mqtt.MQTTv5)
self._setup_callbacks()
@@ -42,29 +44,35 @@ class MQTTClient:
def _on_connect(self, client, userdata, flags, rc, properties=None):
logger.info("[MQTT] Connected with result code " + str(rc))
client.subscribe(f"labs/{MQConfig.lab_id}/job/start/", 0)
isok, data = devices()
if not isok:
logger.error("[MQTT] on_connect ErrorHostNotInit")
return
client.subscribe(f"labs/{MQConfig.lab_id}/pong/", 0)
def _on_message(self, client, userdata, msg) -> None:
logger.info("[MQTT] on_message<<<< " + msg.topic + " " + str(msg.payload))
# logger.info("[MQTT] on_message<<<< " + msg.topic + " " + str(msg.payload))
try:
payload_str = msg.payload.decode("utf-8")
payload_json = json.loads(payload_str)
logger.debug(f"Topic: {msg.topic}")
logger.debug("Payload:", json.dumps(payload_json, indent=2, ensure_ascii=False))
if msg.topic == f"labs/{MQConfig.lab_id}/job/start/":
logger.debug("job_add", type(payload_json), payload_json)
if "data" not in payload_json:
payload_json["data"] = {}
if "action" in payload_json:
payload_json["data"]["action"] = payload_json.pop("action")
if "action_type" in payload_json:
payload_json["data"]["action_type"] = payload_json.pop("action_type")
if "action_args" in payload_json:
payload_json["data"]["action_args"] = payload_json.pop("action_args")
if "action_kwargs" in payload_json:
payload_json["data"]["action_kwargs"] = payload_json.pop("action_kwargs")
job_req = JobAddReq.model_validate(payload_json)
data = job_add(job_req)
return
elif msg.topic == f"labs/{MQConfig.lab_id}/pong/":
# 处理pong响应通知HostNode
from unilabos.ros.nodes.presets.host_node import HostNode
host_instance = HostNode.get_instance(0)
if host_instance:
host_instance.handle_pong_response(payload_json)
return
except json.JSONDecodeError as e:
logger.error(f"[MQTT] JSON 解析错误: {e}")
@@ -155,24 +163,27 @@ class MQTTClient:
# status = device_status.get(device_id, {})
if self.mqtt_disable:
return
status = {"data": device_status.get(device_id, {}), "device_id": device_id}
status = {"data": device_status.get(device_id, {}), "device_id": device_id, "timestamp": time.time()}
address = f"labs/{MQConfig.lab_id}/devices/"
self.client.publish(address, json.dumps(status), qos=2)
logger.critical(f"Device status published: address: {address}, {status}")
logger.debug(f"Device status published: address: {address}, {status}")
def publish_job_status(self, feedback_data: dict, job_id: str, status: str):
def publish_job_status(self, feedback_data: dict, job_id: str, status: str, return_info: Optional[str] = None):
if self.mqtt_disable:
return
jobdata = {"job_id": job_id, "data": feedback_data, "status": status}
if return_info is None:
return_info = "{}"
jobdata = {"job_id": job_id, "data": feedback_data, "status": status, "return_info": return_info}
self.client.publish(f"labs/{MQConfig.lab_id}/job/list/", json.dumps(jobdata), qos=2)
def publish_registry(self, device_id: str, device_info: dict):
def publish_registry(self, device_id: str, device_info: dict, print_debug: bool = True):
if self.mqtt_disable:
return
address = f"labs/{MQConfig.lab_id}/registry/"
registry_data = json.dumps({device_id: device_info}, ensure_ascii=False, cls=TypeEncoder)
self.client.publish(address, registry_data, qos=2)
logger.debug(f"Registry data published: address: {address}, {registry_data}")
if print_debug:
logger.debug(f"Registry data published: address: {address}, {registry_data}")
def publish_actions(self, action_id: str, action_info: dict):
if self.mqtt_disable:
@@ -181,6 +192,28 @@ class MQTTClient:
self.client.publish(address, json.dumps(action_info), qos=2)
logger.debug(f"Action data published: address: {address}, {action_id}, {action_info}")
def send_ping(self, ping_id: str, timestamp: float):
"""发送ping消息到服务端"""
if self.mqtt_disable:
return
address = f"labs/{MQConfig.lab_id}/ping/"
ping_data = {"ping_id": ping_id, "client_timestamp": timestamp, "type": "ping"}
self.client.publish(address, json.dumps(ping_data), qos=2)
def setup_pong_subscription(self):
"""设置pong消息订阅"""
if self.mqtt_disable:
return
pong_topic = f"labs/{MQConfig.lab_id}/pong/"
self.client.subscribe(pong_topic, 0)
logger.debug(f"Subscribed to pong topic: {pong_topic}")
def handle_pong(self, pong_data: dict):
"""处理pong响应这个方法会在收到pong消息时被调用"""
logger.debug(f"Pong received: {pong_data}")
# 这里会被HostNode的ping-pong处理逻辑调用
pass
mqtt_client = MQTTClient()

73
unilabos/app/register.py Normal file
View File

@@ -0,0 +1,73 @@
import argparse
import time
from unilabos.registry.registry import build_registry
from unilabos.app.main import load_config_from_file
from unilabos.utils.log import logger
def register_devices_and_resources(mqtt_client, lab_registry):
"""
注册设备和资源到 MQTT
"""
logger.info("[UniLab Register] 开始注册设备和资源...")
# 注册设备信息
for device_info in lab_registry.obtain_registry_device_info():
mqtt_client.publish_registry(device_info["id"], device_info, False)
logger.debug(f"[UniLab Register] 注册设备: {device_info['id']}")
# 注册资源信息
for resource_info in lab_registry.obtain_registry_resource_info():
mqtt_client.publish_registry(resource_info["id"], resource_info, False)
logger.debug(f"[UniLab Register] 注册资源: {resource_info['id']}")
time.sleep(10)
logger.info("[UniLab Register] 设备和资源注册完成.")
def main():
"""
命令行入口函数
"""
parser = argparse.ArgumentParser(description="注册设备和资源到 MQTT")
parser.add_argument(
"--registry",
type=str,
default=None,
action="append",
help="注册表路径",
)
parser.add_argument(
"--config",
type=str,
default=None,
help="配置文件路径,支持.py格式的Python配置文件",
)
parser.add_argument(
"--complete_registry",
action="store_true",
default=False,
help="是否补全注册表",
)
args = parser.parse_args()
# 构建注册表
build_registry(args.registry, args.complete_registry)
load_config_from_file(args.config)
from unilabos.app.mq import mqtt_client
# 连接mqtt
mqtt_client.start()
from unilabos.registry.registry import lab_registry
# 注册设备和资源
register_devices_and_resources(mqtt_client, lab_registry)
if __name__ == "__main__":
main()

View File

@@ -30,22 +30,44 @@ class HTTPClient:
self.auth = MQConfig.lab_id
info(f"HTTPClient 初始化完成: remote_addr={self.remote_addr}")
def resource_add(self, resources: List[Dict[str, Any]]) -> requests.Response:
def resource_edge_add(self, resources: List[Dict[str, Any]], database_process_later: bool) -> requests.Response:
"""
添加资源
Args:
resources: 要添加的资源列表
database_process_later: 后台处理资源
Returns:
Response: API响应对象
"""
response = requests.post(
f"{self.remote_addr}/lab/resource/",
f"{self.remote_addr}/lab/resource/edge/batch_create/?database_process_later={1 if database_process_later else 0}",
json=resources,
headers={"Authorization": f"lab {self.auth}"},
timeout=5,
timeout=100,
)
if response.status_code != 200 and response.status_code != 201:
logger.error(f"添加物料关系失败: {response.status_code}, {response.text}")
return response
def resource_add(self, resources: List[Dict[str, Any]], database_process_later: bool) -> requests.Response:
"""
添加资源
Args:
resources: 要添加的资源列表
database_process_later: 后台处理资源
Returns:
Response: API响应对象
"""
response = requests.post(
f"{self.remote_addr}/lab/resource/?database_process_later={1 if database_process_later else 0}",
json=resources,
headers={"Authorization": f"lab {self.auth}"},
timeout=100,
)
if response.status_code != 200:
logger.error(f"添加物料失败: {response.text}")
return response
def resource_get(self, id: str, with_children: bool = False) -> Dict[str, Any]:
@@ -60,10 +82,10 @@ class HTTPClient:
Dict: 返回的资源数据
"""
response = requests.get(
f"{self.remote_addr}/lab/resource/",
f"{self.remote_addr}/lab/resource/?edge_format=1",
params={"id": id, "with_children": with_children},
headers={"Authorization": f"lab {self.auth}"},
timeout=5,
timeout=20,
)
return response.json()
@@ -81,7 +103,7 @@ class HTTPClient:
f"{self.remote_addr}/lab/resource/batch_delete/",
params={"id": id},
headers={"Authorization": f"lab {self.auth}"},
timeout=5,
timeout=20,
)
return response
@@ -96,10 +118,10 @@ class HTTPClient:
Response: API响应对象
"""
response = requests.patch(
f"{self.remote_addr}/lab/resource/batch_update/",
f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1",
json=resources,
headers={"Authorization": f"lab {self.auth}"},
timeout=5,
timeout=100,
)
return response

View File

@@ -7,6 +7,7 @@ Web页面模块
import json
import os
import sys
import traceback
from pathlib import Path
from typing import Dict
@@ -16,9 +17,8 @@ from jinja2 import Environment, FileSystemLoader
from unilabos.config.config import BasicConfig
from unilabos.registry.registry import lab_registry
from unilabos.app.mq import mqtt_client
from unilabos.ros.msgs.message_converter import msg_converter_manager
from unilabos.utils.log import error
from unilabos.utils.log import error, debug
from unilabos.utils.type_check import TypeEncoder
from unilabos.app.web.utils.device_utils import get_registry_info
from unilabos.app.web.utils.host_utils import get_host_node_info
@@ -124,6 +124,7 @@ def setup_web_pages(router: APIRouter) -> None:
return html
except Exception as e:
debug(traceback.format_exc())
error(f"生成状态页面时出错: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error generating status page: {str(e)}")

View File

@@ -65,6 +65,8 @@ def get_yaml_from_goal_type(goal_type) -> str:
Returns:
str: 默认Goal参数的YAML格式字符串
"""
if isinstance(goal_type, str):
return "{}"
if not goal_type:
return "{}"

View File

@@ -42,7 +42,7 @@ def get_host_node_info() -> Dict[str, Any]:
host_info["subscribed_topics"] = sorted(list(host_node._subscribed_topics))
# 获取动作客户端信息
for action_id, client in host_node._action_clients.items():
host_info["action_clients"] = {action_id: get_action_info(client, full_name=action_id)}
host_info["action_clients"][action_id] = get_action_info(client, full_name=action_id)
# 获取设备状态
host_info["device_status"] = host_node.device_status

View File

@@ -5,15 +5,55 @@ from .separate_protocol import generate_separate_protocol
from .evaporate_protocol import generate_evaporate_protocol
from .evacuateandrefill_protocol import generate_evacuateandrefill_protocol
from .agv_transfer_protocol import generate_agv_transfer_protocol
from .add_protocol import generate_add_protocol
from .centrifuge_protocol import generate_centrifuge_protocol
from .filter_protocol import generate_filter_protocol
from .heatchill_protocol import (
generate_heat_chill_protocol,
generate_heat_chill_start_protocol,
generate_heat_chill_stop_protocol,
generate_heat_chill_to_temp_protocol # 保留导入,但不注册为协议
)
from .stir_protocol import generate_stir_protocol, generate_start_stir_protocol, generate_stop_stir_protocol
from .transfer_protocol import generate_transfer_protocol
from .clean_vessel_protocol import generate_clean_vessel_protocol
from .dissolve_protocol import generate_dissolve_protocol
from .filter_through_protocol import generate_filter_through_protocol
from .run_column_protocol import generate_run_column_protocol
from .wash_solid_protocol import generate_wash_solid_protocol
from .adjustph_protocol import generate_adjust_ph_protocol
from .reset_handling_protocol import generate_reset_handling_protocol
from .dry_protocol import generate_dry_protocol
from .recrystallize_protocol import generate_recrystallize_protocol
from .hydrogenate_protocol import generate_hydrogenate_protocol
# Define a dictionary of protocol generators.
action_protocol_generators = {
PumpTransferProtocol: generate_pump_protocol_with_rinsing,
CleanProtocol: generate_clean_protocol,
SeparateProtocol: generate_separate_protocol,
EvaporateProtocol: generate_evaporate_protocol,
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
AddProtocol: generate_add_protocol,
AGVTransferProtocol: generate_agv_transfer_protocol,
}
# End Protocols
AdjustPHProtocol: generate_adjust_ph_protocol,
CentrifugeProtocol: generate_centrifuge_protocol,
CleanProtocol: generate_clean_protocol,
CleanVesselProtocol: generate_clean_vessel_protocol,
DissolveProtocol: generate_dissolve_protocol,
DryProtocol: generate_dry_protocol,
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
EvaporateProtocol: generate_evaporate_protocol,
FilterProtocol: generate_filter_protocol,
FilterThroughProtocol: generate_filter_through_protocol,
HeatChillProtocol: generate_heat_chill_protocol,
HeatChillStartProtocol: generate_heat_chill_start_protocol,
HeatChillStopProtocol: generate_heat_chill_stop_protocol,
HydrogenateProtocol: generate_hydrogenate_protocol,
PumpTransferProtocol: generate_pump_protocol_with_rinsing,
RecrystallizeProtocol: generate_recrystallize_protocol,
ResetHandlingProtocol: generate_reset_handling_protocol,
RunColumnProtocol: generate_run_column_protocol,
SeparateProtocol: generate_separate_protocol,
StartStirProtocol: generate_start_stir_protocol,
StirProtocol: generate_stir_protocol,
StopStirProtocol: generate_stop_stir_protocol,
TransferProtocol: generate_transfer_protocol,
WashSolidProtocol: generate_wash_solid_protocol,
}

View File

@@ -0,0 +1,702 @@
import networkx as nx
import re
import logging
from typing import List, Dict, Any, Union
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"[ADD] {message}", flush=True)
logger.info(f"[ADD] {message}")
def parse_volume_input(volume_input: Union[str, float]) -> float:
"""
解析体积输入,支持带单位的字符串
Args:
volume_input: 体积输入(如 "2.7 mL", "2.67 mL", "?", 10.0
Returns:
float: 体积(毫升)
"""
if isinstance(volume_input, (int, float)):
debug_print(f"📏 体积输入为数值: {volume_input}")
return float(volume_input)
if not volume_input or not str(volume_input).strip():
debug_print(f"⚠️ 体积输入为空返回0.0mL")
return 0.0
volume_str = str(volume_input).lower().strip()
debug_print(f"🔍 解析体积输入: '{volume_str}'")
# 处理未知体积
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_volume = 10.0 # 默认10mL
debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL 🎯")
return default_volume
# 移除空格并提取数字和单位
volume_clean = re.sub(r'\s+', '', volume_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
if not match:
debug_print(f"❌ 无法解析体积: '{volume_str}'使用默认值10mL")
return 10.0
value = float(match.group(1))
unit = match.group(2) or 'ml' # 默认单位为毫升
# 转换为毫升
if unit in ['l', 'liter']:
volume = value * 1000.0 # L -> mL
debug_print(f"🔄 体积转换: {value}L → {volume}mL")
elif unit in ['μl', 'ul', 'microliter']:
volume = value / 1000.0 # μL -> mL
debug_print(f"🔄 体积转换: {value}μL → {volume}mL")
else: # ml, milliliter 或默认
volume = value # 已经是mL
debug_print(f"✅ 体积已为mL: {volume}mL")
return volume
def parse_mass_input(mass_input: Union[str, float]) -> float:
"""
解析质量输入,支持带单位的字符串
Args:
mass_input: 质量输入(如 "19.3 g", "4.5 g", 2.5
Returns:
float: 质量(克)
"""
if isinstance(mass_input, (int, float)):
debug_print(f"⚖️ 质量输入为数值: {mass_input}g")
return float(mass_input)
if not mass_input or not str(mass_input).strip():
debug_print(f"⚠️ 质量输入为空返回0.0g")
return 0.0
mass_str = str(mass_input).lower().strip()
debug_print(f"🔍 解析质量输入: '{mass_str}'")
# 移除空格并提取数字和单位
mass_clean = re.sub(r'\s+', '', mass_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean)
if not match:
debug_print(f"❌ 无法解析质量: '{mass_str}'返回0.0g")
return 0.0
value = float(match.group(1))
unit = match.group(2) or 'g' # 默认单位为克
# 转换为克
if unit in ['mg', 'milligram']:
mass = value / 1000.0 # mg -> g
debug_print(f"🔄 质量转换: {value}mg → {mass}g")
elif unit in ['kg', 'kilogram']:
mass = value * 1000.0 # kg -> g
debug_print(f"🔄 质量转换: {value}kg → {mass}g")
else: # g, gram 或默认
mass = value # 已经是g
debug_print(f"✅ 质量已为g: {mass}g")
return mass
def parse_time_input(time_input: Union[str, float]) -> float:
"""
解析时间输入,支持带单位的字符串
Args:
time_input: 时间输入(如 "1 h", "20 min", "30 s", 60.0
Returns:
float: 时间(秒)
"""
if isinstance(time_input, (int, float)):
debug_print(f"⏱️ 时间输入为数值: {time_input}")
return float(time_input)
if not time_input or not str(time_input).strip():
debug_print(f"⚠️ 时间输入为空返回0秒")
return 0.0
time_str = str(time_input).lower().strip()
debug_print(f"🔍 解析时间输入: '{time_str}'")
# 处理未知时间
if time_str in ['?', 'unknown', 'tbd']:
default_time = 60.0 # 默认1分钟
debug_print(f"❓ 检测到未知时间,使用默认值: {default_time}s (1分钟) ⏰")
return default_time
# 移除空格并提取数字和单位
time_clean = re.sub(r'\s+', '', time_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean)
if not match:
debug_print(f"❌ 无法解析时间: '{time_str}'返回0s")
return 0.0
value = float(match.group(1))
unit = match.group(2) or 's' # 默认单位为秒
# 转换为秒
if unit in ['min', 'minute']:
time_sec = value * 60.0 # min -> s
debug_print(f"🔄 时间转换: {value}分钟 → {time_sec}")
elif unit in ['h', 'hr', 'hour']:
time_sec = value * 3600.0 # h -> s
debug_print(f"🔄 时间转换: {value}小时 → {time_sec}")
elif unit in ['d', 'day']:
time_sec = value * 86400.0 # d -> s
debug_print(f"🔄 时间转换: {value}天 → {time_sec}")
else: # s, sec, second 或默认
time_sec = value # 已经是s
debug_print(f"✅ 时间已为秒: {time_sec}")
return time_sec
def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
"""增强版试剂容器查找,支持固体和液体"""
debug_print(f"🔍 开始查找试剂 '{reagent}' 的容器...")
# 🔧 方法1直接搜索 data.reagent_name 和 config.reagent
debug_print(f"📋 方法1: 搜索reagent字段...")
for node in G.nodes():
node_data = G.nodes[node].get('data', {})
node_type = G.nodes[node].get('type', '')
config_data = G.nodes[node].get('config', {})
# 只搜索容器类型的节点
if node_type == 'container':
reagent_name = node_data.get('reagent_name', '').lower()
config_reagent = config_data.get('reagent', '').lower()
# 精确匹配
if reagent_name == reagent.lower() or config_reagent == reagent.lower():
debug_print(f"✅ 通过reagent字段精确匹配到容器: {node} 🎯")
return node
# 模糊匹配
if (reagent.lower() in reagent_name and reagent_name) or \
(reagent.lower() in config_reagent and config_reagent):
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node} 🔍")
return node
# 🔧 方法2常见的容器命名规则
debug_print(f"📋 方法2: 使用命名规则查找...")
reagent_clean = reagent.lower().replace(' ', '_').replace('-', '_')
possible_names = [
reagent_clean,
f"flask_{reagent_clean}",
f"bottle_{reagent_clean}",
f"vessel_{reagent_clean}",
f"{reagent_clean}_flask",
f"{reagent_clean}_bottle",
f"reagent_{reagent_clean}",
f"reagent_bottle_{reagent_clean}",
f"solid_reagent_bottle_{reagent_clean}",
f"reagent_bottle_1", # 通用试剂瓶
f"reagent_bottle_2",
f"reagent_bottle_3"
]
debug_print(f"🔍 尝试的容器名称: {possible_names[:5]}... (共{len(possible_names)}个)")
for name in possible_names:
if name in G.nodes():
node_type = G.nodes[name].get('type', '')
if node_type == 'container':
debug_print(f"✅ 通过命名规则找到容器: {name} 📝")
return name
# 🔧 方法3节点名称模糊匹配
debug_print(f"📋 方法3: 节点名称模糊匹配...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if node_data.get('type') == 'container':
# 检查节点名称是否包含试剂名称
if reagent_clean in node_id.lower():
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id} 🔍")
return node_id
# 检查液体类型匹配
vessel_data = node_data.get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type.lower() == reagent.lower():
debug_print(f"✅ 通过液体类型匹配到容器: {node_id} 💧")
return node_id
# 🔧 方法4使用第一个试剂瓶作为备选
debug_print(f"📋 方法4: 查找备选试剂瓶...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if (node_data.get('type') == 'container' and
('reagent' in node_id.lower() or 'bottle' in node_id.lower())):
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id} 🔄")
return node_id
debug_print(f"❌ 所有方法都失败了,无法找到容器!")
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器")
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的搅拌器"""
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
stirrer_nodes = []
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'stirrer' in node_class:
stirrer_nodes.append(node)
debug_print(f"📋 发现搅拌器: {node}")
debug_print(f"📊 共找到 {len(stirrer_nodes)} 个搅拌器")
# 查找连接到容器的搅拌器
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗")
return stirrer
# 返回第一个搅拌器
if stirrer_nodes:
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
return stirrer_nodes[0]
debug_print(f"❌ 未找到任何搅拌器")
return ""
def find_solid_dispenser(G: nx.DiGraph) -> str:
"""查找固体加样器"""
debug_print(f"🔍 查找固体加样器...")
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'solid_dispenser' in node_class or 'dispenser' in node_class:
debug_print(f"✅ 找到固体加样器: {node} 🥄")
return node
debug_print(f"❌ 未找到固体加样器")
return ""
# 🆕 创建进度日志动作
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志"""
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
print(f"[ACTION] {full_message}", flush=True)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message
}
}
def generate_add_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str,
# 🔧 修复:所有参数都用 Union 类型,支持字符串和数值
volume: Union[str, float] = 0.0,
mass: Union[str, float] = 0.0,
amount: str = "",
time: Union[str, float] = 0.0,
stir: bool = False,
stir_speed: float = 300.0,
viscous: bool = False,
purpose: str = "添加试剂",
# XDL扩展参数
mol: str = "",
event: str = "",
rate_spec: str = "",
equiv: str = "",
ratio: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
生成添加试剂协议 - 修复版
支持所有XDL参数和单位
- volume: "2.7 mL", "2.67 mL", "?" 或数值
- mass: "19.3 g", "4.5 g" 或数值
- time: "1 h", "20 min" 或数值(秒)
- mol: "0.28 mol", "16.2 mmol", "25.2 mmol"
- rate_spec: "portionwise", "dropwise"
- event: "A", "B"
- equiv: "1.1"
- ratio: "?", "1:1"
"""
debug_print("=" * 60)
debug_print("🚀 开始生成添加试剂协议")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'")
debug_print(f" 🧪 reagent: '{reagent}'")
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
debug_print(f" ⏱️ time: {time} (类型: {type(time)})")
debug_print(f" 🧬 mol: '{mol}'")
debug_print(f" 🎯 event: '{event}'")
debug_print(f" ⚡ rate_spec: '{rate_spec}'")
debug_print(f" 🌪️ stir: {stir}")
debug_print(f" 🔄 stir_speed: {stir_speed} rpm")
debug_print("=" * 60)
action_sequence = []
# === 参数验证 ===
debug_print("🔍 步骤1: 参数验证...")
action_sequence.append(create_action_log(f"开始添加试剂 '{reagent}' 到容器 '{vessel}'", "🎬"))
if not vessel:
debug_print("❌ vessel 参数不能为空")
raise ValueError("vessel 参数不能为空")
if not reagent:
debug_print("❌ reagent 参数不能为空")
raise ValueError("reagent 参数不能为空")
if vessel not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中")
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
debug_print("✅ 基本参数验证通过")
# === 🔧 关键修复:参数解析 ===
debug_print("🔍 步骤2: 参数解析...")
action_sequence.append(create_action_log("正在解析添加参数...", "🔍"))
# 解析各种参数为数值
final_volume = parse_volume_input(volume)
final_mass = parse_mass_input(mass)
final_time = parse_time_input(time)
debug_print(f"📊 解析结果:")
debug_print(f" 📏 体积: {final_volume}mL")
debug_print(f" ⚖️ 质量: {final_mass}g")
debug_print(f" ⏱️ 时间: {final_time}s")
debug_print(f" 🧬 摩尔: '{mol}'")
debug_print(f" 🎯 事件: '{event}'")
debug_print(f" ⚡ 速率: '{rate_spec}'")
# === 判断添加类型 ===
debug_print("🔍 步骤3: 判断添加类型...")
# 🔧 修复:现在使用解析后的数值进行比较
is_solid = (final_mass > 0 or (mol and mol.strip() != ""))
is_liquid = (final_volume > 0)
if not is_solid and not is_liquid:
# 默认为液体10mL
is_liquid = True
final_volume = 10.0
debug_print("⚠️ 未指定体积或质量默认为10mL液体")
add_type = "固体" if is_solid else "液体"
add_emoji = "🧂" if is_solid else "💧"
debug_print(f"📋 添加类型: {add_type} {add_emoji}")
action_sequence.append(create_action_log(f"确定添加类型: {add_type} {add_emoji}", "📋"))
# === 执行添加流程 ===
debug_print("🔍 步骤4: 执行添加流程...")
try:
if is_solid:
# === 固体添加路径 ===
debug_print(f"🧂 使用固体添加路径")
action_sequence.append(create_action_log("开始固体试剂添加流程", "🧂"))
solid_dispenser = find_solid_dispenser(G)
if solid_dispenser:
action_sequence.append(create_action_log(f"找到固体加样器: {solid_dispenser}", "🥄"))
# 启动搅拌
if stir:
debug_print("🌪️ 准备启动搅拌...")
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed} rpm)", "🔄"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"准备添加固体 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 3}
})
# 固体加样
add_kwargs = {
"vessel": vessel,
"reagent": reagent,
"purpose": purpose,
"event": event,
"rate_spec": rate_spec
}
if final_mass > 0:
add_kwargs["mass"] = str(final_mass)
action_sequence.append(create_action_log(f"准备添加固体: {final_mass}g", "⚖️"))
if mol and mol.strip():
add_kwargs["mol"] = mol
action_sequence.append(create_action_log(f"按摩尔数添加: {mol}", "🧬"))
if equiv and equiv.strip():
add_kwargs["equiv"] = equiv
action_sequence.append(create_action_log(f"当量: {equiv}", "🔢"))
action_sequence.append(create_action_log("开始固体加样操作", "🥄"))
action_sequence.append({
"device_id": solid_dispenser,
"action_name": "add_solid",
"action_kwargs": add_kwargs
})
action_sequence.append(create_action_log("固体加样完成", ""))
# 添加后等待
if final_time > 0:
wait_minutes = final_time / 60
action_sequence.append(create_action_log(f"等待反应进行 ({wait_minutes:.1f}分钟)", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": final_time}
})
debug_print(f"✅ 固体添加完成")
else:
debug_print("❌ 未找到固体加样器,跳过固体添加")
action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", ""))
else:
# === 液体添加路径 ===
debug_print(f"💧 使用液体添加路径")
action_sequence.append(create_action_log("开始液体试剂添加流程", "💧"))
# 查找试剂容器
action_sequence.append(create_action_log("正在查找试剂容器...", "🔍"))
reagent_vessel = find_reagent_vessel(G, reagent)
action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪"))
# 启动搅拌
if stir:
debug_print("🌪️ 准备启动搅拌...")
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed} rpm)", "🔄"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"准备添加液体 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
# 计算流速
if final_time > 0:
flowrate = final_volume / final_time * 60 # mL/min
transfer_flowrate = flowrate
debug_print(f"⚡ 根据时间计算流速: {flowrate:.2f} mL/min")
else:
if rate_spec == "dropwise":
flowrate = 0.5 # 滴加,很慢
transfer_flowrate = 0.2
debug_print(f"💧 滴加模式,流速: {flowrate} mL/min")
elif viscous:
flowrate = 1.0 # 粘性液体
transfer_flowrate = 0.3
debug_print(f"🍯 粘性液体,流速: {flowrate} mL/min")
else:
flowrate = 2.5 # 正常流速
transfer_flowrate = 0.5
debug_print(f"⚡ 正常流速: {flowrate} mL/min")
action_sequence.append(create_action_log(f"设置流速: {flowrate:.2f} mL/min", ""))
action_sequence.append(create_action_log(f"开始转移 {final_volume}mL 液体", "🚰"))
# 调用pump protocol
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=reagent_vessel,
to_vessel=vessel,
volume=final_volume,
amount=amount,
time=final_time,
viscous=viscous,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=flowrate,
transfer_flowrate=transfer_flowrate,
rate_spec=rate_spec,
event=event,
through="",
**kwargs
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 液体转移完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"液体转移完成 ({len(pump_actions)} 个操作)", ""))
except Exception as e:
debug_print(f"❌ 试剂添加失败: {str(e)}")
action_sequence.append(create_action_log(f"试剂添加失败: {str(e)}", ""))
# 添加错误日志
action_sequence.append({
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"试剂 '{reagent}' 添加失败: {str(e)}"
}
})
# === 最终结果 ===
debug_print("=" * 60)
debug_print(f"🎉 添加试剂协议生成完成")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"📋 处理总结:")
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" {add_emoji} 添加类型: {add_type}")
debug_print(f" 🥼 目标容器: {vessel}")
if is_liquid:
debug_print(f" 📏 体积: {final_volume}mL")
if is_solid:
debug_print(f" ⚖️ 质量: {final_mass}g")
debug_print(f" 🧬 摩尔: {mol}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"试剂添加协议完成: {reagent}{vessel}"
if is_liquid:
summary_msg += f" ({final_volume}mL)"
if is_solid:
summary_msg += f" ({final_mass}g)"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
# === 便捷函数 ===
def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
time: Union[str, float] = 0.0, rate_spec: str = "") -> List[Dict[str, Any]]:
"""添加指定体积的液体试剂"""
debug_print(f"💧 快速添加液体: {reagent} ({volume}) → {vessel}")
return generate_add_protocol(
G, vessel, reagent,
volume=volume,
time=time,
rate_spec=rate_spec
)
def add_solid_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
event: str = "") -> List[Dict[str, Any]]:
"""添加指定质量的固体试剂"""
debug_print(f"🧂 快速添加固体: {reagent} ({mass}) → {vessel}")
return generate_add_protocol(
G, vessel, reagent,
mass=mass,
event=event
)
def add_solid_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
event: str = "") -> List[Dict[str, Any]]:
"""按摩尔数添加固体试剂"""
debug_print(f"🧬 按摩尔数添加固体: {reagent} ({mol}) → {vessel}")
return generate_add_protocol(
G, vessel, reagent,
mol=mol,
event=event
)
def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
time: Union[str, float] = "20 min", event: str = "") -> List[Dict[str, Any]]:
"""滴加液体试剂"""
debug_print(f"💧 滴加液体: {reagent} ({volume}) → {vessel} (用时: {time})")
return generate_add_protocol(
G, vessel, reagent,
volume=volume,
time=time,
rate_spec="dropwise",
event=event
)
def add_portionwise_solid(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
time: Union[str, float] = "1 h", event: str = "") -> List[Dict[str, Any]]:
"""分批添加固体试剂"""
debug_print(f"🧂 分批添加固体: {reagent} ({mass}) → {vessel} (用时: {time})")
return generate_add_protocol(
G, vessel, reagent,
mass=mass,
time=time,
rate_spec="portionwise",
event=event
)
# 测试函数
def test_add_protocol():
"""测试添加协议的各种参数解析"""
print("=== ADD PROTOCOL 增强版测试 ===")
# 测试体积解析
debug_print("🧪 测试体积解析...")
volumes = ["2.7 mL", "2.67 mL", "?", 10.0, "1 L", "500 μL"]
for vol in volumes:
result = parse_volume_input(vol)
print(f"📏 体积解析: {vol}{result}mL")
# 测试质量解析
debug_print("⚖️ 测试质量解析...")
masses = ["19.3 g", "4.5 g", 2.5, "500 mg", "1 kg"]
for mass in masses:
result = parse_mass_input(mass)
print(f"⚖️ 质量解析: {mass}{result}g")
# 测试时间解析
debug_print("⏱️ 测试时间解析...")
times = ["1 h", "20 min", "30 s", 60.0, "?"]
for time in times:
result = parse_time_input(time)
print(f"⏱️ 时间解析: {time}{result}s")
print("✅ 测试完成")
if __name__ == "__main__":
test_add_protocol()

View File

@@ -0,0 +1,588 @@
import networkx as nx
import logging
from typing import List, Dict, Any
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"[ADJUST_PH] {message}", flush=True)
logger.info(f"[ADJUST_PH] {message}")
# 🆕 创建进度日志动作
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志"""
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
print(f"[ACTION] {full_message}", flush=True)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message
}
}
def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
"""
查找酸碱试剂容器,支持多种匹配模式
Args:
G: 网络图
reagent: 试剂名称(如 "hydrochloric acid", "sodium hydroxide"
Returns:
str: 试剂容器ID
"""
debug_print(f"🔍 正在查找试剂 '{reagent}' 的容器...")
# 常见酸碱试剂的别名映射
reagent_aliases = {
"hydrochloric acid": ["HCl", "hydrochloric_acid", "hcl", "muriatic_acid"],
"sodium hydroxide": ["NaOH", "sodium_hydroxide", "naoh", "caustic_soda"],
"sulfuric acid": ["H2SO4", "sulfuric_acid", "h2so4"],
"nitric acid": ["HNO3", "nitric_acid", "hno3"],
"acetic acid": ["CH3COOH", "acetic_acid", "glacial_acetic_acid"],
"ammonia": ["NH3", "ammonium_hydroxide", "nh3"],
"potassium hydroxide": ["KOH", "potassium_hydroxide", "koh"]
}
# 构建搜索名称列表
search_names = [reagent.lower()]
debug_print(f"📋 基础搜索名称: {reagent.lower()}")
# 添加别名
for base_name, aliases in reagent_aliases.items():
if reagent.lower() in base_name.lower() or base_name.lower() in reagent.lower():
search_names.extend([alias.lower() for alias in aliases])
debug_print(f"🔗 添加别名: {aliases}")
break
debug_print(f"📝 完整搜索列表: {search_names}")
# 构建可能的容器名称
possible_names = []
for name in search_names:
name_clean = name.replace(" ", "_").replace("-", "_")
possible_names.extend([
f"flask_{name_clean}",
f"bottle_{name_clean}",
f"reagent_{name_clean}",
f"acid_{name_clean}" if "acid" in name else f"base_{name_clean}",
f"{name_clean}_bottle",
f"{name_clean}_flask",
name_clean
])
debug_print(f"🎯 可能的容器名称 (前5个): {possible_names[:5]}... (共{len(possible_names)}个)")
# 第一步:通过容器名称匹配
debug_print(f"📋 方法1: 精确名称匹配...")
for vessel_name in possible_names:
if vessel_name in G.nodes():
debug_print(f"✅ 通过名称匹配找到容器: {vessel_name} 🎯")
return vessel_name
# 第二步:通过模糊匹配
debug_print(f"📋 方法2: 模糊名称匹配...")
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
node_name = G.nodes[node_id].get('name', '').lower()
# 检查是否包含任何搜索名称
for search_name in search_names:
if search_name in node_id.lower() or search_name in node_name:
debug_print(f"✅ 通过模糊匹配找到容器: {node_id} 🔍")
return node_id
# 第三步:通过液体类型匹配
debug_print(f"📋 方法3: 液体类型匹配...")
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
reagent_name = vessel_data.get('reagent_name', '').lower()
for search_name in search_names:
if search_name in liquid_type or search_name in reagent_name:
debug_print(f"✅ 通过液体类型匹配找到容器: {node_id} 💧")
return node_id
# 列出可用容器帮助调试
debug_print(f"📊 列出可用容器帮助调试...")
available_containers = []
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
liquid_types = [liquid.get('liquid_type', '') or liquid.get('name', '')
for liquid in liquids if isinstance(liquid, dict)]
available_containers.append({
'id': node_id,
'name': G.nodes[node_id].get('name', ''),
'liquids': liquid_types,
'reagent_name': vessel_data.get('reagent_name', '')
})
debug_print(f"📋 可用容器列表:")
for container in available_containers:
debug_print(f" - 🧪 {container['id']}: {container['name']}")
debug_print(f" 💧 液体: {container['liquids']}")
debug_print(f" 🏷️ 试剂: {container['reagent_name']}")
debug_print(f"❌ 所有匹配方法都失败了")
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names[:10]}...")
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找与容器相连的搅拌器"""
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
stirrer_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
debug_print(f"📊 发现 {len(stirrer_nodes)} 个搅拌器: {stirrer_nodes}")
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗")
return stirrer
if stirrer_nodes:
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
return stirrer_nodes[0]
debug_print(f"❌ 未找到任何搅拌器")
return None
def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float:
"""
估算需要的试剂体积来调节pH
Args:
target_ph_value: 目标pH值
reagent: 试剂名称
vessel_volume: 容器体积 (mL)
Returns:
float: 估算的试剂体积 (mL)
"""
debug_print(f"🧮 计算试剂体积...")
debug_print(f" 📍 目标pH: {target_ph_value}")
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" 📏 容器体积: {vessel_volume}mL")
# 简化的pH调节体积估算实际应用中需要更精确的计算
if "acid" in reagent.lower() or "hcl" in reagent.lower():
debug_print(f"🍋 检测到酸性试剂")
# 酸性试剂pH越低需要的体积越大
if target_ph_value < 3:
volume = vessel_volume * 0.05 # 5%
debug_print(f" 💪 强酸性 (pH<3): 使用 5% 体积")
elif target_ph_value < 5:
volume = vessel_volume * 0.02 # 2%
debug_print(f" 🔸 中酸性 (pH<5): 使用 2% 体积")
else:
volume = vessel_volume * 0.01 # 1%
debug_print(f" 🔹 弱酸性 (pH≥5): 使用 1% 体积")
elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower():
debug_print(f"🧂 检测到碱性试剂")
# 碱性试剂pH越高需要的体积越大
if target_ph_value > 11:
volume = vessel_volume * 0.05 # 5%
debug_print(f" 💪 强碱性 (pH>11): 使用 5% 体积")
elif target_ph_value > 9:
volume = vessel_volume * 0.02 # 2%
debug_print(f" 🔸 中碱性 (pH>9): 使用 2% 体积")
else:
volume = vessel_volume * 0.01 # 1%
debug_print(f" 🔹 弱碱性 (pH≤9): 使用 1% 体积")
else:
# 未知试剂,使用默认值
volume = vessel_volume * 0.01
debug_print(f"❓ 未知试剂类型,使用默认 1% 体积")
debug_print(f"📊 计算结果: {volume:.2f}mL")
return volume
def generate_adjust_ph_protocol(
G: nx.DiGraph,
vessel: str,
ph_value: float,
reagent: str,
**kwargs
) -> List[Dict[str, Any]]:
"""
生成调节pH的协议序列
Args:
G: 有向图,节点为容器和设备
vessel: 目标容器需要调节pH的容器
ph_value: 目标pH值从XDL传入
reagent: 酸碱试剂名称从XDL传入
**kwargs: 其他可选参数,使用默认值
Returns:
List[Dict[str, Any]]: 动作序列
"""
debug_print("=" * 60)
debug_print("🧪 开始生成pH调节协议")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'")
debug_print(f" 📊 ph_value: {ph_value}")
debug_print(f" 🧪 reagent: '{reagent}'")
debug_print(f" 📦 kwargs: {kwargs}")
debug_print("=" * 60)
action_sequence = []
# 从kwargs中获取可选参数如果没有则使用默认值
volume = kwargs.get('volume', 0.0) # 自动估算体积
stir = kwargs.get('stir', True) # 默认搅拌
stir_speed = kwargs.get('stir_speed', 300.0) # 默认搅拌速度
stir_time = kwargs.get('stir_time', 60.0) # 默认搅拌时间
settling_time = kwargs.get('settling_time', 30.0) # 默认平衡时间
debug_print(f"🔧 处理后的参数:")
debug_print(f" 📏 volume: {volume}mL (0.0表示自动估算)")
debug_print(f" 🌪️ stir: {stir}")
debug_print(f" 🔄 stir_speed: {stir_speed}rpm")
debug_print(f" ⏱️ stir_time: {stir_time}s")
debug_print(f" ⏳ settling_time: {settling_time}s")
# 开始处理
action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪"))
action_sequence.append(create_action_log(f"目标容器: {vessel}", "🥼"))
action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️"))
# 1. 验证目标容器存在
debug_print(f"🔍 步骤1: 验证目标容器...")
if vessel not in G.nodes():
debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中")
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
debug_print(f"✅ 目标容器验证通过")
action_sequence.append(create_action_log("目标容器验证通过", ""))
# 2. 查找酸碱试剂容器
debug_print(f"🔍 步骤2: 查找试剂容器...")
action_sequence.append(create_action_log("正在查找试剂容器...", "🔍"))
try:
reagent_vessel = find_acid_base_vessel(G, reagent)
debug_print(f"✅ 找到试剂容器: {reagent_vessel}")
action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪"))
except ValueError as e:
debug_print(f"❌ 无法找到试剂容器: {str(e)}")
action_sequence.append(create_action_log(f"试剂容器查找失败: {str(e)}", ""))
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
# 3. 体积估算
debug_print(f"🔍 步骤3: 体积处理...")
if volume <= 0:
action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮"))
# 获取目标容器的体积信息
vessel_data = G.nodes[vessel].get('data', {})
vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL
debug_print(f"📏 容器最大体积: {vessel_volume}mL")
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume)
volume = estimated_volume
debug_print(f"✅ 自动估算试剂体积: {volume:.2f} mL")
action_sequence.append(create_action_log(f"估算试剂体积: {volume:.2f}mL", "📊"))
else:
debug_print(f"📏 使用指定体积: {volume}mL")
action_sequence.append(create_action_log(f"使用指定体积: {volume}mL", "📏"))
# 4. 验证路径存在
debug_print(f"🔍 步骤4: 路径验证...")
action_sequence.append(create_action_log("验证转移路径...", "🛤️"))
try:
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
debug_print(f"✅ 找到路径: {''.join(path)}")
action_sequence.append(create_action_log(f"找到转移路径: {''.join(path)}", "🛤️"))
except nx.NetworkXNoPath:
debug_print(f"❌ 无法找到转移路径")
action_sequence.append(create_action_log("转移路径不存在", ""))
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
# 5. 搅拌器设置
debug_print(f"🔍 步骤5: 搅拌器设置...")
stirrer_id = None
if stir:
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
try:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
debug_print(f"✅ 找到搅拌器 {stirrer_id},启动搅拌")
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🔄"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"pH调节: 启动搅拌,准备添加 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
else:
debug_print(f"⚠️ 未找到搅拌器,继续执行")
action_sequence.append(create_action_log("未找到搅拌器,跳过搅拌", "⚠️"))
except Exception as e:
debug_print(f"❌ 搅拌器配置出错: {str(e)}")
action_sequence.append(create_action_log(f"搅拌器配置失败: {str(e)}", ""))
else:
debug_print(f"📋 跳过搅拌设置")
action_sequence.append(create_action_log("跳过搅拌设置", "⏭️"))
# 6. 试剂添加
debug_print(f"🔍 步骤6: 试剂添加...")
action_sequence.append(create_action_log(f"开始添加试剂 {volume:.2f}mL", "🚰"))
# 计算添加时间pH调节需要缓慢添加
addition_time = max(30.0, volume * 2.0) # 至少30秒每mL需要2秒
debug_print(f"⏱️ 计算添加时间: {addition_time}s (缓慢注入)")
action_sequence.append(create_action_log(f"设置添加时间: {addition_time:.0f}s (缓慢注入)", "⏱️"))
try:
action_sequence.append(create_action_log("调用泵协议进行试剂转移", "🔄"))
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=reagent_vessel,
to_vessel=vessel,
volume=volume,
amount="",
time=addition_time,
viscous=False,
rinsing_solvent="", # pH调节不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=0.5, # 缓慢注入
transfer_flowrate=0.3
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 泵协议生成完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"试剂转移完成 ({len(pump_actions)} 个操作)", ""))
except Exception as e:
debug_print(f"❌ 生成泵协议时出错: {str(e)}")
action_sequence.append(create_action_log(f"泵协议生成失败: {str(e)}", ""))
raise ValueError(f"生成泵协议时出错: {str(e)}")
# 7. 混合搅拌
if stir and stirrer_id:
debug_print(f"🔍 步骤7: 混合搅拌...")
action_sequence.append(create_action_log(f"开始混合搅拌 {stir_time:.0f}s", "🌀"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"stir_time": stir_time,
"stir_speed": stir_speed,
"settling_time": settling_time,
"purpose": f"pH调节: 混合试剂目标pH={ph_value}"
}
})
debug_print(f"✅ 混合搅拌设置完成")
else:
debug_print(f"⏭️ 跳过混合搅拌")
action_sequence.append(create_action_log("跳过混合搅拌", "⏭️"))
# 8. 等待平衡
debug_print(f"🔍 步骤8: 反应平衡...")
action_sequence.append(create_action_log(f"等待pH平衡 {settling_time:.0f}s", "⚖️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": settling_time,
"description": f"等待pH平衡到目标值 {ph_value}"
}
})
# 9. 完成总结
total_time = addition_time + stir_time + settling_time
debug_print("=" * 60)
debug_print(f"🎉 pH调节协议生成完成")
debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f}分钟)")
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" 📏 体积: {volume:.2f}mL")
debug_print(f" 📊 目标pH: {ph_value}")
debug_print(f" 🥼 目标容器: {vessel}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"pH调节协议完成: {vessel} → pH {ph_value} (使用 {volume:.2f}mL {reagent})"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
def generate_adjust_ph_protocol_stepwise(
G: nx.DiGraph,
vessel: str,
ph_value: float,
reagent: str,
max_volume: float = 10.0,
steps: int = 3
) -> List[Dict[str, Any]]:
"""
分步调节pH的协议更安全避免过度调节
Args:
G: 网络图
vessel: 目标容器
ph_value: 目标pH值
reagent: 酸碱试剂
max_volume: 最大试剂体积
steps: 分步数量
Returns:
List[Dict[str, Any]]: 动作序列
"""
debug_print("=" * 60)
debug_print(f"🔄 开始分步pH调节")
debug_print(f"📋 分步参数:")
debug_print(f" 🥼 vessel: {vessel}")
debug_print(f" 📊 ph_value: {ph_value}")
debug_print(f" 🧪 reagent: {reagent}")
debug_print(f" 📏 max_volume: {max_volume}mL")
debug_print(f" 🔢 steps: {steps}")
debug_print("=" * 60)
action_sequence = []
# 每步添加的体积
step_volume = max_volume / steps
debug_print(f"📊 每步体积: {step_volume:.2f}mL")
action_sequence.append(create_action_log(f"开始分步pH调节 ({steps}步)", "🔄"))
action_sequence.append(create_action_log(f"每步添加: {step_volume:.2f}mL", "📏"))
for i in range(steps):
debug_print(f"🔄 执行第 {i+1}/{steps} 步,添加 {step_volume:.2f}mL")
action_sequence.append(create_action_log(f"{i+1}/{steps} 步开始", "🚀"))
# 生成单步协议
step_actions = generate_adjust_ph_protocol(
G=G,
vessel=vessel,
ph_value=ph_value,
reagent=reagent,
volume=step_volume,
stir=True,
stir_speed=300.0,
stir_time=30.0,
settling_time=20.0
)
action_sequence.extend(step_actions)
debug_print(f"✅ 第 {i+1}/{steps} 步完成,添加了 {len(step_actions)} 个动作")
action_sequence.append(create_action_log(f"{i+1}/{steps} 步完成", ""))
# 步骤间等待
if i < steps - 1:
debug_print(f"⏳ 步骤间等待30s")
action_sequence.append(create_action_log("步骤间等待...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 30,
"description": f"pH调节第{i+1}步完成,等待下一步"
}
})
debug_print(f"🎉 分步pH调节完成{len(action_sequence)} 个动作")
action_sequence.append(create_action_log("分步pH调节全部完成", "🎉"))
return action_sequence
# 便捷函数常用pH调节
def generate_acidify_protocol(
G: nx.DiGraph,
vessel: str,
target_ph: float = 2.0,
acid: str = "hydrochloric acid"
) -> List[Dict[str, Any]]:
"""酸化协议"""
debug_print(f"🍋 生成酸化协议: {vessel} → pH {target_ph} (使用 {acid})")
return generate_adjust_ph_protocol(
G, vessel, target_ph, acid
)
def generate_basify_protocol(
G: nx.DiGraph,
vessel: str,
target_ph: float = 12.0,
base: str = "sodium hydroxide"
) -> List[Dict[str, Any]]:
"""碱化协议"""
debug_print(f"🧂 生成碱化协议: {vessel} → pH {target_ph} (使用 {base})")
return generate_adjust_ph_protocol(
G, vessel, target_ph, base
)
def generate_neutralize_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str = "sodium hydroxide"
) -> List[Dict[str, Any]]:
"""中和协议pH=7"""
debug_print(f"⚖️ 生成中和协议: {vessel} → pH 7.0 (使用 {reagent})")
return generate_adjust_ph_protocol(
G, vessel, 7.0, reagent
)
# 测试函数
def test_adjust_ph_protocol():
"""测试pH调节协议"""
debug_print("=== ADJUST PH PROTOCOL 增强版测试 ===")
# 测试体积计算
debug_print("🧮 测试体积计算...")
test_cases = [
(2.0, "hydrochloric acid", 100.0),
(4.0, "hydrochloric acid", 100.0),
(12.0, "sodium hydroxide", 100.0),
(10.0, "sodium hydroxide", 100.0),
(7.0, "unknown reagent", 100.0)
]
for ph, reagent, volume in test_cases:
result = calculate_reagent_volume(ph, reagent, volume)
debug_print(f"📊 {reagent} → pH {ph}: {result:.2f}mL")
debug_print("✅ 测试完成")
if __name__ == "__main__":
test_adjust_ph_protocol()

View File

@@ -0,0 +1,285 @@
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,
vessel: str,
speed: float,
time: float,
temp: float = 25.0
) -> List[Dict[str, Any]]:
"""
生成离心操作的协议序列,复用 pump_protocol 的成熟算法
离心流程:
1. 液体转移:将待离心溶液从源容器转移到离心机容器
2. 离心操作:执行离心分离
3. 上清液转移:将离心后的上清液转移回原容器或新容器
4. 沉淀处理:处理离心沉淀(可选)
Args:
G: 有向图,节点为设备和容器,边为流体管道
vessel: 包含待离心溶液的容器名称
speed: 离心速度 (rpm)
time: 离心时间 (秒)
temp: 离心温度 (°C)默认25°C
Returns:
List[Dict[str, Any]]: 离心操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
centrifuge_actions = generate_centrifuge_protocol(G, "reaction_mixture", 5000, 600, 4.0)
"""
action_sequence = []
print(f"CENTRIFUGE: 开始生成离心协议")
print(f" - 源容器: {vessel}")
print(f" - 离心速度: {speed} rpm")
print(f" - 离心时间: {time}s ({time/60:.1f}分钟)")
print(f" - 离心温度: {temp}°C")
# 验证源容器存在
if vessel not in G.nodes():
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
# 获取源容器中的液体体积
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": 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_low_speed_centrifuge_protocol(
G: nx.DiGraph,
vessel: str,
time: float = 300.0 # 5分钟
) -> List[Dict[str, Any]]:
"""低速离心:细胞分离或大颗粒沉淀"""
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)

View File

@@ -0,0 +1,439 @@
from typing import List, Dict, Any
import networkx as nx
from .pump_protocol import generate_pump_protocol
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""
查找溶剂容器,支持多种匹配模式:
1. 容器名称匹配(如 flask_water, reagent_bottle_1-DMF
2. 容器内液体类型匹配(如 liquid_type: "DMF", "ethanol"
"""
print(f"CLEAN_VESSEL: 正在查找溶剂 '{solvent}' 的容器...")
# 第一步:通过容器名称匹配
possible_names = [
f"flask_{solvent}", # flask_water, flask_ethanol
f"bottle_{solvent}", # bottle_water, bottle_ethanol
f"vessel_{solvent}", # vessel_water, vessel_ethanol
f"{solvent}_flask", # water_flask, ethanol_flask
f"{solvent}_bottle", # water_bottle, ethanol_bottle
f"{solvent}", # 直接用溶剂名
f"solvent_{solvent}", # solvent_water, solvent_ethanol
f"reagent_bottle_{solvent}", # reagent_bottle_DMF
]
# 尝试名称匹配
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"CLEAN_VESSEL: 通过名称匹配找到容器: {vessel_name}")
return vessel_name
# 第二步:通过模糊名称匹配(名称中包含溶剂名)
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
# 检查节点ID或名称中是否包含溶剂名
node_name = G.nodes[node_id].get('name', '').lower()
if (solvent.lower() in node_id.lower() or
solvent.lower() in node_name):
print(f"CLEAN_VESSEL: 通过模糊名称匹配找到容器: {node_id} (名称: {node_name})")
return node_id
# 第三步:通过液体类型匹配
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
# 支持两种格式的液体类型字段
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
reagent_name = vessel_data.get('reagent_name', '')
config_reagent = G.nodes[node_id].get('config', {}).get('reagent', '')
# 检查多个可能的字段
if (liquid_type.lower() == solvent.lower() or
reagent_name.lower() == solvent.lower() or
config_reagent.lower() == solvent.lower()):
print(f"CLEAN_VESSEL: 通过液体类型匹配找到容器: {node_id}")
print(f" - liquid_type: {liquid_type}")
print(f" - reagent_name: {reagent_name}")
print(f" - config.reagent: {config_reagent}")
return node_id
# 第四步:列出所有可用的容器信息帮助调试
available_containers = []
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
config_data = G.nodes[node_id].get('config', {})
liquids = vessel_data.get('liquid', [])
container_info = {
'id': node_id,
'name': G.nodes[node_id].get('name', ''),
'liquid_types': [],
'reagent_name': vessel_data.get('reagent_name', ''),
'config_reagent': config_data.get('reagent', '')
}
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type:
container_info['liquid_types'].append(liquid_type)
available_containers.append(container_info)
print(f"CLEAN_VESSEL: 可用容器列表:")
for container in available_containers:
print(f" - {container['id']}: {container['name']}")
print(f" 液体类型: {container['liquid_types']}")
print(f" 试剂名称: {container['reagent_name']}")
print(f" 配置试剂: {container['config_reagent']}")
raise ValueError(f"未找到溶剂 '{solvent}' 的容器。尝试了名称匹配: {possible_names}")
def find_solvent_vessel_by_any_match(G: nx.DiGraph, solvent: str) -> str:
"""
增强版溶剂容器查找,支持各种匹配方式的别名函数
"""
return find_solvent_vessel(G, solvent)
def find_waste_vessel(G: nx.DiGraph) -> str:
"""
查找废液容器
"""
possible_waste_names = [
"waste_workup",
"flask_waste",
"bottle_waste",
"waste",
"waste_vessel",
"waste_container"
]
for waste_name in possible_waste_names:
if waste_name in G.nodes():
return waste_name
raise ValueError(f"未找到废液容器。尝试了以下名称: {possible_waste_names}")
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
"""
查找与指定容器相连的加热冷却设备
"""
# 查找所有加热冷却设备节点
heatchill_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_heatchill']
# 检查哪个加热设备与目标容器相连(机械连接)
for heatchill in heatchill_nodes:
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
return heatchill
# 如果没有直接连接,返回第一个可用的加热设备
if heatchill_nodes:
return heatchill_nodes[0]
return None # 没有加热设备也可以工作,只是不能加热
def generate_clean_vessel_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float,
temp: float,
repeats: int = 1
) -> List[Dict[str, Any]]:
"""
生成容器清洗操作的协议序列,复用 pump_protocol 的成熟算法
清洗流程:
1. 查找溶剂容器和废液容器
2. 如果需要加热,启动加热设备
3. 重复以下操作 repeats 次:
a. 使用 pump_protocol 将溶剂从溶剂容器转移到目标容器
b. (可选) 等待清洗作用时间
c. 使用 pump_protocol 将清洗液从目标容器转移到废液容器
4. 如果加热了,停止加热
Args:
G: 有向图,节点为设备和容器,边为流体管道
vessel: 要清洗的容器名称
solvent: 用于清洗的溶剂名称
volume: 每次清洗使用的溶剂体积
temp: 清洗时的温度
repeats: 清洗操作的重复次数,默认为 1
Returns:
List[Dict[str, Any]]: 容器清洗操作的动作序列
Raises:
ValueError: 当找不到必要的容器或设备时抛出异常
Examples:
clean_protocol = generate_clean_vessel_protocol(G, "main_reactor", "water", 100.0, 60.0, 2)
"""
action_sequence = []
print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
print(f" - 目标容器: {vessel}")
print(f" - 清洗溶剂: {solvent}")
print(f" - 清洗体积: {volume} mL")
print(f" - 清洗温度: {temp}°C")
print(f" - 重复次数: {repeats}")
# 验证目标容器存在
if vessel not in G.nodes():
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
# 查找溶剂容器
try:
solvent_vessel = find_solvent_vessel(G, solvent)
print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂容器: {str(e)}")
# 查找废液容器
try:
waste_vessel = find_waste_vessel(G)
print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}")
except ValueError as e:
raise ValueError(f"无法找到废液容器: {str(e)}")
# 查找加热设备(可选)
heatchill_id = find_connected_heatchill(G, vessel)
if heatchill_id:
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
else:
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
# 第一步:如果需要加热且有加热设备,启动加热
if temp > 25.0 and heatchill_id:
print(f"CLEAN_VESSEL: 启动加热至 {temp}°C")
heatchill_start_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": f"cleaning with {solvent}"
}
}
action_sequence.append(heatchill_start_action)
# 等待温度稳定
wait_action = {
"action_name": "wait",
"action_kwargs": {"time": 30} # 等待30秒让温度稳定
}
action_sequence.append(wait_action)
# 第二步:重复清洗操作
for repeat in range(repeats):
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
# 2a. 使用 pump_protocol 将溶剂转移到目标容器
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel}")
try:
# 调用成熟的 pump_protocol 算法
add_solvent_actions = generate_pump_protocol(
G=G,
from_vessel=solvent_vessel,
to_vessel=vessel,
volume=volume,
flowrate=2.5, # 适中的流速,避免飞溅
transfer_flowrate=2.5
)
action_sequence.extend(add_solvent_actions)
except Exception as e:
raise ValueError(f"无法将溶剂转移到容器: {str(e)}")
# 2b. 等待清洗作用时间(让溶剂充分清洗容器)
cleaning_wait_time = 60 if temp > 50.0 else 30 # 高温下等待更久
print(f"CLEAN_VESSEL: 等待清洗作用 {cleaning_wait_time}")
wait_action = {
"action_name": "wait",
"action_kwargs": {"time": cleaning_wait_time}
}
action_sequence.append(wait_action)
# 2c. 使用 pump_protocol 将清洗液转移到废液容器
print(f"CLEAN_VESSEL: 将清洗液从 {vessel} 转移到废液容器")
try:
# 调用成熟的 pump_protocol 算法
remove_waste_actions = generate_pump_protocol(
G=G,
from_vessel=vessel,
to_vessel=waste_vessel,
volume=volume,
flowrate=2.5, # 适中的流速
transfer_flowrate=2.5
)
action_sequence.extend(remove_waste_actions)
except Exception as e:
raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}")
# 2d. 清洗循环间的短暂等待
if repeat < repeats - 1: # 不是最后一次清洗
print(f"CLEAN_VESSEL: 清洗循环间等待")
wait_action = {
"action_name": "wait",
"action_kwargs": {"time": 10}
}
action_sequence.append(wait_action)
# 第三步:如果加热了,停止加热
if temp > 25.0 and heatchill_id:
print(f"CLEAN_VESSEL: 停止加热")
heatchill_stop_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
}
action_sequence.append(heatchill_stop_action)
print(f"CLEAN_VESSEL: 生成了 {len(action_sequence)} 个动作")
print(f"CLEAN_VESSEL: 清洗协议生成完成")
return action_sequence
# 便捷函数:常用清洗方案
def generate_quick_clean_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str = "water",
volume: float = 100.0
) -> List[Dict[str, Any]]:
"""快速清洗:室温,单次清洗"""
return generate_clean_vessel_protocol(G, vessel, solvent, volume, 25.0, 1)
def generate_thorough_clean_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str = "water",
volume: float = 150.0,
temp: float = 60.0
) -> List[Dict[str, Any]]:
"""深度清洗:加热,多次清洗"""
return generate_clean_vessel_protocol(G, vessel, solvent, volume, temp, 3)
def generate_organic_clean_protocol(
G: nx.DiGraph,
vessel: str,
volume: float = 100.0
) -> List[Dict[str, Any]]:
"""有机清洗:先用有机溶剂,再用水清洗"""
action_sequence = []
# 第一步:有机溶剂清洗
try:
organic_actions = generate_clean_vessel_protocol(
G, vessel, "acetone", volume, 25.0, 2
)
action_sequence.extend(organic_actions)
except ValueError:
# 如果没有丙酮,尝试乙醇
try:
organic_actions = generate_clean_vessel_protocol(
G, vessel, "ethanol", volume, 25.0, 2
)
action_sequence.extend(organic_actions)
except ValueError:
print("警告:未找到有机溶剂,跳过有机清洗步骤")
# 第二步:水清洗
water_actions = generate_clean_vessel_protocol(
G, vessel, "water", volume, 25.0, 2
)
action_sequence.extend(water_actions)
return action_sequence
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):
# 支持两种格式:新格式 (name, volume) 和旧格式 (liquid_type, liquid_volume)
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
total_volume += volume
return total_volume
def get_vessel_liquid_types(G: nx.DiGraph, vessel: str) -> List[str]:
"""获取容器中所有液体的类型"""
if vessel not in G.nodes():
return []
vessel_data = G.nodes[vessel].get('data', {})
liquids = vessel_data.get('liquid', [])
liquid_types = []
for liquid in liquids:
if isinstance(liquid, dict):
# 支持两种格式的液体类型字段
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type:
liquid_types.append(liquid_type)
return liquid_types
def find_vessel_by_content(G: nx.DiGraph, content: str) -> List[str]:
"""
根据内容物查找所有匹配的容器
返回匹配容器的ID列表
"""
matching_vessels = []
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
# 检查容器名称匹配
node_name = G.nodes[node_id].get('name', '').lower()
if content.lower() in node_id.lower() or content.lower() in node_name:
matching_vessels.append(node_id)
continue
# 检查液体类型匹配
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
config_data = G.nodes[node_id].get('config', {})
# 检查 reagent_name 和 config.reagent
reagent_name = vessel_data.get('reagent_name', '').lower()
config_reagent = config_data.get('reagent', '').lower()
if (content.lower() == reagent_name or
content.lower() == config_reagent):
matching_vessels.append(node_id)
continue
# 检查液体列表
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type.lower() == content.lower():
matching_vessels.append(node_id)
break
return matching_vessels

View File

@@ -0,0 +1,889 @@
import networkx as nx
import re
import logging
from typing import List, Dict, Any, Union
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"[DISSOLVE] {message}", flush=True)
logger.info(f"[DISSOLVE] {message}")
# 🆕 创建进度日志动作
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志"""
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
print(f"[ACTION] {full_message}", flush=True)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message
}
}
def parse_volume_input(volume_input: Union[str, float]) -> float:
"""
解析体积输入,支持带单位的字符串
Args:
volume_input: 体积输入(如 "10 mL", "?", 10.0
Returns:
float: 体积(毫升)
"""
if isinstance(volume_input, (int, float)):
debug_print(f"📏 体积输入为数值: {volume_input}")
return float(volume_input)
if not volume_input or not str(volume_input).strip():
debug_print(f"⚠️ 体积输入为空返回0.0mL")
return 0.0
volume_str = str(volume_input).lower().strip()
debug_print(f"🔍 解析体积输入: '{volume_str}'")
# 处理未知体积
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_volume = 50.0 # 默认50mL
debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL 🎯")
return default_volume
# 移除空格并提取数字和单位
volume_clean = re.sub(r'\s+', '', volume_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
if not match:
debug_print(f"❌ 无法解析体积: '{volume_str}'使用默认值50mL")
return 50.0
value = float(match.group(1))
unit = match.group(2) or 'ml' # 默认单位为毫升
# 转换为毫升
if unit in ['l', 'liter']:
volume = value * 1000.0 # L -> mL
debug_print(f"🔄 体积转换: {value}L → {volume}mL")
elif unit in ['μl', 'ul', 'microliter']:
volume = value / 1000.0 # μL -> mL
debug_print(f"🔄 体积转换: {value}μL → {volume}mL")
else: # ml, milliliter 或默认
volume = value # 已经是mL
debug_print(f"✅ 体积已为mL: {volume}mL")
return volume
def parse_mass_input(mass_input: Union[str, float]) -> float:
"""
解析质量输入,支持带单位的字符串
Args:
mass_input: 质量输入(如 "2.9 g", "?", 2.5
Returns:
float: 质量(克)
"""
if isinstance(mass_input, (int, float)):
debug_print(f"⚖️ 质量输入为数值: {mass_input}g")
return float(mass_input)
if not mass_input or not str(mass_input).strip():
debug_print(f"⚠️ 质量输入为空返回0.0g")
return 0.0
mass_str = str(mass_input).lower().strip()
debug_print(f"🔍 解析质量输入: '{mass_str}'")
# 处理未知质量
if mass_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_mass = 1.0 # 默认1g
debug_print(f"❓ 检测到未知质量,使用默认值: {default_mass}g 🎯")
return default_mass
# 移除空格并提取数字和单位
mass_clean = re.sub(r'\s+', '', mass_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean)
if not match:
debug_print(f"❌ 无法解析质量: '{mass_str}'返回0.0g")
return 0.0
value = float(match.group(1))
unit = match.group(2) or 'g' # 默认单位为克
# 转换为克
if unit in ['mg', 'milligram']:
mass = value / 1000.0 # mg -> g
debug_print(f"🔄 质量转换: {value}mg → {mass}g")
elif unit in ['kg', 'kilogram']:
mass = value * 1000.0 # kg -> g
debug_print(f"🔄 质量转换: {value}kg → {mass}g")
else: # g, gram 或默认
mass = value # 已经是g
debug_print(f"✅ 质量已为g: {mass}g")
return mass
def parse_time_input(time_input: Union[str, float]) -> float:
"""
解析时间输入,支持带单位的字符串
Args:
time_input: 时间输入(如 "30 min", "1 h", "?", 60.0
Returns:
float: 时间(秒)
"""
if isinstance(time_input, (int, float)):
debug_print(f"⏱️ 时间输入为数值: {time_input}")
return float(time_input)
if not time_input or not str(time_input).strip():
debug_print(f"⚠️ 时间输入为空返回0秒")
return 0.0
time_str = str(time_input).lower().strip()
debug_print(f"🔍 解析时间输入: '{time_str}'")
# 处理未知时间
if time_str in ['?', 'unknown', 'tbd']:
default_time = 600.0 # 默认10分钟
debug_print(f"❓ 检测到未知时间,使用默认值: {default_time}s (10分钟) ⏰")
return default_time
# 移除空格并提取数字和单位
time_clean = re.sub(r'\s+', '', time_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean)
if not match:
debug_print(f"❌ 无法解析时间: '{time_str}'返回0s")
return 0.0
value = float(match.group(1))
unit = match.group(2) or 's' # 默认单位为秒
# 转换为秒
if unit in ['min', 'minute']:
time_sec = value * 60.0 # min -> s
debug_print(f"🔄 时间转换: {value}分钟 → {time_sec}")
elif unit in ['h', 'hr', 'hour']:
time_sec = value * 3600.0 # h -> s
debug_print(f"🔄 时间转换: {value}小时 → {time_sec}")
elif unit in ['d', 'day']:
time_sec = value * 86400.0 # d -> s
debug_print(f"🔄 时间转换: {value}天 → {time_sec}")
else: # s, sec, second 或默认
time_sec = value # 已经是s
debug_print(f"✅ 时间已为秒: {time_sec}")
return time_sec
def parse_temperature_input(temp_input: Union[str, float]) -> float:
"""
解析温度输入,支持带单位的字符串
Args:
temp_input: 温度输入(如 "60 °C", "room temperature", "?", 25.0
Returns:
float: 温度(摄氏度)
"""
if isinstance(temp_input, (int, float)):
debug_print(f"🌡️ 温度输入为数值: {temp_input}°C")
return float(temp_input)
if not temp_input or not str(temp_input).strip():
debug_print(f"⚠️ 温度输入为空使用默认室温25°C")
return 25.0 # 默认室温
temp_str = str(temp_input).lower().strip()
debug_print(f"🔍 解析温度输入: '{temp_str}'")
# 处理特殊温度描述
temp_aliases = {
'room temperature': 25.0,
'rt': 25.0,
'ambient': 25.0,
'cold': 4.0,
'ice': 0.0,
'reflux': 80.0, # 默认回流温度
'?': 25.0,
'unknown': 25.0
}
if temp_str in temp_aliases:
result = temp_aliases[temp_str]
debug_print(f"🏷️ 温度别名解析: '{temp_str}'{result}°C")
return result
# 移除空格并提取数字和单位
temp_clean = re.sub(r'\s+', '', temp_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(°c|c|celsius|°f|f|fahrenheit|k|kelvin)?', temp_clean)
if not match:
debug_print(f"❌ 无法解析温度: '{temp_str}'使用默认值25°C")
return 25.0
value = float(match.group(1))
unit = match.group(2) or 'c' # 默认单位为摄氏度
# 转换为摄氏度
if unit in ['°f', 'f', 'fahrenheit']:
temp_c = (value - 32) * 5/9 # F -> C
debug_print(f"🔄 温度转换: {value}°F → {temp_c:.1f}°C")
elif unit in ['k', 'kelvin']:
temp_c = value - 273.15 # K -> C
debug_print(f"🔄 温度转换: {value}K → {temp_c:.1f}°C")
else: # °c, c, celsius 或默认
temp_c = value # 已经是C
debug_print(f"✅ 温度已为°C: {temp_c}°C")
return temp_c
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""增强版溶剂容器查找,支持多种匹配模式"""
debug_print(f"🔍 开始查找溶剂 '{solvent}' 的容器...")
# 🔧 方法1直接搜索 data.reagent_name 和 config.reagent
debug_print(f"📋 方法1: 搜索reagent字段...")
for node in G.nodes():
node_data = G.nodes[node].get('data', {})
node_type = G.nodes[node].get('type', '')
config_data = G.nodes[node].get('config', {})
# 只搜索容器类型的节点
if node_type == 'container':
reagent_name = node_data.get('reagent_name', '').lower()
config_reagent = config_data.get('reagent', '').lower()
# 精确匹配
if reagent_name == solvent.lower() or config_reagent == solvent.lower():
debug_print(f"✅ 通过reagent字段精确匹配到容器: {node} 🎯")
return node
# 模糊匹配
if (solvent.lower() in reagent_name and reagent_name) or \
(solvent.lower() in config_reagent and config_reagent):
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node} 🔍")
return node
# 🔧 方法2常见的容器命名规则
debug_print(f"📋 方法2: 使用命名规则查找...")
solvent_clean = solvent.lower().replace(' ', '_').replace('-', '_')
possible_names = [
solvent_clean,
f"flask_{solvent_clean}",
f"bottle_{solvent_clean}",
f"vessel_{solvent_clean}",
f"{solvent_clean}_flask",
f"{solvent_clean}_bottle",
f"solvent_{solvent_clean}",
f"reagent_{solvent_clean}",
f"reagent_bottle_{solvent_clean}",
f"reagent_bottle_1", # 通用试剂瓶
f"reagent_bottle_2",
f"reagent_bottle_3"
]
debug_print(f"🔍 尝试的容器名称: {possible_names[:5]}... (共{len(possible_names)}个)")
for name in possible_names:
if name in G.nodes():
node_type = G.nodes[name].get('type', '')
if node_type == 'container':
debug_print(f"✅ 通过命名规则找到容器: {name} 📝")
return name
# 🔧 方法3节点名称模糊匹配
debug_print(f"📋 方法3: 节点名称模糊匹配...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if node_data.get('type') == 'container':
# 检查节点名称是否包含溶剂名称
if solvent_clean in node_id.lower():
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id} 🔍")
return node_id
# 检查液体类型匹配
vessel_data = node_data.get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type.lower() == solvent.lower():
debug_print(f"✅ 通过液体类型匹配到容器: {node_id} 💧")
return node_id
# 🔧 方法4使用第一个试剂瓶作为备选
debug_print(f"📋 方法4: 查找备选试剂瓶...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if (node_data.get('type') == 'container' and
('reagent' in node_id.lower() or 'bottle' in node_id.lower() or 'flask' in node_id.lower())):
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id} 🔄")
return node_id
debug_print(f"❌ 所有方法都失败了,无法找到容器!")
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的加热搅拌器"""
debug_print(f"🔍 查找连接到容器 '{vessel}' 的加热搅拌器...")
heatchill_nodes = []
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'heatchill' in node_class:
heatchill_nodes.append(node)
debug_print(f"📋 发现加热搅拌器: {node}")
debug_print(f"📊 共找到 {len(heatchill_nodes)} 个加热搅拌器")
# 查找连接到容器的加热器
for heatchill in heatchill_nodes:
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
debug_print(f"✅ 找到连接的加热搅拌器: {heatchill} 🔗")
return heatchill
# 返回第一个加热器
if heatchill_nodes:
debug_print(f"⚠️ 未找到直接连接的加热搅拌器,使用第一个: {heatchill_nodes[0]} 🔄")
return heatchill_nodes[0]
debug_print(f"❌ 未找到任何加热搅拌器")
return ""
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的搅拌器"""
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
stirrer_nodes = []
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'stirrer' in node_class:
stirrer_nodes.append(node)
debug_print(f"📋 发现搅拌器: {node}")
debug_print(f"📊 共找到 {len(stirrer_nodes)} 个搅拌器")
# 查找连接到容器的搅拌器
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗")
return stirrer
# 返回第一个搅拌器
if stirrer_nodes:
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
return stirrer_nodes[0]
debug_print(f"❌ 未找到任何搅拌器")
return ""
def find_solid_dispenser(G: nx.DiGraph) -> str:
"""查找固体加样器"""
debug_print(f"🔍 查找固体加样器...")
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'solid_dispenser' in node_class or 'dispenser' in node_class:
debug_print(f"✅ 找到固体加样器: {node} 🥄")
return node
debug_print(f"❌ 未找到固体加样器")
return ""
def generate_dissolve_protocol(
G: nx.DiGraph,
vessel: str,
# 🔧 修复按照checklist.md的DissolveProtocol参数
solvent: str = "",
volume: Union[str, float] = 0.0,
amount: str = "",
temp: Union[str, float] = 25.0,
time: Union[str, float] = 0.0,
stir_speed: float = 300.0,
# 🔧 关键修复:添加缺失的参数,防止"unexpected keyword argument"错误
mass: Union[str, float] = 0.0, # 这个参数在action文件中存在必须包含
mol: str = "", # 这个参数在action文件中存在必须包含
reagent: str = "", # 这个参数在action文件中存在必须包含
event: str = "", # 这个参数在action文件中存在必须包含
**kwargs # 🔧 关键接受所有其他参数防止unexpected keyword错误
) -> List[Dict[str, Any]]:
"""
生成溶解操作的协议序列 - 增强版
🔧 修复要点:
1. 添加action文件中的所有参数mass, mol, reagent, event
2. 使用 **kwargs 接受所有额外参数,防止 unexpected keyword argument 错误
3. 支持固体溶解和液体溶解两种模式
4. 添加详细的emoji日志系统
支持两种溶解模式:
1. 液体溶解:指定 solvent + volume使用pump protocol转移溶剂
2. 固体溶解:指定 mass/mol + reagent使用固体加样器添加固体试剂
支持所有XDL参数和单位
- volume: "10 mL", "?" 或数值
- mass: "2.9 g", "?" 或数值
- temp: "60 °C", "room temperature", "?" 或数值
- time: "30 min", "1 h", "?" 或数值
- mol: "0.12 mol", "16.2 mmol"
"""
debug_print("=" * 60)
debug_print("🧪 开始生成溶解协议")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'")
debug_print(f" 💧 solvent: '{solvent}'")
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
debug_print(f" 🌡️ temp: {temp} (类型: {type(temp)})")
debug_print(f" ⏱️ time: {time} (类型: {type(time)})")
debug_print(f" 🧪 reagent: '{reagent}'")
debug_print(f" 🧬 mol: '{mol}'")
debug_print(f" 🎯 event: '{event}'")
debug_print(f" 📦 kwargs: {kwargs}") # 显示额外参数
debug_print("=" * 60)
action_sequence = []
# === 参数验证 ===
debug_print("🔍 步骤1: 参数验证...")
action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel}", "🎬"))
if not vessel:
debug_print("❌ vessel 参数不能为空")
raise ValueError("vessel 参数不能为空")
if vessel not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中")
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
debug_print("✅ 基本参数验证通过")
action_sequence.append(create_action_log("参数验证通过", ""))
# === 🔧 关键修复:参数解析 ===
debug_print("🔍 步骤2: 参数解析...")
action_sequence.append(create_action_log("正在解析溶解参数...", "🔍"))
# 解析各种参数为数值
final_volume = parse_volume_input(volume)
final_mass = parse_mass_input(mass)
final_temp = parse_temperature_input(temp)
final_time = parse_time_input(time)
debug_print(f"📊 解析结果:")
debug_print(f" 📏 体积: {final_volume}mL")
debug_print(f" ⚖️ 质量: {final_mass}g")
debug_print(f" 🌡️ 温度: {final_temp}°C")
debug_print(f" ⏱️ 时间: {final_time}s")
debug_print(f" 🧪 试剂: '{reagent}'")
debug_print(f" 🧬 摩尔: '{mol}'")
debug_print(f" 🎯 事件: '{event}'")
# === 判断溶解类型 ===
debug_print("🔍 步骤3: 判断溶解类型...")
action_sequence.append(create_action_log("正在判断溶解类型...", "🔍"))
# 判断是固体溶解还是液体溶解
is_solid_dissolve = (final_mass > 0 or (mol and mol.strip() != "") or (reagent and reagent.strip() != ""))
is_liquid_dissolve = (final_volume > 0 and solvent and solvent.strip() != "")
if not is_solid_dissolve and not is_liquid_dissolve:
# 默认为液体溶解50mL
is_liquid_dissolve = True
final_volume = 50.0
if not solvent:
solvent = "water" # 默认溶剂
debug_print("⚠️ 未明确指定溶解参数默认为50mL水溶解")
dissolve_type = "固体溶解" if is_solid_dissolve else "液体溶解"
dissolve_emoji = "🧂" if is_solid_dissolve else "💧"
debug_print(f"📋 溶解类型: {dissolve_type} {dissolve_emoji}")
action_sequence.append(create_action_log(f"确定溶解类型: {dissolve_type} {dissolve_emoji}", "📋"))
# === 查找设备 ===
debug_print("🔍 步骤4: 查找设备...")
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
# 查找加热搅拌器
heatchill_id = find_connected_heatchill(G, vessel)
stirrer_id = find_connected_stirrer(G, vessel)
# 优先使用加热搅拌器,否则使用独立搅拌器
stir_device_id = heatchill_id or stirrer_id
debug_print(f"📊 设备映射:")
debug_print(f" 🔥 加热器: '{heatchill_id}'")
debug_print(f" 🌪️ 搅拌器: '{stirrer_id}'")
debug_print(f" 🎯 使用设备: '{stir_device_id}'")
if heatchill_id:
action_sequence.append(create_action_log(f"找到加热搅拌器: {heatchill_id}", "🔥"))
elif stirrer_id:
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
else:
action_sequence.append(create_action_log("未找到搅拌设备,将跳过搅拌", "⚠️"))
# === 执行溶解流程 ===
debug_print("🔍 步骤5: 执行溶解流程...")
try:
# 步骤5.1: 启动加热搅拌(如果需要)
if stir_device_id and (final_temp > 25.0 or final_time > 0 or stir_speed > 0):
debug_print(f"🔍 5.1: 启动加热搅拌,温度: {final_temp}°C")
action_sequence.append(create_action_log(f"准备加热搅拌 (目标温度: {final_temp}°C)", "🔥"))
if heatchill_id and (final_temp > 25.0 or final_time > 0):
# 使用加热搅拌器
action_sequence.append(create_action_log(f"启动加热搅拌器 {heatchill_id}", "🔥"))
heatchill_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": final_temp,
"purpose": f"溶解准备 - {event}" if event else "溶解准备"
}
}
action_sequence.append(heatchill_action)
# 等待温度稳定
if final_temp > 25.0:
wait_time = min(60, abs(final_temp - 25.0) * 1.5)
action_sequence.append(create_action_log(f"等待温度稳定 ({wait_time:.0f}秒)", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": wait_time}
})
elif stirrer_id:
# 使用独立搅拌器
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🌪️"))
stir_action = {
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"溶解搅拌 - {event}" if event else "溶解搅拌"
}
}
action_sequence.append(stir_action)
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
if is_solid_dissolve:
# === 固体溶解路径 ===
debug_print(f"🔍 5.2: 使用固体溶解路径")
action_sequence.append(create_action_log("开始固体溶解流程", "🧂"))
solid_dispenser = find_solid_dispenser(G)
if solid_dispenser:
action_sequence.append(create_action_log(f"找到固体加样器: {solid_dispenser}", "🥄"))
# 固体加样
add_kwargs = {
"vessel": vessel,
"reagent": reagent or amount or "solid reagent",
"purpose": f"溶解固体试剂 - {event}" if event else "溶解固体试剂",
"event": event
}
if final_mass > 0:
add_kwargs["mass"] = str(final_mass)
action_sequence.append(create_action_log(f"准备添加固体: {final_mass}g", "⚖️"))
if mol and mol.strip():
add_kwargs["mol"] = mol
action_sequence.append(create_action_log(f"按摩尔数添加: {mol}", "🧬"))
action_sequence.append(create_action_log("开始固体加样操作", "🥄"))
action_sequence.append({
"device_id": solid_dispenser,
"action_name": "add_solid",
"action_kwargs": add_kwargs
})
debug_print(f"✅ 固体加样完成")
action_sequence.append(create_action_log("固体加样完成", ""))
else:
debug_print("⚠️ 未找到固体加样器,跳过固体添加")
action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", ""))
elif is_liquid_dissolve:
# === 液体溶解路径 ===
debug_print(f"🔍 5.3: 使用液体溶解路径")
action_sequence.append(create_action_log("开始液体溶解流程", "💧"))
# 查找溶剂容器
action_sequence.append(create_action_log("正在查找溶剂容器...", "🔍"))
try:
solvent_vessel = find_solvent_vessel(G, solvent)
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "🧪"))
except ValueError as e:
debug_print(f"⚠️ {str(e)},跳过溶剂添加")
action_sequence.append(create_action_log(f"溶剂容器查找失败: {str(e)}", ""))
solvent_vessel = None
if solvent_vessel:
# 计算流速 - 溶解时通常用较慢的速度,避免飞溅
flowrate = 1.0 # 较慢的注入速度
transfer_flowrate = 0.5 # 较慢的转移速度
action_sequence.append(create_action_log(f"设置流速: {flowrate}mL/min (缓慢注入)", ""))
action_sequence.append(create_action_log(f"开始转移 {final_volume}mL {solvent}", "🚰"))
# 调用pump protocol
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent_vessel,
to_vessel=vessel,
volume=final_volume,
amount=amount,
time=0.0, # 不在pump level控制时间
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=flowrate,
transfer_flowrate=transfer_flowrate,
rate_spec="",
event=event,
through="",
**kwargs
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 溶剂转移完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", ""))
# 溶剂添加后等待
action_sequence.append(create_action_log("溶剂添加后短暂等待...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
# 步骤5.4: 等待溶解完成
if final_time > 0:
debug_print(f"🔍 5.4: 等待溶解完成 - {final_time}s")
wait_minutes = final_time / 60
action_sequence.append(create_action_log(f"开始溶解等待 ({wait_minutes:.1f}分钟)", ""))
if heatchill_id:
# 使用定时加热搅拌
action_sequence.append(create_action_log(f"使用加热搅拌器进行定时溶解", "🔥"))
dissolve_action = {
"device_id": heatchill_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": final_temp,
"time": final_time,
"stir": True,
"stir_speed": stir_speed,
"purpose": f"溶解等待 - {event}" if event else "溶解等待"
}
}
action_sequence.append(dissolve_action)
elif stirrer_id:
# 使用定时搅拌
action_sequence.append(create_action_log(f"使用搅拌器进行定时溶解", "🌪️"))
stir_action = {
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"vessel": vessel,
"stir_time": final_time,
"stir_speed": stir_speed,
"settling_time": 0,
"purpose": f"溶解搅拌 - {event}" if event else "溶解搅拌"
}
}
action_sequence.append(stir_action)
else:
# 简单等待
action_sequence.append(create_action_log(f"简单等待溶解完成", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": final_time}
})
# 步骤5.5: 停止加热搅拌(如果需要)
if heatchill_id and final_time == 0 and final_temp > 25.0:
debug_print(f"🔍 5.5: 停止加热器")
action_sequence.append(create_action_log("停止加热搅拌器", "🛑"))
stop_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
}
action_sequence.append(stop_action)
except Exception as e:
debug_print(f"❌ 溶解流程执行失败: {str(e)}")
action_sequence.append(create_action_log(f"溶解流程失败: {str(e)}", ""))
# 添加错误日志
action_sequence.append({
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"溶解失败: {str(e)}"
}
})
# === 最终结果 ===
debug_print("=" * 60)
debug_print(f"🎉 溶解协议生成完成")
debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" 🥼 容器: {vessel}")
debug_print(f" {dissolve_emoji} 溶解类型: {dissolve_type}")
if is_liquid_dissolve:
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
if is_solid_dissolve:
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" ⚖️ 质量: {final_mass}g")
debug_print(f" 🧬 摩尔: {mol}")
debug_print(f" 🌡️ 温度: {final_temp}°C")
debug_print(f" ⏱️ 时间: {final_time}s")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"溶解协议完成: {vessel}"
if is_liquid_dissolve:
summary_msg += f" (使用 {final_volume}mL {solvent})"
if is_solid_dissolve:
summary_msg += f" (溶解 {final_mass}g {reagent})"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
# === 便捷函数 ===
def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
"""按质量溶解固体"""
debug_print(f"🧂 快速固体溶解: {reagent} ({mass}) → {vessel}")
return generate_dissolve_protocol(
G, vessel,
mass=mass,
reagent=reagent,
temp=temp,
time=time
)
def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
"""按摩尔数溶解固体"""
debug_print(f"🧬 按摩尔数溶解固体: {reagent} ({mol}) → {vessel}")
return generate_dissolve_protocol(
G, vessel,
mol=mol,
reagent=reagent,
temp=temp,
time=time
)
def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
temp: Union[str, float] = 25.0, time: Union[str, float] = "5 min") -> List[Dict[str, Any]]:
"""用溶剂溶解"""
debug_print(f"💧 溶剂溶解: {solvent} ({volume}) → {vessel}")
return generate_dissolve_protocol(
G, vessel,
solvent=solvent,
volume=volume,
temp=temp,
time=time
)
def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float]) -> List[Dict[str, Any]]:
"""室温溶解"""
debug_print(f"🌡️ 室温溶解: {solvent} ({volume}) → {vessel}")
return generate_dissolve_protocol(
G, vessel,
solvent=solvent,
volume=volume,
temp="room temperature",
time="5 min"
)
def dissolve_with_heating(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
temp: Union[str, float] = "60 °C", time: Union[str, float] = "15 min") -> List[Dict[str, Any]]:
"""加热溶解"""
debug_print(f"🔥 加热溶解: {solvent} ({volume}) → {vessel} @ {temp}")
return generate_dissolve_protocol(
G, vessel,
solvent=solvent,
volume=volume,
temp=temp,
time=time
)
# 测试函数
def test_dissolve_protocol():
"""测试溶解协议的各种参数解析"""
debug_print("=== DISSOLVE PROTOCOL 增强版测试 ===")
# 测试体积解析
debug_print("💧 测试体积解析...")
volumes = ["10 mL", "?", 10.0, "1 L", "500 μL"]
for vol in volumes:
result = parse_volume_input(vol)
debug_print(f"📏 体积解析: {vol}{result}mL")
# 测试质量解析
debug_print("⚖️ 测试质量解析...")
masses = ["2.9 g", "?", 2.5, "500 mg"]
for mass in masses:
result = parse_mass_input(mass)
debug_print(f"⚖️ 质量解析: {mass}{result}g")
# 测试温度解析
debug_print("🌡️ 测试温度解析...")
temps = ["60 °C", "room temperature", "?", 25.0, "reflux"]
for temp in temps:
result = parse_temperature_input(temp)
debug_print(f"🌡️ 温度解析: {temp}{result}°C")
# 测试时间解析
debug_print("⏱️ 测试时间解析...")
times = ["30 min", "1 h", "?", 60.0]
for time in times:
result = parse_time_input(time)
debug_print(f"⏱️ 时间解析: {time}{result}s")
debug_print("✅ 测试完成")
if __name__ == "__main__":
test_dissolve_protocol()

View File

@@ -0,0 +1,185 @@
import networkx as nx
from typing import List, Dict, Any
def find_connected_heater(G: nx.DiGraph, vessel: str) -> str:
"""
查找与容器相连的加热器
Args:
G: 网络图
vessel: 容器名称
Returns:
str: 加热器ID如果没有则返回None
"""
print(f"DRY: 正在查找与容器 '{vessel}' 相连的加热器...")
# 查找所有加热器节点
heater_nodes = [node for node in G.nodes()
if ('heater' in node.lower() or
'heat' in node.lower() or
G.nodes[node].get('class') == 'virtual_heatchill' or
G.nodes[node].get('type') == 'heater')]
print(f"DRY: 找到的加热器节点: {heater_nodes}")
# 检查是否有加热器与目标容器相连
for heater in heater_nodes:
if G.has_edge(heater, vessel) or G.has_edge(vessel, heater):
print(f"DRY: 找到与容器 '{vessel}' 相连的加热器: {heater}")
return heater
# 如果没有直接连接,查找距离最近的加热器
for heater in heater_nodes:
try:
path = nx.shortest_path(G, source=heater, target=vessel)
if len(path) <= 3: # 最多2个中间节点
print(f"DRY: 找到距离较近的加热器: {heater}, 路径: {''.join(path)}")
return heater
except nx.NetworkXNoPath:
continue
print(f"DRY: 未找到与容器 '{vessel}' 相连的加热器")
return None
def generate_dry_protocol(
G: nx.DiGraph,
compound: str,
vessel: str,
**kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]:
"""
生成干燥协议序列
Args:
G: 有向图,节点为容器和设备
compound: 化合物名称从XDL传入
vessel: 目标容器从XDL传入
**kwargs: 其他可选参数,但不使用
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
# 默认参数
dry_temp = 60.0 # 默认干燥温度 60°C
dry_time = 3600.0 # 默认干燥时间 1小时3600秒
simulation_time = 60.0 # 模拟时间 1分钟
print(f"🌡️ DRY: 开始生成干燥协议 ✨")
print(f" 🧪 化合物: {compound}")
print(f" 🥽 容器: {vessel}")
print(f" 🔥 干燥温度: {dry_temp}°C")
print(f" ⏰ 干燥时间: {dry_time/60:.0f} 分钟")
# 1. 验证目标容器存在
print(f"\n📋 步骤1: 验证目标容器 '{vessel}' 是否存在...")
if vessel not in G.nodes():
print(f"⚠️ DRY: 警告 - 容器 '{vessel}' 不存在于系统中,跳过干燥 😢")
return action_sequence
print(f"✅ 容器 '{vessel}' 验证通过!")
# 2. 查找相连的加热器
print(f"\n🔍 步骤2: 查找与容器相连的加热器...")
heater_id = find_connected_heater(G, vessel)
if heater_id is None:
print(f"😭 DRY: 警告 - 未找到与容器 '{vessel}' 相连的加热器,跳过干燥")
print(f"🎭 添加模拟干燥动作...")
# 添加一个等待动作,表示干燥过程(模拟)
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0, # 模拟等待时间
"description": f"模拟干燥 {compound} (无加热器可用)"
}
})
print(f"📄 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯")
return action_sequence
print(f"🎉 找到加热器: {heater_id}!")
# 3. 启动加热器进行干燥
print(f"\n🚀 步骤3: 开始执行干燥流程...")
print(f"🔥 启动加热器 {heater_id} 进行干燥")
# 3.1 启动加热
print(f" ⚡ 动作1: 启动加热到 {dry_temp}°C...")
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": dry_temp,
"purpose": f"干燥 {compound}"
}
})
print(f" ✅ 加热器启动命令已添加 🔥")
# 3.2 等待温度稳定
print(f" ⏳ 动作2: 等待温度稳定...")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0,
"description": f"等待温度稳定到 {dry_temp}°C"
}
})
print(f" ✅ 温度稳定等待命令已添加 🌡️")
# 3.3 保持干燥温度
print(f" 🔄 动作3: 保持干燥温度 {simulation_time/60:.0f} 分钟...")
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": dry_temp,
"time": simulation_time,
"purpose": f"干燥 {compound},保持温度 {dry_temp}°C"
}
})
print(f" ✅ 温度保持命令已添加 🌡️⏰")
# 3.4 停止加热
print(f" ⏹️ 动作4: 停止加热...")
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel,
"purpose": f"干燥完成,停止加热"
}
})
print(f" ✅ 停止加热命令已添加 🛑")
# 3.5 等待冷却
print(f" ❄️ 动作5: 等待冷却...")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0, # 等待10秒冷却
"description": f"等待 {compound} 冷却"
}
})
print(f" ✅ 冷却等待命令已添加 🧊")
print(f"\n🎊 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯")
print(f"⏱️ DRY: 预计总时间: {(dry_time + 360)/60:.0f} 分钟 ⌛")
print(f"🏁 所有动作序列准备就绪! ✨")
return action_sequence
# 测试函数
def test_dry_protocol():
"""测试干燥协议"""
print("=== DRY PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_dry_protocol()

View File

@@ -1,143 +1,772 @@
import numpy as np
import networkx as nx
import logging
import uuid
import sys
from typing import List, Dict, Any, Optional
from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol
# 设置日志
logger = logging.getLogger(__name__)
# 确保输出编码为UTF-8
if hasattr(sys.stdout, 'reconfigure'):
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except:
pass
def debug_print(message):
"""调试输出函数 - 支持中文"""
try:
# 确保消息是字符串格式
safe_message = str(message)
print(f"[抽真空充气] {safe_message}", flush=True)
logger.info(f"[抽真空充气] {safe_message}")
except UnicodeEncodeError:
# 如果编码失败,尝试替换不支持的字符
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
print(f"[抽真空充气] {safe_message}", flush=True)
logger.info(f"[抽真空充气] {safe_message}")
except Exception as e:
# 最后的安全措施
fallback_message = f"日志输出错误: {repr(message)}"
print(f"[抽真空充气] {fallback_message}", flush=True)
logger.info(f"[抽真空充气] {fallback_message}")
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志 - 支持中文和emoji"""
try:
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message,
"progress_message": full_message
}
}
except Exception as e:
# 如果emoji有问题使用纯文本
safe_message = f"[日志] {message}"
debug_print(safe_message)
logger.info(safe_message)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": safe_message,
"progress_message": safe_message
}
}
def find_gas_source(G: nx.DiGraph, gas: str) -> str:
"""
根据气体名称查找对应的气源,支持多种匹配模式:
1. 容器名称匹配
2. 气体类型匹配data.gas_type
3. 默认气源
"""
debug_print(f"🔍 正在查找气体 '{gas}' 的气源...")
# 第一步:通过容器名称匹配
debug_print(f"📋 方法1: 容器名称匹配...")
gas_source_patterns = [
f"gas_source_{gas}",
f"gas_{gas}",
f"flask_{gas}",
f"{gas}_source",
f"source_{gas}",
f"reagent_bottle_{gas}",
f"bottle_{gas}"
]
debug_print(f"🎯 尝试的容器名称: {gas_source_patterns}")
for pattern in gas_source_patterns:
if pattern in G.nodes():
debug_print(f"✅ 通过名称找到气源: {pattern}")
return pattern
# 第二步:通过气体类型匹配 (data.gas_type)
debug_print(f"📋 方法2: 气体类型匹配...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
node_class = node_data.get('class', '') or ''
# 检查是否是气源设备
if ('gas_source' in node_class or
'gas' in node_id.lower() or
node_id.startswith('flask_')):
# 检查 data.gas_type
data = node_data.get('data', {})
gas_type = data.get('gas_type', '')
if gas_type.lower() == gas.lower():
debug_print(f"✅ 通过气体类型找到气源: {node_id} (气体类型: {gas_type})")
return node_id
# 检查 config.gas_type
config = node_data.get('config', {})
config_gas_type = config.get('gas_type', '')
if config_gas_type.lower() == gas.lower():
debug_print(f"✅ 通过配置气体类型找到气源: {node_id} (配置气体类型: {config_gas_type})")
return node_id
# 第三步:查找所有可用的气源设备
debug_print(f"📋 方法3: 查找可用气源...")
available_gas_sources = []
for node_id in G.nodes():
node_data = G.nodes[node_id]
node_class = node_data.get('class', '') or ''
if ('gas_source' in node_class or
'gas' in node_id.lower() or
(node_id.startswith('flask_') and any(g in node_id.lower() for g in ['air', 'nitrogen', 'argon']))):
data = node_data.get('data', {})
gas_type = data.get('gas_type', '未知')
available_gas_sources.append(f"{node_id} (气体类型: {gas_type})")
debug_print(f"📊 可用气源: {available_gas_sources}")
# 第四步:如果找不到特定气体,使用默认的第一个气源
debug_print(f"📋 方法4: 查找默认气源...")
default_gas_sources = [
node for node in G.nodes()
if ((G.nodes[node].get('class') or '').find('virtual_gas_source') != -1
or 'gas_source' in node)
]
if default_gas_sources:
default_source = default_gas_sources[0]
debug_print(f"⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}")
return default_source
debug_print(f"❌ 所有方法都失败了!")
raise ValueError(f"无法找到气体 '{gas}' 的气源。可用气源: {available_gas_sources}")
def find_vacuum_pump(G: nx.DiGraph) -> str:
"""查找真空泵设备"""
debug_print("🔍 正在查找真空泵...")
vacuum_pumps = []
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if ('virtual_vacuum_pump' in node_class or
'vacuum_pump' in node.lower() or
'vacuum' in node_class.lower()):
vacuum_pumps.append(node)
debug_print(f"📋 发现真空泵: {node}")
if not vacuum_pumps:
debug_print(f"❌ 系统中未找到真空泵")
raise ValueError("系统中未找到真空泵")
debug_print(f"✅ 使用真空泵: {vacuum_pumps[0]}")
return vacuum_pumps[0]
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> Optional[str]:
"""查找与指定容器相连的搅拌器"""
debug_print(f"🔍 正在查找与容器 {vessel} 连接的搅拌器...")
stirrer_nodes = []
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if 'virtual_stirrer' in node_class or 'stirrer' in node.lower():
stirrer_nodes.append(node)
debug_print(f"📋 发现搅拌器: {node}")
debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}")
# 检查哪个搅拌器与目标容器相连
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"✅ 找到连接的搅拌器: {stirrer}")
return stirrer
# 如果没有连接的搅拌器,返回第一个可用的
if stirrer_nodes:
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
return stirrer_nodes[0]
debug_print("❌ 未找到搅拌器")
return None
def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]:
"""查找真空泵相关的电磁阀"""
debug_print(f"🔍 正在查找真空泵 {vacuum_pump} 的电磁阀...")
# 查找所有电磁阀
solenoid_valves = []
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
solenoid_valves.append(node)
debug_print(f"📋 发现电磁阀: {node}")
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
# 检查连接关系
debug_print(f"📋 方法1: 检查连接关系...")
for solenoid in solenoid_valves:
if G.has_edge(solenoid, vacuum_pump) or G.has_edge(vacuum_pump, solenoid):
debug_print(f"✅ 找到连接的真空电磁阀: {solenoid}")
return solenoid
# 通过命名规则查找
debug_print(f"📋 方法2: 检查命名规则...")
for solenoid in solenoid_valves:
if 'vacuum' in solenoid.lower() or solenoid == 'solenoid_valve_1':
debug_print(f"✅ 通过命名找到真空电磁阀: {solenoid}")
return solenoid
debug_print("⚠️ 未找到真空电磁阀")
return None
def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
"""查找气源相关的电磁阀"""
debug_print(f"🔍 正在查找气源 {gas_source} 的电磁阀...")
# 查找所有电磁阀
solenoid_valves = []
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
solenoid_valves.append(node)
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
# 检查连接关系
debug_print(f"📋 方法1: 检查连接关系...")
for solenoid in solenoid_valves:
if G.has_edge(gas_source, solenoid) or G.has_edge(solenoid, gas_source):
debug_print(f"✅ 找到连接的气源电磁阀: {solenoid}")
return solenoid
# 通过命名规则查找
debug_print(f"📋 方法2: 检查命名规则...")
for solenoid in solenoid_valves:
if 'gas' in solenoid.lower() or solenoid == 'solenoid_valve_2':
debug_print(f"✅ 通过命名找到气源电磁阀: {solenoid}")
return solenoid
debug_print("⚠️ 未找到气源电磁阀")
return None
def generate_evacuateandrefill_protocol(
G: nx.DiGraph,
vessel: str,
gas: str,
repeats: int = 1
) -> list[dict]:
G: nx.DiGraph,
vessel: str,
gas: str,
**kwargs
) -> List[Dict[str, Any]]:
"""
生成操作的动作序列
生成抽真空和充气操作的动作序列 - 中文版
:param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
:param from_vessel: 容器A
:param to_vessel: 容器B
:param volume: 转移的体积
:param flowrate: 最终注入容器B时的流速
:param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
:return: 泵操作的动作序列
Args:
G: 设备图
vessel: 目标容器名称(必需)
gas: 气体名称(必需)
**kwargs: 其他参数(兼容性)
Returns:
List[Dict[str, Any]]: 动作序列
"""
# 生成电磁阀、真空泵、气源操作的动作序列
vacuum_action_sequence = []
nodes = G.nodes(data=True)
# 硬编码重复次数为 3
repeats = 3
# 找到和 vessel 相连的电磁阀和真空泵、气源
vacuum_backbone = {"vessel": vessel}
# 生成协议ID
protocol_id = str(uuid.uuid4())
debug_print(f"🆔 生成协议ID: {protocol_id}")
for neighbor in G.neighbors(vessel):
if nodes[neighbor]["class"].startswith("solenoid_valve"):
for neighbor2 in G.neighbors(neighbor):
if neighbor2 == vessel:
continue
if nodes[neighbor2]["class"].startswith("vacuum_pump"):
vacuum_backbone.update({"vacuum_valve": neighbor, "pump": neighbor2})
break
elif nodes[neighbor2]["class"].startswith("gas_source"):
vacuum_backbone.update({"gas_valve": neighbor, "gas": neighbor2})
break
# 判断是否设备齐全
if len(vacuum_backbone) < 5:
print(f"\n\n\n{vacuum_backbone}\n\n\n")
raise ValueError("Not all devices are connected to the vessel.")
debug_print("=" * 60)
debug_print("🧪 开始生成抽真空充气协议")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 容器: '{vessel}'")
debug_print(f" 💨 气体: '{gas}'")
debug_print(f" 🔄 循环次数: {repeats} (硬编码)")
debug_print(f" 📦 其他参数: {kwargs}")
debug_print("=" * 60)
# 生成操作的动作序列
for i in range(repeats):
# 打开真空泵阀门、关闭气源阀门
vacuum_action_sequence.append([
{
"device_id": vacuum_backbone["vacuum_valve"],
"action_name": "set_valve_position",
"action_kwargs": {
"command": "OPEN"
}
},
{
"device_id": vacuum_backbone["gas_valve"],
"action_name": "set_valve_position",
"action_kwargs": {
"command": "CLOSED"
}
}
])
action_sequence = []
# === 参数验证和修正 ===
debug_print("🔍 步骤1: 参数验证和修正...")
action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel}", "🎬"))
action_sequence.append(create_action_log(f"目标气体: {gas}", "💨"))
action_sequence.append(create_action_log(f"循环次数: {repeats}", "🔄"))
# 验证必需参数
if not vessel:
debug_print("❌ 容器参数不能为空")
raise ValueError("容器参数不能为空")
if not gas:
debug_print("❌ 气体参数不能为空")
raise ValueError("气体参数不能为空")
if vessel not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 在系统中不存在")
raise ValueError(f"容器 '{vessel}' 在系统中不存在")
debug_print("✅ 基本参数验证通过")
action_sequence.append(create_action_log("参数验证通过", ""))
# 标准化气体名称
debug_print("🔧 标准化气体名称...")
gas_aliases = {
'n2': 'nitrogen',
'ar': 'argon',
'air': 'air',
'o2': 'oxygen',
'co2': 'carbon_dioxide',
'h2': 'hydrogen',
'氮气': 'nitrogen',
'氩气': 'argon',
'空气': 'air',
'氧气': 'oxygen',
'二氧化碳': 'carbon_dioxide',
'氢气': 'hydrogen'
}
original_gas = gas
gas_lower = gas.lower().strip()
if gas_lower in gas_aliases:
gas = gas_aliases[gas_lower]
debug_print(f"🔄 标准化气体名称: {original_gas} -> {gas}")
action_sequence.append(create_action_log(f"气体名称标准化: {original_gas} -> {gas}", "🔄"))
debug_print(f"📋 最终参数: 容器={vessel}, 气体={gas}, 重复={repeats}")
# === 查找设备 ===
debug_print("🔍 步骤2: 查找设备...")
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
try:
vacuum_pump = find_vacuum_pump(G)
action_sequence.append(create_action_log(f"找到真空泵: {vacuum_pump}", "🌪️"))
# 打开真空泵、关闭气源
vacuum_action_sequence.append([
{
"device_id": vacuum_backbone["pump"],
"action_name": "set_status",
"action_kwargs": {
"command": "ON"
}
},
{
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
"command": "OFF"
}
}
])
vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
gas_source = find_gas_source(G, gas)
action_sequence.append(create_action_log(f"找到气源: {gas_source}", "💨"))
# 关闭真空泵阀门、打开气源阀门
vacuum_action_sequence.append([
{
"device_id": vacuum_backbone["vacuum_valve"],
"action_name": "set_valve_position",
"action_kwargs": {
"command": "CLOSED"
}
},
{
"device_id": vacuum_backbone["gas_valve"],
"action_name": "set_valve_position",
"action_kwargs": {
"command": "OPEN"
}
}
])
vacuum_solenoid = find_vacuum_solenoid_valve(G, vacuum_pump)
if vacuum_solenoid:
action_sequence.append(create_action_log(f"找到真空电磁阀: {vacuum_solenoid}", "🚪"))
else:
action_sequence.append(create_action_log("未找到真空电磁阀", "⚠️"))
# 关闭真空泵、打开气源
vacuum_action_sequence.append([
{
"device_id": vacuum_backbone["pump"],
"action_name": "set_status",
"action_kwargs": {
"command": "OFF"
}
},
{
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
"command": "ON"
}
gas_solenoid = find_gas_solenoid_valve(G, gas_source)
if gas_solenoid:
action_sequence.append(create_action_log(f"找到气源电磁阀: {gas_solenoid}", "🚪"))
else:
action_sequence.append(create_action_log("未找到气源电磁阀", "⚠️"))
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
else:
action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
debug_print(f"📊 设备配置:")
debug_print(f" 🌪️ 真空泵: {vacuum_pump}")
debug_print(f" 💨 气源: {gas_source}")
debug_print(f" 🚪 真空电磁阀: {vacuum_solenoid}")
debug_print(f" 🚪 气源电磁阀: {gas_solenoid}")
debug_print(f" 🌪️ 搅拌器: {stirrer_id}")
except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)}")
action_sequence.append(create_action_log(f"设备查找失败: {str(e)}", ""))
raise ValueError(f"设备查找失败: {str(e)}")
# === 参数设置 ===
debug_print("🔍 步骤3: 参数设置...")
action_sequence.append(create_action_log("设置操作参数...", "⚙️"))
# 根据气体类型调整参数
if gas.lower() in ['nitrogen', 'argon']:
VACUUM_VOLUME = 25.0
REFILL_VOLUME = 25.0
PUMP_FLOW_RATE = 2.0
VACUUM_TIME = 30.0
REFILL_TIME = 20.0
debug_print("💨 惰性气体: 使用标准参数")
action_sequence.append(create_action_log("检测到惰性气体,使用标准参数", "💨"))
elif gas.lower() in ['air', 'oxygen']:
VACUUM_VOLUME = 20.0
REFILL_VOLUME = 20.0
PUMP_FLOW_RATE = 1.5
VACUUM_TIME = 45.0
REFILL_TIME = 25.0
debug_print("🔥 活性气体: 使用保守参数")
action_sequence.append(create_action_log("检测到活性气体,使用保守参数", "🔥"))
else:
VACUUM_VOLUME = 15.0
REFILL_VOLUME = 15.0
PUMP_FLOW_RATE = 1.0
VACUUM_TIME = 60.0
REFILL_TIME = 30.0
debug_print("❓ 未知气体: 使用安全参数")
action_sequence.append(create_action_log("未知气体类型,使用安全参数", ""))
STIR_SPEED = 200.0
debug_print(f"⚙️ 操作参数:")
debug_print(f" 📏 真空体积: {VACUUM_VOLUME}mL")
debug_print(f" 📏 充气体积: {REFILL_VOLUME}mL")
debug_print(f" ⚡ 泵流速: {PUMP_FLOW_RATE}mL/s")
debug_print(f" ⏱️ 真空时间: {VACUUM_TIME}s")
debug_print(f" ⏱️ 充气时间: {REFILL_TIME}s")
debug_print(f" 🌪️ 搅拌速度: {STIR_SPEED}RPM")
action_sequence.append(create_action_log(f"真空体积: {VACUUM_VOLUME}mL", "📏"))
action_sequence.append(create_action_log(f"充气体积: {REFILL_VOLUME}mL", "📏"))
action_sequence.append(create_action_log(f"泵流速: {PUMP_FLOW_RATE}mL/s", ""))
# === 路径验证 ===
debug_print("🔍 步骤4: 路径验证...")
action_sequence.append(create_action_log("验证传输路径...", "🛤️"))
try:
# 验证抽真空路径
if nx.has_path(G, vessel, vacuum_pump):
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump)
debug_print(f"✅ 真空路径: {' -> '.join(vacuum_path)}")
action_sequence.append(create_action_log(f"真空路径: {' -> '.join(vacuum_path)}", "🛤️"))
else:
debug_print(f"⚠️ 真空路径不存在,继续执行但可能有问题")
action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️"))
# 验证充气路径
if nx.has_path(G, gas_source, vessel):
gas_path = nx.shortest_path(G, source=gas_source, target=vessel)
debug_print(f"✅ 气体路径: {' -> '.join(gas_path)}")
action_sequence.append(create_action_log(f"气体路径: {' -> '.join(gas_path)}", "🛤️"))
else:
debug_print(f"⚠️ 气体路径不存在,继续执行但可能有问题")
action_sequence.append(create_action_log("气体路径检查: 路径不存在", "⚠️"))
except Exception as e:
debug_print(f"⚠️ 路径验证失败: {str(e)},继续执行")
action_sequence.append(create_action_log(f"路径验证失败: {str(e)}", "⚠️"))
# === 启动搅拌器 ===
debug_print("🔍 步骤5: 启动搅拌器...")
if stirrer_id:
debug_print(f"🌪️ 启动搅拌器: {stirrer_id}")
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {STIR_SPEED}rpm)", "🌪️"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": STIR_SPEED,
"purpose": "抽真空充气前预搅拌"
}
])
vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
})
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5.0}
})
else:
debug_print("⚠️ 未找到搅拌器,跳过搅拌器启动")
action_sequence.append(create_action_log("跳过搅拌器启动", "⏭️"))
# === 执行循环 ===
debug_print("🔍 步骤6: 执行抽真空-充气循环...")
action_sequence.append(create_action_log(f"开始 {repeats} 次抽真空-充气循环", "🔄"))
for cycle in range(repeats):
debug_print(f"=== 第 {cycle+1}/{repeats} 轮循环 ===")
action_sequence.append(create_action_log(f"{cycle+1}/{repeats} 轮循环开始", "🚀"))
# ============ 抽真空阶段 ============
debug_print(f"🌪️ 抽真空阶段开始")
action_sequence.append(create_action_log("开始抽真空阶段", "🌪️"))
# 启动真空泵
debug_print(f"🔛 启动真空泵: {vacuum_pump}")
action_sequence.append(create_action_log(f"启动真空泵: {vacuum_pump}", "🔛"))
action_sequence.append({
"device_id": vacuum_pump,
"action_name": "set_status",
"action_kwargs": {"string": "ON"}
})
# 开启真空电磁阀
if vacuum_solenoid:
debug_print(f"🚪 打开真空电磁阀: {vacuum_solenoid}")
action_sequence.append(create_action_log(f"打开真空电磁阀: {vacuum_solenoid}", "🚪"))
action_sequence.append({
"device_id": vacuum_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"}
})
# 抽真空操作
debug_print(f"🌪️ 抽真空操作: {vessel} -> {vacuum_pump}")
action_sequence.append(create_action_log(f"开始抽真空: {vessel} -> {vacuum_pump}", "🌪️"))
try:
vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=vessel,
to_vessel=vacuum_pump,
volume=VACUUM_VOLUME,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=PUMP_FLOW_RATE,
transfer_flowrate=PUMP_FLOW_RATE
)
if vacuum_transfer_actions:
action_sequence.extend(vacuum_transfer_actions)
debug_print(f"✅ 添加了 {len(vacuum_transfer_actions)} 个抽真空动作")
action_sequence.append(create_action_log(f"抽真空协议完成 ({len(vacuum_transfer_actions)} 个操作)", ""))
else:
debug_print("⚠️ 抽真空协议返回空序列,添加手动动作")
action_sequence.append(create_action_log("抽真空协议为空,使用手动等待", "⚠️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": VACUUM_TIME}
})
except Exception as e:
debug_print(f"❌ 抽真空失败: {str(e)}")
action_sequence.append(create_action_log(f"抽真空失败: {str(e)}", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": VACUUM_TIME}
})
# 抽真空后等待
wait_minutes = VACUUM_TIME / 60
action_sequence.append(create_action_log(f"抽真空后等待 ({wait_minutes:.1f} 分钟)", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": VACUUM_TIME}
})
# 关闭真空电磁阀
if vacuum_solenoid:
debug_print(f"🚪 关闭真空电磁阀: {vacuum_solenoid}")
action_sequence.append(create_action_log(f"关闭真空电磁阀: {vacuum_solenoid}", "🚪"))
action_sequence.append({
"device_id": vacuum_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "CLOSED"}
})
# 关闭真空泵
debug_print(f"🔴 停止真空泵: {vacuum_pump}")
action_sequence.append(create_action_log(f"停止真空泵: {vacuum_pump}", "🔴"))
action_sequence.append({
"device_id": vacuum_pump,
"action_name": "set_status",
"action_kwargs": {"string": "OFF"}
})
# 阶段间等待
action_sequence.append(create_action_log("抽真空阶段完成,短暂等待", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5.0}
})
# ============ 充气阶段 ============
debug_print(f"💨 充气阶段开始")
action_sequence.append(create_action_log("开始气体充气阶段", "💨"))
# 启动气源
debug_print(f"🔛 启动气源: {gas_source}")
action_sequence.append(create_action_log(f"启动气源: {gas_source}", "🔛"))
action_sequence.append({
"device_id": gas_source,
"action_name": "set_status",
"action_kwargs": {"string": "ON"}
})
# 开启气源电磁阀
if gas_solenoid:
debug_print(f"🚪 打开气源电磁阀: {gas_solenoid}")
action_sequence.append(create_action_log(f"打开气源电磁阀: {gas_solenoid}", "🚪"))
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"}
})
# 充气操作
debug_print(f"💨 充气操作: {gas_source} -> {vessel}")
action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel}", "💨"))
try:
gas_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=gas_source,
to_vessel=vessel,
volume=REFILL_VOLUME,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=PUMP_FLOW_RATE,
transfer_flowrate=PUMP_FLOW_RATE
)
if gas_transfer_actions:
action_sequence.extend(gas_transfer_actions)
debug_print(f"✅ 添加了 {len(gas_transfer_actions)} 个充气动作")
action_sequence.append(create_action_log(f"气体充气协议完成 ({len(gas_transfer_actions)} 个操作)", ""))
else:
debug_print("⚠️ 充气协议返回空序列,添加手动动作")
action_sequence.append(create_action_log("充气协议为空,使用手动等待", "⚠️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": REFILL_TIME}
})
except Exception as e:
debug_print(f"❌ 气体充气失败: {str(e)}")
action_sequence.append(create_action_log(f"气体充气失败: {str(e)}", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": REFILL_TIME}
})
# 充气后等待
refill_wait_minutes = REFILL_TIME / 60
action_sequence.append(create_action_log(f"充气后等待 ({refill_wait_minutes:.1f} 分钟)", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": REFILL_TIME}
})
# 关闭气源电磁阀
if gas_solenoid:
debug_print(f"🚪 关闭气源电磁阀: {gas_solenoid}")
action_sequence.append(create_action_log(f"关闭气源电磁阀: {gas_solenoid}", "🚪"))
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "CLOSED"}
})
# 关闭气源
vacuum_action_sequence.append(
{
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
"command": "OFF"
}
}
)
debug_print(f"🔴 停止气源: {gas_source}")
action_sequence.append(create_action_log(f"停止气源: {gas_source}", "🔴"))
action_sequence.append({
"device_id": gas_source,
"action_name": "set_status",
"action_kwargs": {"string": "OFF"}
})
# 关闭阀门
vacuum_action_sequence.append(
{
"device_id": vacuum_backbone["gas_valve"],
"action_name": "set_valve_position",
"action_kwargs": {
"command": "CLOSED"
}
}
)
return vacuum_action_sequence
# 循环间等待
if cycle < repeats - 1:
debug_print(f"⏳ 等待下一个循环...")
action_sequence.append(create_action_log("等待下一个循环...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10.0}
})
else:
action_sequence.append(create_action_log(f"{cycle+1}/{repeats} 轮循环完成", ""))
# === 停止搅拌器 ===
debug_print("🔍 步骤7: 停止搅拌器...")
if stirrer_id:
debug_print(f"🛑 停止搅拌器: {stirrer_id}")
action_sequence.append(create_action_log(f"停止搅拌器: {stirrer_id}", "🛑"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {"vessel": vessel}
})
else:
action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️"))
# === 最终等待 ===
action_sequence.append(create_action_log("最终稳定等待...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10.0}
})
# === 总结 ===
total_time = (VACUUM_TIME + REFILL_TIME + 25) * repeats + 20
debug_print("=" * 60)
debug_print(f"🎉 抽真空充气协议生成完成")
debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
debug_print(f" 🥼 处理容器: {vessel}")
debug_print(f" 💨 使用气体: {gas}")
debug_print(f" 🔄 重复次数: {repeats}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"抽真空充气协议完成: {vessel} (使用 {gas}{repeats} 次循环)"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
# === 便捷函数 ===
def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
"""生成氮气置换协议"""
debug_print(f"💨 生成氮气置换协议: {vessel}")
return generate_evacuateandrefill_protocol(G, vessel, "nitrogen", **kwargs)
def generate_argon_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
"""生成氩气置换协议"""
debug_print(f"💨 生成氩气置换协议: {vessel}")
return generate_evacuateandrefill_protocol(G, vessel, "argon", **kwargs)
def generate_air_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
"""生成空气置换协议"""
debug_print(f"💨 生成空气置换协议: {vessel}")
return generate_evacuateandrefill_protocol(G, vessel, "air", **kwargs)
def generate_inert_atmosphere_protocol(G: nx.DiGraph, vessel: str, gas: str = "nitrogen", **kwargs) -> List[Dict[str, Any]]:
"""生成惰性气氛协议"""
debug_print(f"🛡️ 生成惰性气氛协议: {vessel} (使用 {gas})")
return generate_evacuateandrefill_protocol(G, vessel, gas, **kwargs)
# 测试函数
def test_evacuateandrefill_protocol():
"""测试抽真空充气协议"""
debug_print("=== 抽真空充气协议增强中文版测试 ===")
debug_print("✅ 测试完成")
if __name__ == "__main__":
test_evacuateandrefill_protocol()

View File

@@ -0,0 +1,143 @@
# import numpy as np
# import networkx as nx
# def generate_evacuateandrefill_protocol(
# G: nx.DiGraph,
# vessel: str,
# gas: str,
# repeats: int = 1
# ) -> list[dict]:
# """
# 生成泵操作的动作序列。
# :param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
# :param from_vessel: 容器A
# :param to_vessel: 容器B
# :param volume: 转移的体积
# :param flowrate: 最终注入容器B时的流速
# :param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
# :return: 泵操作的动作序列
# """
# # 生成电磁阀、真空泵、气源操作的动作序列
# vacuum_action_sequence = []
# nodes = G.nodes(data=True)
# # 找到和 vessel 相连的电磁阀和真空泵、气源
# vacuum_backbone = {"vessel": vessel}
# for neighbor in G.neighbors(vessel):
# if nodes[neighbor]["class"].startswith("solenoid_valve"):
# for neighbor2 in G.neighbors(neighbor):
# if neighbor2 == vessel:
# continue
# if nodes[neighbor2]["class"].startswith("vacuum_pump"):
# vacuum_backbone.update({"vacuum_valve": neighbor, "pump": neighbor2})
# break
# elif nodes[neighbor2]["class"].startswith("gas_source"):
# vacuum_backbone.update({"gas_valve": neighbor, "gas": neighbor2})
# break
# # 判断是否设备齐全
# if len(vacuum_backbone) < 5:
# print(f"\n\n\n{vacuum_backbone}\n\n\n")
# raise ValueError("Not all devices are connected to the vessel.")
# # 生成操作的动作序列
# for i in range(repeats):
# # 打开真空泵阀门、关闭气源阀门
# vacuum_action_sequence.append([
# {
# "device_id": vacuum_backbone["vacuum_valve"],
# "action_name": "set_valve_position",
# "action_kwargs": {
# "command": "OPEN"
# }
# },
# {
# "device_id": vacuum_backbone["gas_valve"],
# "action_name": "set_valve_position",
# "action_kwargs": {
# "command": "CLOSED"
# }
# }
# ])
# # 打开真空泵、关闭气源
# vacuum_action_sequence.append([
# {
# "device_id": vacuum_backbone["pump"],
# "action_name": "set_status",
# "action_kwargs": {
# "string": "ON"
# }
# },
# {
# "device_id": vacuum_backbone["gas"],
# "action_name": "set_status",
# "action_kwargs": {
# "string": "OFF"
# }
# }
# ])
# vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
# # 关闭真空泵阀门、打开气源阀门
# vacuum_action_sequence.append([
# {
# "device_id": vacuum_backbone["vacuum_valve"],
# "action_name": "set_valve_position",
# "action_kwargs": {
# "command": "CLOSED"
# }
# },
# {
# "device_id": vacuum_backbone["gas_valve"],
# "action_name": "set_valve_position",
# "action_kwargs": {
# "command": "OPEN"
# }
# }
# ])
# # 关闭真空泵、打开气源
# vacuum_action_sequence.append([
# {
# "device_id": vacuum_backbone["pump"],
# "action_name": "set_status",
# "action_kwargs": {
# "string": "OFF"
# }
# },
# {
# "device_id": vacuum_backbone["gas"],
# "action_name": "set_status",
# "action_kwargs": {
# "string": "ON"
# }
# }
# ])
# vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
# # 关闭气源
# vacuum_action_sequence.append(
# {
# "device_id": vacuum_backbone["gas"],
# "action_name": "set_status",
# "action_kwargs": {
# "string": "OFF"
# }
# }
# )
# # 关闭阀门
# vacuum_action_sequence.append(
# {
# "device_id": vacuum_backbone["gas_valve"],
# "action_name": "set_valve_position",
# "action_kwargs": {
# "command": "CLOSED"
# }
# }
# )
# return vacuum_action_sequence

View File

@@ -1,81 +1,366 @@
import numpy as np
from typing import List, Dict, Any, Optional, Union
import networkx as nx
import logging
import re
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"🧪 [EVAPORATE] {message}", flush=True)
logger.info(f"[EVAPORATE] {message}")
def parse_time_input(time_input: Union[str, float]) -> float:
"""
解析时间输入,支持带单位的字符串
Args:
time_input: 时间输入(如 "3 min", "180", "0.5 h" 等)
Returns:
float: 时间(秒)
"""
if isinstance(time_input, (int, float)):
debug_print(f"⏱️ 时间输入为数字: {time_input}s ✨")
return float(time_input)
if not time_input or not str(time_input).strip():
debug_print(f"⚠️ 时间输入为空,使用默认值: 180s (3分钟) 🕐")
return 180.0 # 默认3分钟
time_str = str(time_input).lower().strip()
debug_print(f"🔍 解析时间输入: '{time_str}' 📝")
# 处理未知时间
if time_str in ['?', 'unknown', 'tbd']:
default_time = 180.0 # 默认3分钟
debug_print(f"❓ 检测到未知时间,使用默认值: {default_time}s (3分钟) 🤷‍♀️")
return default_time
# 移除空格并提取数字和单位
time_clean = re.sub(r'\s+', '', time_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean)
if not match:
# 如果无法解析,尝试直接转换为数字(默认秒)
try:
value = float(time_str)
debug_print(f"✅ 时间解析成功: {time_str}{value}s无单位默认秒")
return value
except ValueError:
debug_print(f"❌ 无法解析时间: '{time_str}'使用默认值180s (3分钟) 😅")
return 180.0
value = float(match.group(1))
unit = match.group(2) or 's' # 默认单位为秒
# 转换为秒
if unit in ['min', 'minute']:
time_sec = value * 60.0 # min -> s
debug_print(f"🕐 时间转换: {value} 分钟 → {time_sec}s ⏰")
elif unit in ['h', 'hr', 'hour']:
time_sec = value * 3600.0 # h -> s
debug_print(f"🕐 时间转换: {value} 小时 → {time_sec}s ({time_sec/60:.1f}分钟) ⏰")
elif unit in ['d', 'day']:
time_sec = value * 86400.0 # d -> s
debug_print(f"🕐 时间转换: {value} 天 → {time_sec}s ({time_sec/3600:.1f}小时) ⏰")
else: # s, sec, second 或默认
time_sec = value # 已经是s
debug_print(f"🕐 时间转换: {value}s → {time_sec}s (已是秒) ⏰")
return time_sec
def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]:
"""
在组态图中查找旋转蒸发仪设备
Args:
G: 设备图
vessel: 指定的设备名称(可选)
Returns:
str: 找到的旋转蒸发仪设备ID如果没找到返回None
"""
debug_print("🔍 开始查找旋转蒸发仪设备... 🌪️")
# 如果指定了vessel先检查是否存在且是旋转蒸发仪
if vessel:
debug_print(f"🎯 检查指定设备: {vessel} 🔧")
if vessel in G.nodes():
node_data = G.nodes[vessel]
node_class = node_data.get('class', '')
node_type = node_data.get('type', '')
debug_print(f"📋 设备信息 {vessel}: class={node_class}, type={node_type}")
# 检查是否为旋转蒸发仪
if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']):
debug_print(f"🎉 找到指定的旋转蒸发仪: {vessel}")
return vessel
elif node_type == 'device':
debug_print(f"✅ 指定设备存在,尝试直接使用: {vessel} 🔧")
return vessel
else:
debug_print(f"❌ 指定的设备 {vessel} 不存在 😞")
# 在所有设备中查找旋转蒸发仪
debug_print("🔎 在所有设备中搜索旋转蒸发仪... 🕵️‍♀️")
rotavap_candidates = []
for node_id, node_data in G.nodes(data=True):
node_class = node_data.get('class', '')
node_type = node_data.get('type', '')
# 跳过非设备节点
if node_type != 'device':
continue
# 检查设备类型
if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']):
rotavap_candidates.append(node_id)
debug_print(f"🌟 找到旋转蒸发仪候选: {node_id} (class: {node_class}) 🌪️")
elif any(keyword in str(node_id).lower() for keyword in ['rotavap', 'rotary', 'evaporat']):
rotavap_candidates.append(node_id)
debug_print(f"🌟 找到旋转蒸发仪候选 (按名称): {node_id} 🌪️")
if rotavap_candidates:
selected = rotavap_candidates[0] # 选择第一个找到的
debug_print(f"🎯 选择旋转蒸发仪: {selected} 🏆")
return selected
debug_print("😭 未找到旋转蒸发仪设备 💔")
return None
def find_connected_vessel(G: nx.DiGraph, rotavap_device: str) -> Optional[str]:
"""
查找与旋转蒸发仪连接的容器
Args:
G: 设备图
rotavap_device: 旋转蒸发仪设备ID
Returns:
str: 连接的容器ID如果没找到返回None
"""
debug_print(f"🔗 查找与 {rotavap_device} 连接的容器... 🥽")
# 查看旋转蒸发仪的子设备
rotavap_data = G.nodes[rotavap_device]
children = rotavap_data.get('children', [])
debug_print(f"👶 检查子设备: {children}")
for child_id in children:
if child_id in G.nodes():
child_data = G.nodes[child_id]
child_type = child_data.get('type', '')
if child_type == 'container':
debug_print(f"🎉 找到连接的容器: {child_id} 🥽✨")
return child_id
# 查看邻接的容器
debug_print("🤝 检查邻接设备...")
for neighbor in G.neighbors(rotavap_device):
neighbor_data = G.nodes[neighbor]
neighbor_type = neighbor_data.get('type', '')
if neighbor_type == 'container':
debug_print(f"🎉 找到邻接的容器: {neighbor} 🥽✨")
return neighbor
debug_print("😞 未找到连接的容器 💔")
return None
def generate_evaporate_protocol(
G: nx.DiGraph,
G: nx.DiGraph,
vessel: str,
pressure: float,
temp: float,
time: float,
stir_speed: float
) -> list[dict]:
pressure: float = 0.1,
temp: float = 60.0,
time: Union[str, float] = "180", # 🔧 修改:支持字符串时间
stir_speed: float = 100.0,
solvent: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
Generate a protocol to evaporate a solution from a vessel.
生成蒸发操作的协议序列 - 支持单位
:param G: Directed graph. Nodes are containers and pumps, edges are fluidic connections.
:param vessel: Vessel to clean.
:param solvent: Solvent to clean vessel with.
:param volume: Volume of solvent to clean vessel with.
:param temp: Temperature to heat vessel to while cleaning.
:param repeats: Number of cleaning cycles to perform.
:return: List of actions to clean vessel.
Args:
G: 设备图
vessel: 容器名称或旋转蒸发仪名称
pressure: 真空度 (bar)默认0.1
temp: 加热温度 (°C)默认60
time: 蒸发时间(支持 "3 min", "180", "0.5 h" 等)
stir_speed: 旋转速度 (RPM)默认100
solvent: 溶剂名称(用于参数优化)
**kwargs: 其他参数(兼容性)
Returns:
List[Dict[str, Any]]: 动作序列
"""
# 生成泵操作的动作序列
pump_action_sequence = []
reactor_volume = 500.0
transfer_flowrate = flowrate = 2.5
debug_print("🌟" * 20)
debug_print("🌪️ 开始生成蒸发协议(支持单位)✨")
debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}")
debug_print(f" 💨 pressure: {pressure} bar")
debug_print(f" 🌡️ temp: {temp}°C")
debug_print(f" ⏰ time: {time} (类型: {type(time)})")
debug_print(f" 🌪️ stir_speed: {stir_speed} RPM")
debug_print(f" 🧪 solvent: '{solvent}'")
debug_print("🌟" * 20)
# 开启冷凝器
pump_action_sequence.append({
"device_id": "rotavap_chiller",
"action_name": "set_temperature",
"action_kwargs": {
"command": "-40"
}
})
# TODO: 通过温度反馈改为 HeatChillToTemp而非等待固定时间
pump_action_sequence.append({
# === 步骤1: 查找旋转蒸发仪设备 ===
debug_print("📍 步骤1: 查找旋转蒸发仪设备... 🔍")
# 验证vessel参数
if not vessel:
debug_print("❌ vessel 参数不能为空! 😱")
raise ValueError("vessel 参数不能为空")
# 查找旋转蒸发仪设备
rotavap_device = find_rotavap_device(G, vessel)
if not rotavap_device:
debug_print("💥 未找到旋转蒸发仪设备! 😭")
raise ValueError(f"未找到旋转蒸发仪设备。请检查组态图中是否包含 class 包含 'rotavap''rotary''evaporat' 的设备")
debug_print(f"🎉 成功找到旋转蒸发仪: {rotavap_device}")
# === 步骤2: 确定目标容器 ===
debug_print("📍 步骤2: 确定目标容器... 🥽")
target_vessel = vessel
# 如果vessel就是旋转蒸发仪设备查找连接的容器
if vessel == rotavap_device:
debug_print("🔄 vessel就是旋转蒸发仪查找连接的容器...")
connected_vessel = find_connected_vessel(G, rotavap_device)
if connected_vessel:
target_vessel = connected_vessel
debug_print(f"✅ 使用连接的容器: {target_vessel} 🥽✨")
else:
debug_print(f"⚠️ 未找到连接的容器,使用设备本身: {rotavap_device} 🔧")
target_vessel = rotavap_device
elif vessel in G.nodes() and G.nodes[vessel].get('type') == 'container':
debug_print(f"✅ 使用指定的容器: {vessel} 🥽✨")
target_vessel = vessel
else:
debug_print(f"⚠️ 容器 '{vessel}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device} 🔧")
target_vessel = rotavap_device
# === 🔧 新增步骤3单位解析处理 ===
debug_print("📍 步骤3: 单位解析处理... ⚡")
# 解析时间
final_time = parse_time_input(time)
debug_print(f"🎯 时间解析完成: {time}{final_time}s ({final_time/60:.1f}分钟) ⏰✨")
# === 步骤4: 参数验证和修正 ===
debug_print("📍 步骤4: 参数验证和修正... 🔧")
# 修正参数范围
if pressure <= 0 or pressure > 1.0:
debug_print(f"⚠️ 真空度 {pressure} bar 超出范围,修正为 0.1 bar 💨")
pressure = 0.1
else:
debug_print(f"✅ 真空度 {pressure} bar 在正常范围内 💨")
if temp < 10.0 or temp > 200.0:
debug_print(f"⚠️ 温度 {temp}°C 超出范围,修正为 60°C 🌡️")
temp = 60.0
else:
debug_print(f"✅ 温度 {temp}°C 在正常范围内 🌡️")
if final_time <= 0:
debug_print(f"⚠️ 时间 {final_time}s 无效,修正为 180s (3分钟) ⏰")
final_time = 180.0
else:
debug_print(f"✅ 时间 {final_time}s ({final_time/60:.1f}分钟) 有效 ⏰")
if stir_speed < 10.0 or stir_speed > 300.0:
debug_print(f"⚠️ 旋转速度 {stir_speed} RPM 超出范围,修正为 100 RPM 🌪️")
stir_speed = 100.0
else:
debug_print(f"✅ 旋转速度 {stir_speed} RPM 在正常范围内 🌪️")
# 根据溶剂优化参数
if solvent:
debug_print(f"🧪 根据溶剂 '{solvent}' 优化参数... 🔬")
solvent_lower = solvent.lower()
if any(s in solvent_lower for s in ['water', 'aqueous', 'h2o']):
temp = max(temp, 80.0)
pressure = max(pressure, 0.2)
debug_print("💧 水系溶剂:提高温度和真空度 🌡️💨")
elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
temp = min(temp, 50.0)
pressure = min(pressure, 0.05)
debug_print("🍺 易挥发溶剂:降低温度和真空度 🌡️💨")
elif any(s in solvent_lower for s in ['dmso', 'dmi', 'toluene']):
temp = max(temp, 100.0)
pressure = min(pressure, 0.01)
debug_print("🔥 高沸点溶剂:提高温度,降低真空度 🌡️💨")
else:
debug_print("🧪 通用溶剂,使用标准参数 ✨")
else:
debug_print("🤷‍♀️ 未指定溶剂,使用默认参数 ✨")
debug_print(f"🎯 最终参数: pressure={pressure} bar 💨, temp={temp}°C 🌡️, time={final_time}s ⏰, stir_speed={stir_speed} RPM 🌪️")
# === 步骤5: 生成动作序列 ===
debug_print("📍 步骤5: 生成动作序列... 🎬")
action_sequence = []
# 1. 等待稳定
debug_print(" 🔄 动作1: 添加初始等待稳定... ⏳")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 1800
}
"action_kwargs": {"time": 10}
})
debug_print(" ✅ 初始等待动作已添加 ⏳✨")
# 开启旋蒸真空泵、旋转在液体转移后运行time时间
pump_action_sequence.append({
"device_id": "rotavap_controller",
"action_name": "set_pump_time",
"action_kwargs": {
"command": str(time + reactor_volume / flowrate * 3)
}
})
pump_action_sequence.append({
"device_id": "rotavap_controller",
"action_name": "set_pump_time",
"action_kwargs": {
"command": str(time + reactor_volume / flowrate * 3)
}
})
# 2. 执行蒸发
debug_print(f" 🌪️ 动作2: 执行蒸发操作...")
debug_print(f" 🔧 设备: {rotavap_device}")
debug_print(f" 🥽 容器: {target_vessel}")
debug_print(f" 💨 真空度: {pressure} bar")
debug_print(f" 🌡️ 温度: {temp}°C")
debug_print(f" ⏰ 时间: {final_time}s ({final_time/60:.1f}分钟)")
debug_print(f" 🌪️ 旋转速度: {stir_speed} RPM")
# 液体转入旋转蒸发器
pump_action_sequence.append({
"device_id": "",
"action_name": "PumpTransferProtocol",
evaporate_action = {
"device_id": rotavap_device,
"action_name": "evaporate",
"action_kwargs": {
"from_vessel": vessel,
"to_vessel": "rotavap",
"volume": reactor_volume,
"time": reactor_volume / flowrate,
# "transfer_flowrate": transfer_flowrate,
"vessel": target_vessel,
"pressure": pressure,
"temp": temp,
"time": final_time,
"stir_speed": stir_speed,
"solvent": solvent
}
})
}
action_sequence.append(evaporate_action)
debug_print(" ✅ 蒸发动作已添加 🌪️✨")
pump_action_sequence.append({
# 3. 蒸发后等待
debug_print(" 🔄 动作3: 添加蒸发后等待... ⏳")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": time
}
"action_kwargs": {"time": 10}
})
return pump_action_sequence
debug_print(" ✅ 蒸发后等待动作已添加 ⏳✨")
# === 总结 ===
debug_print("🎊" * 20)
debug_print(f"🎉 蒸发协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝")
debug_print(f"🌪️ 旋转蒸发仪: {rotavap_device} 🔧")
debug_print(f"🥽 目标容器: {target_vessel} 🧪")
debug_print(f"⚙️ 蒸发参数: {pressure} bar 💨, {temp}°C 🌡️, {final_time}s ⏰, {stir_speed} RPM 🌪️")
debug_print(f"⏱️ 预计总时间: {(final_time + 20)/60:.1f} 分钟 ⌛")
debug_print("🎊" * 20)
return action_sequence

View File

@@ -0,0 +1,236 @@
from typing import List, Dict, Any, Optional
import networkx as nx
import logging
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"🧪 [FILTER] {message}", flush=True)
logger.info(f"[FILTER] {message}")
def find_filter_device(G: nx.DiGraph) -> str:
"""查找过滤器设备"""
debug_print("🔍 查找过滤器设备... 🌊")
# 查找过滤器设备
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if 'filter' in node_class.lower() or 'filter' in node.lower():
debug_print(f"🎉 找到过滤器设备: {node}")
return node
# 如果没找到,寻找可能的过滤器名称
debug_print("🔎 在预定义名称中搜索过滤器... 📋")
possible_names = ["filter", "filter_1", "virtual_filter", "filtration_unit"]
for name in possible_names:
if name in G.nodes():
debug_print(f"🎉 找到过滤器设备: {name}")
return name
debug_print("😭 未找到过滤器设备 💔")
raise ValueError("未找到过滤器设备")
def validate_vessel(G: nx.DiGraph, vessel: str, vessel_type: str = "容器") -> None:
"""验证容器是否存在"""
debug_print(f"🔍 验证{vessel_type}: '{vessel}' 🧪")
if not vessel:
debug_print(f"{vessel_type}不能为空! 😱")
raise ValueError(f"{vessel_type}不能为空")
if vessel not in G.nodes():
debug_print(f"{vessel_type} '{vessel}' 不存在于系统中! 😞")
raise ValueError(f"{vessel_type} '{vessel}' 不存在于系统中")
debug_print(f"{vessel_type} '{vessel}' 验证通过 🎯")
def generate_filter_protocol(
G: nx.DiGraph,
vessel: str,
filtrate_vessel: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
生成过滤操作的协议序列
Args:
G: 设备图
vessel: 过滤容器名称(必需)- 包含需要过滤的混合物
filtrate_vessel: 滤液容器名称(可选)- 如果提供则收集滤液
**kwargs: 其他参数(兼容性)
Returns:
List[Dict[str, Any]]: 过滤操作的动作序列
"""
debug_print("🌊" * 20)
debug_print("🚀 开始生成过滤协议 ✨")
debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}")
debug_print(f" 🧪 filtrate_vessel: {filtrate_vessel}")
debug_print(f" ⚙️ 其他参数: {kwargs}")
debug_print("🌊" * 20)
action_sequence = []
# === 参数验证 ===
debug_print("📍 步骤1: 参数验证... 🔧")
# 验证必需参数
debug_print(" 🔍 验证必需参数...")
validate_vessel(G, vessel, "过滤容器")
debug_print(" ✅ 必需参数验证完成 🎯")
# 验证可选参数
debug_print(" 🔍 验证可选参数...")
if filtrate_vessel:
validate_vessel(G, filtrate_vessel, "滤液容器")
debug_print(" 🌊 模式: 过滤并收集滤液 💧")
else:
debug_print(" 🧱 模式: 过滤并收集固体 🔬")
debug_print(" ✅ 可选参数验证完成 🎯")
# === 查找设备 ===
debug_print("📍 步骤2: 查找设备... 🔍")
try:
debug_print(" 🔎 搜索过滤器设备...")
filter_device = find_filter_device(G)
debug_print(f" 🎉 使用过滤器设备: {filter_device} 🌊✨")
except Exception as e:
debug_print(f" ❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"设备查找失败: {str(e)}")
# === 转移到过滤器(如果需要)===
debug_print("📍 步骤3: 转移到过滤器... 🚚")
if vessel != filter_device:
debug_print(f" 🚛 需要转移: {vessel}{filter_device} 📦")
try:
debug_print(" 🔄 开始执行转移操作...")
# 使用pump protocol转移液体到过滤器
transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=vessel,
to_vessel=filter_device,
volume=0.0, # 转移所有液体
amount="",
time=0.0,
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.0,
transfer_flowrate=2.0
)
if transfer_actions:
action_sequence.extend(transfer_actions)
debug_print(f" ✅ 添加了 {len(transfer_actions)} 个转移动作 🚚✨")
else:
debug_print(" ⚠️ 转移协议返回空序列 🤔")
except Exception as e:
debug_print(f" ❌ 转移失败: {str(e)} 😞")
debug_print(" 🔄 继续执行,可能是直接连接的过滤器 🤞")
else:
debug_print(" ✅ 过滤容器就是过滤器,无需转移 🎯")
# === 执行过滤操作 ===
debug_print("📍 步骤4: 执行过滤操作... 🌊")
# 构建过滤动作参数
debug_print(" ⚙️ 构建过滤参数...")
filter_kwargs = {
"vessel": filter_device, # 过滤器设备
"filtrate_vessel": filtrate_vessel, # 滤液容器(可能为空)
"stir": kwargs.get("stir", False),
"stir_speed": kwargs.get("stir_speed", 0.0),
"temp": kwargs.get("temp", 25.0),
"continue_heatchill": kwargs.get("continue_heatchill", False),
"volume": kwargs.get("volume", 0.0) # 0表示过滤所有
}
debug_print(f" 📋 过滤参数: {filter_kwargs}")
debug_print(" 🌊 开始过滤操作...")
# 过滤动作
filter_action = {
"device_id": filter_device,
"action_name": "filter",
"action_kwargs": filter_kwargs
}
action_sequence.append(filter_action)
debug_print(" ✅ 过滤动作已添加 🌊✨")
# 过滤后等待
debug_print(" ⏳ 添加过滤后等待...")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10.0}
})
debug_print(" ✅ 过滤后等待动作已添加 ⏰✨")
# === 收集滤液(如果需要)===
debug_print("📍 步骤5: 收集滤液... 💧")
if filtrate_vessel:
debug_print(f" 🧪 收集滤液: {filter_device}{filtrate_vessel} 💧")
try:
debug_print(" 🔄 开始执行收集操作...")
# 使用pump protocol收集滤液
collect_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=filter_device,
to_vessel=filtrate_vessel,
volume=0.0, # 收集所有滤液
amount="",
time=0.0,
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.0,
transfer_flowrate=2.0
)
if collect_actions:
action_sequence.extend(collect_actions)
debug_print(f" ✅ 添加了 {len(collect_actions)} 个收集动作 🧪✨")
else:
debug_print(" ⚠️ 收集协议返回空序列 🤔")
except Exception as e:
debug_print(f" ❌ 收集滤液失败: {str(e)} 😞")
debug_print(" 🔄 继续执行,可能滤液直接流入指定容器 🤞")
else:
debug_print(" 🧱 未指定滤液容器,固体保留在过滤器中 🔬")
# === 最终等待 ===
debug_print("📍 步骤6: 最终等待... ⏰")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5.0}
})
debug_print(" ✅ 最终等待动作已添加 ⏰✨")
# === 总结 ===
debug_print("🎊" * 20)
debug_print(f"🎉 过滤协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝")
debug_print(f"🥽 过滤容器: {vessel} 🧪")
debug_print(f"🌊 过滤器设备: {filter_device} 🔧")
debug_print(f"💧 滤液容器: {filtrate_vessel or '无(保留固体)'} 🧱")
debug_print(f"⏱️ 预计总时间: {(len(action_sequence) * 5):.0f} 秒 ⌛")
debug_print("🎊" * 20)
return action_sequence

View File

@@ -0,0 +1,387 @@
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_filter_through_vessel(G: nx.DiGraph, filter_through: str) -> str:
"""查找过滤介质容器"""
# 直接使用 filter_through 参数作为容器名称
if filter_through in G.nodes():
return filter_through
# 尝试常见的过滤介质容器命名
possible_names = [
f"filter_{filter_through}",
f"{filter_through}_filter",
f"column_{filter_through}",
f"{filter_through}_column",
"filter_through_vessel",
"column_vessel",
"chromatography_column",
"filter_column"
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
return vessel_name
raise ValueError(f"未找到过滤介质容器 '{filter_through}'。尝试了以下名称: {[filter_through] + possible_names}")
def find_eluting_solvent_vessel(G: nx.DiGraph, eluting_solvent: str) -> str:
"""查找洗脱溶剂容器"""
if not eluting_solvent:
return ""
# 按照命名规则查找溶剂瓶
solvent_vessel_id = f"flask_{eluting_solvent}"
if solvent_vessel_id in G.nodes():
return solvent_vessel_id
# 如果直接匹配失败,尝试模糊匹配
for node in G.nodes():
if node.startswith('flask_') and eluting_solvent.lower() in node.lower():
return node
# 如果还是找不到,列出所有可用的溶剂瓶
available_flasks = [node for node in G.nodes()
if node.startswith('flask_')
and G.nodes[node].get('type') == 'container']
raise ValueError(f"找不到洗脱溶剂 '{eluting_solvent}' 对应的溶剂瓶。可用溶剂瓶: {available_flasks}")
def generate_filter_through_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
filter_through: str,
eluting_solvent: str = "",
eluting_volume: float = 0.0,
eluting_repeats: int = 0,
residence_time: float = 0.0
) -> List[Dict[str, Any]]:
"""
生成通过过滤介质过滤的协议序列,复用 pump_protocol 的成熟算法
过滤流程:
1. 液体转移:将样品从源容器转移到过滤介质
2. 重力过滤:液体通过过滤介质自动流到目标容器
3. 洗脱操作:将洗脱溶剂通过过滤介质洗脱目标物质
Args:
G: 有向图,节点为设备和容器,边为流体管道
from_vessel: 源容器的名称,即物质起始所在的容器
to_vessel: 目标容器的名称,物质过滤后要到达的容器
filter_through: 过滤时所通过的介质,如滤纸、柱子等
eluting_solvent: 洗脱溶剂的名称,可选参数
eluting_volume: 洗脱溶剂的体积,可选参数
eluting_repeats: 洗脱操作的重复次数,默认为 0
residence_time: 物质在过滤介质中的停留时间,可选参数
Returns:
List[Dict[str, Any]]: 过滤操作的动作序列
Raises:
ValueError: 当找不到必要的设备或容器时
Examples:
filter_through_actions = generate_filter_through_protocol(
G, "reaction_mixture", "collection_bottle_1", "celite", "ethanol", 20.0, 2, 30.0
)
"""
action_sequence = []
print(f"FILTER_THROUGH: 开始生成通过过滤协议")
print(f" - 源容器: {from_vessel}")
print(f" - 目标容器: {to_vessel}")
print(f" - 过滤介质: {filter_through}")
print(f" - 洗脱溶剂: {eluting_solvent}")
print(f" - 洗脱体积: {eluting_volume} mL" if eluting_volume > 0 else " - 洗脱体积: 无")
print(f" - 洗脱重复次数: {eluting_repeats}")
print(f" - 停留时间: {residence_time}s" if residence_time > 0 else " - 停留时间: 无")
# 验证源容器和目标容器存在
if from_vessel not in G.nodes():
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中")
if to_vessel not in G.nodes():
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中")
# 获取源容器中的液体体积
source_volume = get_vessel_liquid_volume(G, from_vessel)
print(f"FILTER_THROUGH: 源容器 {from_vessel} 中有 {source_volume} mL 液体")
# 查找过滤介质容器
try:
filter_through_vessel = find_filter_through_vessel(G, filter_through)
print(f"FILTER_THROUGH: 找到过滤介质容器: {filter_through_vessel}")
except ValueError as e:
raise ValueError(f"无法找到过滤介质容器: {str(e)}")
# 查找洗脱溶剂容器(如果需要)
eluting_vessel = ""
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0:
try:
eluting_vessel = find_eluting_solvent_vessel(G, eluting_solvent)
print(f"FILTER_THROUGH: 找到洗脱溶剂容器: {eluting_vessel}")
except ValueError as e:
raise ValueError(f"无法找到洗脱溶剂容器: {str(e)}")
# === 第一步:将样品从源容器转移到过滤介质 ===
transfer_volume = source_volume if source_volume > 0 else 100.0 # 默认100mL
print(f"FILTER_THROUGH: 将 {transfer_volume} mL 样品从 {from_vessel} 转移到 {filter_through_vessel}")
try:
# 使用成熟的 pump_protocol 算法进行液体转移
sample_transfer_actions = generate_pump_protocol(
G=G,
from_vessel=from_vessel,
to_vessel=filter_through_vessel,
volume=transfer_volume,
flowrate=0.8, # 较慢的流速,避免冲击过滤介质
transfer_flowrate=1.2
)
action_sequence.extend(sample_transfer_actions)
except Exception as e:
raise ValueError(f"无法将样品转移到过滤介质: {str(e)}")
# === 第二步:等待样品通过过滤介质(停留时间) ===
if residence_time > 0:
print(f"FILTER_THROUGH: 等待样品在过滤介质中停留 {residence_time}s")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": residence_time}
})
else:
# 即使没有指定停留时间,也等待一段时间让液体通过
default_wait_time = max(10, transfer_volume / 10) # 根据体积估算等待时间
print(f"FILTER_THROUGH: 等待样品通过过滤介质 {default_wait_time}s")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": default_wait_time}
})
# === 第三步:洗脱操作(如果指定了洗脱参数) ===
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel:
print(f"FILTER_THROUGH: 开始洗脱操作 - {eluting_repeats} 次,每次 {eluting_volume} mL {eluting_solvent}")
for repeat_idx in range(eluting_repeats):
print(f"FILTER_THROUGH: 第 {repeat_idx + 1}/{eluting_repeats} 次洗脱")
try:
# 将洗脱溶剂转移到过滤介质
eluting_transfer_actions = generate_pump_protocol(
G=G,
from_vessel=eluting_vessel,
to_vessel=filter_through_vessel,
volume=eluting_volume,
flowrate=0.6, # 洗脱用更慢的流速
transfer_flowrate=1.0
)
action_sequence.extend(eluting_transfer_actions)
except Exception as e:
raise ValueError(f"{repeat_idx + 1} 次洗脱转移失败: {str(e)}")
# 等待洗脱溶剂通过过滤介质
eluting_wait_time = max(30, eluting_volume / 5) # 根据洗脱体积估算等待时间
print(f"FILTER_THROUGH: 等待第 {repeat_idx + 1} 次洗脱液通过 {eluting_wait_time}s")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": eluting_wait_time}
})
# 洗脱间隔等待
if repeat_idx < eluting_repeats - 1: # 不是最后一次洗脱
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10}
})
# === 第四步:最终等待,确保所有液体完全通过 ===
print(f"FILTER_THROUGH: 最终等待,确保所有液体完全通过过滤介质")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 20}
})
print(f"FILTER_THROUGH: 生成了 {len(action_sequence)} 个动作")
print(f"FILTER_THROUGH: 通过过滤协议生成完成")
print(f"FILTER_THROUGH: 样品从 {from_vessel} 通过 {filter_through} 到达 {to_vessel}")
if eluting_repeats > 0:
total_eluting_volume = eluting_volume * eluting_repeats
print(f"FILTER_THROUGH: 总洗脱体积: {total_eluting_volume} mL {eluting_solvent}")
return action_sequence
# 便捷函数:常用过滤方案
def generate_gravity_column_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column_material: str = "silica_gel"
) -> List[Dict[str, Any]]:
"""重力柱层析:简单重力过滤,无洗脱"""
return generate_filter_through_protocol(G, from_vessel, to_vessel, column_material, "", 0.0, 0, 0.0)
def generate_celite_filter_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
wash_solvent: str = "ethanol",
wash_volume: float = 20.0
) -> List[Dict[str, Any]]:
"""硅藻土过滤:用于去除固体杂质"""
return generate_filter_through_protocol(G, from_vessel, to_vessel, "celite", wash_solvent, wash_volume, 1, 30.0)
def generate_column_chromatography_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column_material: str = "silica_gel",
eluting_solvent: str = "ethyl_acetate",
eluting_volume: float = 30.0,
eluting_repeats: int = 3
) -> List[Dict[str, Any]]:
"""柱层析:多次洗脱分离"""
return generate_filter_through_protocol(
G, from_vessel, to_vessel, column_material, eluting_solvent, eluting_volume, eluting_repeats, 60.0
)
def generate_solid_phase_extraction_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
spe_cartridge: str = "C18",
eluting_solvent: str = "methanol",
eluting_volume: float = 15.0,
eluting_repeats: int = 2
) -> List[Dict[str, Any]]:
"""固相萃取C18柱或其他SPE柱"""
return generate_filter_through_protocol(
G, from_vessel, to_vessel, spe_cartridge, eluting_solvent, eluting_volume, eluting_repeats, 120.0
)
def generate_resin_filter_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
resin_type: str = "ion_exchange",
regeneration_solvent: str = "NaCl_solution",
regeneration_volume: float = 25.0
) -> List[Dict[str, Any]]:
"""树脂过滤:离子交换树脂或其他功能树脂"""
return generate_filter_through_protocol(
G, from_vessel, to_vessel, resin_type, regeneration_solvent, regeneration_volume, 1, 180.0
)
def generate_multi_step_purification_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
filter_steps: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""
多步骤纯化:连续多个过滤介质
Args:
G: 网络图
from_vessel: 源容器
to_vessel: 最终目标容器
filter_steps: 过滤步骤列表,每个元素包含过滤参数
Returns:
List[Dict[str, Any]]: 完整的动作序列
Example:
filter_steps = [
{
"to_vessel": "intermediate_vessel_1",
"filter_through": "celite",
"eluting_solvent": "",
"eluting_volume": 0.0,
"eluting_repeats": 0,
"residence_time": 30.0
},
{
"from_vessel": "intermediate_vessel_1",
"to_vessel": "final_vessel",
"filter_through": "silica_gel",
"eluting_solvent": "ethyl_acetate",
"eluting_volume": 20.0,
"eluting_repeats": 2,
"residence_time": 60.0
}
]
"""
action_sequence = []
current_from_vessel = from_vessel
for i, step in enumerate(filter_steps):
print(f"FILTER_THROUGH: 处理第 {i+1}/{len(filter_steps)} 个过滤步骤")
# 使用步骤中指定的参数,或使用默认值
step_from_vessel = step.get('from_vessel', current_from_vessel)
step_to_vessel = step.get('to_vessel', to_vessel if i == len(filter_steps) - 1 else f"intermediate_vessel_{i+1}")
# 生成单个过滤步骤的协议
step_actions = generate_filter_through_protocol(
G=G,
from_vessel=step_from_vessel,
to_vessel=step_to_vessel,
filter_through=step.get('filter_through', 'silica_gel'),
eluting_solvent=step.get('eluting_solvent', ''),
eluting_volume=step.get('eluting_volume', 0.0),
eluting_repeats=step.get('eluting_repeats', 0),
residence_time=step.get('residence_time', 0.0)
)
action_sequence.extend(step_actions)
# 更新下一步的源容器
current_from_vessel = step_to_vessel
# 在步骤之间加入等待时间
if i < len(filter_steps) - 1: # 不是最后一个步骤
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 15}
})
print(f"FILTER_THROUGH: 多步骤纯化协议生成完成,共 {len(action_sequence)} 个动作")
return action_sequence
# 测试函数
def test_filter_through_protocol():
"""测试通过过滤协议的示例"""
print("=== FILTER THROUGH PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_filter_through_protocol()

View File

@@ -0,0 +1,376 @@
from typing import List, Dict, Any, Union
import networkx as nx
import logging
import re
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"🌡️ [HEATCHILL] {message}", flush=True)
logger.info(f"[HEATCHILL] {message}")
def parse_time_input(time_input: Union[str, float, int]) -> float:
"""
解析时间输入(统一函数)
Args:
time_input: 时间输入(如 "30 min", "1 h", "300", "?", 60.0
Returns:
float: 时间(秒)
"""
if not time_input:
return 300.0
# 🔢 处理数值输入
if isinstance(time_input, (int, float)):
result = float(time_input)
debug_print(f"⏰ 数值时间: {time_input}{result}s")
return result
# 📝 处理字符串输入
time_str = str(time_input).lower().strip()
debug_print(f"🔍 解析时间: '{time_str}'")
# ❓ 特殊值处理
special_times = {
'?': 300.0, 'unknown': 300.0, 'tbd': 300.0,
'overnight': 43200.0, 'several hours': 10800.0,
'few hours': 7200.0, 'long time': 3600.0, 'short time': 300.0
}
if time_str in special_times:
result = special_times[time_str]
debug_print(f"🎯 特殊时间: '{time_str}'{result}s ({result/60:.1f}分钟)")
return result
# 🔢 纯数字处理
try:
result = float(time_str)
debug_print(f"⏰ 纯数字: {time_str}{result}s")
return result
except ValueError:
pass
# 📐 正则表达式解析
pattern = r'(\d+\.?\d*)\s*([a-z]*)'
match = re.match(pattern, time_str)
if not match:
debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值: 300s")
return 300.0
value = float(match.group(1))
unit = match.group(2) or 's'
# 📏 单位转换
unit_multipliers = {
's': 1.0, 'sec': 1.0, 'second': 1.0, 'seconds': 1.0,
'm': 60.0, 'min': 60.0, 'mins': 60.0, 'minute': 60.0, 'minutes': 60.0,
'h': 3600.0, 'hr': 3600.0, 'hrs': 3600.0, 'hour': 3600.0, 'hours': 3600.0,
'd': 86400.0, 'day': 86400.0, 'days': 86400.0
}
multiplier = unit_multipliers.get(unit, 1.0)
result = value * multiplier
debug_print(f"✅ 时间解析: '{time_str}'{value} {unit}{result}s ({result/60:.1f}分钟)")
return result
def parse_temp_input(temp_input: Union[str, float], default_temp: float = 25.0) -> float:
"""
解析温度输入(统一函数)
Args:
temp_input: 温度输入
default_temp: 默认温度
Returns:
float: 温度°C
"""
if not temp_input:
return default_temp
# 🔢 数值输入
if isinstance(temp_input, (int, float)):
result = float(temp_input)
debug_print(f"🌡️ 数值温度: {temp_input}{result}°C")
return result
# 📝 字符串输入
temp_str = str(temp_input).lower().strip()
debug_print(f"🔍 解析温度: '{temp_str}'")
# 🎯 特殊温度
special_temps = {
"room temperature": 25.0, "reflux": 78.0, "ice bath": 0.0,
"boiling": 100.0, "hot": 60.0, "warm": 40.0, "cold": 10.0
}
if temp_str in special_temps:
result = special_temps[temp_str]
debug_print(f"🎯 特殊温度: '{temp_str}'{result}°C")
return result
# 📐 正则解析(如 "256 °C"
temp_pattern = r'(\d+(?:\.\d+)?)\s*°?[cf]?'
match = re.search(temp_pattern, temp_str)
if match:
result = float(match.group(1))
debug_print(f"✅ 温度解析: '{temp_str}'{result}°C")
return result
debug_print(f"⚠️ 无法解析温度: '{temp_str}',使用默认值: {default_temp}°C")
return default_temp
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
"""查找与指定容器相连的加热/冷却设备"""
debug_print(f"🔍 查找加热设备,目标容器: {vessel}")
# 🔧 查找所有加热设备
heatchill_nodes = []
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if 'heatchill' in node_class.lower() or 'virtual_heatchill' in node_class:
heatchill_nodes.append(node)
debug_print(f"🎉 找到加热设备: {node}")
# 🔗 检查连接
if vessel and heatchill_nodes:
for heatchill in heatchill_nodes:
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
debug_print(f"✅ 加热设备 '{heatchill}' 与容器 '{vessel}' 相连")
return heatchill
# 🎯 使用第一个可用设备
if heatchill_nodes:
selected = heatchill_nodes[0]
debug_print(f"🔧 使用第一个加热设备: {selected}")
return selected
# 🆘 默认设备
debug_print("⚠️ 未找到加热设备,使用默认设备")
return "heatchill_1"
def validate_and_fix_params(temp: float, time: float, stir_speed: float) -> tuple:
"""验证和修正参数"""
# 🌡️ 温度范围验证
if temp < -50.0 or temp > 300.0:
debug_print(f"⚠️ 温度 {temp}°C 超出范围,修正为 25°C")
temp = 25.0
else:
debug_print(f"✅ 温度 {temp}°C 在正常范围内")
# ⏰ 时间验证
if time < 0:
debug_print(f"⚠️ 时间 {time}s 无效,修正为 300s")
time = 300.0
else:
debug_print(f"✅ 时间 {time}s ({time/60:.1f}分钟) 有效")
# 🌪️ 搅拌速度验证
if stir_speed < 0 or stir_speed > 1500.0:
debug_print(f"⚠️ 搅拌速度 {stir_speed} RPM 超出范围,修正为 300 RPM")
stir_speed = 300.0
else:
debug_print(f"✅ 搅拌速度 {stir_speed} RPM 在正常范围内")
return temp, time, stir_speed
def generate_heat_chill_protocol(
G: nx.DiGraph,
vessel: str,
temp: float = 25.0,
time: Union[str, float] = "300",
temp_spec: str = "",
time_spec: str = "",
pressure: str = "",
reflux_solvent: str = "",
stir: bool = False,
stir_speed: float = 300.0,
purpose: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
生成加热/冷却操作的协议序列
"""
debug_print("🌡️" * 20)
debug_print("🚀 开始生成加热冷却协议 ✨")
debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}")
debug_print(f" 🌡️ temp: {temp}°C")
debug_print(f" ⏰ time: {time}")
debug_print(f" 🎯 temp_spec: {temp_spec}")
debug_print(f" ⏱️ time_spec: {time_spec}")
debug_print(f" 🌪️ stir: {stir} ({stir_speed} RPM)")
debug_print("🌡️" * 20)
# 📋 参数验证
debug_print("📍 步骤1: 参数验证... 🔧")
if not vessel:
debug_print("❌ vessel 参数不能为空! 😱")
raise ValueError("vessel 参数不能为空")
if vessel not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中! 😞")
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
debug_print("✅ 基础参数验证通过 🎯")
# 🔄 参数解析
debug_print("📍 步骤2: 参数解析... ⚡")
#温度解析:优先使用 temp_spec
final_temp = parse_temp_input(temp_spec, temp) if temp_spec else temp
# 时间解析:优先使用 time_spec
final_time = parse_time_input(time_spec) if time_spec else parse_time_input(time)
# 参数修正
final_temp, final_time, stir_speed = validate_and_fix_params(final_temp, final_time, stir_speed)
debug_print(f"🎯 最终参数: temp={final_temp}°C, time={final_time}s, stir_speed={stir_speed} RPM")
# 🔍 查找设备
debug_print("📍 步骤3: 查找加热设备... 🔍")
try:
heatchill_id = find_connected_heatchill(G, vessel)
debug_print(f"🎉 使用加热设备: {heatchill_id}")
except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"无法找到加热设备: {str(e)}")
# 🚀 生成动作
debug_print("📍 步骤4: 生成加热动作... 🔥")
# 🕐 模拟运行时间优化
debug_print(" ⏱️ 检查模拟运行时间限制...")
original_time = final_time
simulation_time_limit = 100.0 # 模拟运行时间限制100秒
if final_time > simulation_time_limit:
final_time = simulation_time_limit
debug_print(f" 🎮 模拟运行优化: {original_time}s → {final_time}s (限制为{simulation_time_limit}s) ⚡")
debug_print(f" 📊 时间缩短: {original_time/60:.1f}分钟 → {final_time/60:.1f}分钟 🚀")
else:
debug_print(f" ✅ 时间在限制内: {final_time}s ({final_time/60:.1f}分钟) 保持不变 🎯")
action_sequence = []
heatchill_action = {
"device_id": heatchill_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": float(final_temp),
"time": float(final_time),
"stir": bool(stir),
"stir_speed": float(stir_speed),
"purpose": str(purpose or f"加热到 {final_temp}°C") + (f" (模拟时间: {final_time}s)" if original_time != final_time else "")
}
}
action_sequence.append(heatchill_action)
debug_print("✅ 加热动作已添加 🔥✨")
# 显示时间调整信息
if original_time != final_time:
debug_print(f" 🎭 模拟优化说明: 原计划 {original_time/60:.1f}分钟,实际模拟 {final_time/60:.1f}分钟 ⚡")
# 🎊 总结
debug_print("🎊" * 20)
debug_print(f"🎉 加热冷却协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 加热容器: {vessel}")
debug_print(f"🌡️ 目标温度: {final_temp}°C")
debug_print(f"⏰ 加热时间: {final_time}s ({final_time/60:.1f}分钟)")
debug_print("🎊" * 20)
return action_sequence
def generate_heat_chill_to_temp_protocol(
G: nx.DiGraph,
vessel: str,
temp: float = 25.0,
time: Union[str, float] = 100.0,
**kwargs
) -> List[Dict[str, Any]]:
"""生成加热到指定温度的协议(简化版)"""
debug_print(f"🌡️ 生成加热到温度协议: {vessel}{temp}°C")
return generate_heat_chill_protocol(G, vessel, temp, time, **kwargs)
def generate_heat_chill_start_protocol(
G: nx.DiGraph,
vessel: str,
temp: float = 25.0,
purpose: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""生成开始加热操作的协议序列"""
debug_print("🔥 开始生成启动加热协议 ✨")
debug_print(f"🥽 vessel: {vessel}, 🌡️ temp: {temp}°C")
# 基础验证
if not vessel or vessel not in G.nodes():
debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效")
# 查找设备
heatchill_id = find_connected_heatchill(G, vessel)
# 生成动作
action_sequence = [{
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": purpose or f"开始加热到 {temp}°C"
}
}]
debug_print(f"✅ 启动加热协议生成完成 🎯")
return action_sequence
def generate_heat_chill_stop_protocol(
G: nx.DiGraph,
vessel: str,
**kwargs
) -> List[Dict[str, Any]]:
"""生成停止加热操作的协议序列"""
debug_print("🛑 开始生成停止加热协议 ✨")
debug_print(f"🥽 vessel: {vessel}")
# 基础验证
if not vessel or vessel not in G.nodes():
debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效")
# 查找设备
heatchill_id = find_connected_heatchill(G, vessel)
# 生成动作
action_sequence = [{
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
}]
debug_print(f"✅ 停止加热协议生成完成 🎯")
return action_sequence
# 测试函数
def test_heatchill_protocol():
"""测试加热协议"""
debug_print("🧪 === HEATCHILL PROTOCOL 测试 === ✨")
debug_print("✅ 测试完成 🎉")
if __name__ == "__main__":
test_heatchill_protocol()

View File

@@ -0,0 +1,400 @@
import networkx as nx
from typing import List, Dict, Any, Optional
def parse_temperature(temp_str: str) -> float:
"""
解析温度字符串,支持多种格式
Args:
temp_str: 温度字符串(如 "45 °C", "45°C", "45"
Returns:
float: 温度值(摄氏度)
"""
try:
# 移除常见的温度单位和符号
temp_clean = temp_str.replace("°C", "").replace("°", "").replace("C", "").strip()
return float(temp_clean)
except ValueError:
print(f"HYDROGENATE: 无法解析温度 '{temp_str}',使用默认温度 25°C")
return 25.0
def parse_time(time_str: str) -> float:
"""
解析时间字符串,支持多种格式
Args:
time_str: 时间字符串(如 "2 h", "120 min", "7200 s"
Returns:
float: 时间值(秒)
"""
try:
time_clean = time_str.lower().strip()
# 处理小时
if "h" in time_clean:
hours = float(time_clean.replace("h", "").strip())
return hours * 3600.0
# 处理分钟
if "min" in time_clean:
minutes = float(time_clean.replace("min", "").strip())
return minutes * 60.0
# 处理秒
if "s" in time_clean:
seconds = float(time_clean.replace("s", "").strip())
return seconds
# 默认按小时处理
return float(time_clean) * 3600.0
except ValueError:
print(f"HYDROGENATE: 无法解析时间 '{time_str}',使用默认时间 2小时")
return 7200.0 # 2小时
def find_associated_solenoid_valve(G: nx.DiGraph, device_id: str) -> Optional[str]:
"""查找与指定设备相关联的电磁阀"""
solenoid_valves = [
node for node in G.nodes()
if ('solenoid' in (G.nodes[node].get('class') or '').lower()
or 'solenoid_valve' in node)
]
# 通过网络连接查找直接相连的电磁阀
for solenoid in solenoid_valves:
if G.has_edge(device_id, solenoid) or G.has_edge(solenoid, device_id):
return solenoid
# 通过命名规则查找关联的电磁阀
device_type = ""
if 'gas' in device_id.lower():
device_type = "gas"
elif 'h2' in device_id.lower() or 'hydrogen' in device_id.lower():
device_type = "gas"
if device_type:
for solenoid in solenoid_valves:
if device_type in solenoid.lower():
return solenoid
return None
def find_connected_device(G: nx.DiGraph, vessel: str, device_type: str) -> str:
"""
查找与容器相连的指定类型设备
Args:
G: 网络图
vessel: 容器名称
device_type: 设备类型 ('heater', 'stirrer', 'gas_source')
Returns:
str: 设备ID如果没有则返回None
"""
print(f"HYDROGENATE: 正在查找与容器 '{vessel}' 相连的 {device_type}...")
# 根据设备类型定义搜索关键词
if device_type == 'heater':
keywords = ['heater', 'heat', 'heatchill']
device_class = 'virtual_heatchill'
elif device_type == 'stirrer':
keywords = ['stirrer', 'stir']
device_class = 'virtual_stirrer'
elif device_type == 'gas_source':
keywords = ['gas', 'h2', 'hydrogen']
device_class = 'virtual_gas_source'
else:
return None
# 查找设备节点
device_nodes = []
for node in G.nodes():
node_data = G.nodes[node]
node_name = node.lower()
node_class = node_data.get('class', '').lower()
# 通过名称匹配
if any(keyword in node_name for keyword in keywords):
device_nodes.append(node)
# 通过类型匹配
elif device_class in node_class:
device_nodes.append(node)
print(f"HYDROGENATE: 找到的{device_type}节点: {device_nodes}")
# 检查是否有设备与目标容器相连
for device in device_nodes:
if G.has_edge(device, vessel) or G.has_edge(vessel, device):
print(f"HYDROGENATE: 找到与容器 '{vessel}' 相连的{device_type}: {device}")
return device
# 如果没有直接连接,查找距离最近的设备
for device in device_nodes:
try:
path = nx.shortest_path(G, source=device, target=vessel)
if len(path) <= 3: # 最多2个中间节点
print(f"HYDROGENATE: 找到距离较近的{device_type}: {device}")
return device
except nx.NetworkXNoPath:
continue
print(f"HYDROGENATE: 未找到与容器 '{vessel}' 相连的{device_type}")
return None
def generate_hydrogenate_protocol(
G: nx.DiGraph,
temp: str,
time: str,
vessel: str,
**kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]:
"""
生成氢化反应协议序列
Args:
G: 有向图,节点为容器和设备
temp: 反应温度(如 "45 °C"
time: 反应时间(如 "2 h"
vessel: 反应容器
**kwargs: 其他可选参数,但不使用
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
# 解析参数
temperature = parse_temperature(temp)
reaction_time = parse_time(time)
print(f"HYDROGENATE: 开始生成氢化反应协议")
print(f" - 反应温度: {temperature}°C")
print(f" - 反应时间: {reaction_time/3600:.1f} 小时")
print(f" - 反应容器: {vessel}")
# 1. 验证目标容器存在
if vessel not in G.nodes():
print(f"HYDROGENATE: 警告 - 容器 '{vessel}' 不存在于系统中,跳过氢化反应")
return action_sequence
# 2. 查找相连的设备
heater_id = find_connected_device(G, vessel, 'heater')
stirrer_id = find_connected_device(G, vessel, 'stirrer')
gas_source_id = find_connected_device(G, vessel, 'gas_source')
# 3. 启动搅拌器
if stirrer_id:
print(f"HYDROGENATE: 启动搅拌器 {stirrer_id}")
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": 300.0,
"purpose": "氢化反应: 开始搅拌"
}
})
else:
print(f"HYDROGENATE: 警告 - 未找到搅拌器,继续执行")
# 4. 启动气源(氢气)- 修复版本
if gas_source_id:
print(f"HYDROGENATE: 启动气源 {gas_source_id} (氢气)")
action_sequence.append({
"device_id": gas_source_id,
"action_name": "set_status", # 修改为 set_status
"action_kwargs": {
"string": "ON" # 修改参数格式
}
})
# 查找相关的电磁阀
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
if gas_solenoid:
print(f"HYDROGENATE: 开启气源电磁阀 {gas_solenoid}")
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {
"command": "OPEN"
}
})
else:
print(f"HYDROGENATE: 警告 - 未找到气源,继续执行")
# 5. 等待气体稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 30.0,
"description": "等待氢气环境稳定"
}
})
# 6. 启动加热器
if heater_id:
print(f"HYDROGENATE: 启动加热器 {heater_id}{temperature}°C")
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temperature,
"purpose": f"氢化反应: 加热到 {temperature}°C"
}
})
# 等待温度稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 20.0,
"description": f"等待温度稳定到 {temperature}°C"
}
})
# 🕐 模拟运行时间优化
print("HYDROGENATE: 检查模拟运行时间限制...")
original_reaction_time = reaction_time
simulation_time_limit = 60.0 # 模拟运行时间限制60秒
if reaction_time > simulation_time_limit:
reaction_time = simulation_time_limit
print(f"HYDROGENATE: 模拟运行优化: {original_reaction_time}s → {reaction_time}s (限制为{simulation_time_limit}s)")
print(f"HYDROGENATE: 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟")
else:
print(f"HYDROGENATE: 时间在限制内: {reaction_time}s ({reaction_time/60:.1f}分钟) 保持不变")
# 保持反应温度
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": temperature,
"time": reaction_time,
"purpose": f"氢化反应: 保持 {temperature}°C反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "")
}
})
# 显示时间调整信息
if original_reaction_time != reaction_time:
print(f"HYDROGENATE: 模拟优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟")
else:
print(f"HYDROGENATE: 警告 - 未找到加热器,使用室温反应")
# 🕐 室温反应也需要时间优化
print("HYDROGENATE: 检查室温反应模拟时间限制...")
original_reaction_time = reaction_time
simulation_time_limit = 60.0 # 模拟运行时间限制60秒
if reaction_time > simulation_time_limit:
reaction_time = simulation_time_limit
print(f"HYDROGENATE: 室温反应时间优化: {original_reaction_time}s → {reaction_time}s")
print(f"HYDROGENATE: 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟")
else:
print(f"HYDROGENATE: 室温反应时间在限制内: {reaction_time}s 保持不变")
# 室温反应,只等待时间
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": reaction_time,
"description": f"室温氢化反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "")
}
})
# 显示时间调整信息
if original_reaction_time != reaction_time:
print(f"HYDROGENATE: 室温反应优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟")
# 7. 停止加热
if heater_id:
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel,
"purpose": "氢化反应完成,停止加热"
}
})
# 8. 等待冷却
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 300.0,
"description": "等待反应混合物冷却"
}
})
# 9. 停止气源 - 修复版本
if gas_source_id:
# 先关闭电磁阀
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
if gas_solenoid:
print(f"HYDROGENATE: 关闭气源电磁阀 {gas_solenoid}")
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {
"command": "CLOSED"
}
})
# 再关闭气源
action_sequence.append({
"device_id": gas_source_id,
"action_name": "set_status", # 修改为 set_status
"action_kwargs": {
"string": "OFF" # 修改参数格式
}
})
# 10. 停止搅拌
if stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel,
"purpose": "氢化反应完成,停止搅拌"
}
})
print(f"HYDROGENATE: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"HYDROGENATE: 预计总时间: {(reaction_time + 450)/3600:.1f} 小时")
return action_sequence
# 测试函数
def test_hydrogenate_protocol():
"""测试氢化反应协议"""
print("=== HYDROGENATE PROTOCOL 测试 ===")
# 测试温度解析
test_temps = ["45 °C", "45°C", "45", "25 C", "invalid"]
for temp in test_temps:
parsed = parse_temperature(temp)
print(f"温度 '{temp}' -> {parsed}°C")
# 测试时间解析
test_times = ["2 h", "120 min", "7200 s", "2", "invalid"]
for time in test_times:
parsed = parse_time(time)
print(f"时间 '{time}' -> {parsed/3600:.1f} 小时")
print("测试完成")
if __name__ == "__main__":
test_hydrogenate_protocol()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,435 @@
import networkx as nx
import re
from typing import List, Dict, Any, Tuple, Union
from .pump_protocol import generate_pump_protocol_with_rinsing
def debug_print(message):
"""调试输出"""
print(f"💎 [RECRYSTALLIZE] {message}", flush=True)
def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: str = "mL") -> float:
"""
解析带单位的体积输入
Args:
volume_input: 体积输入(如 "100 mL", "2.5 L", "500", "?", 100.0
default_unit: 默认单位(默认为毫升)
Returns:
float: 体积(毫升)
"""
if not volume_input:
debug_print("⚠️ 体积输入为空,返回 0.0mL 📦")
return 0.0
# 处理数值输入
if isinstance(volume_input, (int, float)):
result = float(volume_input)
debug_print(f"🔢 数值体积输入: {volume_input}{result}mL默认单位💧")
return result
# 处理字符串输入
volume_str = str(volume_input).lower().strip()
debug_print(f"🔍 解析体积字符串: '{volume_str}' 📝")
# 处理特殊值
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_volume = 50.0 # 50mL默认值
debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL 🎯")
return default_volume
# 如果是纯数字,使用默认单位
try:
value = float(volume_str)
if default_unit.lower() in ["ml", "milliliter"]:
result = value
elif default_unit.lower() in ["l", "liter"]:
result = value * 1000.0
elif default_unit.lower() in ["μl", "ul", "microliter"]:
result = value / 1000.0
else:
result = value # 默认mL
debug_print(f"🔢 纯数字输入: {volume_str}{result}mL单位: {default_unit})📏")
return result
except ValueError:
pass
# 移除空格并提取数字和单位
volume_clean = re.sub(r'\s+', '', volume_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
if not match:
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值: 50mL 🎯")
return 50.0
value = float(match.group(1))
unit = match.group(2) or default_unit.lower()
# 转换为毫升
if unit in ['l', 'liter']:
volume = value * 1000.0 # L -> mL
debug_print(f"📏 升转毫升: {value}L → {volume}mL 💧")
elif unit in ['μl', 'ul', 'microliter']:
volume = value / 1000.0 # μL -> mL
debug_print(f"📏 微升转毫升: {value}μL → {volume}mL 💧")
else: # ml, milliliter 或默认
volume = value # 已经是mL
debug_print(f"📏 毫升单位: {value}mL → {volume}mL 💧")
debug_print(f"✅ 体积解析完成: '{volume_str}'{volume}mL ✨")
return volume
def parse_ratio(ratio_str: str) -> Tuple[float, float]:
"""
解析比例字符串,支持多种格式
Args:
ratio_str: 比例字符串(如 "1:1", "3:7", "50:50"
Returns:
Tuple[float, float]: 比例元组 (ratio1, ratio2)
"""
debug_print(f"⚖️ 开始解析比例: '{ratio_str}' 📊")
try:
# 处理 "1:1", "3:7", "50:50" 等格式
if ":" in ratio_str:
parts = ratio_str.split(":")
if len(parts) == 2:
ratio1 = float(parts[0])
ratio2 = float(parts[1])
debug_print(f"✅ 冒号格式解析成功: {ratio1}:{ratio2} 🎯")
return ratio1, ratio2
# 处理 "1-1", "3-7" 等格式
if "-" in ratio_str:
parts = ratio_str.split("-")
if len(parts) == 2:
ratio1 = float(parts[0])
ratio2 = float(parts[1])
debug_print(f"✅ 横线格式解析成功: {ratio1}:{ratio2} 🎯")
return ratio1, ratio2
# 处理 "1,1", "3,7" 等格式
if "," in ratio_str:
parts = ratio_str.split(",")
if len(parts) == 2:
ratio1 = float(parts[0])
ratio2 = float(parts[1])
debug_print(f"✅ 逗号格式解析成功: {ratio1}:{ratio2} 🎯")
return ratio1, ratio2
# 默认 1:1
debug_print(f"⚠️ 无法解析比例 '{ratio_str}',使用默认比例 1:1 🎭")
return 1.0, 1.0
except ValueError:
debug_print(f"❌ 比例解析错误 '{ratio_str}',使用默认比例 1:1 🎭")
return 1.0, 1.0
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""
查找溶剂容器
Args:
G: 网络图
solvent: 溶剂名称
Returns:
str: 溶剂容器ID
"""
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器... 🧪")
# 构建可能的容器名称
possible_names = [
f"flask_{solvent}",
f"bottle_{solvent}",
f"reagent_{solvent}",
f"reagent_bottle_{solvent}",
f"{solvent}_flask",
f"{solvent}_bottle",
f"{solvent}",
f"vessel_{solvent}",
]
debug_print(f"📋 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个) 📝")
# 第一步:通过容器名称匹配
debug_print(" 🎯 步骤1: 精确名称匹配...")
for vessel_name in possible_names:
if vessel_name in G.nodes():
debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name}")
return vessel_name
# 第二步:通过模糊匹配
debug_print(" 🔍 步骤2: 模糊名称匹配...")
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
node_name = G.nodes[node_id].get('name', '').lower()
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id}")
return node_id
# 第三步:通过液体类型匹配
debug_print(" 🧪 步骤3: 液体类型匹配...")
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
reagent_name = vessel_data.get('reagent_name', '').lower()
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id}")
return node_id
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭")
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
def generate_recrystallize_protocol(
G: nx.DiGraph,
ratio: str,
solvent1: str,
solvent2: str,
vessel: str,
volume: Union[str, float], # 🔧 修改:支持字符串和数值
**kwargs
) -> List[Dict[str, Any]]:
"""
生成重结晶协议序列 - 支持单位
Args:
G: 有向图,节点为容器和设备
ratio: 溶剂比例(如 "1:1", "3:7"
solvent1: 第一种溶剂名称
solvent2: 第二种溶剂名称
vessel: 目标容器
volume: 总体积(支持 "100 mL", "50", "2.5 L" 等)
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
debug_print("💎" * 20)
debug_print("🚀 开始生成重结晶协议(支持单位)✨")
debug_print(f"📝 输入参数:")
debug_print(f" ⚖️ 比例: {ratio}")
debug_print(f" 🧪 溶剂1: {solvent1}")
debug_print(f" 🧪 溶剂2: {solvent2}")
debug_print(f" 🥽 容器: {vessel}")
debug_print(f" 💧 总体积: {volume} (类型: {type(volume)})")
debug_print("💎" * 20)
# 1. 验证目标容器存在
debug_print("📍 步骤1: 验证目标容器... 🔧")
if vessel not in G.nodes():
debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中! 😱")
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
debug_print(f"✅ 目标容器 '{vessel}' 验证通过 🎯")
# 2. 🔧 新增:解析体积(支持单位)
debug_print("📍 步骤2: 解析体积(支持单位)... 💧")
final_volume = parse_volume_with_units(volume, "mL")
debug_print(f"🎯 体积解析完成: {volume}{final_volume}mL ✨")
# 3. 解析比例
debug_print("📍 步骤3: 解析比例... ⚖️")
ratio1, ratio2 = parse_ratio(ratio)
total_ratio = ratio1 + ratio2
debug_print(f"🎯 比例解析完成: {ratio1}:{ratio2} (总比例: {total_ratio}) ✨")
# 4. 计算各溶剂体积
debug_print("📍 步骤4: 计算各溶剂体积... 🧮")
volume1 = final_volume * (ratio1 / total_ratio)
volume2 = final_volume * (ratio2 / total_ratio)
debug_print(f"🧪 {solvent1} 体积: {volume1:.2f} mL ({ratio1}/{total_ratio} × {final_volume})")
debug_print(f"🧪 {solvent2} 体积: {volume2:.2f} mL ({ratio2}/{total_ratio} × {final_volume})")
debug_print(f"✅ 体积计算完成: 总计 {volume1 + volume2:.2f} mL 🎯")
# 5. 查找溶剂容器
debug_print("📍 步骤5: 查找溶剂容器... 🔍")
try:
debug_print(f" 🔍 查找溶剂1容器...")
solvent1_vessel = find_solvent_vessel(G, solvent1)
debug_print(f" 🎉 找到溶剂1容器: {solvent1_vessel}")
except ValueError as e:
debug_print(f" ❌ 溶剂1容器查找失败: {str(e)} 😭")
raise ValueError(f"无法找到溶剂1 '{solvent1}': {str(e)}")
try:
debug_print(f" 🔍 查找溶剂2容器...")
solvent2_vessel = find_solvent_vessel(G, solvent2)
debug_print(f" 🎉 找到溶剂2容器: {solvent2_vessel}")
except ValueError as e:
debug_print(f" ❌ 溶剂2容器查找失败: {str(e)} 😭")
raise ValueError(f"无法找到溶剂2 '{solvent2}': {str(e)}")
# 6. 验证路径存在
debug_print("📍 步骤6: 验证传输路径... 🛤️")
try:
path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel)
debug_print(f" 🛤️ 溶剂1路径: {''.join(path1)}")
except nx.NetworkXNoPath:
debug_print(f" ❌ 溶剂1路径不可达: {solvent1_vessel}{vessel} 😞")
raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel}' 没有可用路径")
try:
path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel)
debug_print(f" 🛤️ 溶剂2路径: {''.join(path2)}")
except nx.NetworkXNoPath:
debug_print(f" ❌ 溶剂2路径不可达: {solvent2_vessel}{vessel} 😞")
raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel}' 没有可用路径")
# 7. 添加第一种溶剂
debug_print("📍 步骤7: 添加第一种溶剂... 🧪")
debug_print(f" 🚰 开始添加溶剂1: {solvent1} ({volume1:.2f} mL)")
try:
pump_actions1 = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent1_vessel,
to_vessel=vessel,
volume=volume1, # 使用解析后的体积
amount="",
time=0.0,
viscous=False,
rinsing_solvent="", # 重结晶不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.0, # 正常流速
transfer_flowrate=0.5
)
action_sequence.extend(pump_actions1)
debug_print(f" ✅ 溶剂1泵送动作已添加: {len(pump_actions1)} 个动作 🚰✨")
except Exception as e:
debug_print(f" ❌ 溶剂1泵协议生成失败: {str(e)} 😭")
raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}")
# 8. 等待溶剂1稳定
debug_print(" ⏳ 添加溶剂1稳定等待...")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 5.0, # 🕐 缩短等待时间10.0s → 5.0s
"description": f"等待溶剂1 {solvent1} 稳定"
}
})
debug_print(" ✅ 溶剂1稳定等待已添加 ⏰✨")
# 9. 添加第二种溶剂
debug_print("📍 步骤8: 添加第二种溶剂... 🧪")
debug_print(f" 🚰 开始添加溶剂2: {solvent2} ({volume2:.2f} mL)")
try:
pump_actions2 = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent2_vessel,
to_vessel=vessel,
volume=volume2, # 使用解析后的体积
amount="",
time=0.0,
viscous=False,
rinsing_solvent="", # 重结晶不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.0, # 正常流速
transfer_flowrate=0.5
)
action_sequence.extend(pump_actions2)
debug_print(f" ✅ 溶剂2泵送动作已添加: {len(pump_actions2)} 个动作 🚰✨")
except Exception as e:
debug_print(f" ❌ 溶剂2泵协议生成失败: {str(e)} 😭")
raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}")
# 10. 等待溶剂2稳定
debug_print(" ⏳ 添加溶剂2稳定等待...")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 5.0, # 🕐 缩短等待时间10.0s → 5.0s
"description": f"等待溶剂2 {solvent2} 稳定"
}
})
debug_print(" ✅ 溶剂2稳定等待已添加 ⏰✨")
# 11. 等待重结晶完成
debug_print("📍 步骤9: 等待重结晶完成... 💎")
# 🕐 模拟运行时间优化
debug_print(" ⏱️ 检查模拟运行时间限制...")
original_crystallize_time = 600.0 # 原始重结晶时间
simulation_time_limit = 60.0 # 模拟运行时间限制60秒
final_crystallize_time = min(original_crystallize_time, simulation_time_limit)
if original_crystallize_time > simulation_time_limit:
debug_print(f" 🎮 模拟运行优化: {original_crystallize_time}s → {final_crystallize_time}s ⚡")
debug_print(f" 📊 时间缩短: {original_crystallize_time/60:.1f}分钟 → {final_crystallize_time/60:.1f}分钟 🚀")
else:
debug_print(f" ✅ 时间在限制内: {final_crystallize_time}s 保持不变 🎯")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": final_crystallize_time,
"description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio},总体积 {final_volume}mL" + (f" (模拟时间)" if original_crystallize_time != final_crystallize_time else "")
}
})
debug_print(f" ✅ 重结晶等待已添加: {final_crystallize_time}s 💎✨")
# 显示时间调整信息
if original_crystallize_time != final_crystallize_time:
debug_print(f" 🎭 模拟优化说明: 原计划 {original_crystallize_time/60:.1f}分钟,实际模拟 {final_crystallize_time/60:.1f}分钟 ⚡")
# 🎊 总结
debug_print("💎" * 20)
debug_print(f"🎉 重结晶协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 目标容器: {vessel}")
debug_print(f"💧 总体积: {final_volume}mL")
debug_print(f"⚖️ 溶剂比例: {solvent1}:{solvent2} = {ratio1}:{ratio2}")
debug_print(f"🧪 溶剂1: {solvent1} ({volume1:.2f}mL)")
debug_print(f"🧪 溶剂2: {solvent2} ({volume2:.2f}mL)")
debug_print(f"⏱️ 预计总时间: {(final_crystallize_time + 10)/60:.1f} 分钟 ⌛")
debug_print("💎" * 20)
return action_sequence
# 测试函数
def test_recrystallize_protocol():
"""测试重结晶协议"""
debug_print("🧪 === RECRYSTALLIZE PROTOCOL 测试 === ✨")
# 测试比例解析
debug_print("⚖️ 测试比例解析...")
test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"]
for ratio in test_ratios:
r1, r2 = parse_ratio(ratio)
debug_print(f" 📊 比例 '{ratio}' -> {r1}:{r2}")
debug_print("✅ 测试完成 🎉")
if __name__ == "__main__":
test_recrystallize_protocol()

View File

@@ -0,0 +1,249 @@
import networkx as nx
from typing import List, Dict, Any
from .pump_protocol import generate_pump_protocol_with_rinsing
def debug_print(message):
"""调试输出"""
print(f"🔄 [RESET_HANDLING] {message}", flush=True)
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""
查找溶剂容器,支持多种匹配模式
Args:
G: 网络图
solvent: 溶剂名称(如 "methanol", "ethanol", "water"
Returns:
str: 溶剂容器ID
"""
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器... 🧪")
# 构建可能的容器名称
possible_names = [
f"flask_{solvent}", # flask_methanol
f"bottle_{solvent}", # bottle_methanol
f"reagent_{solvent}", # reagent_methanol
f"reagent_bottle_{solvent}", # reagent_bottle_methanol
f"{solvent}_flask", # methanol_flask
f"{solvent}_bottle", # methanol_bottle
f"{solvent}", # methanol
f"vessel_{solvent}", # vessel_methanol
]
debug_print(f"📋 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个) 📝")
# 第一步:通过容器名称匹配
debug_print(" 🎯 步骤1: 精确名称匹配...")
for vessel_name in possible_names:
if vessel_name in G.nodes():
debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name}")
return vessel_name
debug_print(" 😞 精确名称匹配失败,尝试模糊匹配... 🔍")
# 第二步:通过模糊匹配
debug_print(" 🔍 步骤2: 模糊名称匹配...")
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
node_name = G.nodes[node_id].get('name', '').lower()
# 检查是否包含溶剂名称
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id}")
return node_id
debug_print(" 😞 模糊匹配失败,尝试液体类型匹配... 🧪")
# 第三步:通过液体类型匹配
debug_print(" 🧪 步骤3: 液体类型匹配...")
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
reagent_name = vessel_data.get('reagent_name', '').lower()
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id}")
return node_id
# 列出可用容器帮助调试
debug_print(" 📊 显示可用容器信息...")
available_containers = []
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
liquid_types = [liquid.get('liquid_type', '') or liquid.get('name', '')
for liquid in liquids if isinstance(liquid, dict)]
available_containers.append({
'id': node_id,
'name': G.nodes[node_id].get('name', ''),
'liquids': liquid_types,
'reagent_name': vessel_data.get('reagent_name', '')
})
debug_print(f" 📋 可用容器列表 (共{len(available_containers)}个):")
for i, container in enumerate(available_containers[:5]): # 只显示前5个
debug_print(f" {i+1}. 🥽 {container['id']}: {container['name']}")
debug_print(f" 💧 液体: {container['liquids']}")
debug_print(f" 🧪 试剂: {container['reagent_name']}")
if len(available_containers) > 5:
debug_print(f" ... 还有 {len(available_containers)-5} 个容器 📦")
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭")
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names[:3]}...")
def generate_reset_handling_protocol(
G: nx.DiGraph,
solvent: str,
**kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]:
"""
生成重置处理协议序列
Args:
G: 有向图,节点为容器和设备
solvent: 溶剂名称从XDL传入
**kwargs: 其他可选参数,但不使用
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
# 固定参数
target_vessel = "main_reactor" # 默认目标容器
volume = 50.0 # 默认体积 50 mL
debug_print("🔄" * 20)
debug_print("🚀 开始生成重置处理协议 ✨")
debug_print(f"📝 输入参数:")
debug_print(f" 🧪 溶剂: {solvent}")
debug_print(f" 🥽 目标容器: {target_vessel}")
debug_print(f" 💧 体积: {volume} mL")
debug_print(f" ⚙️ 其他参数: {kwargs}")
debug_print("🔄" * 20)
# 1. 验证目标容器存在
debug_print("📍 步骤1: 验证目标容器... 🔧")
if target_vessel not in G.nodes():
debug_print(f"❌ 目标容器 '{target_vessel}' 不存在于系统中! 😱")
raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中")
debug_print(f"✅ 目标容器 '{target_vessel}' 验证通过 🎯")
# 2. 查找溶剂容器
debug_print("📍 步骤2: 查找溶剂容器... 🔍")
try:
solvent_vessel = find_solvent_vessel(G, solvent)
debug_print(f"🎉 找到溶剂容器: {solvent_vessel}")
except ValueError as e:
debug_print(f"❌ 溶剂容器查找失败: {str(e)} 😭")
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
# 3. 验证路径存在
debug_print("📍 步骤3: 验证传输路径... 🛤️")
try:
path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel)
debug_print(f"🛤️ 找到路径: {''.join(path)}")
except nx.NetworkXNoPath:
debug_print(f"❌ 路径不可达: {solvent_vessel}{target_vessel} 😞")
raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径")
# 4. 使用pump_protocol转移溶剂
debug_print("📍 步骤4: 转移溶剂... 🚰")
debug_print(f" 🚛 开始转移: {solvent_vessel}{target_vessel}")
debug_print(f" 💧 转移体积: {volume} mL")
try:
debug_print(" 🔄 生成泵送协议...")
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent_vessel,
to_vessel=target_vessel,
volume=volume,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="", # 重置处理不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.5, # 正常流速
transfer_flowrate=0.5 # 正常转移流速
)
action_sequence.extend(pump_actions)
debug_print(f" ✅ 泵送协议已添加: {len(pump_actions)} 个动作 🚰✨")
except Exception as e:
debug_print(f" ❌ 泵送协议生成失败: {str(e)} 😭")
raise ValueError(f"生成泵协议时出错: {str(e)}")
# 5. 等待溶剂稳定
debug_print("📍 步骤5: 等待溶剂稳定... ⏳")
# 🕐 模拟运行时间优化
debug_print(" ⏱️ 检查模拟运行时间限制...")
original_wait_time = 10.0 # 原始等待时间
simulation_time_limit = 5.0 # 模拟运行时间限制5秒
final_wait_time = min(original_wait_time, simulation_time_limit)
if original_wait_time > simulation_time_limit:
debug_print(f" 🎮 模拟运行优化: {original_wait_time}s → {final_wait_time}s ⚡")
debug_print(f" 📊 时间缩短: {original_wait_time}s → {final_wait_time}s 🚀")
else:
debug_print(f" ✅ 时间在限制内: {final_wait_time}s 保持不变 🎯")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": final_wait_time,
"description": f"等待溶剂 {solvent} 稳定" + (f" (模拟时间)" if original_wait_time != final_wait_time else "")
}
})
debug_print(f" ✅ 稳定等待已添加: {final_wait_time}s ⏰✨")
# 显示时间调整信息
if original_wait_time != final_wait_time:
debug_print(f" 🎭 模拟优化说明: 原计划 {original_wait_time}s实际模拟 {final_wait_time}s ⚡")
# 🎊 总结
debug_print("🔄" * 20)
debug_print(f"🎉 重置处理协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🧪 溶剂: {solvent}")
debug_print(f"🥽 源容器: {solvent_vessel}")
debug_print(f"🥽 目标容器: {target_vessel}")
debug_print(f"💧 转移体积: {volume} mL")
debug_print(f"⏱️ 预计总时间: {(final_wait_time + 5):.0f} 秒 ⌛")
debug_print(f"🎯 已添加 {volume} mL {solvent}{target_vessel} 🚰✨")
debug_print("🔄" * 20)
return action_sequence
# 测试函数
def test_reset_handling_protocol():
"""测试重置处理协议"""
debug_print("🧪 === RESET HANDLING PROTOCOL 测试 === ✨")
# 测试溶剂名称
debug_print("🧪 测试常用溶剂名称...")
test_solvents = ["methanol", "ethanol", "water", "acetone", "dmso"]
for solvent in test_solvents:
debug_print(f" 🔍 测试溶剂: {solvent}")
debug_print("✅ 测试完成 🎉")
if __name__ == "__main__":
test_reset_handling_protocol()

View File

@@ -0,0 +1,646 @@
from typing import List, Dict, Any, Union
import networkx as nx
import logging
import re
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"[RUN_COLUMN] {message}", flush=True)
logger.info(f"[RUN_COLUMN] {message}")
def parse_percentage(pct_str: str) -> float:
"""
解析百分比字符串为数值
Args:
pct_str: 百分比字符串(如 "40 %", "40%", "40"
Returns:
float: 百分比数值0-100
"""
if not pct_str or not pct_str.strip():
return 0.0
pct_str = pct_str.strip().lower()
debug_print(f"解析百分比: '{pct_str}'")
# 移除百分号和空格
pct_clean = re.sub(r'[%\s]', '', pct_str)
# 提取数字
match = re.search(r'([0-9]*\.?[0-9]+)', pct_clean)
if match:
value = float(match.group(1))
debug_print(f"百分比解析结果: {value}%")
return value
debug_print(f"⚠️ 无法解析百分比: '{pct_str}'返回0.0")
return 0.0
def parse_ratio(ratio_str: str) -> tuple:
"""
解析比例字符串为两个数值
Args:
ratio_str: 比例字符串(如 "5:95", "1:1", "40:60"
Returns:
tuple: (ratio1, ratio2) 两个比例值
"""
if not ratio_str or not ratio_str.strip():
return (50.0, 50.0) # 默认1:1
ratio_str = ratio_str.strip()
debug_print(f"解析比例: '{ratio_str}'")
# 支持多种分隔符:: / -
if ':' in ratio_str:
parts = ratio_str.split(':')
elif '/' in ratio_str:
parts = ratio_str.split('/')
elif '-' in ratio_str:
parts = ratio_str.split('-')
elif 'to' in ratio_str.lower():
parts = ratio_str.lower().split('to')
else:
debug_print(f"⚠️ 无法解析比例格式: '{ratio_str}'使用默认1:1")
return (50.0, 50.0)
if len(parts) >= 2:
try:
ratio1 = float(parts[0].strip())
ratio2 = float(parts[1].strip())
total = ratio1 + ratio2
# 转换为百分比
pct1 = (ratio1 / total) * 100
pct2 = (ratio2 / total) * 100
debug_print(f"比例解析结果: {ratio1}:{ratio2} -> {pct1:.1f}%:{pct2:.1f}%")
return (pct1, pct2)
except ValueError as e:
debug_print(f"⚠️ 比例数值转换失败: {str(e)}")
debug_print(f"⚠️ 比例解析失败使用默认1:1")
return (50.0, 50.0)
def parse_rf_value(rf_str: str) -> float:
"""
解析Rf值字符串
Args:
rf_str: Rf值字符串"0.3", "0.45", "?"
Returns:
float: Rf值0-1
"""
if not rf_str or not rf_str.strip():
return 0.3 # 默认Rf值
rf_str = rf_str.strip().lower()
debug_print(f"解析Rf值: '{rf_str}'")
# 处理未知Rf值
if rf_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_rf = 0.3
debug_print(f"检测到未知Rf值使用默认值: {default_rf}")
return default_rf
# 提取数字
match = re.search(r'([0-9]*\.?[0-9]+)', rf_str)
if match:
value = float(match.group(1))
# 确保Rf值在0-1范围内
if value > 1.0:
value = value / 100.0 # 可能是百分比形式
value = max(0.0, min(1.0, value)) # 限制在0-1范围
debug_print(f"Rf值解析结果: {value}")
return value
debug_print(f"⚠️ 无法解析Rf值: '{rf_str}'使用默认值0.3")
return 0.3
def find_column_device(G: nx.DiGraph) -> str:
"""查找柱层析设备"""
debug_print("查找柱层析设备...")
# 查找虚拟柱设备
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if 'virtual_column' in node_class.lower() or 'column' in node_class.lower():
debug_print(f"✅ 找到柱层析设备: {node}")
return node
# 如果没有找到,尝试创建虚拟设备名称
possible_names = ['column_1', 'virtual_column_1', 'chromatography_column_1']
for name in possible_names:
if name in G.nodes():
debug_print(f"✅ 找到柱设备: {name}")
return name
debug_print("⚠️ 未找到柱层析设备将使用pump protocol直接转移")
return ""
def find_column_vessel(G: nx.DiGraph, column: str) -> str:
"""查找柱容器"""
debug_print(f"查找柱容器: '{column}'")
# 直接检查column参数是否是容器
if column in G.nodes():
node_type = G.nodes[column].get('type', '')
if node_type == 'container':
debug_print(f"✅ 找到柱容器: {column}")
return column
# 尝试常见的命名规则
possible_names = [
f"column_{column}",
f"{column}_column",
f"vessel_{column}",
f"{column}_vessel",
"column_vessel",
"chromatography_column",
"silica_column",
"preparative_column",
"column"
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
node_type = G.nodes[vessel_name].get('type', '')
if node_type == 'container':
debug_print(f"✅ 找到柱容器: {vessel_name}")
return vessel_name
debug_print(f"⚠️ 未找到柱容器,将直接在源容器中进行分离")
return ""
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""查找溶剂容器 - 增强版"""
if not solvent or not solvent.strip():
return ""
solvent = solvent.strip().replace(' ', '_').lower()
debug_print(f"查找溶剂容器: '{solvent}'")
# 🔧 方法1直接搜索 data.reagent_name
for node in G.nodes():
node_data = G.nodes[node].get('data', {})
node_type = G.nodes[node].get('type', '')
# 只搜索容器类型的节点
if node_type == 'container':
reagent_name = node_data.get('reagent_name', '').lower()
reagent_config = G.nodes[node].get('config', {}).get('reagent', '').lower()
# 检查 data.reagent_name 和 config.reagent
if reagent_name == solvent or reagent_config == solvent:
debug_print(f"✅ 通过reagent_name找到溶剂容器: {node} (reagent: {reagent_name or reagent_config})")
return node
# 模糊匹配 reagent_name
if solvent in reagent_name or reagent_name in solvent:
debug_print(f"✅ 通过reagent_name模糊匹配到溶剂容器: {node} (reagent: {reagent_name})")
return node
if solvent in reagent_config or reagent_config in solvent:
debug_print(f"✅ 通过config.reagent模糊匹配到溶剂容器: {node} (reagent: {reagent_config})")
return node
# 🔧 方法2常见的溶剂容器命名规则
possible_names = [
f"flask_{solvent}",
f"bottle_{solvent}",
f"reagent_{solvent}",
f"{solvent}_bottle",
f"{solvent}_flask",
f"solvent_{solvent}",
f"reagent_bottle_{solvent}"
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
node_type = G.nodes[vessel_name].get('type', '')
if node_type == 'container':
debug_print(f"✅ 通过命名规则找到溶剂容器: {vessel_name}")
return vessel_name
# 🔧 方法3节点名称模糊匹配
for node in G.nodes():
node_type = G.nodes[node].get('type', '')
if node_type == 'container':
if ('flask_' in node or 'bottle_' in node or 'reagent_' in node) and solvent in node.lower():
debug_print(f"✅ 通过节点名称模糊匹配到溶剂容器: {node}")
return node
# 🔧 方法4特殊溶剂名称映射
solvent_mapping = {
'dmf': ['dmf', 'dimethylformamide', 'n,n-dimethylformamide'],
'ethyl_acetate': ['ethyl_acetate', 'ethylacetate', 'etoac', 'ea'],
'hexane': ['hexane', 'hexanes', 'n-hexane'],
'methanol': ['methanol', 'meoh', 'ch3oh'],
'water': ['water', 'h2o', 'distilled_water'],
'acetone': ['acetone', 'ch3coch3', '2-propanone'],
'dichloromethane': ['dichloromethane', 'dcm', 'ch2cl2', 'methylene_chloride'],
'chloroform': ['chloroform', 'chcl3', 'trichloromethane']
}
# 查找映射的同义词
for canonical_name, synonyms in solvent_mapping.items():
if solvent in synonyms:
debug_print(f"检测到溶剂同义词: '{solvent}' -> '{canonical_name}'")
return find_solvent_vessel(G, canonical_name) # 递归搜索
debug_print(f"⚠️ 未找到溶剂 '{solvent}' 的容器")
return ""
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
"""获取容器中的液体体积 - 增强版"""
if vessel not in G.nodes():
debug_print(f"⚠️ 节点 '{vessel}' 不存在")
return 0.0
node_type = G.nodes[vessel].get('type', '')
vessel_data = G.nodes[vessel].get('data', {})
debug_print(f"读取节点 '{vessel}' (类型: {node_type}) 体积数据: {vessel_data}")
# 🔧 如果是设备类型,尝试查找关联的容器
if node_type == 'device':
debug_print(f"'{vessel}' 是设备,尝试查找关联容器...")
# 查找是否有内置容器数据
config_data = G.nodes[vessel].get('config', {})
if 'volume' in config_data:
default_volume = config_data.get('volume', 50.0)
debug_print(f"使用设备默认容量: {default_volume}mL")
return default_volume
# 对于旋蒸等设备,使用默认值
if 'rotavap' in vessel.lower():
default_volume = 50.0
debug_print(f"旋蒸设备使用默认容量: {default_volume}mL")
return default_volume
debug_print(f"⚠️ 设备 '{vessel}' 无法确定容量返回0")
return 0.0
# 🔧 如果是容器类型,正常读取体积
total_volume = 0.0
# 方法1检查液体列表
liquids = vessel_data.get('liquid', [])
if isinstance(liquids, list):
for liquid in liquids:
if isinstance(liquid, dict):
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
total_volume += volume
# 方法2检查直接体积字段
if total_volume == 0.0:
volume_keys = ['current_volume', 'total_volume', 'volume', 'liquid_volume']
for key in volume_keys:
if key in vessel_data:
try:
total_volume = float(vessel_data[key])
if total_volume > 0:
break
except (ValueError, TypeError):
continue
# 方法3检查配置中的初始体积
if total_volume == 0.0:
config_data = G.nodes[vessel].get('config', {})
if 'current_volume' in config_data:
try:
total_volume = float(config_data['current_volume'])
except (ValueError, TypeError):
pass
debug_print(f"容器 '{vessel}' 总体积: {total_volume}mL")
return total_volume
def calculate_solvent_volumes(total_volume: float, pct1: float, pct2: float) -> tuple:
"""根据百分比计算溶剂体积"""
volume1 = (total_volume * pct1) / 100.0
volume2 = (total_volume * pct2) / 100.0
debug_print(f"溶剂体积计算: 总体积{total_volume}mL")
debug_print(f" - 溶剂1: {pct1}% = {volume1}mL")
debug_print(f" - 溶剂2: {pct2}% = {volume2}mL")
return (volume1, volume2)
def generate_run_column_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column: str,
rf: str = "",
pct1: str = "",
pct2: str = "",
solvent1: str = "",
solvent2: str = "",
ratio: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
生成柱层析分离的协议序列 - 增强版
支持新版XDL的所有参数具有高兼容性和容错性
Args:
G: 有向图,节点为设备和容器,边为流体管道
from_vessel: 源容器的名称,即样品起始所在的容器(必需)
to_vessel: 目标容器的名称,分离后的样品要到达的容器(必需)
column: 所使用的柱子的名称(必需)
rf: Rf值可选支持 "?" 表示未知)
pct1: 第一种溶剂百分比(如 "40 %",可选)
pct2: 第二种溶剂百分比(如 "50 %",可选)
solvent1: 第一种溶剂名称(可选)
solvent2: 第二种溶剂名称(可选)
ratio: 溶剂比例(如 "5:95"可选优先级高于pct1/pct2
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 柱层析分离操作的动作序列
"""
debug_print("=" * 60)
debug_print("开始生成柱层析协议")
debug_print(f"输入参数:")
debug_print(f" - from_vessel: '{from_vessel}'")
debug_print(f" - to_vessel: '{to_vessel}'")
debug_print(f" - column: '{column}'")
debug_print(f" - rf: '{rf}'")
debug_print(f" - pct1: '{pct1}'")
debug_print(f" - pct2: '{pct2}'")
debug_print(f" - solvent1: '{solvent1}'")
debug_print(f" - solvent2: '{solvent2}'")
debug_print(f" - ratio: '{ratio}'")
debug_print(f" - 其他参数: {kwargs}")
debug_print("=" * 60)
action_sequence = []
# === 参数验证 ===
debug_print("步骤1: 参数验证...")
if not from_vessel:
raise ValueError("from_vessel 参数不能为空")
if not to_vessel:
raise ValueError("to_vessel 参数不能为空")
if not column:
raise ValueError("column 参数不能为空")
if from_vessel not in G.nodes():
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中")
if to_vessel not in G.nodes():
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中")
debug_print("✅ 基本参数验证通过")
# === 参数解析 ===
debug_print("步骤2: 参数解析...")
# 解析Rf值
final_rf = parse_rf_value(rf)
debug_print(f"最终Rf值: {final_rf}")
# 解析溶剂比例ratio优先级高于pct1/pct2
if ratio and ratio.strip():
final_pct1, final_pct2 = parse_ratio(ratio)
debug_print(f"使用ratio参数: {final_pct1:.1f}% : {final_pct2:.1f}%")
else:
final_pct1 = parse_percentage(pct1) if pct1 else 50.0
final_pct2 = parse_percentage(pct2) if pct2 else 50.0
# 如果百分比和不是100%,进行归一化
total_pct = final_pct1 + final_pct2
if total_pct == 0:
final_pct1, final_pct2 = 50.0, 50.0
elif total_pct != 100.0:
final_pct1 = (final_pct1 / total_pct) * 100
final_pct2 = (final_pct2 / total_pct) * 100
debug_print(f"使用百分比参数: {final_pct1:.1f}% : {final_pct2:.1f}%")
# 设置默认溶剂(如果未指定)
final_solvent1 = solvent1.strip() if solvent1 else "ethyl_acetate"
final_solvent2 = solvent2.strip() if solvent2 else "hexane"
debug_print(f"最终溶剂: {final_solvent1} : {final_solvent2}")
# === 查找设备和容器 ===
debug_print("步骤3: 查找设备和容器...")
# 查找柱层析设备
column_device_id = find_column_device(G)
# 查找柱容器
column_vessel = find_column_vessel(G, column)
# 查找溶剂容器
solvent1_vessel = find_solvent_vessel(G, final_solvent1)
solvent2_vessel = find_solvent_vessel(G, final_solvent2)
debug_print(f"设备映射:")
debug_print(f" - 柱设备: '{column_device_id}'")
debug_print(f" - 柱容器: '{column_vessel}'")
debug_print(f" - 溶剂1容器: '{solvent1_vessel}'")
debug_print(f" - 溶剂2容器: '{solvent2_vessel}'")
# === 获取源容器体积 ===
debug_print("步骤4: 获取源容器体积...")
source_volume = get_vessel_liquid_volume(G, from_vessel)
if source_volume <= 0:
source_volume = 50.0 # 默认体积
debug_print(f"⚠️ 无法获取源容器体积,使用默认值: {source_volume}mL")
else:
debug_print(f"✅ 源容器体积: {source_volume}mL")
# === 计算溶剂体积 ===
debug_print("步骤5: 计算溶剂体积...")
# 洗脱溶剂通常是样品体积的2-5倍
total_elution_volume = source_volume * 3.0
solvent1_volume, solvent2_volume = calculate_solvent_volumes(
total_elution_volume, final_pct1, final_pct2
)
# === 执行柱层析流程 ===
debug_print("步骤6: 执行柱层析流程...")
try:
# 步骤6.1: 样品上柱(如果有独立的柱容器)
if column_vessel and column_vessel != from_vessel:
debug_print(f"6.1: 样品上柱 - {source_volume}mL 从 {from_vessel}{column_vessel}")
try:
sample_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=from_vessel,
to_vessel=column_vessel,
volume=source_volume,
flowrate=1.0, # 慢速上柱
transfer_flowrate=0.5,
rinsing_solvent="", # 暂不冲洗
rinsing_volume=0.0,
rinsing_repeats=0
)
action_sequence.extend(sample_transfer_actions)
debug_print(f"✅ 样品上柱完成,添加了 {len(sample_transfer_actions)} 个动作")
except Exception as e:
debug_print(f"⚠️ 样品上柱失败: {str(e)}")
# 步骤6.2: 添加洗脱溶剂1如果有溶剂容器
if solvent1_vessel and solvent1_volume > 0:
debug_print(f"6.2: 添加洗脱溶剂1 - {solvent1_volume:.1f}mL {final_solvent1}")
try:
target_vessel = column_vessel if column_vessel else from_vessel
solvent1_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent1_vessel,
to_vessel=target_vessel,
volume=solvent1_volume,
flowrate=2.0,
transfer_flowrate=1.0
)
action_sequence.extend(solvent1_transfer_actions)
debug_print(f"✅ 溶剂1添加完成添加了 {len(solvent1_transfer_actions)} 个动作")
except Exception as e:
debug_print(f"⚠️ 溶剂1添加失败: {str(e)}")
# 步骤6.3: 添加洗脱溶剂2如果有溶剂容器
if solvent2_vessel and solvent2_volume > 0:
debug_print(f"6.3: 添加洗脱溶剂2 - {solvent2_volume:.1f}mL {final_solvent2}")
try:
target_vessel = column_vessel if column_vessel else from_vessel
solvent2_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent2_vessel,
to_vessel=target_vessel,
volume=solvent2_volume,
flowrate=2.0,
transfer_flowrate=1.0
)
action_sequence.extend(solvent2_transfer_actions)
debug_print(f"✅ 溶剂2添加完成添加了 {len(solvent2_transfer_actions)} 个动作")
except Exception as e:
debug_print(f"⚠️ 溶剂2添加失败: {str(e)}")
# 步骤6.4: 使用柱层析设备执行分离(如果有设备)
if column_device_id:
debug_print(f"6.4: 使用柱层析设备执行分离")
column_separation_action = {
"device_id": column_device_id,
"action_name": "run_column",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"column": column,
"rf": rf,
"pct1": pct1,
"pct2": pct2,
"solvent1": solvent1,
"solvent2": solvent2,
"ratio": ratio
}
}
action_sequence.append(column_separation_action)
debug_print(f"✅ 柱层析设备动作已添加")
# 等待分离完成
separation_time = max(30, int(total_elution_volume / 2)) # 基于体积估算时间
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": separation_time}
})
debug_print(f"✅ 等待分离完成: {separation_time}")
# 步骤6.5: 产物收集(从柱容器到目标容器)
if column_vessel and column_vessel != to_vessel:
debug_print(f"6.5: 产物收集 - 从 {column_vessel}{to_vessel}")
try:
# 估算产物体积原始样品体积的70-90%
product_volume = source_volume * 0.8
product_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=column_vessel,
to_vessel=to_vessel,
volume=product_volume,
flowrate=1.5,
transfer_flowrate=0.8
)
action_sequence.extend(product_transfer_actions)
debug_print(f"✅ 产物收集完成,添加了 {len(product_transfer_actions)} 个动作")
except Exception as e:
debug_print(f"⚠️ 产物收集失败: {str(e)}")
# 步骤6.6: 如果没有独立的柱设备和容器,执行简化的直接转移
if not column_device_id and not column_vessel:
debug_print(f"6.6: 简化模式 - 直接转移 {source_volume}mL 从 {from_vessel}{to_vessel}")
try:
direct_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=from_vessel,
to_vessel=to_vessel,
volume=source_volume,
flowrate=2.0,
transfer_flowrate=1.0
)
action_sequence.extend(direct_transfer_actions)
debug_print(f"✅ 直接转移完成,添加了 {len(direct_transfer_actions)} 个动作")
except Exception as e:
debug_print(f"⚠️ 直接转移失败: {str(e)}")
except Exception as e:
debug_print(f"❌ 协议生成失败: {str(e)} 😭")
# 不添加不确定的动作直接让action_sequence保持为空列表
# action_sequence 已经在函数开始时初始化为 []
# 确保至少有一个有效的动作,如果完全失败就返回空列表
if not action_sequence:
debug_print("⚠️ 没有生成任何有效动作")
# 可以选择返回空列表或添加一个基本的等待动作
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 1.0,
"description": "柱层析协议执行完成"
}
})
# 🎊 总结
debug_print("🧪" * 20)
debug_print(f"🎉 柱层析协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 路径: {from_vessel}{to_vessel}")
debug_print(f"🏛️ 柱子: {column}")
debug_print(f"🧪 溶剂: {final_solvent1}:{final_solvent2}")
debug_print("🧪" * 20)
return action_sequence
# 测试函数
def test_run_column_protocol():
"""测试柱层析协议"""
debug_print("🧪 === RUN COLUMN PROTOCOL 测试 === ✨")
debug_print("✅ 测试完成 🎉")
if __name__ == "__main__":
test_run_column_protocol()

View File

@@ -1,230 +1,670 @@
import numpy as np
import networkx as nx
import re
import logging
import sys
from typing import List, Dict, Any, Union
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
# 确保输出编码为UTF-8
if hasattr(sys.stdout, 'reconfigure'):
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except:
pass
def debug_print(message):
"""调试输出函数 - 支持中文"""
try:
# 确保消息是字符串格式
safe_message = str(message)
print(f"[分离协议] {safe_message}", flush=True)
logger.info(f"[分离协议] {safe_message}")
except UnicodeEncodeError:
# 如果编码失败,尝试替换不支持的字符
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
print(f"[分离协议] {safe_message}", flush=True)
logger.info(f"[分离协议] {safe_message}")
except Exception as e:
# 最后的安全措施
fallback_message = f"日志输出错误: {repr(message)}"
print(f"[分离协议] {fallback_message}", flush=True)
logger.info(f"[分离协议] {fallback_message}")
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志 - 支持中文和emoji"""
try:
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message,
"progress_message": full_message
}
}
except Exception as e:
# 如果emoji有问题使用纯文本
safe_message = f"[日志] {message}"
debug_print(safe_message)
logger.info(safe_message)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": safe_message,
"progress_message": safe_message
}
}
def parse_volume_input(volume_input: Union[str, float]) -> float:
"""
解析体积输入,支持带单位的字符串
Args:
volume_input: 体积输入(如 "200 mL", "?", 50.0
Returns:
float: 体积(毫升)
"""
if isinstance(volume_input, (int, float)):
debug_print(f"📏 体积输入为数值: {volume_input}")
return float(volume_input)
if not volume_input or not str(volume_input).strip():
debug_print(f"⚠️ 体积输入为空,返回 0.0mL")
return 0.0
volume_str = str(volume_input).lower().strip()
debug_print(f"🔍 解析体积输入: '{volume_str}'")
# 处理未知体积
if volume_str in ['?', 'unknown', 'tbd', 'to be determined', '未知', '待定']:
default_volume = 100.0 # 默认100mL
debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL")
return default_volume
# 移除空格并提取数字和单位
volume_clean = re.sub(r'\s+', '', volume_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter|毫升|升|微升)?', volume_clean)
if not match:
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值 100mL")
return 100.0
value = float(match.group(1))
unit = match.group(2) or 'ml' # 默认单位为毫升
# 转换为毫升
if unit in ['l', 'liter', '']:
volume = value * 1000.0 # L -> mL
debug_print(f"🔄 体积转换: {value}L -> {volume}mL")
elif unit in ['μl', 'ul', 'microliter', '微升']:
volume = value / 1000.0 # μL -> mL
debug_print(f"🔄 体积转换: {value}μL -> {volume}mL")
else: # ml, milliliter, 毫升 或默认
volume = value # 已经是mL
debug_print(f"✅ 体积已为毫升单位: {volume}mL")
return volume
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""查找溶剂容器,支持多种匹配模式"""
if not solvent or not solvent.strip():
debug_print("⏭️ 未指定溶剂,跳过溶剂容器查找")
return ""
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器...")
# 🔧 方法1直接搜索 data.reagent_name 和 config.reagent
debug_print(f"📋 方法1: 搜索试剂字段...")
for node in G.nodes():
node_data = G.nodes[node].get('data', {})
node_type = G.nodes[node].get('type', '')
config_data = G.nodes[node].get('config', {})
# 只搜索容器类型的节点
if node_type == 'container':
reagent_name = node_data.get('reagent_name', '').lower()
config_reagent = config_data.get('reagent', '').lower()
# 精确匹配
if reagent_name == solvent.lower() or config_reagent == solvent.lower():
debug_print(f"✅ 通过试剂字段精确匹配找到容器: {node}")
return node
# 模糊匹配
if (solvent.lower() in reagent_name and reagent_name) or \
(solvent.lower() in config_reagent and config_reagent):
debug_print(f"✅ 通过试剂字段模糊匹配找到容器: {node}")
return node
# 🔧 方法2常见的容器命名规则
debug_print(f"📋 方法2: 使用命名规则...")
solvent_clean = solvent.lower().replace(' ', '_').replace('-', '_')
possible_names = [
f"flask_{solvent_clean}",
f"bottle_{solvent_clean}",
f"vessel_{solvent_clean}",
f"{solvent_clean}_flask",
f"{solvent_clean}_bottle",
f"solvent_{solvent_clean}",
f"reagent_{solvent_clean}",
f"reagent_bottle_{solvent_clean}",
f"reagent_bottle_1", # 通用试剂瓶
f"reagent_bottle_2",
f"reagent_bottle_3"
]
debug_print(f"🎯 尝试的容器名称: {possible_names[:5]}... (共 {len(possible_names)} 个)")
for name in possible_names:
if name in G.nodes():
node_type = G.nodes[name].get('type', '')
if node_type == 'container':
debug_print(f"✅ 通过命名规则找到容器: {name}")
return name
# 🔧 方法3使用第一个试剂瓶作为备选
debug_print(f"📋 方法3: 查找备用试剂瓶...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if (node_data.get('type') == 'container' and
('reagent' in node_id.lower() or 'bottle' in node_id.lower())):
debug_print(f"⚠️ 未找到专用容器,使用备用容器: {node_id}")
return node_id
debug_print(f"❌ 无法找到溶剂 '{solvent}' 的容器")
return ""
def find_separator_device(G: nx.DiGraph, vessel: str) -> str:
"""查找分离器设备,支持多种查找方式"""
debug_print(f"🔍 正在查找容器 '{vessel}' 的分离器设备...")
# 方法1查找连接到容器的分离器设备
debug_print(f"📋 方法1: 检查连接的分离器...")
separator_nodes = []
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'separator' in node_class:
separator_nodes.append(node)
debug_print(f"📋 发现分离器设备: {node}")
# 检查是否连接到目标容器
if G.has_edge(node, vessel) or G.has_edge(vessel, node):
debug_print(f"✅ 找到连接的分离器: {node}")
return node
debug_print(f"📊 找到的分离器总数: {len(separator_nodes)}")
# 方法2根据命名规则查找
debug_print(f"📋 方法2: 使用命名规则...")
possible_names = [
f"{vessel}_controller",
f"{vessel}_separator",
vessel, # 容器本身可能就是分离器
"separator_1",
"virtual_separator",
"liquid_handler_1", # 液体处理器也可能用于分离
"controller_1"
]
debug_print(f"🎯 尝试的分离器名称: {possible_names}")
for name in possible_names:
if name in G.nodes():
node_class = G.nodes[name].get('class', '').lower()
if 'separator' in node_class or 'controller' in node_class:
debug_print(f"✅ 通过命名规则找到分离器: {name}")
return name
# 方法3查找第一个分离器设备
debug_print(f"📋 方法3: 使用第一个可用分离器...")
if separator_nodes:
debug_print(f"⚠️ 使用第一个分离器设备: {separator_nodes[0]}")
return separator_nodes[0]
debug_print(f"❌ 未找到分离器设备")
return ""
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的搅拌器"""
debug_print(f"🔍 正在查找与容器 {vessel} 连接的搅拌器...")
stirrer_nodes = []
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if 'stirrer' in node_class.lower():
stirrer_nodes.append(node)
debug_print(f"📋 发现搅拌器: {node}")
debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}")
# 检查哪个搅拌器与目标容器相连
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"✅ 找到连接的搅拌器: {stirrer}")
return stirrer
# 如果没有连接的搅拌器,返回第一个可用的
if stirrer_nodes:
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
return stirrer_nodes[0]
debug_print("❌ 未找到搅拌器")
return ""
def generate_separate_protocol(
G: nx.DiGraph,
purpose: str, # 'wash' or 'extract'. 'wash' means that product phase will not be the added solvent phase, 'extract' means product phase will be the added solvent phase. If no solvent is added just use 'extract'.
product_phase: str, # 'top' or 'bottom'. Phase that product will be in.
from_vessel: str, #Contents of from_vessel are transferred to separation_vessel and separation is performed.
separation_vessel: str, # Vessel in which separation of phases will be carried out.
to_vessel: str, # Vessel to send product phase to.
waste_phase_to_vessel: str, # Optional. Vessel to send waste phase to.
solvent: str, # Optional. Solvent to add to separation vessel after contents of from_vessel has been transferred to create two phases.
solvent_volume: float = 50, # Optional. Volume of solvent to add (mL).
through: str = "", # Optional. Solid chemical to send product phase through on way to to_vessel, e.g. 'celite'.
repeats: int = 1, # Optional. Number of separations to perform.
stir_time: float = 30, # Optional. Time stir for after adding solvent, before separation of phases.
stir_speed: float = 300, # Optional. Speed to stir at after adding solvent, before separation of phases.
settling_time: float = 300 # Optional. Time
) -> list[dict]:
G: nx.DiGraph,
# 🔧 基础参数支持XDL的vessel参数
vessel: str = "", # XDL: 分离容器
purpose: str = "separate", # 分离目的
product_phase: str = "top", # 产物相
# 🔧 可选的详细参数
from_vessel: str = "", # 源容器通常在separate前已经transfer了
separation_vessel: str = "", # 分离容器与vessel同义
to_vessel: str = "", # 目标容器(可选)
waste_phase_to_vessel: str = "", # 废相目标容器
product_vessel: str = "", # XDL: 产物容器与to_vessel同义
waste_vessel: str = "", # XDL: 废液容器与waste_phase_to_vessel同义
# 🔧 溶剂相关参数
solvent: str = "", # 溶剂名称
solvent_volume: Union[str, float] = 0.0, # 溶剂体积
volume: Union[str, float] = 0.0, # XDL: 体积与solvent_volume同义
# 🔧 操作参数
through: str = "", # 通过材料
repeats: int = 1, # 重复次数
stir_time: float = 30.0, # 搅拌时间(秒)
stir_speed: float = 300.0, # 搅拌速度
settling_time: float = 300.0, # 沉降时间(秒)
**kwargs
) -> List[Dict[str, Any]]:
"""
Generate a protocol to clean a vessel with a solvent.
生成分离操作的协议序列 - 增强中文版
:param G: Directed graph. Nodes are containers and pumps, edges are fluidic connections.
:param vessel: Vessel to clean.
:param solvent: Solvent to clean vessel with.
:param volume: Volume of solvent to clean vessel with.
:param temp: Temperature to heat vessel to while cleaning.
:param repeats: Number of cleaning cycles to perform.
:return: List of actions to clean vessel.
支持XDL参数格式
- vessel: 分离容器(必需)
- purpose: "wash", "extract", "separate"
- product_phase: "top", "bottom"
- product_vessel: 产物收集容器
- waste_vessel: 废液收集容器
- solvent: 溶剂名称
- volume: "200 mL", "?" 或数值
- repeats: 重复次数
分离流程:
1. (可选)添加溶剂到分离容器
2. 搅拌混合
3. 静置分层
4. 收集指定相到目标容器
5. 重复指定次数
"""
# 生成泵操作的动作序列
pump_action_sequence = []
reactor_volume = 500.0
waste_vessel = waste_phase_to_vessel
debug_print("=" * 60)
debug_print("🧪 开始生成分离协议 - 增强中文版")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 容器: '{vessel}'")
debug_print(f" 🎯 分离目的: '{purpose}'")
debug_print(f" 📊 产物相: '{product_phase}'")
debug_print(f" 💧 溶剂: '{solvent}'")
debug_print(f" 📏 体积: {volume} (类型: {type(volume)})")
debug_print(f" 🔄 重复次数: {repeats}")
debug_print(f" 🎯 产物容器: '{product_vessel}'")
debug_print(f" 🗑️ 废液容器: '{waste_vessel}'")
debug_print(f" 📦 其他参数: {kwargs}")
debug_print("=" * 60)
# TODO通过物料管理系统找到溶剂的容器
if "," in solvent:
solvents = solvent.split(",")
assert len(solvents) == repeats, "Number of solvents must match number of repeats."
action_sequence = []
# === 参数验证和标准化 ===
debug_print("🔍 步骤1: 参数验证和标准化...")
action_sequence.append(create_action_log(f"开始分离操作 - 容器: {vessel}", "🎬"))
action_sequence.append(create_action_log(f"分离目的: {purpose}", "🧪"))
action_sequence.append(create_action_log(f"产物相: {product_phase}", "📊"))
# 统一容器参数
final_vessel = vessel or separation_vessel
if not final_vessel:
debug_print("❌ 必须指定分离容器")
raise ValueError("必须指定分离容器 (vessel 或 separation_vessel)")
final_to_vessel = to_vessel or product_vessel
final_waste_vessel = waste_phase_to_vessel or waste_vessel
# 统一体积参数
final_volume = parse_volume_input(volume or solvent_volume)
# 🔧 修复确保repeats至少为1
if repeats <= 0:
repeats = 1
debug_print(f"⚠️ 重复次数参数 <= 0自动设置为 1")
debug_print(f"🔧 标准化后的参数:")
debug_print(f" 🥼 分离容器: '{final_vessel}'")
debug_print(f" 🎯 产物容器: '{final_to_vessel}'")
debug_print(f" 🗑️ 废液容器: '{final_waste_vessel}'")
debug_print(f" 📏 溶剂体积: {final_volume}mL")
debug_print(f" 🔄 重复次数: {repeats}")
action_sequence.append(create_action_log(f"分离容器: {final_vessel}", "🧪"))
action_sequence.append(create_action_log(f"溶剂体积: {final_volume}mL", "📏"))
action_sequence.append(create_action_log(f"重复次数: {repeats}", "🔄"))
# 验证必需参数
if not purpose:
purpose = "separate"
if not product_phase:
product_phase = "top"
if purpose not in ["wash", "extract", "separate"]:
debug_print(f"⚠️ 未知的分离目的 '{purpose}',使用默认值 'separate'")
purpose = "separate"
action_sequence.append(create_action_log(f"未知目的,使用: {purpose}", "⚠️"))
if product_phase not in ["top", "bottom"]:
debug_print(f"⚠️ 未知的产物相 '{product_phase}',使用默认值 'top'")
product_phase = "top"
action_sequence.append(create_action_log(f"未知相别,使用: {product_phase}", "⚠️"))
debug_print("✅ 参数验证通过")
action_sequence.append(create_action_log("参数验证通过", ""))
# === 查找设备 ===
debug_print("🔍 步骤2: 查找设备...")
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
# 查找分离器设备
separator_device = find_separator_device(G, final_vessel)
if separator_device:
action_sequence.append(create_action_log(f"找到分离器设备: {separator_device}", "🧪"))
else:
solvents = [solvent] * repeats
debug_print("⚠️ 未找到分离器设备,可能无法执行分离")
action_sequence.append(create_action_log("未找到分离器设备", "⚠️"))
# TODO: 通过设备连接图找到分离容器的控制器、底部出口
separator_controller = f"{separation_vessel}_controller"
separation_vessel_bottom = f"flask_{separation_vessel}"
# 查找搅拌器
stirrer_device = find_connected_stirrer(G, final_vessel)
if stirrer_device:
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_device}", "🌪️"))
else:
action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
transfer_flowrate = flowrate = 2.5
# 查找溶剂容器(如果需要)
solvent_vessel = ""
if solvent and solvent.strip():
solvent_vessel = find_solvent_vessel(G, solvent)
if solvent_vessel:
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "💧"))
else:
action_sequence.append(create_action_log(f"未找到溶剂容器: {solvent}", "⚠️"))
if from_vessel != separation_vessel:
pump_action_sequence.append(
{
"device_id": "",
"action_name": "PumpTransferProtocol",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": separation_vessel,
"volume": reactor_volume,
"time": reactor_volume / flowrate,
# "transfer_flowrate": transfer_flowrate,
debug_print(f"📊 设备配置:")
debug_print(f" 🧪 分离器设备: '{separator_device}'")
debug_print(f" 🌪️ 搅拌器设备: '{stirrer_device}'")
debug_print(f" 💧 溶剂容器: '{solvent_vessel}'")
# === 执行分离流程 ===
debug_print("🔍 步骤3: 执行分离流程...")
action_sequence.append(create_action_log("开始分离工作流程", "🎯"))
try:
for repeat_idx in range(repeats):
cycle_num = repeat_idx + 1
debug_print(f"🔄 第{cycle_num}轮: 开始分离循环 {cycle_num}/{repeats}")
action_sequence.append(create_action_log(f"分离循环 {cycle_num}/{repeats} 开始", "🔄"))
# 步骤3.1: 添加溶剂(如果需要)
if solvent_vessel and final_volume > 0:
debug_print(f"🔄 第{cycle_num}轮 步骤1: 添加溶剂 {solvent} ({final_volume}mL)")
action_sequence.append(create_action_log(f"向分离容器添加 {final_volume}mL {solvent}", "💧"))
try:
# 使用pump protocol添加溶剂
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent_vessel,
to_vessel=final_vessel,
volume=final_volume,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.5,
transfer_flowrate=0.5,
rate_spec="",
event="",
through="",
**kwargs
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 溶剂添加完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", ""))
except Exception as e:
debug_print(f"❌ 溶剂添加失败: {str(e)}")
action_sequence.append(create_action_log(f"溶剂添加失败: {str(e)}", ""))
else:
debug_print(f"🔄 第{cycle_num}轮 步骤1: 无需添加溶剂")
action_sequence.append(create_action_log("无需添加溶剂", "⏭️"))
# 步骤3.2: 启动搅拌(如果有搅拌器)
if stirrer_device and stir_time > 0:
debug_print(f"🔄 第{cycle_num}轮 步骤2: 开始搅拌 ({stir_speed}rpm持续 {stir_time}s)")
action_sequence.append(create_action_log(f"开始搅拌: {stir_speed}rpm持续 {stir_time}s", "🌪️"))
action_sequence.append({
"device_id": stirrer_device,
"action_name": "start_stir",
"action_kwargs": {
"vessel": final_vessel,
"stir_speed": stir_speed,
"purpose": f"分离混合 - {purpose}"
}
})
# 搅拌等待
stir_minutes = stir_time / 60
action_sequence.append(create_action_log(f"搅拌中,持续 {stir_minutes:.1f} 分钟", "⏱️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": stir_time}
})
# 停止搅拌
action_sequence.append(create_action_log("停止搅拌器", "🛑"))
action_sequence.append({
"device_id": stirrer_device,
"action_name": "stop_stir",
"action_kwargs": {"vessel": final_vessel}
})
else:
debug_print(f"🔄 第{cycle_num}轮 步骤2: 无需搅拌")
action_sequence.append(create_action_log("无需搅拌", "⏭️"))
# 步骤3.3: 静置分层
if settling_time > 0:
debug_print(f"🔄 第{cycle_num}轮 步骤3: 静置分层 ({settling_time}s)")
settling_minutes = settling_time / 60
action_sequence.append(create_action_log(f"静置分层 ({settling_minutes:.1f} 分钟)", "⚖️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": settling_time}
})
else:
debug_print(f"🔄 第{cycle_num}轮 步骤3: 未指定静置时间")
action_sequence.append(create_action_log("未指定静置时间", "⏭️"))
# 步骤3.4: 执行分离操作
if separator_device:
debug_print(f"🔄 第{cycle_num}轮 步骤4: 执行分离操作")
action_sequence.append(create_action_log(f"执行分离: 收集{product_phase}", "🧪"))
# 调用分离器设备的separate方法
separate_action = {
"device_id": separator_device,
"action_name": "separate",
"action_kwargs": {
"purpose": purpose,
"product_phase": product_phase,
"from_vessel": from_vessel or final_vessel,
"separation_vessel": final_vessel,
"to_vessel": final_to_vessel or final_vessel,
"waste_phase_to_vessel": final_waste_vessel or final_vessel,
"solvent": solvent,
"solvent_volume": final_volume,
"through": through,
"repeats": 1, # 每次调用只做一次分离
"stir_time": 0, # 已经在上面完成
"stir_speed": stir_speed,
"settling_time": 0 # 已经在上面完成
}
}
action_sequence.append(separate_action)
debug_print(f"✅ 分离操作已添加")
action_sequence.append(create_action_log("分离操作完成", ""))
# 收集结果
if final_to_vessel:
action_sequence.append(create_action_log(f"产物 ({product_phase}相) 收集到: {final_to_vessel}", "📦"))
if final_waste_vessel:
action_sequence.append(create_action_log(f"废相收集到: {final_waste_vessel}", "🗑️"))
else:
debug_print(f"🔄 第{cycle_num}轮 步骤4: 无分离器设备,跳过分离")
action_sequence.append(create_action_log("无分离器设备可用", ""))
# 添加等待时间模拟分离
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10.0}
})
# 循环间等待(除了最后一次)
if repeat_idx < repeats - 1:
debug_print(f"🔄 第{cycle_num}轮: 等待下一次循环...")
action_sequence.append(create_action_log("等待下一次循环...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
else:
action_sequence.append(create_action_log(f"分离循环 {cycle_num}/{repeats} 完成", "🌟"))
except Exception as e:
debug_print(f"❌ 分离工作流程执行失败: {str(e)}")
action_sequence.append(create_action_log(f"分离工作流程失败: {str(e)}", ""))
# 添加错误日志
action_sequence.append({
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"分离操作失败: {str(e)}"
}
)
# for i in range(2):
# pump_action_sequence.append(
# {
# "device_id": "",
# "action_name": "CleanProtocol",
# "action_kwargs": {
# "vessel": from_vessel,
# "solvent": "H2O", # Solvent to clean vessel with.
# "volume": solvent_volume, # Optional. Volume of solvent to clean vessel with.
# "temp": 25.0, # Optional. Temperature to heat vessel to while cleaning.
# "repeats": 1
# }
# }
# )
# pump_action_sequence.append(
# {
# "device_id": "",
# "action_name": "CleanProtocol",
# "action_kwargs": {
# "vessel": from_vessel,
# "solvent": "CH2Cl2", # Solvent to clean vessel with.
# "volume": solvent_volume, # Optional. Volume of solvent to clean vessel with.
# "temp": 25.0, # Optional. Temperature to heat vessel to while cleaning.
# "repeats": 1
# }
# }
# )
})
# 生成泵操作的动作序列
for i in range(repeats):
# 找到当次萃取所用溶剂
solvent_thistime = solvents[i]
solvent_vessel = f"flask_{solvent_thistime}"
pump_action_sequence.append(
{
"device_id": "",
"action_name": "PumpTransferProtocol",
"action_kwargs": {
"from_vessel": solvent_vessel,
"to_vessel": separation_vessel,
"volume": solvent_volume,
"time": solvent_volume / flowrate,
# "transfer_flowrate": transfer_flowrate,
}
}
)
pump_action_sequence.extend([
# 搅拌、静置
{
"device_id": separator_controller,
"action_name": "stir",
"action_kwargs": {
"stir_time": stir_time,
"stir_speed": stir_speed,
"settling_time": settling_time
}
},
# 分液(判断电导突跃)
{
"device_id": separator_controller,
"action_name": "valve_open",
"action_kwargs": {
"command": "delta > 0.05"
}
}
])
if product_phase == "bottom":
# 产物转移到目标瓶
pump_action_sequence.append(
{
"device_id": "",
"action_name": "PumpTransferProtocol",
"action_kwargs": {
"from_vessel": separation_vessel_bottom,
"to_vessel": to_vessel,
"volume": 250.0,
"time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate,
}
}
)
# 放出上面那一相60秒后关阀门
pump_action_sequence.append(
{
"device_id": separator_controller,
"action_name": "valve_open",
"action_kwargs": {
"command": "time > 60"
}
}
)
# 弃去上面那一相进废液
pump_action_sequence.append(
{
"device_id": "",
"action_name": "PumpTransferProtocol",
"action_kwargs": {
"from_vessel": separation_vessel_bottom,
"to_vessel": waste_vessel,
"volume": 250.0,
"time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate,
}
}
)
elif product_phase == "top":
# 弃去下面那一相进废液
pump_action_sequence.append(
{
"device_id": "",
"action_name": "PumpTransferProtocol",
"action_kwargs": {
"from_vessel": separation_vessel_bottom,
"to_vessel": waste_vessel,
"volume": 250.0,
"time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate,
}
}
)
# 放出上面那一相
pump_action_sequence.append(
{
"device_id": separator_controller,
"action_name": "valve_open",
"action_kwargs": {
"command": "time > 60"
}
}
)
# 产物转移到目标瓶
pump_action_sequence.append(
{
"device_id": "",
"action_name": "PumpTransferProtocol",
"action_kwargs": {
"from_vessel": separation_vessel_bottom,
"to_vessel": to_vessel,
"volume": 250.0,
"time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate,
}
}
)
elif product_phase == "organic":
pass
# 如果不是最后一次,从中转瓶转移回分液漏斗
if i < repeats - 1:
pump_action_sequence.append(
{
"device_id": "",
"action_name": "PumpTransferProtocol",
"action_kwargs": {
"from_vessel": to_vessel,
"to_vessel": separation_vessel,
"volume": 250.0,
"time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate,
}
}
)
return pump_action_sequence
# === 最终结果 ===
total_time = (stir_time + settling_time + 15) * repeats # 估算总时间
debug_print("=" * 60)
debug_print(f"🎉 分离协议生成完成")
debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
debug_print(f" 🥼 分离容器: {final_vessel}")
debug_print(f" 🎯 分离目的: {purpose}")
debug_print(f" 📊 产物相: {product_phase}")
debug_print(f" 🔄 重复次数: {repeats}")
if solvent:
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
if final_to_vessel:
debug_print(f" 🎯 产物容器: {final_to_vessel}")
if final_waste_vessel:
debug_print(f" 🗑️ 废液容器: {final_waste_vessel}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"分离协议完成: {final_vessel} ({purpose}{repeats} 次循环)"
if solvent:
summary_msg += f",使用 {final_volume}mL {solvent}"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
# === 便捷函数 ===
def separate_phases_only(G: nx.DiGraph, vessel: str, product_phase: str = "top",
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
"""仅进行相分离(不添加溶剂)"""
debug_print(f"⚡ 快速相分离: {vessel} ({product_phase}相)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="separate",
product_phase=product_phase,
product_vessel=product_vessel,
waste_vessel=waste_vessel
)
def wash_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
product_phase: str = "top", repeats: int = 1) -> List[Dict[str, Any]]:
"""用溶剂洗涤"""
debug_print(f"🧽 用{solvent}洗涤: {vessel} ({repeats} 次)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="wash",
product_phase=product_phase,
solvent=solvent,
volume=volume,
repeats=repeats
)
def extract_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
product_phase: str = "bottom", repeats: int = 3) -> List[Dict[str, Any]]:
"""用溶剂萃取"""
debug_print(f"🧪 用{solvent}萃取: {vessel} ({repeats} 次)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="extract",
product_phase=product_phase,
solvent=solvent,
volume=volume,
repeats=repeats
)
def separate_aqueous_organic(G: nx.DiGraph, vessel: str, organic_phase: str = "top",
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
"""水-有机相分离"""
debug_print(f"💧 水-有机相分离: {vessel} (有机相: {organic_phase})")
return generate_separate_protocol(
G, vessel=vessel,
purpose="separate",
product_phase=organic_phase,
product_vessel=product_vessel,
waste_vessel=waste_vessel
)
# 测试函数
def test_separate_protocol():
"""测试分离协议的各种参数解析"""
debug_print("=== 分离协议增强中文版测试 ===")
# 测试体积解析
debug_print("🧪 测试体积解析...")
volumes = ["200 mL", "?", 100.0, "1 L", "500 μL", "未知", "50毫升"]
for vol in volumes:
result = parse_volume_input(vol)
debug_print(f"📊 体积解析结果: {vol} -> {result}mL")
debug_print("✅ 测试完成")
if __name__ == "__main__":
test_separate_protocol()

View File

@@ -0,0 +1,342 @@
from typing import List, Dict, Any, Union
import networkx as nx
import logging
import re
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"🌪️ [STIR] {message}", flush=True)
logger.info(f"[STIR] {message}")
def parse_time_input(time_input: Union[str, float, int], default_unit: str = "s") -> float:
"""
统一的时间解析函数(精简版)
Args:
time_input: 时间输入(如 "30 min", "1 h", "300", "?", 60.0
default_unit: 默认单位(默认为秒)
Returns:
float: 时间(秒)
"""
if not time_input:
return 100.0 # 默认100秒
# 🔢 处理数值输入
if isinstance(time_input, (int, float)):
result = float(time_input)
debug_print(f"⏰ 数值时间: {time_input}{result}s")
return result
# 📝 处理字符串输入
time_str = str(time_input).lower().strip()
debug_print(f"🔍 解析时间: '{time_str}'")
# ❓ 特殊值处理
special_times = {
'?': 300.0, 'unknown': 300.0, 'tbd': 300.0,
'briefly': 30.0, 'quickly': 60.0, 'slowly': 600.0,
'several minutes': 300.0, 'few minutes': 180.0, 'overnight': 3600.0
}
if time_str in special_times:
result = special_times[time_str]
debug_print(f"🎯 特殊时间: '{time_str}'{result}s ({result/60:.1f}分钟)")
return result
# 🔢 纯数字处理
try:
result = float(time_str)
debug_print(f"⏰ 纯数字: {time_str}{result}s")
return result
except ValueError:
pass
# 📐 正则表达式解析
pattern = r'(\d+\.?\d*)\s*([a-z]*)'
match = re.match(pattern, time_str)
if not match:
debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值: 100s")
return 100.0
value = float(match.group(1))
unit = match.group(2) or default_unit
# 📏 单位转换
unit_multipliers = {
's': 1.0, 'sec': 1.0, 'second': 1.0, 'seconds': 1.0,
'm': 60.0, 'min': 60.0, 'mins': 60.0, 'minute': 60.0, 'minutes': 60.0,
'h': 3600.0, 'hr': 3600.0, 'hrs': 3600.0, 'hour': 3600.0, 'hours': 3600.0,
'd': 86400.0, 'day': 86400.0, 'days': 86400.0
}
multiplier = unit_multipliers.get(unit, 1.0)
result = value * multiplier
debug_print(f"✅ 时间解析: '{time_str}'{value} {unit}{result}s ({result/60:.1f}分钟)")
return result
def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str:
"""查找与指定容器相连的搅拌设备"""
debug_print(f"🔍 查找搅拌设备,目标容器: {vessel} 🥽")
# 🔧 查找所有搅拌设备
stirrer_nodes = []
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if 'stirrer' in node_class.lower() or 'virtual_stirrer' in node_class:
stirrer_nodes.append(node)
debug_print(f"🎉 找到搅拌设备: {node} 🌪️")
# 🔗 检查连接
if vessel and stirrer_nodes:
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"✅ 搅拌设备 '{stirrer}' 与容器 '{vessel}' 相连 🔗")
return stirrer
# 🎯 使用第一个可用设备
if stirrer_nodes:
selected = stirrer_nodes[0]
debug_print(f"🔧 使用第一个搅拌设备: {selected} 🌪️")
return selected
# 🆘 默认设备
debug_print("⚠️ 未找到搅拌设备,使用默认设备 🌪️")
return "stirrer_1"
def validate_and_fix_params(stir_time: float, stir_speed: float, settling_time: float) -> tuple:
"""验证和修正参数"""
# ⏰ 搅拌时间验证
if stir_time < 0:
debug_print(f"⚠️ 搅拌时间 {stir_time}s 无效,修正为 100s 🕐")
stir_time = 100.0
elif stir_time > 100: # 限制为100s
debug_print(f"⚠️ 搅拌时间 {stir_time}s 过长,仿真运行时,修正为 100s 🕐")
stir_time = 100.0
else:
debug_print(f"✅ 搅拌时间 {stir_time}s ({stir_time/60:.1f}分钟) 有效 ⏰")
# 🌪️ 搅拌速度验证
if stir_speed < 10.0 or stir_speed > 1500.0:
debug_print(f"⚠️ 搅拌速度 {stir_speed} RPM 超出范围,修正为 300 RPM 🌪️")
stir_speed = 300.0
else:
debug_print(f"✅ 搅拌速度 {stir_speed} RPM 在正常范围内 🌪️")
# ⏱️ 沉降时间验证
if settling_time < 0 or settling_time > 600: # 限制为10分钟
debug_print(f"⚠️ 沉降时间 {settling_time}s 超出范围,修正为 60s ⏱️")
settling_time = 60.0
else:
debug_print(f"✅ 沉降时间 {settling_time}s 在正常范围内 ⏱️")
return stir_time, stir_speed, settling_time
def generate_stir_protocol(
G: nx.DiGraph,
vessel: str,
time: Union[str, float, int] = "300",
stir_time: Union[str, float, int] = "0",
time_spec: str = "",
event: str = "",
stir_speed: float = 300.0,
settling_time: Union[str, float] = "60",
**kwargs
) -> List[Dict[str, Any]]:
"""
生成搅拌操作的协议序列(精简版)
"""
debug_print("🌪️" * 20)
debug_print("🚀 开始生成搅拌协议 ✨")
debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}")
debug_print(f" ⏰ time: {time}")
debug_print(f" 🕐 stir_time: {stir_time}")
debug_print(f" 🎯 time_spec: {time_spec}")
debug_print(f" 🌪️ stir_speed: {stir_speed} RPM")
debug_print(f" ⏱️ settling_time: {settling_time}")
debug_print("🌪️" * 20)
# 📋 参数验证
debug_print("📍 步骤1: 参数验证... 🔧")
if not vessel:
debug_print("❌ vessel 参数不能为空! 😱")
raise ValueError("vessel 参数不能为空")
if vessel not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中! 😞")
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
debug_print("✅ 基础参数验证通过 🎯")
# 🔄 参数解析
debug_print("📍 步骤2: 参数解析... ⚡")
# 确定实际时间优先级time_spec > stir_time > time
if time_spec:
parsed_time = parse_time_input(time_spec)
debug_print(f"🎯 使用time_spec: '{time_spec}'{parsed_time}s")
elif stir_time not in ["0", 0, 0.0]:
parsed_time = parse_time_input(stir_time)
debug_print(f"🎯 使用stir_time: {stir_time}{parsed_time}s")
else:
parsed_time = parse_time_input(time)
debug_print(f"🎯 使用time: {time}{parsed_time}s")
# 解析沉降时间
parsed_settling_time = parse_time_input(settling_time)
# 🕐 模拟运行时间优化
debug_print(" ⏱️ 检查模拟运行时间限制...")
original_stir_time = parsed_time
original_settling_time = parsed_settling_time
# 搅拌时间限制为60秒
stir_time_limit = 60.0
if parsed_time > stir_time_limit:
parsed_time = stir_time_limit
debug_print(f" 🎮 搅拌时间优化: {original_stir_time}s → {parsed_time}s ⚡")
# 沉降时间限制为30秒
settling_time_limit = 30.0
if parsed_settling_time > settling_time_limit:
parsed_settling_time = settling_time_limit
debug_print(f" 🎮 沉降时间优化: {original_settling_time}s → {parsed_settling_time}s ⚡")
# 参数修正
parsed_time, stir_speed, parsed_settling_time = validate_and_fix_params(
parsed_time, stir_speed, parsed_settling_time
)
debug_print(f"🎯 最终参数: time={parsed_time}s, speed={stir_speed}RPM, settling={parsed_settling_time}s")
# 🔍 查找设备
debug_print("📍 步骤3: 查找搅拌设备... 🔍")
try:
stirrer_id = find_connected_stirrer(G, vessel)
debug_print(f"🎉 使用搅拌设备: {stirrer_id}")
except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 🚀 生成动作
debug_print("📍 步骤4: 生成搅拌动作... 🌪️")
action_sequence = []
stir_action = {
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"vessel": vessel,
"time": str(time), # 保持原始格式
"event": event,
"time_spec": time_spec,
"stir_time": float(parsed_time), # 确保是数字
"stir_speed": float(stir_speed), # 确保是数字
"settling_time": float(parsed_settling_time) # 确保是数字
}
}
action_sequence.append(stir_action)
debug_print("✅ 搅拌动作已添加 🌪️✨")
# 显示时间优化信息
if original_stir_time != parsed_time or original_settling_time != parsed_settling_time:
debug_print(f" 🎭 模拟优化说明:")
debug_print(f" 搅拌时间: {original_stir_time/60:.1f}分钟 → {parsed_time/60:.1f}分钟")
debug_print(f" 沉降时间: {original_settling_time/60:.1f}分钟 → {parsed_settling_time/60:.1f}分钟")
# 🎊 总结
debug_print("🎊" * 20)
debug_print(f"🎉 搅拌协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 搅拌容器: {vessel}")
debug_print(f"🌪️ 搅拌参数: {stir_speed} RPM, {parsed_time}s, 沉降 {parsed_settling_time}s")
debug_print(f"⏱️ 预计总时间: {(parsed_time + parsed_settling_time)/60:.1f} 分钟 ⌛")
debug_print("🎊" * 20)
return action_sequence
def generate_start_stir_protocol(
G: nx.DiGraph,
vessel: str,
stir_speed: float = 300.0,
purpose: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""生成开始搅拌操作的协议序列"""
debug_print("🔄 开始生成启动搅拌协议 ✨")
debug_print(f"🥽 vessel: {vessel}, 🌪️ speed: {stir_speed} RPM")
# 基础验证
if not vessel or vessel not in G.nodes():
debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效")
# 参数修正
if stir_speed < 10.0 or stir_speed > 1500.0:
debug_print(f"⚠️ 搅拌速度修正: {stir_speed} → 300 RPM 🌪️")
stir_speed = 300.0
# 查找设备
stirrer_id = find_connected_stirrer(G, vessel)
# 生成动作
action_sequence = [{
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": purpose or f"启动搅拌 {stir_speed} RPM"
}
}]
debug_print(f"✅ 启动搅拌协议生成完成 🎯")
return action_sequence
def generate_stop_stir_protocol(
G: nx.DiGraph,
vessel: str,
**kwargs
) -> List[Dict[str, Any]]:
"""生成停止搅拌操作的协议序列"""
debug_print("🛑 开始生成停止搅拌协议 ✨")
debug_print(f"🥽 vessel: {vessel}")
# 基础验证
if not vessel or vessel not in G.nodes():
debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效")
# 查找设备
stirrer_id = find_connected_stirrer(G, vessel)
# 生成动作
action_sequence = [{
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel
}
}]
debug_print(f"✅ 停止搅拌协议生成完成 🎯")
return action_sequence
# 测试函数
def test_stir_protocol():
"""测试搅拌协议"""
debug_print("🧪 === STIR PROTOCOL 测试 === ✨")
debug_print("✅ 测试完成 🎉")
if __name__ == "__main__":
test_stir_protocol()

View File

@@ -0,0 +1,79 @@
from typing import List, Dict, Any
import networkx as nx
def generate_transfer_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float,
amount: str = "",
time: float = 0,
viscous: bool = False,
rinsing_solvent: str = "",
rinsing_volume: float = 0.0,
rinsing_repeats: int = 0,
solid: bool = False
) -> List[Dict[str, Any]]:
"""
生成液体转移操作的协议序列
Args:
G: 有向图,节点为设备和容器
from_vessel: 源容器
to_vessel: 目标容器
volume: 转移体积 (mL)
amount: 数量描述 (可选)
time: 转移时间 (秒,可选)
viscous: 是否为粘性液体
rinsing_solvent: 冲洗溶剂 (可选)
rinsing_volume: 冲洗体积 (mL可选)
rinsing_repeats: 冲洗重复次数
solid: 是否涉及固体
Returns:
List[Dict[str, Any]]: 转移操作的动作序列
Raises:
ValueError: 当找不到合适的转移设备时抛出异常
Examples:
transfer_protocol = generate_transfer_protocol(G, "flask_1", "reactor", 10.0)
"""
action_sequence = []
# 查找虚拟转移泵设备用于液体转移 - 修复:应该查找 virtual_transfer_pump
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_transfer_pump']
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备进行液体转移")
# 使用第一个可用的泵
pump_id = pump_nodes[0]
# 验证容器是否存在
if from_vessel not in G.nodes():
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
if to_vessel not in G.nodes():
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
# 执行液体转移操作 - 参数完全匹配Transfer.action
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"volume": volume,
"amount": amount,
"time": time,
"viscous": viscous,
"rinsing_solvent": rinsing_solvent,
"rinsing_volume": rinsing_volume,
"rinsing_repeats": rinsing_repeats,
"solid": solid
}
})
return action_sequence

View File

@@ -0,0 +1,206 @@
"""
统一的单位解析工具模块
支持时间、体积、质量等各种单位的解析
"""
import re
import logging
from typing import Union
logger = logging.getLogger(__name__)
def debug_print(message, prefix="[UNIT_PARSER]"):
"""调试输出"""
print(f"{prefix} {message}", flush=True)
logger.info(f"{prefix} {message}")
def parse_time_with_units(time_input: Union[str, float, int], default_unit: str = "s") -> float:
"""
解析带单位的时间输入
Args:
time_input: 时间输入(如 "30 min", "1 h", "300", "?", 60.0
default_unit: 默认单位(默认为秒)
Returns:
float: 时间(秒)
"""
if not time_input:
return 0.0
# 处理数值输入
if isinstance(time_input, (int, float)):
result = float(time_input)
debug_print(f"数值时间输入: {time_input}{result}s默认单位")
return result
# 处理字符串输入
time_str = str(time_input).lower().strip()
debug_print(f"解析时间字符串: '{time_str}'")
# 处理特殊值
if time_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_time = 300.0 # 5分钟默认值
debug_print(f"检测到未知时间,使用默认值: {default_time}s")
return default_time
# 如果是纯数字,使用默认单位
try:
value = float(time_str)
if default_unit == "s":
result = value
elif default_unit in ["min", "minute"]:
result = value * 60.0
elif default_unit in ["h", "hour"]:
result = value * 3600.0
else:
result = value # 默认秒
debug_print(f"纯数字输入: {time_str}{result}s单位: {default_unit}")
return result
except ValueError:
pass
# 使用正则表达式匹配数字和单位
pattern = r'(\d+\.?\d*)\s*([a-z]*)'
match = re.match(pattern, time_str)
if not match:
debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值: 60s")
return 60.0
value = float(match.group(1))
unit = match.group(2) or default_unit
# 单位转换映射
unit_multipliers = {
# 秒
's': 1.0,
'sec': 1.0,
'second': 1.0,
'seconds': 1.0,
# 分钟
'm': 60.0,
'min': 60.0,
'mins': 60.0,
'minute': 60.0,
'minutes': 60.0,
# 小时
'h': 3600.0,
'hr': 3600.0,
'hrs': 3600.0,
'hour': 3600.0,
'hours': 3600.0,
# 天
'd': 86400.0,
'day': 86400.0,
'days': 86400.0,
}
multiplier = unit_multipliers.get(unit, 1.0)
result = value * multiplier
debug_print(f"时间解析: '{time_str}'{value} {unit}{result}s")
return result
def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: str = "mL") -> float:
"""
解析带单位的体积输入
Args:
volume_input: 体积输入(如 "100 mL", "2.5 L", "500", "?", 100.0
default_unit: 默认单位(默认为毫升)
Returns:
float: 体积(毫升)
"""
if not volume_input:
return 0.0
# 处理数值输入
if isinstance(volume_input, (int, float)):
result = float(volume_input)
debug_print(f"数值体积输入: {volume_input}{result}mL默认单位")
return result
# 处理字符串输入
volume_str = str(volume_input).lower().strip()
debug_print(f"解析体积字符串: '{volume_str}'")
# 处理特殊值
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_volume = 50.0 # 50mL默认值
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
return default_volume
# 如果是纯数字,使用默认单位
try:
value = float(volume_str)
if default_unit.lower() in ["ml", "milliliter"]:
result = value
elif default_unit.lower() in ["l", "liter"]:
result = value * 1000.0
elif default_unit.lower() in ["μl", "ul", "microliter"]:
result = value / 1000.0
else:
result = value # 默认mL
debug_print(f"纯数字输入: {volume_str}{result}mL单位: {default_unit}")
return result
except ValueError:
pass
# 移除空格并提取数字和单位
volume_clean = re.sub(r'\s+', '', volume_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
if not match:
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值: 50mL")
return 50.0
value = float(match.group(1))
unit = match.group(2) or default_unit.lower()
# 转换为毫升
if unit in ['l', 'liter']:
volume = value * 1000.0 # L -> mL
elif unit in ['μl', 'ul', 'microliter']:
volume = value / 1000.0 # μL -> mL
else: # ml, milliliter 或默认
volume = value # 已经是mL
debug_print(f"体积解析: '{volume_str}'{value} {unit}{volume}mL")
return volume
# 测试函数
def test_unit_parser():
"""测试单位解析功能"""
print("=== 单位解析器测试 ===")
# 测试时间解析
time_tests = [
"30 min", "1 h", "300", "5.5 h", "?", 60.0, "2 hours", "30 s"
]
print("\n时间解析测试:")
for time_input in time_tests:
result = parse_time_with_units(time_input)
print(f" {time_input}{result}s ({result/60:.1f}min)")
# 测试体积解析
volume_tests = [
"100 mL", "2.5 L", "500", "?", 100.0, "500 μL", "1 liter"
]
print("\n体积解析测试:")
for volume_input in volume_tests:
result = parse_volume_with_units(volume_input)
print(f" {volume_input}{result}mL")
print("\n✅ 测试完成")
if __name__ == "__main__":
test_unit_parser()

View File

@@ -0,0 +1,316 @@
from typing import List, Dict, Any, Union
import networkx as nx
import logging
import re
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"🧼 [WASH_SOLID] {message}", flush=True)
logger.info(f"[WASH_SOLID] {message}")
def parse_time_input(time_input: Union[str, float, int]) -> float:
"""统一时间解析函数(精简版)"""
if not time_input:
return 0.0
# 🔢 处理数值输入
if isinstance(time_input, (int, float)):
result = float(time_input)
debug_print(f"⏰ 数值时间: {time_input}{result}s")
return result
# 📝 处理字符串输入
time_str = str(time_input).lower().strip()
# ❓ 特殊值快速处理
special_times = {
'?': 60.0, 'unknown': 60.0, 'briefly': 30.0,
'quickly': 45.0, 'slowly': 120.0
}
if time_str in special_times:
result = special_times[time_str]
debug_print(f"🎯 特殊时间: '{time_str}'{result}s")
return result
# 🔢 数字提取(简化正则)
try:
# 提取数字
numbers = re.findall(r'\d+\.?\d*', time_str)
if numbers:
value = float(numbers[0])
# 简化单位判断
if any(unit in time_str for unit in ['min', 'm']):
result = value * 60.0
elif any(unit in time_str for unit in ['h', 'hour']):
result = value * 3600.0
else:
result = value # 默认秒
debug_print(f"✅ 时间解析: '{time_str}'{result}s")
return result
except:
pass
debug_print(f"⚠️ 时间解析失败: '{time_str}'使用默认60s")
return 60.0
def parse_volume_input(volume: Union[float, str], volume_spec: str = "", mass: str = "") -> float:
"""统一体积解析函数(精简版)"""
debug_print(f"💧 解析体积: volume={volume}, spec='{volume_spec}', mass='{mass}'")
# 🎯 优先级1volume_spec快速映射
if volume_spec:
spec_map = {
'small': 20.0, 'medium': 50.0, 'large': 100.0,
'minimal': 10.0, 'normal': 50.0, 'generous': 150.0
}
for key, val in spec_map.items():
if key in volume_spec.lower():
debug_print(f"🎯 规格匹配: '{volume_spec}'{val}mL")
return val
# 🧮 优先级2mass转体积简化1g=1mL
if mass:
try:
numbers = re.findall(r'\d+\.?\d*', mass)
if numbers:
value = float(numbers[0])
if 'mg' in mass.lower():
result = value / 1000.0
elif 'kg' in mass.lower():
result = value * 1000.0
else:
result = value # 默认g
debug_print(f"⚖️ 质量转换: {mass}{result}mL")
return result
except:
pass
# 📦 优先级3volume
if volume:
if isinstance(volume, (int, float)):
result = float(volume)
debug_print(f"💧 数值体积: {volume}{result}mL")
return result
elif isinstance(volume, str):
try:
# 提取数字
numbers = re.findall(r'\d+\.?\d*', volume)
if numbers:
value = float(numbers[0])
# 简化单位判断
if 'l' in volume.lower() and 'ml' not in volume.lower():
result = value * 1000.0 # L转mL
else:
result = value # 默认mL
debug_print(f"💧 字符串体积: '{volume}'{result}mL")
return result
except:
pass
# 默认值
debug_print(f"⚠️ 体积解析失败使用默认50mL")
return 50.0
def find_solvent_source(G: nx.DiGraph, solvent: str) -> str:
"""查找溶剂源(精简版)"""
debug_print(f"🔍 查找溶剂源: {solvent}")
# 简化搜索列表
search_patterns = [
f"flask_{solvent}", f"bottle_{solvent}", f"reagent_{solvent}",
"liquid_reagent_bottle_1", "flask_1", "solvent_bottle"
]
for pattern in search_patterns:
if pattern in G.nodes():
debug_print(f"🎉 找到溶剂源: {pattern}")
return pattern
debug_print(f"⚠️ 使用默认溶剂源: flask_{solvent}")
return f"flask_{solvent}"
def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
"""查找滤液容器(精简版)"""
debug_print(f"🔍 查找滤液容器: {filtrate_vessel}")
# 如果指定了且存在,直接使用
if filtrate_vessel and filtrate_vessel in G.nodes():
debug_print(f"✅ 使用指定容器: {filtrate_vessel}")
return filtrate_vessel
# 简化搜索列表
default_vessels = ["waste_workup", "filtrate_vessel", "flask_1", "collection_bottle_1"]
for vessel in default_vessels:
if vessel in G.nodes():
debug_print(f"🎉 找到滤液容器: {vessel}")
return vessel
debug_print(f"⚠️ 使用默认滤液容器: waste_workup")
return "waste_workup"
def generate_wash_solid_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: Union[float, str] = "50",
filtrate_vessel: str = "",
temp: float = 25.0,
stir: bool = False,
stir_speed: float = 0.0,
time: Union[str, float] = "0",
repeats: int = 1,
volume_spec: str = "",
repeats_spec: str = "",
mass: str = "",
event: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
生成固体清洗协议(精简版)
"""
debug_print("🧼" * 20)
debug_print("🚀 开始生成固体清洗协议 ✨")
debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}")
debug_print(f" 🧪 solvent: {solvent}")
debug_print(f" 💧 volume: {volume}")
debug_print(f" ⏰ time: {time}")
debug_print(f" 🔄 repeats: {repeats}")
debug_print("🧼" * 20)
# 📋 快速验证
if not vessel or vessel not in G.nodes():
debug_print("❌ 容器验证失败! 😱")
raise ValueError("vessel 参数无效")
if not solvent:
debug_print("❌ 溶剂不能为空! 😱")
raise ValueError("solvent 参数不能为空")
debug_print("✅ 基础验证通过 🎯")
# 🔄 参数解析
debug_print("📍 步骤1: 参数解析... ⚡")
final_volume = parse_volume_input(volume, volume_spec, mass)
final_time = parse_time_input(time)
# 重复次数处理(简化)
if repeats_spec:
spec_map = {'few': 2, 'several': 3, 'many': 4, 'thorough': 5}
final_repeats = next((v for k, v in spec_map.items() if k in repeats_spec.lower()), repeats)
else:
final_repeats = max(1, min(repeats, 5)) # 限制1-5次
# 🕐 模拟时间优化
debug_print(" ⏱️ 模拟时间优化...")
original_time = final_time
if final_time > 60.0:
final_time = 60.0 # 限制最长60秒
debug_print(f" 🎮 时间优化: {original_time}s → {final_time}s ⚡")
# 参数修正
temp = max(25.0, min(temp, 80.0)) # 温度范围25-80°C
stir_speed = max(0.0, min(stir_speed, 300.0)) if stir else 0.0 # 速度范围0-300
debug_print(f"🎯 最终参数: 体积={final_volume}mL, 时间={final_time}s, 重复={final_repeats}")
# 🔍 查找设备
debug_print("📍 步骤2: 查找设备... 🔍")
try:
solvent_source = find_solvent_source(G, solvent)
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel)
debug_print(f"🎉 设备配置完成 ✨")
except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"设备查找失败: {str(e)}")
# 🚀 生成动作序列
debug_print("📍 步骤3: 生成清洗动作... 🧼")
action_sequence = []
for cycle in range(final_repeats):
debug_print(f" 🔄 第{cycle+1}/{final_repeats}次清洗...")
# 1. 转移溶剂
try:
from .pump_protocol import generate_pump_protocol_with_rinsing
transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent_source,
to_vessel=vessel,
volume=final_volume,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.5,
transfer_flowrate=0.5
)
if transfer_actions:
action_sequence.extend(transfer_actions)
debug_print(f" ✅ 转移动作: {len(transfer_actions)}个 🚚")
except Exception as e:
debug_print(f" ❌ 转移失败: {str(e)} 😞")
# 2. 搅拌(如果需要)
if stir and final_time > 0:
stir_action = {
"device_id": "stirrer_1",
"action_name": "stir",
"action_kwargs": {
"vessel": vessel,
"time": str(time),
"stir_time": final_time,
"stir_speed": stir_speed,
"settling_time": 10.0 # 🕐 缩短沉降时间
}
}
action_sequence.append(stir_action)
debug_print(f" ✅ 搅拌动作: {final_time}s, {stir_speed}RPM 🌪️")
# 3. 过滤
filter_action = {
"device_id": "filter_1",
"action_name": "filter",
"action_kwargs": {
"vessel": vessel,
"filtrate_vessel": actual_filtrate_vessel,
"temp": temp,
"volume": final_volume
}
}
action_sequence.append(filter_action)
debug_print(f" ✅ 过滤动作: → {actual_filtrate_vessel} 🌊")
# 4. 等待(缩短时间)
wait_time = 5.0 # 🕐 缩短等待时间10s → 5s
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": wait_time}
})
debug_print(f" ✅ 等待: {wait_time}s ⏰")
# 🎊 总结
debug_print("🧼" * 20)
debug_print(f"🎉 固体清洗协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 清洗容器: {vessel}")
debug_print(f"🧪 使用溶剂: {solvent}")
debug_print(f"💧 清洗体积: {final_volume}mL × {final_repeats}")
debug_print(f"⏱️ 预计总时间: {(final_time + 5) * final_repeats / 60:.1f} 分钟")
debug_print("🧼" * 20)
return action_sequence

View File

@@ -10,8 +10,9 @@ from unilabos.utils import logger
class BasicConfig:
ENV = "pro" # 'test'
config_path = ""
is_host_mode = True # 从registry.py移动过来
is_host_mode = True
slave_no_host = False # 是否跳过rclient.wait_for_service()
upload_registry = False
machine_name = "undefined"
vis_2d_enable = False

View File

@@ -0,0 +1,9 @@
# Default initial positions for full_dev's ros2_control fake system
initial_positions:
arm_base_joint: 0
arm_link_1_joint: 0
arm_link_2_joint: 0
arm_link_3_joint: 0
gripper_base_joint: 0
gripper_right_joint: 0.03

View File

@@ -0,0 +1,40 @@
# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed
# For beginners, we downscale velocity and acceleration limits.
# You can always specify higher scaling factors (<= 1.0) in your motion requests. # Increase the values below to 1.0 to always move at maximum speed.
default_velocity_scaling_factor: 0.1
default_acceleration_scaling_factor: 0.1
# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
joint_limits:
arm_base_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
arm_link_1_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
arm_link_2_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
arm_link_3_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
gripper_base_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
gripper_right_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0

View File

@@ -0,0 +1,4 @@
arm:
kinematics_solver: lma_kinematics_plugin/LMAKinematicsPlugin
kinematics_solver_search_resolution: 0.0050000000000000001
kinematics_solver_timeout: 0.0050000000000000001

View File

@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="arm_slider_ros2_control" params="device_name mesh_path">
<xacro:property name="initial_positions" value="${load_yaml(mesh_path + '/devices/arm_slider/config/initial_positions.yaml')['initial_positions']}"/>
<ros2_control name="${device_name}arm_slider" type="system">
<hardware>
<!-- By default, set up controllers for simulation. This won't work on real hardware -->
<plugin>mock_components/GenericSystem</plugin>
</hardware>
<joint name="${device_name}arm_base_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['arm_base_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}arm_link_1_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['arm_link_1_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}arm_link_2_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['arm_link_2_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}arm_link_3_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['arm_link_3_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}gripper_base_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['gripper_base_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}gripper_right_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['gripper_right_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
</ros2_control>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--This does not replace URDF, and is not an extension of URDF.
This is a format for representing semantic information about the robot structure.
A URDF file must exist for this robot as well, where the joints and the links that are referenced are defined
-->
<robot xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:macro name="arm_slider_srdf" params="device_name">
<!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc-->
<!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included-->
<!--JOINTS: When a joint is specified, the child link of that joint (which will always exist) is automatically included-->
<!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group-->
<!--SUBGROUPS: Groups can also be formed by referencing to already defined group names-->
<group name="${device_name}arm">
<chain base_link="${device_name}arm_slideway" tip_link="${device_name}gripper_base"/>
</group>
<group name="${device_name}arm_gripper">
<joint name="${device_name}gripper_right_joint"/>
</group>
<!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. -->
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_2" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_1" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_3" reason="Never"/>
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_slideway" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_2" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_3" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_slideway" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_base" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_left" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_right" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_link_3" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_slideway" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_base" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_left" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_right" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}arm_slideway" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_base" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_left" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_right" reason="Never"/>
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_base" reason="Never"/>
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_left" reason="Never"/>
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_right" reason="Never"/>
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_left" reason="Adjacent"/>
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_right" reason="Adjacent"/>
<disable_collisions link1="${device_name}gripper_left" link2="${device_name}gripper_right" reason="Never"/>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,14 @@
{
"arm":
{
"joint_names": [
"arm_base_joint",
"arm_link_1_joint",
"arm_link_2_joint",
"arm_link_3_joint",
"gripper_base_joint"
],
"base_link_name": "device_link",
"end_effector_name": "gripper_base"
}
}

View File

@@ -0,0 +1,29 @@
# MoveIt uses this configuration for controller management
moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager
moveit_simple_controller_manager:
controller_names:
- arm_controller
- gripper_controller
arm_controller:
type: FollowJointTrajectory
action_ns: follow_joint_trajectory
default: true
joints:
- arm_base_joint
- arm_link_1_joint
- arm_link_2_joint
- arm_link_3_joint
- gripper_base_joint
action_ns: follow_joint_trajectory
default: true
gripper_controller:
type: FollowJointTrajectory
action_ns: follow_joint_trajectory
default: true
joints:
- gripper_right_joint
action_ns: follow_joint_trajectory
default: true

Some files were not shown because too many files have changed in this diff Show More