Compare commits

..

3 Commits

Author SHA1 Message Date
Xuwznln
7db3123547 0.9.5 Candidate (#47)
* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

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

* Device visualization (#39)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

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

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#41)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

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

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

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

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

* 补充日志

* Device visualization (#42)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* unilab添加moveit启动

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

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>

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

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

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

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

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

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

* 更名Action

---------

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

* bump version & protocol fix

* hotfix: Add macos_sdk_config (#46)

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

* include device_mesh when pip install

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
Co-authored-by: hh. <103566763+Mile-Away@users.noreply.github.com>
Co-authored-by: quehh <scienceol@outlook.com>
2025-06-13 13:35:53 +08:00
Xuwznln
6da7a20a7a bump version & protocol fix (#45)
* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

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

* Device visualization (#39)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

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

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#41)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

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

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

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

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

* 补充日志

* Device visualization (#42)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* unilab添加moveit启动

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

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>

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

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

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

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

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

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

* 更名Action

---------

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

* bump version & protocol fix

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
2025-06-12 22:10:30 +08:00
Xuwznln
aa1c67de29 Device Visualization & Mock Devices (#44) [37-biomek-i5i7 (#40), Device visualization (#39), Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)]
* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

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

* Device visualization (#39)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

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

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* Device visualization (#41)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

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

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

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

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

* 补充日志

* Device visualization (#42)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>

* Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

* 规范模拟设备代码与注册表信息

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

* Add Virtual Device, Action, YAML, Protocol for Organic Syn

* 单独分类测试的protocol文件夹

* 更名Action

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
2025-06-12 21:01:04 +08:00
120 changed files with 12075 additions and 245 deletions

View File

@@ -1,3 +1,5 @@
recursive-include unilabos/registry *.yaml
recursive-include unilabos/app/web *.html
recursive-include unilabos/app/web *.css
recursive-include unilabos/device_mesh/devices *
recursive-include unilabos/device_mesh/resources *

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
# Currently, you need to install the `unilabos_msgs` package
# You can download the system-specific package from the Release page
conda install ros-humble-unilabos-msgs-0.9.3-xxxxx.tar.bz2
conda install ros-humble-unilabos-msgs-0.9.5-xxxxx.tar.bz2
# Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装
conda install ros-humble-unilabos-msgs-0.9.3-xxxxx.tar.bz2
conda install ros-humble-unilabos-msgs-0.9.5-xxxxx.tar.bz2
# 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -0,0 +1,7 @@
CONDA_BUILD_SYSROOT:
- /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
MACOSX_DEPLOYMENT_TARGET:
- "11.0"
CONDA_SUBDIR:
- osx-arm64
# boa build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/ros-humble-unilabos-msgs

View File

@@ -1,6 +1,6 @@
package:
name: ros-humble-unilabos-msgs
version: 0.9.3
version: 0.9.5
source:
path: ../../unilabos_msgs
folder: ros-humble-unilabos-msgs/src/work

View File

@@ -1,6 +1,6 @@
package:
name: unilabos
version: "0.9.3"
version: "0.9.5"
source:
path: ../..

View File

@@ -4,7 +4,7 @@ package_name = 'unilabos'
setup(
name=package_name,
version='0.9.3',
version='0.9.5',
packages=find_packages(),
include_package_data=True,
install_requires=['setuptools'],

View File

@@ -0,0 +1,296 @@
{
"nodes": [
{
"id": "MockChiller1",
"name": "模拟冷却器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_chiller",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"current_temperature": 25.0,
"target_temperature": 25.0,
"status": "Idle",
"is_cooling": false,
"is_heating": false,
"vessel": "",
"purpose": ""
}
},
{
"id": "MockFilter1",
"name": "模拟过滤器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_filter",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"is_filtering": false,
"flow_rate": 0.0,
"filter_life": 100.0,
"vessel": "",
"filtrate_vessel": "",
"filtered_volume": 0.0,
"target_volume": 0.0,
"progress": 0.0,
"stir": false,
"stir_speed": 0.0,
"temperature": 25.0,
"continue_heatchill": false
}
},
{
"id": "MockHeater1",
"name": "模拟加热器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_heater",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"current_temperature": 25.0,
"target_temperature": 25.0,
"status": "Idle",
"is_heating": false,
"heating_power": 0.0,
"max_temperature": 300.0,
"vessel": "Unknown",
"purpose": "Unknown",
"stir": false,
"stir_speed": 0.0
}
},
{
"id": "MockPump1",
"name": "模拟泵设备",
"children": [],
"parent": null,
"type": "device",
"class": "mock_pump",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"current_device": "MockPump1",
"pump_state": "Stopped",
"flow_rate": 0.0,
"target_flow_rate": 0.0,
"pressure": 0.0,
"total_volume": 0.0,
"max_flow_rate": 100.0,
"max_pressure": 10.0,
"from_vessel": "",
"to_vessel": "",
"transfer_volume": 0.0,
"amount": "",
"transfer_time": 0.0,
"is_viscous": false,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"is_solid": false,
"time_spent": 0.0,
"time_remaining": 0.0
}
},
{
"id": "MockRotavap1",
"name": "模拟旋转蒸发器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_rotavap",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"rotate_state": "Stopped",
"rotate_time": 0.0,
"rotate_speed": 0.0,
"pump_state": "Stopped",
"pump_time": 0.0,
"vacuum_level": 1013.25,
"temperature": 25.0,
"target_temperature": 25.0,
"success": "True"
}
},
{
"id": "MockSeparator1",
"name": "模拟分离器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_separator",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"settling_time": 0.0,
"valve_state": "Closed",
"shake_time": 0.0,
"shake_status": "Not Shaking",
"current_device": "MockSeparator1",
"purpose": "",
"product_phase": "",
"from_vessel": "",
"separation_vessel": "",
"to_vessel": "",
"waste_phase_to_vessel": "",
"solvent": "",
"solvent_volume": 0.0,
"through": "",
"repeats": 1,
"stir_time": 0.0,
"stir_speed": 0.0,
"time_spent": 0.0,
"time_remaining": 0.0
}
},
{
"id": "MockSolenoidValve1",
"name": "模拟电磁阀",
"children": [],
"parent": null,
"type": "device",
"class": "mock_solenoid_valve",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"valve_status": "Closed"
}
},
{
"id": "MockStirrer1NEW",
"name": "模拟搅拌器(new)",
"children": [],
"parent": null,
"type": "device",
"class": "mock_stirrer_new",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"vessel": "",
"purpose": "",
"stir_speed": 0.0,
"target_stir_speed": 0.0,
"stir_state": "Stopped",
"stir_time": 0.0,
"settling_time": 0.0,
"progress": 0.0,
"max_stir_speed": 2000.0
}
},
{
"id": "MockStirrer1",
"name": "模拟搅拌器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_stirrer",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"stir_speed": 0.0,
"target_stir_speed": 0.0,
"stir_state": "Stopped",
"temperature": 25.0,
"target_temperature": 25.0,
"heating_state": "Off",
"heating_power": 0.0,
"max_stir_speed": 2000.0,
"max_temperature": 300.0
}
},
{
"id": "MockVacuum1",
"name": "模拟真空泵",
"children": [],
"parent": null,
"type": "device",
"class": "mock_vacuum",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"power_state": "Off",
"pump_state": "Stopped",
"vacuum_level": 1013.25,
"target_vacuum": 50.0,
"pump_speed": 0.0,
"pump_efficiency": 95.0,
"max_pump_speed": 100.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,30 @@
{
"nodes": [
{
"id": "MockChiller1",
"name": "模拟冷却器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_chiller",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"current_temperature": 25.0,
"target_temperature": 25.0,
"status": "Idle",
"is_cooling": false,
"is_heating": false,
"vessel": "",
"purpose": ""
}
}
],
"links": []
}

View File

@@ -0,0 +1,36 @@
{
"nodes": [
{
"id": "MockFilter1",
"name": "模拟过滤器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_filter",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"is_filtering": false,
"flow_rate": 0.0,
"filter_life": 100.0,
"vessel": "",
"filtrate_vessel": "",
"filtered_volume": 0.0,
"target_volume": 0.0,
"progress": 0.0,
"stir": false,
"stir_speed": 0.0,
"temperature": 25.0,
"continue_heatchill": false
}
}
],
"links": []
}

View File

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "MockHeater1",
"name": "模拟加热器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_heater",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"current_temperature": 25.0,
"target_temperature": 25.0,
"status": "Idle",
"is_heating": false,
"heating_power": 0.0,
"max_temperature": 300.0,
"vessel": "Unknown",
"purpose": "Unknown",
"stir": false,
"stir_speed": 0.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,44 @@
{
"nodes": [
{
"id": "MockPump1",
"name": "模拟泵设备",
"children": [],
"parent": null,
"type": "device",
"class": "mock_pump",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"current_device": "MockPump1",
"pump_state": "Stopped",
"flow_rate": 0.0,
"target_flow_rate": 0.0,
"pressure": 0.0,
"total_volume": 0.0,
"max_flow_rate": 100.0,
"max_pressure": 10.0,
"from_vessel": "",
"to_vessel": "",
"transfer_volume": 0.0,
"amount": "",
"transfer_time": 0.0,
"is_viscous": false,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"is_solid": false,
"time_spent": 0.0,
"time_remaining": 0.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "MockRotavap1",
"name": "模拟旋转蒸发器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_rotavap",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"rotate_state": "Stopped",
"rotate_time": 0.0,
"rotate_speed": 0.0,
"pump_state": "Stopped",
"pump_time": 0.0,
"vacuum_level": 1013.25,
"temperature": 25.0,
"target_temperature": 25.0,
"success": "True"
}
}
],
"links": []
}

View File

@@ -0,0 +1,43 @@
{
"nodes": [
{
"id": "MockSeparator1",
"name": "模拟分离器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_separator",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"settling_time": 0.0,
"valve_state": "Closed",
"shake_time": 0.0,
"shake_status": "Not Shaking",
"current_device": "MockSeparator1",
"purpose": "",
"product_phase": "",
"from_vessel": "",
"separation_vessel": "",
"to_vessel": "",
"waste_phase_to_vessel": "",
"solvent": "",
"solvent_volume": 0.0,
"through": "",
"repeats": 1,
"stir_time": 0.0,
"stir_speed": 0.0,
"time_spent": 0.0,
"time_remaining": 0.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,25 @@
{
"nodes": [
{
"id": "MockSolenoidValve1",
"name": "模拟电磁阀",
"children": [],
"parent": null,
"type": "device",
"class": "mock_solenoid_valve",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"valve_status": "Closed"
}
}
],
"links": []
}

View File

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "MockStirrer1",
"name": "模拟搅拌器",
"children": [],
"parent": null,
"type": "device",
"class": "mock_stirrer",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"stir_speed": 0.0,
"target_stir_speed": 0.0,
"stir_state": "Stopped",
"temperature": 25.0,
"target_temperature": 25.0,
"heating_state": "Off",
"heating_power": 0.0,
"max_stir_speed": 2000.0,
"max_temperature": 300.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "MockStirrer1COPY",
"name": "模拟搅拌器(Copy)",
"children": [],
"parent": null,
"type": "device",
"class": "mock_stirrer_new",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"vessel": "",
"purpose": "",
"stir_speed": 0.0,
"target_stir_speed": 0.0,
"stir_state": "Stopped",
"stir_time": 0.0,
"settling_time": 0.0,
"progress": 0.0,
"max_stir_speed": 2000.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,31 @@
{
"nodes": [
{
"id": "MockVacuum1",
"name": "模拟真空泵",
"children": [],
"parent": null,
"type": "device",
"class": "mock_vacuum",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"port": "MOCK"
},
"data": {
"status": "Idle",
"power_state": "Off",
"pump_state": "Stopped",
"vacuum_level": 1013.25,
"target_vacuum": 50.0,
"pump_speed": 0.0,
"pump_efficiency": 95.0,
"max_pump_speed": 100.0
}
}
],
"links": []
}

View File

@@ -0,0 +1,250 @@
{
"nodes": [
{
"id": "AddTestStation",
"name": "添加试剂测试工作站",
"children": [
"pump_add",
"flask_1",
"flask_2",
"flask_3",
"flask_4",
"reactor",
"stirrer",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "pump_add",
"children": [],
"parent": "AddTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "stirrer",
"children": [],
"parent": "AddTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 698.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_1",
"name": "通用试剂瓶1",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_2",
"name": "通用试剂瓶2",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_3",
"name": "通用试剂瓶3",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_4",
"name": "通用试剂瓶4",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "reactor",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "flask_air",
"children": [],
"parent": "AddTestStation",
"type": "container",
"class": null,
"position": {
"x": 800,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_1",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_1": "top"
}
},
{
"source": "pump_add",
"target": "flask_2",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_2": "top"
}
},
{
"source": "pump_add",
"target": "flask_3",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_3": "top"
}
},
{
"source": "pump_add",
"target": "flask_4",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_4": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
}
]
}

View File

@@ -0,0 +1,271 @@
{
"nodes": [
{
"id": "CentrifugeTestStation",
"name": "离心机测试工作站",
"children": [
"pump_add",
"flask_1",
"flask_2",
"flask_3",
"reactor",
"stirrer",
"centrifuge_1",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "CentrifugeProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "pump_add",
"children": [],
"parent": "CentrifugeTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "stirrer",
"children": [],
"parent": "CentrifugeTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "centrifuge_1",
"name": "离心机",
"children": [],
"parent": "CentrifugeTestStation",
"type": "device",
"class": "virtual_centrifuge",
"position": {
"x": 800,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_speed": 15000.0,
"max_temp": 40.0,
"min_temp": 4.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_1",
"name": "样品瓶1",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_2",
"name": "样品瓶2",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_3",
"name": "缓冲液瓶",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "CentrifugeTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_1",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_1": "top"
}
},
{
"source": "pump_add",
"target": "flask_2",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_2": "top"
}
},
{
"source": "pump_add",
"target": "flask_3",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_3": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
},
{
"source": "centrifuge_1",
"target": "reactor",
"type": "logical",
"port": {
"centrifuge_1": "chamber",
"reactor": "vessel"
}
},
{
"source": "centrifuge_1",
"target": "flask_1",
"type": "logical",
"port": {
"centrifuge_1": "chamber",
"flask_1": "vessel"
}
},
{
"source": "centrifuge_1",
"target": "flask_2",
"type": "logical",
"port": {
"centrifuge_1": "chamber",
"flask_2": "vessel"
}
}
]
}

View File

@@ -0,0 +1,362 @@
{
"nodes": [
{
"id": "CleanVesselTestStation",
"name": "容器清洗测试工作站",
"children": [
"transfer_pump_cleaner",
"heatchill_1",
"flask_water",
"flask_ethanol",
"flask_acetone",
"flask_waste",
"reactor",
"flask_buffer",
"flask_sample",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["CleanVesselProtocol", "TransferProtocol", "AddProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_cleaner",
"name": "清洗转移泵",
"children": [],
"parent": "CleanVesselTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "heatchill_1",
"name": "加热冷却器",
"children": [],
"parent": "CleanVesselTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 150.0,
"min_temp": -20.0
},
"data": {
"status": "Idle",
"current_temp": 25.0,
"target_temp": 25.0,
"vessel": "",
"purpose": "",
"progress": 0.0,
"current_status": "Ready"
}
},
{
"id": "flask_water",
"name": "水溶剂瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "water",
"volume": 1500.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇溶剂瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethanol",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮溶剂瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "acetone",
"volume": 1800.0,
"concentration": 99.9
}
]
}
},
{
"id": "flask_waste",
"name": "废液瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "residue",
"volume": 50.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_buffer",
"name": "缓冲液瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "buffer",
"volume": 1000.0,
"concentration": 10.0
}
]
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 428,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "CleanVesselTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "transfer_pump_cleaner",
"target": "flask_water",
"type": "physical",
"port": {
"transfer_pump_cleaner": "1",
"flask_water": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_ethanol",
"type": "physical",
"port": {
"transfer_pump_cleaner": "2",
"flask_ethanol": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_acetone",
"type": "physical",
"port": {
"transfer_pump_cleaner": "3",
"flask_acetone": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_waste",
"type": "physical",
"port": {
"transfer_pump_cleaner": "4",
"flask_waste": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_cleaner": "5",
"reactor": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_buffer",
"type": "physical",
"port": {
"transfer_pump_cleaner": "6",
"flask_buffer": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_cleaner": "7",
"flask_sample": "top"
}
},
{
"source": "transfer_pump_cleaner",
"target": "flask_air",
"type": "physical",
"port": {
"transfer_pump_cleaner": "8",
"flask_air": "top"
}
},
{
"source": "heatchill_1",
"target": "reactor",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"reactor": "bottom"
}
},
{
"source": "heatchill_1",
"target": "flask_sample",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"flask_sample": "bottom"
}
}
]
}

View File

@@ -0,0 +1,343 @@
{
"nodes": [
{
"id": "DissolveTestStation",
"name": "溶解测试工作站",
"children": [
"transfer_pump_1",
"heatchill_1",
"stirrer_1",
"flask_water",
"flask_ethanol",
"flask_dmso",
"reactor",
"flask_sample",
"flask_buffer"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["DissolveProtocol", "TransferProtocol", "HeatChillProtocol", "StirProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "DissolveTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "heatchill_1",
"name": "加热冷却器",
"children": [],
"parent": "DissolveTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 150.0,
"min_temp": -20.0
},
"data": {
"status": "Idle",
"current_temp": 25.0,
"target_temp": 25.0,
"vessel": "",
"purpose": "",
"progress": 0.0,
"current_status": "Ready"
}
},
{
"id": "stirrer_1",
"name": "搅拌器",
"children": [],
"parent": "DissolveTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 750.1111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_water",
"name": "水溶剂瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "water",
"volume": 1500.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_ethanol",
"name": "乙醇溶剂瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethanol",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_dmso",
"name": "DMSO溶剂瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "dmso",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "solid_sample",
"volume": 10.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 428,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer",
"name": "缓冲液瓶",
"children": [],
"parent": "DissolveTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "buffer",
"volume": 1000.0,
"concentration": 10.0
}
]
}
}
],
"links": [
{
"source": "transfer_pump_1",
"target": "flask_water",
"type": "physical",
"port": {
"transfer_pump_1": "1",
"flask_water": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_ethanol",
"type": "physical",
"port": {
"transfer_pump_1": "2",
"flask_ethanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_dmso",
"type": "physical",
"port": {
"transfer_pump_1": "3",
"flask_dmso": "top"
}
},
{
"source": "transfer_pump_1",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_1": "4",
"reactor": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_1": "5",
"flask_sample": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_buffer",
"type": "physical",
"port": {
"transfer_pump_1": "6",
"flask_buffer": "top"
}
},
{
"source": "heatchill_1",
"target": "reactor",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"reactor": "bottom"
}
},
{
"source": "heatchill_1",
"target": "flask_sample",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"flask_sample": "bottom"
}
},
{
"source": "stirrer_1",
"target": "reactor",
"type": "physical",
"port": {
"stirrer_1": "stir_rod",
"reactor": "center"
}
},
{
"source": "stirrer_1",
"target": "flask_sample",
"type": "physical",
"port": {
"stirrer_1": "stir_rod",
"flask_sample": "center"
}
}
]
}

View File

