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 *.graphml
unilabos/device_mesh/view_robot.rviz 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 # Currently, you need to install the `unilabos_msgs` package
# You can download the system-specific package from the Release page # 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 # Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo 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` 包 # 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装 # 可以前往 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等前置 # 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot plr_repo git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -48,8 +48,9 @@ dependencies:
- ros-humble-ros2-control - ros-humble-ros2-control
- ros-humble-robot-state-publisher - ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher - ros-humble-joint-state-publisher
# web # web and visualization
- ros-humble-rosbridge-server - ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning # geometry & motion planning
- ros-humble-tf2 - ros-humble-tf2
- ros-humble-moveit - ros-humble-moveit

View File

@@ -50,8 +50,9 @@ dependencies:
- ros-humble-ros2-control - ros-humble-ros2-control
- ros-humble-robot-state-publisher - ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher - ros-humble-joint-state-publisher
# web # web and visualization
- ros-humble-rosbridge-server - ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning # geometry & motion planning
- ros-humble-tf2 - ros-humble-tf2
- ros-humble-moveit - ros-humble-moveit

View File

@@ -6,12 +6,12 @@ channels:
dependencies: dependencies:
# Basics # Basics
- python=3.11.11 - python=3.11.11
- compilers # - compilers
- cmake # - cmake
- make # - make
- ninja # - ninja
- sphinx # - sphinx
- sphinx_rtd_theme # - sphinx_rtd_theme
# Data Visualization # Data Visualization
- numpy - numpy
- scipy - scipy
@@ -23,7 +23,7 @@ dependencies:
- pyserial - pyserial
- pyusb - pyusb
- pylibftdi - pylibftdi
- pymodbus - pymodbus==3.6.9
- python-can - python-can
- pyvisa - pyvisa
- opencv - opencv
@@ -48,8 +48,9 @@ dependencies:
- ros-humble-ros2-control - ros-humble-ros2-control
- ros-humble-robot-state-publisher - ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher - ros-humble-joint-state-publisher
# web # web and visualization
- ros-humble-rosbridge-server - ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning # geometry & motion planning
- ros-humble-tf2 - ros-humble-tf2
- ros-humble-moveit - ros-humble-moveit
@@ -61,5 +62,12 @@ dependencies:
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo # ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments # ilab equipments
# ros-humble-unilabos-msgs # ros-humble-unilabos-msgs
# driver
#- crcmod
- pip: - 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, backend: str,
devices_config: dict = {}, devices_config: dict = {},
resources_config: list = [], resources_config: list = [],
resources_edge_config: list = [],
graph=None, graph=None,
controllers_config: dict = {}, controllers_config: dict = {},
bridges=[], bridges=[],
@@ -31,7 +32,7 @@ def start_backend(
backend_thread = threading.Thread( backend_thread = threading.Thread(
target=main if not without_host else slave, 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", name="backend_thread",
daemon=True, daemon=True,
) )

View File

@@ -25,12 +25,13 @@ def job_add(req: JobAddReq) -> JobData:
if req.job_id is None: if req.job_id is None:
req.job_id = str(uuid.uuid4()) req.job_id = str(uuid.uuid4())
action_name = req.data["action"] action_name = req.data["action"]
action_kwargs = req.data["action_kwargs"] action_type = req.data.get("action_type", "LocalUnknown")
req.data['action'] = action_name action_args = req.data.get("action_kwargs", None) # 兼容老版本,后续删除
if action_name == "execute_command_from_outer": if action_args is None:
action_kwargs = {"command": json.dumps(action_kwargs)} action_args = req.data.get("action_args")
elif "command" in action_kwargs: else:
action_kwargs = action_kwargs["command"] if "command" in action_args:
action_args = action_args["command"]
# print(f"job_add:{req.device_id} {action_name} {action_kwargs}") # 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) return JobData(jobId=req.job_id)

View File

@@ -10,7 +10,7 @@ from copy import deepcopy
import yaml 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__)) 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 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(): def parse_args():
"""解析命令行参数""" """解析命令行参数"""
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.") parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
@@ -58,6 +73,11 @@ def parse_args():
action="store_true", action="store_true",
help="Slave模式下跳过等待host服务", help="Slave模式下跳过等待host服务",
) )
parser.add_argument(
"--upload_registry",
action="store_true",
help="启动unilab时同时报送注册表信息",
)
parser.add_argument( parser.add_argument(
"--config", "--config",
type=str, type=str,
@@ -97,22 +117,12 @@ def main():
# 加载配置文件优先加载config然后从env读取 # 加载配置文件优先加载config然后从env读取
config_path = args_dict.get("config") config_path = args_dict.get("config")
if config_path is None: load_config_from_file(config_path)
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)
# 设置BasicConfig参数 # 设置BasicConfig参数
BasicConfig.is_host_mode = not args_dict.get("without_host", False) BasicConfig.is_host_mode = not args_dict.get("without_host", False)
BasicConfig.slave_no_host = args_dict.get("slave_no_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 = os.popen("hostname").read().strip()
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name]) machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
BasicConfig.machine_name = machine_name BasicConfig.machine_name = machine_name
@@ -136,15 +146,16 @@ def main():
# 注册表 # 注册表
build_registry(args_dict["registry_path"]) build_registry(args_dict["registry_path"])
resource_edge_info = []
devices_and_resources = None devices_and_resources = None
if args_dict["graph"] is not None: if args_dict["graph"] is not None:
import unilabos.resources.graphio as graph_res import unilabos.resources.graphio as graph_res
graph_res.physical_setup_graph = ( if args_dict["graph"].endswith(".json"):
read_node_link_json(args_dict["graph"]) graph, data = read_node_link_json(args_dict["graph"])
if args_dict["graph"].endswith(".json") else:
else read_graphml(args_dict["graph"]) 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) devices_and_resources = dict_from_graph(graph_res.physical_setup_graph)
# args_dict["resources_config"] = initialize_resources(list(deepcopy(devices_and_resources).values())) # args_dict["resources_config"] = initialize_resources(list(deepcopy(devices_and_resources).values()))
args_dict["resources_config"] = list(devices_and_resources.values()) args_dict["resources_config"] = list(devices_and_resources.values())
@@ -185,6 +196,7 @@ def main():
signal.signal(signal.SIGTERM, _exit) signal.signal(signal.SIGTERM, _exit)
mqtt_client.start() mqtt_client.start()
args_dict["resources_mesh_config"] = {} args_dict["resources_mesh_config"] = {}
args_dict["resources_edge_config"] = resource_edge_info
# web visiualize 2D # web visiualize 2D
if args_dict["visual"] != "disable": if args_dict["visual"] != "disable":
enable_rviz = args_dict["visual"] == "rviz" enable_rviz = args_dict["visual"] == "rviz"

View File

@@ -172,12 +172,13 @@ class MQTTClient:
jobdata = {"job_id": job_id, "data": feedback_data, "status": status, "return_info": return_info} jobdata = {"job_id": job_id, "data": feedback_data, "status": status, "return_info": return_info}
self.client.publish(f"labs/{MQConfig.lab_id}/job/list/", json.dumps(jobdata), qos=2) 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: if self.mqtt_disable:
return return
address = f"labs/{MQConfig.lab_id}/registry/" address = f"labs/{MQConfig.lab_id}/registry/"
registry_data = json.dumps({device_id: device_info}, ensure_ascii=False, cls=TypeEncoder) registry_data = json.dumps({device_id: device_info}, ensure_ascii=False, cls=TypeEncoder)
self.client.publish(address, registry_data, qos=2) self.client.publish(address, registry_data, qos=2)
if print_debug:
logger.debug(f"Registry data published: address: {address}, {registry_data}") logger.debug(f"Registry data published: address: {address}, {registry_data}")
def publish_actions(self, action_id: str, action_info: dict): def publish_actions(self, action_id: str, action_info: dict):

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 self.auth = MQConfig.lab_id
info(f"HTTPClient 初始化完成: remote_addr={self.remote_addr}") 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}", f"{self.remote_addr}/lab/resource/?database_process_later={1 if database_process_later else 0}",
json=resources, json=resources,
headers={"Authorization": f"lab {self.auth}"}, headers={"Authorization": f"lab {self.auth}"},
timeout=5, timeout=100,
) )
if response.status_code != 200:
logger.error(f"添加物料失败: {response.text}")
return response return response
def resource_get(self, id: str, with_children: bool = False) -> Dict[str, Any]: 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", f"{self.remote_addr}/lab/resource/?edge_format=1",
params={"id": id, "with_children": with_children}, params={"id": id, "with_children": with_children},
headers={"Authorization": f"lab {self.auth}"}, headers={"Authorization": f"lab {self.auth}"},
timeout=5, timeout=20,
) )
return response.json() return response.json()
@@ -81,7 +103,7 @@ class HTTPClient:
f"{self.remote_addr}/lab/resource/batch_delete/", f"{self.remote_addr}/lab/resource/batch_delete/",
params={"id": id}, params={"id": id},
headers={"Authorization": f"lab {self.auth}"}, headers={"Authorization": f"lab {self.auth}"},
timeout=5, timeout=20,
) )
return response return response
@@ -99,7 +121,7 @@ class HTTPClient:
f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1", f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1",
json=resources, json=resources,
headers={"Authorization": f"lab {self.auth}"}, headers={"Authorization": f"lab {self.auth}"},
timeout=5, timeout=100,
) )
return response return response

View File

@@ -7,6 +7,7 @@ Web页面模块
import json import json
import os import os
import sys import sys
import traceback
from pathlib import Path from pathlib import Path
from typing import Dict from typing import Dict
@@ -16,9 +17,8 @@ from jinja2 import Environment, FileSystemLoader
from unilabos.config.config import BasicConfig from unilabos.config.config import BasicConfig
from unilabos.registry.registry import lab_registry 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.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.utils.type_check import TypeEncoder
from unilabos.app.web.utils.device_utils import get_registry_info from unilabos.app.web.utils.device_utils import get_registry_info
from unilabos.app.web.utils.host_utils import get_host_node_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 return html
except Exception as e: except Exception as e:
debug(traceback.format_exc())
error(f"生成状态页面时出错: {str(e)}") error(f"生成状态页面时出错: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error generating status page: {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: Returns:
str: 默认Goal参数的YAML格式字符串 str: 默认Goal参数的YAML格式字符串
""" """
if isinstance(goal_type, str):
return "{}"
if not goal_type: if not goal_type:
return "{}" return "{}"

View File

@@ -8,7 +8,12 @@ from .agv_transfer_protocol import generate_agv_transfer_protocol
from .add_protocol import generate_add_protocol from .add_protocol import generate_add_protocol
from .centrifuge_protocol import generate_centrifuge_protocol from .centrifuge_protocol import generate_centrifuge_protocol
from .filter_protocol import generate_filter_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 .stir_protocol import generate_stir_protocol, generate_start_stir_protocol, generate_stop_stir_protocol
from .transfer_protocol import generate_transfer_protocol from .transfer_protocol import generate_transfer_protocol
from .clean_vessel_protocol import generate_clean_vessel_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 .filter_through_protocol import generate_filter_through_protocol
from .run_column_protocol import generate_run_column_protocol from .run_column_protocol import generate_run_column_protocol
from .wash_solid_protocol import generate_wash_solid_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. # Define a dictionary of protocol generators.
action_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, 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, FilterProtocol: generate_filter_protocol,
FilterThroughProtocol: generate_filter_through_protocol,
HeatChillProtocol: generate_heat_chill_protocol, HeatChillProtocol: generate_heat_chill_protocol,
HeatChillStartProtocol: generate_heat_chill_start_protocol, HeatChillStartProtocol: generate_heat_chill_start_protocol,
HeatChillStopProtocol: generate_heat_chill_stop_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, StartStirProtocol: generate_start_stir_protocol,
StirProtocol: generate_stir_protocol,
StopStirProtocol: generate_stop_stir_protocol, StopStirProtocol: generate_stop_stir_protocol,
TransferProtocol: generate_transfer_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, WashSolidProtocol: generate_wash_solid_protocol,
} }

View File

