Compare commits

..

103 Commits

Author SHA1 Message Date
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
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
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
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
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
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
137 changed files with 39912 additions and 3847 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

4
.gitignore vendored
View File

@@ -234,3 +234,7 @@ CATKIN_IGNORE
*.graphml
unilabos/device_mesh/view_robot.rviz
# Certs
**/.certs

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
# 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.5-xxxxx.tar.bz2
conda install ros-humble-unilabos-msgs-0.9.8-xxxxx.tar.bz2
# Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装
conda install ros-humble-unilabos-msgs-0.9.5-xxxxx.tar.bz2
conda install ros-humble-unilabos-msgs-0.9.8-xxxxx.tar.bz2
# 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

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

View File

@@ -1,6 +1,6 @@
package:
name: ros-humble-unilabos-msgs
version: 0.9.5
version: 0.9.8
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.5"
version: "0.9.8"
source:
path: ../..

View File

@@ -4,7 +4,7 @@ package_name = 'unilabos'
setup(
name=package_name,
version='0.9.5',
version='0.9.8',
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

@@ -2,4 +2,10 @@
```bash
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,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,43 @@
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"/>

View File

@@ -0,0 +1,969 @@
{
"nodes": [
{
"id": "OrganicSynthesisStation",
"name": "有机化学流程综合测试工作站",
"children": [
"multiway_valve_1",
"multiway_valve_2",
"transfer_pump_1",
"transfer_pump_2",
"reagent_bottle_1",
"reagent_bottle_2",
"reagent_bottle_3",
"reagent_bottle_4",
"reagent_bottle_5",
"centrifuge_1",
"rotavap_1",
"main_reactor",
"heater_1",
"stirrer_1",
"stirrer_2",
"waste_bottle_1",
"waste_bottle_2",
"solenoid_valve_1",
"solenoid_valve_2",
"solenoid_valve_3",
"vacuum_pump_1",
"gas_source_1",
"h2_gas_source",
"filter_1",
"column_1",
"separator_1",
"collection_bottle_1",
"collection_bottle_2",
"collection_bottle_3"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 600,
"y": 400,
"z": 0
},
"config": {
"protocol_type": [
"AddProtocol",
"TransferProtocol",
"StartStirProtocol",
"StopStirProtocol",
"StirProtocol",
"RunColumnProtocol",
"CentrifugeProtocol",
"FilterProtocol",
"CleanVesselProtocol",
"DissolveProtocol",
"FilterThroughProtocol",
"WashSolidProtocol",
"SeparateProtocol",
"EvaporateProtocol",
"HeatChillProtocol",
"HeatChillStartProtocol",
"HeatChillStopProtocol",
"EvacuateAndRefillProtocol",
"PumpTransferProtocol",
"AdjustPHProtocol",
"ResetHandlingProtocol",
"DryProtocol",
"HydrogenateProtocol",
"RecrystallizeProtocol"
]
},
"data": {}
},
{
"id": "multiway_valve_1",
"name": "八通阀门1",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 400,
"y": 300,
"z": 0
},
"config": {
"positions": 8
},
"data": {
"valve_state": "Ready",
"current_position": 1
}
},
{
"id": "multiway_valve_2",
"name": "八通阀门2",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_multiway_valve",
"position": {
"x": 800,
"y": 300,
"z": 0
},
"config": {
"positions": 8
},
"data": {
"valve_state": "Ready",
"current_position": 1
}
},
{
"id": "transfer_pump_1",
"name": "转移泵1",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 350,
"y": 250,
"z": 0
},
"config": {
"max_volume": 25.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0
}
},
{
"id": "transfer_pump_2",
"name": "转移泵2",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 850,
"y": 250,
"z": 0
},
"config": {
"max_volume": 25.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0
}
},
{
"id": "reagent_bottle_1",
"name": "试剂瓶1-DMF",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 200,
"y": 150,
"z": 0
},
"config": {
"volume": 1000.0,
"reagent": "DMF"
},
"data": {
"current_volume": 1000.0,
"reagent_name": "DMF"
}
},
{
"id": "reagent_bottle_2",
"name": "试剂瓶2-乙酸乙酯",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 250,
"y": 150,
"z": 0
},
"config": {
"volume": 1000.0,
"reagent": "ethyl_acetate"
},
"data": {
"current_volume": 1000.0,
"reagent_name": "ethyl_acetate"
}
},
{
"id": "reagent_bottle_3",
"name": "试剂瓶3-己烷",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 300,
"y": 150,
"z": 0
},
"config": {
"volume": 1000.0,
"reagent": "hexane"
},
"data": {
"current_volume": 1000.0,
"reagent_name": "hexane"
}
},
{
"id": "reagent_bottle_4",
"name": "试剂瓶4-甲醇",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 900,
"y": 150,
"z": 0
},
"config": {
"volume": 1000.0,
"reagent": "methanol"
},
"data": {
"current_volume": 1000.0,
"reagent_name": "methanol"
}
},
{
"id": "reagent_bottle_5",
"name": "试剂瓶5-水",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 950,
"y": 150,
"z": 0
},
"config": {
"volume": 1000.0,
"reagent": "water"
},
"data": {
"current_volume": 1000.0,
"reagent_name": "water"
}
},
{
"id": "centrifuge_1",
"name": "离心机",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_centrifuge",
"position": {
"x": 200,
"y": 400,
"z": 0
},
"config": {
"max_speed": 15000.0,
"max_temp": 40.0,
"min_temp": 4.0
},
"data": {
"current_speed": 0.0,
"status": "Idle"
}
},
{
"id": "rotavap_1",
"name": "旋转蒸发仪",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_rotavap",
"position": {
"x": 300,
"y": 400,
"z": 0
},
"config": {
"max_temp": 180.0,
"max_rotation_speed": 280.0
},
"data": {
"status": "Idle",
"current_temp": 25.0,
"rotation_speed": 0.0
}
},
{
"id": "main_reactor",
"name": "主反应器",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 400,
"y": 450,
"z": 0
},
"config": {
"volume": 500.0,
"max_temp": 200.0,
"min_temp": -20.0,
"has_stirrer": true,
"has_heater": true
},
"data": {
"current_volume": 0.0,
"current_temp": 25.0
}
},
{
"id": "heater_1",
"name": "加热器",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 450,
"y": 450,
"z": 0
},
"config": {
"max_temp": 200.0,
"min_temp": -20.0
},
"data": {
"status": "Idle",
"current_temp": 25.0
}
},
{
"id": "stirrer_1",
"name": "搅拌器1",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 350,
"y": 450,
"z": 0
},
"config": {
"max_speed": 2000.0
},
"data": {
"status": "Idle",
"current_speed": 0.0
}
},
{
"id": "stirrer_2",
"name": "搅拌器2",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 351,
"y": 451,
"z": 0
},
"config": {
"max_speed": 2000.0
},
"data": {
"status": "Idle",
"current_speed": 0.0
}
},
{
"id": "waste_bottle_1",
"name": "废液瓶1",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 500,
"y": 400,
"z": 0
},
"config": {
"volume": 2000.0
},
"data": {
"current_volume": 0.0
}
},
{
"id": "waste_bottle_2",
"name": "废液瓶2",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 1100,
"y": 500,
"z": 0
},
"config": {
"volume": 2000.0
},
"data": {
"current_volume": 0.0
}
},
{
"id": "solenoid_valve_1",
"name": "电磁阀1",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_solenoid_valve",
"position": {
"x": 700,
"y": 200,
"z": 0
},
"config": {
"voltage": 12.0,
"response_time": 0.1
},
"data": {
"valve_state": "Closed",
"is_open": false
}
},
{
"id": "solenoid_valve_2",
"name": "电磁阀2",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_solenoid_valve",
"position": {
"x": 700,
"y": 150,
"z": 0
},
"config": {
"voltage": 12.0,
"response_time": 0.1
},
"data": {
"valve_state": "Closed",
"is_open": false
}
},
{
"id": "solenoid_valve_3",
"name": "氢气电磁阀",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_solenoid_valve",
"position": {
"x": 450,
"y": 400,
"z": 0
},
"config": {
"voltage": 12.0,
"response_time": 0.1,
"gas_compatible": true
},
"data": {
"valve_state": "Closed",
"is_open": false
}
},
{
"id": "vacuum_pump_1",
"name": "真空泵",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_vacuum_pump",
"position": {
"x": 650,
"y": 200,
"z": 0
},
"config": {
"max_vacuum": 0.1,
"pump_rate": 50.0
},
"data": {
"status": "Off",
"current_vacuum": 1.0
}
},
{
"id": "gas_source_1",
"name": "气源",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_gas_source",
"position": {
"x": 650,
"y": 150,
"z": 0
},
"config": {},
"data": {
"gas_type": "nitrogen",
"max_pressure": 5.0
}
},
{
"id": "h2_gas_source",
"name": "氢气气源",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_gas_source",
"position": {
"x": 500,
"y": 350,
"z": 0
},
"config": {
"max_pressure": 10.0,
"gas_type": "hydrogen"
},
"data": {
"gas_type": "hydrogen",
"max_pressure": 10.0,
"current_pressure": 0.0,
"status": "OFF"
}
},
{
"id": "filter_1",
"name": "过滤器",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_filter",
"position": {
"x": 900,
"y": 400,
"z": 0
},
"config": {
"filter_type": "membrane",
"max_pressure": 5.0
},
"data": {
"status": "Ready",
"pressure": 0.0
}
},
{
"id": "column_1",
"name": "洗脱柱",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_column",
"position": {
"x": 950,
"y": 400,
"z": 0
},
"config": {
"column_type": "silica_gel",
"length": 30.0,
"diameter": 2.5
},
"data": {
"status": "Ready",
"loaded": false
}
},
{
"id": "separator_1",
"name": "分液器",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_separator",
"position": {
"x": 1000,
"y": 450,
"z": 0
},
"config": {
"volume": 250.0,
"has_phases": true
},
"data": {
"status": "Ready",
"phase_separation": false
}
},
{
"id": "collection_bottle_1",
"name": "接收瓶1",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 900,
"y": 500,
"z": 0
},
"config": {
"volume": 250.0
},
"data": {
"current_volume": 0.0
}
},
{
"id": "collection_bottle_2",
"name": "接收瓶2",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 950,
"y": 500,
"z": 0
},
"config": {
"volume": 250.0
},
"data": {
"current_volume": 0.0
}
},
{
"id": "collection_bottle_3",
"name": "接收瓶3",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "container",
"class": "container",
"position": {
"x": 1050,
"y": 500,
"z": 0
},
"config": {
"volume": 250.0
},
"data": {
"current_volume": 0.0
}
}
],
"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_valve1_reagent1",
"source": "multiway_valve_1",
"target": "reagent_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_1": "1",
"reagent_bottle_1": "top"
}
},
{
"id": "link_valve1_reagent2",
"source": "multiway_valve_1",
"target": "reagent_bottle_2",
"type": "fluid",
"port": {
"multiway_valve_1": "2",
"reagent_bottle_2": "top"
}
},
{
"id": "link_valve1_reagent3",
"source": "multiway_valve_1",
"target": "reagent_bottle_3",
"type": "fluid",
"port": {
"multiway_valve_1": "3",
"reagent_bottle_3": "top"
}
},
{
"id": "link_valve1_centrifuge",
"source": "multiway_valve_1",
"target": "centrifuge_1",
"type": "transport",
"port": {
"multiway_valve_1": "4",
"centrifuge_1": "centrifuge"
}
},
{
"id": "link_valve1_rotavap",
"source": "multiway_valve_1",
"target": "rotavap_1",
"type": "fluid",
"port": {
"multiway_valve_1": "5",
"rotavap_1": "samplein"
}
},
{
"id": "link_valve1_reactor",
"source": "multiway_valve_1",
"target": "main_reactor",
"type": "fluid",
"port": {
"multiway_valve_1": "6",
"main_reactor": "top"
}
},
{
"id": "link_valve1_waste1",
"source": "multiway_valve_1",
"target": "waste_bottle_1",
"type": "fluid",
"port": {
"multiway_valve_1": "7",
"waste_bottle_1": "top"
}
},
{
"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_pump2_valve2",
"source": "transfer_pump_2",
"target": "multiway_valve_2",
"type": "fluid",
"port": {
"transfer_pump_2": "transferpump",
"multiway_valve_2": "transferpump"
}
},
{
"id": "link_valve2_solenoid1",
"source": "multiway_valve_2",
"target": "solenoid_valve_1",
"type": "fluid",
"port": {
"multiway_valve_2": "2",
"solenoid_valve_1": "in"
}
},
{
"id": "link_vacuum_solenoid1",
"source": "vacuum_pump_1",
"target": "solenoid_valve_1",
"type": "fluid",
"port": {
"vacuum_pump_1": "vacuumpump",
"solenoid_valve_1": "out"
}
},
{
"id": "link_valve2_solenoid2",
"source": "multiway_valve_2",
"target": "solenoid_valve_2",
"type": "fluid",
"port": {
"multiway_valve_2": "3",
"solenoid_valve_2": "in"
}
},
{
"id": "link_gas_solenoid2",
"source": "gas_source_1",
"target": "solenoid_valve_2",
"type": "fluid",
"port": {
"gas_source_1": "gassource",
"solenoid_valve_2": "out"
}
},
{
"id": "link_valve2_filter",
"source": "multiway_valve_2",
"target": "filter_1",
"type": "transport",
"port": {
"multiway_valve_2": "4",
"filter_1": "filterin"
}
},
{
"id": "link_valve2_column",
"source": "multiway_valve_2",
"target": "column_1",
"type": "transport",
"port": {
"multiway_valve_2": "5",
"column_1": "columnin"
}
},
{
"id": "link_column_collection2",
"source": "column_1",
"target": "collection_bottle_2",
"type": "transport",
"port": {
"column_1": "columnout",
"collection_bottle_2": "top"
}
},
{
"id": "link_valve2_separator",
"source": "multiway_valve_2",
"target": "separator_1",
"type": "fluid",
"port": {
"multiway_valve_2": "6",
"separator_1": "separatorin"
}
},
{
"id": "link_separator_collection3",
"source": "separator_1",
"target": "collection_bottle_3",
"type": "fluid",
"port": {
"separator_1": "bottomphaseout",
"collection_bottle_3": "top"
}
},
{
"id": "link_valve2_reagent4",
"source": "multiway_valve_2",
"target": "reagent_bottle_4",
"type": "fluid",
"port": {
"multiway_valve_2": "7",
"reagent_bottle_4": "top"
}
},
{
"id": "link_valve2_reagent5",
"source": "multiway_valve_2",
"target": "reagent_bottle_5",
"type": "fluid",
"port": {
"multiway_valve_2": "8",
"reagent_bottle_5": "top"
}
},
{
"id": "mech_stirrer_reactor",
"source": "stirrer_1",
"target": "main_reactor",
"type": "mechanical",
"port": {
"stirrer_1": "stirrer",
"main_reactor": "bind"
}
},
{
"id": "thermal_heater_reactor",
"source": "heater_1",
"target": "main_reactor",
"type": "mechanical",
"port": {
"heater_1": "heatchill",
"main_reactor": "bind"
}
},
{
"id": "link_separator_waste2",
"source": "separator_1",
"target": "waste_bottle_2",
"type": "fluid",
"port": {
"separator_1": "topphaseout",
"waste_bottle_2": "top"
}
},
{
"id": "mech_stirrer2_separator",
"source": "stirrer_2",
"target": "separator_1",
"type": "mechanical",
"port": {
"stirrer_2": "stirrer",
"separator_1": "bind"
}
},
{
"id": "link_filter_filtrate_to_collection1",
"source": "filter_1",
"target": "collection_bottle_1",
"type": "transport",
"port": {
"filter_1": "filtrateout",
"collection_bottle_1": "top"
}
},
{
"id": "link_filter_retentate_to_waste1",
"source": "filter_1",
"target": "waste_bottle_1",
"type": "transport",
"port": {
"filter_1": "retentateout",
"waste_bottle_1": "top"
}
},
{
"id": "link_h2_gas_to_valve3",
"source": "h2_gas_source",
"target": "solenoid_valve_3",
"type": "fluid",
"port": {
"h2_gas_source": "gassource",
"solenoid_valve_3": "in"
}
},
{
"id": "link_valve3_to_reactor",
"source": "solenoid_valve_3",
"target": "main_reactor",
"type": "fluid",
"port": {
"solenoid_valve_3": "out",
"main_reactor": "top"
}
}
]
}

View File

@@ -3,7 +3,9 @@
{
"id": "MockChiller1",
"name": "模拟冷却器",
"children": [],
"children": [
"MockContainerForChiller1"
],
"parent": null,
"type": "device",
"class": "mock_chiller",
@@ -25,6 +27,22 @@
"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": "模拟过滤器",

View File

@@ -4,58 +4,83 @@
"id": "AddTestStation",
"name": "添加试剂测试工作站",
"children": [
"pump_add",
"flask_1",
"flask_2",
"flask_3",
"flask_4",
"reactor",
"transfer_pump",
"multiway_valve",
"stirrer",
"flask_air"
"flask_reagent1",
"flask_reagent2",
"flask_reagent3",
"flask_reagent4",
"reactor",
"flask_waste",
"flask_rinsing",
"flask_buffer"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"x": 620,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol"]
"protocol_type": ["AddProtocol", "TransferProtocol", "StartStirProtocol", "StopStirProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "pump_add",
"id": "transfer_pump",
"name": "注射器泵",
"children": [],
"parent": "AddTestStation",
"type": "device",
"class": "virtual_pump",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"x": 520,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
"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": "stirrer",
"name": "搅拌器",
"children": [],
"parent": "AddTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 698.1111111111111,
"y": 478,
"x": 720,
"y": 450,
"z": 0
},
"config": {
@@ -68,110 +93,115 @@
}
},
{
"id": "flask_1",
"name": "通用试剂瓶1",
"id": "flask_reagent1",
"name": "试剂瓶1 (甲醇)",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"y": 400,
"z": 0
},
"config": {
"max_volume": 2000.0
"max_volume": 1000.0
},
"data": {
"liquid": []
"liquid": [
{
"name": "甲醇",
"volume": 800.0,
"concentration": "99.9%"
}
]
}
},
{
"id": "flask_2",
"name": "通用试剂瓶2",
"id": "flask_reagent2",
"name": "试剂瓶2 (乙醇)",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"x": 180,
"y": 400,
"z": 0
},
"config": {
"max_volume": 2000.0
"max_volume": 1000.0
},
"data": {
"liquid": []
"liquid": [
{
"name": "乙醇",
"volume": 750.0,
"concentration": "95%"
}
]
}
},
{
"id": "flask_3",
"name": "通用试剂瓶3",
"id": "flask_reagent3",
"name": "试剂瓶3 (丙酮)",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"x": 260,
"y": 400,
"z": 0
},
"config": {
"max_volume": 2000.0
"max_volume": 1000.0
},
"data": {
"liquid": []
"liquid": [
{
"name": "丙酮",
"volume": 900.0,
"concentration": "99.5%"
}
]
}
},
{
"id": "flask_4",
"name": "通用试剂瓶4",
"id": "flask_reagent4",
"name": "试剂瓶4 (二氯甲烷)",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"x": 340,
"y": 400,
"z": 0
},
"config": {
"max_volume": 2000.0
"max_volume": 1000.0
},
"data": {
"liquid": []
"liquid": [
{
"name": "二氯甲烷",
"volume": 850.0,
"concentration": "99.8%"
}
]
}
},
{
"id": "reactor",
"name": "reactor",
"name": "反应器",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "flask_air",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 300,
"x": 720,
"y": 400,
"z": 0
},
"config": {
@@ -180,70 +210,166 @@
"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": "stirrer",
"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": {
"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": "flask_4",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_4": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"multiway_valve": "multiway-valve-port-5",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"source": "multiway_valve",
"target": "flask_waste",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
"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

@@ -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"
}
}
]

View File

@@ -48,8 +48,9 @@ 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

View File

@@ -50,8 +50,9 @@ 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

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,8 +48,9 @@ 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
@@ -61,5 +62,12 @@ dependencies:
# 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
# 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

@@ -25,12 +25,13 @@ 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"]
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}")
HostNode.get_instance().send_goal(req.device_id, action_name=action_name, action_kwargs=action_kwargs, goal_uuid=req.job_id, server_info=req.server_info)
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)
return JobData(jobId=req.job_id)

View File

@@ -10,7 +10,7 @@ from copy import deepcopy
import yaml
from unilabos.resources.graphio import tree_to_list
from unilabos.resources.graphio import tree_to_list, modify_to_backend_format
# 首先添加项目根目录到路径
current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -22,6 +22,21 @@ from unilabos.config.config import load_config, BasicConfig, _update_config_from
from unilabos.utils.banner_print import print_status, print_unilab_banner
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():
"""解析命令行参数"""
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
@@ -58,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,
@@ -97,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
@@ -136,15 +146,16 @@ 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"] = list(devices_and_resources.values())
@@ -185,6 +196,7 @@ 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"

View File

@@ -172,13 +172,14 @@ class MQTTClient:
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:

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,7 +30,27 @@ class HTTPClient:
self.auth = MQConfig.lab_id
info(f"HTTPClient 初始化完成: remote_addr={self.remote_addr}")
def resource_add(self, resources: List[Dict[str, Any]], database_process_later:bool) -> 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/edge/batch_create/?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 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:
"""
添加资源
@@ -44,8 +64,10 @@ class HTTPClient:
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=5,
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]:
@@ -63,7 +85,7 @@ class HTTPClient:
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
@@ -99,7 +121,7 @@ class HTTPClient:
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

@@ -8,7 +8,12 @@ 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
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
@@ -16,29 +21,39 @@ 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,
AGVTransferProtocol: generate_agv_transfer_protocol,
CentrifugeProtocol: generate_centrifuge_protocol,
AddProtocol: generate_add_protocol,
AGVTransferProtocol: generate_agv_transfer_protocol,
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,
StirProtocol: generate_stir_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,
CleanVesselProtocol: generate_clean_vessel_protocol,
DissolveProtocol: generate_dissolve_protocol,
FilterThroughProtocol: generate_filter_through_protocol,
RunColumnProtocol: generate_run_column_protocol,
WashSolidProtocol: generate_wash_solid_protocol,
}
}

View File

@@ -1,74 +1,627 @@
import networkx as nx
from typing import List, Dict, Any
def generate_add_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str,
volume: float,
mass: float,
amount: str,
time: float,
stir: bool,
stir_speed: float,
viscous: bool,
purpose: str
) -> List[Dict[str, Any]]:
"""
生成添加试剂的协议序列 - 严格按照 Add.action
"""
action_sequence = []
# 如果指定了体积,执行液体转移
if volume > 0:
# 查找可用的试剂瓶
available_flasks = [node for node in G.nodes()
if node.startswith('flask_')
and G.nodes[node].get('type') == 'container']
if not available_flasks:
raise ValueError("没有找到可用的试剂容器")
import networkx as nx
from typing import List, Dict, Any
from .pump_protocol import generate_pump_protocol_with_rinsing
def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
"""
根据试剂名称查找对应的试剂瓶,支持多种匹配模式:
1. 容器名称匹配(如 flask_DMF, reagent_bottle_1-DMF
2. 容器内液体类型匹配(如 liquid_type: "DMF", name: "ethanol"
3. 试剂名称匹配(如 reagent_name: "DMF", config.reagent: "ethyl_acetate"
Args:
G: 网络图
reagent: 试剂名称
Returns:
str: 试剂瓶的vessel ID
Raises:
ValueError: 如果找不到对应的试剂瓶
"""
print(f"ADD_PROTOCOL: 正在查找试剂 '{reagent}' 的容器...")
# 第一步:通过容器名称匹配
possible_names = [
f"flask_{reagent}", # flask_DMF, flask_ethanol
f"bottle_{reagent}", # bottle_DMF, bottle_ethanol
f"vessel_{reagent}", # vessel_DMF, vessel_ethanol
f"{reagent}_flask", # DMF_flask, ethanol_flask
f"{reagent}_bottle", # DMF_bottle, ethanol_bottle
f"{reagent}", # 直接用试剂名
f"reagent_{reagent}", # reagent_DMF, reagent_ethanol
f"reagent_bottle_{reagent}", # reagent_bottle_DMF
]
# 尝试名称匹配
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"ADD_PROTOCOL: 通过名称匹配找到容器: {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 (reagent.lower() in node_id.lower() or
reagent.lower() in node_name):
print(f"ADD_PROTOCOL: 通过模糊名称匹配找到容器: {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', [])
reagent_vessel = available_flasks[0]
# 查找泵设备
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_pump']
if pump_nodes:
pump_id = pump_nodes[0]
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": reagent_vessel,
"to_vessel": vessel,
"volume": volume,
"amount": amount,
"time": time,
"viscous": viscous,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
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() == reagent.lower() or
reagent_name.lower() == reagent.lower() or
config_reagent.lower() == reagent.lower()):
print(f"ADD_PROTOCOL: 通过液体类型匹配找到容器: {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"ADD_PROTOCOL: 可用容器列表:")
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"找不到试剂 '{reagent}' 对应的试剂瓶。尝试了名称匹配: {possible_names}")
def find_reagent_vessel_by_any_match(G: nx.DiGraph, reagent: str) -> str:
"""
增强版试剂容器查找,支持各种匹配方式的别名函数
"""
return find_reagent_vessel(G, reagent)
def get_vessel_reagent_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_reagent_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', [])
reagent_types = []
for liquid in liquids:
if isinstance(liquid, dict):
# 支持两种格式的试剂类型字段
reagent_type = liquid.get('liquid_type') or liquid.get('name', '')
if reagent_type:
reagent_types.append(reagent_type)
# 同时检查配置中的试剂信息
config_reagent = G.nodes[vessel].get('config', {}).get('reagent', '')
reagent_name = vessel_data.get('reagent_name', '')
if config_reagent and config_reagent not in reagent_types:
reagent_types.append(config_reagent)
if reagent_name and reagent_name not in reagent_types:
reagent_types.append(reagent_name)
return reagent_types
def find_vessels_by_reagent(G: nx.DiGraph, reagent: 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 reagent.lower() in node_id.lower() or reagent.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 (reagent.lower() == reagent_name or
reagent.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() == reagent.lower():
matching_vessels.append(node_id)
break
return matching_vessels
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""
查找与指定容器相连的搅拌器
Args:
G: 网络图
vessel: 容器ID
Returns:
str: 搅拌器ID如果找不到则返回None
"""
# 查找所有搅拌器节点
stirrer_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
# 检查哪个搅拌器与目标容器相连
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
return stirrer
# 如果没有直接连接,返回第一个可用的搅拌器
return stirrer_nodes[0] if stirrer_nodes else None
def generate_add_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str,
volume: float,
mass: float = 0.0,
amount: str = "",
time: float = 0.0,
stir: bool = False,
stir_speed: float = 300.0,
viscous: bool = False,
purpose: str = "添加试剂"
) -> List[Dict[str, Any]]:
"""
生成添加试剂的协议序列,支持智能试剂匹配
基于pump_protocol的成熟算法实现试剂添加功能
1. 智能查找试剂瓶(支持名称匹配、液体类型匹配、试剂配置匹配)
2. **先启动搅拌,再进行转移** - 确保试剂添加更均匀
3. 使用pump_protocol实现液体转移
Args:
G: 有向图,节点为容器和设备,边为连接关系
vessel: 目标容器(要添加试剂的容器)
reagent: 试剂名称(用于查找对应的试剂瓶)
volume: 要添加的体积 (mL)
mass: 要添加的质量 (g) - 暂时未使用,预留接口
amount: 其他数量描述
time: 添加时间 (s),如果指定则计算流速
stir: 是否启用搅拌
stir_speed: 搅拌速度 (RPM)
viscous: 是否为粘稠液体
purpose: 添加目的描述
Returns:
List[Dict[str, Any]]: 动作序列
Raises:
ValueError: 当找不到必要的设备或容器时
"""
action_sequence = []
print(f"ADD_PROTOCOL: 开始生成添加试剂协议")
print(f" - 目标容器: {vessel}")
print(f" - 试剂: {reagent}")
print(f" - 体积: {volume} mL")
print(f" - 质量: {mass} g")
print(f" - 搅拌: {stir} (速度: {stir_speed} RPM)")
print(f" - 粘稠: {viscous}")
print(f" - 目的: {purpose}")
# 1. 验证目标容器存在
if vessel not in G.nodes():
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
# 2. 智能查找试剂瓶
try:
reagent_vessel = find_reagent_vessel(G, reagent)
print(f"ADD_PROTOCOL: 找到试剂容器: {reagent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
# 3. 验证试剂容器中的试剂体积
available_volume = get_vessel_reagent_volume(G, reagent_vessel)
print(f"ADD_PROTOCOL: 试剂容器 {reagent_vessel} 中有 {available_volume} mL 试剂")
if available_volume < volume:
print(f"ADD_PROTOCOL: 警告 - 试剂容器中的试剂不足!需要 {volume} mL可用 {available_volume} mL")
# 4. 验证是否存在从试剂瓶到目标容器的路径
try:
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
print(f"ADD_PROTOCOL: 找到路径 {reagent_vessel} -> {vessel}: {path}")
except nx.NetworkXNoPath:
raise ValueError(f"从试剂瓶 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
# 5. **先启动搅拌** - 关键改进!
if stir:
try:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
print(f"ADD_PROTOCOL: 找到搅拌器 {stirrer_id},将在添加前启动搅拌")
# 先启动搅拌
stir_action = {
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"{purpose}: 启动搅拌,准备添加 {reagent}"
}
}
})
# 如果需要搅拌,使用 StartStir 而不是 Stir
if stir:
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
if stirrer_nodes:
stirrer_id = stirrer_nodes[0]
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir", # 使用 start_stir 而不是 stir
"action_kwargs": {
action_sequence.append(stir_action)
print(f"ADD_PROTOCOL: 已添加搅拌动作,速度 {stir_speed} RPM")
# 等待搅拌稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
else:
print(f"ADD_PROTOCOL: 警告 - 需要搅拌但未找到与容器 {vessel} 相连的搅拌器")
except Exception as e:
print(f"ADD_PROTOCOL: 搅拌器配置出错: {str(e)}")
# 6. 如果指定了体积,执行液体转移
if volume > 0:
# 6.1 计算流速参数
if time > 0:
# 根据时间计算流速
transfer_flowrate = volume / time
flowrate = transfer_flowrate
else:
# 使用默认流速
if viscous:
transfer_flowrate = 0.3 # 粘稠液体用较慢速度
flowrate = 1.0
else:
transfer_flowrate = 0.5 # 普通液体默认速度
flowrate = 2.5
print(f"ADD_PROTOCOL: 准备转移 {volume} mL 从 {reagent_vessel}{vessel}")
print(f"ADD_PROTOCOL: 转移流速={transfer_flowrate} mL/s, 注入流速={flowrate} mL/s")
# 6.2 使用pump_protocol的核心算法实现液体转移
try:
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=reagent_vessel,
to_vessel=vessel,
volume=volume,
amount=amount,
time=time,
viscous=viscous,
rinsing_solvent="", # 添加试剂通常不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=flowrate,
transfer_flowrate=transfer_flowrate
)
# 添加pump actions到序列中
action_sequence.extend(pump_actions)
except Exception as e:
raise ValueError(f"生成泵协议时出错: {str(e)}")
print(f"ADD_PROTOCOL: 生成了 {len(action_sequence)} 个动作")
print(f"ADD_PROTOCOL: 添加试剂协议生成完成")
return action_sequence
def generate_add_protocol_with_cleaning(
G: nx.DiGraph,
vessel: str,
reagent: str,
volume: float,
mass: float = 0.0,
amount: str = "",
time: float = 0.0,
stir: bool = False,
stir_speed: float = 300.0,
viscous: bool = False,
purpose: str = "添加试剂",
cleaning_solvent: str = "air",
cleaning_volume: float = 5.0,
cleaning_repeats: int = 1
) -> List[Dict[str, Any]]:
"""
生成带清洗的添加试剂协议,支持智能试剂匹配
与普通添加协议的区别是会在添加后进行管道清洗
Args:
G: 有向图
vessel: 目标容器
reagent: 试剂名称
volume: 添加体积
mass: 添加质量(预留)
amount: 其他数量描述
time: 添加时间
stir: 是否搅拌
stir_speed: 搅拌速度
viscous: 是否粘稠
purpose: 添加目的
cleaning_solvent: 清洗溶剂("air"表示空气清洗)
cleaning_volume: 清洗体积
cleaning_repeats: 清洗重复次数
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
# 1. 智能查找试剂瓶
reagent_vessel = find_reagent_vessel(G, reagent)
# 2. **先启动搅拌**
if stir:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"添加 {reagent} 后搅拌"
}
})
return action_sequence
"purpose": f"{purpose}: 启动搅拌,准备添加 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
# 3. 计算流速
if time > 0:
transfer_flowrate = volume / time
flowrate = transfer_flowrate
else:
if viscous:
transfer_flowrate = 0.3
flowrate = 1.0
else:
transfer_flowrate = 0.5
flowrate = 2.5
# 4. 使用带清洗的pump_protocol
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=reagent_vessel,
to_vessel=vessel,
volume=volume,
amount=amount,
time=time,
viscous=viscous,
rinsing_solvent=cleaning_solvent,
rinsing_volume=cleaning_volume,
rinsing_repeats=cleaning_repeats,
solid=False,
flowrate=flowrate,
transfer_flowrate=transfer_flowrate
)
action_sequence.extend(pump_actions)
return action_sequence
def generate_sequential_add_protocol(
G: nx.DiGraph,
vessel: str,
reagents: List[Dict[str, Any]],
stir_between_additions: bool = True,
final_stir: bool = True,
final_stir_speed: float = 400.0,
final_stir_time: float = 300.0
) -> List[Dict[str, Any]]:
"""
生成连续添加多种试剂的协议,支持智能试剂匹配
Args:
G: 网络图
vessel: 目标容器
reagents: 试剂列表,每个元素包含试剂添加参数
stir_between_additions: 是否在每次添加之间搅拌
final_stir: 是否在所有添加完成后进行最终搅拌
final_stir_speed: 最终搅拌速度
final_stir_time: 最终搅拌时间
Returns:
List[Dict[str, Any]]: 完整的动作序列
Example:
reagents = [
{
"reagent": "DMF", # 会匹配 reagent_bottle_1 (reagent_name: "DMF")
"volume": 10.0,
"viscous": False,
"stir_speed": 300.0
},
{
"reagent": "ethyl_acetate", # 会匹配 reagent_bottle_2 (reagent_name: "ethyl_acetate")
"volume": 5.0,
"viscous": False,
"stir_speed": 350.0
}
]
"""
action_sequence = []
print(f"ADD_PROTOCOL: 开始连续添加 {len(reagents)} 种试剂到容器 {vessel}")
for i, reagent_params in enumerate(reagents):
reagent_name = reagent_params.get('reagent')
print(f"ADD_PROTOCOL: 处理第 {i+1}/{len(reagents)} 个试剂: {reagent_name}")
# 生成单个试剂的添加协议
add_actions = generate_add_protocol(
G=G,
vessel=vessel,
reagent=reagent_name,
volume=reagent_params.get('volume', 0.0),
mass=reagent_params.get('mass', 0.0),
amount=reagent_params.get('amount', ''),
time=reagent_params.get('time', 0.0),
stir=stir_between_additions,
stir_speed=reagent_params.get('stir_speed', 300.0),
viscous=reagent_params.get('viscous', False),
purpose=reagent_params.get('purpose', f'添加试剂 {reagent_name} ({i+1}/{len(reagents)})')
)
action_sequence.extend(add_actions)
# 在添加之间加入等待时间
if i < len(reagents) - 1: # 不是最后一个试剂
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10} # 试剂混合时间
})
# 最终搅拌
if final_stir:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
print(f"ADD_PROTOCOL: 添加最终搅拌动作,速度 {final_stir_speed} RPM时间 {final_stir_time}")
action_sequence.extend([
{
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"stir_time": final_stir_time,
"stir_speed": final_stir_speed,
"settling_time": 30.0
}
}
])
print(f"ADD_PROTOCOL: 连续添加协议生成完成,共 {len(action_sequence)} 个动作")
return action_sequence
# 便捷函数:常用添加方案
def generate_organic_add_protocol(
G: nx.DiGraph,
vessel: str,
organic_reagent: str,
volume: float,
stir_speed: float = 400.0
) -> List[Dict[str, Any]]:
"""有机试剂添加:慢速、搅拌"""
return generate_add_protocol(
G, vessel, organic_reagent, volume, 0.0, "", 0.0,
True, stir_speed, False, f"添加有机试剂 {organic_reagent}"
)
def generate_viscous_add_protocol(
G: nx.DiGraph,
vessel: str,
viscous_reagent: str,
volume: float,
addition_time: float = 120.0
) -> List[Dict[str, Any]]:
"""粘稠试剂添加:慢速、长时间"""
return generate_add_protocol(
G, vessel, viscous_reagent, volume, 0.0, "", addition_time,
True, 250.0, True, f"缓慢添加粘稠试剂 {viscous_reagent}"
)
def generate_solvent_add_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float
) -> List[Dict[str, Any]]:
"""溶剂添加:快速、无需特殊处理"""
return generate_add_protocol(
G, vessel, solvent, volume, 0.0, "", 0.0,
False, 300.0, False, f"添加溶剂 {solvent}"
)
# 使用示例和测试函数
def test_add_protocol():
"""测试添加协议的示例"""
print("=== ADD PROTOCOL 智能匹配测试 ===")
print("测试完成")
if __name__ == "__main__":
test_add_protocol()

View File

@@ -0,0 +1,411 @@
import networkx as nx
from typing import List, Dict, Any
from .pump_protocol import generate_pump_protocol_with_rinsing
def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
"""
查找酸碱试剂容器,支持多种匹配模式
Args:
G: 网络图
reagent: 试剂名称(如 "hydrochloric acid", "sodium hydroxide"
Returns:
str: 试剂容器ID
"""
print(f"ADJUST_PH: 正在查找试剂 '{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()]
# 添加别名
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])
# 构建可能的容器名称
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
])
# 第一步:通过容器名称匹配
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"ADJUST_PH: 通过名称匹配找到容器: {vessel_name}")
return vessel_name
# 第二步:通过模糊匹配
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:
print(f"ADJUST_PH: 通过模糊匹配找到容器: {node_id}")
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', '')).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:
print(f"ADJUST_PH: 通过液体类型匹配找到容器: {node_id}")
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', {})
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', '')
})
print(f"ADJUST_PH: 可用容器列表:")
for container in available_containers:
print(f" - {container['id']}: {container['name']}")
print(f" 液体: {container['liquids']}")
print(f" 试剂: {container['reagent_name']}")
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names}")
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找与容器相连的搅拌器"""
stirrer_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
return stirrer
return stirrer_nodes[0] if stirrer_nodes else None
def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float: # 改为 target_ph_value
"""
估算需要的试剂体积来调节pH
Args:
target_ph_value: 目标pH值 # 改为 target_ph_value
reagent: 试剂名称
vessel_volume: 容器体积 (mL)
Returns:
float: 估算的试剂体积 (mL)
"""
# 简化的pH调节体积估算实际应用中需要更精确的计算
if "acid" in reagent.lower() or "hcl" in reagent.lower():
# 酸性试剂pH越低需要的体积越大
if target_ph_value < 3: # 改为 target_ph_value
return vessel_volume * 0.05 # 5%
elif target_ph_value < 5: # 改为 target_ph_value
return vessel_volume * 0.02 # 2%
else:
return vessel_volume * 0.01 # 1%
elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower():
# 碱性试剂pH越高需要的体积越大
if target_ph_value > 11: # 改为 target_ph_value
return vessel_volume * 0.05 # 5%
elif target_ph_value > 9: # 改为 target_ph_value
return vessel_volume * 0.02 # 2%
else:
return vessel_volume * 0.01 # 1%
else:
# 未知试剂,使用默认值
return vessel_volume * 0.01
def generate_adjust_ph_protocol(
G: nx.DiGraph,
vessel: str,
ph_value: float, # 改为 ph_value
reagent: str,
**kwargs
) -> List[Dict[str, Any]]:
"""
生成调节pH的协议序列
Args:
G: 有向图,节点为容器和设备
vessel: 目标容器需要调节pH的容器
ph_value: 目标pH值从XDL传入 # 改为 ph_value
reagent: 酸碱试剂名称从XDL传入
**kwargs: 其他可选参数,使用默认值
Returns:
List[Dict[str, Any]]: 动作序列
"""
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) # 默认平衡时间
print(f"ADJUST_PH: 开始生成pH调节协议")
print(f" - 目标容器: {vessel}")
print(f" - 目标pH: {ph_value}") # 改为 ph_value
print(f" - 试剂: {reagent}")
print(f" - 使用默认参数: 体积=自动估算, 搅拌=True, 搅拌速度=300RPM")
# 1. 验证目标容器存在
if vessel not in G.nodes():
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
# 2. 查找酸碱试剂容器
try:
reagent_vessel = find_acid_base_vessel(G, reagent)
print(f"ADJUST_PH: 找到试剂容器: {reagent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
# 3. 如果未指定体积,自动估算
if volume <= 0:
# 获取目标容器的体积信息
vessel_data = G.nodes[vessel].get('data', {})
vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume) # 改为 ph_value
volume = estimated_volume
print(f"ADJUST_PH: 自动估算试剂体积: {volume:.2f} mL")
# 4. 验证路径存在
try:
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
print(f"ADJUST_PH: 找到路径: {''.join(path)}")
except nx.NetworkXNoPath:
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
# 5. 先启动搅拌(如果需要)
stirrer_id = None
if stir:
try:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
print(f"ADJUST_PH: 找到搅拌器 {stirrer_id},启动搅拌")
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({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
else:
print(f"ADJUST_PH: 警告 - 未找到搅拌器,继续执行")
except Exception as e:
print(f"ADJUST_PH: 搅拌器配置出错: {str(e)}")
# 6. 缓慢添加试剂 - 使用pump_protocol
print(f"ADJUST_PH: 开始添加试剂 {volume:.2f} mL")
# 计算添加时间pH调节需要缓慢添加
addition_time = max(30.0, volume * 2.0) # 至少30秒每mL需要2秒
try:
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 # 缓慢注入
)
action_sequence.extend(pump_actions)
except Exception as e:
raise ValueError(f"生成泵协议时出错: {str(e)}")
# 7. 持续搅拌以混合和平衡
if stir and stirrer_id:
print(f"ADJUST_PH: 持续搅拌 {stir_time} 秒以混合试剂")
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}" # 改为 ph_value
}
})
# 8. 等待反应平衡
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": settling_time,
"description": f"等待pH平衡到目标值 {ph_value}" # 改为 ph_value
}
})
print(f"ADJUST_PH: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"ADJUST_PH: 预计总时间: {addition_time + stir_time + settling_time:.0f}")
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: 目标pH值
reagent: 酸碱试剂
max_volume: 最大试剂体积
steps: 分步数量
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
print(f"ADJUST_PH: 开始分步pH调节{steps}步)")
# 每步添加的体积
step_volume = max_volume / steps
for i in range(steps):
print(f"ADJUST_PH: 第 {i+1}/{steps} 步,添加 {step_volume} mL")
# 生成单步协议
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)
# 步骤间等待
if i < steps - 1:
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 30,
"description": f"pH调节第{i+1}步完成,等待下一步"
}
})
print(f"ADJUST_PH: 分步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]]:
"""酸化协议"""
return generate_adjust_ph_protocol(
G, vessel, target_ph, acid, 0.0, True, 300.0, 120.0, 60.0
)
def generate_basify_protocol(
G: nx.DiGraph,
vessel: str,
target_ph: float = 12.0,
base: str = "sodium hydroxide"
) -> List[Dict[str, Any]]:
"""碱化协议"""
return generate_adjust_ph_protocol(
G, vessel, target_ph, base, 0.0, True, 300.0, 120.0, 60.0
)
def generate_neutralize_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str = "sodium hydroxide"
) -> List[Dict[str, Any]]:
"""中和协议pH=7"""
return generate_adjust_ph_protocol(
G, vessel, 7.0, reagent, 0.0, True, 350.0, 180.0, 90.0
)
# 测试函数
def test_adjust_ph_protocol():
"""测试pH调节协议"""
print("=== ADJUST PH PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_adjust_ph_protocol()

View File

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

View File

@@ -1,5 +1,147 @@
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,
@@ -10,13 +152,22 @@ def generate_clean_vessel_protocol(
repeats: int = 1
) -> List[Dict[str, Any]]:
"""
生成容器清洗操作的协议序列,使用transfer操作实现清洗
生成容器清洗操作的协议序列,复用 pump_protocol 的成熟算法
清洗流程:
1. 查找溶剂容器和废液容器
2. 如果需要加热,启动加热设备
3. 重复以下操作 repeats 次:
a. 使用 pump_protocol 将溶剂从溶剂容器转移到目标容器
b. (可选) 等待清洗作用时间
c. 使用 pump_protocol 将清洗液从目标容器转移到废液容器
4. 如果加热了,停止加热
Args:
G: 有向图,节点为设备和容器
G: 有向图,节点为设备和容器,边为流体管道
vessel: 要清洗的容器名称
solvent: 用于清洗容器的溶剂名称
volume: 清洗溶剂体积
solvent: 用于清洗的溶剂名称
volume: 每次清洗使用的溶剂体积
temp: 清洗时的温度
repeats: 清洗操作的重复次数,默认为 1
@@ -24,103 +175,265 @@ def generate_clean_vessel_protocol(
List[Dict[str, Any]]: 容器清洗操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
ValueError: 当找不到必要的容器或设备时抛出异常
Examples:
clean_vessel_protocol = generate_clean_vessel_protocol(G, "reactor", "water", 50.0, 25.0, 2)
clean_protocol = generate_clean_vessel_protocol(G, "main_reactor", "water", 100.0, 60.0, 2)
"""
action_sequence = []
# 查找虚拟转移泵设备进行清洗操作
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_transfer_pump']
print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
print(f" - 目标容器: {vessel}")
print(f" - 清洗溶剂: {solvent}")
print(f" - 清洗体积: {volume} mL")
print(f" - 清洗温度: {temp}°C")
print(f" - 重复次数: {repeats}")
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备进行容器清洗")
pump_id = pump_nodes[0]
# 验证容器是否存在
# 验证目标容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于")
raise ValueError(f"目标容器 '{vessel}' 不存在于系统")
# 查找溶剂容器
solvent_vessel = f"flask_{solvent}"
if solvent_vessel not in G.nodes():
raise ValueError(f"溶剂容器 {solvent_vessel} 不存在于图中")
try:
solvent_vessel = find_solvent_vessel(G, solvent)
print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂容器: {str(e)}")
# 查找废液容器
waste_vessel = "flask_waste"
if waste_vessel not in G.nodes():
raise ValueError(f"废液容器 {waste_vessel} 不存在于图中")
try:
waste_vessel = find_waste_vessel(G)
print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}")
except ValueError as e:
raise ValueError(f"无法找到废液容器: {str(e)}")
# 查找加热设备(如果需要加热
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
# 查找加热设备(可选
heatchill_id = find_connected_heatchill(G, vessel)
if heatchill_id:
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
else:
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
# 第一步:如果需要加热且有加热设备,启动加热
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):
# 1. 如果需要加热,先设置温度
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": "cleaning"
}
})
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
# 2. 使用transfer操作从溶剂容器转移清洗溶剂到目标容器
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": solvent_vessel,
"to_vessel": vessel,
"volume": volume,
"amount": f"cleaning with {solvent} - cycle {repeat + 1}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
# 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}
}
})
# 3. 等待清洗作用时间可选可以添加wait操作
# 这里省略wait操作直接进行下一步
# 4. 将清洗后的溶剂转移到废液容器
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": vessel,
"to_vessel": waste_vessel,
"volume": volume,
"amount": f"waste from cleaning {vessel} - cycle {repeat + 1}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 5. 如果加热了,停止加热
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
})
action_sequence.append(wait_action)
return action_sequence
# 第三步:如果加热了,停止加热
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

@@ -1,5 +1,47 @@
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:
"""
查找溶剂容器
"""
# 按照pump_protocol的命名规则查找溶剂瓶
solvent_vessel_id = f"flask_{solvent}"
if solvent_vessel_id in G.nodes():
return solvent_vessel_id
# 如果直接匹配失败,尝试模糊匹配
for node in G.nodes():
if node.startswith('flask_') and 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"找不到溶剂 '{solvent}' 对应的溶剂瓶。可用溶剂瓶: {available_flasks}")
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
"""
查找与指定容器相连的加热搅拌器
"""
# 查找所有加热搅拌器节点
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
# 检查哪个加热器与目标容器相连
for heatchill in heatchill_nodes:
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
return heatchill
# 如果没有直接连接,返回第一个可用的加热器
return heatchill_nodes[0] if heatchill_nodes else None
def generate_dissolve_protocol(
G: nx.DiGraph,
@@ -9,154 +51,309 @@ def generate_dissolve_protocol(
amount: str = "",
temp: float = 25.0,
time: float = 0.0,
stir_speed: float = 0.0
stir_speed: float = 300.0
) -> List[Dict[str, Any]]:
"""
生成溶解操作的协议序列
生成溶解操作的协议序列,复用 pump_protocol 的成熟算法
溶解流程:
1. 溶剂转移:将溶剂从溶剂瓶转移到目标容器
2. 启动加热搅拌:设置温度和搅拌
3. 等待溶解:监控溶解过程
4. 停止加热搅拌:完成溶解
Args:
G: 有向图,节点为设备和容器
vessel: 装有要溶解物质的容器名称
solvent: 用于溶解物质的溶剂名称
volume: 溶剂体积,可选参数
amount: 要溶解物质的量,可选参数
temp: 溶解时的温度,可选参数
time: 溶解时间,可选参数
stir_speed: 搅拌速度,可选参数
G: 有向图,节点为设备和容器,边为流体管道
vessel: 目标容器(要进行溶解的容器
solvent: 溶剂名称(用于查找对应的溶剂瓶)
volume: 溶剂体积 (mL)
amount: 要溶解物质描述
temp: 溶解温度 (°C)默认25°C室温
time: 溶解时间 (秒)默认0立即完成
stir_speed: 搅拌速度 (RPM)默认300 RPM
Returns:
List[Dict[str, Any]]: 溶解操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
ValueError: 当找不到必要的设备或容器
Examples:
dissolve_protocol = generate_dissolve_protocol(G, "reactor", "water", 100.0, "NaCl 5g", 60.0, 300.0, 500.0)
dissolve_actions = generate_dissolve_protocol(G, "reaction_mixture", "DMF", 10.0, "NaCl 2g", 60.0, 600.0, 400.0)
"""
action_sequence = []
# 验证容器是否存在
print(f"DISSOLVE: 开始生成溶解协议")
print(f" - 目标容器: {vessel}")
print(f" - 溶剂: {solvent}")
print(f" - 溶剂体积: {volume} mL")
print(f" - 要溶解的物质: {amount}")
print(f" - 溶解温度: {temp}°C")
print(f" - 溶解时间: {time}s ({time/60:.1f}分钟)" if time > 0 else " - 溶解时间: 立即完成")
print(f" - 搅拌速度: {stir_speed} RPM")
# 验证目标容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于")
raise ValueError(f"目标容器 '{vessel}' 不存在于系统")
# 查找溶剂容器
solvent_vessel = f"flask_{solvent}"
if solvent_vessel not in G.nodes():
# 如果没有找到特定溶剂容器,查找可用的源容器
available_vessels = [node for node in G.nodes()
if node.startswith('flask_') and
G.nodes[node].get('type') == 'container']
if available_vessels:
solvent_vessel = available_vessels[0]
else:
raise ValueError(f"没有找到溶剂容器 {solvent}")
# 查找溶剂
try:
solvent_vessel = find_solvent_vessel(G, solvent)
print(f"DISSOLVE: 找到溶剂瓶: {solvent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
# 查找转移泵设备
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_transfer_pump']
# 验证是否存在从溶剂瓶到目标容器的路径
try:
path = nx.shortest_path(G, source=solvent_vessel, target=vessel)
print(f"DISSOLVE: 找到路径 {solvent_vessel} -> {vessel}: {path}")
except nx.NetworkXNoPath:
raise ValueError(f"从溶剂瓶 '{solvent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备")
# 查找加热搅拌器
heatchill_id = None
if temp > 25.0 or stir_speed > 0 or time > 0:
try:
heatchill_id = find_connected_heatchill(G, vessel)
if heatchill_id:
print(f"DISSOLVE: 找到加热搅拌器: {heatchill_id}")
else:
print(f"DISSOLVE: 警告 - 需要加热/搅拌但未找到与容器 {vessel} 相连的加热搅拌器")
except Exception as e:
print(f"DISSOLVE: 加热搅拌器配置出错: {str(e)}")
pump_id = pump_nodes[0]
# 查找加热设备(如果需要加热)
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
# 查找搅拌设备(如果需要搅拌)
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
# 步骤1如果需要加热先设置温度
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": "dissolution"
}
})
# 步骤2添加溶剂到容器中
if volume > 0:
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": solvent_vessel,
"to_vessel": vessel,
"volume": volume,
"amount": f"solvent {solvent} for dissolving {amount}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 步骤3如果需要搅拌开始搅拌
if stir_speed > 0 and stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"dissolving {amount} in {solvent}"
}
})
# 步骤4如果指定了溶解时间等待溶解完成
if time > 0:
# 这里可以添加等待操作,或者使用搅拌操作来模拟溶解时间
if stirrer_id and stir_speed > 0:
# 停止之前的搅拌,使用定时搅拌
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
# === 第一步:启动加热搅拌(在添加溶剂前) ===
if heatchill_id and (temp > 25.0 or time > 0):
print(f"DISSOLVE: 启动加热搅拌器,温度: {temp}°C")
if time > 0:
# 如果指定了时间,使用定时加热搅拌
heatchill_action = {
"device_id": heatchill_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel
}
})
# 开始定时搅拌
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"stir_time": time,
"vessel": vessel,
"temp": temp,
"time": time,
"stir": True,
"stir_speed": stir_speed,
"settling_time": 10.0 # 搅拌后静置10秒
"purpose": f"溶解 {amount}{solvent}"
}
}
else:
# 如果没有指定时间,使用持续加热搅拌
heatchill_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": f"溶解 {amount}{solvent}"
}
}
action_sequence.append(heatchill_action)
# 等待温度稳定
if temp > 25.0:
wait_time = min(60, abs(temp - 25.0) * 1.5) # 根据温差估算预热时间
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": wait_time}
})
# 步骤5如果加热了停止加热
if temp > 25.0 and heatchill_id:
# === 第二步:添加溶剂到目标容器 ===
if volume > 0:
print(f"DISSOLVE: 将 {volume} mL {solvent}{solvent_vessel} 转移到 {vessel}")
# 计算流速 - 溶解时通常用较慢的速度,避免飞溅
transfer_flowrate = 1.0 # 较慢的转移速度
flowrate = 0.5 # 较慢的注入速度
try:
# 使用成熟的 pump_protocol 算法进行液体转移
pump_actions = generate_pump_protocol(
G=G,
from_vessel=solvent_vessel,
to_vessel=vessel,
volume=volume,
flowrate=flowrate, # 注入速度 - 较慢避免飞溅
transfer_flowrate=transfer_flowrate # 转移速度
)
action_sequence.extend(pump_actions)
except Exception as e:
raise ValueError(f"生成泵协议时出错: {str(e)}")
# 溶剂添加后等待
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
# === 第三步:如果没有使用定时加热搅拌,但需要等待溶解 ===
if time > 0 and heatchill_id and temp <= 25.0:
# 只需要搅拌等待,不需要加热
print(f"DISSOLVE: 室温搅拌 {time}s 等待溶解")
stir_action = {
"device_id": heatchill_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": 25.0, # 室温
"time": time,
"stir": True,
"stir_speed": stir_speed,
"purpose": f"室温搅拌溶解 {amount}"
}
}
action_sequence.append(stir_action)
# === 第四步:如果使用了持续加热,需要手动停止 ===
if heatchill_id and time == 0 and temp > 25.0:
print(f"DISSOLVE: 停止加热搅拌器")
stop_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
})
}
action_sequence.append(stop_action)
# 步骤6如果还在搅拌停止搅拌除非已经用定时搅拌
if stir_speed > 0 and stirrer_id and time == 0:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel
print(f"DISSOLVE: 生成了 {len(action_sequence)} 个动作")
print(f"DISSOLVE: 溶解协议生成完成")
return action_sequence
# 便捷函数:常用溶解方案
def generate_room_temp_dissolve_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float,
amount: str = "",
stir_time: float = 300.0 # 5分钟
) -> List[Dict[str, Any]]:
"""室温溶解:快速搅拌,短时间"""
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, 25.0, stir_time, 400.0)
def generate_heated_dissolve_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float,
amount: str = "",
temp: float = 60.0,
dissolve_time: float = 900.0 # 15分钟
) -> List[Dict[str, Any]]:
"""加热溶解:中等温度,较长时间"""
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 300.0)
def generate_gentle_dissolve_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float,
amount: str = "",
temp: float = 40.0,
dissolve_time: float = 1800.0 # 30分钟
) -> List[Dict[str, Any]]:
"""温和溶解:低温,长时间,慢搅拌"""
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 200.0)
def generate_hot_dissolve_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float,
amount: str = "",
temp: float = 80.0,
dissolve_time: float = 600.0 # 10分钟
) -> List[Dict[str, Any]]:
"""高温溶解:高温,中等时间,快搅拌"""
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 500.0)
def generate_sequential_dissolve_protocol(
G: nx.DiGraph,
vessel: str,
dissolve_steps: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""
生成连续溶解多种物质的协议
Args:
G: 网络图
vessel: 目标容器
dissolve_steps: 溶解步骤列表,每个元素包含溶解参数
Returns:
List[Dict[str, Any]]: 完整的动作序列
Example:
dissolve_steps = [
{
"solvent": "water",
"volume": 5.0,
"amount": "NaCl 1g",
"temp": 25.0,
"time": 300.0,
"stir_speed": 300.0
},
{
"solvent": "ethanol",
"volume": 2.0,
"amount": "organic compound 0.5g",
"temp": 40.0,
"time": 600.0,
"stir_speed": 400.0
}
})
]
"""
action_sequence = []
return action_sequence
for i, step in enumerate(dissolve_steps):
print(f"DISSOLVE: 处理第 {i+1}/{len(dissolve_steps)} 个溶解步骤")
# 生成单个溶解步骤的协议
dissolve_actions = generate_dissolve_protocol(
G=G,
vessel=vessel,
solvent=step.get('solvent'),
volume=step.get('volume', 0.0),
amount=step.get('amount', ''),
temp=step.get('temp', 25.0),
time=step.get('time', 0.0),
stir_speed=step.get('stir_speed', 300.0)
)
action_sequence.extend(dissolve_actions)
# 在步骤之间加入等待时间
if i < len(dissolve_steps) - 1: # 不是最后一个步骤
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10}
})
print(f"DISSOLVE: 连续溶解协议生成完成,共 {len(action_sequence)} 个动作")
return action_sequence
# 测试函数
def test_dissolve_protocol():
"""测试溶解协议的示例"""
print("=== DISSOLVE PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_dissolve_protocol()

View File

@@ -0,0 +1,165 @@
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秒
print(f"DRY: 开始生成干燥协议")
print(f" - 化合物: {compound}")
print(f" - 容器: {vessel}")
print(f" - 干燥温度: {dry_temp}°C")
print(f" - 干燥时间: {dry_time/60:.0f} 分钟")
# 1. 验证目标容器存在
if vessel not in G.nodes():
print(f"DRY: 警告 - 容器 '{vessel}' 不存在于系统中,跳过干燥")
return action_sequence
# 2. 查找相连的加热器
heater_id = find_connected_heater(G, vessel)
if heater_id is None:
print(f"DRY: 警告 - 未找到与容器 '{vessel}' 相连的加热器,跳过干燥")
# 添加一个等待动作,表示干燥过程(模拟)
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 60.0, # 等待1分钟
"description": f"模拟干燥 {compound} (无加热器可用)"
}
})
return action_sequence
# 3. 启动加热器进行干燥
print(f"DRY: 启动加热器 {heater_id} 进行干燥")
# 3.1 启动加热
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": dry_temp,
"purpose": f"干燥 {compound}"
}
})
# 3.2 等待温度稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 60.0,
"description": f"等待温度稳定到 {dry_temp}°C"
}
})
# 3.3 保持干燥温度
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": dry_temp,
"time": dry_time,
"purpose": f"干燥 {compound},保持温度 {dry_temp}°C"
}
})
# 3.4 停止加热
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel,
"purpose": f"干燥完成,停止加热"
}
})
# 3.5 等待冷却
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 300.0, # 等待5分钟冷却
"description": f"等待 {compound} 冷却"
}
})
print(f"DRY: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"DRY: 预计总时间: {(dry_time + 360)/60:.0f} 分钟")
return action_sequence
# 测试函数
def test_dry_protocol():
"""测试干燥协议"""
print("=== DRY PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_dry_protocol()

View File

@@ -1,143 +1,537 @@
import numpy as np
import networkx as nx
from typing import List, Dict, Any, Optional
from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol
def find_gas_source(G: nx.DiGraph, gas: str) -> str:
"""
根据气体名称查找对应的气源,支持多种匹配模式:
1. 容器名称匹配
2. 气体类型匹配data.gas_type
3. 默认气源
"""
print(f"EVACUATE_REFILL: 正在查找气体 '{gas}' 的气源...")
# 第一步:通过容器名称匹配
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}"
]
for pattern in gas_source_patterns:
if pattern in G.nodes():
print(f"EVACUATE_REFILL: 通过名称匹配找到气源: {pattern}")
return pattern
# 第二步:通过气体类型匹配 (data.gas_type)
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():
print(f"EVACUATE_REFILL: 通过气体类型匹配找到气源: {node_id} (gas_type: {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():
print(f"EVACUATE_REFILL: 通过配置气体类型匹配找到气源: {node_id} (config.gas_type: {config_gas_type})")
return node_id
# 第三步:查找所有可用的气源设备
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', 'unknown')
available_gas_sources.append(f"{node_id} (gas_type: {gas_type})")
print(f"EVACUATE_REFILL: 可用气源列表: {available_gas_sources}")
# 第四步:如果找不到特定气体,使用默认的第一个气源
default_gas_sources = [
node for node in G.nodes()
if ((G.nodes[node].get('class') or '').startswith('virtual_gas_source')
or 'gas_source' in node)
]
if default_gas_sources:
default_source = default_gas_sources[0]
print(f"EVACUATE_REFILL: ⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}")
return default_source
raise ValueError(f"找不到气体 '{gas}' 对应的气源。可用气源: {available_gas_sources}")
def find_gas_source_by_any_match(G: nx.DiGraph, gas: str) -> str:
"""
增强版气源查找,支持各种匹配方式的别名函数
"""
return find_gas_source(G, gas)
def get_gas_source_type(G: nx.DiGraph, gas_source: str) -> str:
"""获取气源的气体类型"""
if gas_source not in G.nodes():
return "unknown"
node_data = G.nodes[gas_source]
data = node_data.get('data', {})
config = node_data.get('config', {})
# 检查多个可能的字段
gas_type = (data.get('gas_type') or
config.get('gas_type') or
data.get('gas') or
config.get('gas') or
"air") # 默认为空气
return gas_type
def find_vessels_by_gas_type(G: nx.DiGraph, gas: str) -> List[str]:
"""
根据气体类型查找所有匹配的容器/气源
"""
matching_vessels = []
for node_id in G.nodes():
node_data = G.nodes[node_id]
# 检查容器名称匹配
if gas.lower() in node_id.lower():
matching_vessels.append(f"{node_id} (名称匹配)")
continue
# 检查气体类型匹配
data = node_data.get('data', {})
config = node_data.get('config', {})
gas_type = data.get('gas_type', '') or config.get('gas_type', '')
if gas_type.lower() == gas.lower():
matching_vessels.append(f"{node_id} (gas_type: {gas_type})")
return matching_vessels
def find_vacuum_pump(G: nx.DiGraph) -> str:
"""查找真空泵设备"""
vacuum_pumps = [
node for node in G.nodes()
if ((G.nodes[node].get('class') or '').startswith('virtual_vacuum_pump')
or 'vacuum_pump' in node
or 'vacuum' in (G.nodes[node].get('class') or ''))
]
if not vacuum_pumps:
raise ValueError("系统中未找到真空泵设备")
return vacuum_pumps[0]
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找与指定容器相连的搅拌器"""
stirrer_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
# 检查哪个搅拌器与目标容器相连
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
return stirrer
return stirrer_nodes[0] if stirrer_nodes else None
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 'vacuum' in device_id.lower():
device_type = "vacuum"
elif 'gas' 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 generate_evacuateandrefill_protocol(
G: nx.DiGraph,
vessel: str,
gas: str,
G: nx.DiGraph,
vessel: str,
gas: str,
repeats: int = 1
) -> list[dict]:
) -> 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: 泵操作的动作序列
**修复版本**: 正确调用 pump_protocol 并处理异常
"""
action_sequence = []
# 生成电磁阀、真空泵、气源操作的动作序列
vacuum_action_sequence = []
nodes = G.nodes(data=True)
# 参数设置 - 关键修复:减小体积避免超出泵容量
VACUUM_VOLUME = 20.0 # 减小抽真空体积
REFILL_VOLUME = 20.0 # 减小充气体积
PUMP_FLOW_RATE = 2.5 # 降低流速
STIR_SPEED = 300.0
# 找到和 vessel 相连的电磁阀和真空泵、气源
vacuum_backbone = {"vessel": vessel}
print(f"EVACUATE_REFILL: 开始生成协议,目标容器: {vessel}, 气体: {gas}, 重复次数: {repeats}")
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.")
# 1. 验证设备存在
if vessel not in G.nodes():
raise ValueError(f"目标容器 '{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"
}
}
])
# 2. 查找设备
try:
vacuum_pump = find_vacuum_pump(G)
vacuum_solenoid = find_associated_solenoid_valve(G, vacuum_pump)
gas_source = find_gas_source(G, gas)
gas_solenoid = find_associated_solenoid_valve(G, gas_source)
stirrer_id = find_connected_stirrer(G, vessel)
# 打开真空泵、关闭气源
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}})
print(f"EVACUATE_REFILL: 找到设备")
print(f" - 真空泵: {vacuum_pump}")
print(f" - 气源: {gas_source}")
print(f" - 真空电磁阀: {vacuum_solenoid}")
print(f" - 气源电磁阀: {gas_solenoid}")
print(f" - 搅拌器: {stirrer_id}")
# 关闭真空泵阀门、打开气源阀门
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"
}
}
])
except ValueError as e:
raise ValueError(f"设备查找失败: {str(e)}")
# 3. **关键修复**: 验证路径存在性
try:
# 验证抽真空路径
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump)
print(f"EVACUATE_REFILL: 抽真空路径: {''.join(vacuum_path)}")
# 关闭真空泵、打开气源
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"
}
# 验证充气路径
gas_path = nx.shortest_path(G, source=gas_source, target=vessel)
print(f"EVACUATE_REFILL: 充气路径: {''.join(gas_path)}")
# **新增**: 检查路径中的边数据
for i in range(len(vacuum_path) - 1):
nodeA, nodeB = vacuum_path[i], vacuum_path[i + 1]
edge_data = G.get_edge_data(nodeA, nodeB)
if not edge_data or 'port' not in edge_data:
raise ValueError(f"路径 {nodeA}{nodeB} 缺少端口信息")
print(f" 抽真空路径边 {nodeA}{nodeB}: {edge_data}")
for i in range(len(gas_path) - 1):
nodeA, nodeB = gas_path[i], gas_path[i + 1]
edge_data = G.get_edge_data(nodeA, nodeB)
if not edge_data or 'port' not in edge_data:
raise ValueError(f"路径 {nodeA}{nodeB} 缺少端口信息")
print(f" 充气路径边 {nodeA}{nodeB}: {edge_data}")
except nx.NetworkXNoPath as e:
raise ValueError(f"路径不存在: {str(e)}")
except Exception as e:
raise ValueError(f"路径验证失败: {str(e)}")
# 4. 启动搅拌器
if stirrer_id:
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}})
})
# 5. 执行多次抽真空-充气循环
for cycle in range(repeats):
print(f"EVACUATE_REFILL: === 第 {cycle+1}/{repeats} 次循环 ===")
# ============ 抽真空阶段 ============
print(f"EVACUATE_REFILL: 抽真空阶段开始")
# 启动真空泵
action_sequence.append({
"device_id": vacuum_pump,
"action_name": "set_status",
"action_kwargs": {"string": "ON"}
})
# 开启真空电磁阀
if vacuum_solenoid:
action_sequence.append({
"device_id": vacuum_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"}
})
# **关键修复**: 改进 pump_protocol 调用和错误处理
print(f"EVACUATE_REFILL: 调用抽真空 pump_protocol: {vessel}{vacuum_pump}")
print(f" - 体积: {VACUUM_VOLUME} mL")
print(f" - 流速: {PUMP_FLOW_RATE} mL/s")
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)
print(f"EVACUATE_REFILL: ✅ 成功添加 {len(vacuum_transfer_actions)} 个抽真空动作")
else:
print(f"EVACUATE_REFILL: ⚠️ 抽真空 pump_protocol 返回空序列")
# **修复**: 添加手动泵动作作为备选
action_sequence.extend([
{
"device_id": "multiway_valve_1",
"action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 连接到反应器
},
{
"device_id": "transfer_pump_1",
"action_name": "set_position",
"action_kwargs": {
"position": VACUUM_VOLUME,
"max_velocity": PUMP_FLOW_RATE
}
}
])
print(f"EVACUATE_REFILL: 使用备选手动泵动作")
except Exception as e:
print(f"EVACUATE_REFILL: ❌ 抽真空 pump_protocol 失败: {str(e)}")
import traceback
print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}")
# **修复**: 添加手动动作而不是忽略错误
print(f"EVACUATE_REFILL: 使用手动备选方案")
action_sequence.extend([
{
"device_id": "multiway_valve_1",
"action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 反应器端口
},
{
"device_id": "transfer_pump_1",
"action_name": "set_position",
"action_kwargs": {
"position": VACUUM_VOLUME,
"max_velocity": PUMP_FLOW_RATE
}
}
])
# 关闭真空电磁阀
if vacuum_solenoid:
action_sequence.append({
"device_id": vacuum_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "CLOSED"}
})
# 关闭真空泵
action_sequence.append({
"device_id": vacuum_pump,
"action_name": "set_status",
"action_kwargs": {"string": "OFF"}
})
# ============ 充气阶段 ============
print(f"EVACUATE_REFILL: 充气阶段开始")
# 启动气源
action_sequence.append({
"device_id": gas_source,
"action_name": "set_status",
"action_kwargs": {"string": "ON"}
})
# 开启气源电磁阀
if gas_solenoid:
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"}
})
# **关键修复**: 改进充气 pump_protocol 调用
print(f"EVACUATE_REFILL: 调用充气 pump_protocol: {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)
print(f"EVACUATE_REFILL: ✅ 成功添加 {len(gas_transfer_actions)} 个充气动作")
else:
print(f"EVACUATE_REFILL: ⚠️ 充气 pump_protocol 返回空序列")
# **修复**: 添加手动充气动作
action_sequence.extend([
{
"device_id": "multiway_valve_2",
"action_name": "set_valve_position",
"action_kwargs": {"command": "8"} # 氮气端口
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": {
"position": REFILL_VOLUME,
"max_velocity": PUMP_FLOW_RATE
}
},
{
"device_id": "multiway_valve_2",
"action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 反应器端口
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
"max_velocity": PUMP_FLOW_RATE
}
}
])
except Exception as e:
print(f"EVACUATE_REFILL: ❌ 充气 pump_protocol 失败: {str(e)}")
import traceback
print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}")
# **修复**: 使用手动充气动作
print(f"EVACUATE_REFILL: 使用手动充气方案")
action_sequence.extend([
{
"device_id": "multiway_valve_2",
"action_name": "set_valve_position",
"action_kwargs": {"command": "8"} # 连接气源
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": {
"position": REFILL_VOLUME,
"max_velocity": PUMP_FLOW_RATE
}
},
{
"device_id": "multiway_valve_2",
"action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 连接反应器
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
"max_velocity": PUMP_FLOW_RATE
}
}
])
# 关闭气源电磁阀
if 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": {
"string": "OFF"
}
}
)
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:
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 2.0}
})
# 停止搅拌器
if stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {"vessel": vessel}
})
print(f"EVACUATE_REFILL: 协议生成完成,共 {len(action_sequence)} 个动作")
return action_sequence
# 测试函数
def test_evacuateandrefill_protocol():
"""测试抽真空充气协议"""
print("=== EVACUATE AND REFILL PROTOCOL 测试 ===")
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,326 @@
import numpy as np
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_rotavap_device(G: nx.DiGraph) -> str:
"""查找旋转蒸发仪设备"""
rotavap_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_rotavap']
if rotavap_nodes:
return rotavap_nodes[0]
raise ValueError("系统中未找到旋转蒸发仪设备")
def find_solvent_recovery_vessel(G: nx.DiGraph) -> str:
"""查找溶剂回收容器"""
possible_names = [
"flask_distillate",
"bottle_distillate",
"vessel_distillate",
"distillate",
"solvent_recovery",
"flask_solvent_recovery",
"collection_flask"
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
return vessel_name
# 如果找不到专门的回收容器,使用废液容器
waste_names = ["waste_workup", "flask_waste", "bottle_waste", "waste"]
for vessel_name in waste_names:
if vessel_name in G.nodes():
return vessel_name
raise ValueError(f"未找到溶剂回收容器。尝试了以下名称: {possible_names + waste_names}")
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: float = 1800.0,
stir_speed: float = 100.0
) -> 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.
蒸发流程:
1. 液体转移:将待蒸发溶液从源容器转移到旋转蒸发仪
2. 蒸发操作:执行旋转蒸发
3. (可选) 溶剂回收:将冷凝的溶剂转移到回收容器
4. 残留物转移:将浓缩物从旋转蒸发仪转移回原容器或新容器
Args:
G: 有向图,节点为设备和容器,边为流体管道
vessel: 包含待蒸发溶液的容器名称
pressure: 蒸发时的真空度 (bar)默认0.1 bar
temp: 蒸发时的加热温度 (°C)默认60°C
time: 蒸发时间 (秒)默认1800秒(30分钟)
stir_speed: 旋转速度 (RPM)默认100 RPM
Returns:
List[Dict[str, Any]]: 蒸发操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
evaporate_actions = generate_evaporate_protocol(G, "reaction_mixture", 0.05, 80.0, 3600.0)
"""
action_sequence = []
# 生成泵操作的动作序列
pump_action_sequence = []
reactor_volume = 500.0
transfer_flowrate = flowrate = 2.5
print(f"EVAPORATE: 开始生成蒸发协议")
print(f" - 源容器: {vessel}")
print(f" - 真空度: {pressure} bar")
print(f" - 温度: {temp}°C")
print(f" - 时间: {time}s ({time/60:.1f}分钟)")
print(f" - 旋转速度: {stir_speed} RPM")
# 开启冷凝器
pump_action_sequence.append({
"device_id": "rotavap_chiller",
"action_name": "set_temperature",
"action_kwargs": {
"command": "-40"
}
})
# TODO: 通过温度反馈改为 HeatChillToTemp而非等待固定时间
pump_action_sequence.append({
# 验证源容器存在
if vessel not in G.nodes():
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
# 获取源容器中的液体体积
source_volume = get_vessel_liquid_volume(G, vessel)
print(f"EVAPORATE: 源容器 {vessel} 中有 {source_volume} mL 液体")
# 查找旋转蒸发仪
try:
rotavap_id = find_rotavap_device(G)
print(f"EVAPORATE: 找到旋转蒸发仪: {rotavap_id}")
except ValueError as e:
raise ValueError(f"无法找到旋转蒸发仪: {str(e)}")
# 查找旋转蒸发仪样品容器
rotavap_vessel = None
possible_rotavap_vessels = ["rotavap_flask", "rotavap", "flask_rotavap", "evaporation_flask"]
for rv in possible_rotavap_vessels:
if rv in G.nodes():
rotavap_vessel = rv
break
if not rotavap_vessel:
raise ValueError(f"未找到旋转蒸发仪样品容器。尝试了: {possible_rotavap_vessels}")
print(f"EVAPORATE: 找到旋转蒸发仪样品容器: {rotavap_vessel}")
# 查找溶剂回收容器
try:
distillate_vessel = find_solvent_recovery_vessel(G)
print(f"EVAPORATE: 找到溶剂回收容器: {distillate_vessel}")
except ValueError as e:
print(f"EVAPORATE: 警告 - {str(e)}")
distillate_vessel = None
# === 简化的体积计算策略 ===
if source_volume > 0:
# 如果能检测到液体体积,使用实际体积的大部分
transfer_volume = min(source_volume * 0.9, 250.0) # 90%或最多250mL
print(f"EVAPORATE: 检测到液体体积,将转移 {transfer_volume} mL")
else:
# 如果检测不到液体体积,默认转移一整瓶 250mL
transfer_volume = 250.0
print(f"EVAPORATE: 未检测到液体体积,默认转移整瓶 {transfer_volume} mL")
# === 第一步:将待蒸发溶液转移到旋转蒸发仪 ===
print(f"EVAPORATE: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {rotavap_vessel}")
try:
transfer_to_rotavap_actions = generate_pump_protocol(
G=G,
from_vessel=vessel,
to_vessel=rotavap_vessel,
volume=transfer_volume,
flowrate=2.0,
transfer_flowrate=2.0
)
action_sequence.extend(transfer_to_rotavap_actions)
except Exception as e:
raise ValueError(f"无法将溶液转移到旋转蒸发仪: {str(e)}")
# 转移后等待
wait_action = {
"action_name": "wait",
"action_kwargs": {
"time": 1800
}
})
"action_kwargs": {"time": 10}
}
action_sequence.append(wait_action)
# 开启旋蒸真空泵、旋转在液体转移后运行time时间
pump_action_sequence.append({
"device_id": "rotavap_controller",
"action_name": "set_pump_time",
# === 第二步:执行旋转蒸发 ===
print(f"EVAPORATE: 执行旋转蒸发操作")
evaporate_action = {
"device_id": rotavap_id,
"action_name": "evaporate",
"action_kwargs": {
"command": str(time + reactor_volume / flowrate * 3)
"vessel": rotavap_vessel,
"pressure": pressure,
"temp": temp,
"time": time,
"stir_speed": stir_speed
}
})
pump_action_sequence.append({
"device_id": "rotavap_controller",
"action_name": "set_pump_time",
"action_kwargs": {
"command": str(time + reactor_volume / flowrate * 3)
}
})
}
action_sequence.append(evaporate_action)
# 液体转入旋转蒸发器
pump_action_sequence.append({
"device_id": "",
"action_name": "PumpTransferProtocol",
"action_kwargs": {
"from_vessel": vessel,
"to_vessel": "rotavap",
"volume": reactor_volume,
"time": reactor_volume / flowrate,
# "transfer_flowrate": transfer_flowrate,
}
})
pump_action_sequence.append({
# 蒸发后等待系统稳定
wait_action = {
"action_name": "wait",
"action_kwargs": {
"time": time
}
})
return pump_action_sequence
"action_kwargs": {"time": 30}
}
action_sequence.append(wait_action)
# === 第三步:溶剂回收(如果有回收容器)===
if distillate_vessel:
print(f"EVAPORATE: 回收冷凝溶剂到 {distillate_vessel}")
try:
condenser_vessel = "rotavap_condenser"
if condenser_vessel in G.nodes():
# 估算回收体积约为转移体积的70% - 大部分溶剂被蒸发回收)
recovery_volume = transfer_volume * 0.7
print(f"EVAPORATE: 预计回收 {recovery_volume} mL 溶剂")
recovery_actions = generate_pump_protocol(
G=G,
from_vessel=condenser_vessel,
to_vessel=distillate_vessel,
volume=recovery_volume,
flowrate=3.0,
transfer_flowrate=3.0
)
action_sequence.extend(recovery_actions)
else:
print("EVAPORATE: 未找到冷凝器容器,跳过溶剂回收")
except Exception as e:
print(f"EVAPORATE: 溶剂回收失败: {str(e)}")
# === 第四步:将浓缩物转移回原容器 ===
print(f"EVAPORATE: 将浓缩物从旋转蒸发仪转移回 {vessel}")
try:
# 估算浓缩物体积约为转移体积的20% - 大部分溶剂已蒸发)
concentrate_volume = transfer_volume * 0.2
print(f"EVAPORATE: 预计浓缩物体积 {concentrate_volume} mL")
transfer_back_actions = generate_pump_protocol(
G=G,
from_vessel=rotavap_vessel,
to_vessel=vessel,
volume=concentrate_volume,
flowrate=1.0, # 浓缩物可能粘稠,用较慢流速
transfer_flowrate=1.0
)
action_sequence.extend(transfer_back_actions)
except Exception as e:
print(f"EVAPORATE: 将浓缩物转移回容器失败: {str(e)}")
# === 第五步:清洗旋转蒸发仪 ===
print(f"EVAPORATE: 清洗旋转蒸发仪")
try:
# 查找清洗溶剂
cleaning_solvent = None
for solvent in ["flask_ethanol", "flask_acetone", "flask_water"]:
if solvent in G.nodes():
cleaning_solvent = solvent
break
if cleaning_solvent and distillate_vessel:
# 用固定量溶剂清洗(不依赖检测体积)
cleaning_volume = 50.0 # 固定50mL清洗
print(f"EVAPORATE: 用 {cleaning_volume} mL {cleaning_solvent} 清洗")
# 清洗溶剂加入
cleaning_actions = generate_pump_protocol(
G=G,
from_vessel=cleaning_solvent,
to_vessel=rotavap_vessel,
volume=cleaning_volume,
flowrate=2.0,
transfer_flowrate=2.0
)
action_sequence.extend(cleaning_actions)
# 将清洗液转移到废液/回收容器
waste_actions = generate_pump_protocol(
G=G,
from_vessel=rotavap_vessel,
to_vessel=distillate_vessel, # 使用回收容器作为废液
volume=cleaning_volume,
flowrate=2.0,
transfer_flowrate=2.0
)
action_sequence.extend(waste_actions)
except Exception as e:
print(f"EVAPORATE: 清洗步骤失败: {str(e)}")
print(f"EVAPORATE: 生成了 {len(action_sequence)} 个动作")
print(f"EVAPORATE: 蒸发协议生成完成")
print(f"EVAPORATE: 总处理体积: {transfer_volume} mL")
return action_sequence
# 便捷函数:常用蒸发方案 - 都使用250mL标准瓶体积
def generate_quick_evaporate_protocol(
G: nx.DiGraph,
vessel: str,
temp: float = 40.0,
time: float = 900.0 # 15分钟
) -> List[Dict[str, Any]]:
"""快速蒸发:低温、短时间、整瓶处理"""
return generate_evaporate_protocol(G, vessel, 0.2, temp, time, 80.0)
def generate_gentle_evaporate_protocol(
G: nx.DiGraph,
vessel: str,
temp: float = 50.0,
time: float = 2700.0 # 45分钟
) -> List[Dict[str, Any]]:
"""温和蒸发:中等条件、较长时间、整瓶处理"""
return generate_evaporate_protocol(G, vessel, 0.1, temp, time, 60.0)
def generate_high_vacuum_evaporate_protocol(
G: nx.DiGraph,
vessel: str,
temp: float = 35.0,
time: float = 3600.0 # 1小时
) -> List[Dict[str, Any]]:
"""高真空蒸发:低温、高真空、长时间、整瓶处理"""
return generate_evaporate_protocol(G, vessel, 0.01, temp, time, 120.0)
def generate_standard_evaporate_protocol(
G: nx.DiGraph,
vessel: str
) -> List[Dict[str, Any]]:
"""标准蒸发常用参数、整瓶250mL处理"""
return generate_evaporate_protocol(
G=G,
vessel=vessel,
pressure=0.1, # 标准真空度
temp=60.0, # 适中温度
time=1800.0, # 30分钟
stir_speed=100.0 # 适中旋转速度
)

View File

@@ -1,5 +1,89 @@
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_device(G: nx.DiGraph) -> str:
"""查找过滤器设备"""
filter_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_filter']
if filter_nodes:
return filter_nodes[0]
raise ValueError("系统中未找到过滤器设备")
def find_filter_vessel(G: nx.DiGraph) -> str:
"""查找过滤器专用容器"""
possible_names = [
"filter_vessel", # 标准过滤器容器
"filtration_vessel", # 备选名称
"vessel_filter", # 备选名称
"filter_unit", # 备选名称
"filter" # 简单名称
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
return vessel_name
raise ValueError(f"未找到过滤器容器。尝试了以下名称: {possible_names}")
def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
"""查找滤液收集容器"""
if filtrate_vessel and filtrate_vessel in G.nodes():
return filtrate_vessel
# 自动查找滤液容器
possible_names = [
"filtrate_vessel",
"collection_bottle_1",
"collection_bottle_2",
"waste_workup"
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
return vessel_name
raise ValueError(f"未找到滤液收集容器。尝试了以下名称: {possible_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') == '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]
raise ValueError(f"未找到与容器 {vessel} 相连的加热搅拌器")
def generate_filter_protocol(
G: nx.DiGraph,
@@ -12,59 +96,209 @@ def generate_filter_protocol(
volume: float = 0.0
) -> List[Dict[str, Any]]:
"""
生成过滤操作的协议序列
生成过滤操作的协议序列,复用 pump_protocol 的成熟算法
过滤流程:
1. 液体转移:将待过滤溶液从源容器转移到过滤器
2. 启动加热搅拌:设置温度和搅拌
3. 执行过滤:通过过滤器分离固液
4. (可选) 继续或停止加热搅拌
Args:
G: 有向图,节点为设备和容器
vessel: 过滤容器
filtrate_vessel: 滤液容器(可选)
stir: 是否搅拌
stir_speed: 搅拌速度(可选)
temp: 温度(可选,摄氏度)
continue_heatchill: 是否继续加热冷却
volume: 过滤体积(可选)
G: 有向图,节点为设备和容器,边为流体管道
vessel: 包含待过滤溶液的容器名称
filtrate_vessel: 滤液收集容器(可选,自动查找
stir: 是否在过滤过程中搅拌
stir_speed: 搅拌速度 (RPM)
temp: 过滤温度 (°C)
continue_heatchill: 过滤后是否继续加热搅拌
volume: 预期过滤体积 (mL)0表示全部过滤
Returns:
List[Dict[str, Any]]: 过滤操作的动作序列
Raises:
ValueError: 当找不到过滤设备时抛出异常
Examples:
filter_protocol = generate_filter_protocol(G, "reactor", "filtrate_vessel", stir=True, volume=100.0)
"""
action_sequence = []
# 查找过滤设备
filter_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_filter']
print(f"FILTER: 开始生成过滤协议")
print(f" - 源容器: {vessel}")
print(f" - 滤液容器: {filtrate_vessel}")
print(f" - 搅拌: {stir} ({stir_speed} RPM)" if stir else " - 搅拌: 否")
print(f" - 过滤温度: {temp}°C")
print(f" - 预期过滤体积: {volume} mL" if volume > 0 else " - 预期过滤体积: 全部")
print(f" - 继续加热搅拌: {continue_heatchill}")
if not filter_nodes:
raise ValueError("没有找到可用的过滤设备")
# 使用第一个可用的过滤器
filter_id = filter_nodes[0]
# 验证容器是否存在
# 验证源容器存在
if vessel not in G.nodes():
raise ValueError(f"过滤容器 {vessel} 不存在于")
raise ValueError(f"容器 '{vessel}' 不存在于系统")
if filtrate_vessel and filtrate_vessel not in G.nodes():
raise ValueError(f"滤液容器 {filtrate_vessel} 不存在于图中")
# 获取源容器中的液体体积
source_volume = get_vessel_liquid_volume(G, vessel)
print(f"FILTER: 源容器 {vessel} 中有 {source_volume} mL 液体")
# 执行过滤操作
# 查找过滤器设备
try:
filter_id = find_filter_device(G)
print(f"FILTER: 找到过滤器: {filter_id}")
except ValueError as e:
raise ValueError(f"无法找到过滤器: {str(e)}")
# 查找过滤器容器
try:
filter_vessel_id = find_filter_vessel(G)
print(f"FILTER: 找到过滤器容器: {filter_vessel_id}")
except ValueError as e:
raise ValueError(f"无法找到过滤器容器: {str(e)}")
# 查找滤液收集容器
try:
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel)
print(f"FILTER: 找到滤液收集容器: {actual_filtrate_vessel}")
except ValueError as e:
raise ValueError(f"无法找到滤液收集容器: {str(e)}")
# 查找加热搅拌器(如果需要温度控制或搅拌)
heatchill_id = None
if temp != 25.0 or stir or continue_heatchill:
try:
heatchill_id = find_connected_heatchill(G, filter_vessel_id)
print(f"FILTER: 找到加热搅拌器: {heatchill_id}")
except ValueError as e:
print(f"FILTER: 警告 - {str(e)}")
# === 简化的体积计算策略 ===
if volume > 0:
transfer_volume = min(volume, source_volume if source_volume > 0 else volume)
print(f"FILTER: 指定过滤体积 {transfer_volume} mL")
elif source_volume > 0:
transfer_volume = source_volume * 0.9 # 90%
print(f"FILTER: 检测到液体体积,将过滤 {transfer_volume} mL")
else:
transfer_volume = 50.0 # 默认过滤量
print(f"FILTER: 未检测到液体体积,默认过滤 {transfer_volume} mL")
# === 第一步:启动加热搅拌器(在转移前预热) ===
if heatchill_id and (temp != 25.0 or stir):
print(f"FILTER: 启动加热搅拌器,温度: {temp}°C搅拌: {stir}")
heatchill_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": filter_vessel_id,
"temp": temp,
"purpose": f"过滤过程温度控制和搅拌"
}
}
action_sequence.append(heatchill_action)
# 等待温度稳定
if temp != 25.0:
wait_time = min(30, abs(temp - 25.0) * 1.0) # 根据温差估算预热时间
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": wait_time}
})
# === 第二步:将待过滤溶液转移到过滤器 ===
print(f"FILTER: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {filter_vessel_id}")
try:
# 使用成熟的 pump_protocol 算法进行液体转移
transfer_to_filter_actions = generate_pump_protocol(
G=G,
from_vessel=vessel,
to_vessel=filter_vessel_id,
volume=transfer_volume,
flowrate=1.0, # 过滤转移用较慢速度,避免扰动
transfer_flowrate=1.5
)
action_sequence.extend(transfer_to_filter_actions)
except Exception as e:
raise ValueError(f"无法将溶液转移到过滤器: {str(e)}")
# 转移后等待
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
# === 第三步:执行过滤操作(完全按照 Filter.action 参数) ===
print(f"FILTER: 执行过滤操作")
filter_action = {
"device_id": filter_id,
"action_name": "filter_sample",
"action_name": "filter",
"action_kwargs": {
"vessel": vessel,
"filtrate_vessel": filtrate_vessel,
"vessel": filter_vessel_id,
"filtrate_vessel": actual_filtrate_vessel,
"stir": stir,
"stir_speed": stir_speed,
"temp": temp,
"continue_heatchill": continue_heatchill,
"volume": volume
"volume": transfer_volume
}
}
action_sequence.append(filter_action)
# 过滤后等待
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10}
})
return action_sequence
# === 第四步:如果不继续加热搅拌,停止加热器 ===
if heatchill_id and not continue_heatchill and (temp != 25.0 or stir):
print(f"FILTER: 停止加热搅拌器")
stop_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": filter_vessel_id
}
}
action_sequence.append(stop_action)
print(f"FILTER: 生成了 {len(action_sequence)} 个动作")
print(f"FILTER: 过滤协议生成完成")
return action_sequence
# 便捷函数:常用过滤方案
def generate_gravity_filter_protocol(
G: nx.DiGraph,
vessel: str,
filtrate_vessel: str = ""
) -> List[Dict[str, Any]]:
"""重力过滤:室温,无搅拌"""
return generate_filter_protocol(G, vessel, filtrate_vessel, False, 0.0, 25.0, False, 0.0)
def generate_hot_filter_protocol(
G: nx.DiGraph,
vessel: str,
filtrate_vessel: str = "",
temp: float = 60.0
) -> List[Dict[str, Any]]:
"""热过滤:高温过滤,防止结晶析出"""
return generate_filter_protocol(G, vessel, filtrate_vessel, False, 0.0, temp, False, 0.0)
def generate_stirred_filter_protocol(
G: nx.DiGraph,
vessel: str,
filtrate_vessel: str = "",
stir_speed: float = 200.0
) -> List[Dict[str, Any]]:
"""搅拌过滤:低速搅拌,防止滤饼堵塞"""
return generate_filter_protocol(G, vessel, filtrate_vessel, True, stir_speed, 25.0, False, 0.0)
def generate_hot_stirred_filter_protocol(
G: nx.DiGraph,
vessel: str,
filtrate_vessel: str = "",
temp: float = 60.0,
stir_speed: float = 300.0
) -> List[Dict[str, Any]]:
"""热搅拌过滤:高温搅拌过滤"""
return generate_filter_protocol(G, vessel, filtrate_vessel, True, stir_speed, temp, False, 0.0)

View File

@@ -1,5 +1,72 @@
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,
@@ -12,10 +79,15 @@ def generate_filter_through_protocol(
residence_time: float = 0.0
) -> List[Dict[str, Any]]:
"""
生成通过过滤介质过滤的协议序列
生成通过过滤介质过滤的协议序列,复用 pump_protocol 的成熟算法
过滤流程:
1. 液体转移:将样品从源容器转移到过滤介质
2. 重力过滤:液体通过过滤介质自动流到目标容器
3. 洗脱操作:将洗脱溶剂通过过滤介质洗脱目标物质
Args:
G: 有向图,节点为设备和容器
G: 有向图,节点为设备和容器,边为流体管道
from_vessel: 源容器的名称,即物质起始所在的容器
to_vessel: 目标容器的名称,物质过滤后要到达的容器
filter_through: 过滤时所通过的介质,如滤纸、柱子等
@@ -28,123 +100,288 @@ def generate_filter_through_protocol(
List[Dict[str, Any]]: 过滤操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
ValueError: 当找不到必要的设备或容器
Examples:
filter_through_protocol = generate_filter_through_protocol(
G, "reactor", "collection_flask", "celite", "ethanol", 50.0, 2, 60.0
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} 不存在于")
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统")
if to_vessel not in G.nodes():
raise ValueError(f"目标容器 {to_vessel} 不存在于")
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统")
# 查找转移泵设备(用于液体转移)
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_transfer_pump']
# 获取源容器中的液体体积
source_volume = get_vessel_liquid_volume(G, from_vessel)
print(f"FILTER_THROUGH: 源容器 {from_vessel} 中有 {source_volume} mL 液体")
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备")
# 查找过滤介质容器
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)}")
pump_id = pump_nodes[0]
# 查找洗脱溶剂容器(如果需要)
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)}")
# 查找过滤设备(可选,如果有专门的过滤设备)
filter_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_filter']
# === 第一步:将样品从源容器转移到过滤介质 ===
transfer_volume = source_volume if source_volume > 0 else 100.0 # 默认100mL
print(f"FILTER_THROUGH: 将 {transfer_volume} mL 样品从 {from_vessel} 转移到 {filter_through_vessel}")
filter_id = filter_nodes[0] if filter_nodes else None
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)}")
# 查找洗脱溶剂容器(如果需要洗脱)
eluting_vessel = None
if eluting_solvent and eluting_volume > 0:
eluting_vessel = f"flask_{eluting_solvent}"
if eluting_vessel not in G.nodes():
# 查找可用的溶剂容器
available_vessels = [node for node in G.nodes()
if node.startswith('flask_') and
G.nodes[node].get('type') == 'container']
if available_vessels:
eluting_vessel = available_vessels[0]
else:
raise ValueError(f"没有找到洗脱溶剂容器 {eluting_solvent}")
# 步骤1将样品从源容器转移到过滤装置模拟通过过滤介质
# 这里我们将过滤过程分解为多个转移步骤来模拟通过介质的过程
# 首先转移样品(模拟样品通过过滤介质)
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"volume": 0.0, # 转移所有液体,体积由系统确定
"amount": f"通过 {filter_through} 过滤",
"time": residence_time if residence_time > 0 else 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": True # 通过过滤介质可能涉及固体分离
}
})
# 步骤2如果有专门的过滤设备使用过滤设备处理
if filter_id:
# === 第二步:等待样品通过过滤介质(停留时间) ===
if residence_time > 0:
print(f"FILTER_THROUGH: 等待样品在过滤介质中停留 {residence_time}s")
action_sequence.append({
"device_id": filter_id,
"action_name": "filter_sample",
"action_kwargs": {
"vessel": to_vessel,
"filtrate_vessel": to_vessel,
"stir": False,
"stir_speed": 0.0,
"temp": 25.0,
"continue_heatchill": False,
"volume": 0.0
}
"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}
})
# 步骤3:洗脱操作(如果指定了洗脱溶剂和重复次数)
# === 第三步:洗脱操作(如果指定了洗脱参数) ===
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel:
for repeat in range(eluting_repeats):
# 添加洗脱溶剂
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({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": eluting_vessel,
"to_vessel": to_vessel,
"volume": eluting_volume,
"amount": f"洗脱溶剂 {eluting_solvent} - 第 {repeat + 1}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
"action_name": "wait",
"action_kwargs": {"time": eluting_wait_time}
})
# 如果有过滤设备,再次过滤洗脱液
if filter_id:
# 洗脱间隔等待
if repeat_idx < eluting_repeats - 1: # 不是最后一次洗脱
action_sequence.append({
"device_id": filter_id,
"action_name": "filter_sample",
"action_kwargs": {
"vessel": to_vessel,
"filtrate_vessel": to_vessel,
"stir": False,
"stir_speed": 0.0,
"temp": 25.0,
"continue_heatchill": False,
"volume": eluting_volume
}
"action_name": "wait",
"action_kwargs": {"time": 10}
})
return action_sequence
# === 第四步:最终等待,确保所有液体完全通过 ===
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

@@ -1,33 +1,61 @@
from typing import List, Dict, Any
from typing import List, Dict, Any, Optional
import networkx as nx
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]
raise ValueError("系统中未找到可用的加热/冷却设备")
def generate_heat_chill_protocol(
G: nx.DiGraph,
vessel: str,
temp: float,
time: float,
stir: bool,
stir_speed: float,
purpose: str
stir: bool = False,
stir_speed: float = 300.0,
purpose: str = "加热/冷却操作"
) -> List[Dict[str, Any]]:
"""
生成加热/冷却操作的协议序列 - 严格按照 HeatChill.action
生成加热/冷却操作的协议序列 - 带时间限制的完整操作
"""
action_sequence = []
# 查找加热/冷却设备
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
if not heatchill_nodes:
raise ValueError("没有找到可用的加热/冷却设备")
heatchill_id = heatchill_nodes[0]
print(f"HEATCHILL: 开始生成加热/冷却协议")
print(f" - 容器: {vessel}")
print(f" - 目标温度: {temp}°C")
print(f" - 持续时间: {time}")
print(f" - 使用内置搅拌: {stir}, 速度: {stir_speed} RPM")
print(f" - 目的: {purpose}")
# 1. 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于")
raise ValueError(f"容器 '{vessel}' 不存在于系统")
action_sequence.append({
# 2. 查找加热/冷却设备
try:
heatchill_id = find_connected_heatchill(G, vessel)
print(f"HEATCHILL: 找到加热/冷却设备: {heatchill_id}")
except ValueError as e:
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
# 3. 执行加热/冷却操作
heatchill_action = {
"device_id": heatchill_id,
"action_name": "heat_chill",
"action_kwargs": {
@@ -36,10 +64,13 @@ def generate_heat_chill_protocol(
"time": time,
"stir": stir,
"stir_speed": stir_speed,
"purpose": purpose
"status": "start"
}
})
}
action_sequence.append(heatchill_action)
print(f"HEATCHILL: 生成了 {len(action_sequence)} 个动作")
return action_sequence
@@ -47,25 +78,31 @@ def generate_heat_chill_start_protocol(
G: nx.DiGraph,
vessel: str,
temp: float,
purpose: str
purpose: str = "开始加热/冷却"
) -> List[Dict[str, Any]]:
"""
生成开始加热/冷却操作的协议序列 - 严格按照 HeatChillStart.action
生成开始加热/冷却操作的协议序列
"""
action_sequence = []
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
if not heatchill_nodes:
raise ValueError("没有找到可用的加热/冷却设备")
heatchill_id = heatchill_nodes[0]
print(f"HEATCHILL_START: 开始生成加热/冷却启动协议")
print(f" - 容器: {vessel}")
print(f" - 目标温度: {temp}°C")
print(f" - 目的: {purpose}")
# 1. 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于")
raise ValueError(f"容器 '{vessel}' 不存在于系统")
action_sequence.append({
# 2. 查找加热/冷却设备
try:
heatchill_id = find_connected_heatchill(G, vessel)
print(f"HEATCHILL_START: 找到加热/冷却设备: {heatchill_id}")
except ValueError as e:
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
# 3. 执行开始加热/冷却操作
heatchill_start_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
@@ -73,8 +110,11 @@ def generate_heat_chill_start_protocol(
"temp": temp,
"purpose": purpose
}
})
}
action_sequence.append(heatchill_start_action)
print(f"HEATCHILL_START: 生成了 {len(action_sequence)} 个动作")
return action_sequence
@@ -84,34 +124,250 @@ def generate_heat_chill_stop_protocol(
) -> List[Dict[str, Any]]:
"""
生成停止加热/冷却操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 容器名称
Returns:
List[Dict[str, Any]]: 停止加热/冷却操作的动作序列
"""
action_sequence = []
# 查找加热/冷却设备
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
if not heatchill_nodes:
raise ValueError("没有找到可用的加热/冷却设备")
heatchill_id = heatchill_nodes[0]
print(f"HEATCHILL_STOP: 开始生成加热/冷却停止协议")
print(f" - 容器: {vessel}")
# 1. 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于")
raise ValueError(f"容器 '{vessel}' 不存在于系统")
action_sequence.append({
# 2. 查找加热/冷却设备
try:
heatchill_id = find_connected_heatchill(G, vessel)
print(f"HEATCHILL_STOP: 找到加热/冷却设备: {heatchill_id}")
except ValueError as e:
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
# 3. 执行停止加热/冷却操作
heatchill_stop_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
})
}
return action_sequence
action_sequence.append(heatchill_stop_action)
print(f"HEATCHILL_STOP: 生成了 {len(action_sequence)} 个动作")
return action_sequence
def generate_heat_chill_to_temp_protocol(
G: nx.DiGraph,
vessel: str,
temp: float,
active: bool = True,
continue_heatchill: bool = False,
stir: bool = False,
stir_speed: Optional[float] = None,
purpose: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
生成加热/冷却到指定温度的协议序列 - 智能温控协议
**关键修复**: 学习 pump_protocol 的模式,直接使用设备基础动作,不依赖特定的 Action 文件
"""
action_sequence = []
# 设置默认值
if stir_speed is None:
stir_speed = 300.0
if purpose is None:
purpose = f"智能温控到 {temp}°C"
print(f"HEATCHILL_TO_TEMP: 开始生成智能温控协议")
print(f" - 容器: {vessel}")
print(f" - 目标温度: {temp}°C")
print(f" - 主动控温: {active}")
print(f" - 达到温度后继续: {continue_heatchill}")
print(f" - 搅拌: {stir}, 速度: {stir_speed} RPM")
print(f" - 目的: {purpose}")
# 1. 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
# 2. 查找加热/冷却设备
try:
heatchill_id = find_connected_heatchill(G, vessel)
print(f"HEATCHILL_TO_TEMP: 找到加热/冷却设备: {heatchill_id}")
except ValueError as e:
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
# 3. 根据参数选择合适的基础动作组合 (学习 pump_protocol 的模式)
if not active:
print(f"HEATCHILL_TO_TEMP: 非主动模式,仅等待")
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0,
"purpose": f"等待容器 {vessel} 自然达到 {temp}°C"
}
})
else:
if continue_heatchill:
# 持续模式:使用 heat_chill_start 基础动作
print(f"HEATCHILL_TO_TEMP: 使用持续温控模式")
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_start", # ← 直接使用设备基础动作
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": f"{purpose} (持续保温)"
}
})
else:
# 一次性模式:使用 heat_chill 基础动作
print(f"HEATCHILL_TO_TEMP: 使用一次性温控模式")
estimated_time = max(60.0, min(900.0, abs(temp - 25.0) * 30.0))
print(f"HEATCHILL_TO_TEMP: 估算所需时间: {estimated_time}")
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill", # ← 直接使用设备基础动作
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"time": estimated_time,
"stir": stir,
"stir_speed": stir_speed,
"status": "start"
}
})
print(f"HEATCHILL_TO_TEMP: 生成了 {len(action_sequence)} 个动作")
return action_sequence
# 扩展版本的加热/冷却协议,集成智能温控功能
def generate_smart_heat_chill_protocol(
G: nx.DiGraph,
vessel: str,
temp: float,
time: float = 0.0, # 0表示自动估算
active: bool = True,
continue_heatchill: bool = False,
stir: bool = False,
stir_speed: float = 300.0,
purpose: str = "智能加热/冷却"
) -> List[Dict[str, Any]]:
"""
这个函数集成了 generate_heat_chill_to_temp_protocol 的智能逻辑,
但使用现有的 Action 类型
"""
# 如果时间为0自动估算
if time == 0.0:
estimated_time = max(60.0, min(900.0, abs(temp - 25.0) * 30.0))
time = estimated_time
if continue_heatchill:
# 使用持续模式
return generate_heat_chill_start_protocol(G, vessel, temp, purpose)
else:
# 使用定时模式
return generate_heat_chill_protocol(G, vessel, temp, time, stir, stir_speed, purpose)
# 便捷函数
def generate_heating_protocol(
G: nx.DiGraph,
vessel: str,
temp: float,
time: float = 300.0,
stir: bool = True,
stir_speed: float = 300.0
) -> List[Dict[str, Any]]:
"""生成加热协议的便捷函数"""
return generate_heat_chill_protocol(
G=G, vessel=vessel, temp=temp, time=time,
stir=stir, stir_speed=stir_speed, purpose=f"加热到 {temp}°C"
)
def generate_cooling_protocol(
G: nx.DiGraph,
vessel: str,
temp: float,
time: float = 600.0,
stir: bool = True,
stir_speed: float = 200.0
) -> List[Dict[str, Any]]:
"""生成冷却协议的便捷函数"""
return generate_heat_chill_protocol(
G=G, vessel=vessel, temp=temp, time=time,
stir=stir, stir_speed=stir_speed, purpose=f"冷却到 {temp}°C"
)
# # 温度预设快捷函数
# def generate_room_temp_protocol(
# G: nx.DiGraph,
# vessel: str,
# stir: bool = False
# ) -> List[Dict[str, Any]]:
# """返回室温的快捷函数"""
# return generate_heat_chill_to_temp_protocol(
# G=G,
# vessel=vessel,
# temp=25.0,
# active=True,
# continue_heatchill=False,
# stir=stir,
# purpose="冷却到室温"
# )
# def generate_reflux_heating_protocol(
# G: nx.DiGraph,
# vessel: str,
# temp: float,
# time: float = 3600.0 # 1小时回流
# ) -> List[Dict[str, Any]]:
# """回流加热的快捷函数"""
# return generate_heat_chill_protocol(
# G=G,
# vessel=vessel,
# temp=temp,
# time=time,
# stir=True,
# stir_speed=400.0, # 回流时较快搅拌
# purpose=f"回流加热到 {temp}°C"
# )
# def generate_ice_bath_protocol(
# G: nx.DiGraph,
# vessel: str,
# time: float = 600.0 # 10分钟冰浴
# ) -> List[Dict[str, Any]]:
# """冰浴冷却的快捷函数"""
# return generate_heat_chill_protocol(
# G=G,
# vessel=vessel,
# temp=0.0,
# time=time,
# stir=True,
# stir_speed=150.0, # 冰浴时缓慢搅拌
# purpose="冰浴冷却到 0°C"
# )
# 测试函数
def test_heatchill_protocol():
"""测试加热/冷却协议的示例"""
print("=== HEAT CHILL PROTOCOL 测试 ===")
print("完整的四个协议函数:")
print("1. generate_heat_chill_protocol - 带时间限制的完整操作")
print("2. generate_heat_chill_start_protocol - 持续加热/冷却")
print("3. generate_heat_chill_stop_protocol - 停止加热/冷却")
print("4. generate_heat_chill_to_temp_protocol - 智能温控 (您的 HeatChillToTemp)")
print("测试完成")
if __name__ == "__main__":
test_heatchill_protocol()

View File

@@ -0,0 +1,366 @@
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": 120.0,
"description": f"等待温度稳定到 {temperature}°C"
}
})
# 保持反应温度
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/3600:.1f} 小时"
}
})
else:
print(f"HYDROGENATE: 警告 - 未找到加热器,使用室温反应")
# 室温反应,只等待时间
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": reaction_time,
"description": f"室温氢化反应 {reaction_time/3600:.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()

View File

@@ -8,7 +8,8 @@ def is_integrated_pump(node_name):
def find_connected_pump(G, valve_node):
for neighbor in G.neighbors(valve_node):
if "pump" in G.nodes[neighbor]["class"]:
node_class = G.nodes[neighbor].get("class") or "" # 防止 None
if "pump" in node_class:
return neighbor
raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点")

View File

@@ -0,0 +1,281 @@
import networkx as nx
from typing import List, Dict, Any, Tuple
from .pump_protocol import generate_pump_protocol_with_rinsing
def parse_ratio(ratio_str: str) -> Tuple[float, float]:
"""
解析比例字符串,支持多种格式
Args:
ratio_str: 比例字符串(如 "1:1", "3:7", "50:50"
Returns:
Tuple[float, float]: 比例元组 (ratio1, ratio2)
"""
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])
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])
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])
return ratio1, ratio2
# 默认 1:1
print(f"RECRYSTALLIZE: 无法解析比例 '{ratio_str}',使用默认比例 1:1")
return 1.0, 1.0
except ValueError:
print(f"RECRYSTALLIZE: 比例解析错误 '{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
"""
print(f"RECRYSTALLIZE: 正在查找溶剂 '{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}",
]
# 第一步:通过容器名称匹配
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"RECRYSTALLIZE: 通过名称匹配找到容器: {vessel_name}")
return vessel_name
# 第二步:通过模糊匹配
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:
print(f"RECRYSTALLIZE: 通过模糊匹配找到容器: {node_id}")
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', '')).lower()
reagent_name = vessel_data.get('reagent_name', '').lower()
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
print(f"RECRYSTALLIZE: 通过液体类型匹配找到容器: {node_id}")
return node_id
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
def generate_recrystallize_protocol(
G: nx.DiGraph,
ratio: str,
solvent1: str,
solvent2: str,
vessel: str,
volume: float,
**kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]:
"""
生成重结晶协议序列
Args:
G: 有向图,节点为容器和设备
ratio: 溶剂比例(如 "1:1", "3:7"
solvent1: 第一种溶剂名称
solvent2: 第二种溶剂名称
vessel: 目标容器
volume: 总体积 (mL)
**kwargs: 其他可选参数,但不使用
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
print(f"RECRYSTALLIZE: 开始生成重结晶协议")
print(f" - 比例: {ratio}")
print(f" - 溶剂1: {solvent1}")
print(f" - 溶剂2: {solvent2}")
print(f" - 容器: {vessel}")
print(f" - 总体积: {volume} mL")
# 1. 验证目标容器存在
if vessel not in G.nodes():
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
# 2. 解析比例
ratio1, ratio2 = parse_ratio(ratio)
total_ratio = ratio1 + ratio2
# 3. 计算各溶剂体积
volume1 = volume * (ratio1 / total_ratio)
volume2 = volume * (ratio2 / total_ratio)
print(f"RECRYSTALLIZE: 解析比例: {ratio1}:{ratio2}")
print(f"RECRYSTALLIZE: {solvent1} 体积: {volume1:.2f} mL")
print(f"RECRYSTALLIZE: {solvent2} 体积: {volume2:.2f} mL")
# 4. 查找溶剂容器
try:
solvent1_vessel = find_solvent_vessel(G, solvent1)
print(f"RECRYSTALLIZE: 找到溶剂1容器: {solvent1_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂1 '{solvent1}': {str(e)}")
try:
solvent2_vessel = find_solvent_vessel(G, solvent2)
print(f"RECRYSTALLIZE: 找到溶剂2容器: {solvent2_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂2 '{solvent2}': {str(e)}")
# 5. 验证路径存在
try:
path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel)
print(f"RECRYSTALLIZE: 溶剂1路径: {''.join(path1)}")
except nx.NetworkXNoPath:
raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel}' 没有可用路径")
try:
path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel)
print(f"RECRYSTALLIZE: 溶剂2路径: {''.join(path2)}")
except nx.NetworkXNoPath:
raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel}' 没有可用路径")
# 6. 添加第一种溶剂
print(f"RECRYSTALLIZE: 开始添加溶剂1 {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)
except Exception as e:
raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}")
# 7. 等待溶剂1稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0,
"description": f"等待溶剂1 {solvent1} 稳定"
}
})
# 8. 添加第二种溶剂
print(f"RECRYSTALLIZE: 开始添加溶剂2 {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)
except Exception as e:
raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}")
# 9. 等待溶剂2稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0,
"description": f"等待溶剂2 {solvent2} 稳定"
}
})
# 10. 等待重结晶完成
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 600.0, # 等待10分钟进行重结晶
"description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio}"
}
})
print(f"RECRYSTALLIZE: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"RECRYSTALLIZE: 预计总时间: {620/60:.1f} 分钟")
return action_sequence
# 测试函数
def test_recrystallize_protocol():
"""测试重结晶协议"""
print("=== RECRYSTALLIZE PROTOCOL 测试 ===")
# 测试比例解析
test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"]
for ratio in test_ratios:
r1, r2 = parse_ratio(ratio)
print(f"比例 '{ratio}' -> {r1}:{r2}")
print("测试完成")
if __name__ == "__main__":
test_recrystallize_protocol()

View File

@@ -0,0 +1,180 @@
import networkx as nx
from typing import List, Dict, Any
from .pump_protocol import generate_pump_protocol_with_rinsing
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""
查找溶剂容器,支持多种匹配模式
Args:
G: 网络图
solvent: 溶剂名称(如 "methanol", "ethanol", "water"
Returns:
str: 溶剂容器ID
"""
print(f"RESET_HANDLING: 正在查找溶剂 '{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
]
# 第一步:通过容器名称匹配
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"RESET_HANDLING: 通过名称匹配找到容器: {vessel_name}")
return vessel_name
# 第二步:通过模糊匹配
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:
print(f"RESET_HANDLING: 通过模糊匹配找到容器: {node_id}")
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', '')).lower()
reagent_name = vessel_data.get('reagent_name', '').lower()
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
print(f"RESET_HANDLING: 通过液体类型匹配找到容器: {node_id}")
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', {})
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', '')
})
print(f"RESET_HANDLING: 可用容器列表:")
for container in available_containers:
print(f" - {container['id']}: {container['name']}")
print(f" 液体: {container['liquids']}")
print(f" 试剂: {container['reagent_name']}")
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names}")
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 = 100.0 # 默认体积 100 mL
print(f"RESET_HANDLING: 开始生成重置处理协议")
print(f" - 溶剂: {solvent}")
print(f" - 目标容器: {target_vessel}")
print(f" - 体积: {volume} mL")
# 1. 验证目标容器存在
if target_vessel not in G.nodes():
raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中")
# 2. 查找溶剂容器
try:
solvent_vessel = find_solvent_vessel(G, solvent)
print(f"RESET_HANDLING: 找到溶剂容器: {solvent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
# 3. 验证路径存在
try:
path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel)
print(f"RESET_HANDLING: 找到路径: {''.join(path)}")
except nx.NetworkXNoPath:
raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径")
# 4. 使用pump_protocol转移溶剂
print(f"RESET_HANDLING: 开始转移溶剂 {volume} mL")
try:
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)
except Exception as e:
raise ValueError(f"生成泵协议时出错: {str(e)}")
# 5. 等待溶剂稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0,
"description": f"等待溶剂 {solvent} 稳定"
}
})
print(f"RESET_HANDLING: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"RESET_HANDLING: 已添加 {volume} mL {solvent}{target_vessel}")
return action_sequence
# 测试函数
def test_reset_handling_protocol():
"""测试重置处理协议"""
print("=== RESET HANDLING PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_reset_handling_protocol()

View File

@@ -1,5 +1,87 @@
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):
# 支持两种格式:新格式 (name, volume) 和旧格式 (liquid_type, liquid_volume)
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
total_volume += volume
return total_volume
def find_column_device(G: nx.DiGraph, column: str) -> str:
"""查找柱层析设备"""
# 首先检查是否有虚拟柱设备
column_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_column']
if column_nodes:
return column_nodes[0]
# 如果没有虚拟柱设备,抛出异常
raise ValueError(f"系统中未找到柱层析设备。请确保配置了 virtual_column 设备")
def find_column_vessel(G: nx.DiGraph, column: str) -> str:
"""查找柱容器"""
# 直接使用 column 参数作为容器名称
if column in G.nodes():
return column
# 尝试常见的柱容器命名规则
possible_names = [
f"column_{column}",
f"{column}_column",
f"vessel_{column}",
f"{column}_vessel",
"column_vessel",
"chromatography_column",
"silica_column",
"preparative_column"
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
return vessel_name
raise ValueError(f"未找到柱容器 '{column}'。尝试了以下名称: {[column] + 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_run_column_protocol(
G: nx.DiGraph,
@@ -11,92 +93,220 @@ def generate_run_column_protocol(
生成柱层析分离的协议序列
Args:
G: 有向图,节点为设备和容器
G: 有向图,节点为设备和容器,边为流体管道
from_vessel: 源容器的名称,即样品起始所在的容器
to_vessel: 目标容器的名称,分离后的样品要到达的容器
column: 所使用的柱子的名称
Returns:
List[Dict[str, Any]]: 柱层析分离操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
run_column_protocol = generate_run_column_protocol(G, "reactor", "collection_flask", "silica_column")
"""
action_sequence = []
# 验证容器是否存在
print(f"RUN_COLUMN: 开始生成柱层析协议")
print(f" - 源容器: {from_vessel}")
print(f" - 目标容器: {to_vessel}")
print(f" - 柱子: {column}")
# 验证源容器和目标容器存在
if from_vessel not in G.nodes():
raise ValueError(f"源容器 {from_vessel} 不存在于")
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统")
if to_vessel not in G.nodes():
raise ValueError(f"目标容器 {to_vessel} 不存在于")
# 查找转移泵设备(用于样品转移)
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]
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统")
# 查找柱层析设备
column_device_id = None
column_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_column']
if (G.nodes[node].get('class') or '') == 'virtual_column']
if not column_nodes:
raise ValueError("没有找到可用的柱层析设备")
if column_nodes:
column_device_id = column_nodes[0]
print(f"RUN_COLUMN: 找到柱层析设备: {column_device_id}")
else:
print(f"RUN_COLUMN: 警告 - 未找到柱层析设备")
column_id = column_nodes[0]
# 获取源容器中的液体体积
source_volume = get_vessel_liquid_volume(G, from_vessel)
print(f"RUN_COLUMN: 源容器 {from_vessel} 中有 {source_volume} mL 液体")
# 步骤1将样品从源容器转移到柱子上
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": column_id, # 将样品转移到柱子设备
"volume": 0.0, # 转移所有液体,体积由系统确定
"amount": f"样品上柱 - 使用 {column}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
# === 第一步:样品转移到柱子(如果柱子是容器) ===
if column in G.nodes() and G.nodes[column].get('type') == 'container':
print(f"RUN_COLUMN: 样品转移 - {source_volume} mL 从 {from_vessel}{column}")
try:
sample_transfer_actions = generate_pump_protocol(
G=G,
from_vessel=from_vessel,
to_vessel=column,
volume=source_volume if source_volume > 0 else 100.0,
flowrate=2.0
)
action_sequence.extend(sample_transfer_actions)
except Exception as e:
print(f"RUN_COLUMN: 样品转移失败: {str(e)}")
# === 第二步:使用柱层析设备执行分离 ===
if column_device_id:
print(f"RUN_COLUMN: 使用柱层析设备执行分离")
column_separation_action = {
"device_id": column_device_id,
"action_name": "run_column",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"column": column
}
}
})
action_sequence.append(column_separation_action)
# 等待柱层析设备完成分离
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 60}
})
# 步骤2运行柱层析分离
action_sequence.append({
"device_id": column_id,
"action_name": "run_column",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"column": column
}
})
# === 第三步:从柱子转移到目标容器(如果需要) ===
if column in G.nodes() and column != to_vessel:
print(f"RUN_COLUMN: 产物转移 - 从 {column}{to_vessel}")
try:
product_transfer_actions = generate_pump_protocol(
G=G,
from_vessel=column,
to_vessel=to_vessel,
volume=source_volume * 0.8 if source_volume > 0 else 80.0, # 假设有一些损失
flowrate=1.5
)
action_sequence.extend(product_transfer_actions)
except Exception as e:
print(f"RUN_COLUMN: 产物转移失败: {str(e)}")
# 步骤3将分离后的产物从柱子转移到目标容器
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": column_id, # 从柱子设备转移
"to_vessel": to_vessel,
"volume": 0.0, # 转移所有液体,体积由系统确定
"amount": f"收集分离产物 - 来自 {column}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
print(f"RUN_COLUMN: 生成了 {len(action_sequence)} 个动作")
return action_sequence
# 便捷函数:常用柱层析方案
def generate_flash_column_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column_material: str = "silica_gel",
mobile_phase: str = "ethyl_acetate",
mobile_phase_volume: float = 100.0
) -> List[Dict[str, Any]]:
"""快速柱层析:高流速分离"""
return generate_run_column_protocol(
G, from_vessel, to_vessel, column_material,
mobile_phase, mobile_phase_volume, 1, "", 0.0, 3.0
)
def generate_preparative_column_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column_material: str = "silica_gel",
equilibration_solvent: str = "hexane",
eluting_solvent: str = "ethyl_acetate",
eluting_volume: float = 50.0,
eluting_repeats: int = 3
) -> List[Dict[str, Any]]:
"""制备柱层析:带平衡和多次洗脱"""
return generate_run_column_protocol(
G, from_vessel, to_vessel, column_material,
eluting_solvent, eluting_volume, eluting_repeats,
equilibration_solvent, 30.0, 1.5
)
def generate_gradient_column_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column_material: str = "silica_gel",
gradient_solvents: List[str] = None,
gradient_volumes: List[float] = None
) -> List[Dict[str, Any]]:
"""梯度洗脱柱层析:多种溶剂系统"""
if gradient_solvents is None:
gradient_solvents = ["hexane", "ethyl_acetate", "methanol"]
if gradient_volumes is None:
gradient_volumes = [50.0, 50.0, 30.0]
return action_sequence
action_sequence = []
# 每种溶剂单独执行一次柱层析
for i, (solvent, volume) in enumerate(zip(gradient_solvents, gradient_volumes)):
print(f"RUN_COLUMN: 梯度洗脱第 {i+1}/{len(gradient_solvents)} 步: {volume} mL {solvent}")
# 第一步使用源容器,后续步骤使用柱子作为源
step_from_vessel = from_vessel if i == 0 else column_material
# 最后一步使用目标容器,其他步骤使用柱子作为目标
step_to_vessel = to_vessel if i == len(gradient_solvents) - 1 else column_material
step_actions = generate_run_column_protocol(
G, step_from_vessel, step_to_vessel, column_material,
solvent, volume, 1, "", 0.0, 1.0
)
action_sequence.extend(step_actions)
# 在梯度步骤之间加入等待时间
if i < len(gradient_solvents) - 1:
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 20}
})
return action_sequence
def generate_reverse_phase_column_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column_material: str = "C18",
aqueous_phase: str = "water",
organic_phase: str = "methanol",
gradient_ratio: float = 0.5
) -> List[Dict[str, Any]]:
"""反相柱层析C18柱水-有机相梯度"""
# 先用水相平衡
equilibration_volume = 20.0
# 然后用有机相洗脱
eluting_volume = 30.0 * gradient_ratio
return generate_run_column_protocol(
G, from_vessel, to_vessel, column_material,
organic_phase, eluting_volume, 2,
aqueous_phase, equilibration_volume, 0.8
)
def generate_ion_exchange_column_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column_material: str = "ion_exchange",
buffer_solution: str = "buffer",
salt_solution: str = "NaCl_solution",
salt_volume: float = 40.0
) -> List[Dict[str, Any]]:
"""离子交换柱层析:缓冲液平衡,盐溶液洗脱"""
return generate_run_column_protocol(
G, from_vessel, to_vessel, column_material,
salt_solution, salt_volume, 1,
buffer_solution, 25.0, 0.5
)
# 测试函数
def test_run_column_protocol():
"""测试柱层析协议的示例"""
print("=== RUN COLUMN PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_run_column_protocol()

View File

@@ -1,6 +1,28 @@
from typing import List, Dict, Any
import networkx as nx
def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str:
"""
查找与指定容器相连的搅拌设备,或查找可用的搅拌设备
"""
# 查找所有搅拌设备节点
stirrer_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
if vessel:
# 检查哪个搅拌设备与目标容器相连(机械连接)
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
return stirrer
# 如果没有指定容器或没有直接连接,返回第一个可用的搅拌设备
if stirrer_nodes:
return stirrer_nodes[0]
raise ValueError("系统中未找到可用的搅拌设备")
def generate_stir_protocol(
G: nx.DiGraph,
stir_time: float,
@@ -8,37 +30,24 @@ def generate_stir_protocol(
settling_time: float
) -> List[Dict[str, Any]]:
"""
生成搅拌操作的协议序列
Args:
G: 有向图,节点为设备和容器
stir_time: 搅拌时间 (秒)
stir_speed: 搅拌速度 (rpm)
settling_time: 沉降时间 (秒)
Returns:
List[Dict[str, Any]]: 搅拌操作的动作序列
Raises:
ValueError: 当找不到搅拌设备时抛出异常
Examples:
stir_protocol = generate_stir_protocol(G, 300.0, 500.0, 60.0)
生成搅拌操作的协议序列 - 定时搅拌 + 沉降
"""
action_sequence = []
print(f"STIR: 开始生成搅拌协议")
print(f" - 搅拌时间: {stir_time}")
print(f" - 搅拌速度: {stir_speed} RPM")
print(f" - 沉降时间: {settling_time}")
# 查找搅拌设备
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
if not stirrer_nodes:
raise ValueError("没有找到可用的搅拌设备")
# 使用第一个可用的搅拌器
stirrer_id = stirrer_nodes[0]
try:
stirrer_id = find_connected_stirrer(G)
print(f"STIR: 找到搅拌设备: {stirrer_id}")
except ValueError as e:
raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 执行搅拌操作
action_sequence.append({
stir_action = {
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
@@ -46,8 +55,11 @@ def generate_stir_protocol(
"stir_speed": stir_speed,
"settling_time": settling_time
}
})
}
action_sequence.append(stir_action)
print(f"STIR: 生成了 {len(action_sequence)} 个动作")
return action_sequence
@@ -58,33 +70,28 @@ def generate_start_stir_protocol(
purpose: str
) -> List[Dict[str, Any]]:
"""
生成开始搅拌操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 搅拌容器
stir_speed: 搅拌速度 (rpm)
purpose: 搅拌目的
Returns:
List[Dict[str, Any]]: 开始搅拌操作的动作序列
生成开始搅拌操作的协议序列 - 持续搅拌
"""
action_sequence = []
# 查找搅拌设备
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
print(f"START_STIR: 开始生成启动搅拌协议")
print(f" - 容器: {vessel}")
print(f" - 搅拌速度: {stir_speed} RPM")
print(f" - 目的: {purpose}")
if not stirrer_nodes:
raise ValueError("没有找到可用的搅拌设备")
stirrer_id = stirrer_nodes[0]
# 验证容器是否存在
# 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于")
raise ValueError(f"容器 '{vessel}' 不存在于系统")
action_sequence.append({
# 查找搅拌设备
try:
stirrer_id = find_connected_stirrer(G, vessel)
print(f"START_STIR: 找到搅拌设备: {stirrer_id}")
except ValueError as e:
raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 执行开始搅拌操作
start_stir_action = {
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
@@ -92,8 +99,11 @@ def generate_start_stir_protocol(
"stir_speed": stir_speed,
"purpose": purpose
}
})
}
action_sequence.append(start_stir_action)
print(f"START_STIR: 生成了 {len(action_sequence)} 个动作")
return action_sequence
@@ -103,35 +113,54 @@ def generate_stop_stir_protocol(
) -> List[Dict[str, Any]]:
"""
生成停止搅拌操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 搅拌容器
Returns:
List[Dict[str, Any]]: 停止搅拌操作的动作序列
"""
action_sequence = []
# 查找搅拌设备
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
print(f"STOP_STIR: 开始生成停止搅拌协议")
print(f" - 容器: {vessel}")
if not stirrer_nodes:
raise ValueError("没有找到可用的搅拌设备")
stirrer_id = stirrer_nodes[0]
# 验证容器是否存在
# 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于")
raise ValueError(f"容器 '{vessel}' 不存在于系统")
action_sequence.append({
# 查找搅拌设备
try:
stirrer_id = find_connected_stirrer(G, vessel)
print(f"STOP_STIR: 找到搅拌设备: {stirrer_id}")
except ValueError as e:
raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 执行停止搅拌操作
stop_stir_action = {
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel
}
})
}
return action_sequence
action_sequence.append(stop_stir_action)
print(f"STOP_STIR: 生成了 {len(action_sequence)} 个动作")
return action_sequence
# 便捷函数
def generate_fast_stir_protocol(
G: nx.DiGraph,
time: float = 300.0,
speed: float = 800.0,
settling: float = 60.0
) -> List[Dict[str, Any]]:
"""快速搅拌的便捷函数"""
return generate_stir_protocol(G, time, speed, settling)
def generate_gentle_stir_protocol(
G: nx.DiGraph,
time: float = 600.0,
speed: float = 200.0,
settling: float = 120.0
) -> List[Dict[str, Any]]:
"""温和搅拌的便捷函数"""
return generate_stir_protocol(G, time, speed, settling)

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

@@ -1,9 +1,12 @@
import rtde_control
import dashboard_client
try:
import rtde_control
import dashboard_client
import rtde_receive
except ImportError as ex:
print("Import Error, Please Install Packages in ur_arm_task.py First!", ex)
import time
import json
from unilabos.devices.agv.robotiq_gripper import RobotiqGripper
import rtde_receive
from std_msgs.msg import Float64MultiArray
from pydantic import BaseModel

View File

@@ -234,71 +234,71 @@ class Laiyu:
resp_reset = self.reset()
return actual_mass_mg
if __name__ == "__main__":
'''
样例:对单个粉筒进行称量
'''
modbus = Laiyu(port="COM25")
mass_test = modbus.add_powder_tube(1, 'h12', 6.0)
print(f"实际出料质量:{mass_test}mg")
'''
样例:对单个粉筒进行称量
'''
'''
样例: 对一份excel文件记录的化合物进行称量
'''
modbus = Laiyu(port="COM25")
excel_file = r"C:\auto\laiyu\test1.xlsx"
# 定义输出文件路径,用于记录实际加样多少
output_file = r"C:\auto\laiyu\test_output.xlsx"
mass_test = modbus.add_powder_tube(1, 'h12', 6.0)
print(f"实际出料质量:{mass_test}mg")
# 定义物料名称和料筒位置关系
compound_positions = {
'XPhos': '1',
'Cu(OTf)2': '2',
'CuSO4': '3',
'PPh3': '4',
}
# read excel file
# excel_file = r"C:\auto\laiyu\test.xlsx"
df = pd.read_excel(excel_file, sheet_name='Sheet1')
# 读取Excel文件中的数据
# 遍历每一行数据
for index, row in df.iterrows():
# 获取物料名称和质量
copper_name = row['copper']
copper_mass = row['copper_mass']
ligand_name = row['ligand']
ligand_mass = row['ligand_mass']
target_tube_position = row['position']
# 获取物料位置 from compound_positions
copper_position = compound_positions.get(copper_name)
ligand_position = compound_positions.get(ligand_name)
# 判断物料位置是否存在
if copper_position is None:
print(f"物料位置不存在:{copper_name}")
continue
if ligand_position is None:
print(f"物料位置不存在:{ligand_name}")
continue
# 加铜
copper_actual_mass = modbus.add_powder_tube(int(copper_position), target_tube_position, copper_mass)
time.sleep(1)
# 加配体
ligand_actual_mass = modbus.add_powder_tube(int(ligand_position), target_tube_position, ligand_mass)
time.sleep(1)
# 保存至df
df.at[index, 'copper_actual_mass'] = copper_actual_mass
df.at[index, 'ligand_actual_mass'] = ligand_actual_mass
'''
样例: 对一份excel文件记录的化合物进行称量
'''
# 保存修改后的数据到新的Excel文件
df.to_excel(output_file, index=False)
print(f"已保存到文件:{output_file}")
excel_file = r"C:\auto\laiyu\test1.xlsx"
# 定义输出文件路径,用于记录实际加样多少
output_file = r"C:\auto\laiyu\test_output.xlsx"
# 定义物料名称和料筒位置关系
compound_positions = {
'XPhos': '1',
'Cu(OTf)2': '2',
'CuSO4': '3',
'PPh3': '4',
}
# read excel file
# excel_file = r"C:\auto\laiyu\test.xlsx"
df = pd.read_excel(excel_file, sheet_name='Sheet1')
# 读取Excel文件中的数据
# 遍历每一行数据
for index, row in df.iterrows():
# 获取物料名称和质量
copper_name = row['copper']
copper_mass = row['copper_mass']
ligand_name = row['ligand']
ligand_mass = row['ligand_mass']
target_tube_position = row['position']
# 获取物料位置 from compound_positions
copper_position = compound_positions.get(copper_name)
ligand_position = compound_positions.get(ligand_name)
# 判断物料位置是否存在
if copper_position is None:
print(f"物料位置不存在:{copper_name}")
continue
if ligand_position is None:
print(f"物料位置不存在:{ligand_name}")
continue
# 加铜
copper_actual_mass = modbus.add_powder_tube(int(copper_position), target_tube_position, copper_mass)
time.sleep(1)
# 加配体
ligand_actual_mass = modbus.add_powder_tube(int(ligand_position), target_tube_position, ligand_mass)
time.sleep(1)
# 保存至df
df.at[index, 'copper_actual_mass'] = copper_actual_mass
df.at[index, 'ligand_actual_mass'] = ligand_actual_mass
# 保存修改后的数据到新的Excel文件
df.to_excel(output_file, index=False)
print(f"已保存到文件:{output_file}")
# 关闭串口
modbus.ser.close()
print("串口已关闭")
# 关闭串口
modbus.ser.close()
print("串口已关闭")

View File

@@ -3,7 +3,11 @@ import sys
import io
# sys.path.insert(0, r'C:\kui\winprep_cli\winprep_c_Uni-lab\x64\Debug')
import winprep_c
try:
import winprep_c
except ImportError as e:
print("Error importing winprep_c:", e)
print("Please ensure that the winprep_c module is correctly installed and accessible.")
from queue import Queue

View File

@@ -21,7 +21,7 @@ except Exception as e:
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")))
from unilabos.utils.pywinauto_util import connect_application, get_process_pid_by_name, get_ui_path_with_window_specification, print_wrapper_identifiers
from unilabos.device_comms.universal_driver import UniversalDriver, SingleRunningExecutor
from unilabos.devices.template_driver import universal_driver as ud
from unilabos.device_comms import universal_driver as ud
print(f"使用文件DEBUG运行: {e}")

View File

@@ -9,7 +9,7 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import time
class RamanObj:
def __init__(self, port_laser,port_ccd, baudrate_laser=9600, baudrate_ccd=921600):
def __init__(self, port_laser, port_ccd, baudrate_laser=9600, baudrate_ccd=921600):
self.port_laser = port_laser
self.port_ccd = port_ccd

View File

@@ -1,158 +1,213 @@
import asyncio
import logging
from typing import Dict, Any
import time as time_module
from typing import Dict, Any, Optional
class VirtualCentrifuge:
"""Virtual centrifuge device for CentrifugeProtocol testing"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
"""Virtual centrifuge device - 简化版,只保留核心功能"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
if device_id is None and "id" in kwargs:
device_id = kwargs.pop("id")
if config is None and "config" in kwargs:
config = kwargs.pop("config")
# 设置默认值
self.device_id = device_id or "unknown_centrifuge"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualCentrifuge {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 15000.0)
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 40.0)
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', 4.0)
# 处理其他kwargs参数,但跳过已知的配置参数
skip_keys = {'port', 'max_speed', 'max_temp', 'min_temp'}
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
self._max_speed = self.config.get("max_speed") or kwargs.get("max_speed", 15000.0)
self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 40.0)
self._min_temp = self.config.get("min_temp") or kwargs.get("min_temp", 4.0)
# 处理其他kwargs参数
skip_keys = {"port", "max_speed", "max_temp", "min_temp"}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual centrifuge"""
print(f"=== VirtualCentrifuge {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual centrifuge {self.device_id}")
# 只保留核心状态
self.data.update({
"status": "Idle",
"centrifuge_state": "Stopped", # Stopped, Running, Completed, Error
"current_speed": 0.0,
"target_speed": 0.0,
"current_temp": 25.0,
"target_temp": 25.0,
"max_speed": self._max_speed,
"max_temp": self._max_temp,
"min_temp": self._min_temp,
"centrifuge_state": "Stopped",
"time_remaining": 0.0,
"progress": 0.0,
"message": ""
"message": "Ready for centrifugation"
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual centrifuge"""
self.logger.info(f"Cleaning up virtual centrifuge {self.device_id}")
self.data.update({
"status": "Offline",
"centrifuge_state": "Offline",
"current_speed": 0.0,
"current_temp": 25.0,
"message": "System offline"
})
return True
async def centrifuge(self, vessel: str, speed: float, time: float, temp: float = 25.0) -> bool:
"""Execute centrifuge action - matches Centrifuge action"""
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} RPM, time={time}s, temp={temp}°C")
async def centrifuge(
self,
vessel: str,
speed: float,
time: float,
temp: float = 25.0
) -> bool:
"""Execute centrifuge action - 简化的离心流程"""
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} rpm, time={time}s, temp={temp}°C")
# 验证参数
if speed > self._max_speed:
self.logger.error(f"Speed {speed} exceeds maximum {self._max_speed}")
self.data["message"] = f"速度 {speed} 超过最大值 {self._max_speed}"
if speed > self._max_speed or speed < 100.0:
error_msg = f"离心速度 {speed} rpm 超出范围 (100-{self._max_speed} rpm)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"centrifuge_state": "Error",
"message": error_msg
})
return False
if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
self.data["message"] = f"温度 {temp} 超出范围 {self._min_temp}-{self._max_temp}"
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}-{self._max_temp}°C)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"centrifuge_state": "Error",
"message": error_msg
})
return False
# 开始离心
self.data.update({
"status": "Running",
"centrifuge_state": "Centrifuging",
"target_speed": speed,
"status": f"离心中: {vessel}",
"centrifuge_state": "Running",
"current_speed": speed,
"target_temp": temp,
"target_speed": speed,
"current_temp": temp,
"target_temp": temp,
"time_remaining": time,
"vessel": vessel,
"progress": 0.0,
"message": f"离心中: {vessel} at {speed} RPM"
"message": f"Centrifuging {vessel} at {speed} rpm, {temp}°C"
})
# 模拟离心过程
simulation_time = min(time, 5.0) # 最多等待5秒用于测试
await asyncio.sleep(simulation_time)
# 离心完成
self.data.update({
"status": "Idle",
"centrifuge_state": "Stopped",
"current_speed": 0.0,
"target_speed": 0.0,
"time_remaining": 0.0,
"progress": 100.0,
"message": f"离心完成: {vessel}"
})
self.logger.info(f"Centrifuge completed for vessel {vessel}")
return True
# 状态属性
try:
# 离心过程 - 实时更新进度
start_time = time_module.time()
total_time = time
while True:
current_time = time_module.time()
elapsed = current_time - start_time
remaining = max(0, total_time - elapsed)
progress = min(100.0, (elapsed / total_time) * 100)
# 更新状态
self.data.update({
"time_remaining": remaining,
"progress": progress,
"status": f"离心中: {vessel} | {speed} rpm | {temp}°C | {progress:.1f}% | 剩余: {remaining:.0f}s",
"message": f"Centrifuging: {progress:.1f}% complete, {remaining:.0f}s remaining"
})
# 时间到了,退出循环
if remaining <= 0:
break
# 每秒更新一次
await asyncio.sleep(1.0)
# 离心完成
self.data.update({
"status": f"离心完成: {vessel} | {speed} rpm | {time}s",
"centrifuge_state": "Completed",
"progress": 100.0,
"time_remaining": 0.0,
"current_speed": 0.0, # 停止旋转
"current_temp": 25.0, # 恢复室温
"message": f"Centrifugation completed: {vessel} at {speed} rpm for {time}s"
})
self.logger.info(f"Centrifugation completed: {vessel} at {speed} rpm for {time}s")
return True
except Exception as e:
# 出错处理
self.logger.error(f"Error during centrifugation: {str(e)}")
self.data.update({
"status": f"离心错误: {str(e)}",
"centrifuge_state": "Error",
"current_speed": 0.0,
"current_temp": 25.0,
"progress": 0.0,
"time_remaining": 0.0,
"message": f"Centrifugation failed: {str(e)}"
})
return False
# === 核心状态属性 ===
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def current_speed(self) -> float:
return self.data.get("current_speed", 0.0)
@property
def target_speed(self) -> float:
return self.data.get("target_speed", 0.0)
@property
def current_temp(self) -> float:
return self.data.get("current_temp", 25.0)
@property
def target_temp(self) -> float:
return self.data.get("target_temp", 25.0)
@property
def max_speed(self) -> float:
return self.data.get("max_speed", self._max_speed)
@property
def max_temp(self) -> float:
return self.data.get("max_temp", self._max_temp)
@property
def min_temp(self) -> float:
return self.data.get("min_temp", self._min_temp)
@property
def centrifuge_state(self) -> str:
return self.data.get("centrifuge_state", "Unknown")
@property
def current_speed(self) -> float:
return self.data.get("current_speed", 0.0)
@property
def target_speed(self) -> float:
return self.data.get("target_speed", 0.0)
@property
def current_temp(self) -> float:
return self.data.get("current_temp", 25.0)
@property
def target_temp(self) -> float:
return self.data.get("target_temp", 25.0)
@property
def max_speed(self) -> float:
return self._max_speed
@property
def max_temp(self) -> float:
return self._max_temp
@property
def min_temp(self) -> float:
return self._min_temp
@property
def time_remaining(self) -> float:
return self.data.get("time_remaining", 0.0)
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def message(self) -> str:
return self.data.get("message", "")

View File

@@ -1,151 +1,221 @@
import asyncio
import logging
from typing import Dict, Any
import time as time_module
from typing import Dict, Any, Optional
class VirtualFilter:
"""Virtual filter device for FilterProtocol testing"""
"""Virtual filter device - 完全按照 Filter.action 规范"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_filter"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualFilter {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 500.0)
# 处理其他kwargs参数,但跳过已知的配置参数
skip_keys = {'port', 'max_temp', 'max_stir_speed'}
# 处理其他kwargs参数
skip_keys = {'port', 'max_temp', 'max_stir_speed', 'max_volume'}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual filter"""
print(f"=== VirtualFilter {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual filter {self.device_id}")
# 按照 Filter.action 的 feedback 字段初始化
self.data.update({
"status": "Idle",
"filter_state": "Ready",
"current_temp": 25.0,
"target_temp": 25.0,
"max_temp": self._max_temp,
"stir_speed": 0.0,
"max_stir_speed": self._max_stir_speed,
"filtered_volume": 0.0,
"progress": 0.0,
"message": ""
"progress": 0.0, # Filter.action feedback
"current_temp": 25.0, # Filter.action feedback
"filtered_volume": 0.0, # Filter.action feedback
"current_status": "Ready for filtration", # Filter.action feedback
"message": "Ready for filtration"
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual filter"""
self.logger.info(f"Cleaning up virtual filter {self.device_id}")
self.data.update({
"status": "Offline",
"current_status": "System offline",
"message": "System offline"
})
return True
async def filter_sample(self, vessel: str, filtrate_vessel: str = "", stir: bool = False,
stir_speed: float = 300.0, temp: float = 25.0,
continue_heatchill: bool = False, volume: float = 0.0) -> bool:
"""Execute filter action - matches Filter action"""
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}, stir={stir}, volume={volume}")
async def filter(
self,
vessel: str,
filtrate_vessel: str = "",
stir: bool = False,
stir_speed: float = 300.0,
temp: float = 25.0,
continue_heatchill: bool = False,
volume: float = 0.0
) -> bool:
"""Execute filter action - 完全按照 Filter.action 参数"""
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}")
self.logger.info(f" stir={stir}, stir_speed={stir_speed}, temp={temp}")
self.logger.info(f" continue_heatchill={continue_heatchill}, volume={volume}")
# 验证参数
if temp > self._max_temp:
self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}")
self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}"
if temp > self._max_temp or temp < 4.0:
error_msg = f"温度 {temp}°C 超出范围 (4-{self._max_temp}°C)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"message": error_msg
})
return False
if stir and stir_speed > self._max_stir_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
self.data["message"] = f"搅拌速度 {stir_speed} 超过最大值 {self._max_stir_speed}"
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"message": error_msg
})
return False
if volume > self._max_volume:
error_msg = f"过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"message": error_msg
})
return False
# 开始过滤
filter_volume = volume if volume > 0 else 50.0
self.data.update({
"status": "Running",
"filter_state": "Filtering",
"target_temp": temp,
"status": f"过滤中: {vessel}",
"current_temp": temp,
"stir_speed": stir_speed if stir else 0.0,
"vessel": vessel,
"filtrate_vessel": filtrate_vessel,
"target_volume": volume,
"filtered_volume": 0.0,
"progress": 0.0,
"message": f"过滤中: {vessel}"
"current_status": f"Filtering {vessel}{filtrate_vessel}",
"message": f"Starting filtration: {vessel}{filtrate_vessel}"
})
# 模拟过滤过程
simulation_time = min(volume / 10.0 if volume > 0 else 5.0, 10.0)
await asyncio.sleep(simulation_time)
# 过滤完成
filtered_vol = volume if volume > 0 else 50.0 # 默认过滤量
self.data.update({
"status": "Idle",
"filter_state": "Ready",
"current_temp": 25.0 if not continue_heatchill else temp,
"target_temp": 25.0 if not continue_heatchill else temp,
"stir_speed": 0.0 if not stir else stir_speed,
"filtered_volume": filtered_vol,
"progress": 100.0,
"message": f"过滤完成: {filtered_vol}mL"
})
self.logger.info(f"Filter completed: {filtered_vol}mL from {vessel}")
return True
try:
# 过滤过程 - 实时更新进度
start_time = time_module.time()
# 根据体积和搅拌估算过滤时间
base_time = filter_volume / 5.0 # 5mL/s 基础速度
if stir:
base_time *= 0.8 # 搅拌加速过滤
if temp > 50.0:
base_time *= 0.7 # 高温加速过滤
filter_time = max(base_time, 10.0) # 最少10秒
while True:
current_time = time_module.time()
elapsed = current_time - start_time
remaining = max(0, filter_time - elapsed)
progress = min(100.0, (elapsed / filter_time) * 100)
current_filtered = (progress / 100.0) * filter_volume
# 更新状态 - 按照 Filter.action feedback 字段
status_msg = f"过滤中: {vessel}"
if stir:
status_msg += f" | 搅拌: {stir_speed} RPM"
status_msg += f" | {temp}°C | {progress:.1f}% | 已过滤: {current_filtered:.1f}mL"
self.data.update({
"progress": progress, # Filter.action feedback
"current_temp": temp, # Filter.action feedback
"filtered_volume": current_filtered, # Filter.action feedback
"current_status": f"Filtering: {progress:.1f}% complete", # Filter.action feedback
"status": status_msg,
"message": f"Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
})
if remaining <= 0:
break
await asyncio.sleep(1.0)
# 过滤完成
final_temp = temp if continue_heatchill else 25.0
final_status = f"过滤完成: {vessel} | {filter_volume}mL → {filtrate_vessel}"
if continue_heatchill:
final_status += " | 继续加热搅拌"
self.data.update({
"status": final_status,
"progress": 100.0, # Filter.action feedback
"current_temp": final_temp, # Filter.action feedback
"filtered_volume": filter_volume, # Filter.action feedback
"current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback
"message": f"Filtration completed: {filter_volume}mL filtered from {vessel}"
})
self.logger.info(f"Filtration completed: {filter_volume}mL from {vessel} to {filtrate_vessel}")
return True
except Exception as e:
self.logger.error(f"Error during filtration: {str(e)}")
self.data.update({
"status": f"过滤错误: {str(e)}",
"current_status": f"Filtration failed: {str(e)}",
"message": f"Filtration failed: {str(e)}"
})
return False
# 状态属性
# === 核心状态属性 - 按照 Filter.action feedback 字段 ===
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def filter_state(self) -> str:
return self.data.get("filter_state", "Unknown")
@property
def current_temp(self) -> float:
return self.data.get("current_temp", 25.0)
@property
def target_temp(self) -> float:
return self.data.get("target_temp", 25.0)
@property
def max_temp(self) -> float:
return self.data.get("max_temp", self._max_temp)
@property
def stir_speed(self) -> float:
return self.data.get("stir_speed", 0.0)
@property
def max_stir_speed(self) -> float:
return self.data.get("max_stir_speed", self._max_stir_speed)
@property
def filtered_volume(self) -> float:
return self.data.get("filtered_volume", 0.0)
@property
def progress(self) -> float:
"""Filter.action feedback 字段"""
return self.data.get("progress", 0.0)
@property
def current_temp(self) -> float:
"""Filter.action feedback 字段"""
return self.data.get("current_temp", 25.0)
@property
def filtered_volume(self) -> float:
"""Filter.action feedback 字段"""
return self.data.get("filtered_volume", 0.0)
@property
def current_status(self) -> str:
"""Filter.action feedback 字段"""
return self.data.get("current_status", "")
@property
def message(self) -> str:
return self.data.get("message", "")
return self.data.get("message", "")
@property
def max_temp(self) -> float:
return self._max_temp
@property
def max_stir_speed(self) -> float:
return self._max_stir_speed
@property
def max_volume(self) -> float:
return self._max_volume

View File

@@ -0,0 +1,46 @@
import time
from typing import Dict, Any, Optional
class VirtualGasSource:
"""Virtual gas source for testing"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
self.device_id = device_id or "unknown_gas_source"
self.config = config or {}
self.data = {}
self._status = "OPEN"
async def initialize(self) -> bool:
"""Initialize virtual gas source"""
self.data.update({
"status": self._status
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual gas source"""
return True
@property
def status(self) -> str:
return self._status
def get_status(self) -> str:
return self._status
def set_status(self, string):
self._status = string
time.sleep(5)
def open(self):
self._status = "OPEN"
def close(self):
self._status = "CLOSED"
def is_open(self):
return self._status
def is_closed(self):
return not self._status

View File

@@ -1,5 +1,6 @@
import asyncio
import logging
import time as time_module # 重命名time模块避免与参数冲突
from typing import Dict, Any
class VirtualHeatChill:
@@ -19,18 +20,13 @@ class VirtualHeatChill:
self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualHeatChill {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 200.0)
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', -80.0)
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
# 处理其他kwargs参数,但跳过已知的配置参数
# 处理其他kwargs参数
skip_keys = {'port', 'max_temp', 'min_temp', 'max_stir_speed'}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
@@ -38,70 +34,177 @@ class VirtualHeatChill:
async def initialize(self) -> bool:
"""Initialize virtual heat chill"""
print(f"=== VirtualHeatChill {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual heat chill {self.device_id}")
# 初始化状态信息
self.data.update({
"status": "Idle"
"status": "Idle",
"operation_mode": "Idle",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0,
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual heat chill"""
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
self.data.update({
"status": "Offline",
"operation_mode": "Offline",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0
})
return True
async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
stir_speed: float, purpose: str) -> bool:
"""Execute heat chill action - matches HeatChill action exactly"""
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}, purpose={purpose}")
"""Execute heat chill action - 按实际时间运行,实时更新剩余时间"""
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}")
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
self.data["status"] = f"温度 {temp} 超出范围"
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False
if stir and stir_speed > self._max_stir_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
error_msg = f"搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False
# 开始加热/冷却
# 确定操作模式
if temp > 25.0:
operation_mode = "Heating"
status_action = "加热"
elif temp < 25.0:
operation_mode = "Cooling"
status_action = "冷却"
else:
operation_mode = "Maintaining"
status_action = "保温"
# **修复**: 使用重命名的time模块
start_time = time_module.time()
total_time = time
# 开始操作
stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
self.data.update({
"status": f"加热/冷却中: {vessel}{temp}°C"
"status": f"运行中: {status_action} {vessel}{temp}°C | 剩余: {total_time:.0f}s{stir_info}",
"operation_mode": operation_mode,
"is_stirring": stir,
"stir_speed": stir_speed if stir else 0.0,
"remaining_time": total_time,
})
# 模拟加热/冷却时间
simulation_time = min(time, 10.0) # 最多等待10秒用于测试
await asyncio.sleep(simulation_time)
# **修复**: 在等待过程中每秒更新剩余时间
while True:
current_time = time_module.time() # 使用重命名的time模块
elapsed = current_time - start_time
remaining = max(0, total_time - elapsed)
# 更新剩余时间和状态
self.data.update({
"remaining_time": remaining,
"status": f"运行中: {status_action} {vessel}{temp}°C | 剩余: {remaining:.0f}s{stir_info}"
})
# 如果时间到了,退出循环
if remaining <= 0:
break
# 等待1秒后再次检查
await asyncio.sleep(1.0)
# 加热/冷却完成
self.data["status"] = f"完成: {vessel} 已达到 {temp}°C"
# 操作完成
final_stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
self.data.update({
"status": f"完成: {vessel} 已达到 {temp}°C | 用时: {total_time:.0f}s{final_stir_info}",
"operation_mode": "Completed",
"remaining_time": 0.0,
"is_stirring": False,
"stir_speed": 0.0
})
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C")
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C after {total_time}s")
return True
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
"""Start heat chill - matches HeatChillStart action exactly"""
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C, purpose={purpose}")
"""Start continuous heat chill"""
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C")
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
self.data["status"] = f"温度 {temp} 超出范围"
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False
self.data["status"] = f"开始加热/冷却: {vessel}{temp}°C"
# 确定操作模式
if temp > 25.0:
operation_mode = "Heating"
status_action = "持续加热"
elif temp < 25.0:
operation_mode = "Cooling"
status_action = "持续冷却"
else:
operation_mode = "Maintaining"
status_action = "恒温保持"
self.data.update({
"status": f"启动: {status_action} {vessel}{temp}°C | 持续运行",
"operation_mode": operation_mode,
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": -1.0, # -1 表示持续运行
})
return True
async def heat_chill_stop(self, vessel: str) -> bool:
"""Stop heat chill - matches HeatChillStop action exactly"""
"""Stop heat chill"""
self.logger.info(f"HeatChillStop: vessel={vessel}")
self.data["status"] = f"停止加热/冷却: {vessel}"
self.data.update({
"status": f"已停止: {vessel} 温控停止",
"operation_mode": "Stopped",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0,
})
return True
# 状态属性 - 只保留 action 中定义的 feedback
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Idle")
return self.data.get("status", "Idle")
@property
def operation_mode(self) -> str:
return self.data.get("operation_mode", "Idle")
@property
def is_stirring(self) -> bool:
return self.data.get("is_stirring", False)
@property
def stir_speed(self) -> float:
return self.data.get("stir_speed", 0.0)
@property
def remaining_time(self) -> float:
return self.data.get("remaining_time", 0.0)

View File

@@ -0,0 +1,231 @@
import time
from typing import Union, Dict, Optional
class VirtualMultiwayValve:
"""
虚拟九通阀门 - 0号位连接transfer pump1-8号位连接其他设备
"""
def __init__(self, port: str = "VIRTUAL", positions: int = 8):
self.port = port
self.max_positions = positions # 1-8号位
self.total_positions = positions + 1 # 0-8号位共9个位置
# 状态属性
self._status = "Idle"
self._valve_state = "Ready"
self._current_position = 0 # 默认在0号位transfer pump位置
self._target_position = 0
# 位置映射说明
self.position_map = {
0: "transfer_pump", # 0号位连接转移泵
1: "port_1", # 1号位
2: "port_2", # 2号位
3: "port_3", # 3号位
4: "port_4", # 4号位
5: "port_5", # 5号位
6: "port_6", # 6号位
7: "port_7", # 7号位
8: "port_8" # 8号位
}
@property
def status(self) -> str:
return self._status
@property
def valve_state(self) -> str:
return self._valve_state
@property
def current_position(self) -> int:
return self._current_position
@property
def target_position(self) -> int:
return self._target_position
def get_current_position(self) -> int:
"""获取当前阀门位置"""
return self._current_position
def get_current_port(self) -> str:
"""获取当前连接的端口名称"""
return self.position_map.get(self._current_position, "unknown")
def set_position(self, command: Union[int, str]):
"""
设置阀门位置 - 支持0-8位置
Args:
command: 目标位置 (0-8) 或位置字符串
0: transfer pump位置
1-8: 其他设备位置
"""
try:
# 如果是字符串形式的位置,先转换为数字
if isinstance(command, str):
pos = int(command)
else:
pos = int(command)
if pos < 0 or pos > self.max_positions:
raise ValueError(f"Position must be between 0 and {self.max_positions}")
self._status = "Busy"
self._valve_state = "Moving"
self._target_position = pos
# 模拟阀门切换时间
switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.5秒
time.sleep(switch_time)
self._current_position = pos
self._status = "Idle"
self._valve_state = "Ready"
current_port = self.get_current_port()
return f"Position set to {pos} ({current_port})"
except ValueError as e:
self._status = "Error"
self._valve_state = "Error"
return f"Error: {str(e)}"
def set_to_pump_position(self):
"""切换到transfer pump位置0号位"""
return self.set_position(0)
def set_to_port(self, port_number: int):
"""
切换到指定端口位置
Args:
port_number: 端口号 (1-8)
"""
if port_number < 1 or port_number > self.max_positions:
raise ValueError(f"Port number must be between 1 and {self.max_positions}")
return self.set_position(port_number)
def open(self):
"""打开阀门 - 设置到transfer pump位置0号位"""
return self.set_to_pump_position()
def close(self):
"""关闭阀门 - 对于多通阀门,设置到一个"关闭"状态"""
self._status = "Busy"
self._valve_state = "Closing"
time.sleep(0.5)
# 可以选择保持当前位置或设置特殊关闭状态
self._status = "Idle"
self._valve_state = "Closed"
return f"Valve closed at position {self._current_position}"
def get_valve_position(self) -> int:
"""获取阀门位置 - 兼容性方法"""
return self._current_position
def is_at_position(self, position: int) -> bool:
"""检查是否在指定位置"""
return self._current_position == position
def is_at_pump_position(self) -> bool:
"""检查是否在transfer pump位置"""
return self._current_position == 0
def is_at_port(self, port_number: int) -> bool:
"""检查是否在指定端口位置"""
return self._current_position == port_number
def get_available_positions(self) -> list:
"""获取可用位置列表"""
return list(range(0, self.max_positions + 1))
def get_available_ports(self) -> Dict[int, str]:
"""获取可用端口映射"""
return self.position_map.copy()
def reset(self):
"""重置阀门到transfer pump位置0号位"""
return self.set_position(0)
def switch_between_pump_and_port(self, port_number: int):
"""
在transfer pump位置和指定端口之间切换
Args:
port_number: 目标端口号 (1-8)
"""
if self._current_position == 0:
# 当前在pump位置切换到指定端口
return self.set_to_port(port_number)
else:
# 当前在某个端口切换到pump位置
return self.set_to_pump_position()
def get_flow_path(self) -> str:
"""获取当前流路路径描述"""
current_port = self.get_current_port()
if self._current_position == 0:
return f"Transfer pump connected (position {self._current_position})"
else:
return f"Port {self._current_position} connected ({current_port})"
def get_info(self) -> dict:
"""获取阀门详细信息"""
return {
"port": self.port,
"max_positions": self.max_positions,
"total_positions": self.total_positions,
"current_position": self._current_position,
"current_port": self.get_current_port(),
"target_position": self._target_position,
"status": self._status,
"valve_state": self._valve_state,
"flow_path": self.get_flow_path(),
"position_map": self.position_map
}
def __str__(self):
return f"VirtualMultiwayValve(Position: {self._current_position}/{self.max_positions}, Port: {self.get_current_port()}, Status: {self._status})"
def set_valve_position(self, command: Union[int, str]):
"""
设置阀门位置 - 兼容pump_protocol调用
这是set_position的别名方法用于兼容pump_protocol.py
Args:
command: 目标位置 (0-8) 或位置字符串
"""
return self.set_position(command)
# 使用示例
if __name__ == "__main__":
valve = VirtualMultiwayValve()
print("=== 虚拟九通阀门测试 ===")
print(f"初始状态: {valve}")
print(f"当前流路: {valve.get_flow_path()}")
# 切换到试剂瓶11号位
print(f"\n切换到1号位: {valve.set_position(1)}")
print(f"当前状态: {valve}")
# 切换到transfer pump位置0号位
print(f"\n切换到pump位置: {valve.set_to_pump_position()}")
print(f"当前状态: {valve}")
# 切换到试剂瓶22号位
print(f"\n切换到2号位: {valve.set_to_port(2)}")
print(f"当前状态: {valve}")
# 显示所有可用位置
print(f"\n可用位置: {valve.get_available_positions()}")
print(f"端口映射: {valve.get_available_ports()}")
# 获取详细信息
print(f"\n详细信息: {valve.get_info()}")

View File

@@ -0,0 +1,228 @@
import asyncio
import logging
import time as time_module
from typing import Dict, Any, Optional
class VirtualRotavap:
"""Virtual rotary evaporator device - 简化版,只保留核心功能"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and "id" in kwargs:
device_id = kwargs.pop("id")
if config is None and "config" in kwargs:
config = kwargs.pop("config")
# 设置默认值
self.device_id = device_id or "unknown_rotavap"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualRotavap.{self.device_id}")
self.data = {}
# 从config或kwargs中获取配置参数
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 180.0)
self._max_rotation_speed = self.config.get("max_rotation_speed") or kwargs.get("max_rotation_speed", 280.0)
# 处理其他kwargs参数
skip_keys = {"port", "max_temp", "max_rotation_speed"}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual rotary evaporator"""
self.logger.info(f"Initializing virtual rotary evaporator {self.device_id}")
# 只保留核心状态
self.data.update({
"status": "Idle",
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
"current_temp": 25.0,
"target_temp": 25.0,
"rotation_speed": 0.0,
"vacuum_pressure": 1.0, # 大气压
"evaporated_volume": 0.0,
"progress": 0.0,
"remaining_time": 0.0,
"message": "Ready for evaporation"
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual rotary evaporator"""
self.logger.info(f"Cleaning up virtual rotary evaporator {self.device_id}")
self.data.update({
"status": "Offline",
"rotavap_state": "Offline",
"current_temp": 25.0,
"rotation_speed": 0.0,
"vacuum_pressure": 1.0,
"message": "System offline"
})
return True
async def evaporate(
self,
vessel: str,
pressure: float = 0.1,
temp: float = 60.0,
time: float = 1800.0, # 30分钟默认
stir_speed: float = 100.0
) -> bool:
"""Execute evaporate action - 简化的蒸发流程"""
self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM")
# 验证参数
if temp > self._max_temp or temp < 10.0:
error_msg = f"温度 {temp}°C 超出范围 (10-{self._max_temp}°C)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"rotavap_state": "Error",
"message": error_msg
})
return False
if stir_speed > self._max_rotation_speed or stir_speed < 10.0:
error_msg = f"旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"rotavap_state": "Error",
"message": error_msg
})
return False
if pressure < 0.01 or pressure > 1.0:
error_msg = f"真空度 {pressure} bar 超出范围 (0.01-1.0 bar)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"rotavap_state": "Error",
"message": error_msg
})
return False
# 开始蒸发
self.data.update({
"status": f"蒸发中: {vessel}",
"rotavap_state": "Evaporating",
"current_temp": temp,
"target_temp": temp,
"rotation_speed": stir_speed,
"vacuum_pressure": pressure,
"remaining_time": time,
"progress": 0.0,
"evaporated_volume": 0.0,
"message": f"Evaporating {vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM"
})
try:
# 蒸发过程 - 实时更新进度
start_time = time_module.time()
total_time = time
while True:
current_time = time_module.time()
elapsed = current_time - start_time
remaining = max(0, total_time - elapsed)
progress = min(100.0, (elapsed / total_time) * 100)
# 模拟蒸发体积
evaporated_vol = progress * 0.8 # 假设最多蒸发80mL
# 更新状态
self.data.update({
"remaining_time": remaining,
"progress": progress,
"evaporated_volume": evaporated_vol,
"status": f"蒸发中: {vessel} | {temp}°C | {pressure} bar | {progress:.1f}% | 剩余: {remaining:.0f}s",
"message": f"Evaporating: {progress:.1f}% complete, {remaining:.0f}s remaining"
})
# 时间到了,退出循环
if remaining <= 0:
break
# 每秒更新一次
await asyncio.sleep(1.0)
# 蒸发完成
final_evaporated = 80.0
self.data.update({
"status": f"蒸发完成: {vessel} | 蒸发量: {final_evaporated:.1f}mL",
"rotavap_state": "Completed",
"evaporated_volume": final_evaporated,
"progress": 100.0,
"remaining_time": 0.0,
"current_temp": 25.0, # 冷却下来
"rotation_speed": 0.0, # 停止旋转
"vacuum_pressure": 1.0, # 恢复大气压
"message": f"Evaporation completed: {final_evaporated}mL evaporated from {vessel}"
})
self.logger.info(f"Evaporation completed: {final_evaporated}mL evaporated from {vessel}")
return True
except Exception as e:
# 出错处理
self.logger.error(f"Error during evaporation: {str(e)}")
self.data.update({
"status": f"蒸发错误: {str(e)}",
"rotavap_state": "Error",
"current_temp": 25.0,
"rotation_speed": 0.0,
"vacuum_pressure": 1.0,
"message": f"Evaporation failed: {str(e)}"
})
return False
# === 核心状态属性 ===
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def rotavap_state(self) -> str:
return self.data.get("rotavap_state", "Unknown")
@property
def current_temp(self) -> float:
return self.data.get("current_temp", 25.0)
@property
def rotation_speed(self) -> float:
return self.data.get("rotation_speed", 0.0)
@property
def vacuum_pressure(self) -> float:
return self.data.get("vacuum_pressure", 1.0)
@property
def evaporated_volume(self) -> float:
return self.data.get("evaporated_volume", 0.0)
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def message(self) -> str:
return self.data.get("message", "")
@property
def max_temp(self) -> float:
return self._max_temp
@property
def max_rotation_speed(self) -> float:
return self._max_rotation_speed
@property
def remaining_time(self) -> float:
return self.data.get("remaining_time", 0.0)

View File

@@ -0,0 +1,184 @@
import asyncio
import logging
from typing import Dict, Any, Optional
class VirtualSeparator:
"""Virtual separator device for SeparateProtocol testing"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and "id" in kwargs:
device_id = kwargs.pop("id")
if config is None and "config" in kwargs:
config = kwargs.pop("config")
# 设置默认值
self.device_id = device_id or "unknown_separator"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualSeparator.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualSeparator {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
self._volume = self.config.get("volume") or kwargs.get("volume", 250.0)
self._has_phases = self.config.get("has_phases") or kwargs.get("has_phases", True)
# 处理其他kwargs参数但跳过已知的配置参数
skip_keys = {"port", "volume", "has_phases"}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual separator"""
print(f"=== VirtualSeparator {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual separator {self.device_id}")
self.data.update(
{
"status": "Ready",
"separator_state": "Ready",
"volume": self._volume,
"has_phases": self._has_phases,
"phase_separation": False,
"stir_speed": 0.0,
"settling_time": 0.0,
"progress": 0.0,
"message": "",
}
)
return True
async def cleanup(self) -> bool:
"""Cleanup virtual separator"""
self.logger.info(f"Cleaning up virtual separator {self.device_id}")
return True
async def separate(
self,
purpose: str,
product_phase: str,
from_vessel: str,
separation_vessel: str,
to_vessel: str,
waste_phase_to_vessel: str = "",
solvent: str = "",
solvent_volume: float = 50.0,
through: str = "",
repeats: int = 1,
stir_time: float = 30.0,
stir_speed: float = 300.0,
settling_time: float = 300.0,
) -> bool:
"""Execute separate action - matches Separate action"""
self.logger.info(f"Separate: purpose={purpose}, product_phase={product_phase}, from_vessel={from_vessel}")
# 验证参数
if product_phase not in ["top", "bottom"]:
self.logger.error(f"Invalid product_phase {product_phase}, must be 'top' or 'bottom'")
self.data["message"] = f"产物相位 {product_phase} 无效,必须是 'top''bottom'"
return False
if purpose not in ["wash", "extract"]:
self.logger.error(f"Invalid purpose {purpose}, must be 'wash' or 'extract'")
self.data["message"] = f"分离目的 {purpose} 无效,必须是 'wash''extract'"
return False
# 开始分离
self.data.update(
{
"status": "Running",
"separator_state": "Separating",
"purpose": purpose,
"product_phase": product_phase,
"from_vessel": from_vessel,
"separation_vessel": separation_vessel,
"to_vessel": to_vessel,
"waste_phase_to_vessel": waste_phase_to_vessel,
"solvent": solvent,
"solvent_volume": solvent_volume,
"repeats": repeats,
"stir_speed": stir_speed,
"settling_time": settling_time,
"phase_separation": True,
"progress": 0.0,
"message": f"正在分离: {from_vessel} -> {to_vessel}",
}
)
# 模拟分离过程
total_time = (stir_time + settling_time) * repeats
simulation_time = min(total_time / 60.0, 15.0) # 最多模拟15秒
for repeat in range(repeats):
# 搅拌阶段
for progress in range(0, 51, 10):
await asyncio.sleep(simulation_time / (repeats * 10))
overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats
self.data["progress"] = overall_progress
self.data["message"] = f"{repeat+1}次分离 - 搅拌中 ({progress}%)"
# 静置分相阶段
for progress in range(50, 101, 10):
await asyncio.sleep(simulation_time / (repeats * 10))
overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats
self.data["progress"] = overall_progress
self.data["message"] = f"{repeat+1}次分离 - 静置分相中 ({progress}%)"
# 分离完成
self.data.update(
{
"status": "Ready",
"separator_state": "Ready",
"phase_separation": False,
"stir_speed": 0.0,
"progress": 100.0,
"message": f"分离完成: {repeats}次分离操作",
}
)
self.logger.info(f"Separation completed: {repeats} cycles from {from_vessel} to {to_vessel}")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def separator_state(self) -> str:
return self.data.get("separator_state", "Unknown")
@property
def volume(self) -> float:
return self.data.get("volume", self._volume)
@property
def has_phases(self) -> bool:
return self.data.get("has_phases", self._has_phases)
@property
def phase_separation(self) -> bool:
return self.data.get("phase_separation", False)
@property
def stir_speed(self) -> float:
return self.data.get("stir_speed", 0.0)
@property
def settling_time(self) -> float:
return self.data.get("settling_time", 0.0)
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def message(self) -> str:
return self.data.get("message", "")

View File

@@ -0,0 +1,147 @@
import time
import asyncio
from typing import Union
class VirtualSolenoidValve:
"""
虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态
"""
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
# 从配置中获取参数,提供默认值
if config is None:
config = {}
self.device_id = device_id
self.port = config.get("port", "VIRTUAL")
self.voltage = config.get("voltage", 12.0)
self.response_time = config.get("response_time", 0.1)
# 状态属性
self._status = "Idle"
self._valve_state = "Closed" # "Open" or "Closed"
self._is_open = False
async def initialize(self) -> bool:
"""初始化设备"""
self._status = "Idle"
return True
async def cleanup(self) -> bool:
"""清理资源"""
return True
@property
def status(self) -> str:
return self._status
@property
def valve_state(self) -> str:
return self._valve_state
@property
def is_open(self) -> bool:
return self._is_open
def get_valve_position(self) -> str:
"""获取阀门位置状态"""
return "OPEN" if self._is_open else "CLOSED"
async def set_valve_position(self, command: str = None, **kwargs):
"""
设置阀门位置 - ROS动作接口
Args:
command: "OPEN"/"CLOSED" 或其他控制命令
"""
if command is None:
return {"success": False, "message": "Missing command parameter"}
print(f"SOLENOID_VALVE: {self.device_id} 接收到命令: {command}")
self._status = "Busy"
# 模拟阀门响应时间
await asyncio.sleep(self.response_time)
# 处理不同的命令格式
if isinstance(command, str):
cmd_upper = command.upper()
if cmd_upper in ["OPEN", "ON", "TRUE", "1"]:
self._is_open = True
self._valve_state = "Open"
result_msg = f"Valve {self.device_id} opened"
elif cmd_upper in ["CLOSED", "CLOSE", "OFF", "FALSE", "0"]:
self._is_open = False
self._valve_state = "Closed"
result_msg = f"Valve {self.device_id} closed"
else:
# 可能是端口名称,处理路径设置
# 对于简单电磁阀,任何非关闭命令都视为开启
self._is_open = True
self._valve_state = "Open"
result_msg = f"Valve {self.device_id} set to position: {command}"
else:
self._status = "Error"
return {"success": False, "message": "Invalid command type"}
self._status = "Idle"
print(f"SOLENOID_VALVE: {result_msg}")
return {
"success": True,
"message": result_msg,
"valve_position": self.get_valve_position()
}
async def open(self, **kwargs):
"""打开电磁阀 - ROS动作接口"""
return await self.set_valve_position(command="OPEN")
async def close(self, **kwargs):
"""关闭电磁阀 - ROS动作接口"""
return await self.set_valve_position(command="CLOSED")
async def set_state(self, command: Union[bool, str], **kwargs):
"""
设置阀门状态 - 兼容 SendCmd 类型
Args:
command: True/False 或 "open"/"close"
"""
if isinstance(command, bool):
cmd_str = "OPEN" if command else "CLOSED"
elif isinstance(command, str):
cmd_str = command
else:
return {"success": False, "message": "Invalid command type"}
return await self.set_valve_position(command=cmd_str)
def toggle(self):
"""切换阀门状态"""
if self._is_open:
return self.close()
else:
return self.open()
def is_closed(self) -> bool:
"""检查阀门是否关闭"""
return not self._is_open
def get_state(self) -> dict:
"""获取阀门完整状态"""
return {
"device_id": self.device_id,
"port": self.port,
"voltage": self.voltage,
"response_time": self.response_time,
"is_open": self._is_open,
"valve_state": self._valve_state,
"status": self._status,
"position": self.get_valve_position()
}
async def reset(self):
"""重置阀门到关闭状态"""
return await self.close()

View File

@@ -1,9 +1,10 @@
import asyncio
import logging
import time as time_module
from typing import Dict, Any
class VirtualStirrer:
"""Virtual stirrer device for StirProtocol testing"""
"""Virtual stirrer device for StirProtocol testing - 功能完整版"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
@@ -19,86 +20,196 @@ class VirtualStirrer:
self.logger = logging.getLogger(f"VirtualStirrer.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualStirrer {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1000.0)
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1500.0)
self._min_speed = self.config.get('min_speed') or kwargs.get('min_speed', 50.0)
# 处理其他kwargs参数,但跳过已知的配置参数
skip_keys = {'port', 'max_temp', 'max_speed'}
# 处理其他kwargs参数
skip_keys = {'port', 'max_speed', 'min_speed'}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual stirrer"""
print(f"=== VirtualStirrer {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual stirrer {self.device_id}")
# 初始化状态信息
self.data.update({
"status": "Idle"
"status": "Idle",
"operation_mode": "Idle", # 操作模式: Idle, Stirring, Settling, Completed, Error
"current_vessel": "", # 当前搅拌的容器
"current_speed": 0.0, # 当前搅拌速度
"is_stirring": False, # 是否正在搅拌
"remaining_time": 0.0, # 剩余时间
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual stirrer"""
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
self.data.update({
"status": "Offline",
"operation_mode": "Offline",
"current_vessel": "",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": 0.0,
})
return True
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
"""Execute stir action - matches Stir action exactly"""
"""Execute stir action - 定时搅拌 + 沉降"""
self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
# 验证参数
if stir_speed > self._max_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
if stir_speed > self._max_speed or stir_speed < self._min_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False
# 开始搅拌
self.data["status"] = f"搅拌中: {stir_speed} RPM, {stir_time}s"
# === 第一阶段:搅拌 ===
start_time = time_module.time()
total_stir_time = stir_time
# 模拟搅拌时间
simulation_time = min(stir_time, 10.0) # 最多等待10秒用于测试
await asyncio.sleep(simulation_time)
self.data.update({
"status": f"搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s",
"operation_mode": "Stirring",
"current_speed": stir_speed,
"is_stirring": True,
"remaining_time": total_stir_time,
})
# 搅拌完成,开始沉降
# 搅拌过程 - 实时更新剩余时间
while True:
current_time = time_module.time()
elapsed = current_time - start_time
remaining = max(0, total_stir_time - elapsed)
# 更新状态
self.data.update({
"remaining_time": remaining,
"status": f"搅拌中: {stir_speed} RPM | 剩余: {remaining:.0f}s"
})
# 搅拌时间到了
if remaining <= 0:
break
await asyncio.sleep(1.0)
# === 第二阶段:沉降(如果需要)===
if settling_time > 0:
self.data["status"] = f"沉降中: {settling_time}s"
settling_simulation = min(settling_time, 5.0) # 最多等待5秒
await asyncio.sleep(settling_simulation)
start_settling_time = time_module.time()
total_settling_time = settling_time
self.data.update({
"status": f"沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s",
"operation_mode": "Settling",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": total_settling_time,
})
# 沉降过程 - 实时更新剩余时间
while True:
current_time = time_module.time()
elapsed = current_time - start_settling_time
remaining = max(0, total_settling_time - elapsed)
# 更新状态
self.data.update({
"remaining_time": remaining,
"status": f"沉降中: 停止搅拌 | 剩余: {remaining:.0f}s"
})
# 沉降时间到了
if remaining <= 0:
break
await asyncio.sleep(1.0)
# 操作完成
self.data["status"] = "搅拌完成"
# === 操作完成 ===
settling_info = f" | 沉降: {settling_time:.0f}s" if settling_time > 0 else ""
self.data.update({
"status": f"完成: 搅拌 {stir_speed} RPM, {stir_time:.0f}s{settling_info}",
"operation_mode": "Completed",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": 0.0,
})
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s")
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s + settling {settling_time}s")
return True
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
"""Start stir action - matches StartStir action exactly"""
"""Start stir action - 开始持续搅拌"""
self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
# 验证参数
if stir_speed > self._max_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
if stir_speed > self._max_speed or stir_speed < self._min_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False
self.data["status"] = f"开始搅拌: {vessel} at {stir_speed} RPM"
self.data.update({
"status": f"启动: 持续搅拌 {vessel} at {stir_speed} RPM | {purpose}",
"operation_mode": "Stirring",
"current_vessel": vessel,
"current_speed": stir_speed,
"is_stirring": True,
"remaining_time": -1.0, # -1 表示持续运行
})
return True
async def stop_stir(self, vessel: str) -> bool:
"""Stop stir action - matches StopStir action exactly"""
"""Stop stir action - 停止搅拌"""
self.logger.info(f"StopStir: vessel={vessel}")
self.data["status"] = f"停止搅拌: {vessel}"
current_speed = self.data.get("current_speed", 0.0)
self.data.update({
"status": f"已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM",
"operation_mode": "Stopped",
"current_vessel": "",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": 0.0,
})
return True
# 状态属性 - 只保留 action 中定义的 feedback
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Idle")
return self.data.get("status", "Idle")
@property
def operation_mode(self) -> str:
return self.data.get("operation_mode", "Idle")
@property
def current_vessel(self) -> str:
return self.data.get("current_vessel", "")
@property
def current_speed(self) -> float:
return self.data.get("current_speed", 0.0)
@property
def is_stirring(self) -> bool:
return self.data.get("is_stirring", False)
@property
def remaining_time(self) -> float:
return self.data.get("remaining_time", 0.0)

View File

@@ -1,149 +1,328 @@
import asyncio
import time
from enum import Enum
from typing import Union, Optional
import logging
from typing import Dict, Any, Optional
class VirtualPumpMode(Enum):
Normal = 0
AccuratePos = 1
AccuratePosVel = 2
class VirtualTransferPump:
"""Virtual pump device specifically for Transfer protocol"""
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
"""
初始化虚拟转移泵
# 设置默认值
self.device_id = device_id or "unknown_transfer_pump"
self.config = config or {}
Args:
device_id: 设备ID
config: 配置字典包含max_volume, port等参数
**kwargs: 其他参数,确保兼容性
"""
self.device_id = device_id or "virtual_transfer_pump"
# 从config或kwargs中获取参数确保类型正确
if config:
self.max_volume = float(config.get('max_volume', 25.0))
self.port = config.get('port', 'VIRTUAL')
else:
self.max_volume = float(kwargs.get('max_volume', 25.0))
self.port = kwargs.get('port', 'VIRTUAL')
self._transfer_rate = float(kwargs.get('transfer_rate', 0))
self.mode = kwargs.get('mode', VirtualPumpMode.Normal)
# 状态变量 - 确保都是正确类型
self._status = "Idle"
self._position = 0.0 # float
self._max_velocity = 5.0 # float
self._current_volume = 0.0 # float
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualTransferPump {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 50.0)
self._transfer_rate = self.config.get('transfer_rate') or kwargs.get('transfer_rate', 5.0)
self._current_volume = 0.0
self.is_running = False
async def initialize(self) -> bool:
"""Initialize virtual transfer pump"""
print(f"=== VirtualTransferPump {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual transfer pump {self.device_id}")
self.data.update({
"status": "Idle",
"current_volume": 0.0,
"max_volume": self._max_volume,
"transfer_rate": self._transfer_rate,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
})
"""初始化虚拟泵"""
self.logger.info(f"Initializing virtual pump {self.device_id}")
self._status = "Idle"
self._position = 0.0
self._current_volume = 0.0
return True
async def cleanup(self) -> bool:
"""Cleanup virtual transfer pump"""
self.logger.info(f"Cleaning up virtual transfer pump {self.device_id}")
"""清理虚拟泵"""
self.logger.info(f"Cleaning up virtual pump {self.device_id}")
self._status = "Idle"
return True
async def transfer(self, 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) -> bool:
"""Execute liquid transfer - matches Transfer action"""
self.logger.info(f"Transfer: {volume}mL from {from_vessel} to {to_vessel}")
# 计算转移时间
if time > 0:
transfer_time = time
else:
# 如果是粘性液体,降低转移速率
rate = self._transfer_rate * 0.5 if viscous else self._transfer_rate
transfer_time = volume / rate
self.data.update({
"status": "Running",
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"current_status": "Transferring",
"progress": 0.0,
"transferred_volume": 0.0
})
# 模拟转移过程
steps = 10
step_time = transfer_time / steps
step_volume = volume / steps
for i in range(steps):
await asyncio.sleep(step_time)
progress = (i + 1) / steps * 100
transferred = (i + 1) * step_volume
self.data.update({
"progress": progress,
"transferred_volume": transferred,
"current_status": f"Transferring {progress:.1f}%"
})
self.logger.info(f"Transfer progress: {progress:.1f}% ({transferred:.1f}/{volume}mL)")
# 如果需要冲洗
if rinsing_solvent and rinsing_volume > 0 and rinsing_repeats > 0:
self.data["current_status"] = "Rinsing"
for repeat in range(rinsing_repeats):
self.logger.info(f"Rinsing cycle {repeat + 1}/{rinsing_repeats} with {rinsing_solvent}")
await asyncio.sleep(1) # 模拟冲洗时间
self.data.update({
"status": "Idle",
"current_status": "Transfer completed",
"progress": 100.0,
"transferred_volume": volume
})
return True
# 添加所有在virtual_device.yaml中定义的状态属性
# 基本属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
return self._status
@property
def position(self) -> float:
"""当前柱塞位置 (ml)"""
return self._position
@property
def current_volume(self) -> float:
return self.data.get("current_volume", 0.0)
"""当前注射器中的体积 (ml)"""
return self._current_volume
@property
def max_volume(self) -> float:
return self.data.get("max_volume", self._max_volume)
def max_velocity(self) -> float:
return self._max_velocity
@property
def transfer_rate(self) -> float:
return self.data.get("transfer_rate", self._transfer_rate)
return self._transfer_rate
def set_max_velocity(self, velocity: float):
"""设置最大速度 (ml/s)"""
self._max_velocity = max(0.1, min(50.0, velocity)) # 限制在合理范围内
self.logger.info(f"Set max velocity to {self._max_velocity} ml/s")
@property
def from_vessel(self) -> str:
return self.data.get("from_vessel", "")
def get_status(self) -> str:
"""获取泵状态"""
return self._status
@property
def to_vessel(self) -> str:
return self.data.get("to_vessel", "")
async def _simulate_operation(self, duration: float):
"""模拟操作延时"""
self._status = "Busy"
await asyncio.sleep(duration)
self._status = "Idle"
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
def _calculate_duration(self, volume: float, velocity: float = None) -> float:
"""计算操作持续时间"""
if velocity is None:
velocity = self._max_velocity
return abs(volume) / velocity
@property
def transferred_volume(self) -> float:
return self.data.get("transferred_volume", 0.0)
# 新的set_position方法 - 专门用于SetPumpPosition动作
async def set_position(self, position: float, max_velocity: float = None):
"""
移动到绝对位置 - 专门用于SetPumpPosition动作
Args:
position (float): 目标位置 (ml)
max_velocity (float): 移动速度 (ml/s)
Returns:
dict: 符合SetPumpPosition.action定义的结果
"""
try:
# 验证并转换参数
target_position = float(position)
velocity = float(max_velocity) if max_velocity is not None else self._max_velocity
# 限制位置在有效范围内
target_position = max(0.0, min(float(self.max_volume), target_position))
# 计算移动距离和时间
volume_to_move = abs(target_position - self._position)
duration = self._calculate_duration(volume_to_move, velocity)
self.logger.info(f"SET_POSITION: Moving to {target_position} ml (current: {self._position} ml), velocity: {velocity} ml/s")
# 模拟移动过程
start_position = self._position
steps = 10 if duration > 0.1 else 1 # 如果移动距离很小只用1步
step_duration = duration / steps if steps > 1 else duration
for i in range(steps + 1):
# 计算当前位置和进度
progress = (i / steps) * 100 if steps > 0 else 100
current_pos = start_position + (target_position - start_position) * (i / steps) if steps > 0 else target_position
# 更新状态
self._status = "Moving" if i < steps else "Idle"
self._position = current_pos
self._current_volume = current_pos
# 等待一小步时间
if i < steps and step_duration > 0:
await asyncio.sleep(step_duration)
# 确保最终位置准确
self._position = target_position
self._current_volume = target_position
self._status = "Idle"
self.logger.info(f"SET_POSITION: Reached position {self._position} ml, current volume: {self._current_volume} ml")
# 返回符合action定义的结果
return {
"success": True,
"message": f"Successfully moved to position {self._position} ml"
}
except Exception as e:
error_msg = f"Failed to set position: {str(e)}"
self.logger.error(error_msg)
return {
"success": False,
"message": error_msg
}
@property
def current_status(self) -> str:
return self.data.get("current_status", "Ready")
# 其他泵操作方法
async def pull_plunger(self, volume: float, velocity: float = None):
"""
拉取柱塞(吸液)
Args:
volume (float): 要拉取的体积 (ml)
velocity (float): 拉取速度 (ml/s)
"""
new_position = min(self.max_volume, self._position + volume)
actual_volume = new_position - self._position
if actual_volume <= 0:
self.logger.warning("Cannot pull - already at maximum volume")
return
duration = self._calculate_duration(actual_volume, velocity)
self.logger.info(f"Pulling {actual_volume} ml (from {self._position} to {new_position})")
await self._simulate_operation(duration)
self._position = new_position
self._current_volume = new_position
self.logger.info(f"Pulled {actual_volume} ml, current volume: {self._current_volume} ml")
async def push_plunger(self, volume: float, velocity: float = None):
"""
推出柱塞(排液)
Args:
volume (float): 要推出的体积 (ml)
velocity (float): 推出速度 (ml/s)
"""
new_position = max(0, self._position - volume)
actual_volume = self._position - new_position
if actual_volume <= 0:
self.logger.warning("Cannot push - already at minimum volume")
return
duration = self._calculate_duration(actual_volume, velocity)
self.logger.info(f"Pushing {actual_volume} ml (from {self._position} to {new_position})")
await self._simulate_operation(duration)
self._position = new_position
self._current_volume = new_position
self.logger.info(f"Pushed {actual_volume} ml, current volume: {self._current_volume} ml")
# 便捷操作方法
async def aspirate(self, volume: float, velocity: float = None):
"""吸液操作"""
await self.pull_plunger(volume, velocity)
async def dispense(self, volume: float, velocity: float = None):
"""排液操作"""
await self.push_plunger(volume, velocity)
async def transfer(self, volume: float, aspirate_velocity: float = None, dispense_velocity: float = None):
"""转移操作(先吸后排)"""
# 吸液
await self.aspirate(volume, aspirate_velocity)
# 短暂停顿
await asyncio.sleep(0.1)
# 排液
await self.dispense(volume, dispense_velocity)
async def empty_syringe(self, velocity: float = None):
"""清空注射器"""
await self.set_position(0, velocity)
async def fill_syringe(self, velocity: float = None):
"""充满注射器"""
await self.set_position(self.max_volume, velocity)
async def stop_operation(self):
"""停止当前操作"""
self._status = "Idle"
self.logger.info("Operation stopped")
# 状态查询方法
def get_position(self) -> float:
"""获取当前位置"""
return self._position
def get_current_volume(self) -> float:
"""获取当前体积"""
return self._current_volume
def get_remaining_capacity(self) -> float:
"""获取剩余容量"""
return self.max_volume - self._current_volume
def is_empty(self) -> bool:
"""检查是否为空"""
return self._current_volume <= 0.01 # 允许小量误差
def is_full(self) -> bool:
"""检查是否已满"""
return self._current_volume >= (self.max_volume - 0.01) # 允许小量误差
# 调试和状态信息
def get_pump_info(self) -> dict:
"""获取泵的详细信息"""
return {
"device_id": self.device_id,
"status": self._status,
"position": self._position,
"current_volume": self._current_volume,
"max_volume": self.max_volume,
"max_velocity": self._max_velocity,
"mode": self.mode.name,
"is_empty": self.is_empty(),
"is_full": self.is_full(),
"remaining_capacity": self.get_remaining_capacity()
}
def __str__(self):
return f"VirtualTransferPump({self.device_id}: {self._current_volume:.2f}/{self.max_volume} ml, {self._status})"
def __repr__(self):
return self.__str__()
# 使用示例
async def demo():
"""虚拟泵使用示例"""
pump = VirtualTransferPump("demo_pump", {"max_volume": 50.0})
await pump.initialize()
print(f"Initial state: {pump}")
# 测试set_position方法
result = await pump.set_position(10.0, max_velocity=2.0)
print(f"Set position result: {result}")
print(f"After setting position to 10ml: {pump}")
# 吸液测试
await pump.aspirate(5.0, velocity=2.0)
print(f"After aspirating 5ml: {pump}")
# 清空测试
result = await pump.set_position(0.0)
print(f"Empty result: {result}")
print(f"After emptying: {pump}")
print("\nPump info:", pump.get_pump_info())
if __name__ == "__main__":
asyncio.run(demo())

View File

@@ -0,0 +1,47 @@
import asyncio
import time
from typing import Dict, Any, Optional
class VirtualVacuumPump:
"""Virtual vacuum pump for testing"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
self.device_id = device_id or "unknown_vacuum_pump"
self.config = config or {}
self.data = {}
self._status = "OPEN"
async def initialize(self) -> bool:
"""Initialize virtual vacuum pump"""
self.data.update({
"status": self._status
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual vacuum pump"""
return True
@property
def status(self) -> str:
return self._status
def get_status(self) -> str:
return self._status
def set_status(self, string):
self._status = string
time.sleep(5)
def open(self):
self._status = "OPEN"
def close(self):
self._status = "CLOSED"
def is_open(self):
return self._status
def is_closed(self):
return not self._status

View File

@@ -1,11 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import json
import base64
import argparse
import sys
import json
import socket
import time
@@ -96,17 +94,20 @@ class ZhidaClient:
def abort(self) -> dict:
return self._send_command({"command": "abort"})
"""
a,b,c
1,2,4
2,4,5
"""
client = ZhidaClient()
# 连接
client.connect()
# 获取状态
print(client.status)
if __name__ == "__main__":
"""
a,b,c
1,2,4
2,4,5
"""
client = ZhidaClient()
# 连接
client.connect()
# 获取状态
print(client.status)
# 命令格式python zhida.py <subcommand> [options]
# 命令格式python zhida.py <subcommand> [options]

View File

@@ -33,19 +33,19 @@ class CleanProtocol(BaseModel):
class SeparateProtocol(BaseModel):
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 # Optional. Volume of solvent to add.
through: str # Optional. Solid chemical to send product phase through on way to to_vessel, e.g. 'celite'.
repeats: int # Optional. Number of separations to perform.
stir_time: float # Optional. Time stir for after adding solvent, before separation of phases.
stir_speed: float # Optional. Speed to stir at after adding solvent, before separation of phases.
settling_time: float # Optional. Time
purpose: str
product_phase: str
from_vessel: str
separation_vessel: str
to_vessel: str
waste_phase_to_vessel: str
solvent: str
solvent_volume: float
through: str
repeats: int
stir_time: float
stir_speed: float
settling_time: float
class EvaporateProtocol(BaseModel):
@@ -67,6 +67,7 @@ class AGVTransferProtocol(BaseModel):
to_repo: dict
from_repo_position: str
to_repo_position: str
#=============新添加的新的协议================
class AddProtocol(BaseModel):
vessel: str
@@ -84,16 +85,16 @@ class CentrifugeProtocol(BaseModel):
vessel: str
speed: float
time: float
temp: float # 移除默认值
temp: float
class FilterProtocol(BaseModel):
vessel: str
filtrate_vessel: str # 移除默认值
stir: bool # 移除默认值
stir_speed: float # 移除默认值
temp: float # 移除默认值
continue_heatchill: bool # 移除默认值
volume: float # 移除默认值
filtrate_vessel: str
stir: bool
stir_speed: float
temp: float
continue_heatchill: bool
volume: float
class HeatChillProtocol(BaseModel):
vessel: str
@@ -137,45 +138,80 @@ class TransferProtocol(BaseModel):
solid: bool = False
class CleanVesselProtocol(BaseModel):
vessel: str # 要清洗的容器名称
solvent: str # 用于清洗容器的溶剂名称
volume: float # 清洗溶剂的体积,可选参数
temp: float # 清洗时的温度,可选参数
repeats: int = 1 # 清洗操作的重复次数,默认为 1
vessel: str
solvent: str
volume: float
temp: float
repeats: int = 1
class DissolveProtocol(BaseModel):
vessel: str # 装有要溶解物质的容器名称
solvent: str # 用于溶解物质的溶剂名称
volume: float # 溶剂的体积,可选参数
amount: str = "" # 要溶解物质的量,可选参数
temp: float = 25.0 # 溶解时的温度,可选参数
time: float = 0.0 # 溶解的时间,可选参数
stir_speed: float = 0.0 # 搅拌速度,可选参数
vessel: str
solvent: str
volume: float
amount: str = ""
temp: float = 25.0
time: float = 0.0
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 # 洗脱操作的重复次数,默认为 0
residence_time: float = 0.0 # 物质在过滤介质中的停留时间,可选参数
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 # 目标容器的名称,分离后的样品要到达的容器
column: str # 所使用的柱子的名称
from_vessel: str
to_vessel: str
column: str
class WashSolidProtocol(BaseModel):
vessel: str # 装有固体物质的容器名称
solvent: str # 用于清洗固体的溶剂名称
volume: float # 清洗溶剂的体积
filtrate_vessel: str = "" # 滤液要收集到的容器名称,可选参数
temp: float = 25.0 # 清洗时的温度,可选参数
stir: bool = False # 是否在清洗过程中搅拌,默认为 False
stir_speed: float = 0.0 # 搅拌速度,可选参数
time: float = 0.0 # 清洗的时间,可选参数
repeats: int = 1 # 清洗操作的重复次数,默认为 1
vessel: str
solvent: str
volume: float
filtrate_vessel: str = ""
temp: float = 25.0
stir: bool = False
stir_speed: float = 0.0
time: float = 0.0
repeats: int = 1
__all__ = ["Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol", "CentrifugeProtocol", "AddProtocol", "FilterProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol", "TransferProtocol", "CleanVesselProtocol", "DissolveProtocol", "FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol"]
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="反应容器")
__all__ = [
"Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol",
"EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol",
"CentrifugeProtocol", "AddProtocol", "FilterProtocol",
"HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol",
"StirProtocol", "StartStirProtocol", "StopStirProtocol",
"TransferProtocol", "CleanVesselProtocol", "DissolveProtocol",
"FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol",
"AdjustPHProtocol", "ResetHandlingProtocol", "DryProtocol",
"RecrystallizeProtocol", "HydrogenateProtocol"
]
# End Protocols

View File

@@ -1,10 +1,10 @@
io_snrd:
description: IO Board with 16 IOs
class:
module: ilabos.device_comms.SRND_16_IO:SRND_16_IO
type: python
hardware_interface:
name: modbus_client
extra_info: []
read: read_io_coil
write: write_io_coil
#io_snrd:
# description: IO Board with 16 IOs
# class:
# module: unilabos.device_comms.SRND_16_IO:SRND_16_IO
# type: python
# hardware_interface:
# name: modbus_client
# extra_info: []
# read: read_io_coil
# write: write_io_coil

View File

@@ -1,7 +1,102 @@
serial:
description: Serial communication interface, used when sharing same serial port for multiple devices
class:
action_value_mappings:
auto-handle_serial_request:
feedback: {}
goal: {}
goal_default:
request: null
response: null
handles: []
result: {}
schema:
description: handle_serial_request的参数schema
properties:
feedback: {}
goal:
properties:
request:
type: string
response:
type: string
required:
- request
- response
type: object
result: {}
required:
- goal
title: handle_serial_request参数
type: object
type: UniLabJsonCommand
auto-read_data:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: read_data的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: read_data参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode
status_types: {}
type: ros2
schema:
properties: {}
description: Serial communication interface, used when sharing same serial port
for multiple devices
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate:
default: 9600
type: integer
device_id:
type: string
port:
type: string
resource_tracker:
type: string
required:
- device_id
- port
type: object
data:
properties: {}
required: []
type: object

View File

@@ -0,0 +1,69 @@
camera:
class:
action_value_mappings:
auto-destroy_node:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: destroy_node的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: destroy_node参数
type: object
type: UniLabJsonCommand
auto-timer_callback:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: timer_callback的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: timer_callback参数
type: object
type: UniLabJsonCommand
module: unilabos.ros.nodes.presets.camera:VideoPublisher
status_types: {}
type: ros2
description: ''
handles: []
icon: ''
init_param_schema:
config:
properties:
camera_index:
default: 0
type: string
device_id:
default: video_publisher
type: string
period:
default: 0.1
type: number
resource_tracker:
type: string
required: []
type: object
data:
properties: {}
required: []
type: object

View File

@@ -1,67 +1,433 @@
# 光学表征设备:红外、紫外可见、拉曼等
raman_home_made:
description: Raman spectroscopy device
class:
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
type: python
status_types:
status: String
action_value_mappings:
raman_cmd:
type: SendCmd
goal:
command: command
feedback: {}
result:
success: success
schema:
properties:
status:
type: string
required:
- status
additionalProperties: false
type: object
hplc.agilent:
description: HPLC device
class:
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
type: python
status_types:
device_status: String
could_run: Bool
driver_init_ok: Bool
is_running: Bool
finish_status: String
status_text: String
action_value_mappings:
auto-check_status:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: check_status的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_status参数
type: object
type: UniLabJsonCommand
auto-extract_data_from_txt:
feedback: {}
goal: {}
goal_default:
file_path: null
handles: []
result: {}
schema:
description: extract_data_from_txt的参数schema
properties:
feedback: {}
goal:
properties:
file_path:
type: string
required:
- file_path
type: object
result: {}
required:
- goal
title: extract_data_from_txt参数
type: object
type: UniLabJsonCommand
auto-start_sequence:
feedback: {}
goal: {}
goal_default:
params: null
resource: null
wf_name: null
handles: []
result: {}
schema:
description: start_sequence的参数schema
properties:
feedback: {}
goal:
properties:
params:
type: string
resource:
type: object
wf_name:
type: string
required:
- wf_name
type: object
result: {}
required:
- goal
title: start_sequence参数
type: object
type: UniLabJsonCommand
auto-try_close_sub_device:
feedback: {}
goal: {}
goal_default:
device_name: null
handles: []
result: {}
schema:
description: try_close_sub_device的参数schema
properties:
feedback: {}
goal:
properties:
device_name:
type: string
required: []
type: object
result: {}
required:
- goal
title: try_close_sub_device参数
type: object
type: UniLabJsonCommand
auto-try_open_sub_device:
feedback: {}
goal: {}
goal_default:
device_name: null
handles: []
result: {}
schema:
description: try_open_sub_device的参数schema
properties:
feedback: {}
goal:
properties:
device_name:
type: string
required: []
type: object
result: {}
required:
- goal
title: try_open_sub_device参数
type: object
type: UniLabJsonCommand
execute_command_from_outer:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result:
success: success
schema:
properties:
device_status:
type: string
could_run:
type: boolean
driver_init_ok:
type: boolean
is_running:
type: boolean
finish_status:
type: string
status_text:
type: string
required:
- device_status
- could_run
- driver_init_ok
- is_running
- finish_status
- status_text
additionalProperties: false
type: object
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
status_types:
could_run: bool
data_file: list
device_status: str
driver_init_ok: bool
finish_status: str
is_running: bool
status_text: str
success: bool
type: python
description: HPLC device
handles: []
icon: ''
init_param_schema:
config:
properties:
driver_debug:
default: false
type: string
required: []
type: object
data:
properties:
could_run:
type: boolean
data_file:
type: array
device_status:
type: string
driver_init_ok:
type: boolean
finish_status:
type: string
is_running:
type: boolean
status_text:
type: string
success:
type: boolean
required:
- status_text
- device_status
- could_run
- driver_init_ok
- is_running
- success
- finish_status
- data_file
type: object
raman_home_made:
class:
action_value_mappings:
auto-ccd_time:
feedback: {}
goal: {}
goal_default:
int_time: null
handles: []
result: {}
schema:
description: ccd_time的参数schema
properties:
feedback: {}
goal:
properties:
int_time:
type: string
required:
- int_time
type: object
result: {}
required:
- goal
title: ccd_time参数
type: object
type: UniLabJsonCommand
auto-laser_on_power:
feedback: {}
goal: {}
goal_default:
output_voltage_laser: null
handles: []
result: {}
schema:
description: laser_on_power的参数schema
properties:
feedback: {}
goal:
properties:
output_voltage_laser:
type: string
required:
- output_voltage_laser
type: object
result: {}
required:
- goal
title: laser_on_power参数
type: object
type: UniLabJsonCommand
auto-raman_cmd:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: raman_cmd的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: raman_cmd参数
type: object
type: UniLabJsonCommand
auto-raman_without_background:
feedback: {}
goal: {}
goal_default:
int_time: null
laser_power: null
handles: []
result: {}
schema:
description: raman_without_background的参数schema
properties:
feedback: {}
goal:
properties:
int_time:
type: string
laser_power:
type: string
required:
- int_time
- laser_power
type: object
result: {}
required:
- goal
title: raman_without_background参数
type: object
type: UniLabJsonCommand
auto-raman_without_background_average:
feedback: {}
goal: {}
goal_default:
average: null
int_time: null
laser_power: null
sample_name: null
handles: []
result: {}
schema:
description: raman_without_background_average的参数schema
properties:
feedback: {}
goal:
properties:
average:
type: string
int_time:
type: string
laser_power:
type: string
sample_name:
type: string
required:
- sample_name
- int_time
- laser_power
- average
type: object
result: {}
required:
- goal
title: raman_without_background_average参数
type: object
type: UniLabJsonCommand
raman_cmd:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result:
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
status_types: {}
type: python
description: Raman spectroscopy device
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate_ccd:
default: 921600
type: string
baudrate_laser:
default: 9600
type: string
port_ccd:
type: string
port_laser:
type: string
required:
- port_laser
- port_ccd
type: object
data:
properties: {}
required: []
type: object

View File

@@ -1,9 +1,31 @@
hotel.thermo_orbitor_rs2_hotel:
description: Thermo Orbitor RS2 Hotel
class:
class:
action_value_mappings: {}
module: unilabos.devices.resource_container.container:HotelContainer
status_types:
rotation: String
type: python
description: Thermo Orbitor RS2 Hotel
handles: []
icon: ''
init_param_schema:
config:
properties:
device_config:
type: object
rotation:
type: object
required:
- rotation
- device_config
type: object
data:
properties:
rotation:
type: string
required:
- rotation
type: object
model:
type: device
mesh: thermo_orbitor_rs2_hotel
type: device

View File

@@ -1,56 +1,582 @@
laiyu_add_solid:
description: Laiyu Add Solid
class:
module: unilabos.devices.laiyu_add_solid.laiyu:Laiyu
type: python
status_types: {}
action_value_mappings:
add_powder_tube:
feedback: {}
goal:
compound_mass: compound_mass
powder_tube_number: powder_tube_number
target_tube_position: target_tube_position
goal_default:
compound_mass: 0.0
powder_tube_number: 0
target_tube_position: ''
handles: []
result:
actual_mass_mg: actual_mass_mg
schema:
description: ROS Action SolidDispenseAddPowderTube 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: SolidDispenseAddPowderTube_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
compound_mass:
type: number
powder_tube_number:
maximum: 2147483647
minimum: -2147483648
type: integer
target_tube_position:
type: string
required:
- powder_tube_number
- target_tube_position
- compound_mass
title: SolidDispenseAddPowderTube_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
actual_mass_mg:
type: number
return_info:
type: string
success:
type: boolean
required:
- return_info
- actual_mass_mg
- success
title: SolidDispenseAddPowderTube_Result
type: object
required:
- goal
title: SolidDispenseAddPowderTube
type: object
type: SolidDispenseAddPowderTube
auto-add_powder_tube:
feedback: {}
goal: {}
goal_default:
compound_mass: null
powder_tube_number: null
target_tube_position: null
handles: []
result: {}
schema:
description: add_powder_tube的参数schema
properties:
feedback: {}
goal:
properties:
compound_mass:
type: string
powder_tube_number:
type: string
target_tube_position:
type: string
required:
- powder_tube_number
- target_tube_position
- compound_mass
type: object
result: {}
required:
- goal
title: add_powder_tube参数
type: object
type: UniLabJsonCommand
auto-calculate_crc:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: calculate_crc的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: calculate_crc参数
type: object
type: UniLabJsonCommand
auto-discharge:
feedback: {}
goal: {}
goal_default:
float_in: null
handles: []
result: {}
schema:
description: discharge的参数schema
properties:
feedback: {}
goal:
properties:
float_in:
type: number
required:
- float_in
type: object
result: {}
required:
- goal
title: discharge参数
type: object
type: UniLabJsonCommand
auto-move_to_plate:
feedback: {}
goal: {}
goal_default:
string: null
handles: []
result: {}
schema:
description: move_to_plate的参数schema
properties:
feedback: {}
goal:
properties:
string:
type: string
required:
- string
type: object
result: {}
required:
- goal
title: move_to_plate参数
type: object
type: UniLabJsonCommand
auto-move_to_xyz:
feedback: {}
goal: {}
goal_default:
x: null
y: null
z: null
handles: []
result: {}
schema:
description: move_to_xyz的参数schema
properties:
feedback: {}
goal:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
type: object
result: {}
required:
- goal
title: move_to_xyz参数
type: object
type: UniLabJsonCommand
auto-pick_powder_tube:
feedback: {}
goal: {}
goal_default:
int_input: null
handles: []
result: {}
schema:
description: pick_powder_tube的参数schema
properties:
feedback: {}
goal:
properties:
int_input:
type: integer
required:
- int_input
type: object
result: {}
required:
- goal
title: pick_powder_tube参数
type: object
type: UniLabJsonCommand
auto-put_powder_tube:
feedback: {}
goal: {}
goal_default:
int_input: null
handles: []
result: {}
schema:
description: put_powder_tube的参数schema
properties:
feedback: {}
goal:
properties:
int_input:
type: integer
required:
- int_input
type: object
result: {}
required:
- goal
title: put_powder_tube参数
type: object
type: UniLabJsonCommand
auto-reset:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: reset的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: reset参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
discharge:
feedback: {}
goal:
float_input: float_input
goal_default:
float_in: 0.0
handles: []
result: {}
schema:
description: ROS Action FloatSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: FloatSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
float_in:
type: number
required:
- float_in
title: FloatSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: FloatSingleInput_Result
type: object
required:
- goal
title: FloatSingleInput
type: object
type: FloatSingleInput
move_to_plate:
feedback: {}
goal:
string: string
goal_default:
string: ''
handles: []
result: {}
schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
move_to_xyz:
type: Point3DSeparateInput
feedback: {}
goal:
x: x
y: y
z: z
feedback: {}
goal_default:
x: 0.0
y: 0.0
z: 0.0
handles: []
result: {}
schema:
description: ROS Action Point3DSeparateInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: Point3DSeparateInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point3DSeparateInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: Point3DSeparateInput_Result
type: object
required:
- goal
title: Point3DSeparateInput
type: object
type: Point3DSeparateInput
pick_powder_tube:
type: IntSingleInput
feedback: {}
goal:
int_input: int_input
feedback: {}
goal_default:
int_input: 0
handles: []
result: {}
schema:
description: ROS Action IntSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: IntSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
int_input:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- int_input
title: IntSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: IntSingleInput_Result
type: object
required:
- goal
title: IntSingleInput
type: object
type: IntSingleInput
put_powder_tube:
type: IntSingleInput
feedback: {}
goal:
int_input: int_input
feedback: {}
goal_default:
int_input: 0
handles: []
result: {}
schema:
description: ROS Action IntSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: IntSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
int_input:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- int_input
title: IntSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: IntSingleInput_Result
type: object
required:
- goal
title: IntSingleInput
type: object
type: IntSingleInput
reset:
type: EmptyIn
feedback: {}
goal: {}
feedback: {}
goal_default: {}
handles: []
result: {}
add_powder_tube:
type: SolidDispenseAddPowderTube
goal:
powder_tube_number: powder_tube_number
target_tube_position: target_tube_position
compound_mass: compound_mass
feedback: {}
result:
actual_mass_mg: actual_mass_mg
move_to_plate:
type: StrSingleInput
goal:
string: string
feedback: {}
result: {}
discharge:
type: FloatSingleInput
goal:
float_input: float_input
feedback: {}
result: {}
schema:
properties: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
module: unilabos.devices.laiyu_add_solid.laiyu:Laiyu
status_types:
status: str
type: python
description: Laiyu Add Solid
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate:
default: 115200
type: string
port:
type: string
timeout:
default: 0.5
type: string
required:
- port
type: object
data:
properties:
status:
type: string
required:
- status
type: object

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +1,858 @@
moveit.toyo_xyz:
description: Toyo XYZ
class:
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
type: python
action_value_mappings:
set_position:
type: SendCmd
goal:
command: command
feedback: { }
result: { }
pick_and_place:
type: SendCmd
goal:
command: command
feedback: { }
result: { }
set_status:
type: SendCmd
goal:
command: command
feedback: { }
result: { }
model:
type: device
mesh: toyo_xyz
moveit.arm_slider:
description: Arm with Slider
model:
type: device
mesh: arm_slider
class:
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
type: python
action_value_mappings:
set_position:
type: SendCmd
goal:
command: command
auto-check_tf_update_actions:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: check_tf_update_actions的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_tf_update_actions参数
type: object
type: UniLabJsonCommand
auto-moveit_joint_task:
feedback: {}
goal: {}
goal_default:
joint_names: null
joint_positions: null
move_group: null
retry: 10
speed: 1
handles: []
result: {}
schema:
description: moveit_joint_task的参数schema
properties:
feedback: {}
goal:
properties:
joint_names:
type: string
joint_positions:
type: string
move_group:
type: string
retry:
default: 10
type: string
speed:
default: 1
type: string
required:
- move_group
- joint_positions
type: object
result: {}
required:
- goal
title: moveit_joint_task参数
type: object
type: UniLabJsonCommand
auto-moveit_task:
feedback: {}
goal: {}
goal_default:
cartesian: false
move_group: null
offsets:
- 0
- 0
- 0
position: null
quaternion: null
retry: 10
speed: 1
target_link: null
handles: []
result: {}
schema:
description: moveit_task的参数schema
properties:
feedback: {}
goal:
properties:
cartesian:
default: false
type: string
move_group:
type: string
offsets:
default:
- 0
- 0
- 0
type: string
position:
type: string
quaternion:
type: string
retry:
default: 10
type: string
speed:
default: 1
type: string
target_link:
type: string
required:
- move_group
- position
- quaternion
type: object
result: {}
required:
- goal
title: moveit_task参数
type: object
type: UniLabJsonCommand
auto-pick_and_place:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: pick_and_place的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: pick_and_place参数
type: object
type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: []
result: {}
schema:
description: post_init的参数schema
properties:
feedback: {}
goal:
properties:
ros_node:
type: string
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
auto-resource_manager:
feedback: {}
goal: {}
goal_default:
parent_link: null
resource: null
handles: []
result: {}
schema:
description: resource_manager的参数schema
properties:
feedback: {}
goal:
properties:
parent_link:
type: string
resource:
type: string
required:
- resource
- parent_link
type: object
result: {}
required:
- goal
title: resource_manager参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_status:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_status的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_status参数
type: object
type: UniLabJsonCommand
auto-wait_for_resource_action:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_resource_action的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_resource_action参数
type: object
type: UniLabJsonCommand
pick_and_place:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_position:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_status:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
status_types: {}
type: python
description: Arm with Slider
handles: []
icon: ''
init_param_schema:
config:
properties:
device_config:
type: string
joint_poses:
type: string
moveit_type:
type: string
rotation:
type: string
required:
- moveit_type
- joint_poses
type: object
data:
properties: {}
required: []
type: object
model:
mesh: arm_slider
type: device
moveit.toyo_xyz:
class:
action_value_mappings:
auto-check_tf_update_actions:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: check_tf_update_actions的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_tf_update_actions参数
type: object
type: UniLabJsonCommand
auto-moveit_joint_task:
feedback: {}
goal: {}
goal_default:
joint_names: null
joint_positions: null
move_group: null
retry: 10
speed: 1
handles: []
result: {}
schema:
description: moveit_joint_task的参数schema
properties:
feedback: {}
goal:
properties:
joint_names:
type: string
joint_positions:
type: string
move_group:
type: string
retry:
default: 10
type: string
speed:
default: 1
type: string
required:
- move_group
- joint_positions
type: object
result: {}
required:
- goal
title: moveit_joint_task参数
type: object
type: UniLabJsonCommand
auto-moveit_task:
feedback: {}
goal: {}
goal_default:
cartesian: false
move_group: null
offsets:
- 0
- 0
- 0
position: null
quaternion: null
retry: 10
speed: 1
target_link: null
handles: []
result: {}
schema:
description: moveit_task的参数schema
properties:
feedback: {}
goal:
properties:
cartesian:
default: false
type: string
move_group:
type: string
offsets:
default:
- 0
- 0
- 0
type: string
position:
type: string
quaternion:
type: string
retry:
default: 10
type: string
speed:
default: 1
type: string
target_link:
type: string
required:
- move_group
- position
- quaternion
type: object
result: {}
required:
- goal
title: moveit_task参数
type: object
type: UniLabJsonCommand
auto-pick_and_place:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: pick_and_place的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: pick_and_place参数
type: object
type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: []
result: {}
schema:
description: post_init的参数schema
properties:
feedback: {}
goal:
properties:
ros_node:
type: string
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
auto-resource_manager:
feedback: {}
goal: {}
goal_default:
parent_link: null
resource: null
handles: []
result: {}
schema:
description: resource_manager的参数schema
properties:
feedback: {}
goal:
properties:
parent_link:
type: string
resource:
type: string
required:
- resource
- parent_link
type: object
result: {}
required:
- goal
title: resource_manager参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_status:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_status的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_status参数
type: object
type: UniLabJsonCommand
auto-wait_for_resource_action:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_resource_action的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_resource_action参数
type: object
type: UniLabJsonCommand
pick_and_place:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_position:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_status:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
status_types: {}
type: python
description: Toyo XYZ
handles: []
icon: ''
init_param_schema:
config:
properties:
device_config:
type: string
joint_poses:
type: string
moveit_type:
type: string
rotation:
type: string
required:
- moveit_type
- joint_poses
type: object
data:
properties: {}
required: []
type: object
model:
mesh: toyo_xyz
type: device

View File

@@ -1,73 +1,451 @@
separator.homemade:
description: Separator device with homemade grbl controller
class:
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
type: python
status_types:
sensordata: Float64
status: String
action_value_mappings:
stir:
type: Stir
goal:
stir_time: stir_time,
stir_speed: stir_speed
settling_time: settling_time
feedback:
status: status
result:
success: success
valve_open_cmd:
type: SendCmd
goal:
command: command
feedback:
status: status
result":
success: success
schema:
type: object
properties:
status:
type: string
description: The status of the device
sensordata:
type: number
description: 电导传感器数据
required:
- status
- sensordata
additionalProperties: false
rotavap.one:
description: Rotavap device
class:
module: unilabos.devices.rotavap.rotavap_one:RotavapOne
type: python
status_types:
pump_time: Float64
rotate_time: Float64
action_value_mappings:
auto-cmd_write:
feedback: {}
goal: {}
goal_default:
cmd: null
handles: []
result: {}
schema:
description: cmd_write的参数schema
properties:
feedback: {}
goal:
properties:
cmd:
type: string
required:
- cmd
type: object
result: {}
required:
- goal
title: cmd_write参数
type: object
type: UniLabJsonCommand
auto-main_loop:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: main_loop的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: main_loop参数
type: object
type: UniLabJsonCommand
auto-set_pump_time:
feedback: {}
goal: {}
goal_default:
time: null
handles: []
result: {}
schema:
description: set_pump_time的参数schema
properties:
feedback: {}
goal:
properties:
time:
type: string
required:
- time
type: object
result: {}
required:
- goal
title: set_pump_time参数
type: object
type: UniLabJsonCommand
auto-set_rotate_time:
feedback: {}
goal: {}
goal_default:
time: null
handles: []
result: {}
schema:
description: set_rotate_time的参数schema
properties:
feedback: {}
goal:
properties:
time:
type: string
required:
- time
type: object
result: {}
required:
- goal
title: set_rotate_time参数
type: object
type: UniLabJsonCommand
auto-set_timer:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_timer的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_timer参数
type: object
type: UniLabJsonCommand
set_timer:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result:
success: success
schema:
type: object
properties:
temperature:
type: number
description: 旋蒸水浴温度
pump_time:
type: number
description: The pump time of the device
rotate_time:
type: number
description: The rotate time of the device
required:
- pump_time
- rotate_time
additionalProperties: false
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.rotavap.rotavap_one:RotavapOne
status_types: {}
type: python
description: Rotavap device
handles: []
icon: ''
init_param_schema:
config:
properties:
port:
type: string
rate:
default: 9600
type: string
required:
- port
type: object
data:
properties: {}
required: []
type: object
separator.homemade:
class:
action_value_mappings:
auto-read_sensor_loop:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: read_sensor_loop的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: read_sensor_loop参数
type: object
type: UniLabJsonCommand
auto-stir:
feedback: {}
goal: {}
goal_default:
settling_time: 10
stir_speed: 300
stir_time: 10
handles: []
result: {}
schema:
description: stir的参数schema
properties:
feedback: {}
goal:
properties:
settling_time:
default: 10
type: number
stir_speed:
default: 300
type: number
stir_time:
default: 10
type: number
required: []
type: object
result: {}
required:
- goal
title: stir参数
type: object
type: UniLabJsonCommand
auto-valve_open:
feedback: {}
goal: {}
goal_default:
condition: null
value: null
handles: []
result: {}
schema:
description: valve_open的参数schema
properties:
feedback: {}
goal:
properties:
condition:
type: string
value:
type: string
required:
- condition
- value
type: object
result: {}
required:
- goal
title: valve_open参数
type: object
type: UniLabJsonCommand
auto-valve_open_cmd:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: valve_open_cmd的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: valve_open_cmd参数
type: object
type: UniLabJsonCommand
auto-write:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: write的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: write参数
type: object
type: UniLabJsonCommand
stir:
feedback:
status: status
goal:
settling_time: settling_time
stir_speed: stir_speed
stir_time: stir_time,
goal_default:
settling_time: 0.0
stir_speed: 0.0
stir_time: 0.0
handles: []
result:
success: success
schema:
description: ROS Action Stir 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: Stir_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
settling_time:
type: number
stir_speed:
type: number
stir_time:
type: number
required:
- stir_time
- stir_speed
- settling_time
title: Stir_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: Stir_Result
type: object
required:
- goal
title: Stir
type: object
type: Stir
valve_open_cmd:
feedback:
status: status
goal:
command: command
goal_default:
command: ''
handles: []
result":
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
status_types: {}
type: python
description: Separator device with homemade grbl controller
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate_executor:
default: 115200
type: integer
baudrate_sensor:
default: 115200
type: integer
port_executor:
type: string
port_sensor:
type: string
required:
- port_executor
- port_sensor
type: object
data:
properties: {}
required: []
type: object

View File

@@ -1,83 +1,877 @@
syringe_pump_with_valve.runze:
description: Runze Syringe pump with valve
solenoid_valve:
class:
module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-is_closed:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_closed的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_closed参数
type: object
type: UniLabJsonCommand
auto-is_open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_open参数
type: object
type: UniLabJsonCommand
auto-open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: open参数
type: object
type: UniLabJsonCommand
auto-read_data:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: read_data的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: read_data参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
auto-set_valve_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_valve_position的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: string
required:
- position
type: object
result: {}
required:
- goal
title: set_valve_position参数
type: object
type: UniLabJsonCommand
set_valve_position:
feedback: {}
goal:
string: position
goal_default:
string: ''
handles: []
result: {}
schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
status_types:
status: str
valve_position: str
type: python
description: Solenoid valve
handles: []
icon: ''
init_param_schema:
config:
properties:
io_device_port:
type: string
required:
- io_device_port
type: object
data:
properties:
status:
type: string
valve_position:
type: string
required:
- status
- valve_position
type: object
solenoid_valve.mock:
class:
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-is_closed:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_closed的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_closed参数
type: object
type: UniLabJsonCommand
auto-is_open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_open参数
type: object
type: UniLabJsonCommand
auto-open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: open参数
type: object
type: UniLabJsonCommand
auto-set_valve_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_valve_position的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: string
required:
- position
type: object
result: {}
required:
- goal
title: set_valve_position参数
type: object
type: UniLabJsonCommand
close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock
status_types:
status: str
valve_position: str
type: python
description: Mock solenoid valve
handles:
- data_type: fluid
handler_key: in
io_type: target
label: in
side: NORTH
- data_type: fluid
handler_key: out
io_type: source
label: out
side: SOUTH
icon: ''
init_param_schema:
config:
properties:
port:
default: COM6
type: string
required: []
type: object
data:
properties:
status:
type: string
valve_position:
type: string
required:
- status
- valve_position
type: object
syringe_pump_with_valve.runze:
class:
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-initialize:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: initialize的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: initialize参数
type: object
type: UniLabJsonCommand
auto-pull_plunger:
feedback: {}
goal: {}
goal_default:
volume: null
handles: []
result: {}
schema:
description: pull_plunger的参数schema
properties:
feedback: {}
goal:
properties:
volume:
type: number
required:
- volume
type: object
result: {}
required:
- goal
title: pull_plunger参数
type: object
type: UniLabJsonCommand
auto-push_plunger:
feedback: {}
goal: {}
goal_default:
volume: null
handles: []
result: {}
schema:
description: push_plunger的参数schema
properties:
feedback: {}
goal:
properties:
volume:
type: number
required:
- volume
type: object
result: {}
required:
- goal
title: push_plunger参数
type: object
type: UniLabJsonCommand
auto-query_aux_input_status_1:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_aux_input_status_1的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_aux_input_status_1参数
type: object
type: UniLabJsonCommand
auto-query_aux_input_status_2:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_aux_input_status_2的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_aux_input_status_2参数
type: object
type: UniLabJsonCommand
auto-query_backlash_position:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_backlash_position的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_backlash_position参数
type: object
type: UniLabJsonCommand
auto-query_command_buffer_status:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_command_buffer_status的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_command_buffer_status参数
type: object
type: UniLabJsonCommand
auto-query_software_version:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_software_version的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_software_version参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
full_command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
full_command:
type: string
required:
- full_command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
auto-set_baudrate:
feedback: {}
goal: {}
goal_default:
baudrate: null
handles: []
result: {}
schema:
description: set_baudrate的参数schema
properties:
feedback: {}
goal:
properties:
baudrate:
type: string
required:
- baudrate
type: object
result: {}
required:
- goal
title: set_baudrate参数
type: object
type: UniLabJsonCommand
auto-set_max_velocity:
feedback: {}
goal: {}
goal_default:
velocity: null
handles: []
result: {}
schema:
description: set_max_velocity的参数schema
properties:
feedback: {}
goal:
properties:
velocity:
type: number
required:
- velocity
type: object
result: {}
required:
- goal
title: set_max_velocity参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
max_velocity: null
position: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties:
max_velocity:
type: number
position:
type: number
required:
- position
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_valve_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_valve_position的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: string
required:
- position
type: object
result: {}
required:
- goal
title: set_valve_position参数
type: object
type: UniLabJsonCommand
auto-set_velocity_grade:
feedback: {}
goal: {}
goal_default:
velocity: null
handles: []
result: {}
schema:
description: set_velocity_grade的参数schema
properties:
feedback: {}
goal:
properties:
velocity:
type: string
required:
- velocity
type: object
result: {}
required:
- goal
title: set_velocity_grade参数
type: object
type: UniLabJsonCommand
auto-stop_operation:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: stop_operation的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop_operation参数
type: object
type: UniLabJsonCommand
auto-wait_error:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_error的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_error参数
type: object
type: UniLabJsonCommand
hardware_interface:
name: hardware_interface
read: send_command
write: send_command
schema:
type: object
properties:
status:
type: string
description: The status of the device
position:
type: number
description: The volume of the syringe
speed_max:
type: number
description: The speed of the syringe
valve_position:
type: string
description: The position of the valve
required:
- status
- position
- valve_position
additionalProperties: false
solenoid_valve.mock:
description: Mock solenoid valve
class:
module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock
type: python
module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
status_types:
status: String
valve_position: String
action_value_mappings:
open:
type: EmptyIn
goal: {}
feedback: {}
result: {}
close:
type: EmptyIn
goal: {}
feedback: {}
result: {}
handles:
input:
- handler_key: fluid-input
label: Fluid Input
data_type: fluid
output:
- handler_key: fluid-output
label: Fluid Output
data_type: fluid
max_velocity: float
mode: int
plunger_position: String
position: float
status: str
valve_position: str
velocity_end: String
velocity_grade: String
velocity_init: String
type: python
description: Runze Syringe pump with valve
handles: []
icon: ''
init_param_schema:
type: object
properties:
port:
type: string
description: "通信端口"
default: "COM6"
required:
- port
solenoid_valve:
description: Solenoid valve
class:
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
type: python
status_types:
status: String
valve_position: String
action_value_mappings:
set_valve_position:
type: StrSingleInput
goal:
string: position
feedback: {}
result: {}
config:
properties:
address:
default: '1'
type: string
max_volume:
default: 25.0
type: number
mode:
type: string
port:
type: string
required:
- port
type: object
data:
properties:
max_velocity:
type: number
mode:
type: integer
plunger_position:
type: string
position:
type: number
status:
type: string
valve_position:
type: string
velocity_end:
type: string
velocity_grade:
type: string
velocity_init:
type: string
required:
- status
- mode
- max_velocity
- velocity_grade
- velocity_init
- velocity_end
- valve_position
- position
- plunger_position
type: object

View File

@@ -1,29 +1,132 @@
# 仙工智能底盘(知行使用)
agv.SEER:
description: SEER AGV
class:
module: unilabos.devices.agv.agv_navigator:AgvNavigator
type: python
status_types:
pose: Float64MultiArray
status: String
action_value_mappings:
auto-send:
feedback: {}
goal: {}
goal_default:
cmd: null
ex_data: ''
obj: receive_socket
handles: []
result: {}
schema:
description: send的参数schema
properties:
feedback: {}
goal:
properties:
cmd:
type: string
ex_data:
default: ''
type: string
obj:
default: receive_socket
type: string
required:
- cmd
type: object
result: {}
required:
- goal
title: send参数
type: object
type: UniLabJsonCommand
auto-send_nav_task:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_nav_task的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_nav_task参数
type: object
type: UniLabJsonCommand
send_nav_task:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result:
success: success
schema:
properties:
pose:
type: array
items:
type: number
status:
type: string
required:
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.agv.agv_navigator:AgvNavigator
status_types:
pose: list
status: str
type: python
description: SEER AGV
handles: []
icon: ''
init_param_schema:
config:
properties:
host:
type: string
required:
- host
type: object
data:
properties:
pose:
type: array
status:
type: string
required:
- pose
- status
additionalProperties: false
type: object
type: object

View File

@@ -1,37 +1,199 @@
robotic_arm.UR:
description: UR robotic arm
class:
module: unilabos.devices.agv.ur_arm_task:UrArmTask
type: python
status_types:
arm_pose: Float64MultiArray
gripper_pose: Float64
arm_status: String
gripper_status: String
action_value_mappings:
auto-arm_init:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: arm_init的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: arm_init参数
type: object
type: UniLabJsonCommand
auto-load_pose_data:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: load_pose_data的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: load_pose_data参数
type: object
type: UniLabJsonCommand
auto-load_pose_file:
feedback: {}
goal: {}
goal_default:
file: null
handles: []
result: {}
schema:
description: load_pose_file的参数schema
properties:
feedback: {}
goal:
properties:
file:
type: string
required:
- file
type: object
result: {}
required:
- goal
title: load_pose_file参数
type: object
type: UniLabJsonCommand
auto-move_pos_task:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: move_pos_task的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: move_pos_task参数
type: object
type: UniLabJsonCommand
auto-reload_pose:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: reload_pose的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: reload_pose参数
type: object
type: UniLabJsonCommand
move_pos_task:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result:
success: success
schema:
properties:
arm_pose:
type: array
items:
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.agv.ur_arm_task:UrArmTask
status_types:
arm_pose: list
arm_status: str
gripper_pose: float
gripper_status: str
type: python
description: UR robotic arm
handles: []
icon: ''
init_param_schema:
config:
properties:
host:
type: string
retry:
default: 30
type: string
required:
- host
type: object
data:
properties:
arm_pose:
type: array
arm_status:
type: string
gripper_pose:
type: number
gripper_pose:
type: number
arm_status:
type: string
description: 机械臂设备状态
gripper_status:
type: string
description: 机械爪设备状态
required:
gripper_status:
type: string
required:
- arm_pose
- gripper_pose
- arm_status
- gripper_status
additionalProperties: false
type: object
type: object

View File

@@ -1,37 +1,626 @@
gripper.mock:
description: Mock gripper
class:
module: unilabos.devices.gripper.mock:MockGripper
type: python
status_types:
position: Float64
torque: Float64
status: String
action_value_mappings:
push_to:
type: GripperCommand
goal:
command.position: position
command.max_effort: torque
feedback:
position: position
effort: torque
result:
position: position
effort: torque
gripper.misumi_rz:
description: Misumi RZ gripper
class:
module: unilabos.devices.motor:Grasp.EleGripper
type: python
status_types:
status: String
action_value_mappings:
auto-data_loop:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: data_loop的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: data_loop参数
type: object
type: UniLabJsonCommand
auto-data_reader:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: data_reader的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: data_reader参数
type: object
type: UniLabJsonCommand
auto-gripper_move:
feedback: {}
goal: {}
goal_default:
force: null
pos: null
speed: null
handles: []
result: {}
schema:
description: gripper_move的参数schema
properties:
feedback: {}
goal:
properties:
force:
type: string
pos:
type: string
speed:
type: string
required:
- pos
- speed
- force
type: object
result: {}
required:
- goal
title: gripper_move参数
type: object
type: UniLabJsonCommand
auto-init_gripper:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: init_gripper的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: init_gripper参数
type: object
type: UniLabJsonCommand
auto-modbus_crc:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: modbus_crc的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: modbus_crc参数
type: object
type: UniLabJsonCommand
auto-move_and_rotate:
feedback: {}
goal: {}
goal_default:
grasp_F: null
grasp_pos: null
grasp_v: null
spin_F: null
spin_pos: null
spin_v: null
handles: []
result: {}
schema:
description: move_and_rotate的参数schema
properties:
feedback: {}
goal:
properties:
grasp_F:
type: string
grasp_pos:
type: string
grasp_v:
type: string
spin_F:
type: string
spin_pos:
type: string
spin_v:
type: string
required:
- spin_pos
- grasp_pos
- spin_v
- grasp_v
- spin_F
- grasp_F
type: object
result: {}
required:
- goal
title: move_and_rotate参数
type: object
type: UniLabJsonCommand
auto-node_gripper_move:
feedback: {}
goal: {}
goal_default:
cmd: null
handles: []
result: {}
schema:
description: node_gripper_move的参数schema
properties:
feedback: {}
goal:
properties:
cmd:
type: string
required:
- cmd
type: object
result: {}
required:
- goal
title: node_gripper_move参数
type: object
type: UniLabJsonCommand
auto-node_rotate_move:
feedback: {}
goal: {}
goal_default:
cmd: null
handles: []
result: {}
schema:
description: node_rotate_move的参数schema
properties:
feedback: {}
goal:
properties:
cmd:
type: string
required:
- cmd
type: object
result: {}
required:
- goal
title: node_rotate_move参数
type: object
type: UniLabJsonCommand
auto-read_address:
feedback: {}
goal: {}
goal_default:
address: null
data_len: null
id: null
handles: []
result: {}
schema:
description: read_address的参数schema
properties:
feedback: {}
goal:
properties:
address:
type: string
data_len:
type: string
id:
type: string
required:
- id
- address
- data_len
type: object
result: {}
required:
- goal
title: read_address参数
type: object
type: UniLabJsonCommand
auto-rotate_move_abs:
feedback: {}
goal: {}
goal_default:
force: null
pos: null
speed: null
handles: []
result: {}
schema:
description: rotate_move_abs的参数schema
properties:
feedback: {}
goal:
properties:
force:
type: string
pos:
type: string
speed:
type: string
required:
- pos
- speed
- force
type: object
result: {}
required:
- goal
title: rotate_move_abs参数
type: object
type: UniLabJsonCommand
auto-send_cmd:
feedback: {}
goal: {}
goal_default:
address: null
data: null
fun: null
id: null
handles: []
result: {}
schema:
description: send_cmd的参数schema
properties:
feedback: {}
goal:
properties:
address:
type: string
data:
type: string
fun:
type: string
id:
type: string
required:
- id
- fun
- address
- data
type: object
result: {}
required:
- goal
title: send_cmd参数
type: object
type: UniLabJsonCommand
auto-wait_for_gripper:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_gripper的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_gripper参数
type: object
type: UniLabJsonCommand
auto-wait_for_gripper_init:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_gripper_init的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_gripper_init参数
type: object
type: UniLabJsonCommand
auto-wait_for_rotate:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_rotate的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_rotate参数
type: object
type: UniLabJsonCommand
execute_command_from_outer:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result:
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.motor.Grasp:EleGripper
status_types:
status: str
type: python
description: Misumi RZ gripper
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate:
default: 115200
type: string
id:
default: 9
type: string
port:
type: string
pos_error:
default: -11
type: string
required:
- port
type: object
data:
properties:
status:
type: string
required:
- status
type: object
gripper.mock:
class:
action_value_mappings:
auto-edit_id:
feedback: {}
goal: {}
goal_default:
params: '{}'
resource:
Gripper1: {}
wf_name: gripper_run
handles: []
result: {}
schema:
description: edit_id的参数schema
properties:
feedback: {}
goal:
properties:
params:
default: '{}'
type: string
resource:
default:
Gripper1: {}
type: object
wf_name:
default: gripper_run
type: string
required: []
type: object
result: {}
required:
- goal
title: edit_id参数
type: object
type: UniLabJsonCommand
auto-push_to:
feedback: {}
goal: {}
goal_default:
position: null
torque: null
velocity: 0.0
handles: []
result: {}
schema:
description: push_to的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: number
torque:
type: number
velocity:
default: 0.0
type: number
required:
- position
- torque
type: object
result: {}
required:
- goal
title: push_to参数
type: object
type: UniLabJsonCommand
push_to:
feedback:
effort: torque
position: position
goal:
command.max_effort: torque
command.position: position
goal_default:
command:
max_effort: 0.0
position: 0.0
handles: []
result:
effort: torque
position: position
schema:
description: ROS Action GripperCommand 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
effort:
type: number
position:
type: number
reached_goal:
type: boolean
stalled:
type: boolean
required:
- position
- effort
- stalled
- reached_goal
title: GripperCommand_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
properties:
max_effort:
type: number
position:
type: number
required:
- position
- max_effort
title: GripperCommand
type: object
required:
- command
title: GripperCommand_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
effort:
type: number
position:
type: number
reached_goal:
type: boolean
stalled:
type: boolean
required:
- position
- effort
- stalled
- reached_goal
title: GripperCommand_Result
type: object
required:
- goal
title: GripperCommand
type: object
type: GripperCommand
module: unilabos.devices.gripper.mock:MockGripper
status_types:
position: float
status: str
torque: float
velocity: float
type: python
description: Mock gripper
handles: []
icon: ''
init_param_schema:
config:
properties: {}
required: []
type: object
data:
properties:
position:
type: number
status:
type: string
torque:
type: number
velocity:
type: number
required:
- position
- velocity
- torque
- status
type: object

View File

@@ -1,57 +1,693 @@
linear_motion.grbl:
description: Grbl CNC
class:
module: unilabos.devices.cnc.grbl_sync:GrblCNC
type: python
action_value_mappings:
move_through_points: &move_through_points
type: NavigateThroughPoses
goal:
poses[].pose.position: positions[]
feedback:
current_pose.pose.position: position
navigation_time.sec: time_spent
estimated_time_remaining.sec: time_remaining
number_of_poses_remaining: pose_number_remaining
auto-initialize:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
set_spindle_speed:
type: SingleJointPosition
schema:
description: initialize的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: initialize参数
type: object
type: UniLabJsonCommand
auto-move_through_points:
feedback: {}
goal: {}
goal_default:
positions: null
handles: []
result: {}
schema:
description: move_through_points的参数schema
properties:
feedback: {}
goal:
properties:
positions:
type: array
required:
- positions
type: object
result: {}
required:
- goal
title: move_through_points参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: string
required:
- position
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_spindle_speed:
feedback: {}
goal: {}
goal_default:
max_velocity: 500
spindle_speed: null
handles: []
result: {}
schema:
description: set_spindle_speed的参数schema
properties:
feedback: {}
goal:
properties:
max_velocity:
default: 500
type: number
spindle_speed:
type: number
required:
- spindle_speed
type: object
result: {}
required:
- goal
title: set_spindle_speed参数
type: object
type: UniLabJsonCommand
auto-stop_operation:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: stop_operation的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop_operation参数
type: object
type: UniLabJsonCommand
auto-wait_error:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_error的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_error参数
type: object
type: UniLabJsonCommandAsync
move_through_points:
feedback:
current_pose.pose.position: position
estimated_time_remaining.sec: time_remaining
navigation_time.sec: time_spent
number_of_poses_remaining: pose_number_remaining
goal:
position: spindle_speed
poses[].pose.position: positions[]
goal_default:
behavior_tree: ''
poses:
- header:
frame_id: ''
stamp:
nanosec: 0
sec: 0
pose:
orientation:
w: 1.0
x: 0.0
y: 0.0
z: 0.0
position:
x: 0.0
y: 0.0
z: 0.0
handles: []
result: {}
schema:
description: ROS Action NavigateThroughPoses 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
current_pose:
properties:
header:
properties:
frame_id:
type: string
stamp:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Time
type: object
required:
- stamp
- frame_id
title: Header
type: object
pose:
properties:
orientation:
properties:
w:
type: number
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
- w
title: Quaternion
type: object
position:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point
type: object
required:
- position
- orientation
title: Pose
type: object
required:
- header
- pose
title: PoseStamped
type: object
distance_remaining:
type: number
estimated_time_remaining:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Duration
type: object
navigation_time:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Duration
type: object
number_of_poses_remaining:
maximum: 32767
minimum: -32768
type: integer
number_of_recoveries:
maximum: 32767
minimum: -32768
type: integer
required:
- current_pose
- navigation_time
- estimated_time_remaining
- number_of_recoveries
- distance_remaining
- number_of_poses_remaining
title: NavigateThroughPoses_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
behavior_tree:
type: string
poses:
items:
properties:
header:
properties:
frame_id:
type: string
stamp:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Time
type: object
required:
- stamp
- frame_id
title: Header
type: object
pose:
properties:
orientation:
properties:
w:
type: number
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
- w
title: Quaternion
type: object
position:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point
type: object
required:
- position
- orientation
title: Pose
type: object
required:
- header
- pose
title: PoseStamped
type: object
type: array
required:
- poses
- behavior_tree
title: NavigateThroughPoses_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
result:
properties: {}
required: []
title: Empty
type: object
required:
- result
title: NavigateThroughPoses_Result
type: object
required:
- goal
title: NavigateThroughPoses
type: object
type: NavigateThroughPoses
set_spindle_speed:
feedback:
position: spindle_speed
goal:
position: spindle_speed
goal_default:
max_velocity: 0.0
min_duration:
nanosec: 0
sec: 0
position: 0.0
handles: []
result: {}
schema:
type: object
properties:
position:
type: array
items:
schema:
description: ROS Action SingleJointPosition 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
error:
type: number
header:
properties:
frame_id:
type: string
stamp:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Time
type: object
required:
- stamp
- frame_id
title: Header
type: object
position:
type: number
velocity:
type: number
required:
- header
- position
- velocity
- error
title: SingleJointPosition_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
max_velocity:
type: number
min_duration:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Duration
type: object
position:
type: number
required:
- position
- min_duration
- max_velocity
title: SingleJointPosition_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties: {}
required: []
title: SingleJointPosition_Result
type: object
required:
- goal
title: SingleJointPosition
type: object
type: SingleJointPosition
module: unilabos.devices.cnc.grbl_sync:GrblCNC
status_types:
position: unilabos.messages:Point3D
spindle_speed: float
status: str
type: python
description: Grbl CNC
handles: []
icon: ''
init_param_schema:
config:
properties:
address:
default: '1'
type: string
limits:
default:
- -150
- 150
- -200
- 0
- -80
- 0
type: array
port:
type: string
required:
- port
type: object
data:
properties:
position:
type: string
spindle_speed:
type: number
description: The position of the device
spindle_speed:
type: number
description: The spindle speed of the device
required:
status:
type: string
required:
- status
- position
- spindle_speed
additionalProperties: false
type: object
motor.iCL42:
description: iCL42 motor
class:
module: unilabos.devices.motor.iCL42:iCL42Driver
type: python
status_types:
motor_position: Int64
is_executing_run: Bool
success: Bool
action_value_mappings:
auto-execute_run_motor:
feedback: {}
goal: {}
goal_default:
mode: null
position: null
velocity: null
handles: []
result: {}
schema:
description: execute_run_motor的参数schema
properties:
feedback: {}
goal:
properties:
mode:
type: string
position:
type: number
velocity:
type: integer
required:
- mode
- position
- velocity
type: object
result: {}
required:
- goal
title: execute_run_motor参数
type: object
type: UniLabJsonCommand
auto-init_device:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: init_device的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: init_device参数
type: object
type: UniLabJsonCommand
auto-run_motor:
feedback: {}
goal: {}
goal_default:
mode: null
position: null
velocity: null
handles: []
result: {}
schema:
description: run_motor的参数schema
properties:
feedback: {}
goal:
properties:
mode:
type: string
position:
type: number
velocity:
type: integer
required:
- mode
- position
- velocity
type: object
result: {}
required:
- goal
title: run_motor参数
type: object
type: UniLabJsonCommand
execute_command_from_outer:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result:
success: success
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.motor.iCL42:iCL42Driver
status_types:
is_executing_run: bool
motor_position: int
success: bool
type: python
description: iCL42 motor
handles: []
icon: ''
init_param_schema:
config:
properties:
device_address:
default: 1
type: integer
device_com:
default: COM9
type: string
required: []
type: object
data:
properties:
is_executing_run:
type: boolean
motor_position:
type: integer
success:
type: boolean
required:
- motor_position
- is_executing_run
- success
type: object

View File

@@ -1,5 +1,311 @@
lh_joint_publisher:
class:
action_value_mappings:
auto-check_tf_update_actions:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: check_tf_update_actions的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_tf_update_actions参数
type: object
type: UniLabJsonCommand
auto-find_resource_parent:
feedback: {}
goal: {}
goal_default:
resource_id: null
handles: []
result: {}
schema:
description: find_resource_parent的参数schema
properties:
feedback: {}
goal:
properties:
resource_id:
type: string
required:
- resource_id
type: object
result: {}
required:
- goal
title: find_resource_parent参数
type: object
type: UniLabJsonCommand
auto-inverse_kinematics:
feedback: {}
goal: {}
goal_default:
parent_id: null
x: null
x_joint: null
y: null
y_joint: null
z: null
z_joint: null
handles: []
result: {}
schema:
description: inverse_kinematics的参数schema
properties:
feedback: {}
goal:
properties:
parent_id:
type: string
x:
type: string
x_joint:
type: object
y:
type: string
y_joint:
type: object
z:
type: string
z_joint:
type: object
required:
- x
- y
- z
- parent_id
- x_joint
- y_joint
- z_joint
type: object
result: {}
required:
- goal
title: inverse_kinematics参数
type: object
type: UniLabJsonCommand
auto-lh_joint_action_callback:
feedback: {}
goal: {}
goal_default:
goal_handle: null
handles: []
result: {}
schema:
description: lh_joint_action_callback的参数schema
properties:
feedback: {}
goal:
properties:
goal_handle:
type: string
required:
- goal_handle
type: object
result: {}
required:
- goal
title: lh_joint_action_callback参数
type: object
type: UniLabJsonCommand
auto-lh_joint_pub_callback:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: lh_joint_pub_callback的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: lh_joint_pub_callback参数
type: object
type: UniLabJsonCommand
auto-move_joints:
feedback: {}
goal: {}
goal_default:
option: null
resource_names: null
speed: 0.1
x: null
x_joint: null
y: null
y_joint: null
z: null
z_joint: null
handles: []
result: {}
schema:
description: move_joints的参数schema
properties:
feedback: {}
goal:
properties:
option:
type: string
resource_names:
type: string
speed:
default: 0.1
type: string
x:
type: string
x_joint:
type: string
y:
type: string
y_joint:
type: string
z:
type: string
z_joint:
type: string
required:
- resource_names
- x
- y
- z
- option
type: object
result: {}
required:
- goal
title: move_joints参数
type: object
type: UniLabJsonCommand
auto-move_to:
feedback: {}
goal: {}
goal_default:
joint_positions: null
parent_id: null
speed: null
handles: []
result: {}
schema:
description: move_to的参数schema
properties:
feedback: {}
goal:
properties:
joint_positions:
type: string
parent_id:
type: string
speed:
type: string
required:
- joint_positions
- speed
- parent_id
type: object
result: {}
required:
- goal
title: move_to参数
type: object
type: UniLabJsonCommand
auto-resource_move:
feedback: {}
goal: {}
goal_default:
channels: null
link_name: null
resource_id: null
handles: []
result: {}
schema:
description: resource_move的参数schema
properties:
feedback: {}
goal:
properties:
channels:
type: array
link_name:
type: string
resource_id:
type: string
required:
- resource_id
- link_name
- channels
type: object
result: {}
required:
- goal
title: resource_move参数
type: object
type: UniLabJsonCommand
auto-send_resource_action:
feedback: {}
goal: {}
goal_default:
link_name: null
resource_id_list: null
handles: []
result: {}
schema:
description: send_resource_action的参数schema
properties:
feedback: {}
goal:
properties:
link_name:
type: string
resource_id_list:
type: array
required:
- resource_id_list
- link_name
type: object
result: {}
required:
- goal
title: send_resource_action参数
type: object
type: UniLabJsonCommand
module: unilabos.devices.ros_dev.liquid_handler_joint_publisher:LiquidHandlerJointPublisher
status_types: {}
type: ros2
description: ''
handles: []
icon: ''
init_param_schema:
config:
properties:
device_id:
default: lh_joint_publisher
type: string
rate:
default: 50
type: string
resource_tracker:
type: string
resources_config:
type: array
required:
- resources_config
- resource_tracker
type: object
data:
properties: {}
required: []
type: object

View File

@@ -1,65 +1,792 @@
heaterstirrer.dalong:
description: DaLong heater stirrer
chiller:
class:
module: unilabos.devices.heaterstirrer.dalong:HeaterStirrer_DaLong
type: python
status_types:
temp: Float64
temp_warning: Float64
stir_speed: Float64
action_value_mappings:
set_temp_warning:
type: SendCmd
goal:
command: temp
auto-build_modbus_frame:
feedback: {}
goal: {}
goal_default:
device_address: null
function_code: null
register_address: null
value: null
handles: []
result: {}
schema:
description: build_modbus_frame的参数schema
properties:
feedback: {}
goal:
properties:
device_address:
type: integer
function_code:
type: integer
register_address:
type: integer
value:
type: integer
required:
- device_address
- function_code
- register_address
- value
type: object
result: {}
required:
- goal
title: build_modbus_frame参数
type: object
type: UniLabJsonCommand
auto-convert_temperature_to_modbus_value:
feedback: {}
goal: {}
goal_default:
decimal_points: 1
temperature: null
handles: []
result: {}
schema:
description: convert_temperature_to_modbus_value的参数schema
properties:
feedback: {}
goal:
properties:
decimal_points:
default: 1
type: integer
temperature:
type: number
required:
- temperature
type: object
result: {}
required:
- goal
title: convert_temperature_to_modbus_value参数
type: object
type: UniLabJsonCommand
auto-modbus_crc:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: modbus_crc的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: modbus_crc参数
type: object
type: UniLabJsonCommand
auto-set_temperature:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_temperature的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_temperature参数
type: object
type: UniLabJsonCommand
auto-stop:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: stop的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop参数
type: object
type: UniLabJsonCommand
set_temperature:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result:
success: success
set_temp_target:
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
goal:
command: temp
module: unilabos.devices.temperature.chiller:Chiller
status_types: {}
type: python
description: Chiller
handles: []
icon: ''
init_param_schema:
config:
properties:
port:
type: string
rate:
default: 9600
type: string
required:
- port
type: object
data:
properties: {}
required: []
type: object
heaterstirrer.dalong:
class:
action_value_mappings:
auto-close:
feedback: {}
result:
success: success
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-heatchill:
feedback: {}
goal: {}
goal_default:
purpose: reaction
stir: true
stir_speed: 300
temp: null
time: 3600
vessel: null
handles: []
result: {}
schema:
description: heatchill的参数schema
properties:
feedback: {}
goal:
properties:
purpose:
default: reaction
type: string
stir:
default: true
type: boolean
stir_speed:
default: 300
type: number
temp:
type: number
time:
default: 3600
type: number
vessel:
type: string
required:
- vessel
- temp
type: object
result: {}
required:
- goal
title: heatchill参数
type: object
type: UniLabJsonCommand
auto-set_stir_speed:
feedback: {}
goal: {}
goal_default:
speed: null
handles: []
result: {}
schema:
description: set_stir_speed的参数schema
properties:
feedback: {}
goal:
properties:
speed:
type: number
required:
- speed
type: object
result: {}
required:
- goal
title: set_stir_speed参数
type: object
type: UniLabJsonCommand
auto-set_temp_inner:
feedback: {}
goal: {}
goal_default:
temp: null
type: warning
handles: []
result: {}
schema:
description: set_temp_inner的参数schema
properties:
feedback: {}
goal:
properties:
temp:
type: number
type:
default: warning
type: string
required:
- temp
type: object
result: {}
required:
- goal
title: set_temp_inner参数
type: object
type: UniLabJsonCommand
auto-set_temp_target:
feedback: {}
goal: {}
goal_default:
temp: null
handles: []
result: {}
schema:
description: set_temp_target的参数schema
properties:
feedback: {}
goal:
properties:
temp:
type: string
required:
- temp
type: object
result: {}
required:
- goal
title: set_temp_target参数
type: object
type: UniLabJsonCommand
auto-set_temp_warning:
feedback: {}
goal: {}
goal_default:
temp: null
handles: []
result: {}
schema:
description: set_temp_warning的参数schema
properties:
feedback: {}
goal:
properties:
temp:
type: string
required:
- temp
type: object
result: {}
required:
- goal
title: set_temp_warning参数
type: object
type: UniLabJsonCommand
heatchill:
type: HeatChill
goal:
vessel: vessel
temp: temp
time: time
purpose: purpose
feedback:
status: status
result:
success: success
chiller:
description: Chiller
class:
module: unilabos.devices.temperature.chiller:Chiller
type: python
action_value_mappings:
set_temperature:
type: SendCmd
goal:
command: command
feedback: {}
purpose: purpose
temp: temp
time: time
vessel: vessel
goal_default:
purpose: ''
stir: false
stir_speed: 0.0
temp: 0.0
time: 0.0
vessel: ''
handles: []
result:
success: success
tempsensor:
description: Temperature sensor
class:
module: unilabos.devices.temperature.sensor_node:TempSensorNode
type: python
schema:
description: ROS Action HeatChill 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: HeatChill_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
purpose:
type: string
stir:
type: boolean
stir_speed:
type: number
temp:
type: number
time:
type: number
vessel:
type: string
required:
- vessel
- temp
- time
- stir
- stir_speed
- purpose
title: HeatChill_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: HeatChill_Result
type: object
required:
- goal
title: HeatChill
type: object
type: HeatChill
set_temp_target:
feedback: {}
goal:
command: temp
goal_default:
command: ''
handles: []
result:
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_temp_warning:
feedback: {}
goal:
command: temp
goal_default:
command: ''
handles: []
result:
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.heaterstirrer.dalong:HeaterStirrer_DaLong
status_types:
value: Float64
warning: Float64
status: str
stir_speed: float
temp: float
temp_target: float
temp_warning: float
type: python
description: DaLong heater stirrer
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate:
default: 9600
type: integer
port:
default: COM6
type: string
temp_warning:
default: 50.0
type: string
required: []
type: object
data:
properties:
status:
type: string
stir_speed:
type: number
temp:
type: number
temp_target:
type: number
temp_warning:
type: number
required:
- status
- stir_speed
- temp
- temp_warning
- temp_target
type: object
tempsensor:
class:
action_value_mappings:
auto-build_modbus_request:
feedback: {}
goal: {}
goal_default:
device_id: null
function_code: null
register_address: null
register_count: null
handles: []
result: {}
schema:
description: build_modbus_request的参数schema
properties:
feedback: {}
goal:
properties:
device_id:
type: string
function_code:
type: string
register_address:
type: string
register_count:
type: string
required:
- device_id
- function_code
- register_address
- register_count
type: object
result: {}
required:
- goal
title: build_modbus_request参数
type: object
type: UniLabJsonCommand
auto-calculate_crc:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: calculate_crc的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: calculate_crc参数
type: object
type: UniLabJsonCommand
auto-read_modbus_response:
feedback: {}
goal: {}
goal_default:
response: null
handles: []
result: {}
schema:
description: read_modbus_response的参数schema
properties:
feedback: {}
goal:
properties:
response:
type: string
required:
- response
type: object
result: {}
required:
- goal
title: read_modbus_response参数
type: object
type: UniLabJsonCommand
auto-send_prototype_command:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_prototype_command的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_prototype_command参数
type: object
type: UniLabJsonCommand
auto-set_warning:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_warning的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_warning参数
type: object
type: UniLabJsonCommand
set_warning:
type: SendCmd
feedback: {}
goal:
command: command
feedback: {}
goal_default:
command: ''
handles: []
result:
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.temperature.sensor_node:TempSensorNode
status_types:
value: float
type: python
description: Temperature sensor
handles: []
icon: ''
init_param_schema:
config:
properties:
address:
type: string
baudrate:
default: 9600
type: string
port:
type: string
warning:
type: string
required:
- port
- warning
- address
type: object
data:
properties:
value:
type: number
required:
- value
type: object

View File

@@ -1,97 +1,496 @@
vacuum_pump.mock:
description: Mock vacuum pump
class:
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
type: python
status_types:
status: String
action_value_mappings:
open:
type: EmptyIn
goal: {}
feedback: {}
result: {}
close:
type: EmptyIn
goal: {}
feedback: {}
result: {}
set_status:
type: StrSingleInput
goal:
string: string
feedback: {}
result: {}
handles:
input:
- handler_key: fluid-input
label: Fluid Input
data_type: fluid
io_type: target
data_source: handle
data_key: fluid_in
output:
- handler_key: fluid-output
label: Fluid Output
data_type: fluid
io_type: source
data_source: executor
data_key: fluid_out
init_param_schema:
type: object
properties:
port:
type: string
description: "通信端口"
default: "COM6"
required:
- port
gas_source.mock:
description: Mock gas source
class:
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
type: python
status_types:
status: String
action_value_mappings:
open:
type: EmptyIn
goal: {}
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-is_closed:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_closed的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_closed参数
type: object
type: UniLabJsonCommand
auto-is_open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_open参数
type: object
type: UniLabJsonCommand
auto-open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: open参数
type: object
type: UniLabJsonCommand
auto-set_status:
feedback: {}
goal: {}
goal_default:
string: null
handles: []
result: {}
schema:
description: set_status的参数schema
properties:
feedback: {}
goal:
properties:
string:
type: string
required:
- string
type: object
result: {}
required:
- goal
title: set_status参数
type: object
type: UniLabJsonCommand
close:
type: EmptyIn
goal: {}
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
set_status:
type: StrSingleInput
feedback: {}
goal:
string: string
feedback: {}
goal_default:
string: ''
handles: []
result: {}
schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
status_types:
status: str
type: python
description: Mock gas source
handles:
input:
- handler_key: fluid-input
label: Fluid Input
data_type: fluid
io_type: target
data_source: handle
data_key: fluid_in
output:
- handler_key: fluid-output
label: Fluid Output
data_type: fluid
io_type: source
data_source: executor
data_key: fluid_out
- data_key: fluid_out
data_source: executor
data_type: fluid
handler_key: out
io_type: source
label: out
icon: ''
init_param_schema:
type: object
properties:
port:
type: string
description: "通信端口"
default: "COM6"
required:
- port
config:
properties:
port:
default: COM6
type: string
required: []
type: object
data:
properties:
status:
type: string
required:
- status
type: object
vacuum_pump.mock:
class:
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-is_closed:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_closed的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_closed参数
type: object
type: UniLabJsonCommand
auto-is_open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_open参数
type: object
type: UniLabJsonCommand
auto-open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: open参数
type: object
type: UniLabJsonCommand
auto-set_status:
feedback: {}
goal: {}
goal_default:
string: null
handles: []
result: {}
schema:
description: set_status的参数schema
properties:
feedback: {}
goal:
properties:
string:
type: string
required:
- string
type: object
result: {}
required:
- goal
title: set_status参数
type: object
type: UniLabJsonCommand
close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
set_status:
feedback: {}
goal:
string: string
goal_default:
string: ''
handles: []
result: {}
schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
status_types:
status: str
type: python
description: Mock vacuum pump
handles:
- data_key: fluid_in
data_source: handle
data_type: fluid
handler_key: out
io_type: source
label: out
icon: ''
init_param_schema:
config:
properties:
port:
default: COM6
type: string
required: []
type: object
data:
properties:
status:
type: string
required:
- status
type: object

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,232 @@
zhida_hplc:
description: Zhida HPLC
class:
module: unilabos.devices.zhida_hplc.zhida:ZhidaClient
type: python
status_types:
status: String
action_value_mappings:
abort:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
auto-abort:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: abort的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: abort参数
type: object
type: UniLabJsonCommand
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-connect:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: connect的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: connect参数
type: object
type: UniLabJsonCommand
auto-start:
feedback: {}
goal: {}
goal_default:
text: null
handles: []
result: {}
schema:
description: start的参数schema
properties:
feedback: {}
goal:
properties:
text:
type: string
required:
- text
type: object
result: {}
required:
- goal
title: start参数
type: object
type: UniLabJsonCommand
get_methods:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
start:
type: StrSingleInput
feedback: {}
goal:
string: string
feedback: {}
goal_default:
string: ''
handles: []
result: {}
abort:
type: EmptyIn
goal: {}
feedback: {}
result: {}
get_methods:
type: EmptyIn
goal: {}
feedback: {}
result: {}
schema:
properties: {}
schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
module: unilabos.devices.zhida_hplc.zhida:ZhidaClient
status_types:
methods: dict
status: dict
type: python
description: Zhida HPLC
handles: []
icon: ''
init_param_schema:
config:
properties:
host:
default: 192.168.1.47
type: string
port:
default: 5792
type: string
timeout:
default: 10.0
type: string
required: []
type: object
data:
properties:
methods:
type: object
status:
type: object
required:
- status
- methods
type: object

View File

@@ -1,14 +1,17 @@
import copy
import io
import os
import sys
from pathlib import Path
from typing import Any
from typing import Any, Dict, List
import yaml
from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema
from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema, String
from unilabos.utils import logger
from unilabos.utils.decorator import singleton
from unilabos.utils.import_manager import get_enhanced_class_info
from unilabos.utils.type_check import NoAliasDumper
DEFAULT_PATHS = [Path(__file__).absolute().parent]
@@ -32,7 +35,7 @@ class Registry:
# 其他状态变量
# self.is_host_mode = False # 移至BasicConfig中
def setup(self):
def setup(self, complete_registry=False):
# 检查是否已调用过setup
if self._setup_called:
logger.critical("[UniLab Registry] setup方法已被调用过不允许多次调用")
@@ -86,13 +89,15 @@ class Registry:
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal))
),
"handles": {
"output": [{
"handler_key": "labware",
"label": "Labware",
"data_type": "resource",
"data_source": "handle",
"data_key": "liquid"
}]
"output": [
{
"handler_key": "labware",
"label": "Labware",
"data_type": "resource",
"data_source": "handle",
"data_key": "liquid",
}
]
},
},
"test_latency": {
@@ -110,7 +115,6 @@ class Registry:
"registry_type": "device",
"handles": [],
"init_param_schema": {},
"schema": {"properties": {}, "additionalProperties": False, "type": "object"},
"file_path": "/",
}
}
@@ -121,13 +125,13 @@ class Registry:
sys_path = path.parent
logger.debug(f"[UniLab Registry] Path {i+1}/{len(self.registry_paths)}: {sys_path}")
sys.path.append(str(sys_path))
self.load_device_types(path)
self.load_resource_types(path)
self.load_device_types(path, complete_registry)
self.load_resource_types(path, complete_registry)
logger.info("[UniLab Registry] 注册表设置完成")
# 标记setup已被调用
self._setup_called = True
def load_resource_types(self, path: os.PathLike):
def load_resource_types(self, path: os.PathLike, complete_registry: bool):
abs_path = Path(path).absolute()
resource_path = abs_path / "resources"
files = list(resource_path.glob("*/*.yaml"))
@@ -176,7 +180,14 @@ class Registry:
if not type_name or type_name == "":
logger.warning(f"[UniLab Registry] 设备 {device_id}{field_name} 类型为空,跳过替换")
return type_name
if "." in type_name:
convert_manager = { # 将python基本对象转为ros2基本对象
"str": "String",
"bool": "Bool",
"int": "Int64",
"float": "Float64",
}
type_name = convert_manager.get(type_name, type_name) # 替换为ROS2类型
if ":" in type_name:
type_class = msg_converter_manager.get_class(type_name)
else:
type_class = msg_converter_manager.search_class(type_name)
@@ -186,7 +197,98 @@ class Registry:
logger.error(f"[UniLab Registry] 无法找到类型 '{type_name}' 用于设备 {device_id}{field_name}")
sys.exit(1)
def load_device_types(self, path: os.PathLike):
def _generate_schema_from_info(
self,
param_name: str,
param_type: str,
param_default: Any,
) -> Dict[str, Any]:
"""
根据参数信息生成JSON Schema
"""
prop_schema = {}
# 根据类型设置schema FIXME 不完整
if param_type:
param_type_lower = param_type.lower()
if param_type_lower in ["str", "string"]:
prop_schema["type"] = "string"
elif param_type_lower in ["int", "integer"]:
prop_schema["type"] = "integer"
elif param_type_lower in ["float", "number"]:
prop_schema["type"] = "number"
elif param_type_lower in ["bool", "boolean"]:
prop_schema["type"] = "boolean"
elif param_type_lower in ["list", "array"]:
prop_schema["type"] = "array"
elif param_type_lower in ["dict", "object"]:
prop_schema["type"] = "object"
else:
# 默认为字符串类型
prop_schema["type"] = "string"
else:
# 如果没有类型信息,默认为字符串
prop_schema["type"] = "string"
# 设置默认值
if param_default is not None:
prop_schema["default"] = param_default
return prop_schema
def _generate_status_types_schema(self, status_types: Dict[str, Any]) -> Dict[str, Any]:
"""
根据状态类型生成JSON Schema
"""
status_schema = {
"type": "object",
"properties": {},
"required": [],
}
for status_name, status_type in status_types.items():
status_schema["properties"][status_name] = self._generate_schema_from_info(
status_name, status_type["return_type"], None
)
status_schema["required"].append(status_name)
return status_schema
def _generate_unilab_json_command_schema(
self, method_args: List[Dict[str, Any]], method_name: str
) -> Dict[str, Any]:
"""
根据UniLabJsonCommand方法信息生成JSON Schema暂不支持嵌套类型
Args:
method_args: 方法信息字典包含args等
method_name: 方法名称
Returns:
JSON Schema格式的参数schema
"""
schema = {
"type": "object",
"properties": {},
"required": [],
}
for arg_info in method_args:
param_name = arg_info.get("name", "")
param_type = arg_info.get("type", "")
param_default = arg_info.get("default")
param_required = arg_info.get("required", True)
schema["properties"][param_name] = self._generate_schema_from_info(
param_name, param_type, param_default
)
if param_required:
schema["required"].append(param_name)
return {
"title": f"{method_name}参数",
"description": f"{method_name}的参数schema",
"type": "object",
"properties": {"goal": schema, "feedback": {}, "result": {}},
"required": ["goal"],
}
def load_device_types(self, path: os.PathLike, complete_registry: bool):
abs_path = Path(path).absolute()
devices_path = abs_path / "devices"
device_comms_path = abs_path / "device_comms"
@@ -199,12 +301,18 @@ class Registry:
from unilabos.app.web.utils.action_utils import get_yaml_from_goal_type
for i, file in enumerate(files):
data = yaml.safe_load(open(file, encoding="utf-8"))
with open(file, encoding="utf-8", mode="r") as f:
data = yaml.safe_load(io.StringIO(f.read()))
complete_data = {}
action_str_type_mapping = {
"UniLabJsonCommand": "UniLabJsonCommand",
"UniLabJsonCommandAsync": "UniLabJsonCommandAsync",
}
status_str_type_mapping = {}
if data:
# 在添加到注册表前处理类型替换
for device_id, device_config in data.items():
# 添加文件路径信息 - 使用规范化的完整文件路径
device_config["file_path"] = str(file.absolute()).replace("\\", "/")
if "description" not in device_config:
device_config["description"] = ""
if "icon" not in device_config:
@@ -213,42 +321,128 @@ class Registry:
device_config["handles"] = []
if "init_param_schema" not in device_config:
device_config["init_param_schema"] = {}
device_config["registry_type"] = "device"
if "class" in device_config:
# 处理状态类型
if "status_types" in device_config["class"]:
for status_name, status_type in device_config["class"]["status_types"].items():
device_config["class"]["status_types"][status_name] = self._replace_type_with_class(
status_type, device_id, f"状态 {status_name}"
)
if "status_types" not in device_config["class"]:
device_config["class"]["status_types"] = {}
if "action_value_mappings" not in device_config["class"]:
device_config["class"]["action_value_mappings"] = {}
enhanced_info = {}
if complete_registry:
device_config["class"]["status_types"].clear()
enhanced_info = get_enhanced_class_info(device_config["class"]["module"], use_dynamic=True)
device_config["class"]["status_types"].update(
{k: v["return_type"] for k, v in enhanced_info["status_methods"].items()}
)
for status_name, status_type in device_config["class"]["status_types"].items():
if status_type in ["Any", "None", "Unknown"]:
status_type = "String" # 替换成ROS的String便于显示
device_config["class"]["status_types"][status_name] = status_type
target_type = self._replace_type_with_class(status_type, device_id, f"状态 {status_name}")
if target_type in [
dict,
list,
]: # 对于嵌套类型返回的对象,暂时处理成字符串,无法直接进行转换
target_type = String
status_str_type_mapping[status_type] = target_type
device_config["class"]["status_types"] = dict(
sorted(device_config["class"]["status_types"].items())
)
if complete_registry:
device_config["class"]["action_value_mappings"] = {
k: v
for k, v in device_config["class"]["action_value_mappings"].items()
if not k.startswith("auto-")
}
# 处理动作值映射
device_config["class"]["action_value_mappings"].update(
{
f"auto-{k}": {
"type": "UniLabJsonCommandAsync" if v["is_async"] else "UniLabJsonCommand",
"goal": {},
"feedback": {},
"result": {},
"schema": self._generate_unilab_json_command_schema(v["args"], k),
"goal_default": {i["name"]: i["default"] for i in v["args"]},
"handles": [],
}
for k, v in enhanced_info["action_methods"].items()
}
)
device_config["init_param_schema"] = {}
device_config["init_param_schema"]["config"] = self._generate_unilab_json_command_schema(
enhanced_info["init_params"], "__init__"
)["properties"]["goal"]
device_config["init_param_schema"]["data"] = self._generate_status_types_schema(
enhanced_info["status_methods"]
)
# 处理动作值映射
if "action_value_mappings" in device_config["class"]:
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
if "handles" not in action_config:
action_config["handles"] = []
if "type" in action_config:
action_config["type"] = self._replace_type_with_class(
action_config["type"], device_id, f"动作 {action_name}"
device_config.pop("schema", None)
device_config["class"]["action_value_mappings"] = dict(
sorted(device_config["class"]["action_value_mappings"].items())
)
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
if "handles" not in action_config:
action_config["handles"] = []
if "type" in action_config:
action_type_str: str = action_config["type"]
# 通过Json发放指令而不是通过特殊的ros action进行处理
if not action_type_str.startswith("UniLabJsonCommand"):
target_type = self._replace_type_with_class(
action_type_str, device_id, f"动作 {action_name}"
)
if action_config["type"] is not None:
action_str_type_mapping[action_type_str] = target_type
if target_type is not None:
action_config["goal_default"] = yaml.safe_load(
io.StringIO(get_yaml_from_goal_type(action_config["type"].Goal))
io.StringIO(get_yaml_from_goal_type(target_type.Goal))
)
action_config["schema"] = ros_action_to_json_schema(action_config["type"])
action_config["schema"] = ros_action_to_json_schema(target_type)
else:
logger.warning(
f"[UniLab Registry] 设备 {device_id} 的动作 {action_name} 类型为空,跳过替换"
)
self.device_type_registry.update(data)
for device_id in data.keys():
complete_data[device_id] = copy.deepcopy(dict(sorted(device_config.items()))) # 稍后dump到文件
for status_name, status_type in device_config["class"]["status_types"].items():
device_config["class"]["status_types"][status_name] = status_str_type_mapping[status_type]
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
action_config["type"] = action_str_type_mapping[action_config["type"]]
for additional_action in ["_execute_driver_command", "_execute_driver_command_async"]:
device_config["class"]["action_value_mappings"][additional_action] = {
"type": self._replace_type_with_class(
"StrSingleInput", device_id, f"动作 {additional_action}"
),
"goal": {"string": "string"},
"feedback": {},
"result": {},
"schema": ros_action_to_json_schema(
self._replace_type_with_class(
"StrSingleInput", device_id, f"动作 {additional_action}"
)
),
"goal_default": yaml.safe_load(
io.StringIO(
get_yaml_from_goal_type(
self._replace_type_with_class(
"StrSingleInput", device_id, f"动作 {additional_action}"
).Goal
)
)
),
"handles": [],
}
if "registry_type" not in device_config:
device_config["registry_type"] = "device"
device_config["file_path"] = str(file.absolute()).replace("\\", "/")
device_config["registry_type"] = "device"
logger.debug(
f"[UniLab Registry] Device-{current_device_number} File-{i+1}/{len(files)} Add {device_id} "
+ f"[{data[device_id].get('name', '未命名设备')}]"
)
current_device_number += 1
complete_data = dict(sorted(complete_data.items()))
complete_data = copy.deepcopy(complete_data)
with open(file, "w", encoding="utf-8") as f:
yaml.dump(complete_data, f, allow_unicode=True, default_flow_style=False, Dumper=NoAliasDumper)
self.device_type_registry.update(data)
else:
logger.debug(
f"[UniLab Registry] Device File-{i+1}/{len(files)} Not Valid YAML File: {file.absolute()}"
@@ -257,7 +451,28 @@ class Registry:
def obtain_registry_device_info(self):
devices = []
for device_id, device_info in self.device_type_registry.items():
msg = {"id": device_id, **device_info}
device_info_copy = copy.deepcopy(device_info)
if "class" in device_info_copy and "action_value_mappings" in device_info_copy["class"]:
action_mappings = device_info_copy["class"]["action_value_mappings"]
for action_name, action_config in action_mappings.items():
if "schema" in action_config and action_config["schema"]:
schema = action_config["schema"]
# 确保schema结构存在
if (
"properties" in schema
and "goal" in schema["properties"]
and "properties" in schema["properties"]["goal"]
):
schema["properties"]["goal"]["properties"] = {
"unilabos_device_id": {
"type": "string",
"default": "",
"description": "UniLabOS设备ID用于指定执行动作的具体设备实例",
},
**schema["properties"]["goal"]["properties"],
}
msg = {"id": device_id, **device_info_copy}
devices.append(msg)
return devices
@@ -273,7 +488,7 @@ class Registry:
lab_registry = Registry()
def build_registry(registry_paths=None):
def build_registry(registry_paths=None, complete_registry=False):
"""
构建或获取Registry单例实例
@@ -297,6 +512,6 @@ def build_registry(registry_paths=None):
lab_registry.registry_paths.append(path)
# 初始化注册表
lab_registry.setup()
lab_registry.setup(complete_registry)
return lab_registry

View File

@@ -0,0 +1,28 @@
container:
description: regular organic container
icon: Flask.webp
class:
module: unilabos.resources.container:RegularContainer
type: unilabos
handles:
- handler_key: top
label: top
io_type: target
data_type: fluid
side: NORTH
data_source: handle
data_key: fluid_in
- handler_key: bottom
label: bottom
io_type: source
data_type: fluid
side: SOUTH
data_source: handle
data_key: fluid_out
- handler_key: bind
label: bind
io_type: target
data_type: mechanical
side: SOUTH
data_source: handle
data_key: mechanical_port

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