@@ -0,0 +1,270 @@
{
"nodes": [
{
"id": "FilterTestStation",
"name": "过滤器测试工作站",
"children": [
"pump_add",
"flask_sample",
"flask_filtrate",
"flask_buffer",
"reactor",
"stirrer",
"filter_1",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "FilterProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "pump_add",
"children": [],
"parent": "FilterTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "stirrer",
"children": [],
"parent": "FilterTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "filter_1",
"name": "过滤器",
"children": [],
"parent": "FilterTestStation",
"type": "device",
"class": "virtual_filter",
"position": {
"x": 800,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_filtrate",
"name": "滤液瓶",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer",
"name": "缓冲液瓶",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "FilterTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_sample",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_sample": "top"
}
},
{
"source": "pump_add",
"target": "flask_filtrate",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_filtrate": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
},
{
"source": "filter_1",
"target": "reactor",
"type": "logical",
"port": {
"filter_1": "input",
"reactor": "vessel"
}
},
{
"source": "filter_1",
"target": "flask_sample",
"type": "logical",
"port": {
"filter_1": "input",
"flask_sample": "vessel"
}
},
{
"source": "filter_1",
"target": "flask_filtrate",
"type": "logical",
"port": {
"filter_1": "output",
"flask_filtrate": "vessel"
}
}
]
}

View File

@@ -0,0 +1,388 @@
{
"nodes": [
{
"id": "FilterThroughTestStation",
"name": "过滤通过测试工作站",
"children": [
"transfer_pump_1",
"filter_1",
"flask_ethanol",
"flask_water",
"flask_methanol",
"reactor",
"collection_flask",
"waste_flask",
"flask_sample",
"flask_celite",
"flask_silica"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["FilterThroughProtocol", "TransferProtocol", "FilterProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "FilterThroughTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "filter_1",
"name": "过滤器",
"children": [],
"parent": "FilterThroughTestStation",
"type": "device",
"class": "virtual_filter",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle",
"filter_state": "Ready",
"current_temp": 25.0,
"target_temp": 25.0,
"max_temp": 100.0,
"stir_speed": 0.0,
"max_stir_speed": 1000.0,
"filtered_volume": 0.0,
"progress": 0.0,
"message": ""
}
},
{
"id": "flask_ethanol",
"name": "乙醇溶剂瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethanol",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_water",
"name": "水溶剂瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "water",
"volume": 1800.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇溶剂瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "methanol",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "crude_product",
"volume": 200.0,
"concentration": 80.0
}
]
}
},
{
"id": "collection_flask",
"name": "收集瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_flask",
"name": "废液瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 300,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "sample_mixture",
"volume": 100.0,
"concentration": 50.0
}
]
}
},
{
"id": "flask_celite",
"name": "硅藻土容器",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 150,
"y": 300,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "celite",
"volume": 50.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_silica",
"name": "硅胶容器",
"children": [],
"parent": "FilterThroughTestStation",
"type": "container",
"class": null,
"position": {
"x": 300,
"y": 300,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "silica",
"volume": 30.0,
"concentration": 100.0
}
]
}
}
],
"links": [
{
"source": "transfer_pump_1",
"target": "flask_ethanol",
"type": "physical",
"port": {
"transfer_pump_1": "1",
"flask_ethanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_water",
"type": "physical",
"port": {
"transfer_pump_1": "2",
"flask_water": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_methanol",
"type": "physical",
"port": {
"transfer_pump_1": "3",
"flask_methanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_1": "4",
"reactor": "top"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask",
"type": "physical",
"port": {
"transfer_pump_1": "5",
"collection_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "waste_flask",
"type": "physical",
"port": {
"transfer_pump_1": "6",
"waste_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_1": "7",
"flask_sample": "top"
}
},
{
"source": "filter_1",
"target": "collection_flask",
"type": "physical",
"port": {
"filter_1": "filter_element",
"collection_flask": "top"
}
},
{
"source": "filter_1",
"target": "reactor",
"type": "physical",
"port": {
"filter_1": "filter_element",
"reactor": "top"
}
}
]
}

View File

@@ -0,0 +1,262 @@
{
"nodes": [
{
"id": "HeatChillTestStation",
"name": "加热冷却测试工作站",
"children": [
"pump_add",
"flask_sample",
"flask_buffer1",
"flask_buffer2",
"reactor",
"stirrer",
"heatchill_1",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "pump_add",
"children": [],
"parent": "HeatChillTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "stirrer",
"children": [],
"parent": "HeatChillTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "heatchill_1",
"name": "加热冷却器",
"children": [],
"parent": "HeatChillTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 800,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 200.0,
"min_temp": -80.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer1",
"name": "缓冲液瓶1",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer2",
"name": "缓冲液瓶2",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "HeatChillTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_sample",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_sample": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer1",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer1": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer2",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer2": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
},
{
"source": "heatchill_1",
"target": "reactor",
"type": "logical",
"port": {
"heatchill_1": "heating_element",
"reactor": "vessel"
}
},
{
"source": "heatchill_1",
"target": "flask_sample",
"type": "logical",
"port": {
"heatchill_1": "heating_element",
"flask_sample": "vessel"
}
}
]
}

View File

@@ -0,0 +1,412 @@
{
"nodes": [
{
"id": "RunColumnTestStation",
"name": "柱层析测试工作站",
"children": [
"transfer_pump_1",
"column_1",
"flask_sample",
"flask_hexane",
"flask_ethyl_acetate",
"flask_methanol",
"collection_flask_1",
"collection_flask_2",
"collection_flask_3",
"waste_flask",
"reactor"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["RunColumnProtocol", "TransferProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "RunColumnTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "column_1",
"name": "柱层析设备",
"children": [],
"parent": "RunColumnTestStation",
"type": "device",
"class": "virtual_column",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_flow_rate": 5.0,
"column_length": 30.0,
"column_diameter": 2.5
},
"data": {
"status": "Idle",
"column_state": "Ready",
"current_flow_rate": 0.0,
"max_flow_rate": 5.0,
"column_length": 30.0,
"column_diameter": 2.5,
"processed_volume": 0.0,
"progress": 0.0,
"current_status": "Ready"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "crude_mixture",
"volume": 200.0,
"concentration": 70.0
}
]
}
},
{
"id": "flask_hexane",
"name": "正己烷洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "hexane",
"volume": 1500.0,
"concentration": 99.8
}
]
}
},
{
"id": "flask_ethyl_acetate",
"name": "乙酸乙酯洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethyl_acetate",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇洗脱剂",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "methanol",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "collection_flask_1",
"name": "收集瓶1",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 750,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_flask_2",
"name": "收集瓶2",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 900,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "collection_flask_3",
"name": "收集瓶3",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 1050,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_flask",
"name": "废液瓶",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 1200,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "RunColumnTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "reaction_mixture",
"volume": 300.0,
"concentration": 85.0
}
]
}
}
],
"links": [
{
"source": "transfer_pump_1",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_1": "1",
"flask_sample": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_hexane",
"type": "physical",
"port": {
"transfer_pump_1": "2",
"flask_hexane": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_ethyl_acetate",
"type": "physical",
"port": {
"transfer_pump_1": "3",
"flask_ethyl_acetate": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_methanol",
"type": "physical",
"port": {
"transfer_pump_1": "4",
"flask_methanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "column_1",
"type": "physical",
"port": {
"transfer_pump_1": "5",
"column_1": "inlet"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask_1",
"type": "physical",
"port": {
"transfer_pump_1": "6",
"collection_flask_1": "top"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask_2",
"type": "physical",
"port": {
"transfer_pump_1": "7",
"collection_flask_2": "top"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask_3",
"type": "physical",
"port": {
"transfer_pump_1": "8",
"collection_flask_3": "top"
}
},
{
"source": "transfer_pump_1",
"target": "waste_flask",
"type": "physical",
"port": {
"transfer_pump_1": "9",
"waste_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_1": "10",
"reactor": "top"
}
},
{
"source": "column_1",
"target": "collection_flask_1",
"type": "physical",
"port": {
"column_1": "outlet",
"collection_flask_1": "top"
}
},
{
"source": "column_1",
"target": "collection_flask_2",
"type": "physical",
"port": {
"column_1": "outlet",
"collection_flask_2": "top"
}
},
{
"source": "column_1",
"target": "collection_flask_3",
"type": "physical",
"port": {
"column_1": "outlet",
"collection_flask_3": "top"
}
}
]
}

View File

@@ -0,0 +1,250 @@
{
"nodes": [
{
"id": "StirTestStation",
"name": "搅拌测试工作站",
"children": [
"pump_add",
"flask_sample",
"flask_buffer1",
"flask_buffer2",
"reactor",
"stirrer",
"flask_waste",
"flask_air"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol"]
},
"data": {}
},
{
"id": "pump_add",
"name": "添加泵",
"children": [],
"parent": "StirTestStation",
"type": "device",
"class": "virtual_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 25.0
},
"data": {
"status": "Idle"
}
},
{
"id": "stirrer",
"name": "搅拌器",
"children": [],
"parent": "StirTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer1",
"name": "缓冲液瓶1",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_buffer2",
"name": "缓冲液瓶2",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 5000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_waste",
"name": "废液瓶",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 3000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_air",
"name": "空气瓶",
"children": [],
"parent": "StirTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "stirrer",
"target": "reactor",
"type": "physical",
"port": {
"stirrer": "top",
"reactor": "bottom"
}
},
{
"source": "pump_add",
"target": "flask_sample",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_sample": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer1",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer1": "top"
}
},
{
"source": "pump_add",
"target": "flask_buffer2",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_buffer2": "top"
}
},
{
"source": "pump_add",
"target": "reactor",
"type": "physical",
"port": {
"pump_add": "outlet",
"reactor": "top"
}
},
{
"source": "pump_add",
"target": "flask_waste",
"type": "physical",
"port": {
"pump_add": "outlet",
"flask_waste": "top"
}
},
{
"source": "pump_add",
"target": "flask_air",
"type": "physical",
"port": {
"pump_add": "inlet",
"flask_air": "top"
}
}
]
}

View File

@@ -0,0 +1,249 @@
{
"nodes": [
{
"id": "TransferTestStation",
"name": "液体转移测试工作站",
"children": [
"transfer_pump",
"flask_source1",
"flask_source2",
"flask_target1",
"flask_target2",
"reactor",
"flask_waste",
"flask_rinsing"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["TransferProtocol"]
},
"data": {}
},
{
"id": "transfer_pump",
"name": "转移泵",
"children": [],
"parent": "TransferTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 5.0
},
"data": {
"status": "Idle"
}
},
{
"id": "flask_source1",
"name": "源容器1",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_source2",
"name": "源容器2",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_target1",
"name": "目标容器1",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_target2",
"name": "目标容器2",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_waste",
"name": "废液瓶",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_rinsing",
"name": "冲洗液瓶",
"children": [],
"parent": "TransferTestStation",
"type": "container",
"class": null,
"position": {
"x": 950,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "transfer_pump",
"target": "flask_source1",
"type": "physical",
"port": {
"transfer_pump": "inlet",
"flask_source1": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_source2",
"type": "physical",
"port": {
"transfer_pump": "inlet",
"flask_source2": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_target1",
"type": "physical",
"port": {
"transfer_pump": "outlet",
"flask_target1": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_target2",
"type": "physical",
"port": {
"transfer_pump": "outlet",
"flask_target2": "top"
}
},
{
"source": "transfer_pump",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump": "outlet",
"reactor": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_waste",
"type": "physical",
"port": {
"transfer_pump": "outlet",
"flask_waste": "top"
}
},
{
"source": "transfer_pump",
"target": "flask_rinsing",
"type": "physical",
"port": {
"transfer_pump": "inlet",
"flask_rinsing": "top"
}
}
]
}

View File

@@ -0,0 +1,494 @@
{
"nodes": [
{
"id": "WashSolidTestStation",
"name": "固体清洗测试工作站",
"children": [
"transfer_pump_1",
"heatchill_1",
"stirrer_1",
"filter_1",
"flask_ethanol",
"flask_water",
"flask_acetone",
"flask_methanol",
"reactor",
"collection_flask",
"waste_flask",
"flask_sample",
"filtrate_flask"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 620.6111111111111,
"y": 171,
"z": 0
},
"config": {
"protocol_type": ["WashSolidProtocol", "TransferProtocol", "FilterProtocol", "HeatChillProtocol", "StirProtocol"]
},
"data": {}
},
{
"id": "transfer_pump_1",
"name": "转移泵",
"children": [],
"parent": "WashSolidTestStation",
"type": "device",
"class": "virtual_transfer_pump",
"position": {
"x": 520.6111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_volume": 50.0,
"transfer_rate": 10.0
},
"data": {
"status": "Idle",
"current_volume": 0.0,
"max_volume": 50.0,
"transfer_rate": 10.0,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
}
},
{
"id": "heatchill_1",
"name": "加热冷却器",
"children": [],
"parent": "WashSolidTestStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 650.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 150.0,
"min_temp": -20.0
},
"data": {
"status": "Idle",
"current_temp": 25.0,
"target_temp": 25.0,
"vessel": "",
"purpose": "",
"progress": 0.0,
"current_status": "Ready"
}
},
{
"id": "stirrer_1",
"name": "搅拌器",
"children": [],
"parent": "WashSolidTestStation",
"type": "device",
"class": "virtual_stirrer",
"position": {
"x": 750.1111111111111,
"y": 300,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_speed": 1000.0
},
"data": {
"status": "Idle"
}
},
{
"id": "filter_1",
"name": "过滤器",
"children": [],
"parent": "WashSolidTestStation",
"type": "device",
"class": "virtual_filter",
"position": {
"x": 850.1111111111111,
"y": 478,
"z": 0
},
"config": {
"port": "VIRTUAL",
"max_temp": 100.0,
"max_stir_speed": 1000.0
},
"data": {
"status": "Idle",
"filter_state": "Ready",
"current_temp": 25.0,
"target_temp": 25.0,
"max_temp": 100.0,
"stir_speed": 0.0,
"max_stir_speed": 1000.0,
"filtered_volume": 0.0,
"progress": 0.0,
"message": ""
}
},
{
"id": "flask_ethanol",
"name": "乙醇清洗剂",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 100,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "ethanol",
"volume": 1500.0,
"concentration": 99.5
}
]
}
},
{
"id": "flask_water",
"name": "水清洗剂",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 250,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": [
{
"name": "water",
"volume": 1800.0,
"concentration": 100.0
}
]
}
},
{
"id": "flask_acetone",
"name": "丙酮清洗剂",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 400,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "acetone",
"volume": 800.0,
"concentration": 99.8
}
]
}
},
{
"id": "flask_methanol",
"name": "甲醇清洗剂",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 550,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "methanol",
"volume": 800.0,
"concentration": 99.9
}
]
}
},
{
"id": "reactor",
"name": "反应器",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 698.1111111111111,
"y": 428,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": [
{
"name": "solid_product",
"volume": 50.0,
"concentration": 100.0
}
]
}
},
{
"id": "collection_flask",
"name": "收集瓶",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 850,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1000.0
},
"data": {
"liquid": []
}
},
{
"id": "waste_flask",
"name": "废液瓶",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 428,
"z": 0
},
"config": {
"max_volume": 2000.0
},
"data": {
"liquid": []
}
},
{
"id": "flask_sample",
"name": "样品瓶",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 1150,
"y": 300,
"z": 0
},
"config": {
"max_volume": 500.0
},
"data": {
"liquid": [
{
"name": "crude_solid",
"volume": 30.0,
"concentration": 80.0
}
]
}
},
{
"id": "filtrate_flask",
"name": "滤液收集瓶",
"children": [],
"parent": "WashSolidTestStation",
"type": "container",
"class": null,
"position": {
"x": 1000,
"y": 300,
"z": 0
},
"config": {
"max_volume": 1500.0
},
"data": {
"liquid": []
}
}
],
"links": [
{
"source": "transfer_pump_1",
"target": "flask_ethanol",
"type": "physical",
"port": {
"transfer_pump_1": "1",
"flask_ethanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_water",
"type": "physical",
"port": {
"transfer_pump_1": "2",
"flask_water": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_acetone",
"type": "physical",
"port": {
"transfer_pump_1": "3",
"flask_acetone": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_methanol",
"type": "physical",
"port": {
"transfer_pump_1": "4",
"flask_methanol": "top"
}
},
{
"source": "transfer_pump_1",
"target": "reactor",
"type": "physical",
"port": {
"transfer_pump_1": "5",
"reactor": "top"
}
},
{
"source": "transfer_pump_1",
"target": "collection_flask",
"type": "physical",
"port": {
"transfer_pump_1": "6",
"collection_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "waste_flask",
"type": "physical",
"port": {
"transfer_pump_1": "7",
"waste_flask": "top"
}
},
{
"source": "transfer_pump_1",
"target": "flask_sample",
"type": "physical",
"port": {
"transfer_pump_1": "8",
"flask_sample": "top"
}
},
{
"source": "transfer_pump_1",
"target": "filtrate_flask",
"type": "physical",
"port": {
"transfer_pump_1": "9",
"filtrate_flask": "top"
}
},
{
"source": "heatchill_1",
"target": "reactor",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"reactor": "bottom"
}
},
{
"source": "heatchill_1",
"target": "flask_sample",
"type": "physical",
"port": {
"heatchill_1": "heating_element",
"flask_sample": "bottom"
}
},
{
"source": "stirrer_1",
"target": "reactor",
"type": "physical",
"port": {
"stirrer_1": "stir_rod",
"reactor": "center"
}
},
{
"source": "stirrer_1",
"target": "flask_sample",
"type": "physical",
"port": {
"stirrer_1": "stir_rod",
"flask_sample": "center"
}
},
{
"source": "filter_1",
"target": "reactor",
"type": "physical",
"port": {
"filter_1": "filter_element",
"reactor": "top"
}
},
{
"source": "filter_1",
"target": "flask_sample",
"type": "physical",
"port": {
"filter_1": "filter_element",
"flask_sample": "top"
}
},
{
"source": "filter_1",
"target": "filtrate_flask",
"type": "physical",
"port": {
"filter_1": "filter_element",
"filtrate_flask": "top"
}
}
]
}

View File

@@ -9645,19 +9645,19 @@
{
"id": "benyao",
"name": "benyao",
"id": "arm_slider",
"name": "arm_slider",
"children": [],
"parent": null,
"type": "device",
"class": "moveit.benyao_arm",
"class": "moveit.arm_slider",
"position": {
"x": -500,
"y": 1000,
"z": -100
},
"config": {
"moveit_type": "benyao_arm",
"moveit_type": "arm_slider",
"joint_poses": {
"arm": {
"hotel_1": [

View File

@@ -8,14 +8,14 @@
],
"parent": null,
"type": "device",
"class": "moveit.benyao_arm",
"class": "moveit.arm_slider",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"moveit_type": "benyao_arm",
"moveit_type": "arm_slider",
"joint_poses": {
"arm": {
"home": [0.0, 0.2, 0.0, 0.0, 0.0],

View File

@@ -5,6 +5,17 @@ from .separate_protocol import generate_separate_protocol
from .evaporate_protocol import generate_evaporate_protocol
from .evacuateandrefill_protocol import generate_evacuateandrefill_protocol
from .agv_transfer_protocol import generate_agv_transfer_protocol
from .add_protocol import generate_add_protocol
from .centrifuge_protocol import generate_centrifuge_protocol
from .filter_protocol import generate_filter_protocol
from .heatchill_protocol import generate_heat_chill_protocol, generate_heat_chill_start_protocol, generate_heat_chill_stop_protocol
from .stir_protocol import generate_stir_protocol, generate_start_stir_protocol, generate_stop_stir_protocol
from .transfer_protocol import generate_transfer_protocol
from .clean_vessel_protocol import generate_clean_vessel_protocol
from .dissolve_protocol import generate_dissolve_protocol
from .filter_through_protocol import generate_filter_through_protocol
from .run_column_protocol import generate_run_column_protocol
from .wash_solid_protocol import generate_wash_solid_protocol
# Define a dictionary of protocol generators.
@@ -15,5 +26,19 @@ action_protocol_generators = {
EvaporateProtocol: generate_evaporate_protocol,
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
AGVTransferProtocol: generate_agv_transfer_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,
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,
WashSolidProtocol: generate_wash_solid_protocol,
}
# End Protocols

View File

@@ -0,0 +1,74 @@
import networkx as nx
from typing import List, Dict, Any
def generate_add_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str,
volume: float,
mass: float,
amount: str,
time: float,
stir: bool,
stir_speed: float,
viscous: bool,
purpose: str
) -> List[Dict[str, Any]]:
"""
生成添加试剂的协议序列 - 严格按照 Add.action
"""
action_sequence = []
# 如果指定了体积,执行液体转移
if volume > 0:
# 查找可用的试剂瓶
available_flasks = [node for node in G.nodes()
if node.startswith('flask_')
and G.nodes[node].get('type') == 'container']
if not available_flasks:
raise ValueError("没有找到可用的试剂容器")
reagent_vessel = available_flasks[0]
# 查找泵设备
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_pump']
if pump_nodes:
pump_id = pump_nodes[0]
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": reagent_vessel,
"to_vessel": vessel,
"volume": volume,
"amount": amount,
"time": time,
"viscous": viscous,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 如果需要搅拌,使用 StartStir 而不是 Stir
if stir:
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
if stirrer_nodes:
stirrer_id = stirrer_nodes[0]
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir", # 使用 start_stir 而不是 stir
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"添加 {reagent} 后搅拌"
}
})
return action_sequence

View File

@@ -0,0 +1,123 @@
from typing import List, Dict, Any
import networkx as nx
def generate_centrifuge_protocol(
G: nx.DiGraph,
vessel: str,
speed: float,
time: float,
temp: float = 25.0
) -> List[Dict[str, Any]]:
"""
生成离心操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 离心容器名称
speed: 离心速度 (rpm)
time: 离心时间 (秒)
temp: 温度 (摄氏度,可选)
Returns:
List[Dict[str, Any]]: 离心操作的动作序列
Raises:
ValueError: 当找不到离心机设备时抛出异常
Examples:
centrifuge_protocol = generate_centrifuge_protocol(G, "reactor", 5000, 300, 4.0)
"""
action_sequence = []
# 查找离心机设备
centrifuge_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_centrifuge']
if not centrifuge_nodes:
raise ValueError("没有找到可用的离心机设备")
# 使用第一个可用的离心机
centrifuge_id = centrifuge_nodes[0]
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
# 执行离心操作
action_sequence.append({
"device_id": centrifuge_id,
"action_name": "centrifuge",
"action_kwargs": {
"vessel": vessel,
"speed": speed,
"time": time,
"temp": temp
}
})
return action_sequence
def generate_multi_step_centrifuge_protocol(
G: nx.DiGraph,
vessel: str,
steps: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""
生成多步骤离心操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 离心容器名称
steps: 离心步骤列表,每个步骤包含 speed, time, temp 参数
Returns:
List[Dict[str, Any]]: 多步骤离心操作的动作序列
Examples:
steps = [
{"speed": 1000, "time": 60, "temp": 4.0}, # 低速预离心
{"speed": 12000, "time": 600, "temp": 4.0} # 高速离心
]
protocol = generate_multi_step_centrifuge_protocol(G, "reactor", steps)
"""
action_sequence = []
# 查找离心机设备
centrifuge_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_centrifuge']
if not centrifuge_nodes:
raise ValueError("没有找到可用的离心机设备")
centrifuge_id = centrifuge_nodes[0]
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
# 执行每个离心步骤
for i, step in enumerate(steps):
speed = step.get('speed', 5000)
time = step.get('time', 300)
temp = step.get('temp', 25.0)
action_sequence.append({
"device_id": centrifuge_id,
"action_name": "centrifuge",
"action_kwargs": {
"vessel": vessel,
"speed": speed,
"time": time,
"temp": temp
}
})
# 步骤间等待时间(除了最后一步)
if i < len(steps) - 1:
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 3}
})
return action_sequence

View File

@@ -0,0 +1,126 @@
from typing import List, Dict, Any
import networkx as nx
def generate_clean_vessel_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float,
temp: float,
repeats: int = 1
) -> List[Dict[str, Any]]:
"""
生成容器清洗操作的协议序列使用transfer操作实现清洗
Args:
G: 有向图,节点为设备和容器
vessel: 要清洗的容器名称
solvent: 用于清洗容器的溶剂名称
volume: 清洗溶剂的体积
temp: 清洗时的温度
repeats: 清洗操作的重复次数,默认为 1
Returns:
List[Dict[str, Any]]: 容器清洗操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
clean_vessel_protocol = generate_clean_vessel_protocol(G, "reactor", "water", 50.0, 25.0, 2)
"""
action_sequence = []
# 查找虚拟转移泵设备进行清洗操作
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_transfer_pump']
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备进行容器清洗")
pump_id = pump_nodes[0]
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
# 查找溶剂容器
solvent_vessel = f"flask_{solvent}"
if solvent_vessel not in G.nodes():
raise ValueError(f"溶剂容器 {solvent_vessel} 不存在于图中")
# 查找废液容器
waste_vessel = "flask_waste"
if waste_vessel not in G.nodes():
raise ValueError(f"废液容器 {waste_vessel} 不存在于图中")
# 查找加热设备(如果需要加热)
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
# 执行清洗操作序列
for repeat in range(repeats):
# 1. 如果需要加热,先设置温度
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": "cleaning"
}
})
# 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
}
})
# 3. 等待清洗作用时间可选可以添加wait操作
# 这里省略wait操作直接进行下一步
# 4. 将清洗后的溶剂转移到废液容器
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": vessel,
"to_vessel": waste_vessel,
"volume": volume,
"amount": f"waste from cleaning {vessel} - cycle {repeat + 1}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 5. 如果加热了,停止加热
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
})
return action_sequence

View File

@@ -0,0 +1,162 @@
from typing import List, Dict, Any
import networkx as nx
def generate_dissolve_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float,
amount: str = "",
temp: float = 25.0,
time: float = 0.0,
stir_speed: float = 0.0
) -> List[Dict[str, Any]]:
"""
生成溶解操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 装有要溶解物质的容器名称
solvent: 用于溶解物质的溶剂名称
volume: 溶剂的体积,可选参数
amount: 要溶解物质的量,可选参数
temp: 溶解时的温度,可选参数
time: 溶解的时间,可选参数
stir_speed: 搅拌速度,可选参数
Returns:
List[Dict[str, Any]]: 溶解操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
dissolve_protocol = generate_dissolve_protocol(G, "reactor", "water", 100.0, "NaCl 5g", 60.0, 300.0, 500.0)
"""
action_sequence = []
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
# 查找溶剂容器
solvent_vessel = f"flask_{solvent}"
if solvent_vessel not in G.nodes():
# 如果没有找到特定溶剂容器,查找可用的源容器
available_vessels = [node for node in G.nodes()
if node.startswith('flask_') and
G.nodes[node].get('type') == 'container']
if available_vessels:
solvent_vessel = available_vessels[0]
else:
raise ValueError(f"没有找到溶剂容器 {solvent}")
# 查找转移泵设备
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]
# 查找加热设备(如果需要加热)
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
# 查找搅拌设备(如果需要搅拌)
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
# 步骤1如果需要加热先设置温度
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": "dissolution"
}
})
# 步骤2添加溶剂到容器中
if volume > 0:
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": solvent_vessel,
"to_vessel": vessel,
"volume": volume,
"amount": f"solvent {solvent} for dissolving {amount}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 步骤3如果需要搅拌开始搅拌
if stir_speed > 0 and stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"dissolving {amount} in {solvent}"
}
})
# 步骤4如果指定了溶解时间等待溶解完成
if time > 0:
# 这里可以添加等待操作,或者使用搅拌操作来模拟溶解时间
if stirrer_id and stir_speed > 0:
# 停止之前的搅拌,使用定时搅拌
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel
}
})
# 开始定时搅拌
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"stir_time": time,
"stir_speed": stir_speed,
"settling_time": 10.0 # 搅拌后静置10秒
}
})
# 步骤5如果加热了停止加热
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"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
}
})
return action_sequence

View File

@@ -69,14 +69,14 @@ def generate_evacuateandrefill_protocol(
"device_id": vacuum_backbone["pump"],
"action_name": "set_status",
"action_kwargs": {
"command": "ON"
"string": "ON"
}
},
{
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
"command": "OFF"
"string": "OFF"
}
}
])
@@ -106,14 +106,14 @@ def generate_evacuateandrefill_protocol(
"device_id": vacuum_backbone["pump"],
"action_name": "set_status",
"action_kwargs": {
"command": "OFF"
"string": "OFF"
}
},
{
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
"command": "ON"
"string": "ON"
}
}
])
@@ -125,7 +125,7 @@ def generate_evacuateandrefill_protocol(
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
"command": "OFF"
"string": "OFF"
}
}
)

View File

@@ -0,0 +1,70 @@
from typing import List, Dict, Any
import networkx as nx
def generate_filter_protocol(
G: nx.DiGraph,
vessel: str,
filtrate_vessel: str = "",
stir: bool = False,
stir_speed: float = 300.0,
temp: float = 25.0,
continue_heatchill: bool = False,
volume: float = 0.0
) -> List[Dict[str, Any]]:
"""
生成过滤操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 过滤容器
filtrate_vessel: 滤液容器(可选)
stir: 是否搅拌
stir_speed: 搅拌速度(可选)
temp: 温度(可选,摄氏度)
continue_heatchill: 是否继续加热冷却
volume: 过滤体积(可选)
Returns:
List[Dict[str, Any]]: 过滤操作的动作序列
Raises:
ValueError: 当找不到过滤设备时抛出异常
Examples:
filter_protocol = generate_filter_protocol(G, "reactor", "filtrate_vessel", stir=True, volume=100.0)
"""
action_sequence = []
# 查找过滤设备
filter_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_filter']
if not filter_nodes:
raise ValueError("没有找到可用的过滤设备")
# 使用第一个可用的过滤器
filter_id = filter_nodes[0]
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"过滤容器 {vessel} 不存在于图中")
if filtrate_vessel and filtrate_vessel not in G.nodes():
raise ValueError(f"滤液容器 {filtrate_vessel} 不存在于图中")
# 执行过滤操作
action_sequence.append({
"device_id": filter_id,
"action_name": "filter_sample",
"action_kwargs": {
"vessel": vessel,
"filtrate_vessel": filtrate_vessel,
"stir": stir,
"stir_speed": stir_speed,
"temp": temp,
"continue_heatchill": continue_heatchill,
"volume": volume
}
})
return action_sequence

View File

@@ -0,0 +1,150 @@
from typing import List, Dict, Any
import networkx as nx
def generate_filter_through_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
filter_through: str,
eluting_solvent: str = "",
eluting_volume: float = 0.0,
eluting_repeats: int = 0,
residence_time: float = 0.0
) -> List[Dict[str, Any]]:
"""
生成通过过滤介质过滤的协议序列
Args:
G: 有向图,节点为设备和容器
from_vessel: 源容器的名称,即物质起始所在的容器
to_vessel: 目标容器的名称,物质过滤后要到达的容器
filter_through: 过滤时所通过的介质,如滤纸、柱子等
eluting_solvent: 洗脱溶剂的名称,可选参数
eluting_volume: 洗脱溶剂的体积,可选参数
eluting_repeats: 洗脱操作的重复次数,默认为 0
residence_time: 物质在过滤介质中的停留时间,可选参数
Returns:
List[Dict[str, Any]]: 过滤操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
filter_through_protocol = generate_filter_through_protocol(
G, "reactor", "collection_flask", "celite", "ethanol", 50.0, 2, 60.0
)
"""
action_sequence = []
# 验证容器是否存在
if from_vessel not in G.nodes():
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
if to_vessel not in G.nodes():
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
# 查找转移泵设备(用于液体转移)
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_transfer_pump']
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备")
pump_id = pump_nodes[0]
# 查找过滤设备(可选,如果有专门的过滤设备)
filter_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_filter']
filter_id = filter_nodes[0] if filter_nodes else None
# 查找洗脱溶剂容器(如果需要洗脱)
eluting_vessel = None
if eluting_solvent and eluting_volume > 0:
eluting_vessel = f"flask_{eluting_solvent}"
if eluting_vessel not in G.nodes():
# 查找可用的溶剂容器
available_vessels = [node for node in G.nodes()
if node.startswith('flask_') and
G.nodes[node].get('type') == 'container']
if available_vessels:
eluting_vessel = available_vessels[0]
else:
raise ValueError(f"没有找到洗脱溶剂容器 {eluting_solvent}")
# 步骤1将样品从源容器转移到过滤装置模拟通过过滤介质
# 这里我们将过滤过程分解为多个转移步骤来模拟通过介质的过程
# 首先转移样品(模拟样品通过过滤介质)
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"volume": 0.0, # 转移所有液体,体积由系统确定
"amount": f"通过 {filter_through} 过滤",
"time": residence_time if residence_time > 0 else 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": True # 通过过滤介质可能涉及固体分离
}
})
# 步骤2如果有专门的过滤设备使用过滤设备处理
if filter_id:
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:
for repeat in range(eluting_repeats):
# 添加洗脱溶剂
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": eluting_vessel,
"to_vessel": to_vessel,
"volume": eluting_volume,
"amount": f"洗脱溶剂 {eluting_solvent} - 第 {repeat + 1}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 如果有过滤设备,再次过滤洗脱液
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": eluting_volume
}
})
return action_sequence

View File

@@ -0,0 +1,117 @@
from typing import List, Dict, Any
import networkx as nx
def generate_heat_chill_protocol(
G: nx.DiGraph,
vessel: str,
temp: float,
time: float,
stir: bool,
stir_speed: float,
purpose: str
) -> List[Dict[str, Any]]:
"""
生成加热/冷却操作的协议序列 - 严格按照 HeatChill.action
"""
action_sequence = []
# 查找加热/冷却设备
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
if not heatchill_nodes:
raise ValueError("没有找到可用的加热/冷却设备")
heatchill_id = heatchill_nodes[0]
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"time": time,
"stir": stir,
"stir_speed": stir_speed,
"purpose": purpose
}
})
return action_sequence
def generate_heat_chill_start_protocol(
G: nx.DiGraph,
vessel: str,
temp: float,
purpose: str
) -> List[Dict[str, Any]]:
"""
生成开始加热/冷却操作的协议序列 - 严格按照 HeatChillStart.action
"""
action_sequence = []
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
if not heatchill_nodes:
raise ValueError("没有找到可用的加热/冷却设备")
heatchill_id = heatchill_nodes[0]
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": purpose
}
})
return action_sequence
def generate_heat_chill_stop_protocol(
G: nx.DiGraph,
vessel: str
) -> List[Dict[str, Any]]:
"""
生成停止加热/冷却操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 容器名称
Returns:
List[Dict[str, Any]]: 停止加热/冷却操作的动作序列
"""
action_sequence = []
# 查找加热/冷却设备
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
if not heatchill_nodes:
raise ValueError("没有找到可用的加热/冷却设备")
heatchill_id = heatchill_nodes[0]
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
})
return action_sequence

View File

@@ -2,17 +2,42 @@ import numpy as np
import networkx as nx
def is_integrated_pump(node_name):
return "pump" in node_name and "valve" in node_name
def find_connected_pump(G, valve_node):
for neighbor in G.neighbors(valve_node):
if "pump" in G.nodes[neighbor]["class"]:
return neighbor
raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点")
def build_pump_valve_maps(G, pump_backbone):
pumps_from_node = {}
valve_from_node = {}
for node in pump_backbone:
if is_integrated_pump(node):
pumps_from_node[node] = node
valve_from_node[node] = node
else:
pump_node = find_connected_pump(G, node)
pumps_from_node[node] = pump_node
valve_from_node[node] = node
return pumps_from_node, valve_from_node
def generate_pump_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float,
flowrate: float = 0.5,
transfer_flowrate: float = 0,
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float,
flowrate: float = 0.5,
transfer_flowrate: float = 0,
) -> list[dict]:
"""
生成泵操作的动作序列。
:param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
:param from_vessel: 容器A
:param to_vessel: 容器B
@@ -21,7 +46,7 @@ def generate_pump_protocol(
:param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
:return: 泵操作的动作序列
"""
# 生成泵操作的动作序列
pump_action_sequence = []
nodes = G.nodes(data=True)
@@ -34,31 +59,33 @@ def generate_pump_protocol(
pump_backbone = pump_backbone[1:]
if not to_vessel.startswith("pump"):
pump_backbone = pump_backbone[:-1]
if transfer_flowrate == 0:
transfer_flowrate = flowrate
min_transfer_volume = min([nodes[pump]["max_volume"] for pump in pump_backbone])
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
repeats = int(np.ceil(volume / min_transfer_volume))
if repeats > 1 and (from_vessel.startswith("pump") or to_vessel.startswith("pump")):
raise ValueError("Cannot transfer volume larger than min_transfer_volume between two pumps.")
volume_left = volume
# 生成泵操作的动作序列
for i in range(repeats):
# 单泵依次执行阀指令、活塞指令,将液体吸入与之相连的第一台泵
if not from_vessel.startswith("pump"):
pump_action_sequence.extend([
{
"device_id": pump_backbone[0],
"device_id": valve_from_node[pump_backbone[0]],
"action_name": "set_valve_position",
"action_kwargs": {
"command": G.get_edge_data(pump_backbone[0], from_vessel)["port"][pump_backbone[0]]
}
},
{
"device_id": pump_backbone[0],
"device_id": pumps_from_node[pump_backbone[0]],
"action_name": "set_position",
"action_kwargs": {
"position": float(min(volume_left, min_transfer_volume)),
@@ -67,57 +94,57 @@ def generate_pump_protocol(
}
])
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
for pumpA, pumpB in zip(pump_backbone[:-1], pump_backbone[1:]):
for nodeA, nodeB in zip(pump_backbone[:-1], pump_backbone[1:]):
# 相邻两泵同时切换阀门至连通位置
pump_action_sequence.append([
{
"device_id": pumpA,
"action_name": "set_valve_position",
"action_kwargs": {
"command": G.get_edge_data(pumpA, pumpB)["port"][pumpA]
{
"device_id": valve_from_node[nodeA],
"action_name": "set_valve_position",
"action_kwargs": {
"command": G.get_edge_data(nodeA, nodeB)["port"][nodeA]
}
},
{
"device_id": valve_from_node[nodeB],
"action_name": "set_valve_position",
"action_kwargs": {
"command": G.get_edge_data(nodeB, nodeA)["port"][nodeB],
}
}
},
{
"device_id": pumpB,
"action_name": "set_valve_position",
"action_kwargs": {
"command": G.get_edge_data(pumpB, pumpA)["port"][pumpB],
}
}
])
# 相邻两泵液体转移泵A排出液体泵B吸入液体
pump_action_sequence.append([
{
"device_id": pumpA,
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
"max_velocity": transfer_flowrate
{
"device_id": pumps_from_node[nodeA],
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
"max_velocity": transfer_flowrate
}
},
{
"device_id": pumps_from_node[nodeB],
"action_name": "set_position",
"action_kwargs": {
"position": float(min(volume_left, min_transfer_volume)),
"max_velocity": transfer_flowrate
}
}
},
{
"device_id": pumpB,
"action_name": "set_position",
"action_kwargs": {
"position": float(min(volume_left, min_transfer_volume)),
"max_velocity": transfer_flowrate
}
}
])
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
if not to_vessel.startswith("pump"):
# 单泵依次执行阀指令、活塞指令将最后一台泵液体缓慢加入容器B
pump_action_sequence.extend([
{
"device_id": pump_backbone[-1],
"device_id": valve_from_node[pump_backbone[-1]],
"action_name": "set_valve_position",
"action_kwargs": {
"command": G.get_edge_data(pump_backbone[-1], to_vessel)["port"][pump_backbone[-1]]
}
},
{
"device_id": pump_backbone[-1],
"device_id": pumps_from_node[pump_backbone[-1]],
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
@@ -126,30 +153,30 @@ def generate_pump_protocol(
}
])
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
volume_left -= min_transfer_volume
return pump_action_sequence
# Pump protocol compilation
def generate_pump_protocol_with_rinsing(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float,
amount: str = "",
time: float = 0,
viscous: bool = False,
rinsing_solvent: str = "air",
rinsing_volume: float = 5.0,
rinsing_repeats: int = 2,
solid: bool = False,
flowrate: float = 2.5,
transfer_flowrate: float = 0.5,
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float,
amount: str = "",
time: float = 0,
viscous: bool = False,
rinsing_solvent: str = "air",
rinsing_volume: float = 5.0,
rinsing_repeats: int = 2,
solid: bool = False,
flowrate: float = 2.5,
transfer_flowrate: float = 0.5,
) -> list[dict]:
"""
Generates a pump protocol for transferring a specified volume between vessels, including rinsing steps with a chosen solvent. This function constructs a sequence of pump actions based on the provided parameters and the shortest path in a directed graph.
Args:
G (nx.DiGraph): The directed graph representing the vessels and connections. 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
from_vessel (str): The name of the vessel to transfer from.
@@ -164,50 +191,64 @@ def generate_pump_protocol_with_rinsing(
solid (bool, optional): Indicates if the transfer involves a solid (default is False).
flowrate (float, optional): The flow rate for the transfer (default is 2.5). 最终注入容器B时的流速
transfer_flowrate (float, optional): The flow rate for the transfer action (default is 0.5). 泵骨架中转移流速(若不指定,默认与注入流速相同)
Returns:
list[dict]: A sequence of pump actions to be executed for the transfer and rinsing process. 泵操作的动作序列.
Raises:
AssertionError: If the number of rinsing solvents does not match the number of rinsing repeats.
Examples:
pump_protocol = generate_pump_protocol_with_rinsing(G, "vessel_A", "vessel_B", 0.1, rinsing_solvent="water")
"""
air_vessel = "flask_air"
waste_vessel = f"waste_workup"
shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
pump_backbone = shortest_path[1: -1]
nodes = G.nodes(data=True)
min_transfer_volume = float(min([nodes[pump]["max_volume"] for pump in pump_backbone]))
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
if time != 0:
flowrate = transfer_flowrate = volume / time
pump_action_sequence = generate_pump_protocol(G, from_vessel, to_vessel, float(volume), flowrate, transfer_flowrate)
if rinsing_solvent != "air":
if rinsing_solvent != "air" and rinsing_solvent != "":
if "," in rinsing_solvent:
rinsing_solvents = rinsing_solvent.split(",")
assert len(rinsing_solvents) == rinsing_repeats, "Number of rinsing solvents must match number of rinsing repeats."
assert len(
rinsing_solvents) == rinsing_repeats, "Number of rinsing solvents must match number of rinsing repeats."
else:
rinsing_solvents = [rinsing_solvent] * rinsing_repeats
for rinsing_solvent in rinsing_solvents:
solvent_vessel = f"flask_{rinsing_solvent}"
# 清洗泵
pump_action_sequence.extend(
generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate, transfer_flowrate) +
generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate, transfer_flowrate) +
generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate, transfer_flowrate)
generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate,
transfer_flowrate) +
generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate,
transfer_flowrate) +
generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate,
transfer_flowrate)
)
# 如果转移的是溶液,第一种冲洗溶剂请选用溶液的溶剂,稀释泵内、转移管道内的溶液。后续冲洗溶剂不需要此操作。
if rinsing_solvent == rinsing_solvents[0]:
pump_action_sequence.extend(generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate))
pump_action_sequence.extend(generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate))
pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, solvent_vessel, rinsing_volume, flowrate, transfer_flowrate))
pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, waste_vessel, rinsing_volume, flowrate, transfer_flowrate))
pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
pump_action_sequence.extend(
generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate))
pump_action_sequence.extend(
generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate))
pump_action_sequence.extend(
generate_pump_protocol(G, air_vessel, solvent_vessel, rinsing_volume, flowrate, transfer_flowrate))
pump_action_sequence.extend(
generate_pump_protocol(G, air_vessel, waste_vessel, rinsing_volume, flowrate, transfer_flowrate))
if rinsing_solvent != "":
pump_action_sequence.extend(
generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
pump_action_sequence.extend(
generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
return pump_action_sequence
# End Protocols

View File

@@ -0,0 +1,102 @@
from typing import List, Dict, Any
import networkx as nx
def generate_run_column_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
column: str
) -> List[Dict[str, Any]]:
"""
生成柱层析分离的协议序列
Args:
G: 有向图,节点为设备和容器
from_vessel: 源容器的名称,即样品起始所在的容器
to_vessel: 目标容器的名称,分离后的样品要到达的容器
column: 所使用的柱子的名称
Returns:
List[Dict[str, Any]]: 柱层析分离操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
run_column_protocol = generate_run_column_protocol(G, "reactor", "collection_flask", "silica_column")
"""
action_sequence = []
# 验证容器是否存在
if from_vessel not in G.nodes():
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
if to_vessel not in G.nodes():
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
# 查找转移泵设备(用于样品转移)
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_transfer_pump']
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备")
pump_id = pump_nodes[0]
# 查找柱层析设备
column_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_column']
if not column_nodes:
raise ValueError("没有找到可用的柱层析设备")
column_id = column_nodes[0]
# 步骤1将样品从源容器转移到柱子上
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": column_id, # 将样品转移到柱子设备
"volume": 0.0, # 转移所有液体,体积由系统确定
"amount": f"样品上柱 - 使用 {column}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 步骤2运行柱层析分离
action_sequence.append({
"device_id": column_id,
"action_name": "run_column",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"column": column
}
})
# 步骤3将分离后的产物从柱子转移到目标容器
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": column_id, # 从柱子设备转移
"to_vessel": to_vessel,
"volume": 0.0, # 转移所有液体,体积由系统确定
"amount": f"收集分离产物 - 来自 {column}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
return action_sequence

View File

@@ -0,0 +1,137 @@
from typing import List, Dict, Any
import networkx as nx
def generate_stir_protocol(
G: nx.DiGraph,
stir_time: float,
stir_speed: float,
settling_time: float
) -> List[Dict[str, Any]]:
"""
生成搅拌操作的协议序列
Args:
G: 有向图,节点为设备和容器
stir_time: 搅拌时间 (秒)
stir_speed: 搅拌速度 (rpm)
settling_time: 沉降时间 (秒)
Returns:
List[Dict[str, Any]]: 搅拌操作的动作序列
Raises:
ValueError: 当找不到搅拌设备时抛出异常
Examples:
stir_protocol = generate_stir_protocol(G, 300.0, 500.0, 60.0)
"""
action_sequence = []
# 查找搅拌设备
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
if not stirrer_nodes:
raise ValueError("没有找到可用的搅拌设备")
# 使用第一个可用的搅拌器
stirrer_id = stirrer_nodes[0]
# 执行搅拌操作
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"stir_time": stir_time,
"stir_speed": stir_speed,
"settling_time": settling_time
}
})
return action_sequence
def generate_start_stir_protocol(
G: nx.DiGraph,
vessel: str,
stir_speed: float,
purpose: str
) -> List[Dict[str, Any]]:
"""
生成开始搅拌操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 搅拌容器
stir_speed: 搅拌速度 (rpm)
purpose: 搅拌目的
Returns:
List[Dict[str, Any]]: 开始搅拌操作的动作序列
"""
action_sequence = []
# 查找搅拌设备
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
if not stirrer_nodes:
raise ValueError("没有找到可用的搅拌设备")
stirrer_id = stirrer_nodes[0]
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": purpose
}
})
return action_sequence
def generate_stop_stir_protocol(
G: nx.DiGraph,
vessel: str
) -> List[Dict[str, Any]]:
"""
生成停止搅拌操作的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 搅拌容器
Returns:
List[Dict[str, Any]]: 停止搅拌操作的动作序列
"""
action_sequence = []
# 查找搅拌设备
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
if not stirrer_nodes:
raise ValueError("没有找到可用的搅拌设备")
stirrer_id = stirrer_nodes[0]
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"容器 {vessel} 不存在于图中")
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel
}
})
return action_sequence

View File

@@ -0,0 +1,79 @@
from typing import List, Dict, Any
import networkx as nx
def generate_transfer_protocol(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float,
amount: str = "",
time: float = 0,
viscous: bool = False,
rinsing_solvent: str = "",
rinsing_volume: float = 0.0,
rinsing_repeats: int = 0,
solid: bool = False
) -> List[Dict[str, Any]]:
"""
生成液体转移操作的协议序列
Args:
G: 有向图,节点为设备和容器
from_vessel: 源容器
to_vessel: 目标容器
volume: 转移体积 (mL)
amount: 数量描述 (可选)
time: 转移时间 (秒,可选)
viscous: 是否为粘性液体
rinsing_solvent: 冲洗溶剂 (可选)
rinsing_volume: 冲洗体积 (mL可选)
rinsing_repeats: 冲洗重复次数
solid: 是否涉及固体
Returns:
List[Dict[str, Any]]: 转移操作的动作序列
Raises:
ValueError: 当找不到合适的转移设备时抛出异常
Examples:
transfer_protocol = generate_transfer_protocol(G, "flask_1", "reactor", 10.0)
"""
action_sequence = []
# 查找虚拟转移泵设备用于液体转移 - 修复:应该查找 virtual_transfer_pump
pump_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_transfer_pump']
if not pump_nodes:
raise ValueError("没有找到可用的转移泵设备进行液体转移")
# 使用第一个可用的泵
pump_id = pump_nodes[0]
# 验证容器是否存在
if from_vessel not in G.nodes():
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
if to_vessel not in G.nodes():
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
# 执行液体转移操作 - 参数完全匹配Transfer.action
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"volume": volume,
"amount": amount,
"time": time,
"viscous": viscous,
"rinsing_solvent": rinsing_solvent,
"rinsing_volume": rinsing_volume,
"rinsing_repeats": rinsing_repeats,
"solid": solid
}
})
return action_sequence

View File

@@ -0,0 +1,216 @@
from typing import List, Dict, Any
import networkx as nx
def generate_wash_solid_protocol(
G: nx.DiGraph,
vessel: str,
solvent: str,
volume: float,
filtrate_vessel: str = "",
temp: float = 25.0,
stir: bool = False,
stir_speed: float = 0.0,
time: float = 0.0,
repeats: int = 1
) -> List[Dict[str, Any]]:
"""
生成固体清洗的协议序列
Args:
G: 有向图,节点为设备和容器
vessel: 装有固体物质的容器名称
solvent: 用于清洗固体的溶剂名称
volume: 清洗溶剂的体积
filtrate_vessel: 滤液要收集到的容器名称,可选参数
temp: 清洗时的温度,可选参数
stir: 是否在清洗过程中搅拌,默认为 False
stir_speed: 搅拌速度,可选参数
time: 清洗的时间,可选参数
repeats: 清洗操作的重复次数,默认为 1
Returns:
List[Dict[str, Any]]: 固体清洗操作的动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
wash_solid_protocol = generate_wash_solid_protocol(
G, "reactor", "ethanol", 100.0, "waste_flask", 60.0, True, 300.0, 600.0, 3
)
"""
action_sequence = []
# 验证容器是否存在
if vessel not in G.nodes():
raise ValueError(f"固体容器 {vessel} 不存在于图中")
if filtrate_vessel and filtrate_vessel not in G.nodes():
raise ValueError(f"滤液容器 {filtrate_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]
# 查找加热设备(如果需要加热)
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
# 查找搅拌设备(如果需要搅拌)
stirrer_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_stirrer']
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
# 查找过滤设备(用于分离固体和滤液)
filter_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_filter']
filter_id = filter_nodes[0] if filter_nodes else None
# 查找溶剂容器
solvent_vessel = f"flask_{solvent}"
if solvent_vessel not in G.nodes():
# 如果没有找到特定溶剂容器,查找可用的源容器
available_vessels = [node for node in G.nodes()
if node.startswith('flask_') and
G.nodes[node].get('type') == 'container']
if available_vessels:
solvent_vessel = available_vessels[0]
else:
raise ValueError(f"没有找到溶剂容器 {solvent}")
# 如果没有指定滤液容器,使用废液容器
if not filtrate_vessel:
waste_vessels = [node for node in G.nodes()
if 'waste' in node.lower() and
G.nodes[node].get('type') == 'container']
filtrate_vessel = waste_vessels[0] if waste_vessels else "waste_flask"
# 重复清洗操作
for repeat in range(repeats):
repeat_num = repeat + 1
# 步骤1如果需要加热先设置温度
if temp > 25.0 and heatchill_id:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": f"固体清洗 - 第 {repeat_num}"
}
})
# 步骤2添加清洗溶剂到固体容器
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": solvent_vessel,
"to_vessel": vessel,
"volume": volume,
"amount": f"清洗溶剂 {solvent} - 第 {repeat_num}",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 步骤3如果需要搅拌开始搅拌
if stir and stir_speed > 0 and stirrer_id:
if time > 0:
# 定时搅拌
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"stir_time": time,
"stir_speed": stir_speed,
"settling_time": 30.0 # 搅拌后静置30秒
}
})
else:
# 开始搅拌(需要手动停止)
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"固体清洗搅拌 - 第 {repeat_num}"
}
})
# 步骤4如果指定了清洗时间但没有搅拌等待清洗时间
if time > 0 and (not stir or stir_speed == 0):
# 这里可以添加等待操作,暂时跳过
pass
# 步骤5如果有搅拌且没有定时停止搅拌
if stir and stir_speed > 0 and time == 0 and stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel
}
})
# 步骤6过滤分离固体和滤液
if filter_id:
action_sequence.append({
"device_id": filter_id,
"action_name": "filter_sample",
"action_kwargs": {
"vessel": vessel,
"filtrate_vessel": filtrate_vessel,
"stir": False,
"stir_speed": 0.0,
"temp": temp,
"continue_heatchill": temp > 25.0,
"volume": volume
}
})
else:
# 没有专门的过滤设备,使用转移泵模拟过滤过程
# 将滤液转移到滤液容器
action_sequence.append({
"device_id": pump_id,
"action_name": "transfer",
"action_kwargs": {
"from_vessel": vessel,
"to_vessel": filtrate_vessel,
"volume": volume,
"amount": f"转移滤液 - 第 {repeat_num} 次清洗",
"time": 0.0,
"viscous": False,
"rinsing_solvent": "",
"rinsing_volume": 0.0,
"rinsing_repeats": 0,
"solid": False
}
})
# 步骤7如果加热了停止加热在最后一次清洗后
if temp > 25.0 and heatchill_id and repeat_num == repeats:
action_sequence.append({
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel
}
})
return action_sequence

View File

@@ -0,0 +1,9 @@
# Default initial positions for full_dev's ros2_control fake system
initial_positions:
arm_base_joint: 0
arm_link_1_joint: 0
arm_link_2_joint: 0
arm_link_3_joint: 0
gripper_base_joint: 0
gripper_right_joint: 0.03

View File

@@ -0,0 +1,40 @@
# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed
# For beginners, we downscale velocity and acceleration limits.
# You can always specify higher scaling factors (<= 1.0) in your motion requests. # Increase the values below to 1.0 to always move at maximum speed.
default_velocity_scaling_factor: 0.1
default_acceleration_scaling_factor: 0.1
# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
joint_limits:
arm_base_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
arm_link_1_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
arm_link_2_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
arm_link_3_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
gripper_base_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0
gripper_right_joint:
has_velocity_limits: true
max_velocity: 0
has_acceleration_limits: false
max_acceleration: 0

View File

@@ -0,0 +1,4 @@
arm:
kinematics_solver: lma_kinematics_plugin/LMAKinematicsPlugin
kinematics_solver_search_resolution: 0.0050000000000000001
kinematics_solver_timeout: 0.0050000000000000001

View File

@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="arm_slider_ros2_control" params="device_name mesh_path">
<xacro:property name="initial_positions" value="${load_yaml(mesh_path + '/devices/arm_slider/config/initial_positions.yaml')['initial_positions']}"/>
<ros2_control name="${device_name}arm_slider" type="system">
<hardware>
<!-- By default, set up controllers for simulation. This won't work on real hardware -->
<plugin>mock_components/GenericSystem</plugin>
</hardware>
<joint name="${device_name}arm_base_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['arm_base_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}arm_link_1_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['arm_link_1_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}arm_link_2_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['arm_link_2_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}arm_link_3_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['arm_link_3_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}gripper_base_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['gripper_base_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
<joint name="${device_name}gripper_right_joint">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">${initial_positions['gripper_right_joint']}</param>
</state_interface>
<state_interface name="velocity"/>
</joint>
</ros2_control>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--This does not replace URDF, and is not an extension of URDF.
This is a format for representing semantic information about the robot structure.
A URDF file must exist for this robot as well, where the joints and the links that are referenced are defined
-->
<robot xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:macro name="arm_slider_srdf" params="device_name">
<!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc-->
<!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included-->
<!--JOINTS: When a joint is specified, the child link of that joint (which will always exist) is automatically included-->
<!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group-->
<!--SUBGROUPS: Groups can also be formed by referencing to already defined group names-->
<group name="${device_name}arm">
<chain base_link="${device_name}arm_slideway" tip_link="${device_name}gripper_base"/>
</group>
<group name="${device_name}arm_gripper">
<joint name="${device_name}gripper_right_joint"/>
</group>
<!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. -->
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_2" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_1" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_3" reason="Never"/>
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_slideway" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_2" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_3" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_slideway" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_base" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_left" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_right" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_link_3" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_slideway" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_base" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_left" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_right" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}arm_slideway" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_base" reason="Adjacent"/>
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_left" reason="Never"/>
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_right" reason="Never"/>
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_base" reason="Never"/>
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_left" reason="Never"/>
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_right" reason="Never"/>
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_left" reason="Adjacent"/>
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_right" reason="Adjacent"/>
<disable_collisions link1="${device_name}gripper_left" link2="${device_name}gripper_right" reason="Never"/>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,14 @@
{
"arm":
{
"joint_names": [
"arm_base_joint",
"arm_link_1_joint",
"arm_link_2_joint",
"arm_link_3_joint",
"gripper_base_joint"
],
"base_link_name": "device_link",
"end_effector_name": "gripper_base"
}
}

View File

@@ -0,0 +1,29 @@
# MoveIt uses this configuration for controller management
moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager
moveit_simple_controller_manager:
controller_names:
- arm_controller
- gripper_controller
arm_controller:
type: FollowJointTrajectory
action_ns: follow_joint_trajectory
default: true
joints:
- arm_base_joint
- arm_link_1_joint
- arm_link_2_joint
- arm_link_3_joint
- gripper_base_joint
action_ns: follow_joint_trajectory
default: true
gripper_controller:
type: FollowJointTrajectory
action_ns: follow_joint_trajectory
default: true
joints:
- gripper_right_joint
action_ns: follow_joint_trajectory
default: true

View File

@@ -0,0 +1,2 @@
planner_configs:
- ompl_interface/OMPLPlanner

View File

@@ -0,0 +1,6 @@
# Limits for the Pilz planner
cartesian_limits:
max_trans_vel: 1.0
max_trans_acc: 2.25
max_trans_dec: -5.0
max_rot_vel: 1.57

View File

@@ -0,0 +1,39 @@
# This config file is used by ros2_control
controller_manager:
ros__parameters:
update_rate: 100 # Hz
arm_controller:
type: joint_trajectory_controller/JointTrajectoryController
gripper_controller:
type: joint_trajectory_controller/JointTrajectoryController
joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster
arm_controller:
ros__parameters:
joints:
- arm_base_joint
- arm_link_1_joint
- arm_link_2_joint
- arm_link_3_joint
- gripper_base_joint
command_interfaces:
- position
state_interfaces:
- position
- velocity
gripper_controller:
ros__parameters:
joints:
- gripper_right_joint
command_interfaces:
- position
state_interfaces:
- position
- velocity

View File

@@ -0,0 +1,44 @@
joint_limits:
arm_base_joint:
effort: 50
velocity: 1.0
lower: 0
upper: 1.5
arm_link_1_joint:
effort: 50
velocity: 1.0
lower: 0
upper: 0.6
arm_link_2_joint:
effort: 50
velocity: 1.0
lower: !degrees -95
upper: !degrees 95
arm_link_3_joint:
effort: 50
velocity: 1.0
lower: !degrees -195
upper: !degrees 195
gripper_base_joint:
effort: 50
velocity: 1.0
lower: !degrees -95
upper: !degrees 95
gripper_right_joint:
effort: 50
velocity: 1.0
lower: 0
upper: 0.03
gripper_left_joint:
effort: 50
velocity: 1.0
lower: 0
upper: 0.03

View File

@@ -0,0 +1,293 @@
<?xml version="1.0" ?>
<robot xmlns:xacro="http://ros.org/wiki/xacro" name="arm_slider">
<xacro:macro name="arm_slider" params="mesh_path:='' parent_link:='' station_name:='' device_name:='' x:=0 y:=0 z:=0 rx:=0 ry:=0 r:=0">
<!-- Read .yaml files from disk, load content into properties -->
<xacro:property name= "joint_limit_parameters" value="${xacro.load_yaml(mesh_path + '/devices/arm_slider/joint_limit.yaml')}"/>
<!-- Extract subsections from yaml dictionaries -->
<xacro:property name= "sec_limits" value="${joint_limit_parameters['joint_limits']}"/>
<joint name="${station_name}${device_name}base_link_joint" type="fixed">
<origin xyz="${x} ${y} ${z}" rpy="${rx} ${ry} ${r}" />
<parent link="${parent_link}"/>
<child link="${station_name}${device_name}device_link"/>
<axis xyz="0 0 0"/>
</joint>
<link name="${station_name}${device_name}device_link"/>
<joint name="${station_name}${device_name}device_link_joint" type="fixed">
<origin xyz="0 0 0" rpy="0 0 0" />
<parent link="${station_name}${device_name}device_link"/>
<child link="${station_name}${device_name}arm_slideway"/>
<axis xyz="0 0 0"/>
</joint>
<!-- JOINTS LIMIT PARAMETERS -->
<xacro:property name="limit_arm_base_joint" value="${sec_limits['arm_base_joint']}" />
<xacro:property name="limit_arm_link_1_joint" value="${sec_limits['arm_link_1_joint']}" />
<xacro:property name="limit_arm_link_2_joint" value="${sec_limits['arm_link_2_joint']}" />
<xacro:property name="limit_arm_link_3_joint" value="${sec_limits['arm_link_3_joint']}" />
<xacro:property name="limit_gripper_base_joint" value="${sec_limits['gripper_base_joint']}" />
<xacro:property name="limit_gripper_right_joint" value="${sec_limits['gripper_right_joint']}"/>
<xacro:property name="limit_gripper_left_joint" value="${sec_limits['gripper_left_joint']}" />
<link name="${station_name}${device_name}arm_slideway">
<inertial>
<origin rpy="0 0 0" xyz="-0.913122246354019 -0.00141851388483838 0.0416079172839272"/>
<mass value="13.6578107753627"/>
<inertia ixx="0.0507627640890578" ixy="0.0245166532634714" ixz="-0.0112656803168519" iyy="5.2550852314372" iyz="0.000302974193920367" izz="5.26892263696439"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_slideway.STL"/>
</geometry>
<material name="">
<color rgba="0.752941176470588 0.752941176470588 0.752941176470588 1"/>
</material>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_slideway.STL"/>
</geometry>
</collision>
</link>
<joint name="${station_name}${device_name}arm_base_joint" type="prismatic">
<origin rpy="0 0 0" xyz="0.307 0 0.1225"/>
<parent link="${station_name}${device_name}arm_slideway"/>
<child link="${station_name}${device_name}arm_base"/>
<axis xyz="1 0 0"/>
<limit
effort="${limit_arm_base_joint['effort']}"
lower="${limit_arm_base_joint['lower']}"
upper="${limit_arm_base_joint['upper']}"
velocity="${limit_arm_base_joint['velocity']}"/>
</joint>
<link name="${station_name}${device_name}arm_base">
<inertial>
<origin rpy="0 0 0" xyz="1.48458338655733E-06 -0.00831873687136486 0.351728466012153"/>
<mass value="16.1341586205194"/>
<inertia ixx="0.54871651759045" ixy="7.65476367433116E-07" ixz="2.0515139488158E-07" iyy="0.55113098995396" iyz="-5.13261457726806E-07" izz="0.0619081867727048"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_base.STL"/>
</geometry>
<material name="">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_base.STL"/>
</geometry>
</collision>
</link>
<link name="${station_name}${device_name}arm_link_1">
<inertial>
<origin rpy="0 0 0" xyz="0 -0.0102223856758559 0.0348505130779933"/>
<mass value="0.828629227096429"/>
<inertia ixx="0.00119703598787112" ixy="-2.46083048832131E-19" ixz="1.43864352731199E-19" iyy="0.00108355785790042" iyz="1.88092240278693E-06" izz="0.00160914803816438"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_1.STL"/>
</geometry>
<material name="">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_1.STL"/>
</geometry>
</collision>
</link>
<joint name="${station_name}${device_name}arm_link_1_joint" type="prismatic">
<origin rpy="0 0 0" xyz="0 0.1249 0.15"/>
<parent link="${station_name}${device_name}arm_base"/>
<child link="${station_name}${device_name}arm_link_1"/>
<axis xyz="0 0 1"/>
<limit
effort="${limit_arm_link_1_joint['effort']}"
lower="${limit_arm_link_1_joint['lower']}"
upper="${limit_arm_link_1_joint['upper']}"
velocity="${limit_arm_link_1_joint['velocity']}"/>
</joint>
<link name="${station_name}${device_name}arm_link_2">
<inertial>
<origin rpy="0 0 0" xyz="-3.33066907387547E-16 0.100000000000003 -0.0325000000000004"/>
<mass value="2.04764861029349"/>
<inertia ixx="0.0150150059448827" ixy="-1.28113733272213E-17" ixz="6.7561418872754E-19" iyy="0.00262980501315445" iyz="7.44451536320152E-18" izz="0.0162030186138787"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_2.STL"/>
</geometry>
<material name="">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_2.STL"/>
</geometry>
</collision>
</link>
<joint name="${station_name}${device_name}arm_link_2_joint" type="revolute">
<origin rpy="0 0 0" xyz="0 0 0"/>
<parent link="${station_name}${device_name}arm_link_1"/>
<child link="${station_name}${device_name}arm_link_2"/>
<axis xyz="0 0 1"/>
<limit
effort="${limit_arm_link_2_joint['effort']}"
lower="${limit_arm_link_2_joint['lower']}"
upper="${limit_arm_link_2_joint['upper']}"
velocity="${limit_arm_link_2_joint['velocity']}"/>
</joint>
<link name="${station_name}${device_name}arm_link_3">
<inertial>
<origin rpy="0 0 0" xyz="4.77395900588817E-15 0.0861257730831348 -0.0227999999999999"/>
<mass value="1.19870202871083"/>
<inertia ixx="0.00780783223764428" ixy="7.26567379579506E-18" ixz="1.02766851352053E-18" iyy="0.00109642607170081" iyz="-9.73775385060067E-18" izz="0.0084997384510058"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_3.STL"/>
</geometry>
<material name="">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_3.STL"/>
</geometry>
</collision>
</link>
<joint name="${station_name}${device_name}arm_link_3_joint" type="revolute">
<origin rpy="0 0 0" xyz="0 0.2 -0.0647"/>
<parent link="${station_name}${device_name}arm_link_2"/>
<child link="${station_name}${device_name}arm_link_3"/>
<axis xyz="0 0 1"/>
<limit
effort="${limit_arm_link_3_joint['effort']}"
lower="${limit_arm_link_3_joint['lower']}"
upper="${limit_arm_link_3_joint['upper']}"
velocity="${limit_arm_link_3_joint['velocity']}"/>
</joint>
<link name="${station_name}${device_name}gripper_base">
<inertial>
<origin rpy="0 0 0" xyz="-6.05365748571618E-05 0.0373027483464434 -0.0264392017534612"/>
<mass value="0.511925198394943"/>
<inertia ixx="0.000640463815051467" ixy="1.08132229596356E-06" ixz="7.165124649009E-07" iyy="0.000552164156414554" iyz="9.80000237347941E-06" izz="0.00103553457812823"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_base.STL"/>
</geometry>
<material name="">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_base.STL"/>
</geometry>
</collision>
</link>
<joint name="${station_name}${device_name}gripper_base_joint" type="revolute">
<origin rpy="0 0 0" xyz="0 0.2 -0.045"/>
<parent link="${station_name}${device_name}arm_link_3"/>
<child link="${station_name}${device_name}gripper_base"/>
<axis xyz="0 0 1"/>
<limit
effort="${limit_gripper_base_joint['effort']}"
lower="${limit_gripper_base_joint['lower']}"
upper="${limit_gripper_base_joint['upper']}"
velocity="${limit_gripper_base_joint['velocity']}"/>
</joint>
<link name="${station_name}${device_name}gripper_right">
<inertial>
<origin rpy="0 0 0" xyz="0.0340005471193899 0.0339655085140826 -0.0325252119823062"/>
<mass value="0.013337481136229"/>
<inertia ixx="2.02427962974094E-05" ixy="1.78442722292145E-06" ixz="-4.36485961300289E-07" iyy="1.4816483393622E-06" iyz="2.60539468115799E-06" izz="1.96629693098755E-05"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_right.STL"/>
</geometry>
<material name="">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_right.STL"/>
</geometry>
</collision>
</link>
<joint name="${station_name}${device_name}gripper_right_joint" type="prismatic">
<origin rpy="0 0 0" xyz="0 0.0942 -0.022277"/>
<parent link="${station_name}${device_name}gripper_base"/>
<child link="${station_name}${device_name}gripper_right"/>
<axis xyz="1 0 0"/>
<limit
effort="${limit_gripper_right_joint['effort']}"
lower="${limit_gripper_right_joint['lower']}"
upper="${limit_gripper_right_joint['upper']}"
velocity="${limit_gripper_right_joint['velocity']}"/>
</joint>
<link name="${station_name}${device_name}gripper_left">
<inertial>
<origin rpy="0 3.1416 0" xyz="-0.0340005471193521 0.0339655081029604 -0.0325252119827364"/>
<mass value="0.0133374811362292"/>
<inertia ixx="2.02427962974094E-05" ixy="-1.78442720812615E-06" ixz="4.36485961300305E-07" iyy="1.48164833936224E-06" iyz="2.6053946859901E-06" izz="1.96629693098755E-05"/>
</inertial>
<visual>
<origin rpy="0 3.1416 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_left.STL"/>
</geometry>
<material name="">
<color rgba="1 1 1 1"/>
</material>
</visual>
<collision>
<origin rpy="0 3.1416 0" xyz="0 0 0"/>
<geometry>
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_left.STL"/>
</geometry>
</collision>
</link>
<joint name="${station_name}${device_name}gripper_left_joint" type="prismatic">
<origin rpy="0 3.1416 0" xyz="0 0.0942 -0.022277"/>
<parent link="${station_name}${device_name}gripper_base"/>
<child link="${station_name}${device_name}gripper_left"/>
<axis xyz="1 0 0"/>
<limit
effort="${limit_gripper_left_joint['effort']}"
lower="${limit_gripper_left_joint['lower']}"
upper="${limit_gripper_left_joint['upper']}"
velocity="${limit_gripper_left_joint['velocity']}"/>
<mimic joint="${station_name}${device_name}gripper_right_joint" multiplier="1" />
</joint>
</xacro:macro>
</robot>

View File

@@ -1,30 +1,30 @@
benyao_arm_controller:
arm_slider_arm_controller:
ros__parameters:
command_interfaces:
- position
joints:
- benyao_arm_base_joint
- benyao_arm_link_1_joint
- benyao_arm_link_2_joint
- benyao_arm_link_3_joint
- benyao_gripper_base_joint
- arm_slider_arm_base_joint
- arm_slider_arm_link_1_joint
- arm_slider_arm_link_2_joint
- arm_slider_arm_link_3_joint
- arm_slider_gripper_base_joint
state_interfaces:
- position
- velocity
benyao_gripper_controller:
arm_slider_gripper_controller:
ros__parameters:
command_interfaces:
- position
joints:
- benyao_gripper_right_joint
- arm_slider_gripper_right_joint
state_interfaces:
- position
- velocity
controller_manager:
ros__parameters:
benyao_arm_controller:
arm_slider_arm_controller:
type: joint_trajectory_controller/JointTrajectoryController
benyao_gripper_controller:
arm_slider_gripper_controller:
type: joint_trajectory_controller/JointTrajectoryController
joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster

View File

@@ -105,83 +105,83 @@ Visualization Manager:
Expand Link Details: false
Expand Tree: false
Link Tree Style: Links in Alphabetic Order
PLR_STATION_deck_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_first_link:
arm_slider_arm_base:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_fourth_link:
arm_slider_arm_link_1:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_main_link:
arm_slider_arm_link_2:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_second_link:
arm_slider_arm_link_3:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_socketTypeGenericSbsFootprint:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_socketTypeHEPAModule:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_third_link:
arm_slider_arm_slideway:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_base:
arm_slider_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
arm_slider_gripper_base:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_1:
arm_slider_gripper_left:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_2:
arm_slider_gripper_right:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_3:
deck_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
deck_first_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_slideway:
deck_fourth_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
benyao_gripper_base:
deck_main_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_gripper_left:
deck_second_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_gripper_right:
deck_socketTypeGenericSbsFootprint:
Alpha: 1
Show Axes: false
Show Trail: false
deck_socketTypeHEPAModule:
Alpha: 1
Show Axes: false
Show Trail: false
deck_third_link:
Alpha: 1
Show Axes: false
Show Trail: false
@@ -256,83 +256,83 @@ Visualization Manager:
Expand Link Details: false
Expand Tree: false
Link Tree Style: Links in Alphabetic Order
PLR_STATION_deck_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_first_link:
arm_slider_arm_base:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_fourth_link:
arm_slider_arm_link_1:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_main_link:
arm_slider_arm_link_2:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_second_link:
arm_slider_arm_link_3:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_socketTypeGenericSbsFootprint:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_socketTypeHEPAModule:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_third_link:
arm_slider_arm_slideway:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_base:
arm_slider_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
arm_slider_gripper_base:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_1:
arm_slider_gripper_left:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_2:
arm_slider_gripper_right:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_3:
deck_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
deck_first_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_slideway:
deck_fourth_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
benyao_gripper_base:
deck_main_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_gripper_left:
deck_second_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_gripper_right:
deck_socketTypeGenericSbsFootprint:
Alpha: 1
Show Axes: false
Show Trail: false
deck_socketTypeHEPAModule:
Alpha: 1
Show Axes: false
Show Trail: false
deck_third_link:
Alpha: 1
Show Axes: false
Show Trail: false
@@ -377,7 +377,7 @@ Visualization Manager:
Goal State Color: 250; 128; 0
Interactive Marker Size: 0
Joint Violation Color: 255; 0; 255
Planning Group: benyao_arm
Planning Group: arm_slider_arm
Query Goal State: false
Query Start State: false
Show Workspace: false
@@ -400,83 +400,83 @@ Visualization Manager:
Expand Link Details: false
Expand Tree: false
Link Tree Style: Links in Alphabetic Order
PLR_STATION_deck_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_first_link:
arm_slider_arm_base:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_fourth_link:
arm_slider_arm_link_1:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_main_link:
arm_slider_arm_link_2:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_second_link:
arm_slider_arm_link_3:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
PLR_STATION_deck_socketTypeGenericSbsFootprint:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_socketTypeHEPAModule:
Alpha: 1
Show Axes: false
Show Trail: false
PLR_STATION_deck_third_link:
arm_slider_arm_slideway:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_base:
arm_slider_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
arm_slider_gripper_base:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_1:
arm_slider_gripper_left:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_2:
arm_slider_gripper_right:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_link_3:
deck_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
deck_first_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_arm_slideway:
deck_fourth_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_device_link:
Alpha: 1
Show Axes: false
Show Trail: false
benyao_gripper_base:
deck_main_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_gripper_left:
deck_second_link:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
benyao_gripper_right:
deck_socketTypeGenericSbsFootprint:
Alpha: 1
Show Axes: false
Show Trail: false
deck_socketTypeHEPAModule:
Alpha: 1
Show Axes: false
Show Trail: false
deck_third_link:
Alpha: 1
Show Axes: false
Show Trail: false
@@ -564,10 +564,10 @@ Visualization Manager:
Invert Z Axis: false
Name: Current View
Near Clip Distance: 0.009999999776482582
Pitch: 0.48479583859443665
Pitch: 0.4297958016395569
Target Frame: <Fixed Frame>
Value: Orbit (rviz)
Yaw: 0.042561568319797516
Yaw: 0.3525616228580475
Saved: ~
Window Geometry:
Displays:
@@ -576,10 +576,10 @@ Window Geometry:
Hide Left Dock: false
Hide Right Dock: true
MotionPlanning:
collapsed: false
collapsed: true
MotionPlanning - Trajectory Slider:
collapsed: false
QMainWindow State: 000000ff00000000fd0000000400000000000003a30000079bfc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b000000b000fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c0061007900730100000027000004c60000018200fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000280020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000000000000000fb00000044004d006f00740069006f006e0050006c0061006e006e0069006e00670020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000002600000026fb0000001c004d006f00740069006f006e0050006c0061006e006e0069006e006701000004f9000002c9000002b800ffffff000000010000010f00000387fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b000003870000013200fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d0065010000000000000450000000000000000000000bc50000079b00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730000000000ffffffff0000000000000000
QMainWindow State: 000000ff00000000fd0000000400000000000003a30000079bfc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b000000b000fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c0061007900730100000027000004c60000018200fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000280020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000000000000000fb00000044004d006f00740069006f006e0050006c0061006e006e0069006e00670020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000007a00fffffffb0000001c004d006f00740069006f006e0050006c0061006e006e0069006e006701000004f9000002c9000002b800ffffff000000010000010f00000387fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b000003870000013200fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d0065010000000000000450000000000000000000000bc50000079b00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730000000000ffffffff0000000000000000
Selection:
collapsed: false
Tool Properties:

View File

View File

@@ -0,0 +1,177 @@
import time
import threading
class MockChiller:
def __init__(self, port: str = "MOCK"):
self.port = port
self._current_temperature: float = 25.0 # 室温开始
self._target_temperature: float = 25.0
self._status: str = "Idle"
self._is_cooling: bool = False
self._is_heating: bool = False
self._vessel = "Unknown"
self._purpose = "Unknown"
# 模拟温度变化的线程
self._temperature_thread = None
self._running = True
self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
self._temperature_thread.daemon = True
self._temperature_thread.start()
@property
def current_temperature(self) -> float:
"""当前温度 - 会被自动识别的设备属性"""
return self._current_temperature
@property
def target_temperature(self) -> float:
"""目标温度"""
return self._target_temperature
@property
def status(self) -> str:
"""设备状态 - 会被自动识别的设备属性"""
return self._status
@property
def is_cooling(self) -> bool:
"""是否正在冷却"""
return self._is_cooling
@property
def is_heating(self) -> bool:
"""是否正在加热"""
return self._is_heating
@property
def vessel(self) -> str:
"""当前操作的容器名称"""
return self._vessel
@property
def purpose(self) -> str:
"""当前操作目的"""
return self._purpose
def heat_chill_start(self, vessel: str, temp: float, purpose: str):
"""设置目标温度并记录容器和目的"""
self._vessel = str(vessel)
self._purpose = str(purpose)
self._target_temperature = float(temp)
diff = self._target_temperature - self._current_temperature
if abs(diff) < 0.1:
self._status = "At Target Temperature"
self._is_cooling = False
self._is_heating = False
elif diff < 0:
self._status = "Cooling"
self._is_cooling = True
self._is_heating = False
else:
self._status = "Heating"
self._is_heating = True
self._is_cooling = False
self._start_temperature_control()
return True
def heat_chill_stop(self, vessel: str):
"""停止加热/制冷"""
if vessel != self._vessel:
return {"success": False, "status": f"Wrong vessel: expected {self._vessel}, got {vessel}"}
# 停止温度控制线程,锁定当前温度
self._stop_temperature_control()
# 更新状态
self._status = "Stopped"
self._is_cooling = False
self._is_heating = False
# 重新启动线程但保持温度
self._running = True
self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
self._temperature_thread.daemon = True
self._temperature_thread.start()
return {"success": True, "status": self._status}
def _start_temperature_control(self):
"""启动温度控制线程"""
self._running = True
if self._temperature_thread is None or not self._temperature_thread.is_alive():
self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
self._temperature_thread.daemon = True
self._temperature_thread.start()
def _stop_temperature_control(self):
"""停止温度控制"""
self._running = False
if self._temperature_thread:
self._temperature_thread.join(timeout=1.0)
def _temperature_control_loop(self):
"""温度控制循环 - 模拟真实冷却器的温度变化"""
while self._running:
# 如果状态是 Stopped不改变温度
if self._status == "Stopped":
time.sleep(1.0)
continue
temp_diff = self._target_temperature - self._current_temperature
if abs(temp_diff) < 0.1:
self._status = "At Target Temperature"
self._is_cooling = False
self._is_heating = False
elif temp_diff < 0:
self._status = "Cooling"
self._is_cooling = True
self._is_heating = False
self._current_temperature -= 0.5
else:
self._status = "Heating"
self._is_heating = True
self._is_cooling = False
self._current_temperature += 0.3
time.sleep(1.0)
def emergency_stop(self):
"""紧急停止"""
self._status = "Emergency Stop"
self._stop_temperature_control()
self._is_cooling = False
self._is_heating = False
def get_status_info(self) -> dict:
"""获取完整状态信息"""
return {
"current_temperature": self._current_temperature,
"target_temperature": self._target_temperature,
"status": self._status,
"is_cooling": self._is_cooling,
"is_heating": self._is_heating,
"vessel": self._vessel,
"purpose": self._purpose,
}
# 用于测试的主函数
if __name__ == "__main__":
chiller = MockChiller()
# 测试基本功能
print("启动冷却器测试...")
print(f"初始状态: {chiller.get_status_info()}")
# 模拟运行10秒
for i in range(10):
time.sleep(1)
print(f"{i+1}秒: 当前温度={chiller.current_temperature:.1f}°C, 状态={chiller.status}")
chiller.emergency_stop()
print("测试完成")

View File

@@ -0,0 +1,235 @@
import time
import threading
class MockFilter:
def __init__(self, port: str = "MOCK"):
# 基本参数初始化
self.port = port
self._status: str = "Idle"
self._is_filtering: bool = False
# 过滤性能参数
self._flow_rate: float = 1.0 # 流速(L/min)
self._pressure_drop: float = 0.0 # 压降(Pa)
self._filter_life: float = 100.0 # 滤芯寿命(%)
# 过滤操作参数
self._vessel: str = "" # 源容器
self._filtrate_vessel: str = "" # 目标容器
self._stir: bool = False # 是否搅拌
self._stir_speed: float = 0.0 # 搅拌速度
self._temperature: float = 25.0 # 温度(℃)
self._continue_heatchill: bool = False # 是否继续加热/制冷
self._target_volume: float = 0.0 # 目标过滤体积(L)
self._filtered_volume: float = 0.0 # 已过滤体积(L)
self._progress: float = 0.0 # 过滤进度(%)
# 线程控制
self._filter_thread = None
self._running = False
@property
def status(self) -> str:
return self._status
@property
def is_filtering(self) -> bool:
return self._is_filtering
@property
def flow_rate(self) -> float:
return self._flow_rate
@property
def pressure_drop(self) -> float:
return self._pressure_drop
@property
def filter_life(self) -> float:
return self._filter_life
# 新增 property
@property
def vessel(self) -> str:
return self._vessel
@property
def filtrate_vessel(self) -> str:
return self._filtrate_vessel
@property
def filtered_volume(self) -> float:
return self._filtered_volume
@property
def progress(self) -> float:
return self._progress
@property
def stir(self) -> bool:
return self._stir
@property
def stir_speed(self) -> float:
return self._stir_speed
@property
def temperature(self) -> float:
return self._temperature
@property
def continue_heatchill(self) -> bool:
return self._continue_heatchill
@property
def target_volume(self) -> float:
return self._target_volume
def filter(self, vessel: str, filtrate_vessel: str, stir: bool = False, stir_speed: float = 0.0, temp: float = 25.0, continue_heatchill: bool = False, volume: float = 0.0) -> dict:
"""新的过滤操作"""
# 停止任何正在进行的过滤
if self._is_filtering:
self.stop_filtering()
# 验证参数
if volume <= 0:
return {"success": False, "message": "Target volume must be greater than 0"}
# 设置新的过滤参数
self._vessel = vessel
self._filtrate_vessel = filtrate_vessel
self._stir = stir
self._stir_speed = stir_speed
self._temperature = temp
self._continue_heatchill = continue_heatchill
self._target_volume = volume
# 重置过滤状态
self._filtered_volume = 0.0
self._progress = 0.0
self._status = "Starting Filter"
# 启动过滤过程
self._flow_rate = 1.0 # 设置默认流速
self._start_filter_process()
return {"success": True, "message": "Filter started"}
def stop_filtering(self):
"""停止过滤"""
self._status = "Stopping Filter"
self._stop_filter_process()
self._flow_rate = 0.0
self._is_filtering = False
self._status = "Stopped"
return True
def replace_filter(self):
"""更换滤芯"""
self._filter_life = 100.0
self._status = "Filter Replaced"
return True
def _start_filter_process(self):
"""启动过滤过程线程"""
if not self._running:
self._running = True
self._is_filtering = True
self._filter_thread = threading.Thread(target=self._filter_loop)
self._filter_thread.daemon = True
self._filter_thread.start()
def _stop_filter_process(self):
"""停止过滤过程"""
self._running = False
if self._filter_thread:
self._filter_thread.join(timeout=1.0)
def _filter_loop(self):
"""过滤进程主循环"""
update_interval = 1.0 # 更新间隔(秒)
while self._running and self._is_filtering:
try:
self._status = "Filtering"
# 计算这一秒过滤的体积 (L/min -> L/s)
volume_increment = (self._flow_rate / 60.0) * update_interval
# 更新已过滤体积
self._filtered_volume += volume_increment
# 更新进度 (避免除零错误)
if self._target_volume > 0:
self._progress = min(100.0, (self._filtered_volume / self._target_volume) * 100.0)
# 更新滤芯寿命 (每过滤1L减少0.5%寿命)
self._filter_life = max(0.0, self._filter_life - (volume_increment * 0.5))
# 更新压降 (根据滤芯寿命和流速动态计算)
life_factor = self._filter_life / 100.0 # 将寿命转换为0-1的因子
flow_factor = self._flow_rate / 2.0 # 将流速标准化(假设2L/min是标准流速)
base_pressure = 100.0 # 基础压降
# 压降随滤芯寿命降低而增加,随流速增加而增加
self._pressure_drop = base_pressure * (2 - life_factor) * flow_factor
# 检查是否完成目标体积
if self._target_volume > 0 and self._filtered_volume >= self._target_volume:
self._status = "Completed"
self._progress = 100.0
self.stop_filtering()
break
# 检查滤芯寿命
if self._filter_life <= 10.0:
self._status = "Filter Needs Replacement"
time.sleep(update_interval)
except Exception as e:
print(f"Error in filter loop: {e}")
self.emergency_stop()
break
def emergency_stop(self):
"""紧急停止"""
self._status = "Emergency Stop"
self._stop_filter_process()
self._is_filtering = False
self._flow_rate = 0.0
def get_status_info(self) -> dict:
"""扩展的状态信息"""
return {
"status": self._status,
"is_filtering": self._is_filtering,
"flow_rate": self._flow_rate,
"pressure_drop": self._pressure_drop,
"filter_life": self._filter_life,
"vessel": self._vessel,
"filtrate_vessel": self._filtrate_vessel,
"filtered_volume": self._filtered_volume,
"target_volume": self._target_volume,
"progress": self._progress,
"temperature": self._temperature,
"stir": self._stir,
"stir_speed": self._stir_speed
}
# 用于测试的主函数
if __name__ == "__main__":
filter_device = MockFilter()
# 测试基本功能
print("启动过滤器测试...")
print(f"初始状态: {filter_device.get_status_info()}")
# 模拟运行10秒
for i in range(10):
time.sleep(1)
print(
f"{i+1}秒: "
f"寿命={filter_device.filter_life:.1f}%, 状态={filter_device.status}"
)
filter_device.emergency_stop()
print("测试完成")

View File

@@ -0,0 +1,247 @@
import time
import threading
class MockHeater:
def __init__(self, port: str = "MOCK"):
self.port = port
self._current_temperature: float = 25.0 # 室温开始
self._target_temperature: float = 25.0
self._status: str = "Idle"
self._is_heating: bool = False
self._heating_power: float = 0.0 # 加热功率百分比 0-100
self._max_temperature: float = 300.0 # 最大加热温度
# 新增加的属性
self._vessel: str = "Unknown"
self._purpose: str = "Unknown"
self._stir: bool = False
self._stir_speed: float = 0.0
# 模拟加热过程的线程
self._heating_thread = None
self._running = True
self._heating_thread = threading.Thread(target=self._heating_control_loop)
self._heating_thread.daemon = True
self._heating_thread.start()
@property
def current_temperature(self) -> float:
"""当前温度 - 会被自动识别的设备属性"""
return self._current_temperature
@property
def target_temperature(self) -> float:
"""目标温度"""
return self._target_temperature
@property
def status(self) -> str:
"""设备状态 - 会被自动识别的设备属性"""
return self._status
@property
def is_heating(self) -> bool:
"""是否正在加热"""
return self._is_heating
@property
def heating_power(self) -> float:
"""加热功率百分比"""
return self._heating_power
@property
def max_temperature(self) -> float:
"""最大加热温度"""
return self._max_temperature
@property
def vessel(self) -> str:
"""当前操作的容器名称"""
return self._vessel
@property
def purpose(self) -> str:
"""操作目的"""
return self._purpose
@property
def stir(self) -> bool:
"""是否搅拌"""
return self._stir
@property
def stir_speed(self) -> float:
"""搅拌速度"""
return self._stir_speed
def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> dict:
"""开始加热/制冷过程"""
self._vessel = str(vessel)
self._purpose = str(purpose)
self._target_temperature = float(temp)
diff = self._target_temperature - self._current_temperature
if abs(diff) < 0.1:
self._status = "At Target Temperature"
self._is_heating = False
elif diff > 0:
self._status = "Heating"
self._is_heating = True
else:
self._status = "Cooling Down"
self._is_heating = False
return {"success": True, "status": self._status}
def heat_chill_stop(self, vessel: str) -> dict:
"""停止加热/制冷"""
if vessel != self._vessel:
return {"success": False, "status": f"Wrong vessel: expected {self._vessel}, got {vessel}"}
self._status = "Stopped"
self._is_heating = False
self._heating_power = 0.0
return {"success": True, "status": self._status}
def heat_chill(self, vessel: str, temp: float, time: float,
stir: bool = False, stir_speed: float = 0.0,
purpose: str = "Unknown") -> dict:
"""完整的加热/制冷控制"""
self._vessel = str(vessel)
self._target_temperature = float(temp)
self._purpose = str(purpose)
self._stir = stir
self._stir_speed = stir_speed
diff = self._target_temperature - self._current_temperature
if abs(diff) < 0.1:
self._status = "At Target Temperature"
self._is_heating = False
elif diff > 0:
self._status = "Heating"
self._is_heating = True
else:
self._status = "Cooling Down"
self._is_heating = False
return {"success": True, "status": self._status}
def set_temperature(self, temperature: float):
"""设置目标温度 - 需要在注册表添加的设备动作"""
try:
temperature = float(temperature)
except ValueError:
self._status = "Error: Invalid temperature value"
return False
if temperature > self._max_temperature:
self._status = f"Error: Temperature exceeds maximum ({self._max_temperature}°C)"
return False
self._target_temperature = temperature
self._status = "Setting Temperature"
# 启动加热控制
self._start_heating_control()
return True
def set_heating_power(self, power: float):
"""设置加热功率"""
try:
power = float(power)
except ValueError:
self._status = "Error: Invalid power value"
return False
self._heating_power = max(0.0, min(100.0, power)) # 限制在0-100%
return True
def _start_heating_control(self):
"""启动加热控制线程"""
if not self._running:
self._running = True
self._heating_thread = threading.Thread(target=self._heating_control_loop)
self._heating_thread.daemon = True
self._heating_thread.start()
def _stop_heating_control(self):
"""停止加热控制"""
self._running = False
if self._heating_thread:
self._heating_thread.join(timeout=1.0)
def _heating_control_loop(self):
"""加热控制循环"""
while self._running:
# 如果状态是 Stopped不改变温度
if self._status == "Stopped":
time.sleep(1.0)
continue
temp_diff = self._target_temperature - self._current_temperature
if abs(temp_diff) < 0.1:
self._status = "At Target Temperature"
self._is_heating = False
self._heating_power = 10.0
elif temp_diff > 0:
self._status = "Heating"
self._is_heating = True
self._heating_power = min(100.0, abs(temp_diff) * 2)
self._current_temperature += 0.5
else:
self._status = "Cooling Down"
self._is_heating = False
self._heating_power = 0.0
self._current_temperature -= 0.2
time.sleep(1.0)
def emergency_stop(self):
"""紧急停止"""
self._status = "Emergency Stop"
self._stop_heating_control()
self._is_heating = False
self._heating_power = 0.0
def get_status_info(self) -> dict:
"""获取完整状态信息"""
return {
"current_temperature": self._current_temperature,
"target_temperature": self._target_temperature,
"status": self._status,
"is_heating": self._is_heating,
"heating_power": self._heating_power,
"max_temperature": self._max_temperature,
"vessel": self._vessel,
"purpose": self._purpose,
"stir": self._stir,
"stir_speed": self._stir_speed
}
# 用于测试的主函数
if __name__ == "__main__":
heater = MockHeater()
print("启动加热器测试...")
print(f"初始状态: {heater.get_status_info()}")
# 设置目标温度为80度
heater.set_temperature(80.0)
# 模拟运行15秒
try:
for i in range(15):
time.sleep(1)
status = heater.get_status_info()
print(
f"\r温度: {status['current_temperature']:.1f}°C / {status['target_temperature']:.1f}°C | "
f"功率: {status['heating_power']:.1f}% | 状态: {status['status']}",
end=""
)
except KeyboardInterrupt:
heater.emergency_stop()
print("\n测试被手动停止")
print("\n测试完成")

View File

@@ -0,0 +1,360 @@
import time
import threading
from datetime import datetime, timedelta
class MockPump:
def __init__(self, port: str = "MOCK"):
self.port = port
# 设备基本状态属性
self._current_device = "MockPump1" # 设备标识符
self._status: str = "Idle" # 设备状态Idle, Running, Error, Stopped
self._pump_state: str = "Stopped" # 泵运行状态Running, Stopped, Paused
# 流量相关属性
self._flow_rate: float = 0.0 # 当前流速 (mL/min)
self._target_flow_rate: float = 0.0 # 目标流速 (mL/min)
self._max_flow_rate: float = 100.0 # 最大流速 (mL/min)
self._total_volume: float = 0.0 # 累计流量 (mL)
# 压力相关属性
self._pressure: float = 0.0 # 当前压力 (bar)
self._max_pressure: float = 10.0 # 最大压力 (bar)
# 运行控制线程
self._pump_thread = None
self._running = False
self._thread_lock = threading.Lock()
# 新增 PumpTransfer 相关属性
self._from_vessel: str = ""
self._to_vessel: str = ""
self._transfer_volume: float = 0.0
self._amount: str = ""
self._transfer_time: float = 0.0
self._is_viscous: bool = False
self._rinsing_solvent: str = ""
self._rinsing_volume: float = 0.0
self._rinsing_repeats: int = 0
self._is_solid: bool = False
# 时间追踪
self._start_time: datetime = None
self._time_spent: timedelta = timedelta()
self._time_remaining: timedelta = timedelta()
# ==================== 状态属性 ====================
# 这些属性会被Uni-Lab系统自动识别并定时对外广播
@property
def status(self) -> str:
return self._status
@property
def current_device(self) -> str:
"""当前设备标识符"""
return self._current_device
@property
def pump_state(self) -> str:
return self._pump_state
@property
def flow_rate(self) -> float:
return self._flow_rate
@property
def target_flow_rate(self) -> float:
return self._target_flow_rate
@property
def pressure(self) -> float:
return self._pressure
@property
def total_volume(self) -> float:
return self._total_volume
@property
def max_flow_rate(self) -> float:
return self._max_flow_rate
@property
def max_pressure(self) -> float:
return self._max_pressure
# 添加新的属性访问器
@property
def from_vessel(self) -> str:
return self._from_vessel
@property
def to_vessel(self) -> str:
return self._to_vessel
@property
def transfer_volume(self) -> float:
return self._transfer_volume
@property
def amount(self) -> str:
return self._amount
@property
def transfer_time(self) -> float:
return self._transfer_time
@property
def is_viscous(self) -> bool:
return self._is_viscous
@property
def rinsing_solvent(self) -> str:
return self._rinsing_solvent
@property
def rinsing_volume(self) -> float:
return self._rinsing_volume
@property
def rinsing_repeats(self) -> int:
return self._rinsing_repeats
@property
def is_solid(self) -> bool:
return self._is_solid
# 修改这两个属性装饰器
@property
def time_spent(self) -> float:
"""已用时间(秒)"""
if isinstance(self._time_spent, timedelta):
return self._time_spent.total_seconds()
return float(self._time_spent)
@property
def time_remaining(self) -> float:
"""剩余时间(秒)"""
if isinstance(self._time_remaining, timedelta):
return self._time_remaining.total_seconds()
return float(self._time_remaining)
# ==================== 设备控制方法 ====================
# 这些方法需要在注册表中添加会作为ActionServer接受控制指令
def pump_transfer(self, from_vessel: str, to_vessel: str, volume: float,
amount: str = "", time: float = 0.0, viscous: bool = False,
rinsing_solvent: str = "", rinsing_volume: float = 0.0,
rinsing_repeats: int = 0, solid: bool = False) -> dict:
"""Execute pump transfer operation"""
# Stop any existing operation first
self._stop_pump_operation()
# Set transfer parameters
self._from_vessel = from_vessel
self._to_vessel = to_vessel
self._transfer_volume = float(volume)
self._amount = amount
self._transfer_time = float(time)
self._is_viscous = viscous
self._rinsing_solvent = rinsing_solvent
self._rinsing_volume = float(rinsing_volume)
self._rinsing_repeats = int(rinsing_repeats)
self._is_solid = solid
# Calculate flow rate
if self._transfer_time > 0 and self._transfer_volume > 0:
self._target_flow_rate = (self._transfer_volume / self._transfer_time) * 60.0
else:
self._target_flow_rate = 10.0 if not self._is_viscous else 5.0
# Reset timers and counters
self._start_time = datetime.now()
self._time_spent = timedelta()
self._time_remaining = timedelta(seconds=self._transfer_time)
self._total_volume = 0.0
self._flow_rate = 0.0
# Start pump operation
self._pump_state = "Running"
self._status = "Starting Transfer"
self._running = True
# Start pump operation thread
self._pump_thread = threading.Thread(target=self._pump_operation_loop)
self._pump_thread.daemon = True
self._pump_thread.start()
# Wait briefly to ensure thread starts
time.sleep(0.1)
return {
"success": True,
"status": self._status,
"current_device": self._current_device,
"time_spent": 0.0,
"time_remaining": float(self._transfer_time)
}
def pause_pump(self) -> str:
if self._pump_state != "Running":
self._status = "Error: Pump not running"
return "Error"
self._pump_state = "Paused"
self._status = "Pump Paused"
self._stop_pump_operation()
return "Success"
def resume_pump(self) -> str:
if self._pump_state != "Paused":
self._status = "Error: Pump not paused"
return "Error"
self._pump_state = "Running"
self._status = "Resuming Pump"
self._start_pump_operation()
return "Success"
def reset_volume_counter(self) -> str:
self._total_volume = 0.0
self._status = "Volume counter reset"
return "Success"
def emergency_stop(self) -> str:
self._status = "Emergency Stop"
self._pump_state = "Stopped"
self._stop_pump_operation()
self._flow_rate = 0.0
self._pressure = 0.0
self._target_flow_rate = 0.0
return "Success"
# ==================== 内部控制方法 ====================
def _start_pump_operation(self):
with self._thread_lock:
if not self._running:
self._running = True
self._pump_thread = threading.Thread(target=self._pump_operation_loop)
self._pump_thread.daemon = True
self._pump_thread.start()
def _stop_pump_operation(self):
with self._thread_lock:
self._running = False
if self._pump_thread and self._pump_thread.is_alive():
self._pump_thread.join(timeout=2.0)
def _pump_operation_loop(self):
"""泵运行主循环"""
print("Pump operation loop started") # Debug print
while self._running and self._pump_state == "Running":
try:
# Calculate flow rate adjustment
flow_diff = self._target_flow_rate - self._flow_rate
# Adjust flow rate more aggressively (50% of difference)
adjustment = flow_diff * 0.5
self._flow_rate += adjustment
# Ensure flow rate is within bounds
self._flow_rate = max(0.1, min(self._max_flow_rate, self._flow_rate))
# Update status based on flow rate
if abs(flow_diff) < 0.1:
self._status = "Running at Target Flow Rate"
else:
self._status = "Adjusting Flow Rate"
# Calculate volume increment
volume_increment = (self._flow_rate / 60.0) # mL/s
self._total_volume += volume_increment
# Update time tracking
self._time_spent = datetime.now() - self._start_time
if self._transfer_time > 0:
remaining = self._transfer_time - self._time_spent.total_seconds()
self._time_remaining = timedelta(seconds=max(0, remaining))
# Check completion
if self._total_volume >= self._transfer_volume:
self._status = "Transfer Completed"
self._pump_state = "Stopped"
self._running = False
break
# Update pressure
self._pressure = (self._flow_rate / self._max_flow_rate) * self._max_pressure
print(f"Debug - Flow: {self._flow_rate:.1f}, Volume: {self._total_volume:.1f}") # Debug print
time.sleep(1.0)
except Exception as e:
print(f"Error in pump operation: {str(e)}")
self._status = "Error in pump operation"
self._pump_state = "Stopped"
self._running = False
break
def get_status_info(self) -> dict:
"""
获取完整的设备状态信息
Returns:
dict: 包含所有设备状态的字典
"""
return {
"status": self._status,
"pump_state": self._pump_state,
"flow_rate": self._flow_rate,
"target_flow_rate": self._target_flow_rate,
"pressure": self._pressure,
"total_volume": self._total_volume,
"max_flow_rate": self._max_flow_rate,
"max_pressure": self._max_pressure,
"current_device": self._current_device,
"from_vessel": self._from_vessel,
"to_vessel": self._to_vessel,
"transfer_volume": self._transfer_volume,
"amount": self._amount,
"transfer_time": self._transfer_time,
"is_viscous": self._is_viscous,
"rinsing_solvent": self._rinsing_solvent,
"rinsing_volume": self._rinsing_volume,
"rinsing_repeats": self._rinsing_repeats,
"is_solid": self._is_solid,
"time_spent": self._time_spent.total_seconds(),
"time_remaining": self._time_remaining.total_seconds()
}
# 用于测试的主函数
if __name__ == "__main__":
pump = MockPump()
# 测试基本功能
print("启动泵设备测试...")
print(f"初始状态: {pump.get_status_info()}")
# 设置流速并启动
pump.set_flow_rate(50.0)
pump.start_pump()
# 模拟运行10秒
for i in range(10):
time.sleep(1)
print(f"{i+1}秒: 流速={pump.flow_rate:.1f}mL/min, 压力={pump.pressure:.2f}bar, 状态={pump.status}")
# 测试方向切换
print("切换泵方向...")
pump.emergency_stop()
print("测试完成")

View File

@@ -0,0 +1,390 @@
import time
import threading
import json
class MockRotavap:
"""
模拟旋转蒸发器设备类
这个类模拟了一个实验室旋转蒸发器的行为,包括旋转控制、
真空泵控制、温度控制等功能。参考了现有的 RotavapOne 实现。
"""
def __init__(self, port: str = "MOCK"):
"""
初始化MockRotavap实例
Args:
port (str): 设备端口,默认为"MOCK"表示模拟设备
"""
self.port = port
# 设备基本状态属性
self._status: str = "Idle" # 设备状态Idle, Running, Error, Stopped
# 旋转相关属性
self._rotate_state: str = "Stopped" # 旋转状态Running, Stopped
self._rotate_time: float = 0.0 # 旋转剩余时间 (秒)
self._rotate_speed: float = 0.0 # 旋转速度 (rpm)
self._max_rotate_speed: float = 300.0 # 最大旋转速度 (rpm)
# 真空泵相关属性
self._pump_state: str = "Stopped" # 泵状态Running, Stopped
self._pump_time: float = 0.0 # 泵剩余时间 (秒)
self._vacuum_level: float = 0.0 # 真空度 (mbar)
self._target_vacuum: float = 50.0 # 目标真空度 (mbar)
# 温度相关属性
self._temperature: float = 25.0 # 水浴温度 (°C)
self._target_temperature: float = 25.0 # 目标温度 (°C)
self._max_temperature: float = 180.0 # 最大温度 (°C)
# 运行控制线程
self._operation_thread = None
self._running = False
self._thread_lock = threading.Lock()
# 操作成功标志
self.success: str = "True" # 使用字符串而不是布尔值
# ==================== 状态属性 ====================
# 这些属性会被Uni-Lab系统自动识别并定时对外广播
@property
def status(self) -> str:
return self._status
@property
def rotate_state(self) -> str:
return self._rotate_state
@property
def rotate_time(self) -> float:
return self._rotate_time
@property
def rotate_speed(self) -> float:
return self._rotate_speed
@property
def pump_state(self) -> str:
return self._pump_state
@property
def pump_time(self) -> float:
return self._pump_time
@property
def vacuum_level(self) -> float:
return self._vacuum_level
@property
def temperature(self) -> float:
return self._temperature
@property
def target_temperature(self) -> float:
return self._target_temperature
# ==================== 设备控制方法 ====================
# 这些方法需要在注册表中添加会作为ActionServer接受控制指令
def set_timer(self, command: str) -> str:
"""
设置定时器 - 兼容现有RotavapOne接口
Args:
command (str): JSON格式的命令字符串包含rotate_time和pump_time
Returns:
str: 操作结果状态 ("Success", "Error")
"""
try:
timer = json.loads(command)
rotate_time = timer.get("rotate_time", 0)
pump_time = timer.get("pump_time", 0)
self.success = "False"
self._rotate_time = float(rotate_time)
self._pump_time = float(pump_time)
self.success = "True"
self._status = "Timer Set"
return "Success"
except (json.JSONDecodeError, ValueError, KeyError) as e:
self._status = f"Error: Invalid command format - {str(e)}"
self.success = "False"
return "Error"
def set_rotate_time(self, time_seconds: float) -> str:
"""
设置旋转时间
Args:
time_seconds (float): 旋转时间 (秒)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self.success = "False"
self._rotate_time = max(0.0, float(time_seconds))
self.success = "True"
self._status = "Rotate time set"
return "Success"
def set_pump_time(self, time_seconds: float) -> str:
"""
设置泵时间
Args:
time_seconds (float): 泵时间 (秒)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self.success = "False"
self._pump_time = max(0.0, float(time_seconds))
self.success = "True"
self._status = "Pump time set"
return "Success"
def set_rotate_speed(self, speed: float) -> str:
"""
设置旋转速度
Args:
speed (float): 旋转速度 (rpm)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if speed < 0 or speed > self._max_rotate_speed:
self._status = f"Error: Speed out of range (0-{self._max_rotate_speed})"
return "Error"
self._rotate_speed = speed
self._status = "Rotate speed set"
return "Success"
def set_temperature(self, temperature: float) -> str:
"""
设置水浴温度
Args:
temperature (float): 目标温度 (°C)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if temperature < 0 or temperature > self._max_temperature:
self._status = f"Error: Temperature out of range (0-{self._max_temperature})"
return "Error"
self._target_temperature = temperature
self._status = "Temperature set"
# 启动操作线程以开始温度控制
self._start_operation()
return "Success"
def start_rotation(self) -> str:
"""
启动旋转
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._rotate_time <= 0:
self._status = "Error: No rotate time set"
return "Error"
self._rotate_state = "Running"
self._status = "Rotation started"
return "Success"
def start_pump(self) -> str:
"""
启动真空泵
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._pump_time <= 0:
self._status = "Error: No pump time set"
return "Error"
self._pump_state = "Running"
self._status = "Pump started"
return "Success"
def stop_all_operations(self) -> str:
"""
停止所有操作
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._rotate_state = "Stopped"
self._pump_state = "Stopped"
self._stop_operation()
self._rotate_time = 0.0
self._pump_time = 0.0
self._vacuum_level = 0.0
self._status = "All operations stopped"
return "Success"
def emergency_stop(self) -> str:
"""
紧急停止
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._status = "Emergency Stop"
self.stop_all_operations()
return "Success"
# ==================== 内部控制方法 ====================
def _start_operation(self):
"""
启动操作线程
这个方法启动一个后台线程来模拟旋蒸的实际运行过程。
"""
with self._thread_lock:
if not self._running:
self._running = True
self._operation_thread = threading.Thread(target=self._operation_loop)
self._operation_thread.daemon = True
self._operation_thread.start()
def _stop_operation(self):
"""
停止操作线程
安全地停止后台运行线程并等待其完成。
"""
with self._thread_lock:
self._running = False
if self._operation_thread and self._operation_thread.is_alive():
self._operation_thread.join(timeout=2.0)
def _operation_loop(self):
"""
操作主循环
这个方法在后台线程中运行,模拟真实旋蒸的工作过程:
1. 时间倒计时
2. 温度控制
3. 真空度控制
4. 状态更新
"""
while self._running:
try:
# 处理旋转时间倒计时
if self._rotate_time > 0:
self._rotate_state = "Running"
self._rotate_time = max(0.0, self._rotate_time - 1.0)
else:
self._rotate_state = "Stopped"
# 处理泵时间倒计时
if self._pump_time > 0:
self._pump_state = "Running"
self._pump_time = max(0.0, self._pump_time - 1.0)
# 模拟真空度变化
if self._vacuum_level > self._target_vacuum:
self._vacuum_level = max(self._target_vacuum, self._vacuum_level - 5.0)
else:
self._pump_state = "Stopped"
# 真空度逐渐回升
self._vacuum_level = min(1013.25, self._vacuum_level + 2.0)
# 模拟温度控制
temp_diff = self._target_temperature - self._temperature
if abs(temp_diff) > 0.5:
if temp_diff > 0:
self._temperature += min(1.0, temp_diff * 0.1)
else:
self._temperature += max(-1.0, temp_diff * 0.1)
# 更新整体状态
if self._rotate_state == "Running" or self._pump_state == "Running":
self._status = "Operating"
elif self._rotate_time > 0 or self._pump_time > 0:
self._status = "Ready"
else:
self._status = "Idle"
# 等待1秒后继续下一次循环
time.sleep(1.0)
except Exception as e:
self._status = f"Error in operation: {str(e)}"
break
# 循环结束时的清理工作
self._status = "Idle"
def get_status_info(self) -> dict:
"""
获取完整的设备状态信息
Returns:
dict: 包含所有设备状态的字典
"""
return {
"status": self._status,
"rotate_state": self._rotate_state,
"rotate_time": self._rotate_time,
"rotate_speed": self._rotate_speed,
"pump_state": self._pump_state,
"pump_time": self._pump_time,
"vacuum_level": self._vacuum_level,
"temperature": self._temperature,
"target_temperature": self._target_temperature,
"success": self.success,
}
# 用于测试的主函数
if __name__ == "__main__":
rotavap = MockRotavap()
# 测试基本功能
print("启动旋转蒸发器测试...")
print(f"初始状态: {rotavap.get_status_info()}")
# 设置定时器
timer_command = '{"rotate_time": 300, "pump_time": 600}'
rotavap.set_timer(timer_command)
# 设置温度和转速
rotavap.set_temperature(60.0)
rotavap.set_rotate_speed(120.0)
# 启动操作
rotavap.start_rotation()
rotavap.start_pump()
# 模拟运行10秒
for i in range(10):
time.sleep(1)
print(
f"{i+1}秒: 旋转={rotavap.rotate_time:.0f}s, 泵={rotavap.pump_time:.0f}s, "
f"温度={rotavap.temperature:.1f}°C, 真空={rotavap.vacuum_level:.1f}mbar"
)
rotavap.emergency_stop()
print("测试完成")

View File

@@ -0,0 +1,399 @@
import time
import threading
from datetime import datetime, timedelta
class MockSeparator:
def __init__(self, port: str = "MOCK"):
self.port = port
# 基本状态属性
self._status: str = "Idle" # 当前总体状态
self._valve_state: str = "Closed" # 阀门状态Open 或 Closed
self._settling_time: float = 0.0 # 静置时间(秒)
# 搅拌相关属性
self._shake_time: float = 0.0 # 剩余摇摆时间(秒)
self._shake_status: str = "Not Shaking" # 摇摆状态
# 用于后台模拟 shake 动作
self._operation_thread = None
self._thread_lock = threading.Lock()
self._running = False
# Separate action 相关属性
self._current_device: str = "MockSeparator1"
self._purpose: str = "" # wash or extract
self._product_phase: str = "" # top or bottom
self._from_vessel: str = ""
self._separation_vessel: str = ""
self._to_vessel: str = ""
self._waste_phase_to_vessel: str = ""
self._solvent: str = ""
self._solvent_volume: float = 0.0
self._through: str = ""
self._repeats: int = 1
self._stir_time: float = 0.0
self._stir_speed: float = 0.0
self._time_spent = timedelta()
self._time_remaining = timedelta()
self._start_time = datetime.now() # 添加这一行
@property
def current_device(self) -> str:
return self._current_device
@property
def purpose(self) -> str:
return self._purpose
@property
def valve_state(self) -> str:
return self._valve_state
@property
def settling_time(self) -> float:
return self._settling_time
@property
def status(self) -> str:
return self._status
@property
def shake_time(self) -> float:
with self._thread_lock:
return self._shake_time
@property
def shake_status(self) -> str:
with self._thread_lock:
return self._shake_status
@property
def product_phase(self) -> str:
return self._product_phase
@property
def from_vessel(self) -> str:
return self._from_vessel
@property
def separation_vessel(self) -> str:
return self._separation_vessel
@property
def to_vessel(self) -> str:
return self._to_vessel
@property
def waste_phase_to_vessel(self) -> str:
return self._waste_phase_to_vessel
@property
def solvent(self) -> str:
return self._solvent
@property
def solvent_volume(self) -> float:
return self._solvent_volume
@property
def through(self) -> str:
return self._through
@property
def repeats(self) -> int:
return self._repeats
@property
def stir_time(self) -> float:
return self._stir_time
@property
def stir_speed(self) -> float:
return self._stir_speed
@property
def time_spent(self) -> float:
if self._running:
self._time_spent = datetime.now() - self._start_time
return self._time_spent.total_seconds()
@property
def time_remaining(self) -> float:
if self._running:
elapsed = (datetime.now() - self._start_time).total_seconds()
total_time = (self._stir_time + self._settling_time + 10) * self._repeats
remain = max(0, total_time - elapsed)
self._time_remaining = timedelta(seconds=remain)
return self._time_remaining.total_seconds()
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 = 0.0, through: str = "",
repeats: int = 1, stir_time: float = 0.0, stir_speed: float = 0.0,
settling_time: float = 60.0) -> dict:
"""
执行分离操作
"""
with self._thread_lock:
# 检查是否已经在运行
if self._running:
return {
"success": False,
"status": "Error: Operation already in progress"
}
# 必填参数验证
if not all([from_vessel, separation_vessel, to_vessel]):
self._status = "Error: Missing required vessel parameters"
return {"success": False}
# 验证参数
if purpose not in ["wash", "extract"]:
self._status = "Error: Invalid purpose"
return {"success": False}
if product_phase not in ["top", "bottom"]:
self._status = "Error: Invalid product phase"
return {"success": False}
# 数值参数验证
try:
solvent_volume = float(solvent_volume)
repeats = int(repeats)
stir_time = float(stir_time)
stir_speed = float(stir_speed)
settling_time = float(settling_time)
except ValueError:
self._status = "Error: Invalid numeric parameters"
return {"success": False}
# 设置参数
self._purpose = purpose
self._product_phase = product_phase
self._from_vessel = from_vessel
self._separation_vessel = separation_vessel
self._to_vessel = to_vessel
self._waste_phase_to_vessel = waste_phase_to_vessel
self._solvent = solvent
self._solvent_volume = float(solvent_volume)
self._through = through
self._repeats = int(repeats)
self._stir_time = float(stir_time)
self._stir_speed = float(stir_speed)
self._settling_time = float(settling_time)
# 重置计时器
self._start_time = datetime.now()
self._time_spent = timedelta()
total_time = (self._stir_time + self._settling_time + 10) * self._repeats
self._time_remaining = timedelta(seconds=total_time)
# 启动分离操作
self._status = "Starting Separation"
self._running = True
# 在锁内创建和启动线程
self._operation_thread = threading.Thread(target=self._operation_loop)
self._operation_thread.daemon = True
self._operation_thread.start()
# 等待确认操作已经开始
time.sleep(0.1) # 短暂等待确保操作线程已启动
return {
"success": True,
"status": self._status,
"current_device": self._current_device,
"time_spent": self._time_spent.total_seconds(),
"time_remaining": self._time_remaining.total_seconds()
}
def shake(self, shake_time: float) -> str:
"""
模拟 shake搅拌操作
- 进入 "Shaking" 状态,倒计时 shake_time 秒
- shake 结束后,进入 "Settling" 状态,静置时间固定为 5 秒
- 最后恢复为 Idle
"""
try:
shake_time = float(shake_time)
except ValueError:
self._status = "Error: Invalid shake time"
return "Error"
with self._thread_lock:
self._status = "Shaking"
self._settling_time = 0.0
self._shake_time = shake_time
self._shake_status = "Shaking"
def _run_shake():
remaining = shake_time
while remaining > 0:
time.sleep(1)
remaining -= 1
with self._thread_lock:
self._shake_time = remaining
with self._thread_lock:
self._status = "Settling"
self._settling_time = 60.0 # 固定静置时间为60秒
self._shake_status = "Settling"
while True:
with self._thread_lock:
if self._settling_time <= 0:
self._status = "Idle"
self._shake_status = "Idle"
break
time.sleep(1)
with self._thread_lock:
self._settling_time = max(0.0, self._settling_time - 1)
self._operation_thread = threading.Thread(target=_run_shake)
self._operation_thread.daemon = True
self._operation_thread.start()
return "Success"
def set_valve(self, command: str) -> str:
"""
阀门控制命令:传入 "open""close"
"""
command = command.lower()
if command == "open":
self._valve_state = "Open"
self._status = "Valve Opened"
elif command == "close":
self._valve_state = "Closed"
self._status = "Valve Closed"
else:
self._status = "Error: Invalid valve command"
return "Error"
return "Success"
def _operation_loop(self):
"""分离操作主循环"""
try:
current_repeat = 1
# 立即更新状态确保不会停留在Starting Separation
with self._thread_lock:
self._status = f"Separation Cycle {current_repeat}/{self._repeats}"
while self._running and current_repeat <= self._repeats:
# 第一步:搅拌
if self._stir_time > 0:
with self._thread_lock:
self._status = f"Stirring (Repeat {current_repeat}/{self._repeats})"
remaining_stir = self._stir_time
while remaining_stir > 0 and self._running:
time.sleep(1)
remaining_stir -= 1
# 第二步:静置
if self._settling_time > 0:
with self._thread_lock:
self._status = f"Settling (Repeat {current_repeat}/{self._repeats})"
remaining_settle = self._settling_time
while remaining_settle > 0 and self._running:
time.sleep(1)
remaining_settle -= 1
# 第三步:打开阀门排出
with self._thread_lock:
self._valve_state = "Open"
self._status = f"Draining (Repeat {current_repeat}/{self._repeats})"
# 模拟排出时间5秒
time.sleep(10)
# 关闭阀门
with self._thread_lock:
self._valve_state = "Closed"
# 检查是否继续下一次重复
if current_repeat < self._repeats:
current_repeat += 1
else:
with self._thread_lock:
self._status = "Separation Complete"
break
except Exception as e:
with self._thread_lock:
self._status = f"Error in separation: {str(e)}"
finally:
with self._thread_lock:
self._running = False
self._valve_state = "Closed"
if self._status == "Starting Separation":
self._status = "Error: Operation failed to start"
elif self._status != "Separation Complete":
self._status = "Stopped"
def stop_operations(self) -> str:
"""停止任何正在执行的操作"""
with self._thread_lock:
self._running = False
if self._operation_thread and self._operation_thread.is_alive():
self._operation_thread.join(timeout=1.0)
self._operation_thread = None
self._settling_time = 0.0
self._status = "Idle"
self._shake_status = "Idle"
self._shake_time = 0.0
self._time_remaining = timedelta()
return "Success"
def get_status_info(self) -> dict:
"""获取当前设备状态信息"""
with self._thread_lock:
current_time = datetime.now()
if self._start_time:
self._time_spent = current_time - self._start_time
return {
"status": self._status,
"valve_state": self._valve_state,
"settling_time": self._settling_time,
"shake_time": self._shake_time,
"shake_status": self._shake_status,
"current_device": self._current_device,
"purpose": self._purpose,
"product_phase": self._product_phase,
"from_vessel": self._from_vessel,
"separation_vessel": self._separation_vessel,
"to_vessel": self._to_vessel,
"waste_phase_to_vessel": self._waste_phase_to_vessel,
"solvent": self._solvent,
"solvent_volume": self._solvent_volume,
"through": self._through,
"repeats": self._repeats,
"stir_time": self._stir_time,
"stir_speed": self._stir_speed,
"time_spent": self._time_spent.total_seconds(),
"time_remaining": self._time_remaining.total_seconds()
}
# 主函数用于测试
if __name__ == "__main__":
separator = MockSeparator()
print("启动简单版分离器测试...")
print("初始状态:", separator.get_status_info())
# 触发 shake 操作,模拟 10 秒的搅拌
print("执行 shake 操作...")
print(separator.shake(10.0))
# 循环显示状态变化
for i in range(20):
time.sleep(1)
info = separator.get_status_info()
print(
f"{i+1}秒: 状态={info['status']}, 静置时间={info['settling_time']:.1f}秒, "
f"阀门状态={info['valve_state']}, shake_time={info['shake_time']:.1f}, "
f"shake_status={info['shake_status']}"
)
# 模拟打开阀门
print("打开阀门...", separator.set_valve("open"))
print("最终状态:", separator.get_status_info())

View File

@@ -0,0 +1,89 @@
import time
class MockSolenoidValve:
"""
模拟电磁阀设备类 - 简化版本
这个类提供了电磁阀的基本功能:开启、关闭和状态查询
"""
def __init__(self, port: str = "MOCK"):
"""
初始化MockSolenoidValve实例
Args:
port (str): 设备端口,默认为"MOCK"表示模拟设备
"""
self.port = port
self._status: str = "Idle"
self._valve_status: str = "Closed" # 阀门位置Open, Closed
@property
def status(self) -> str:
"""设备状态 - 会被自动识别的设备属性"""
return self._status
@property
def valve_status(self) -> str:
"""阀门状态"""
return self._valve_status
def set_valve_status(self, status: str) -> str:
"""
设置阀门位置
Args:
position (str): 阀门位置,可选值:"Open", "Closed"
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if status not in ["Open", "Closed"]:
self._status = "Error: Invalid position"
return "Error"
self._status = "Moving"
time.sleep(1) # 模拟阀门动作时间
self._valve_status = status
self._status = "Idle"
return "Success"
def open_valve(self) -> str:
"""打开阀门"""
return self.set_valve_status("Open")
def close_valve(self) -> str:
"""关闭阀门"""
return self.set_valve_status("Closed")
def get_valve_status(self) -> str:
"""获取阀门位置"""
return self._valve_status
def is_open(self) -> bool:
"""检查阀门是否打开"""
return self._valve_status == "Open"
def is_closed(self) -> bool:
"""检查阀门是否关闭"""
return self._valve_status == "Closed"
# 用于测试的主函数
if __name__ == "__main__":
valve = MockSolenoidValve()
print("启动电磁阀测试...")
print(f"初始状态: 位置={valve.valve_status}, 状态={valve.status}")
# 测试开启阀门
valve.open_valve()
print(f"开启后: 位置={valve.valve_status}, 状态={valve.status}")
# 测试关闭阀门
valve.close_valve()
print(f"关闭后: 位置={valve.valve_status}, 状态={valve.status}")
print("测试完成")

View File

@@ -0,0 +1,307 @@
import time
import threading
class MockStirrer:
def __init__(self, port: str = "MOCK"):
self.port = port
# 设备基本状态属性
self._status: str = "Idle" # 设备状态Idle, Running, Error, Stopped
# 搅拌相关属性
self._stir_speed: float = 0.0 # 当前搅拌速度 (rpm)
self._target_stir_speed: float = 0.0 # 目标搅拌速度 (rpm)
self._max_stir_speed: float = 2000.0 # 最大搅拌速度 (rpm)
self._stir_state: str = "Stopped" # 搅拌状态Running, Stopped
# 温度相关属性
self._temperature: float = 25.0 # 当前温度 (°C)
self._target_temperature: float = 25.0 # 目标温度 (°C)
self._max_temperature: float = 300.0 # 最大温度 (°C)
self._heating_state: str = "Off" # 加热状态On, Off
self._heating_power: float = 0.0 # 加热功率百分比 0-100
# 运行控制线程
self._operation_thread = None
self._running = False
self._thread_lock = threading.Lock()
# ==================== 状态属性 ====================
# 这些属性会被Uni-Lab系统自动识别并定时对外广播
@property
def status(self) -> str:
return self._status
@property
def stir_speed(self) -> float:
return self._stir_speed
@property
def target_stir_speed(self) -> float:
return self._target_stir_speed
@property
def stir_state(self) -> str:
return self._stir_state
@property
def temperature(self) -> float:
"""
当前温度
Returns:
float: 当前温度 (°C)
"""
return self._temperature
@property
def target_temperature(self) -> float:
"""
目标温度
Returns:
float: 目标温度 (°C)
"""
return self._target_temperature
@property
def heating_state(self) -> str:
return self._heating_state
@property
def heating_power(self) -> float:
return self._heating_power
@property
def max_stir_speed(self) -> float:
return self._max_stir_speed
@property
def max_temperature(self) -> float:
return self._max_temperature
# ==================== 设备控制方法 ====================
# 这些方法需要在注册表中添加会作为ActionServer接受控制指令
def set_stir_speed(self, speed: float) -> str:
speed = float(speed) # 确保传入的速度是浮点数
if speed < 0 or speed > self._max_stir_speed:
self._status = f"Error: Speed out of range (0-{self._max_stir_speed})"
return "Error"
self._target_stir_speed = speed
self._status = "Setting Stir Speed"
# 如果设置了非零速度,启动搅拌
if speed > 0:
self._stir_state = "Running"
else:
self._stir_state = "Stopped"
return "Success"
def set_temperature(self, temperature: float) -> str:
temperature = float(temperature) # 确保传入的温度是浮点数
if temperature < 0 or temperature > self._max_temperature:
self._status = f"Error: Temperature out of range (0-{self._max_temperature})"
return "Error"
self._target_temperature = temperature
self._status = "Setting Temperature"
return "Success"
def start_stirring(self) -> str:
if self._target_stir_speed <= 0:
self._status = "Error: No target speed set"
return "Error"
self._stir_state = "Running"
self._status = "Stirring Started"
return "Success"
def stop_stirring(self) -> str:
self._stir_state = "Stopped"
self._target_stir_speed = 0.0
self._status = "Stirring Stopped"
return "Success"
def heating_control(self, heating_state: str = "On") -> str:
if heating_state not in ["On", "Off"]:
self._status = "Error: Invalid heating state"
return "Error"
self._heating_state = heating_state
if heating_state == "On":
self._status = "Heating On"
else:
self._status = "Heating Off"
self._heating_power = 0.0
return "Success"
def stop_all_operations(self) -> str:
self._stir_state = "Stopped"
self._heating_state = "Off"
self._stop_operation()
self._stir_speed = 0.0
self._target_stir_speed = 0.0
self._heating_power = 0.0
self._status = "All operations stopped"
return "Success"
def emergency_stop(self) -> str:
"""
紧急停止
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._status = "Emergency Stop"
self.stop_all_operations()
return "Success"
# ==================== 内部控制方法 ====================
def _start_operation(self):
with self._thread_lock:
if not self._running:
self._running = True
self._operation_thread = threading.Thread(target=self._operation_loop)
self._operation_thread.daemon = True
self._operation_thread.start()
def _stop_operation(self):
"""
停止操作线程
安全地停止后台运行线程并等待其完成。
"""
with self._thread_lock:
self._running = False
if self._operation_thread and self._operation_thread.is_alive():
self._operation_thread.join(timeout=2.0)
def _operation_loop(self):
while self._running:
try:
# 处理搅拌速度控制
if self._stir_state == "Running":
speed_diff = self._target_stir_speed - self._stir_speed
if abs(speed_diff) < 1.0: # 速度接近目标值
self._stir_speed = self._target_stir_speed
if self._stir_speed > 0:
self._status = "Stirring at Target Speed"
else:
# 模拟速度调节每秒调整10%的差值
adjustment = speed_diff * 0.1
self._stir_speed += adjustment
self._status = "Adjusting Stir Speed"
# 确保速度在合理范围内
self._stir_speed = max(0.0, min(self._max_stir_speed, self._stir_speed))
else:
# 搅拌停止时速度逐渐降为0
if self._stir_speed > 0:
self._stir_speed = max(0.0, self._stir_speed - 50.0) # 每秒减少50rpm
# 处理温度控制
if self._heating_state == "On":
temp_diff = self._target_temperature - self._temperature
if abs(temp_diff) < 0.5: # 温度接近目标值
self._heating_power = 20.0 # 维持温度的最小功率
elif temp_diff > 0: # 需要加热
# 根据温差调整加热功率
if temp_diff > 50:
self._heating_power = 100.0
elif temp_diff > 20:
self._heating_power = 80.0
elif temp_diff > 10:
self._heating_power = 60.0
else:
self._heating_power = 40.0
# 模拟加热过程
heating_rate = self._heating_power / 100.0 * 1.5 # 最大每秒升温1.5度
self._temperature += heating_rate
else: # 目标温度低于当前温度
self._heating_power = 0.0
# 自然冷却
self._temperature -= 0.1
else:
self._heating_power = 0.0
# 自然冷却到室温
if self._temperature > 25.0:
self._temperature -= 0.2
# 限制温度范围
self._temperature = max(20.0, min(self._max_temperature, self._temperature))
# 更新整体状态
if self._stir_state == "Running" and self._heating_state == "On":
self._status = "Stirring and Heating"
elif self._stir_state == "Running":
self._status = "Stirring Only"
elif self._heating_state == "On":
self._status = "Heating Only"
else:
self._status = "Idle"
# 等待1秒后继续下一次循环
time.sleep(1.0)
except Exception as e:
self._status = f"Error in operation: {str(e)}"
break
# 循环结束时的清理工作
self._status = "Idle"
def get_status_info(self) -> dict:
return {
"status": self._status,
"stir_speed": self._stir_speed,
"target_stir_speed": self._target_stir_speed,
"stir_state": self._stir_state,
"temperature": self._temperature,
"target_temperature": self._target_temperature,
"heating_state": self._heating_state,
"heating_power": self._heating_power,
"max_stir_speed": self._max_stir_speed,
"max_temperature": self._max_temperature,
}
# 用于测试的主函数
if __name__ == "__main__":
stirrer = MockStirrer()
# 测试基本功能
print("启动搅拌器测试...")
print(f"初始状态: {stirrer.get_status_info()}")
# 设置搅拌速度和温度
stirrer.set_stir_speed(800.0)
stirrer.set_temperature(60.0)
stirrer.heating_control("On")
# 模拟运行15秒
for i in range(15):
time.sleep(1)
print(
f"{i+1}秒: 速度={stirrer.stir_speed:.0f}rpm, 温度={stirrer.temperature:.1f}°C, "
f"功率={stirrer.heating_power:.1f}%, 状态={stirrer.status}"
)
stirrer.emergency_stop()
print("测试完成")

View File

@@ -0,0 +1,229 @@
import time
import threading
from datetime import datetime, timedelta
class MockStirrer_new:
def __init__(self, port: str = "MOCK"):
self.port = port
# 基本状态属性
self._status: str = "Idle"
self._vessel: str = ""
self._purpose: str = ""
# 搅拌相关属性
self._stir_speed: float = 0.0
self._target_stir_speed: float = 0.0
self._max_stir_speed: float = 2000.0
self._stir_state: str = "Stopped"
# 计时相关
self._stir_time: float = 0.0
self._settling_time: float = 0.0
self._start_time = datetime.now()
self._time_remaining = timedelta()
# 运行控制
self._operation_thread = None
self._running = False
self._thread_lock = threading.Lock()
# 创建操作线程
self._operation_thread = threading.Thread(target=self._operation_loop)
self._operation_thread.daemon = True
self._operation_thread.start()
# ==================== 状态属性 ====================
@property
def status(self) -> str:
return self._status
@property
def stir_speed(self) -> float:
return self._stir_speed
@property
def target_stir_speed(self) -> float:
return self._target_stir_speed
@property
def stir_state(self) -> str:
return self._stir_state
@property
def vessel(self) -> str:
return self._vessel
@property
def purpose(self) -> str:
return self._purpose
@property
def stir_time(self) -> float:
return self._stir_time
@property
def settling_time(self) -> float:
return self._settling_time
@property
def max_stir_speed(self) -> float:
return self._max_stir_speed
@property
def progress(self) -> float:
"""返回当前操作的进度0-100"""
if not self._running:
return 0.0
elapsed = (datetime.now() - self._start_time).total_seconds()
total_time = self._stir_time + self._settling_time
if total_time <= 0:
return 100.0
return min(100.0, (elapsed / total_time) * 100)
# ==================== Action Server 方法 ====================
def start_stir(self, vessel: str, stir_speed: float = 0.0, purpose: str = "") -> dict:
"""
StartStir.action 对应的方法
"""
with self._thread_lock:
if self._running:
return {
"success": False,
"message": "Operation already in progress"
}
try:
# 重置所有参数
self._vessel = vessel
self._purpose = purpose
self._stir_time = 0.0 # 连续搅拌模式下不设置搅拌时间
self._settling_time = 0.0
self._start_time = datetime.now() # 重置开始时间
if stir_speed > 0:
self._target_stir_speed = min(stir_speed, self._max_stir_speed)
self._stir_state = "Running"
self._status = "Stirring Started"
self._running = True
return {
"success": True,
"message": "Stirring started successfully"
}
except Exception as e:
return {
"success": False,
"message": f"Error: {str(e)}"
}
def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> dict:
"""
Stir.action 对应的方法
"""
with self._thread_lock:
try:
# 如果已经在运行,先停止当前操作
if self._running:
self._running = False
self._stir_state = "Stopped"
self._target_stir_speed = 0.0
time.sleep(0.1) # 给一个短暂的停止时间
# 重置所有参数
self._stir_time = float(stir_time)
self._settling_time = float(settling_time)
self._target_stir_speed = min(float(stir_speed), self._max_stir_speed)
self._start_time = datetime.now() # 重置开始时间
self._stir_state = "Running"
self._status = "Stirring"
self._running = True
return {"success": True}
except ValueError:
self._status = "Error: Invalid parameters"
return {"success": False}
def stop_stir(self, vessel: str) -> dict:
"""
StopStir.action 对应的方法
"""
with self._thread_lock:
if vessel != self._vessel:
return {
"success": False,
"message": "Vessel mismatch"
}
self._running = False
self._stir_state = "Stopped"
self._target_stir_speed = 0.0
self._status = "Stirring Stopped"
return {
"success": True,
"message": "Stirring stopped successfully"
}
# ==================== 内部控制方法 ====================
def _operation_loop(self):
"""操作主循环"""
while True:
try:
current_time = datetime.now()
with self._thread_lock: # 添加锁保护
if self._stir_state == "Running":
# 实际搅拌逻辑
speed_diff = self._target_stir_speed - self._stir_speed
if abs(speed_diff) > 0.1:
adjustment = speed_diff * 0.1
self._stir_speed += adjustment
else:
self._stir_speed = self._target_stir_speed
# 更新进度
if self._running:
if self._stir_time > 0: # 定时搅拌模式
elapsed = (current_time - self._start_time).total_seconds()
if elapsed >= self._stir_time + self._settling_time:
self._running = False
self._stir_state = "Stopped"
self._target_stir_speed = 0.0
self._stir_speed = 0.0
self._status = "Stirring Complete"
elif elapsed >= self._stir_time:
self._status = "Settling"
else: # 连续搅拌模式
self._status = "Stirring"
else:
# 停止状态下慢慢降低速度
if self._stir_speed > 0:
self._stir_speed = max(0, self._stir_speed - 20.0)
time.sleep(0.1)
except Exception as e:
print(f"Error in operation loop: {str(e)}") # 添加错误输出
self._status = f"Error: {str(e)}"
time.sleep(1.0) # 错误发生时等待较长时间
def get_status_info(self) -> dict:
"""获取设备状态信息"""
return {
"status": self._status,
"vessel": self._vessel,
"purpose": self._purpose,
"stir_speed": self._stir_speed,
"target_stir_speed": self._target_stir_speed,
"stir_state": self._stir_state,
"stir_time": self._stir_time, # 添加
"settling_time": self._settling_time, # 添加
"progress": self.progress,
"max_stir_speed": self._max_stir_speed
}

View File

@@ -0,0 +1,410 @@
import time
import threading
class MockVacuum:
"""
模拟真空泵设备类
这个类模拟了一个实验室真空泵的行为,包括真空度控制、
压力监测、运行状态管理等功能。参考了现有的 VacuumPumpMock 实现。
"""
def __init__(self, port: str = "MOCK"):
"""
初始化MockVacuum实例
Args:
port (str): 设备端口,默认为"MOCK"表示模拟设备
"""
self.port = port
# 设备基本状态属性
self._status: str = "Idle" # 设备状态Idle, Running, Error, Stopped
self._power_state: str = "Off" # 电源状态On, Off
self._pump_state: str = "Stopped" # 泵运行状态Running, Stopped, Paused
# 真空相关属性
self._vacuum_level: float = 1013.25 # 当前真空度 (mbar) - 大气压开始
self._target_vacuum: float = 50.0 # 目标真空度 (mbar)
self._min_vacuum: float = 1.0 # 最小真空度 (mbar)
self._max_vacuum: float = 1013.25 # 最大真空度 (mbar) - 大气压
# 泵性能相关属性
self._pump_speed: float = 0.0 # 泵速 (L/s)
self._max_pump_speed: float = 100.0 # 最大泵速 (L/s)
self._pump_efficiency: float = 95.0 # 泵效率百分比
# 运行控制线程
self._vacuum_thread = None
self._running = False
self._thread_lock = threading.Lock()
# ==================== 状态属性 ====================
# 这些属性会被Uni-Lab系统自动识别并定时对外广播
@property
def status(self) -> str:
"""
设备状态 - 会被自动识别的设备属性
Returns:
str: 当前设备状态 (Idle, Running, Error, Stopped)
"""
return self._status
@property
def power_state(self) -> str:
"""
电源状态
Returns:
str: 电源状态 (On, Off)
"""
return self._power_state
@property
def pump_state(self) -> str:
"""
泵运行状态
Returns:
str: 泵状态 (Running, Stopped, Paused)
"""
return self._pump_state
@property
def vacuum_level(self) -> float:
"""
当前真空度
Returns:
float: 当前真空度 (mbar)
"""
return self._vacuum_level
@property
def target_vacuum(self) -> float:
"""
目标真空度
Returns:
float: 目标真空度 (mbar)
"""
return self._target_vacuum
@property
def pump_speed(self) -> float:
"""
泵速
Returns:
float: 泵速 (L/s)
"""
return self._pump_speed
@property
def pump_efficiency(self) -> float:
"""
泵效率
Returns:
float: 泵效率百分比
"""
return self._pump_efficiency
@property
def max_pump_speed(self) -> float:
"""
最大泵速
Returns:
float: 最大泵速 (L/s)
"""
return self._max_pump_speed
# ==================== 设备控制方法 ====================
# 这些方法需要在注册表中添加会作为ActionServer接受控制指令
def power_control(self, power_state: str = "On") -> str:
"""
电源控制方法
Args:
power_state (str): 电源状态,可选值:"On", "Off"
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if power_state not in ["On", "Off"]:
self._status = "Error: Invalid power state"
return "Error"
self._power_state = power_state
if power_state == "On":
self._status = "Power On"
self._start_vacuum_operation()
else:
self._status = "Power Off"
self.stop_vacuum()
return "Success"
def set_vacuum_level(self, vacuum_level: float) -> str:
"""
设置目标真空度
Args:
vacuum_level (float): 目标真空度 (mbar)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
try:
vacuum_level = float(vacuum_level)
except ValueError:
self._status = "Error: Invalid vacuum level"
return "Error"
if self._power_state != "On":
self._status = "Error: Power Off"
return "Error"
if vacuum_level < self._min_vacuum or vacuum_level > self._max_vacuum:
self._status = f"Error: Vacuum level out of range ({self._min_vacuum}-{self._max_vacuum})"
return "Error"
self._target_vacuum = vacuum_level
self._status = "Setting Vacuum Level"
return "Success"
def start_vacuum(self) -> str:
"""
启动真空泵
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._power_state != "On":
self._status = "Error: Power Off"
return "Error"
self._pump_state = "Running"
self._status = "Starting Vacuum Pump"
self._start_vacuum_operation()
return "Success"
def stop_vacuum(self) -> str:
"""
停止真空泵
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._pump_state = "Stopped"
self._status = "Stopping Vacuum Pump"
self._stop_vacuum_operation()
self._pump_speed = 0.0
return "Success"
def pause_vacuum(self) -> str:
"""
暂停真空泵
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._pump_state != "Running":
self._status = "Error: Pump not running"
return "Error"
self._pump_state = "Paused"
self._status = "Vacuum Pump Paused"
self._stop_vacuum_operation()
return "Success"
def resume_vacuum(self) -> str:
"""
恢复真空泵运行
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._pump_state != "Paused":
self._status = "Error: Pump not paused"
return "Error"
if self._power_state != "On":
self._status = "Error: Power Off"
return "Error"
self._pump_state = "Running"
self._status = "Resuming Vacuum Pump"
self._start_vacuum_operation()
return "Success"
def vent_to_atmosphere(self) -> str:
"""
通大气 - 将真空度恢复到大气压
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._target_vacuum = self._max_vacuum # 设置为大气压
self._status = "Venting to Atmosphere"
return "Success"
def emergency_stop(self) -> str:
"""
紧急停止
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._status = "Emergency Stop"
self._pump_state = "Stopped"
self._stop_vacuum_operation()
self._pump_speed = 0.0
return "Success"
# ==================== 内部控制方法 ====================
def _start_vacuum_operation(self):
"""
启动真空操作线程
这个方法启动一个后台线程来模拟真空泵的实际运行过程。
"""
with self._thread_lock:
if not self._running and self._power_state == "On":
self._running = True
self._vacuum_thread = threading.Thread(target=self._vacuum_operation_loop)
self._vacuum_thread.daemon = True
self._vacuum_thread.start()
def _stop_vacuum_operation(self):
"""
停止真空操作线程
安全地停止后台运行线程并等待其完成。
"""
with self._thread_lock:
self._running = False
if self._vacuum_thread and self._vacuum_thread.is_alive():
self._vacuum_thread.join(timeout=2.0)
def _vacuum_operation_loop(self):
"""
真空操作主循环
这个方法在后台线程中运行,模拟真空泵的工作过程:
1. 检查电源状态和运行状态
2. 如果泵状态为 "Running",根据目标真空调整泵速和真空度
3. 否则等待
"""
while self._running and self._power_state == "On":
try:
with self._thread_lock:
# 只有泵状态为 Running 时才进行更新
if self._pump_state == "Running":
vacuum_diff = self._vacuum_level - self._target_vacuum
if abs(vacuum_diff) < 1.0: # 真空度接近目标值
self._status = "At Target Vacuum"
self._pump_speed = self._max_pump_speed * 0.2 # 维持真空的最小泵速
elif vacuum_diff > 0: # 需要抽真空(降低压力)
self._status = "Pumping Down"
if vacuum_diff > 500:
self._pump_speed = self._max_pump_speed
elif vacuum_diff > 100:
self._pump_speed = self._max_pump_speed * 0.8
elif vacuum_diff > 50:
self._pump_speed = self._max_pump_speed * 0.6
else:
self._pump_speed = self._max_pump_speed * 0.4
# 根据泵速和效率计算真空降幅
pump_rate = (self._pump_speed / self._max_pump_speed) * self._pump_efficiency / 100.0
vacuum_reduction = pump_rate * 10.0 # 每秒最大降低10 mbar
self._vacuum_level = max(self._target_vacuum, self._vacuum_level - vacuum_reduction)
else: # 目标真空度高于当前值,需要通气
self._status = "Venting"
self._pump_speed = 0.0
self._vacuum_level = min(self._target_vacuum, self._vacuum_level + 5.0)
# 限制真空度范围
self._vacuum_level = max(self._min_vacuum, min(self._max_vacuum, self._vacuum_level))
else:
# 当泵状态不是 Running 时,可保持原状态
self._status = "Vacuum Pump Not Running"
# 释放锁后等待1秒钟
time.sleep(1.0)
except Exception as e:
with self._thread_lock:
self._status = f"Error in vacuum operation: {str(e)}"
break
# 循环结束后的清理工作
if self._pump_state == "Running":
self._status = "Idle"
# 停止泵后,真空度逐渐回升到大气压
while self._vacuum_level < self._max_vacuum * 0.9:
with self._thread_lock:
self._vacuum_level += 2.0
time.sleep(0.1)
def get_status_info(self) -> dict:
"""
获取完整的设备状态信息
Returns:
dict: 包含所有设备状态的字典
"""
return {
"status": self._status,
"power_state": self._power_state,
"pump_state": self._pump_state,
"vacuum_level": self._vacuum_level,
"target_vacuum": self._target_vacuum,
"pump_speed": self._pump_speed,
"pump_efficiency": self._pump_efficiency,
"max_pump_speed": self._max_pump_speed,
}
# 用于测试的主函数
if __name__ == "__main__":
vacuum = MockVacuum()
# 测试基本功能
print("启动真空泵测试...")
vacuum.power_control("On")
print(f"初始状态: {vacuum.get_status_info()}")
# 设置目标真空度并启动
vacuum.set_vacuum_level(10.0) # 设置为10mbar
vacuum.start_vacuum()
# 模拟运行15秒
for i in range(15):
time.sleep(1)
print(
f"{i+1}秒: 真空度={vacuum.vacuum_level:.1f}mbar, 泵速={vacuum.pump_speed:.1f}L/s, 状态={vacuum.status}"
)
# 测试通大气
print("测试通大气...")
vacuum.vent_to_atmosphere()
# 继续运行5秒观察通大气过程
for i in range(5):
time.sleep(1)
print(f"通大气第{i+1}秒: 真空度={vacuum.vacuum_level:.1f}mbar, 状态={vacuum.status}")
vacuum.emergency_stop()
print("测试完成")

View File

@@ -12,10 +12,8 @@ class VacuumPumpMock:
def get_status(self) -> str:
return self._status
def set_status(self, position):
time.sleep(5)
self._status = position
def set_status(self, string):
self._status = string
time.sleep(5)
def open(self):

View File

@@ -292,9 +292,17 @@ class MoveitInterface:
)
end_pose = deep_pose
retval_ik = self.moveit2[cmd_dict["move_group"]].compute_ik(
position=end_pose, quat_xyzw=quaternion, constraints=Constraints(joint_constraints=constraints)
)
retval_ik = None
retry = config.get("retry", 10)
while retval_ik is None and retry > 0:
retval_ik = self.moveit2[cmd_dict["move_group"]].compute_ik(
position=end_pose, quat_xyzw=quaternion, constraints=Constraints(joint_constraints=constraints)
)
time.sleep(0.1)
retry -= 1
if retval_ik is None:
result.success = False
return result
position_ = [
retval_ik.position[retval_ik.name.index(i)]
for i in self.moveit2[cmd_dict["move_group"]].joint_names

View File

View File

@@ -0,0 +1,158 @@
import asyncio
import logging
from typing import Dict, Any
class VirtualCentrifuge:
"""Virtual centrifuge device for CentrifugeProtocol testing"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_centrifuge"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualCentrifuge {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 15000.0)
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 40.0)
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', 4.0)
# 处理其他kwargs参数但跳过已知的配置参数
skip_keys = {'port', 'max_speed', 'max_temp', 'min_temp'}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual centrifuge"""
print(f"=== VirtualCentrifuge {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual centrifuge {self.device_id}")
self.data.update({
"status": "Idle",
"current_speed": 0.0,
"target_speed": 0.0,
"current_temp": 25.0,
"target_temp": 25.0,
"max_speed": self._max_speed,
"max_temp": self._max_temp,
"min_temp": self._min_temp,
"centrifuge_state": "Stopped",
"time_remaining": 0.0,
"progress": 0.0,
"message": ""
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual centrifuge"""
self.logger.info(f"Cleaning up virtual centrifuge {self.device_id}")
return True
async def centrifuge(self, vessel: str, speed: float, time: float, temp: float = 25.0) -> bool:
"""Execute centrifuge action - matches Centrifuge action"""
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} RPM, time={time}s, temp={temp}°C")
# 验证参数
if speed > self._max_speed:
self.logger.error(f"Speed {speed} exceeds maximum {self._max_speed}")
self.data["message"] = f"速度 {speed} 超过最大值 {self._max_speed}"
return False
if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
self.data["message"] = f"温度 {temp} 超出范围 {self._min_temp}-{self._max_temp}"
return False
# 开始离心
self.data.update({
"status": "Running",
"centrifuge_state": "Centrifuging",
"target_speed": speed,
"current_speed": speed,
"target_temp": temp,
"current_temp": temp,
"time_remaining": time,
"vessel": vessel,
"progress": 0.0,
"message": f"离心中: {vessel} at {speed} RPM"
})
# 模拟离心过程
simulation_time = min(time, 5.0) # 最多等待5秒用于测试
await asyncio.sleep(simulation_time)
# 离心完成
self.data.update({
"status": "Idle",
"centrifuge_state": "Stopped",
"current_speed": 0.0,
"target_speed": 0.0,
"time_remaining": 0.0,
"progress": 100.0,
"message": f"离心完成: {vessel}"
})
self.logger.info(f"Centrifuge completed for vessel {vessel}")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def current_speed(self) -> float:
return self.data.get("current_speed", 0.0)
@property
def target_speed(self) -> float:
return self.data.get("target_speed", 0.0)
@property
def current_temp(self) -> float:
return self.data.get("current_temp", 25.0)
@property
def target_temp(self) -> float:
return self.data.get("target_temp", 25.0)
@property
def max_speed(self) -> float:
return self.data.get("max_speed", self._max_speed)
@property
def max_temp(self) -> float:
return self.data.get("max_temp", self._max_temp)
@property
def min_temp(self) -> float:
return self.data.get("min_temp", self._min_temp)
@property
def centrifuge_state(self) -> str:
return self.data.get("centrifuge_state", "Unknown")
@property
def time_remaining(self) -> float:
return self.data.get("time_remaining", 0.0)
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def message(self) -> str:
return self.data.get("message", "")

View File

@@ -0,0 +1,132 @@
import asyncio
import logging
from typing import Dict, Any, Optional
class VirtualColumn:
"""Virtual column device for RunColumn protocol"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_column"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualColumn.{self.device_id}")
self.data = {}
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_flow_rate = self.config.get('max_flow_rate') or kwargs.get('max_flow_rate', 10.0)
self._column_length = self.config.get('column_length') or kwargs.get('column_length', 25.0)
self._column_diameter = self.config.get('column_diameter') or kwargs.get('column_diameter', 2.0)
print(f"=== VirtualColumn {self.device_id} created with max_flow_rate={self._max_flow_rate}, length={self._column_length}cm ===")
async def initialize(self) -> bool:
"""Initialize virtual column"""
self.logger.info(f"Initializing virtual column {self.device_id}")
self.data.update({
"status": "Idle",
"column_state": "Ready",
"current_flow_rate": 0.0,
"max_flow_rate": self._max_flow_rate,
"column_length": self._column_length,
"column_diameter": self._column_diameter,
"processed_volume": 0.0,
"progress": 0.0,
"current_status": "Ready"
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual column"""
self.logger.info(f"Cleaning up virtual column {self.device_id}")
return True
async def run_column(self, from_vessel: str, to_vessel: str, column: str) -> bool:
"""Execute column chromatography run - matches RunColumn action"""
self.logger.info(f"Running column separation: {from_vessel} -> {to_vessel} using {column}")
# 更新设备状态
self.data.update({
"status": "Running",
"column_state": "Separating",
"current_status": "Column separation in progress",
"progress": 0.0,
"processed_volume": 0.0
})
# 模拟柱层析分离过程
# 假设处理时间基于流速和柱子长度
separation_time = (self._column_length * 2) / self._max_flow_rate # 简化计算
steps = 20 # 分20个步骤模拟分离过程
step_time = separation_time / steps
for i in range(steps):
await asyncio.sleep(step_time)
progress = (i + 1) / steps * 100
volume_processed = (i + 1) * 5.0 # 假设每步处理5mL
# 更新状态
self.data.update({
"progress": progress,
"processed_volume": volume_processed,
"current_status": f"Column separation: {progress:.1f}% - Processing {volume_processed:.1f}mL"
})
self.logger.info(f"Column separation progress: {progress:.1f}%")
# 分离完成
self.data.update({
"status": "Idle",
"column_state": "Ready",
"current_status": "Column separation completed",
"progress": 100.0
})
self.logger.info(f"Column separation completed: {from_vessel} -> {to_vessel}")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def column_state(self) -> str:
return self.data.get("column_state", "Unknown")
@property
def current_flow_rate(self) -> float:
return self.data.get("current_flow_rate", 0.0)
@property
def max_flow_rate(self) -> float:
return self.data.get("max_flow_rate", 0.0)
@property
def column_length(self) -> float:
return self.data.get("column_length", 0.0)
@property
def column_diameter(self) -> float:
return self.data.get("column_diameter", 0.0)
@property
def processed_volume(self) -> float:
return self.data.get("processed_volume", 0.0)
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def current_status(self) -> str:
return self.data.get("current_status", "Ready")

View File

@@ -0,0 +1,151 @@
import asyncio
import logging
from typing import Dict, Any
class VirtualFilter:
"""Virtual filter device for FilterProtocol testing"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_filter"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualFilter {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
# 处理其他kwargs参数但跳过已知的配置参数
skip_keys = {'port', 'max_temp', 'max_stir_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 filter"""
print(f"=== VirtualFilter {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual filter {self.device_id}")
self.data.update({
"status": "Idle",
"filter_state": "Ready",
"current_temp": 25.0,
"target_temp": 25.0,
"max_temp": self._max_temp,
"stir_speed": 0.0,
"max_stir_speed": self._max_stir_speed,
"filtered_volume": 0.0,
"progress": 0.0,
"message": ""
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual filter"""
self.logger.info(f"Cleaning up virtual filter {self.device_id}")
return True
async def filter_sample(self, vessel: str, filtrate_vessel: str = "", stir: bool = False,
stir_speed: float = 300.0, temp: float = 25.0,
continue_heatchill: bool = False, volume: float = 0.0) -> bool:
"""Execute filter action - matches Filter action"""
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}, stir={stir}, volume={volume}")
# 验证参数
if temp > self._max_temp:
self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}")
self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}"
return False
if stir and stir_speed > self._max_stir_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
self.data["message"] = f"搅拌速度 {stir_speed} 超过最大值 {self._max_stir_speed}"
return False
# 开始过滤
self.data.update({
"status": "Running",
"filter_state": "Filtering",
"target_temp": temp,
"current_temp": temp,
"stir_speed": stir_speed if stir else 0.0,
"vessel": vessel,
"filtrate_vessel": filtrate_vessel,
"target_volume": volume,
"progress": 0.0,
"message": f"过滤中: {vessel}"
})
# 模拟过滤过程
simulation_time = min(volume / 10.0 if volume > 0 else 5.0, 10.0)
await asyncio.sleep(simulation_time)
# 过滤完成
filtered_vol = volume if volume > 0 else 50.0 # 默认过滤量
self.data.update({
"status": "Idle",
"filter_state": "Ready",
"current_temp": 25.0 if not continue_heatchill else temp,
"target_temp": 25.0 if not continue_heatchill else temp,
"stir_speed": 0.0 if not stir else stir_speed,
"filtered_volume": filtered_vol,
"progress": 100.0,
"message": f"过滤完成: {filtered_vol}mL"
})
self.logger.info(f"Filter completed: {filtered_vol}mL from {vessel}")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def filter_state(self) -> str:
return self.data.get("filter_state", "Unknown")
@property
def current_temp(self) -> float:
return self.data.get("current_temp", 25.0)
@property
def target_temp(self) -> float:
return self.data.get("target_temp", 25.0)
@property
def max_temp(self) -> float:
return self.data.get("max_temp", self._max_temp)
@property
def stir_speed(self) -> float:
return self.data.get("stir_speed", 0.0)
@property
def max_stir_speed(self) -> float:
return self.data.get("max_stir_speed", self._max_stir_speed)
@property
def filtered_volume(self) -> float:
return self.data.get("filtered_volume", 0.0)
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def message(self) -> str:
return self.data.get("message", "")

View File

@@ -0,0 +1,107 @@
import asyncio
import logging
from typing import Dict, Any
class VirtualHeatChill:
"""Virtual heat chill device for HeatChillProtocol testing"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_heatchill"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualHeatChill {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 200.0)
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', -80.0)
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
# 处理其他kwargs参数但跳过已知的配置参数
skip_keys = {'port', 'max_temp', 'min_temp', 'max_stir_speed'}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual heat chill"""
print(f"=== VirtualHeatChill {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual heat chill {self.device_id}")
self.data.update({
"status": "Idle"
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual heat chill"""
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
return True
async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
stir_speed: float, purpose: str) -> bool:
"""Execute heat chill action - matches HeatChill action exactly"""
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}, purpose={purpose}")
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
self.data["status"] = f"温度 {temp} 超出范围"
return False
if stir and stir_speed > self._max_stir_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
return False
# 开始加热/冷却
self.data.update({
"status": f"加热/冷却中: {vessel}{temp}°C"
})
# 模拟加热/冷却时间
simulation_time = min(time, 10.0) # 最多等待10秒用于测试
await asyncio.sleep(simulation_time)
# 加热/冷却完成
self.data["status"] = f"完成: {vessel} 已达到 {temp}°C"
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C")
return True
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
"""Start heat chill - matches HeatChillStart action exactly"""
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C, purpose={purpose}")
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
self.data["status"] = f"温度 {temp} 超出范围"
return False
self.data["status"] = f"开始加热/冷却: {vessel}{temp}°C"
return True
async def heat_chill_stop(self, vessel: str) -> bool:
"""Stop heat chill - matches HeatChillStop action exactly"""
self.logger.info(f"HeatChillStop: vessel={vessel}")
self.data["status"] = f"停止加热/冷却: {vessel}"
return True
# 状态属性 - 只保留 action 中定义的 feedback
@property
def status(self) -> str:
return self.data.get("status", "Idle")

View File

@@ -0,0 +1,197 @@
import asyncio
import logging
from typing import Dict, Any, Optional
class VirtualPump:
"""Virtual pump device for transfer and cleaning operations"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_pump"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualPump.{self.device_id}")
self.data = {}
# 从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', 10.0)
print(f"=== VirtualPump {self.device_id} created with max_volume={self._max_volume}, transfer_rate={self._transfer_rate} ===")
async def initialize(self) -> bool:
"""Initialize virtual pump"""
self.logger.info(f"Initializing virtual pump {self.device_id}")
self.data.update({
"status": "Idle",
"valve_position": 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
async def cleanup(self) -> bool:
"""Cleanup virtual pump"""
self.logger.info(f"Cleaning up virtual pump {self.device_id}")
return True
async def transfer(self, from_vessel: str, to_vessel: str, volume: float,
amount: str = "", time: float = 0.0, viscous: bool = False,
rinsing_solvent: str = "", rinsing_volume: float = 0.0,
rinsing_repeats: int = 0, solid: bool = False) -> bool:
"""Execute transfer operation"""
self.logger.info(f"Transferring {volume}mL from {from_vessel} to {to_vessel}")
# 计算转移时间
transfer_time = volume / self._transfer_rate if time == 0 else time
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
current_volume = step_volume * (i + 1)
self.data.update({
"progress": progress,
"transferred_volume": current_volume,
"current_status": f"Transferring: {progress:.1f}%"
})
self.logger.info(f"Transfer progress: {progress:.1f}%")
self.data.update({
"status": "Idle",
"current_status": "Transfer completed",
"progress": 100.0,
"transferred_volume": volume
})
return True
async def clean_vessel(self, vessel: str, solvent: str, volume: float,
temp: float, repeats: int = 1) -> bool:
"""Execute vessel cleaning operation - matches CleanVessel action"""
self.logger.info(f"Starting vessel cleaning: {vessel} with {solvent} ({volume}mL at {temp}°C, {repeats} repeats)")
# 更新设备状态
self.data.update({
"status": "Running",
"from_vessel": f"flask_{solvent}",
"to_vessel": vessel,
"current_status": "Cleaning in progress",
"progress": 0.0,
"transferred_volume": 0.0
})
# 计算清洗时间(基于体积和重复次数)
# 假设清洗速度为 transfer_rate 的一半(因为需要加载和排放)
cleaning_rate = self._transfer_rate / 2
cleaning_time_per_cycle = volume / cleaning_rate
total_cleaning_time = cleaning_time_per_cycle * repeats
# 模拟清洗过程
steps_per_repeat = 10 # 每次重复清洗分10个步骤
total_steps = steps_per_repeat * repeats
step_time = total_cleaning_time / total_steps
for repeat in range(repeats):
self.logger.info(f"Starting cleaning cycle {repeat + 1}/{repeats}")
for step in range(steps_per_repeat):
await asyncio.sleep(step_time)
# 计算当前进度
current_step = repeat * steps_per_repeat + step + 1
progress = (current_step / total_steps) * 100
# 计算已处理的体积
volume_processed = (current_step / total_steps) * volume * repeats
# 更新状态
self.data.update({
"progress": progress,
"transferred_volume": volume_processed,
"current_status": f"Cleaning cycle {repeat + 1}/{repeats} - Step {step + 1}/{steps_per_repeat} ({progress:.1f}%)"
})
self.logger.info(f"Cleaning progress: {progress:.1f}% (Cycle {repeat + 1}/{repeats})")
# 清洗完成
self.data.update({
"status": "Idle",
"current_status": "Cleaning completed successfully",
"progress": 100.0,
"transferred_volume": volume * repeats,
"from_vessel": "",
"to_vessel": ""
})
self.logger.info(f"Vessel cleaning completed: {vessel}")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def valve_position(self) -> int:
return self.data.get("valve_position", 0)
@property
def current_volume(self) -> float:
return self.data.get("current_volume", 0.0)
@property
def max_volume(self) -> float:
return self.data.get("max_volume", 0.0)
@property
def transfer_rate(self) -> float:
return self.data.get("transfer_rate", 0.0)
@property
def from_vessel(self) -> str:
return self.data.get("from_vessel", "")
@property
def to_vessel(self) -> str:
return self.data.get("to_vessel", "")
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def transferred_volume(self) -> float:
return self.data.get("transferred_volume", 0.0)
@property
def current_status(self) -> str:
return self.data.get("current_status", "Ready")

View File

@@ -0,0 +1,104 @@
import asyncio
import logging
from typing import Dict, Any
class VirtualStirrer:
"""Virtual stirrer device for StirProtocol testing"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_stirrer"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualStirrer.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualStirrer {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1000.0)
# 处理其他kwargs参数但跳过已知的配置参数
skip_keys = {'port', 'max_temp', 'max_speed'}
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual stirrer"""
print(f"=== VirtualStirrer {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual stirrer {self.device_id}")
self.data.update({
"status": "Idle"
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual stirrer"""
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
return True
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
"""Execute stir action - matches Stir action exactly"""
self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
# 验证参数
if stir_speed > self._max_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
return False
# 开始搅拌
self.data["status"] = f"搅拌中: {stir_speed} RPM, {stir_time}s"
# 模拟搅拌时间
simulation_time = min(stir_time, 10.0) # 最多等待10秒用于测试
await asyncio.sleep(simulation_time)
# 搅拌完成,开始沉降
if settling_time > 0:
self.data["status"] = f"沉降中: {settling_time}s"
settling_simulation = min(settling_time, 5.0) # 最多等待5秒
await asyncio.sleep(settling_simulation)
# 操作完成
self.data["status"] = "搅拌完成"
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s")
return True
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
"""Start stir action - matches StartStir action exactly"""
self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
# 验证参数
if stir_speed > self._max_speed:
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
return False
self.data["status"] = f"开始搅拌: {vessel} at {stir_speed} RPM"
return True
async def stop_stir(self, vessel: str) -> bool:
"""Stop stir action - matches StopStir action exactly"""
self.logger.info(f"StopStir: vessel={vessel}")
self.data["status"] = f"停止搅拌: {vessel}"
return True
# 状态属性 - 只保留 action 中定义的 feedback
@property
def status(self) -> str:
return self.data.get("status", "Idle")

View File

@@ -0,0 +1,149 @@
import asyncio
import logging
from typing import Dict, Any, Optional
class VirtualTransferPump:
"""Virtual pump device specifically for Transfer protocol"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_transfer_pump"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
self.data = {}
# 添加调试信息
print(f"=== VirtualTransferPump {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 50.0)
self._transfer_rate = self.config.get('transfer_rate') or kwargs.get('transfer_rate', 5.0)
self._current_volume = 0.0
self.is_running = False
async def initialize(self) -> bool:
"""Initialize virtual transfer pump"""
print(f"=== VirtualTransferPump {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual transfer pump {self.device_id}")
self.data.update({
"status": "Idle",
"current_volume": 0.0,
"max_volume": self._max_volume,
"transfer_rate": self._transfer_rate,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual transfer pump"""
self.logger.info(f"Cleaning up virtual transfer pump {self.device_id}")
return True
async def transfer(self, from_vessel: str, to_vessel: str, volume: float,
amount: str = "", time: float = 0, viscous: bool = False,
rinsing_solvent: str = "", rinsing_volume: float = 0.0,
rinsing_repeats: int = 0, solid: bool = False) -> bool:
"""Execute liquid transfer - matches Transfer action"""
self.logger.info(f"Transfer: {volume}mL from {from_vessel} to {to_vessel}")
# 计算转移时间
if time > 0:
transfer_time = time
else:
# 如果是粘性液体,降低转移速率
rate = self._transfer_rate * 0.5 if viscous else self._transfer_rate
transfer_time = volume / rate
self.data.update({
"status": "Running",
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"current_status": "Transferring",
"progress": 0.0,
"transferred_volume": 0.0
})
# 模拟转移过程
steps = 10
step_time = transfer_time / steps
step_volume = volume / steps
for i in range(steps):
await asyncio.sleep(step_time)
progress = (i + 1) / steps * 100
transferred = (i + 1) * step_volume
self.data.update({
"progress": progress,
"transferred_volume": transferred,
"current_status": f"Transferring {progress:.1f}%"
})
self.logger.info(f"Transfer progress: {progress:.1f}% ({transferred:.1f}/{volume}mL)")
# 如果需要冲洗
if rinsing_solvent and rinsing_volume > 0 and rinsing_repeats > 0:
self.data["current_status"] = "Rinsing"
for repeat in range(rinsing_repeats):
self.logger.info(f"Rinsing cycle {repeat + 1}/{rinsing_repeats} with {rinsing_solvent}")
await asyncio.sleep(1) # 模拟冲洗时间
self.data.update({
"status": "Idle",
"current_status": "Transfer completed",
"progress": 100.0,
"transferred_volume": volume
})
return True
# 添加所有在virtual_device.yaml中定义的状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def current_volume(self) -> float:
return self.data.get("current_volume", 0.0)
@property
def max_volume(self) -> float:
return self.data.get("max_volume", self._max_volume)
@property
def transfer_rate(self) -> float:
return self.data.get("transfer_rate", self._transfer_rate)
@property
def from_vessel(self) -> str:
return self.data.get("from_vessel", "")
@property
def to_vessel(self) -> str:
return self.data.get("to_vessel", "")
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def transferred_volume(self) -> float:
return self.data.get("transferred_volume", 0.0)
@property
def current_status(self) -> str:
return self.data.get("current_status", "Ready")

View File

@@ -0,0 +1,105 @@
import asyncio
import logging
from typing import Dict, Any
class VirtualValve:
"""Virtual valve device for AddProtocol testing"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_valve"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualValve.{self.device_id}")
self.data = {}
print(f"=== VirtualValve {self.device_id} is being created! ===")
print(f"=== Config: {self.config} ===")
print(f"=== Kwargs: {kwargs} ===")
# 处理所有配置参数包括port
self.port = self.config.get('port', 'VIRTUAL')
self.positions = self.config.get('positions', 6)
self.current_position = 0
# 忽略其他可能的kwargs参数
for key, value in kwargs.items():
if not hasattr(self, key):
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual valve"""
print(f"=== VirtualValve {self.device_id} initialize() called! ===")
self.logger.info(f"Initializing virtual valve {self.device_id}")
self.data.update({
"status": "Idle",
"valve_state": "Closed",
"current_position": 0,
"target_position": 0,
"max_positions": self.positions
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual valve"""
self.logger.info(f"Cleaning up virtual valve {self.device_id}")
return True
async def set_position(self, position: int) -> bool:
"""Set valve position - matches SendCmd action"""
if 0 <= position <= self.positions:
self.logger.info(f"Setting valve position to {position}")
self.data.update({
"target_position": position,
"current_position": position,
"valve_state": "Open" if position > 0 else "Closed"
})
return True
else:
self.logger.error(f"Invalid position {position}. Must be 0-{self.positions}")
return False
async def open(self) -> bool:
"""Open valve - matches EmptyIn action"""
self.logger.info("Opening valve")
self.data.update({
"valve_state": "Open",
"current_position": 1
})
return True
async def close(self) -> bool:
"""Close valve - matches EmptyIn action"""
self.logger.info("Closing valve")
self.data.update({
"valve_state": "Closed",
"current_position": 0
})
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def valve_state(self) -> str:
return self.data.get("valve_state", "Unknown")
@property
def current_position(self) -> int:
return self.data.get("current_position", 0)
@property
def target_position(self) -> int:
return self.data.get("target_position", 0)
@property
def max_positions(self) -> int:
return self.data.get("max_positions", 6)

View File

@@ -67,7 +67,115 @@ class AGVTransferProtocol(BaseModel):
to_repo: dict
from_repo_position: str
to_repo_position: str
#=============新添加的新的协议================
class AddProtocol(BaseModel):
vessel: str
reagent: str
volume: float
mass: float
amount: str
time: float
stir: bool
stir_speed: float
viscous: bool
purpose: str
class CentrifugeProtocol(BaseModel):
vessel: str
speed: float
time: float
temp: float # 移除默认值
__all__ = ["Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol"]
class FilterProtocol(BaseModel):
vessel: str
filtrate_vessel: str # 移除默认值
stir: bool # 移除默认值
stir_speed: float # 移除默认值
temp: float # 移除默认值
continue_heatchill: bool # 移除默认值
volume: float # 移除默认值
class HeatChillProtocol(BaseModel):
vessel: str
temp: float
time: float
stir: bool
stir_speed: float
purpose: str
class HeatChillStartProtocol(BaseModel):
vessel: str
temp: float
purpose: str
class HeatChillStopProtocol(BaseModel):
vessel: str
class StirProtocol(BaseModel):
stir_time: float
stir_speed: float
settling_time: float
class StartStirProtocol(BaseModel):
vessel: str
stir_speed: float
purpose: str
class StopStirProtocol(BaseModel):
vessel: str
class TransferProtocol(BaseModel):
from_vessel: str
to_vessel: str
volume: float
amount: str = ""
time: float = 0
viscous: bool = False
rinsing_solvent: str = ""
rinsing_volume: float = 0.0
rinsing_repeats: int = 0
solid: bool = False
class CleanVesselProtocol(BaseModel):
vessel: str # 要清洗的容器名称
solvent: str # 用于清洗容器的溶剂名称
volume: float # 清洗溶剂的体积,可选参数
temp: float # 清洗时的温度,可选参数
repeats: int = 1 # 清洗操作的重复次数,默认为 1
class DissolveProtocol(BaseModel):
vessel: str # 装有要溶解物质的容器名称
solvent: str # 用于溶解物质的溶剂名称
volume: float # 溶剂的体积,可选参数
amount: str = "" # 要溶解物质的量,可选参数
temp: float = 25.0 # 溶解时的温度,可选参数
time: float = 0.0 # 溶解的时间,可选参数
stir_speed: float = 0.0 # 搅拌速度,可选参数
class FilterThroughProtocol(BaseModel):
from_vessel: str # 源容器的名称,即物质起始所在的容器
to_vessel: str # 目标容器的名称,物质过滤后要到达的容器
filter_through: str # 过滤时所通过的介质,如滤纸、柱子等
eluting_solvent: str = "" # 洗脱溶剂的名称,可选参数
eluting_volume: float = 0.0 # 洗脱溶剂的体积,可选参数
eluting_repeats: int = 0 # 洗脱操作的重复次数,默认为 0
residence_time: float = 0.0 # 物质在过滤介质中的停留时间,可选参数
class RunColumnProtocol(BaseModel):
from_vessel: str # 源容器的名称,即样品起始所在的容器
to_vessel: str # 目标容器的名称,分离后的样品要到达的容器
column: str # 所使用的柱子的名称
class WashSolidProtocol(BaseModel):
vessel: str # 装有固体物质的容器名称
solvent: str # 用于清洗固体的溶剂名称
volume: float # 清洗溶剂的体积
filtrate_vessel: str = "" # 滤液要收集到的容器名称,可选参数
temp: float = 25.0 # 清洗时的温度,可选参数
stir: bool = False # 是否在清洗过程中搅拌,默认为 False
stir_speed: float = 0.0 # 搅拌速度,可选参数
time: float = 0.0 # 清洗的时间,可选参数
repeats: int = 1 # 清洗操作的重复次数,默认为 1
__all__ = ["Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol", "CentrifugeProtocol", "AddProtocol", "FilterProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol", "TransferProtocol", "CleanVesselProtocol", "DissolveProtocol", "FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol"]
# End Protocols

View File

@@ -1,10 +1,10 @@
io_snrd:
description: IO Board with 16 IOs
class:
module: unilabos.device_comms.SRND_16_IO:SRND_16_IO
module: ilabos.device_comms.SRND_16_IO:SRND_16_IO
type: python
hardware_interface:
name: modbus_client
extra_info: address
extra_info: []
read: read_io_coil
write: write_io_coil

View File

@@ -0,0 +1,892 @@
mock_chiller:
description: Mock Chiller Device
class:
module: unilabos.devices.mock.mock_chiller:MockChiller
type: python
status_types:
current_temperature: Float64
target_temperature: Float64
status: String
is_cooling: Bool
is_heating: Bool
vessel: String # 新增
purpose: String # 新增
action_value_mappings:
emergency_stop:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
heat_chill_start:
type: HeatChillStart
goal:
vessel: vessel
temp: temp
purpose: purpose
feedback: {}
result:
success: success
status: status
heat_chill_stop:
type: HeatChillStop
goal:
vessel: vessel
feedback: {}
result:
success: success
status: status
schema:
type: object
properties:
current_temperature:
type: number
description: Current temperature of the chiller in °C
target_temperature:
type: number
description: Target temperature setting in °C
status:
type: string
description: Current status of the device
is_cooling:
type: boolean
description: Whether the device is actively cooling
is_heating:
type: boolean
description: Whether the device is actively heating
vessel: # 新增
type: string
description: Current vessel being processed
purpose: # 新增
type: string
description: Purpose of the current operation
required:
- current_temperature
- target_temperature
- status
- vessel
- purpose
additionalProperties: false
mock_filter:
description: Mock Filter Device
class:
module: unilabos.devices.mock.mock_filter:MockFilter
type: python
status_types:
status: String
is_filtering: Bool
flow_rate: Float64
pressure_drop: Float64
filter_life: Float64
vessel: String
filtrate_vessel: String
filtered_volume: Float64
progress: Float64
stir: Bool
stir_speed: Float64
temperature: Float64
continue_heatchill: Bool
target_volume: Float64
action_value_mappings:
filter:
type: ProtocolFilter
goal:
vessel: vessel
filtrate_vessel: filtrate_vessel
stir: stir
stir_speed: stir_speed
temp: temp
continue_heatchill: continue_heatchill
volume: volume
feedback:
progress: progress
current_temp: current_temp
filtered_volume: filtered_volume
current_status: current_status
result:
success: success
message: message
stop_filtering:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
replace_filter:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
schema:
type: object
properties:
status:
type: string
description: Current status of the filter
is_filtering:
type: boolean
description: Whether the filter is actively filtering
flow_rate:
type: number
description: Current flow rate in L/min
pressure_drop:
type: number
description: Pressure drop across the filter in Pa
filter_life:
type: number
description: Remaining filter life percentage
power_on:
type: boolean
description: Power state of the device
required:
- status
- is_filtering
- flow_rate
- filter_life
- vessel
- filtrate_vessel
- filtered_volume
- progress
additionalProperties: false
mock_heater:
description: Mock Heater Device
class:
module: unilabos.devices.mock.mock_heater:MockHeater
type: python
status_types:
current_temperature: Float64
target_temperature: Float64
status: String
is_heating: Bool
heating_power: Float64
max_temperature: Float64
vessel: String
purpose: String
stir: Bool
stir_speed: Float64
action_value_mappings:
heat_chill_start:
type: HeatChillStart
goal:
vessel: vessel
temp: temp
purpose: purpose
feedback:
status: status
result:
success: success
heat_chill_stop:
type: HeatChillStop
goal:
vessel: vessel
feedback:
status: status
result:
success: success
heat_chill:
type: HeatChill
goal:
vessel: vessel
temp: temp
time: time
stir: stir
stir_speed: stir_speed
purpose: purpose
feedback:
status: status
result:
success: success
emergency_stop:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
schema:
type: object
properties:
current_temperature:
type: number
description: Current temperature of the heater in °C
target_temperature:
type: number
description: Target temperature setting in °C
status:
type: string
description: Current status of the device
is_heating:
type: boolean
description: Whether the device is actively heating
heating_power:
type: number
description: Current heating power percentage
max_temperature:
type: number
description: Maximum temperature limit
vessel:
type: string
description: Current vessel being heated
purpose:
type: string
description: Purpose of the heating operation
stir:
type: boolean
description: Whether stirring is enabled
stir_speed:
type: number
description: Current stirring speed
required:
- current_temperature
- target_temperature
- status
- vessel
- purpose
additionalProperties: false
mock_pump:
description: Mock Pump Device
class:
module: unilabos.devices.mock.mock_pump:MockPump
type: python
status_types:
status: String
pump_state: String
flow_rate: Float64
target_flow_rate: Float64
pressure: Float64
total_volume: Float64
max_flow_rate: Float64
max_pressure: Float64
from_vessel: String
to_vessel: String
transfer_volume: Float64
amount: String
transfer_time: Float64
is_viscous: Bool
rinsing_solvent: String
rinsing_volume: Float64
rinsing_repeats: Int32
is_solid: Bool
time_spent: Float64
time_remaining: Float64
current_device: String
action_value_mappings:
pump_transfer:
type: PumpTransfer
goal:
from_vessel: from_vessel
to_vessel: to_vessel
volume: volume
amount: amount
time: time
viscous: viscous
rinsing_solvent: rinsing_solvent
rinsing_volume: rinsing_volume
rinsing_repeats: rinsing_repeats
solid: solid
feedback:
status: status
current_device: current_device
time_spent: time_spent
time_remaining: time_remaining
result:
success: success
pause_pump:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
resume_pump:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
reset_volume_counter:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
schema:
type: object
properties:
status:
type: string
description: Current status of the pump
pump_state:
type: string
description: Pump operation state (Running/Stopped/Paused)
flow_rate:
type: number
description: Current flow rate in mL/min
target_flow_rate:
type: number
description: Target flow rate in mL/min
pressure:
type: number
description: Current pressure in bar
total_volume:
type: number
description: Total accumulated volume in mL
max_flow_rate:
type: number
description: Maximum flow rate in mL/min
max_pressure:
type: number
description: Maximum pressure in bar
from_vessel:
type: string
description: Source vessel for transfer
to_vessel:
type: string
description: Target vessel for transfer
transfer_volume:
type: number
description: Volume to transfer in mL
amount:
type: string
description: Amount description
transfer_time:
type: number
description: Transfer time in seconds
is_viscous:
type: boolean
description: Whether the liquid is viscous
rinsing_solvent:
type: string
description: Solvent used for rinsing
rinsing_volume:
type: number
description: Volume used for rinsing
rinsing_repeats:
type: integer
description: Number of rinsing cycles
is_solid:
type: boolean
description: Whether transferring solid material
current_device:
type: string
description: Current device identifier
required:
- status
- pump_state
- flow_rate
- from_vessel
- to_vessel
additionalProperties: false
mock_rotavap:
description: Mock Rotavap Device
class:
module: unilabos.devices.mock.mock_rotavap:MockRotavap
type: python
status_types:
status: String
rotate_state: String
rotate_time: Float64
rotate_speed: Float64
pump_state: String
pump_time: Float64
vacuum_level: Float64
temperature: Float64
target_temperature: Float64
success: String
action_value_mappings:
set_timer:
type: StrSingleInput
goal:
string: command
feedback: {}
result:
success: success
set_rotate_time:
type: FloatSingleInput
goal:
float_in: time_seconds
feedback: {}
result:
success: success
set_pump_time:
type: FloatSingleInput
goal:
float_in: time_seconds
feedback: {}
result:
success: success
set_rotate_speed:
type: FloatSingleInput
goal:
float_in: speed
feedback: {}
result:
success: success
set_temperature:
type: FloatSingleInput
goal:
float_in: temperature
feedback: {}
result:
success: success
start_rotation:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
start_pump:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
schema:
type: object
properties:
status:
type: string
description: Current status of the rotavap
rotate_state:
type: string
description: Rotation state (Running/Stopped)
rotate_time:
type: number
description: Remaining rotation time in seconds
rotate_speed:
type: number
description: Rotation speed in rpm
pump_state:
type: string
description: Pump state (Running/Stopped)
pump_time:
type: number
description: Remaining pump time in seconds
vacuum_level:
type: number
description: Current vacuum level in mbar
temperature:
type: number
description: Current water bath temperature
target_temperature:
type: number
description: Target water bath temperature
success:
type: string
description: Operation success status
required:
- status
- rotate_time
- pump_time
- temperature
additionalProperties: false
mock_separator:
description: Simplified Mock Separator Device
class:
module: unilabos.devices.mock.mock_separator:MockSeparator
type: python
status_types:
status: String
settling_time: Float64
valve_state: String
shake_time: Float64
shake_status: String
current_device: String
purpose: String
product_phase: String
from_vessel: String
separation_vessel: String
to_vessel: String
waste_phase_to_vessel: String
solvent: String
solvent_volume: Float64
through: String
repeats: Int32
stir_time: Float64
stir_speed: Float64
time_spent: Float64
time_remaining: Float64
action_value_mappings:
separate:
type: Separate
goal:
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
through: through
repeats: repeats
stir_time: stir_time
stir_speed: stir_speed
settling_time: settling_time
feedback:
status: status
current_device: current_device
time_spent: time_spent
time_remaining: time_remaining
result:
success: success
shake:
type: FloatSingleInput
goal:
float_in: shake_time
feedback:
status: status
result:
success: success
stop_operations:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
set_valve:
type: StrSingleInput
goal:
string: command
feedback: {}
result:
success: success
schema:
type: object
properties:
status:
type: string
description: Current status of the separator
settling_time:
type: number
description: Settling time in seconds
valve_state:
type: string
description: Valve state (Open/Closed)
shake_time:
type: number
description: Remaining shake time in seconds
shake_status:
type: string
description: Current shake state
purpose:
type: string
description: Separation purpose (wash/extract)
product_phase:
type: string
description: Product phase (top/bottom)
from_vessel:
type: string
description: Source vessel
separation_vessel:
type: string
description: Vessel for separation
to_vessel:
type: string
description: Target vessel
required:
- status
- valve_state
- shake_status
- current_device
additionalProperties: false
mock_solenoid_valve:
description: Mock Solenoid Valve Device
class:
module: unilabos.devices.mock.mock_solenoid_valve:MockSolenoidValve
type: python
status_types:
status: String
valve_status: String
action_value_mappings:
set_valve_status:
type: StrSingleInput
goal:
string: status
feedback: {}
result:
success: success
open_valve:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
close_valve:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
schema:
type: object
properties:
status:
type: string
description: Current status of the valve
valve_status:
type: string
description: Valve status (Open/Closed)
required:
- status
- valve_status
additionalProperties: false
mock_stirrer:
description: Mock Stirrer Device
class:
module: unilabos.devices.mock.mock_stirrer:MockStirrer
type: python
status_types:
status: String
stir_speed: Float64
target_stir_speed: Float64
stir_state: String
temperature: Float64
target_temperature: Float64
heating_state: String
heating_power: Float64
max_stir_speed: Float64
max_temperature: Float64
action_value_mappings:
set_stir_speed:
type: FloatSingleInput
goal:
float_in: speed
feedback: {}
result:
success: success
set_temperature:
type: FloatSingleInput
goal:
float_in: temperature
feedback: {}
result:
success: success
start_stirring:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
stop_stirring:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
heating_control:
type: StrSingleInput
goal:
string: heating_state
feedback: {}
result:
success: success
schema:
type: object
properties:
status:
type: string
description: Current status of the stirrer
stir_speed:
type: number
description: Current stirring speed in rpm
target_stir_speed:
type: number
description: Target stirring speed in rpm
stir_state:
type: string
description: Stirring state (Running/Stopped)
temperature:
type: number
description: Current temperature in °C
target_temperature:
type: number
description: Target temperature in °C
heating_state:
type: string
description: Heating state (On/Off)
heating_power:
type: number
description: Current heating power percentage
max_stir_speed:
type: number
description: Maximum stirring speed in rpm
max_temperature:
type: number
description: Maximum temperature in °C
required:
- status
- stir_speed
- temperature
- power_state
additionalProperties: false
mock_stirrer_new:
description: Mock Stirrer Device (Copy Version)
class:
module: unilabos.devices.mock.mock_stirrer_new:MockStirrer_new
type: python
status_types:
status: String
vessel: String
purpose: String
stir_speed: Float64
target_stir_speed: Float64
stir_state: String
stir_time: Float64
settling_time: Float64
progress: Float64
max_stir_speed: Float64
action_value_mappings:
start_stir:
type: ProtocolStartStir
goal:
vessel: vessel
stir_speed: stir_speed
purpose: purpose
feedback:
progress: progress
current_speed: stir_speed
current_status: status
result:
success: success
message: message
stir:
type: Stir
goal:
stir_time: stir_time
stir_speed: stir_speed
settling_time: settling_time
feedback:
status: status
result:
success: success
stop_stir:
type: ProtocolStopStir
goal:
vessel: vessel
feedback:
progress: progress
current_status: status
result:
success: success
message: message
schema:
type: object
properties:
status:
type: string
vessel:
type: string
purpose:
type: string
stir_speed:
type: number
target_stir_speed:
type: number
stir_state:
type: string
stir_time:
type: number
settling_time:
type: number
progress:
type: number
max_stir_speed:
type: number
required:
- status
- stir_speed
- stir_state
- vessel
additionalProperties: false
mock_vacuum:
description: Mock Vacuum Pump Device
class:
module: unilabos.devices.mock.mock_vacuum:MockVacuum
type: python
status_types:
status: String
power_state: String
pump_state: String
vacuum_level: Float64
target_vacuum: Float64
pump_speed: Float64
pump_efficiency: Float64
max_pump_speed: Float64
action_value_mappings:
power_control:
type: StrSingleInput
goal:
string: power_state
feedback: {}
result:
success: success
set_vacuum_level:
type: FloatSingleInput
goal:
float_in: vacuum_level
feedback: {}
result:
success: success
start_vacuum:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
stop_vacuum:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
pause_vacuum:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
resume_vacuum:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
vent_to_atmosphere:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
schema:
type: object
properties:
status:
type: string
description: Current status of the vacuum pump
power_state:
type: string
description: Power state (On/Off)
pump_state:
type: string
description: Pump operation state (Running/Stopped/Paused)
vacuum_level:
type: number
description: Current vacuum level in mbar
target_vacuum:
type: number
description: Target vacuum level in mbar
pump_speed:
type: number
description: Current pump speed in L/s
pump_efficiency:
type: number
description: Pump efficiency percentage
max_pump_speed:
type: number
description: Maximum pump speed in L/s
required:
- status
- power_state
- pump_state
- vacuum_level
additionalProperties: false

View File

@@ -27,11 +27,11 @@ moveit.toyo_xyz:
type: device
mesh: toyo_xyz
moveit.benyao_arm:
description: Benyao Arm
moveit.arm_slider:
description: Arm with Slider
model:
type: device
mesh: benyao_arm
mesh: arm_slider
class:
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
type: python

View File

@@ -71,3 +71,13 @@ solenoid_valve:
class:
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
type: python
status_types:
status: String
valve_position: String
action_value_mappings:
set_valve_position:
type: StrSingleInput
goal:
string: position
feedback: {}
result: {}

View File

@@ -62,4 +62,4 @@ tempsensor:
command: command
feedback: {}
result:
success: success
success: success

View File

@@ -94,4 +94,4 @@ gas_source.mock:
description: "通信端口"
default: "COM6"
required:
- port
- port

View File

@@ -0,0 +1,388 @@
virtual_pump:
description: Virtual Pump for PumpTransferProtocol Testing
class:
module: unilabos.devices.virtual.virtual_pump:VirtualPump
type: python
status_types:
status: String
position: Float64
valve_position: Int32 # 修复:使用 Int32 而不是 String
max_volume: Float64
current_volume: Float64
action_value_mappings:
transfer:
type: PumpTransfer
goal:
from_vessel: from_vessel
to_vessel: to_vessel
volume: volume
amount: amount
time: time
viscous: viscous
rinsing_solvent: rinsing_solvent
rinsing_volume: rinsing_volume
rinsing_repeats: rinsing_repeats
solid: solid
feedback:
status: status
result:
success: success
set_valve_position:
type: FloatSingleInput
goal:
Int32: Int32
feedback:
status: status
result:
success: success
schema:
type: object
properties:
port:
type: string
default: "VIRTUAL"
max_volume:
type: number
default: 25.0
additionalProperties: false
virtual_stirrer:
description: Virtual Stirrer for StirProtocol Testing
class:
module: unilabos.devices.virtual.virtual_stirrer:VirtualStirrer
type: python
status_types:
status: String
action_value_mappings:
stir:
type: Stir
goal:
stir_time: stir_time
stir_speed: stir_speed
settling_time: settling_time
feedback:
status: status
result:
success: success
start_stir:
type: ProtocolStartStir
goal:
vessel: vessel
stir_speed: stir_speed
purpose: purpose
feedback:
status: status
result:
success: success
stop_stir:
type: ProtocolStopStir
goal:
vessel: vessel
feedback:
status: status
result:
success: success
schema:
type: object
properties:
port:
type: string
default: "VIRTUAL"
max_temp:
type: number
default: 100.0
max_speed:
type: number
default: 1000.0
additionalProperties: false
virtual_valve:
description: Virtual Valve for AddProtocol Testing
class:
module: unilabos.devices.virtual.virtual_valve:VirtualValve
type: python
status_types:
status: String
valve_state: String
current_position: Int32
target_position: Int32
max_positions: Int32
action_value_mappings:
set_position:
type: SendCmd
goal:
command: position
feedback: {}
result:
success: success
open:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
close:
type: EmptyIn
goal: {}
feedback: {}
result:
success: success
schema:
type: object
properties:
port:
type: string
default: "VIRTUAL"
positions:
type: integer
default: 6
additionalProperties: false
virtual_centrifuge:
description: Virtual Centrifuge for CentrifugeProtocol Testing
class:
module: unilabos.devices.virtual.virtual_centrifuge:VirtualCentrifuge
type: python
status_types:
status: String
current_speed: Float64
target_speed: Float64
current_temp: Float64
target_temp: Float64
max_speed: Float64
max_temp: Float64
min_temp: Float64
centrifuge_state: String
time_remaining: Float64
action_value_mappings:
centrifuge:
type: ProtocolCentrifuge
goal:
vessel: vessel
speed: speed
time: time
temp: temp
feedback:
progress: progress
current_speed: current_speed
current_temp: current_temp
current_status: status
result:
success: success
message: message
schema:
type: object
properties:
port:
type: string
default: "VIRTUAL"
max_speed:
type: number
default: 15000.0
max_temp:
type: number
default: 40.0
min_temp:
type: number
default: 4.0
additionalProperties: false
virtual_filter:
description: Virtual Filter for FilterProtocol Testing
class:
module: unilabos.devices.virtual.virtual_filter:VirtualFilter
type: python
status_types:
status: String
filter_state: String
current_temp: Float64
target_temp: Float64
max_temp: Float64
stir_speed: Float64
max_stir_speed: Float64
filtered_volume: Float64
progress: Float64
message: String
action_value_mappings:
filter_sample:
type: ProtocolFilter
goal:
vessel: vessel
filtrate_vessel: filtrate_vessel
stir: stir
stir_speed: stir_speed
temp: temp
continue_heatchill: continue_heatchill
volume: volume
feedback:
progress: progress
current_temp: current_temp
filtered_volume: filtered_volume
current_status: status
result:
success: success
message: message
schema:
type: object
properties:
port:
type: string
default: "VIRTUAL"
max_temp:
type: number
default: 100.0
max_stir_speed:
type: number
default: 1000.0
additionalProperties: false
virtual_heatchill:
description: Virtual HeatChill for HeatChillProtocol Testing
class:
module: unilabos.devices.virtual.virtual_heatchill:VirtualHeatChill
type: python
status_types:
status: String
action_value_mappings:
heat_chill:
type: HeatChill
goal:
vessel: vessel
temp: temp
time: time
stir: stir
stir_speed: stir_speed
purpose: purpose
feedback:
status: status
result:
success: success
heat_chill_start:
type: HeatChillStart
goal:
vessel: vessel
temp: temp
purpose: purpose
feedback:
status: status
result:
success: success
heat_chill_stop:
type: HeatChillStop
goal:
vessel: vessel
feedback:
status: status
result:
success: success
schema:
type: object
properties:
port:
type: string
default: "VIRTUAL"
max_temp:
type: number
default: 200.0
min_temp:
type: number
default: -80.0
max_stir_speed:
type: number
default: 1000.0
additionalProperties: false
virtual_transfer_pump:
description: Virtual Transfer Pump for TransferProtocol Testing
class:
module: unilabos.devices.virtual.virtual_transferpump:VirtualTransferPump
type: python
status_types:
status: String
current_volume: Float64
max_volume: Float64
transfer_rate: Float64
from_vessel: String
to_vessel: String
progress: Float64
transferred_volume: Float64
current_status: String
action_value_mappings:
transfer:
type: ProtocolTransfer
goal:
from_vessel: from_vessel
to_vessel: to_vessel
volume: volume
amount: amount
time: time
viscous: viscous
rinsing_solvent: rinsing_solvent
rinsing_volume: rinsing_volume
rinsing_repeats: rinsing_repeats
solid: solid
feedback:
progress: progress
transferred_volume: transferred_volume
current_status: current_status
result:
success: success
message: message
schema:
type: object
properties:
port:
type: string
default: "VIRTUAL"
max_volume:
type: number
default: 50.0
transfer_rate:
type: number
default: 5.0
additionalProperties: false
virtual_column:
description: Virtual Column for RunColumn Protocol Testing
class:
module: unilabos.devices.virtual.virtual_column:VirtualColumn
type: python
status_types:
status: String
column_state: String
current_flow_rate: Float64
max_flow_rate: Float64
column_length: Float64
column_diameter: Float64
processed_volume: Float64
progress: Float64
current_status: String
action_value_mappings:
run_column:
type: ProtocolRunColumn
goal:
from_vessel: from_vessel
to_vessel: to_vessel
column: column
feedback:
status: current_status
progress: progress
result:
success: success
message: message
schema:
type: object
properties:
port:
type: string
default: "VIRTUAL"
max_flow_rate:
type: number
default: 10.0
column_length:
type: number
default: 25.0
column_diameter:
type: number
default: 2.0
additionalProperties: false

View File

@@ -343,8 +343,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
ADD_LIQUID_TYPE = other_calling_param.pop("ADD_LIQUID_TYPE", [])
LIQUID_VOLUME = other_calling_param.pop("LIQUID_VOLUME", [])
LIQUID_INPUT_SLOT = other_calling_param.pop("LIQUID_INPUT_SLOT", [])
slot = other_calling_param.pop("slot", -1)
if slot >= 0: # slot为负数的时候采用assign方法
slot = other_calling_param.pop("slot", "-1")
if slot != "-1": # slot为负数的时候采用assign方法
other_calling_param["slot"] = slot
# 本地拿到这个物料,可能需要先做初始化?
if isinstance(resources, list):
@@ -368,16 +368,21 @@ class BaseROS2DeviceNode(Node, Generic[T]):
# 如果driver自己就有assign的方法那就使用driver自己的assign方法
if hasattr(self.driver_instance, "create_resource"):
create_resource_func = getattr(self.driver_instance, "create_resource")
create_resource_func(
resource_tracker=self.resource_tracker,
resources=request.resources,
bind_parent_id=bind_parent_id,
bind_location=location,
liquid_input_slot=LIQUID_INPUT_SLOT,
liquid_type=ADD_LIQUID_TYPE,
liquid_volume=LIQUID_VOLUME,
slot_on_deck=slot,
)
try:
ret = create_resource_func(
resource_tracker=self.resource_tracker,
resources=request.resources,
bind_parent_id=bind_parent_id,
bind_location=location,
liquid_input_slot=LIQUID_INPUT_SLOT,
liquid_type=ADD_LIQUID_TYPE,
liquid_volume=LIQUID_VOLUME,
slot_on_deck=slot,
)
res.response = serialize_result_info("", True, ret)
except Exception as e:
traceback.print_exc()
res.response = serialize_result_info(traceback.format_exc(), False, {})
return res
# 接下来该根据bind_parent_id进行assign了目前只有plr可以进行assign不然没有办法输入到物料系统中
resource = self.resource_tracker.figure_resource({"name": bind_parent_id})
@@ -403,9 +408,10 @@ class BaseROS2DeviceNode(Node, Generic[T]):
empty_liquid_info_in[liquid_input_slot] = (liquid_type, liquid_volume)
plr_instance.set_well_liquids(empty_liquid_info_in)
if isinstance(resource, OTDeck) and "slot" in other_calling_param:
other_calling_param["slot"] = int(other_calling_param["slot"])
resource.assign_child_at_slot(plr_instance, **other_calling_param)
else:
_discard_slot = other_calling_param.pop("slot", -1)
_discard_slot = other_calling_param.pop("slot", "-1")
resource.assign_child_resource(
plr_instance,
Coordinate(location["x"], location["y"], location["z"]),
@@ -595,10 +601,10 @@ class BaseROS2DeviceNode(Node, Generic[T]):
goal = goal_handle.request
# 从目标消息中提取参数, 并调用对应的方法
if "sequence" in self._action_value_mappings:
if "sequence" in action_value_mapping:
# 如果一个指令对应函数的连续调用,如启动和等待结果,默认参数应该属于第一个函数调用
def ACTION(**kwargs):
for i, action in enumerate(self._action_value_mappings["sequence"]):
for i, action in enumerate(action_value_mapping["sequence"]):
if i == 0:
self.lab_logger().info(f"执行序列动作第一步: {action}")
self.get_real_function(self.driver_instance, action)[0](**kwargs)
@@ -606,9 +612,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self.lab_logger().info(f"执行序列动作后续步骤: {action}")
self.get_real_function(self.driver_instance, action)[0]()
action_paramtypes = get_type_hints(
self.get_real_function(self.driver_instance, self._action_value_mappings["sequence"][0])
)[1]
action_paramtypes = self.get_real_function(self.driver_instance, action_value_mapping["sequence"][0])[1]
else:
ACTION, action_paramtypes = self.get_real_function(self.driver_instance, action_name)

View File

@@ -342,6 +342,7 @@ class HostNode(BaseROS2DeviceNode):
bind_locations: list[Point],
other_calling_params: list[str],
):
responses = []
for resource, device_id, bind_parent_id, bind_location, other_calling_param in zip(
resources, device_ids, bind_parent_ids, bind_locations, other_calling_params
):
@@ -367,8 +368,8 @@ class HostNode(BaseROS2DeviceNode):
ensure_ascii=False,
)
response = sclient.call(request)
pass
pass
responses.append(response)
return responses
def create_resource(
self,
@@ -380,7 +381,7 @@ class HostNode(BaseROS2DeviceNode):
liquid_input_slot: list[int],
liquid_type: list[str],
liquid_volume: list[int],
slot_on_deck: int,
slot_on_deck: str,
):
init_new_res = initialize_resource(
{

Some files were not shown because too many files have changed in this diff Show More