@@ -1,74 +1,627 @@
import networkx as nx import networkx as nx
from typing import List, Dict, Any 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', [])
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( def generate_add_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
reagent: str, reagent: str,
volume: float, volume: float,
mass: float, mass: float = 0.0,
amount: str, amount: str = "",
time: float, time: float = 0.0,
stir: bool, stir: bool = False,
stir_speed: float, stir_speed: float = 300.0,
viscous: bool, viscous: bool = False,
purpose: str purpose: str = "添加试剂"
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成添加试剂的协议序列 - 严格按照 Add.action 生成添加试剂的协议序列,支持智能试剂匹配
基于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 = [] action_sequence = []
# 如果指定了体积,执行液体转移 print(f"ADD_PROTOCOL: 开始生成添加试剂协议")
if volume > 0: print(f" - 目标容器: {vessel}")
# 查找可用的试剂瓶 print(f" - 试剂: {reagent}")
available_flasks = [node for node in G.nodes() print(f" - 体积: {volume} mL")
if node.startswith('flask_') print(f" - 质量: {mass} g")
and G.nodes[node].get('type') == 'container'] print(f" - 搅拌: {stir} (速度: {stir_speed} RPM)")
print(f" - 粘稠: {viscous}")
print(f" - 目的: {purpose}")
if not available_flasks: # 1. 验证目标容器存在
raise ValueError("没有找到可用的试剂容器") if vessel not in G.nodes():
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
reagent_vessel = available_flasks[0] # 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. 验证试剂容器中的试剂体积
pump_nodes = [node for node in G.nodes() available_volume = get_vessel_reagent_volume(G, reagent_vessel)
if G.nodes[node].get('class') == 'virtual_pump'] print(f"ADD_PROTOCOL: 试剂容器 {reagent_vessel} 中有 {available_volume} mL 试剂")
if pump_nodes: if available_volume < volume:
pump_id = pump_nodes[0] print(f"ADD_PROTOCOL: 警告 - 试剂容器中的试剂不足!需要 {volume} mL可用 {available_volume} mL")
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
}
})
# 如果需要搅拌,使用 StartStir 而不是 Stir # 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: if stir:
stirrer_nodes = [node for node in G.nodes() try:
if G.nodes[node].get('class') == 'virtual_stirrer'] stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_nodes: if stirrer_id:
stirrer_id = stirrer_nodes[0] print(f"ADD_PROTOCOL: 找到搅拌器 {stirrer_id},将在添加前启动搅拌")
action_sequence.append({
# 先启动搅拌
stir_action = {
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", # 使用 start_stir 而不是 stir "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": f"添加 {reagent} 后搅拌" "purpose": f"{purpose}: 启动搅拌,准备添加 {reagent}"
}
}
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"{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 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 from typing import List, Dict, Any
import networkx as nx 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( def generate_centrifuge_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -9,115 +63,223 @@ def generate_centrifuge_protocol(
temp: float = 25.0 temp: float = 25.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成离心操作的协议序列 生成离心操作的协议序列,复用 pump_protocol 的成熟算法
离心流程:
1. 液体转移:将待离心溶液从源容器转移到离心机容器
2. 离心操作:执行离心分离
3. 上清液转移:将离心后的上清液转移回原容器或新容器
4. 沉淀处理:处理离心沉淀(可选)
Args: Args:
G: 有向图,节点为设备和容器 G: 有向图,节点为设备和容器,边为流体管道
vessel: 离心容器名称 vessel: 包含待离心溶液的容器名称
speed: 离心速度 (rpm) speed: 离心速度 (rpm)
time: 离心时间 (秒) time: 离心时间 (秒)
temp: 温度 (摄氏度,可选) temp: 离心温度 (°C)默认25°C
Returns: Returns:
List[Dict[str, Any]]: 离心操作的动作序列 List[Dict[str, Any]]: 离心操作的动作序列
Raises: Raises:
ValueError: 当找不到离心机设备时抛出异常 ValueError: 当找不到必要的设备时抛出异常
Examples: 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 = [] action_sequence = []
# 查找离心机设备 print(f"CENTRIFUGE: 开始生成离心协议")
centrifuge_nodes = [node for node in G.nodes() print(f" - 源容器: {vessel}")
if G.nodes[node].get('class') == 'virtual_centrifuge'] 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(): 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, "device_id": centrifuge_id,
"action_name": "centrifuge", "action_name": "centrifuge",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": centrifuge_vessel,
"speed": speed, "speed": speed,
"time": time, "time": time,
"temp": temp "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 return action_sequence
def generate_multi_step_centrifuge_protocol( # 便捷函数:常用离心方案
def generate_low_speed_centrifuge_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
steps: List[Dict[str, Any]] time: float = 300.0 # 5分钟
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """低速离心:细胞分离或大颗粒沉淀"""
生成多步骤离心操作的协议序列 return generate_centrifuge_protocol(G, vessel, 1000.0, time, 4.0)
Args:
G: 有向图,节点为设备和容器
vessel: 离心容器名称
steps: 离心步骤列表,每个步骤包含 speed, time, temp 参数
Returns: def generate_high_speed_centrifuge_protocol(
List[Dict[str, Any]]: 多步骤离心操作的动作序列 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)
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 = []
# 查找离心机设备 def generate_standard_centrifuge_protocol(
centrifuge_nodes = [node for node in G.nodes() G: nx.DiGraph,
if G.nodes[node].get('class') == 'virtual_centrifuge'] vessel: str,
time: float = 600.0 # 10分钟
) -> List[Dict[str, Any]]:
"""标准离心:常规样品处理"""
return generate_centrifuge_protocol(G, vessel, 5000.0, time, 25.0)
if not centrifuge_nodes:
raise ValueError("没有找到可用的离心机设备")
centrifuge_id = centrifuge_nodes[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)
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
# 执行每个离心步骤 def generate_ultra_centrifuge_protocol(
for i, step in enumerate(steps): G: nx.DiGraph,
speed = step.get('speed', 5000) vessel: str,
time = step.get('time', 300) time: float = 1800.0 # 30分钟
temp = step.get('temp', 25.0) ) -> List[Dict[str, Any]]:
"""超高速离心:超细颗粒分离"""
action_sequence.append({ return generate_centrifuge_protocol(G, vessel, 15000.0, time, 4.0)
"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

View File

@@ -1,5 +1,147 @@
from typing import List, Dict, Any from typing import List, Dict, Any
import networkx as nx 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( def generate_clean_vessel_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -10,13 +152,22 @@ def generate_clean_vessel_protocol(
repeats: int = 1 repeats: int = 1
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成容器清洗操作的协议序列,使用transfer操作实现清洗 生成容器清洗操作的协议序列,复用 pump_protocol 的成熟算法
清洗流程:
1. 查找溶剂容器和废液容器
2. 如果需要加热,启动加热设备
3. 重复以下操作 repeats 次:
a. 使用 pump_protocol 将溶剂从溶剂容器转移到目标容器
b. (可选) 等待清洗作用时间
c. 使用 pump_protocol 将清洗液从目标容器转移到废液容器
4. 如果加热了,停止加热
Args: Args:
G: 有向图,节点为设备和容器 G: 有向图,节点为设备和容器,边为流体管道
vessel: 要清洗的容器名称 vessel: 要清洗的容器名称
solvent: 用于清洗容器的溶剂名称 solvent: 用于清洗的溶剂名称
volume: 清洗溶剂体积 volume: 每次清洗使用的溶剂体积
temp: 清洗时的温度 temp: 清洗时的温度
repeats: 清洗操作的重复次数,默认为 1 repeats: 清洗操作的重复次数,默认为 1
@@ -24,103 +175,265 @@ def generate_clean_vessel_protocol(
List[Dict[str, Any]]: 容器清洗操作的动作序列 List[Dict[str, Any]]: 容器清洗操作的动作序列
Raises: Raises:
ValueError: 当找不到必要的设备时抛出异常 ValueError: 当找不到必要的容器或设备时抛出异常
Examples: 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 = [] action_sequence = []
# 查找虚拟转移泵设备进行清洗操作 print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
pump_nodes = [node for node in G.nodes() print(f" - 目标容器: {vessel}")
if G.nodes[node].get('class') == 'virtual_transfer_pump'] 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(): if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于") raise ValueError(f"目标容器 '{vessel}' 不存在于系统")
# 查找溶剂容器 # 查找溶剂容器
solvent_vessel = f"flask_{solvent}" try:
if solvent_vessel not in G.nodes(): solvent_vessel = find_solvent_vessel(G, solvent)
raise ValueError(f"溶剂容器 {solvent_vessel} 不存在于图中") print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂容器: {str(e)}")
# 查找废液容器 # 查找废液容器
waste_vessel = "flask_waste" try:
if waste_vessel not in G.nodes(): waste_vessel = find_waste_vessel(G)
raise ValueError(f"废液容器 {waste_vessel} 不存在于图中") print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}")
except ValueError as e:
raise ValueError(f"无法找到废液容器: {str(e)}")
# 查找加热设备(如果需要加热 # 查找加热设备(可选
heatchill_nodes = [node for node in G.nodes() heatchill_id = find_connected_heatchill(G, vessel)
if G.nodes[node].get('class') == 'virtual_heatchill'] if heatchill_id:
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
else:
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None # 第一步:如果需要加热且有加热设备,启动加热
# 执行清洗操作序列
for repeat in range(repeats):
# 1. 如果需要加热,先设置温度
if temp > 25.0 and heatchill_id: if temp > 25.0 and heatchill_id:
action_sequence.append({ print(f"CLEAN_VESSEL: 启动加热至 {temp}°C")
heatchill_start_action = {
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel,
"temp": temp, "temp": temp,
"purpose": "cleaning" "purpose": f"cleaning with {solvent}"
} }
})
# 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
} }
}) action_sequence.append(heatchill_start_action)
# 3. 等待清洗作用时间可选可以添加wait操作 # 等待温度稳定
# 这里省略wait操作直接进行下一步 wait_action = {
"action_name": "wait",
# 4. 将清洗后的溶剂转移到废液容器 "action_kwargs": {"time": 30} # 等待30秒让温度稳定
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
} }
}) action_sequence.append(wait_action)
# 5. 如果加热了,停止加热 # 第二步:重复清洗操作
for repeat in range(repeats):
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
# 2a. 使用 pump_protocol 将溶剂转移到目标容器
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel}")
try:
# 调用成熟的 pump_protocol 算法
add_solvent_actions = generate_pump_protocol(
G=G,
from_vessel=solvent_vessel,
to_vessel=vessel,
volume=volume,
flowrate=2.5, # 适中的流速,避免飞溅
transfer_flowrate=2.5
)
action_sequence.extend(add_solvent_actions)
except Exception as e:
raise ValueError(f"无法将溶剂转移到容器: {str(e)}")
# 2b. 等待清洗作用时间(让溶剂充分清洗容器)
cleaning_wait_time = 60 if temp > 50.0 else 30 # 高温下等待更久
print(f"CLEAN_VESSEL: 等待清洗作用 {cleaning_wait_time}")
wait_action = {
"action_name": "wait",
"action_kwargs": {"time": cleaning_wait_time}
}
action_sequence.append(wait_action)
# 2c. 使用 pump_protocol 将清洗液转移到废液容器
print(f"CLEAN_VESSEL: 将清洗液从 {vessel} 转移到废液容器")
try:
# 调用成熟的 pump_protocol 算法
remove_waste_actions = generate_pump_protocol(
G=G,
from_vessel=vessel,
to_vessel=waste_vessel,
volume=volume,
flowrate=2.5, # 适中的流速
transfer_flowrate=2.5
)
action_sequence.extend(remove_waste_actions)
except Exception as e:
raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}")
# 2d. 清洗循环间的短暂等待
if repeat < repeats - 1: # 不是最后一次清洗
print(f"CLEAN_VESSEL: 清洗循环间等待")
wait_action = {
"action_name": "wait",
"action_kwargs": {"time": 10}
}
action_sequence.append(wait_action)
# 第三步:如果加热了,停止加热
if temp > 25.0 and heatchill_id: if temp > 25.0 and heatchill_id:
action_sequence.append({ print(f"CLEAN_VESSEL: 停止加热")
heatchill_stop_action = {
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "action_kwargs": {
"vessel": vessel "vessel": vessel
} }
}) }
action_sequence.append(heatchill_stop_action)
print(f"CLEAN_VESSEL: 生成了 {len(action_sequence)} 个动作")
print(f"CLEAN_VESSEL: 清洗协议生成完成")
return action_sequence 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 from typing import List, Dict, Any
import networkx as nx 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( def generate_dissolve_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -9,154 +51,309 @@ def generate_dissolve_protocol(
amount: str = "", amount: str = "",
temp: float = 25.0, temp: float = 25.0,
time: float = 0.0, time: float = 0.0,
stir_speed: float = 0.0 stir_speed: float = 300.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成溶解操作的协议序列 生成溶解操作的协议序列,复用 pump_protocol 的成熟算法
溶解流程:
1. 溶剂转移:将溶剂从溶剂瓶转移到目标容器
2. 启动加热搅拌:设置温度和搅拌
3. 等待溶解:监控溶解过程
4. 停止加热搅拌:完成溶解
Args: Args:
G: 有向图,节点为设备和容器 G: 有向图,节点为设备和容器,边为流体管道
vessel: 装有要溶解物质的容器名称 vessel: 目标容器(要进行溶解的容器
solvent: 用于溶解物质的溶剂名称 solvent: 溶剂名称(用于查找对应的溶剂瓶)
volume: 溶剂体积,可选参数 volume: 溶剂体积 (mL)
amount: 要溶解物质的量,可选参数 amount: 要溶解物质描述
temp: 溶解时的温度,可选参数 temp: 溶解温度 (°C)默认25°C室温
time: 溶解时间,可选参数 time: 溶解时间 (秒)默认0立即完成
stir_speed: 搅拌速度,可选参数 stir_speed: 搅拌速度 (RPM)默认300 RPM
Returns: Returns:
List[Dict[str, Any]]: 溶解操作的动作序列 List[Dict[str, Any]]: 溶解操作的动作序列
Raises: Raises:
ValueError: 当找不到必要的设备时抛出异常 ValueError: 当找不到必要的设备或容器
Examples: 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 = [] 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(): if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于") raise ValueError(f"目标容器 '{vessel}' 不存在于系统")
# 查找溶剂容器 # 查找溶剂
solvent_vessel = f"flask_{solvent}" try:
if solvent_vessel not in G.nodes(): solvent_vessel = find_solvent_vessel(G, solvent)
# 如果没有找到特定溶剂容器,查找可用的源容器 print(f"DISSOLVE: 找到溶剂瓶: {solvent_vessel}")
available_vessels = [node for node in G.nodes() except ValueError as e:
if node.startswith('flask_') and raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
G.nodes[node].get('type') == 'container']
if available_vessels: # 验证是否存在从溶剂瓶到目标容器的路径
solvent_vessel = available_vessels[0] 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}' 没有可用路径")
# 查找加热搅拌器
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: else:
raise ValueError(f"没有找到溶剂容器 {solvent}") print(f"DISSOLVE: 警告 - 需要加热/搅拌但未找到与容器 {vessel} 相连的加热搅拌器")
except Exception as e:
print(f"DISSOLVE: 加热搅拌器配置出错: {str(e)}")
# 查找转移泵设备 # === 第一步:启动加热搅拌(在添加溶剂前) ===
pump_nodes = [node for node in G.nodes() if heatchill_id and (temp > 25.0 or time > 0):
if G.nodes[node].get('class') == 'virtual_transfer_pump'] print(f"DISSOLVE: 启动加热搅拌器,温度: {temp}°C")
if not pump_nodes: if time > 0:
raise ValueError("没有找到可用的转移泵设备") # 如果指定了时间,使用定时加热搅拌
heatchill_action = {
pump_id = pump_nodes[0] "device_id": heatchill_id,
"action_name": "heat_chill",
# 查找加热设备(如果需要加热) "action_kwargs": {
heatchill_nodes = [node for node in G.nodes() "vessel": vessel,
if G.nodes[node].get('class') == 'virtual_heatchill'] "temp": temp,
"time": time,
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None "stir": True,
"stir_speed": stir_speed,
# 查找搅拌设备(如果需要搅拌) "purpose": f"溶解 {amount}{solvent}"
stirrer_nodes = [node for node in G.nodes() }
if G.nodes[node].get('class') == 'virtual_stirrer'] }
else:
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None # 如果没有指定时间,使用持续加热搅拌
heatchill_action = {
# 步骤1如果需要加热先设置温度
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel,
"temp": temp, "temp": temp,
"purpose": "dissolution" "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}
}) })
# 步骤2添加溶剂到容器中 # === 第二步:添加溶剂到目标容器 ===
if volume > 0: 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_sequence.append({
"device_id": pump_id, "action_name": "wait",
"action_name": "transfer", "action_kwargs": {"time": 5}
"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: if time > 0 and heatchill_id and temp <= 25.0:
action_sequence.append({ # 只需要搅拌等待,不需要加热
"device_id": stirrer_id, print(f"DISSOLVE: 室温搅拌 {time}s 等待溶解")
"action_name": "start_stir",
stir_action = {
"device_id": heatchill_id,
"action_name": "heat_chill",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel,
"temp": 25.0, # 室温
"time": time,
"stir": True,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": f"dissolving {amount} in {solvent}" "purpose": f"室温搅拌溶解 {amount}"
} }
})
# 步骤4如果指定了溶解时间等待溶解完成
if time > 0:
# 这里可以添加等待操作,或者使用搅拌操作来模拟溶解时间
if stirrer_id and stir_speed > 0:
# 停止之前的搅拌,使用定时搅拌
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel
} }
}) action_sequence.append(stir_action)
# 开始定时搅拌 # === 第四步:如果使用了持续加热,需要手动停止 ===
action_sequence.append({ if heatchill_id and time == 0 and temp > 25.0:
"device_id": stirrer_id, print(f"DISSOLVE: 停止加热搅拌器")
"action_name": "stir",
"action_kwargs": {
"stir_time": time,
"stir_speed": stir_speed,
"settling_time": 10.0 # 搅拌后静置10秒
}
})
# 步骤5如果加热了停止加热 stop_action = {
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "action_kwargs": {
"vessel": vessel "vessel": vessel
} }
})
# 步骤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
} }
}) action_sequence.append(stop_action)
print(f"DISSOLVE: 生成了 {len(action_sequence)} 个动作")
print(f"DISSOLVE: 溶解协议生成完成")
return action_sequence 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 = []
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,5 +1,196 @@
import numpy as np import numpy as np
import networkx as nx 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( def generate_evacuateandrefill_protocol(
@@ -7,137 +198,340 @@ def generate_evacuateandrefill_protocol(
vessel: str, vessel: str,
gas: str, gas: str,
repeats: int = 1 repeats: int = 1
) -> list[dict]: ) -> List[Dict[str, Any]]:
""" """
生成操作的动作序列 生成抽真空和充气操作的动作序列
:param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置 **修复版本**: 正确调用 pump_protocol 并处理异常
:param from_vessel: 容器A
:param to_vessel: 容器B
:param volume: 转移的体积
:param flowrate: 最终注入容器B时的流速
:param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
:return: 泵操作的动作序列
""" """
action_sequence = []
# 生成电磁阀、真空泵、气源操作的动作序列 # 参数设置 - 关键修复:减小体积避免超出泵容量
vacuum_action_sequence = [] VACUUM_VOLUME = 20.0 # 减小抽真空体积
nodes = G.nodes(data=True) REFILL_VOLUME = 20.0 # 减小充气体积
PUMP_FLOW_RATE = 2.5 # 降低流速
STIR_SPEED = 300.0
# 找到和 vessel 相连的电磁阀和真空泵、气源 print(f"EVACUATE_REFILL: 开始生成协议,目标容器: {vessel}, 气体: {gas}, 重复次数: {repeats}")
vacuum_backbone = {"vessel": vessel}
for neighbor in G.neighbors(vessel): # 1. 验证设备存在
if nodes[neighbor]["class"].startswith("solenoid_valve"): if vessel not in G.nodes():
for neighbor2 in G.neighbors(neighbor): raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
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.")
# 生成操作的动作序列 # 2. 查找设备
for i in range(repeats): try:
# 打开真空泵阀门、关闭气源阀门 vacuum_pump = find_vacuum_pump(G)
vacuum_action_sequence.append([ vacuum_solenoid = find_associated_solenoid_valve(G, vacuum_pump)
{ gas_source = find_gas_source(G, gas)
"device_id": vacuum_backbone["vacuum_valve"], gas_solenoid = find_associated_solenoid_valve(G, gas_source)
"action_name": "set_valve_position", stirrer_id = find_connected_stirrer(G, vessel)
print(f"EVACUATE_REFILL: 找到设备")
print(f" - 真空泵: {vacuum_pump}")
print(f" - 气源: {gas_source}")
print(f" - 真空电磁阀: {vacuum_solenoid}")
print(f" - 气源电磁阀: {gas_solenoid}")
print(f" - 搅拌器: {stirrer_id}")
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)}")
# 验证充气路径
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": { "action_kwargs": {
"command": "OPEN" "vessel": vessel,
"stir_speed": STIR_SPEED,
"purpose": "抽真空充气操作前启动搅拌"
} }
})
# 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": vacuum_backbone["gas_valve"], "device_id": "transfer_pump_1",
"action_name": "set_valve_position", "action_name": "set_position",
"action_kwargs": { "action_kwargs": {
"command": "CLOSED" "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
} }
} }
]) ])
# 打开真空泵、关闭气源 # 关闭真空电磁阀
vacuum_action_sequence.append([ if vacuum_solenoid:
{ action_sequence.append({
"device_id": vacuum_backbone["pump"], "device_id": vacuum_solenoid,
"action_name": "set_status", "action_name": "set_valve_position",
"action_kwargs": { "action_kwargs": {"command": "CLOSED"}
"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([ action_sequence.append({
{ "device_id": vacuum_pump,
"device_id": vacuum_backbone["vacuum_valve"], "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_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": { "action_kwargs": {
"command": "CLOSED" "position": REFILL_VOLUME,
"max_velocity": PUMP_FLOW_RATE
} }
}, },
{ {
"device_id": vacuum_backbone["gas_valve"], "device_id": "multiway_valve_2",
"action_name": "set_valve_position", "action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 反应器端口
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": { "action_kwargs": {
"command": "OPEN" "position": 0.0,
"max_velocity": PUMP_FLOW_RATE
} }
} }
]) ])
# 关闭真空泵、打开气源 except Exception as e:
vacuum_action_sequence.append([ 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": vacuum_backbone["pump"], "device_id": "multiway_valve_2",
"action_name": "set_status", "action_name": "set_valve_position",
"action_kwargs": {"command": "8"} # 连接气源
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": { "action_kwargs": {
"string": "OFF" "position": REFILL_VOLUME,
"max_velocity": PUMP_FLOW_RATE
} }
}, },
{ {
"device_id": vacuum_backbone["gas"], "device_id": "multiway_valve_2",
"action_name": "set_status", "action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 连接反应器
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": { "action_kwargs": {
"string": "ON" "position": 0.0,
"max_velocity": PUMP_FLOW_RATE
} }
} }
]) ])
vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
# 关闭气源电磁阀
if gas_solenoid:
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "CLOSED"}
})
# 关闭气源 # 关闭气源
vacuum_action_sequence.append( action_sequence.append({
{ "device_id": gas_source,
"device_id": vacuum_backbone["gas"],
"action_name": "set_status", "action_name": "set_status",
"action_kwargs": { "action_kwargs": {"string": "OFF"}
"string": "OFF" })
}
}
)
# 关闭阀门 # 等待下一次循环
vacuum_action_sequence.append( if cycle < repeats - 1:
{ action_sequence.append({
"device_id": vacuum_backbone["gas_valve"], "action_name": "wait",
"action_name": "set_valve_position", "action_kwargs": {"time": 2.0}
"action_kwargs": { })
"command": "CLOSED"
} # 停止搅拌器
} if stirrer_id:
) action_sequence.append({
return vacuum_action_sequence "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 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( def generate_evaporate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
pressure: float, pressure: float = 0.1,
temp: float, temp: float = 60.0,
time: float, time: float = 1800.0,
stir_speed: float stir_speed: float = 100.0
) -> list[dict]: ) -> 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. 1. 液体转移:将待蒸发溶液从源容器转移到旋转蒸发仪
:param solvent: Solvent to clean vessel with. 2. 蒸发操作:执行旋转蒸发
:param volume: Volume of solvent to clean vessel with. 3. (可选) 溶剂回收:将冷凝的溶剂转移到回收容器
:param temp: Temperature to heat vessel to while cleaning. 4. 残留物转移:将浓缩物从旋转蒸发仪转移回原容器或新容器
:param repeats: Number of cleaning cycles to perform.
:return: List of actions to clean vessel. 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 = []
# 生成泵操作的动作序列 print(f"EVAPORATE: 开始生成蒸发协议")
pump_action_sequence = [] print(f" - 源容器: {vessel}")
reactor_volume = 500.0 print(f" - 真空度: {pressure} bar")
transfer_flowrate = flowrate = 2.5 print(f" - 温度: {temp}°C")
print(f" - 时间: {time}s ({time/60:.1f}分钟)")
print(f" - 旋转速度: {stir_speed} RPM")
# 开启冷凝器 # 验证源容器存在
pump_action_sequence.append({ if vessel not in G.nodes():
"device_id": "rotavap_chiller", raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
"action_name": "set_temperature",
"action_kwargs": { # 获取源容器中的液体体积
"command": "-40" source_volume = get_vessel_liquid_volume(G, vessel)
} print(f"EVAPORATE: 源容器 {vessel} 中有 {source_volume} mL 液体")
})
# TODO: 通过温度反馈改为 HeatChillToTemp而非等待固定时间 # 查找旋转蒸发仪
pump_action_sequence.append({ 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_name": "wait",
"action_kwargs": { "action_kwargs": {"time": 10}
"time": 1800
} }
}) action_sequence.append(wait_action)
# 开启旋蒸真空泵、旋转在液体转移后运行time时间 # === 第二步:执行旋转蒸发 ===
pump_action_sequence.append({ print(f"EVAPORATE: 执行旋转蒸发操作")
"device_id": "rotavap_controller", evaporate_action = {
"action_name": "set_pump_time", "device_id": rotavap_id,
"action_name": "evaporate",
"action_kwargs": { "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({ wait_action = {
"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({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {"time": 30}
"time": time
} }
}) action_sequence.append(wait_action)
return pump_action_sequence
# === 第三步:溶剂回收(如果有回收容器)===
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 from typing import List, Dict, Any
import networkx as nx 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( def generate_filter_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -12,59 +96,209 @@ def generate_filter_protocol(
volume: float = 0.0 volume: float = 0.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成过滤操作的协议序列 生成过滤操作的协议序列,复用 pump_protocol 的成熟算法
过滤流程:
1. 液体转移:将待过滤溶液从源容器转移到过滤器
2. 启动加热搅拌:设置温度和搅拌
3. 执行过滤:通过过滤器分离固液
4. (可选) 继续或停止加热搅拌
Args: Args:
G: 有向图,节点为设备和容器 G: 有向图,节点为设备和容器,边为流体管道
vessel: 过滤容器 vessel: 包含待过滤溶液的容器名称
filtrate_vessel: 滤液容器(可选) filtrate_vessel: 滤液收集容器(可选,自动查找
stir: 是否搅拌 stir: 是否在过滤过程中搅拌
stir_speed: 搅拌速度(可选) stir_speed: 搅拌速度 (RPM)
temp: 温度(可选,摄氏度) temp: 过滤温度 (°C)
continue_heatchill: 是否继续加热冷却 continue_heatchill: 过滤后是否继续加热搅拌
volume: 过滤体积(可选) volume: 预期过滤体积 (mL)0表示全部过滤
Returns: Returns:
List[Dict[str, Any]]: 过滤操作的动作序列 List[Dict[str, Any]]: 过滤操作的动作序列
Raises:
ValueError: 当找不到过滤设备时抛出异常
Examples:
filter_protocol = generate_filter_protocol(G, "reactor", "filtrate_vessel", stir=True, volume=100.0)
""" """
action_sequence = [] action_sequence = []
# 查找过滤设备 print(f"FILTER: 开始生成过滤协议")
filter_nodes = [node for node in G.nodes() print(f" - 源容器: {vessel}")
if G.nodes[node].get('class') == 'virtual_filter'] 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(): 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 液体")
# 执行过滤操作 # 查找过滤器设备
action_sequence.append({ try:
"device_id": filter_id, filter_id = find_filter_device(G)
"action_name": "filter_sample", 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": { "action_kwargs": {
"vessel": vessel, "vessel": filter_vessel_id,
"filtrate_vessel": filtrate_vessel, "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",
"action_kwargs": {
"vessel": filter_vessel_id,
"filtrate_vessel": actual_filtrate_vessel,
"stir": stir, "stir": stir,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"temp": temp, "temp": temp,
"continue_heatchill": continue_heatchill, "continue_heatchill": continue_heatchill,
"volume": volume "volume": transfer_volume
} }
}
action_sequence.append(filter_action)
# 过滤后等待
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10}
}) })
# === 第四步:如果不继续加热搅拌,停止加热器 ===
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 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 from typing import List, Dict, Any
import networkx as nx 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( def generate_filter_through_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -12,10 +79,15 @@ def generate_filter_through_protocol(
residence_time: float = 0.0 residence_time: float = 0.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成通过过滤介质过滤的协议序列 生成通过过滤介质过滤的协议序列,复用 pump_protocol 的成熟算法
过滤流程:
1. 液体转移:将样品从源容器转移到过滤介质
2. 重力过滤:液体通过过滤介质自动流到目标容器
3. 洗脱操作:将洗脱溶剂通过过滤介质洗脱目标物质
Args: Args:
G: 有向图,节点为设备和容器 G: 有向图,节点为设备和容器,边为流体管道
from_vessel: 源容器的名称,即物质起始所在的容器 from_vessel: 源容器的名称,即物质起始所在的容器
to_vessel: 目标容器的名称,物质过滤后要到达的容器 to_vessel: 目标容器的名称,物质过滤后要到达的容器
filter_through: 过滤时所通过的介质,如滤纸、柱子等 filter_through: 过滤时所通过的介质,如滤纸、柱子等
@@ -28,123 +100,288 @@ def generate_filter_through_protocol(
List[Dict[str, Any]]: 过滤操作的动作序列 List[Dict[str, Any]]: 过滤操作的动作序列
Raises: Raises:
ValueError: 当找不到必要的设备时抛出异常 ValueError: 当找不到必要的设备或容器
Examples: Examples:
filter_through_protocol = generate_filter_through_protocol( filter_through_actions = generate_filter_through_protocol(
G, "reactor", "collection_flask", "celite", "ethanol", 50.0, 2, 60.0 G, "reaction_mixture", "collection_bottle_1", "celite", "ethanol", 20.0, 2, 30.0
) )
""" """
action_sequence = [] 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(): if from_vessel not in G.nodes():
raise ValueError(f"源容器 {from_vessel} 不存在于") raise ValueError(f"源容器 '{from_vessel}' 不存在于系统")
if to_vessel not in G.nodes(): 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() source_volume = get_vessel_liquid_volume(G, from_vessel)
if G.nodes[node].get('class') == 'virtual_transfer_pump'] 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() transfer_volume = source_volume if source_volume > 0 else 100.0 # 默认100mL
if G.nodes[node].get('class') == 'virtual_filter'] 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 residence_time > 0:
if eluting_solvent and eluting_volume > 0: print(f"FILTER_THROUGH: 等待样品在过滤介质中停留 {residence_time}s")
eluting_vessel = f"flask_{eluting_solvent}" action_sequence.append({
if eluting_vessel not in G.nodes(): "action_name": "wait",
# 查找可用的溶剂容器 "action_kwargs": {"time": residence_time}
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: else:
raise ValueError(f"没有找到洗脱溶剂容器 {eluting_solvent}") # 即使没有指定停留时间,也等待一段时间让液体通过
default_wait_time = max(10, transfer_volume / 10) # 根据体积估算等待时间
# 步骤1将样品从源容器转移到过滤装置模拟通过过滤介质 print(f"FILTER_THROUGH: 等待样品通过过滤介质 {default_wait_time}s")
# 这里我们将过滤过程分解为多个转移步骤来模拟通过介质的过程
# 首先转移样品(模拟样品通过过滤介质)
action_sequence.append({ action_sequence.append({
"device_id": pump_id, "action_name": "wait",
"action_name": "transfer", "action_kwargs": {"time": default_wait_time}
"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:
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
}
})
# 步骤3洗脱操作如果指定了洗脱溶剂和重复次数
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel: 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({ action_sequence.append({
"device_id": pump_id, "action_name": "wait",
"action_name": "transfer", "action_kwargs": {"time": eluting_wait_time}
"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
}
}) })
# 如果有过滤设备,再次过滤洗脱液 # 洗脱间隔等待
if filter_id: if repeat_idx < eluting_repeats - 1: # 不是最后一次洗脱
action_sequence.append({ action_sequence.append({
"device_id": filter_id, "action_name": "wait",
"action_name": "filter_sample", "action_kwargs": {"time": 10}
"action_kwargs": {
"vessel": to_vessel,
"filtrate_vessel": to_vessel,
"stir": False,
"stir_speed": 0.0,
"temp": 25.0,
"continue_heatchill": False,
"volume": eluting_volume
}
}) })
# === 第四步:最终等待,确保所有液体完全通过 ===
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 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 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( def generate_heat_chill_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
temp: float, temp: float,
time: float, time: float,
stir: bool, stir: bool = False,
stir_speed: float, stir_speed: float = 300.0,
purpose: str purpose: str = "加热/冷却操作"
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成加热/冷却操作的协议序列 - 严格按照 HeatChill.action 生成加热/冷却操作的协议序列 - 带时间限制的完整操作
""" """
action_sequence = [] action_sequence = []
# 查找加热/冷却设备 print(f"HEATCHILL: 开始生成加热/冷却协议")
heatchill_nodes = [node for node in G.nodes() print(f" - 容器: {vessel}")
if G.nodes[node].get('class') == 'virtual_heatchill'] print(f" - 目标温度: {temp}°C")
print(f" - 持续时间: {time}")
if not heatchill_nodes: print(f" - 使用内置搅拌: {stir}, 速度: {stir_speed} RPM")
raise ValueError("没有找到可用的加热/冷却设备") print(f" - 目的: {purpose}")
heatchill_id = heatchill_nodes[0]
# 1. 验证容器存在
if vessel not in G.nodes(): 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, "device_id": heatchill_id,
"action_name": "heat_chill", "action_name": "heat_chill",
"action_kwargs": { "action_kwargs": {
@@ -36,10 +64,13 @@ def generate_heat_chill_protocol(
"time": time, "time": time,
"stir": stir, "stir": stir,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": purpose "status": "start"
}
} }
})
action_sequence.append(heatchill_action)
print(f"HEATCHILL: 生成了 {len(action_sequence)} 个动作")
return action_sequence return action_sequence
@@ -47,25 +78,31 @@ def generate_heat_chill_start_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
temp: float, temp: float,
purpose: str purpose: str = "开始加热/冷却"
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成开始加热/冷却操作的协议序列 - 严格按照 HeatChillStart.action 生成开始加热/冷却操作的协议序列
""" """
action_sequence = [] action_sequence = []
heatchill_nodes = [node for node in G.nodes() print(f"HEATCHILL_START: 开始生成加热/冷却启动协议")
if G.nodes[node].get('class') == 'virtual_heatchill'] print(f" - 容器: {vessel}")
print(f" - 目标温度: {temp}°C")
if not heatchill_nodes: print(f" - 目的: {purpose}")
raise ValueError("没有找到可用的加热/冷却设备")
heatchill_id = heatchill_nodes[0]
# 1. 验证容器存在
if vessel not in G.nodes(): 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, "device_id": heatchill_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
@@ -73,8 +110,11 @@ def generate_heat_chill_start_protocol(
"temp": temp, "temp": temp,
"purpose": purpose "purpose": purpose
} }
}) }
action_sequence.append(heatchill_start_action)
print(f"HEATCHILL_START: 生成了 {len(action_sequence)} 个动作")
return action_sequence return action_sequence
@@ -84,34 +124,250 @@ def generate_heat_chill_stop_protocol(
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成停止加热/冷却操作的协议序列 生成停止加热/冷却操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 容器名称
Returns:
List[Dict[str, Any]]: 停止加热/冷却操作的动作序列
""" """
action_sequence = [] action_sequence = []
# 查找加热/冷却设备 print(f"HEATCHILL_STOP: 开始生成加热/冷却停止协议")
heatchill_nodes = [node for node in G.nodes() print(f" - 容器: {vessel}")
if G.nodes[node].get('class') == 'virtual_heatchill']
if not heatchill_nodes:
raise ValueError("没有找到可用的加热/冷却设备")
heatchill_id = heatchill_nodes[0]
# 1. 验证容器存在
if vessel not in G.nodes(): 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, "device_id": heatchill_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "action_kwargs": {
"vessel": vessel "vessel": vessel
} }
}
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 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): def find_connected_pump(G, valve_node):
for neighbor in G.neighbors(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 return neighbor
raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点") 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 from typing import List, Dict, Any
import networkx as nx 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( def generate_run_column_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -11,92 +93,220 @@ def generate_run_column_protocol(
生成柱层析分离的协议序列 生成柱层析分离的协议序列
Args: Args:
G: 有向图,节点为设备和容器 G: 有向图,节点为设备和容器,边为流体管道
from_vessel: 源容器的名称,即样品起始所在的容器 from_vessel: 源容器的名称,即样品起始所在的容器
to_vessel: 目标容器的名称,分离后的样品要到达的容器 to_vessel: 目标容器的名称,分离后的样品要到达的容器
column: 所使用的柱子的名称 column: 所使用的柱子的名称
Returns: Returns:
List[Dict[str, Any]]: 柱层析分离操作的动作序列 List[Dict[str, Any]]: 柱层析分离操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
run_column_protocol = generate_run_column_protocol(G, "reactor", "collection_flask", "silica_column")
""" """
action_sequence = [] action_sequence = []
# 验证容器是否存在 print(f"RUN_COLUMN: 开始生成柱层析协议")
print(f" - 源容器: {from_vessel}")
print(f" - 目标容器: {to_vessel}")
print(f" - 柱子: {column}")
# 验证源容器和目标容器存在
if from_vessel not in G.nodes(): if from_vessel not in G.nodes():
raise ValueError(f"源容器 {from_vessel} 不存在于") raise ValueError(f"源容器 '{from_vessel}' 不存在于系统")
if to_vessel not in G.nodes(): 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']
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备")
pump_id = pump_nodes[0]
# 查找柱层析设备 # 查找柱层析设备
column_device_id = None
column_nodes = [node for node in G.nodes() 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: if column_nodes:
raise ValueError("没有找到可用的柱层析设备") 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({ if column in G.nodes() and G.nodes[column].get('type') == 'container':
"device_id": pump_id, print(f"RUN_COLUMN: 样品转移 - {source_volume} mL 从 {from_vessel}{column}")
"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
}
})
# 步骤2运行柱层析分离 try:
action_sequence.append({ sample_transfer_actions = generate_pump_protocol(
"device_id": column_id, 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_name": "run_column",
"action_kwargs": { "action_kwargs": {
"from_vessel": from_vessel, "from_vessel": from_vessel,
"to_vessel": to_vessel, "to_vessel": to_vessel,
"column": column "column": column
} }
}
action_sequence.append(column_separation_action)
# 等待柱层析设备完成分离
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 60}
}) })
# 步骤3将分离后的产物从柱子转移到目标容器 # === 第三步:从柱子转移到目标容器(如果需要) ===
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)}")
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]
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_sequence.append({
"device_id": pump_id, "action_name": "wait",
"action_name": "transfer", "action_kwargs": {"time": 20}
"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
}
}) })
return action_sequence 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 from typing import List, Dict, Any
import networkx as nx 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( def generate_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
stir_time: float, stir_time: float,
@@ -8,37 +30,24 @@ def generate_stir_protocol(
settling_time: float settling_time: float
) -> List[Dict[str, Any]]: ) -> 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 = [] 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() try:
if G.nodes[node].get('class') == 'virtual_stirrer'] stirrer_id = find_connected_stirrer(G)
print(f"STIR: 找到搅拌设备: {stirrer_id}")
if not stirrer_nodes: except ValueError as e:
raise ValueError("没有找到可用的搅拌设备") raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 使用第一个可用的搅拌器
stirrer_id = stirrer_nodes[0]
# 执行搅拌操作 # 执行搅拌操作
action_sequence.append({ stir_action = {
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "stir", "action_name": "stir",
"action_kwargs": { "action_kwargs": {
@@ -46,8 +55,11 @@ def generate_stir_protocol(
"stir_speed": stir_speed, "stir_speed": stir_speed,
"settling_time": settling_time "settling_time": settling_time
} }
}) }
action_sequence.append(stir_action)
print(f"STIR: 生成了 {len(action_sequence)} 个动作")
return action_sequence return action_sequence
@@ -58,33 +70,28 @@ def generate_start_stir_protocol(
purpose: str purpose: str
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成开始搅拌操作的协议序列 生成开始搅拌操作的协议序列 - 持续搅拌
Args:
G: 有向图,节点为设备和容器
vessel: 搅拌容器
stir_speed: 搅拌速度 (rpm)
purpose: 搅拌目的
Returns:
List[Dict[str, Any]]: 开始搅拌操作的动作序列
""" """
action_sequence = [] action_sequence = []
# 查找搅拌设备 print(f"START_STIR: 开始生成启动搅拌协议")
stirrer_nodes = [node for node in G.nodes() print(f" - 容器: {vessel}")
if G.nodes[node].get('class') == 'virtual_stirrer'] 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(): 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, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
@@ -92,8 +99,11 @@ def generate_start_stir_protocol(
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": purpose "purpose": purpose
} }
}) }
action_sequence.append(start_stir_action)
print(f"START_STIR: 生成了 {len(action_sequence)} 个动作")
return action_sequence return action_sequence
@@ -103,35 +113,54 @@ def generate_stop_stir_protocol(
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成停止搅拌操作的协议序列 生成停止搅拌操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 搅拌容器
Returns:
List[Dict[str, Any]]: 停止搅拌操作的动作序列
""" """
action_sequence = [] action_sequence = []
# 查找搅拌设备 print(f"STOP_STIR: 开始生成停止搅拌协议")
stirrer_nodes = [node for node in G.nodes() print(f" - 容器: {vessel}")
if G.nodes[node].get('class') == 'virtual_stirrer']
if not stirrer_nodes: # 验证容器存在
raise ValueError("没有找到可用的搅拌设备")
stirrer_id = stirrer_nodes[0]
# 验证容器是否存在
if vessel not in G.nodes(): 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, "device_id": stirrer_id,
"action_name": "stop_stir", "action_name": "stop_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel "vessel": vessel
} }
}) }
action_sequence.append(stop_stir_action)
print(f"STOP_STIR: 生成了 {len(action_sequence)} 个动作")
return 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: class BasicConfig:
ENV = "pro" # 'test' ENV = "pro" # 'test'
config_path = "" config_path = ""
is_host_mode = True # 从registry.py移动过来 is_host_mode = True
slave_no_host = False # 是否跳过rclient.wait_for_service() slave_no_host = False # 是否跳过rclient.wait_for_service()
upload_registry = False
machine_name = "undefined" machine_name = "undefined"
vis_2d_enable = False vis_2d_enable = False

View File

@@ -1,9 +1,12 @@
import rtde_control try:
import dashboard_client 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 time
import json import json
from unilabos.devices.agv.robotiq_gripper import RobotiqGripper from unilabos.devices.agv.robotiq_gripper import RobotiqGripper
import rtde_receive
from std_msgs.msg import Float64MultiArray from std_msgs.msg import Float64MultiArray
from pydantic import BaseModel from pydantic import BaseModel

View File

@@ -234,40 +234,40 @@ class Laiyu:
resp_reset = self.reset() resp_reset = self.reset()
return actual_mass_mg 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 = {
'''
样例: 对一份excel文件记录的化合物进行称量
'''
excel_file = r"C:\auto\laiyu\test1.xlsx"
# 定义输出文件路径,用于记录实际加样多少
output_file = r"C:\auto\laiyu\test_output.xlsx"
# 定义物料名称和料筒位置关系
compound_positions = {
'XPhos': '1', 'XPhos': '1',
'Cu(OTf)2': '2', 'Cu(OTf)2': '2',
'CuSO4': '3', 'CuSO4': '3',
'PPh3': '4', 'PPh3': '4',
} }
# read excel file # read excel file
# excel_file = r"C:\auto\laiyu\test.xlsx" # excel_file = r"C:\auto\laiyu\test.xlsx"
df = pd.read_excel(excel_file, sheet_name='Sheet1') df = pd.read_excel(excel_file, sheet_name='Sheet1')
# 读取Excel文件中的数据 # 读取Excel文件中的数据
# 遍历每一行数据 # 遍历每一行数据
for index, row in df.iterrows(): for index, row in df.iterrows():
# 获取物料名称和质量 # 获取物料名称和质量
copper_name = row['copper'] copper_name = row['copper']
copper_mass = row['copper_mass'] copper_mass = row['copper_mass']
@@ -294,11 +294,11 @@ for index, row in df.iterrows():
df.at[index, 'copper_actual_mass'] = copper_actual_mass df.at[index, 'copper_actual_mass'] = copper_actual_mass
df.at[index, 'ligand_actual_mass'] = ligand_actual_mass df.at[index, 'ligand_actual_mass'] = ligand_actual_mass
# 保存修改后的数据到新的Excel文件 # 保存修改后的数据到新的Excel文件
df.to_excel(output_file, index=False) df.to_excel(output_file, index=False)
print(f"已保存到文件:{output_file}") print(f"已保存到文件:{output_file}")
# 关闭串口 # 关闭串口
modbus.ser.close() modbus.ser.close()
print("串口已关闭") print("串口已关闭")

View File

@@ -3,7 +3,11 @@ import sys
import io import io
# sys.path.insert(0, r'C:\kui\winprep_cli\winprep_c_Uni-lab\x64\Debug') # 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 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__), "..", "..", ".."))) 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.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.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}") print(f"使用文件DEBUG运行: {e}")

View File

@@ -9,7 +9,7 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import time import time
class RamanObj: 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_laser = port_laser
self.port_ccd = port_ccd self.port_ccd = port_ccd

View File

@@ -1,16 +1,18 @@
import asyncio import asyncio
import logging import logging
from typing import Dict, Any import time as time_module
from typing import Dict, Any, Optional
class VirtualCentrifuge: class VirtualCentrifuge:
"""Virtual centrifuge device for CentrifugeProtocol testing""" """Virtual centrifuge device - 简化版,只保留核心功能"""
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: if device_id is None and "id" in kwargs:
device_id = kwargs.pop('id') device_id = kwargs.pop("id")
if config is None and 'config' in kwargs: if config is None and "config" in kwargs:
config = kwargs.pop('config') config = kwargs.pop("config")
# 设置默认值 # 设置默认值
self.device_id = device_id or "unknown_centrifuge" self.device_id = device_id or "unknown_centrifuge"
@@ -19,100 +21,157 @@ class VirtualCentrifuge:
self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}") self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}")
self.data = {} self.data = {}
# 添加调试信息
print(f"=== VirtualCentrifuge {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数 # 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL') 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_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._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) self._min_temp = self.config.get("min_temp") or kwargs.get("min_temp", 4.0)
# 处理其他kwargs参数,但跳过已知的配置参数 # 处理其他kwargs参数
skip_keys = {'port', 'max_speed', 'max_temp', 'min_temp'} skip_keys = {"port", "max_speed", "max_temp", "min_temp"}
for key, value in kwargs.items(): for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key): if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)
async def initialize(self) -> bool: async def initialize(self) -> bool:
"""Initialize virtual centrifuge""" """Initialize virtual centrifuge"""
print(f"=== VirtualCentrifuge {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual centrifuge {self.device_id}") self.logger.info(f"Initializing virtual centrifuge {self.device_id}")
# 只保留核心状态
self.data.update({ self.data.update({
"status": "Idle", "status": "Idle",
"centrifuge_state": "Stopped", # Stopped, Running, Completed, Error
"current_speed": 0.0, "current_speed": 0.0,
"target_speed": 0.0, "target_speed": 0.0,
"current_temp": 25.0, "current_temp": 25.0,
"target_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, "time_remaining": 0.0,
"progress": 0.0, "progress": 0.0,
"message": "" "message": "Ready for centrifugation"
}) })
return True return True
async def cleanup(self) -> bool: async def cleanup(self) -> bool:
"""Cleanup virtual centrifuge""" """Cleanup virtual centrifuge"""
self.logger.info(f"Cleaning up virtual centrifuge {self.device_id}") 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 return True
async def centrifuge(self, vessel: str, speed: float, time: float, temp: float = 25.0) -> bool: async def centrifuge(
"""Execute centrifuge action - matches Centrifuge action""" self,
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} RPM, time={time}s, temp={temp}°C") 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: if speed > self._max_speed or speed < 100.0:
self.logger.error(f"Speed {speed} exceeds maximum {self._max_speed}") error_msg = f"离心速度 {speed} rpm 超出范围 (100-{self._max_speed} rpm)"
self.data["message"] = f"速度 {speed} 超过最大值 {self._max_speed}" self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"centrifuge_state": "Error",
"message": error_msg
})
return False return False
if temp > self._max_temp or temp < self._min_temp: if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}") error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}-{self._max_temp}°C)"
self.data["message"] = f"温度 {temp} 超出范围 {self._min_temp}-{self._max_temp}" self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"centrifuge_state": "Error",
"message": error_msg
})
return False return False
# 开始离心 # 开始离心
self.data.update({ self.data.update({
"status": "Running", "status": f"离心中: {vessel}",
"centrifuge_state": "Centrifuging", "centrifuge_state": "Running",
"target_speed": speed,
"current_speed": speed, "current_speed": speed,
"target_temp": temp, "target_speed": speed,
"current_temp": temp, "current_temp": temp,
"target_temp": temp,
"time_remaining": time, "time_remaining": time,
"vessel": vessel,
"progress": 0.0, "progress": 0.0,
"message": f"离心中: {vessel} at {speed} RPM" "message": f"Centrifuging {vessel} at {speed} rpm, {temp}°C"
}) })
# 模拟离心过程 try:
simulation_time = min(time, 5.0) # 最多等待5秒用于测试 # 离心过程 - 实时更新进度
await asyncio.sleep(simulation_time) 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({ self.data.update({
"status": "Idle", "status": f"离心完成: {vessel} | {speed} rpm | {time}s",
"centrifuge_state": "Stopped", "centrifuge_state": "Completed",
"current_speed": 0.0,
"target_speed": 0.0,
"time_remaining": 0.0,
"progress": 100.0, "progress": 100.0,
"message": f"离心完成: {vessel}" "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"Centrifuge completed for vessel {vessel}") self.logger.info(f"Centrifugation completed: {vessel} at {speed} rpm for {time}s")
return True 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 @property
def status(self) -> str: def status(self) -> str:
return self.data.get("status", "Unknown") return self.data.get("status", "Unknown")
@property
def centrifuge_state(self) -> str:
return self.data.get("centrifuge_state", "Unknown")
@property @property
def current_speed(self) -> float: def current_speed(self) -> float:
return self.data.get("current_speed", 0.0) return self.data.get("current_speed", 0.0)
@@ -131,19 +190,15 @@ class VirtualCentrifuge:
@property @property
def max_speed(self) -> float: def max_speed(self) -> float:
return self.data.get("max_speed", self._max_speed) return self._max_speed
@property @property
def max_temp(self) -> float: def max_temp(self) -> float:
return self.data.get("max_temp", self._max_temp) return self._max_temp
@property @property
def min_temp(self) -> float: def min_temp(self) -> float:
return self.data.get("min_temp", self._min_temp) return self._min_temp
@property
def centrifuge_state(self) -> str:
return self.data.get("centrifuge_state", "Unknown")
@property @property
def time_remaining(self) -> float: def time_remaining(self) -> float:

View File

@@ -1,151 +1,221 @@
import asyncio import asyncio
import logging import logging
from typing import Dict, Any import time as time_module
from typing import Dict, Any, Optional
class VirtualFilter: 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: if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id') device_id = kwargs.pop('id')
if config is None and 'config' in kwargs: if config is None and 'config' in kwargs:
config = kwargs.pop('config') config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_filter" self.device_id = device_id or "unknown_filter"
self.config = config or {} self.config = config or {}
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}") self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
self.data = {} self.data = {}
# 添加调试信息
print(f"=== VirtualFilter {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数 # 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL') 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_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_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参数,但跳过已知的配置参数 # 处理其他kwargs参数
skip_keys = {'port', 'max_temp', 'max_stir_speed'} skip_keys = {'port', 'max_temp', 'max_stir_speed', 'max_volume'}
for key, value in kwargs.items(): for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key): if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)
async def initialize(self) -> bool: async def initialize(self) -> bool:
"""Initialize virtual filter""" """Initialize virtual filter"""
print(f"=== VirtualFilter {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual filter {self.device_id}") self.logger.info(f"Initializing virtual filter {self.device_id}")
# 按照 Filter.action 的 feedback 字段初始化
self.data.update({ self.data.update({
"status": "Idle", "status": "Idle",
"filter_state": "Ready", "progress": 0.0, # Filter.action feedback
"current_temp": 25.0, "current_temp": 25.0, # Filter.action feedback
"target_temp": 25.0, "filtered_volume": 0.0, # Filter.action feedback
"max_temp": self._max_temp, "current_status": "Ready for filtration", # Filter.action feedback
"stir_speed": 0.0, "message": "Ready for filtration"
"max_stir_speed": self._max_stir_speed,
"filtered_volume": 0.0,
"progress": 0.0,
"message": ""
}) })
return True return True
async def cleanup(self) -> bool: async def cleanup(self) -> bool:
"""Cleanup virtual filter""" """Cleanup virtual filter"""
self.logger.info(f"Cleaning up virtual filter {self.device_id}") 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 return True
async def filter_sample(self, vessel: str, filtrate_vessel: str = "", stir: bool = False, async def filter(
stir_speed: float = 300.0, temp: float = 25.0, self,
continue_heatchill: bool = False, volume: float = 0.0) -> bool: vessel: str,
"""Execute filter action - matches Filter action""" filtrate_vessel: str = "",
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}, stir={stir}, volume={volume}") 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: if temp > self._max_temp or temp < 4.0:
self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}") error_msg = f"温度 {temp}°C 超出范围 (4-{self._max_temp}°C)"
self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}" self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"message": error_msg
})
return False return False
if stir and stir_speed > self._max_stir_speed: if stir and stir_speed > self._max_stir_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}") error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM)"
self.data["message"] = f"搅拌速度 {stir_speed} 超过最大值 {self._max_stir_speed}" 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 return False
# 开始过滤 # 开始过滤
filter_volume = volume if volume > 0 else 50.0
self.data.update({ self.data.update({
"status": "Running", "status": f"过滤中: {vessel}",
"filter_state": "Filtering",
"target_temp": temp,
"current_temp": temp, "current_temp": temp,
"stir_speed": stir_speed if stir else 0.0, "filtered_volume": 0.0,
"vessel": vessel,
"filtrate_vessel": filtrate_vessel,
"target_volume": volume,
"progress": 0.0, "progress": 0.0,
"message": f"过滤中: {vessel}" "current_status": f"Filtering {vessel}{filtrate_vessel}",
"message": f"Starting filtration: {vessel}{filtrate_vessel}"
}) })
# 模拟过滤过程 try:
simulation_time = min(volume / 10.0 if volume > 0 else 5.0, 10.0) # 过滤过程 - 实时更新进度
await asyncio.sleep(simulation_time) 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)
# 过滤完成 # 过滤完成
filtered_vol = volume if volume > 0 else 50.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({ self.data.update({
"status": "Idle", "status": final_status,
"filter_state": "Ready", "progress": 100.0, # Filter.action feedback
"current_temp": 25.0 if not continue_heatchill else temp, "current_temp": final_temp, # Filter.action feedback
"target_temp": 25.0 if not continue_heatchill else temp, "filtered_volume": filter_volume, # Filter.action feedback
"stir_speed": 0.0 if not stir else stir_speed, "current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback
"filtered_volume": filtered_vol, "message": f"Filtration completed: {filter_volume}mL filtered from {vessel}"
"progress": 100.0,
"message": f"过滤完成: {filtered_vol}mL"
}) })
self.logger.info(f"Filter completed: {filtered_vol}mL from {vessel}") self.logger.info(f"Filtration completed: {filter_volume}mL from {vessel} to {filtrate_vessel}")
return True 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 @property
def status(self) -> str: def status(self) -> str:
return self.data.get("status", "Unknown") return self.data.get("status", "Unknown")
@property @property
def filter_state(self) -> str: def progress(self) -> float:
return self.data.get("filter_state", "Unknown") """Filter.action feedback 字段"""
return self.data.get("progress", 0.0)
@property @property
def current_temp(self) -> float: def current_temp(self) -> float:
"""Filter.action feedback 字段"""
return self.data.get("current_temp", 25.0) 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 @property
def filtered_volume(self) -> float: def filtered_volume(self) -> float:
"""Filter.action feedback 字段"""
return self.data.get("filtered_volume", 0.0) return self.data.get("filtered_volume", 0.0)
@property @property
def progress(self) -> float: def current_status(self) -> str:
return self.data.get("progress", 0.0) """Filter.action feedback 字段"""
return self.data.get("current_status", "")
@property @property
def message(self) -> str: 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 asyncio
import logging import logging
import time as time_module # 重命名time模块避免与参数冲突
from typing import Dict, Any from typing import Dict, Any
class VirtualHeatChill: class VirtualHeatChill:
@@ -19,18 +20,13 @@ class VirtualHeatChill:
self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}") self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}")
self.data = {} self.data = {}
# 添加调试信息
print(f"=== VirtualHeatChill {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数 # 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL') 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._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._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) 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'} skip_keys = {'port', 'max_temp', 'min_temp', 'max_stir_speed'}
for key, value in kwargs.items(): for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key): if key not in skip_keys and not hasattr(self, key):
@@ -38,70 +34,177 @@ class VirtualHeatChill:
async def initialize(self) -> bool: async def initialize(self) -> bool:
"""Initialize virtual heat chill""" """Initialize virtual heat chill"""
print(f"=== VirtualHeatChill {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual heat chill {self.device_id}") self.logger.info(f"Initializing virtual heat chill {self.device_id}")
# 初始化状态信息
self.data.update({ self.data.update({
"status": "Idle" "status": "Idle",
"operation_mode": "Idle",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0,
}) })
return True return True
async def cleanup(self) -> bool: async def cleanup(self) -> bool:
"""Cleanup virtual heat chill""" """Cleanup virtual heat chill"""
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}") 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 return True
async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool, async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
stir_speed: float, purpose: str) -> bool: stir_speed: float, purpose: str) -> bool:
"""Execute heat chill action - matches HeatChill action exactly""" """Execute heat chill action - 按实际时间运行,实时更新剩余时间"""
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}, purpose={purpose}") 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: if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}") error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
self.data["status"] = f"温度 {temp} 超出范围" self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False return False
if stir and stir_speed > self._max_stir_speed: if stir and stir_speed > self._max_stir_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}") error_msg = f"搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM"
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围" self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False 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({ 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秒用于测试 while True:
await asyncio.sleep(simulation_time) current_time = time_module.time() # 使用重命名的time模块
elapsed = current_time - start_time
remaining = max(0, total_time - elapsed)
# 加热/冷却完成 # 更新剩余时间和状态
self.data["status"] = f"完成: {vessel} 已达到 {temp}°C" self.data.update({
"remaining_time": remaining,
"status": f"运行中: {status_action} {vessel}{temp}°C | 剩余: {remaining:.0f}s{stir_info}"
})
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C") # 如果时间到了,退出循环
if remaining <= 0:
break
# 等待1秒后再次检查
await asyncio.sleep(1.0)
# 操作完成
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 after {total_time}s")
return True return True
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool: async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
"""Start heat chill - matches HeatChillStart action exactly""" """Start continuous heat chill"""
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C, purpose={purpose}") self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C")
# 验证参数 # 验证参数
if temp > self._max_temp or temp < self._min_temp: if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}") error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
self.data["status"] = f"温度 {temp} 超出范围" self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False 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 return True
async def heat_chill_stop(self, vessel: str) -> bool: 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.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 return True
# 状态属性 - 只保留 action 中定义的 feedback # 状态属性
@property @property
def status(self) -> str: 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 asyncio
import logging import logging
import time as time_module
from typing import Dict, Any from typing import Dict, Any
class VirtualStirrer: 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): 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.logger = logging.getLogger(f"VirtualStirrer.{self.device_id}")
self.data = {} self.data = {}
# 添加调试信息
print(f"=== VirtualStirrer {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数 # 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL') 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', 1500.0)
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1000.0) self._min_speed = self.config.get('min_speed') or kwargs.get('min_speed', 50.0)
# 处理其他kwargs参数,但跳过已知的配置参数 # 处理其他kwargs参数
skip_keys = {'port', 'max_temp', 'max_speed'} skip_keys = {'port', 'max_speed', 'min_speed'}
for key, value in kwargs.items(): for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key): if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)
async def initialize(self) -> bool: async def initialize(self) -> bool:
"""Initialize virtual stirrer""" """Initialize virtual stirrer"""
print(f"=== VirtualStirrer {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual stirrer {self.device_id}") self.logger.info(f"Initializing virtual stirrer {self.device_id}")
# 初始化状态信息
self.data.update({ 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 return True
async def cleanup(self) -> bool: async def cleanup(self) -> bool:
"""Cleanup virtual stirrer""" """Cleanup virtual stirrer"""
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}") 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 return True
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool: 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") self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
# 验证参数 # 验证参数
if stir_speed > self._max_speed: if stir_speed > self._max_speed or stir_speed < self._min_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}") error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围" self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False return False
# 开始搅拌 # === 第一阶段:搅拌 ===
self.data["status"] = f"搅拌中: {stir_speed} RPM, {stir_time}s" start_time = time_module.time()
total_stir_time = stir_time
# 模拟搅拌时间 self.data.update({
simulation_time = min(stir_time, 10.0) # 最多等待10秒用于测试 "status": f"搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s",
await asyncio.sleep(simulation_time) "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: if settling_time > 0:
self.data["status"] = f"沉降中: {settling_time}s" start_settling_time = time_module.time()
settling_simulation = min(settling_time, 5.0) # 最多等待5秒 total_settling_time = settling_time
await asyncio.sleep(settling_simulation)
# 操作完成 self.data.update({
self.data["status"] = "搅拌完成" "status": f"沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s",
"operation_mode": "Settling",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": total_settling_time,
})
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s") # 沉降过程 - 实时更新剩余时间
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)
# === 操作完成 ===
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 + settling {settling_time}s")
return True return True
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool: 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}") self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
# 验证参数 # 验证参数
if stir_speed > self._max_speed: if stir_speed > self._max_speed or stir_speed < self._min_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}") error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围" self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False 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 return True
async def stop_stir(self, vessel: str) -> bool: 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.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 return True
# 状态属性 - 只保留 action 中定义的 feedback # 状态属性
@property @property
def status(self) -> str: 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 asyncio
import time
from enum import Enum
from typing import Union, Optional
import logging import logging
from typing import Dict, Any, Optional
class VirtualPumpMode(Enum):
Normal = 0
AccuratePos = 1
AccuratePosVel = 2
class VirtualTransferPump: class VirtualTransferPump:
"""Virtual pump device specifically for Transfer protocol""" """虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): def __init__(self, device_id: str = None, config: dict = 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')
# 设置默认值 Args:
self.device_id = device_id or "unknown_transfer_pump" device_id: 设备ID
self.config = config or {} 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.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: async def initialize(self) -> bool:
"""Initialize virtual transfer pump""" """初始化虚拟泵"""
print(f"=== VirtualTransferPump {self.device_id} initialize() called! ===") self.logger.info(f"Initializing virtual pump {self.device_id}")
self.logger.info(f"Initializing virtual transfer pump {self.device_id}") self._status = "Idle"
self.data.update({ self._position = 0.0
"status": "Idle", self._current_volume = 0.0
"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"
})
return True return True
async def cleanup(self) -> bool: 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 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 @property
def status(self) -> str: def status(self) -> str:
return self.data.get("status", "Unknown") return self._status
@property
def position(self) -> float:
"""当前柱塞位置 (ml)"""
return self._position
@property @property
def current_volume(self) -> float: def current_volume(self) -> float:
return self.data.get("current_volume", 0.0) """当前注射器中的体积 (ml)"""
return self._current_volume
@property @property
def max_volume(self) -> float: def max_velocity(self) -> float:
return self.data.get("max_volume", self._max_volume) return self._max_velocity
@property @property
def transfer_rate(self) -> float: def transfer_rate(self) -> float:
return self.data.get("transfer_rate", self._transfer_rate) return self._transfer_rate
@property def set_max_velocity(self, velocity: float):
def from_vessel(self) -> str: """设置最大速度 (ml/s)"""
return self.data.get("from_vessel", "") 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 get_status(self) -> str:
def to_vessel(self) -> str: """获取泵状态"""
return self.data.get("to_vessel", "") return self._status
@property async def _simulate_operation(self, duration: float):
def progress(self) -> float: """模拟操作延时"""
return self.data.get("progress", 0.0) self._status = "Busy"
await asyncio.sleep(duration)
self._status = "Idle"
@property def _calculate_duration(self, volume: float, velocity: float = None) -> float:
def transferred_volume(self) -> float: """计算操作持续时间"""
return self.data.get("transferred_volume", 0.0) if velocity is None:
velocity = self._max_velocity
return abs(volume) / velocity
@property # 新的set_position方法 - 专门用于SetPumpPosition动作
def current_status(self) -> str: async def set_position(self, position: float, max_velocity: float = None):
return self.data.get("current_status", "Ready") """
移动到绝对位置 - 专门用于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
}
# 其他泵操作方法
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 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import socket
import json
import base64 import base64
import argparse import json
import sys import socket
import time import time
@@ -96,17 +94,20 @@ class ZhidaClient:
def abort(self) -> dict: def abort(self) -> dict:
return self._send_command({"command": "abort"}) return self._send_command({"command": "abort"})
"""
a,b,c
1,2,4
2,4,5
"""
client = ZhidaClient() if __name__ == "__main__":
# 连接
client.connect() """
# 获取状态 a,b,c
print(client.status) 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): 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'. purpose: str
product_phase: str # 'top' or 'bottom'. Phase that product will be in. product_phase: str
from_vessel: str #Contents of from_vessel are transferred to separation_vessel and separation is performed. from_vessel: str
separation_vessel: str # Vessel in which separation of phases will be carried out. separation_vessel: str
to_vessel: str # Vessel to send product phase to. to_vessel: str
waste_phase_to_vessel: str # Optional. Vessel to send waste phase to. waste_phase_to_vessel: str
solvent: str # Optional. Solvent to add to separation vessel after contents of from_vessel has been transferred to create two phases. solvent: str
solvent_volume: float # Optional. Volume of solvent to add. solvent_volume: float
through: str # Optional. Solid chemical to send product phase through on way to to_vessel, e.g. 'celite'. through: str
repeats: int # Optional. Number of separations to perform. repeats: int
stir_time: float # Optional. Time stir for after adding solvent, before separation of phases. stir_time: float
stir_speed: float # Optional. Speed to stir at after adding solvent, before separation of phases. stir_speed: float
settling_time: float # Optional. Time settling_time: float
class EvaporateProtocol(BaseModel): class EvaporateProtocol(BaseModel):
@@ -67,6 +67,7 @@ class AGVTransferProtocol(BaseModel):
to_repo: dict to_repo: dict
from_repo_position: str from_repo_position: str
to_repo_position: str to_repo_position: str
#=============新添加的新的协议================ #=============新添加的新的协议================
class AddProtocol(BaseModel): class AddProtocol(BaseModel):
vessel: str vessel: str
@@ -84,16 +85,16 @@ class CentrifugeProtocol(BaseModel):
vessel: str vessel: str
speed: float speed: float
time: float time: float
temp: float # 移除默认值 temp: float
class FilterProtocol(BaseModel): class FilterProtocol(BaseModel):
vessel: str vessel: str
filtrate_vessel: str # 移除默认值 filtrate_vessel: str
stir: bool # 移除默认值 stir: bool
stir_speed: float # 移除默认值 stir_speed: float
temp: float # 移除默认值 temp: float
continue_heatchill: bool # 移除默认值 continue_heatchill: bool
volume: float # 移除默认值 volume: float
class HeatChillProtocol(BaseModel): class HeatChillProtocol(BaseModel):
vessel: str vessel: str
@@ -137,45 +138,80 @@ class TransferProtocol(BaseModel):
solid: bool = False solid: bool = False
class CleanVesselProtocol(BaseModel): class CleanVesselProtocol(BaseModel):
vessel: str # 要清洗的容器名称 vessel: str
solvent: str # 用于清洗容器的溶剂名称 solvent: str
volume: float # 清洗溶剂的体积,可选参数 volume: float
temp: float # 清洗时的温度,可选参数 temp: float
repeats: int = 1 # 清洗操作的重复次数,默认为 1 repeats: int = 1
class DissolveProtocol(BaseModel): class DissolveProtocol(BaseModel):
vessel: str # 装有要溶解物质的容器名称 vessel: str
solvent: str # 用于溶解物质的溶剂名称 solvent: str
volume: float # 溶剂的体积,可选参数 volume: float
amount: str = "" # 要溶解物质的量,可选参数 amount: str = ""
temp: float = 25.0 # 溶解时的温度,可选参数 temp: float = 25.0
time: float = 0.0 # 溶解的时间,可选参数 time: float = 0.0
stir_speed: float = 0.0 # 搅拌速度,可选参数 stir_speed: float = 0.0
class FilterThroughProtocol(BaseModel): class FilterThroughProtocol(BaseModel):
from_vessel: str # 源容器的名称,即物质起始所在的容器 from_vessel: str
to_vessel: str # 目标容器的名称,物质过滤后要到达的容器 to_vessel: str
filter_through: str # 过滤时所通过的介质,如滤纸、柱子等 filter_through: str
eluting_solvent: str = "" # 洗脱溶剂的名称,可选参数 eluting_solvent: str = ""
eluting_volume: float = 0.0 # 洗脱溶剂的体积,可选参数 eluting_volume: float = 0.0
eluting_repeats: int = 0 # 洗脱操作的重复次数,默认为 0 eluting_repeats: int = 0
residence_time: float = 0.0 # 物质在过滤介质中的停留时间,可选参数 residence_time: float = 0.0
class RunColumnProtocol(BaseModel): class RunColumnProtocol(BaseModel):
from_vessel: str # 源容器的名称,即样品起始所在的容器 from_vessel: str
to_vessel: str # 目标容器的名称,分离后的样品要到达的容器 to_vessel: str
column: str # 所使用的柱子的名称 column: str
class WashSolidProtocol(BaseModel): class WashSolidProtocol(BaseModel):
vessel: str # 装有固体物质的容器名称 vessel: str
solvent: str # 用于清洗固体的溶剂名称 solvent: str
volume: float # 清洗溶剂的体积 volume: float
filtrate_vessel: str = "" # 滤液要收集到的容器名称,可选参数 filtrate_vessel: str = ""
temp: float = 25.0 # 清洗时的温度,可选参数 temp: float = 25.0
stir: bool = False # 是否在清洗过程中搅拌,默认为 False stir: bool = False
stir_speed: float = 0.0 # 搅拌速度,可选参数 stir_speed: float = 0.0
time: float = 0.0 # 清洗的时间,可选参数 time: float = 0.0
repeats: int = 1 # 清洗操作的重复次数,默认为 1 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 # End Protocols

View File

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

View File

@@ -1,7 +1,102 @@
serial: serial:
description: Serial communication interface, used when sharing same serial port for multiple devices
class: class:
module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode action_value_mappings:
type: ros2 auto-handle_serial_request:
feedback: {}
goal: {}
goal_default:
request: null
response: null
handles: []
result: {}
schema: 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: {} 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
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 @@
# 光学表征设备:红外、紫外可见、拉曼等 hplc.agilent:
raman_home_made:
description: Raman spectroscopy device
class: class:
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
type: python
status_types:
status: String
action_value_mappings: action_value_mappings:
raman_cmd: auto-check_status:
type: SendCmd 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:
feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema: schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: properties:
status: status:
type: string type: string
required: required:
- status - status
additionalProperties: false title: SendCmd_Feedback
type: object 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:
execute_command_from_outer:
type: SendCmd
goal: goal:
command: command description: Action 目标 - 从客户端发送到服务器
feedback: {}
result:
success: success
schema:
properties: properties:
device_status: command:
type: string 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: could_run:
type: boolean type: boolean
data_file:
type: array
device_status:
type: string
driver_init_ok: driver_init_ok:
type: boolean type: boolean
is_running:
type: boolean
finish_status: finish_status:
type: string type: string
is_running:
type: boolean
status_text: status_text:
type: string type: string
success:
type: boolean
required: required:
- status_text
- device_status - device_status
- could_run - could_run
- driver_init_ok - driver_init_ok
- is_running - is_running
- success
- finish_status - finish_status
- status_text - data_file
additionalProperties: false 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 type: object

View File

@@ -1,9 +1,31 @@
hotel.thermo_orbitor_rs2_hotel: hotel.thermo_orbitor_rs2_hotel:
description: Thermo Orbitor RS2 Hotel
class: class:
action_value_mappings: {}
module: unilabos.devices.resource_container.container:HotelContainer module: unilabos.devices.resource_container.container:HotelContainer
status_types:
rotation: String
type: python 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: model:
type: device
mesh: thermo_orbitor_rs2_hotel mesh: thermo_orbitor_rs2_hotel
type: device

View File

@@ -1,56 +1,582 @@
laiyu_add_solid: laiyu_add_solid:
description: Laiyu Add Solid
class: class:
module: unilabos.devices.laiyu_add_solid.laiyu:Laiyu
type: python
status_types: {}
action_value_mappings: 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: move_to_xyz:
type: Point3DSeparateInput feedback: {}
goal: goal:
x: x x: x
y: y y: y
z: z z: z
feedback: {} goal_default:
x: 0.0
y: 0.0
z: 0.0
handles: []
result: {} result: {}
pick_powder_tube:
type: IntSingleInput
goal:
int_input: int_input
feedback: {}
result: {}
put_powder_tube:
type: IntSingleInput
goal:
int_input: int_input
feedback: {}
result: {}
reset:
type: EmptyIn
goal: {}
feedback: {}
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: schema:
description: ROS Action Point3DSeparateInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {} 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:
feedback: {}
goal:
int_input: int_input
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:
feedback: {}
goal:
int_input: int_input
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:
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.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: moveit.arm_slider:
description: Arm with Slider
model:
type: device
mesh: arm_slider
class: class:
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
type: python
action_value_mappings: action_value_mappings:
set_position: auto-check_tf_update_actions:
type: SendCmd
goal:
command: command
feedback: {} feedback: {}
goal: {}
goal_default: {}
handles: []
result: {} 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: pick_and_place:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: {} 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: set_status:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: {} 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: rotavap.one:
description: Separator device with homemade grbl controller
class: class:
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
type: python
status_types:
sensordata: Float64
status: String
action_value_mappings: action_value_mappings:
stir: auto-cmd_write:
type: Stir feedback: {}
goal: {}
goal_default:
cmd: null
handles: []
result: {}
schema:
description: cmd_write的参数schema
properties:
feedback: {}
goal: goal:
stir_time: stir_time, properties:
stir_speed: stir_speed cmd:
settling_time: settling_time type: string
feedback: required:
status: status - cmd
result: type: object
success: success result: {}
valve_open_cmd: required:
type: SendCmd - 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:
feedback: {}
goal: goal:
command: command command: command
feedback: goal_default:
status: status command: ''
result": handles: []
result:
success: success success: success
schema: schema:
type: object description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: properties:
status: status:
type: string type: string
description: The status of the device
sensordata:
type: number
description: 电导传感器数据
required: required:
- status - status
- sensordata title: SendCmd_Feedback
additionalProperties: false type: object
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:
set_timer:
type: SendCmd
goal: goal:
command: command 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: {} 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: result:
success: success success: success
schema: schema:
type: object description: ROS Action Stir 的 JSON Schema
properties: properties:
temperature: feedback:
type: number description: Action 反馈 - 执行过程中从服务器发送到客户端
description: 旋蒸水浴温度 properties:
pump_time: status:
type: number type: string
description: The pump time of the device
rotate_time:
type: number
description: The rotate time of the device
required: required:
- pump_time - status
- rotate_time title: Stir_Feedback
additionalProperties: false 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: solenoid_valve:
description: Runze Syringe pump with valve
class: 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 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: hardware_interface:
name: hardware_interface name: hardware_interface
read: send_command read: send_command
write: send_command write: send_command
schema: module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
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
status_types: status_types:
status: String max_velocity: float
valve_position: String mode: int
action_value_mappings: plunger_position: String
open: position: float
type: EmptyIn status: str
goal: {} valve_position: str
feedback: {} velocity_end: String
result: {} velocity_grade: String
close: velocity_init: String
type: EmptyIn type: python
goal: {} description: Runze Syringe pump with valve
feedback: {} handles: []
result: {} icon: ''
handles:
input:
- handler_key: fluid-input
label: Fluid Input
data_type: fluid
output:
- handler_key: fluid-output
label: Fluid Output
data_type: fluid
init_param_schema: init_param_schema:
type: object config:
properties: properties:
address:
default: '1'
type: string
max_volume:
default: 25.0
type: number
mode:
type: string
port: port:
type: string type: string
description: "通信端口"
default: "COM6"
required: required:
- port - port
type: object
solenoid_valve: data:
description: Solenoid valve properties:
class: max_velocity:
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve type: number
type: python mode:
status_types: type: integer
status: String plunger_position:
valve_position: String type: string
action_value_mappings: position:
set_valve_position: type: number
type: StrSingleInput status:
goal: type: string
string: position valve_position:
feedback: {} type: string
result: {} 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: agv.SEER:
description: SEER AGV
class: class:
module: unilabos.devices.agv.agv_navigator:AgvNavigator
type: python
status_types:
pose: Float64MultiArray
status: String
action_value_mappings: 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: send_nav_task:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema: schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: properties:
pose:
type: array
items:
type: number
status: status:
type: string type: string
required: required:
- status - status
additionalProperties: false 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
type: object type: object

View File

@@ -1,37 +1,199 @@
robotic_arm.UR: robotic_arm.UR:
description: UR robotic arm
class: 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: 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: move_pos_task:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema: 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: properties:
arm_pose: arm_pose:
type: array type: array
items:
type: number
gripper_pose:
type: number
arm_status: arm_status:
type: string type: string
description: 机械臂设备状态 gripper_pose:
type: number
gripper_status: gripper_status:
type: string type: string
description: 机械爪设备状态
required: required:
- arm_pose
- gripper_pose
- arm_status - arm_status
- gripper_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: gripper.misumi_rz:
description: Misumi RZ gripper
class: class:
module: unilabos.devices.motor:Grasp.EleGripper
type: python
status_types:
status: String
action_value_mappings: 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: execute_command_from_outer:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: 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.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: linear_motion.grbl:
description: Grbl CNC
class: class:
module: unilabos.devices.cnc.grbl_sync:GrblCNC
type: python
action_value_mappings: action_value_mappings:
move_through_points: &move_through_points auto-initialize:
type: NavigateThroughPoses feedback: {}
goal: goal: {}
poses[].pose.position: positions[] goal_default: {}
feedback: handles: []
current_pose.pose.position: position
navigation_time.sec: time_spent
estimated_time_remaining.sec: time_remaining
number_of_poses_remaining: pose_number_remaining
result: {}
set_spindle_speed:
type: SingleJointPosition
goal:
position: spindle_speed
feedback:
position: spindle_speed
result: {} result: {}
schema: schema:
description: initialize的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object 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: properties:
position: position:
type: array type: string
items:
type: number
description: The position of the device
spindle_speed:
type: number
description: The spindle speed of the device
required: required:
- position - 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 - spindle_speed
additionalProperties: false type: object
result: {}
required:
motor.iCL42: - goal
description: iCL42 motor title: set_spindle_speed参数
class: type: object
module: unilabos.devices.motor.iCL42:iCL42Driver type: UniLabJsonCommand
type: python 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:
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:
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: status_types:
motor_position: Int64 position: unilabos.messages:Point3D
is_executing_run: Bool spindle_speed: float
success: Bool 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
status:
type: string
required:
- status
- position
- spindle_speed
type: object
motor.iCL42:
class:
action_value_mappings: 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: execute_command_from_outer:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: 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: lh_joint_publisher:
class: 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 module: unilabos.devices.ros_dev.liquid_handler_joint_publisher:LiquidHandlerJointPublisher
status_types: {}
type: ros2 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: chiller:
description: DaLong heater stirrer
class: class:
module: unilabos.devices.heaterstirrer.dalong:HeaterStirrer_DaLong
type: python
status_types:
temp: Float64
temp_warning: Float64
stir_speed: Float64
action_value_mappings: action_value_mappings:
set_temp_warning: auto-build_modbus_frame:
type: SendCmd
goal:
command: temp
feedback: {} 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: result:
success: success success: success
set_temp_target: schema:
type: SendCmd description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal: goal:
command: temp description: Action 目标 - 从客户端发送到服务器
feedback: {} properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result: result:
success: success 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.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: {}
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: heatchill:
type: HeatChill
goal:
vessel: vessel
temp: temp
time: time
purpose: purpose
feedback: feedback:
status: status 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: goal:
command: command purpose: purpose
feedback: {} temp: temp
time: time
vessel: vessel
goal_default:
purpose: ''
stir: false
stir_speed: 0.0
temp: 0.0
time: 0.0
vessel: ''
handles: []
result: result:
success: success success: success
tempsensor: schema:
description: Temperature sensor description: ROS Action HeatChill 的 JSON Schema
class: properties:
module: unilabos.devices.temperature.sensor_node:TempSensorNode feedback:
type: python 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: status_types:
value: Float64 status: str
warning: Float64 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: 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: set_warning:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: 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.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: gas_source.mock:
description: Mock gas source
class: class:
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
type: python
status_types:
status: String
action_value_mappings: action_value_mappings:
open: auto-close:
type: EmptyIn
goal: {}
feedback: {} feedback: {}
goal: {}
goal_default: {}
handles: []
result: {} 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: close:
type: EmptyIn
goal: {}
feedback: {} feedback: {}
goal: {}
goal_default: {}
handles: []
result: {} 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: set_status:
type: StrSingleInput feedback: {}
goal: goal:
string: string string: string
feedback: {} goal_default:
string: ''
handles: []
result: {} result: {}
handles: schema:
input: description: ROS Action StrSingleInput 的 JSON Schema
- handler_key: fluid-input properties:
label: Fluid Input feedback:
data_type: fluid description: Action 反馈 - 执行过程中从服务器发送到客户端
io_type: target properties: {}
data_source: handle required: []
data_key: fluid_in title: StrSingleInput_Feedback
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 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:
- data_key: fluid_out
data_source: executor
data_type: fluid
handler_key: out
io_type: source
label: out
icon: ''
init_param_schema:
config:
properties: properties:
port: port:
default: COM6
type: string
required: []
type: object
data:
properties:
status:
type: string type: string
description: "通信端口"
default: "COM6"
required: required:
- port - 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: zhida_hplc:
description: Zhida HPLC
class: class:
module: unilabos.devices.zhida_hplc.zhida:ZhidaClient
type: python
status_types:
status: String
action_value_mappings: 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: start:
type: StrSingleInput feedback: {}
goal: goal:
string: string string: string
feedback: {} goal_default:
string: ''
handles: []
result: {} result: {}
abort:
type: EmptyIn
goal: {}
feedback: {}
result: {}
get_methods:
type: EmptyIn
goal: {}
feedback: {}
result: {}
schema: schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {} 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 io
import os import os
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, Dict, List
import yaml 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 import logger
from unilabos.utils.decorator import singleton 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] DEFAULT_PATHS = [Path(__file__).absolute().parent]
@@ -32,7 +35,7 @@ class Registry:
# 其他状态变量 # 其他状态变量
# self.is_host_mode = False # 移至BasicConfig中 # self.is_host_mode = False # 移至BasicConfig中
def setup(self): def setup(self, complete_registry=False):
# 检查是否已调用过setup # 检查是否已调用过setup
if self._setup_called: if self._setup_called:
logger.critical("[UniLab Registry] setup方法已被调用过不允许多次调用") logger.critical("[UniLab Registry] setup方法已被调用过不允许多次调用")
@@ -86,13 +89,15 @@ class Registry:
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal)) io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal))
), ),
"handles": { "handles": {
"output": [{ "output": [
{
"handler_key": "labware", "handler_key": "labware",
"label": "Labware", "label": "Labware",
"data_type": "resource", "data_type": "resource",
"data_source": "handle", "data_source": "handle",
"data_key": "liquid" "data_key": "liquid",
}] }
]
}, },
}, },
"test_latency": { "test_latency": {
@@ -110,7 +115,6 @@ class Registry:
"registry_type": "device", "registry_type": "device",
"handles": [], "handles": [],
"init_param_schema": {}, "init_param_schema": {},
"schema": {"properties": {}, "additionalProperties": False, "type": "object"},
"file_path": "/", "file_path": "/",
} }
} }
@@ -121,13 +125,13 @@ class Registry:
sys_path = path.parent sys_path = path.parent
logger.debug(f"[UniLab Registry] Path {i+1}/{len(self.registry_paths)}: {sys_path}") logger.debug(f"[UniLab Registry] Path {i+1}/{len(self.registry_paths)}: {sys_path}")
sys.path.append(str(sys_path)) sys.path.append(str(sys_path))
self.load_device_types(path) self.load_device_types(path, complete_registry)
self.load_resource_types(path) self.load_resource_types(path, complete_registry)
logger.info("[UniLab Registry] 注册表设置完成") logger.info("[UniLab Registry] 注册表设置完成")
# 标记setup已被调用 # 标记setup已被调用
self._setup_called = True 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() abs_path = Path(path).absolute()
resource_path = abs_path / "resources" resource_path = abs_path / "resources"
files = list(resource_path.glob("*/*.yaml")) files = list(resource_path.glob("*/*.yaml"))
@@ -176,7 +180,14 @@ class Registry:
if not type_name or type_name == "": if not type_name or type_name == "":
logger.warning(f"[UniLab Registry] 设备 {device_id}{field_name} 类型为空,跳过替换") logger.warning(f"[UniLab Registry] 设备 {device_id}{field_name} 类型为空,跳过替换")
return type_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) type_class = msg_converter_manager.get_class(type_name)
else: else:
type_class = msg_converter_manager.search_class(type_name) 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}") logger.error(f"[UniLab Registry] 无法找到类型 '{type_name}' 用于设备 {device_id}{field_name}")
sys.exit(1) 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() abs_path = Path(path).absolute()
devices_path = abs_path / "devices" devices_path = abs_path / "devices"
device_comms_path = abs_path / "device_comms" 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 from unilabos.app.web.utils.action_utils import get_yaml_from_goal_type
for i, file in enumerate(files): 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: if data:
# 在添加到注册表前处理类型替换 # 在添加到注册表前处理类型替换
for device_id, device_config in data.items(): for device_id, device_config in data.items():
# 添加文件路径信息 - 使用规范化的完整文件路径 # 添加文件路径信息 - 使用规范化的完整文件路径
device_config["file_path"] = str(file.absolute()).replace("\\", "/")
if "description" not in device_config: if "description" not in device_config:
device_config["description"] = "" device_config["description"] = ""
if "icon" not in device_config: if "icon" not in device_config:
@@ -213,42 +321,128 @@ class Registry:
device_config["handles"] = [] device_config["handles"] = []
if "init_param_schema" not in device_config: if "init_param_schema" not in device_config:
device_config["init_param_schema"] = {} device_config["init_param_schema"] = {}
device_config["registry_type"] = "device"
if "class" in device_config: if "class" in device_config:
# 处理状态类型 if "status_types" not in device_config["class"]:
if "status_types" 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(): for status_name, status_type in device_config["class"]["status_types"].items():
device_config["class"]["status_types"][status_name] = self._replace_type_with_class( if status_type in ["Any", "None", "Unknown"]:
status_type, device_id, f"状态 {status_name}" 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"]
) )
# 处理动作值映射 device_config.pop("schema", None)
if "action_value_mappings" in device_config["class"]: 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(): for action_name, action_config in device_config["class"]["action_value_mappings"].items():
if "handles" not in action_config: if "handles" not in action_config:
action_config["handles"] = [] action_config["handles"] = []
if "type" in action_config: if "type" in action_config:
action_config["type"] = self._replace_type_with_class( action_type_str: str = action_config["type"]
action_config["type"], device_id, f"动作 {action_name}" # 通过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( 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: else:
logger.warning( logger.warning(
f"[UniLab Registry] 设备 {device_id} 的动作 {action_name} 类型为空,跳过替换" f"[UniLab Registry] 设备 {device_id} 的动作 {action_name} 类型为空,跳过替换"
) )
complete_data[device_id] = copy.deepcopy(dict(sorted(device_config.items()))) # 稍后dump到文件
self.device_type_registry.update(data) 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 device_id in data.keys(): 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( logger.debug(
f"[UniLab Registry] Device-{current_device_number} File-{i+1}/{len(files)} Add {device_id} " f"[UniLab Registry] Device-{current_device_number} File-{i+1}/{len(files)} Add {device_id} "
+ f"[{data[device_id].get('name', '未命名设备')}]" + f"[{data[device_id].get('name', '未命名设备')}]"
) )
current_device_number += 1 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: else:
logger.debug( logger.debug(
f"[UniLab Registry] Device File-{i+1}/{len(files)} Not Valid YAML File: {file.absolute()}" 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): def obtain_registry_device_info(self):
devices = [] devices = []
for device_id, device_info in self.device_type_registry.items(): 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) devices.append(msg)
return devices return devices
@@ -273,7 +488,7 @@ class Registry:
lab_registry = Registry() lab_registry = Registry()
def build_registry(registry_paths=None): def build_registry(registry_paths=None, complete_registry=False):
""" """
构建或获取Registry单例实例 构建或获取Registry单例实例
@@ -297,6 +512,6 @@ def build_registry(registry_paths=None):
lab_registry.registry_paths.append(path) lab_registry.registry_paths.append(path)
# 初始化注册表 # 初始化注册表
lab_registry.setup() lab_registry.setup(complete_registry)
return lab_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