Version 0.10.1 (#66)

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* 37-biomek-i5i7 (#40)

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

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

* Device visualization (#39)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

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

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

---------

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

* Device visualization (#41)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* add biomek.py demo implementation

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

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

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

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

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

* biomek_test.py

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

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

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

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

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

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

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

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

* fix move it

* fix move it

* create_resource

* bump ver
modify slot type

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

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

* 补充日志

* Device visualization (#42)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* unilab添加moveit启动

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

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

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

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

* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

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

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

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

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

* 更名Action

---------

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

* bump version & protocol fix

* hotfix: Add macos_sdk_config (#46)

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

* include device_mesh when pip install

* 测试自动构建

* try build fix

* try build

* test artifacts

* hotfix: Add .certs in .gitignore

* create container

* container 添加和更新完成

* Device registry port (#49)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* unify liquid_handler definition

* Update virtual_device.yaml

* 更正了stir和heater的连接方式

* 区分了虚拟仪器中的八通阀和电磁阀,添加了两个阀门的驱动

* 修改了add protocol

* 修复了阀门更新版的bug

* 修复了添加protocol前缀导致的不能启动的bug

* Fix handles

* bump version to 0.9.6

* add resource edge upload

* update container registry and handles

* add virtual_separator virtual_rotavap
fix transfer_pump

* fix container value
add parent_name to edge device id

* 大图的问题都修复好了,添加了gassource和vacuum pump的驱动以及注册表

* default resource upload mode is false

* 添加了icon的文件名在注册表里面

* 修改了json图中link的格式

* fix resource and edge upload

* fix device ports

* Fix edge id

* 移除device的父节点关联

* separate registry sync and resource_add

* 默认不进行注册表报送,通过命令unilabos-register或者增加启动参数

* 完善tip

* protocol node不再嵌套显示

* bump version to 0.9.7  新增一个测试PumpTransferProtocol的teststation,亲测可以运行,将八通阀们和转移泵与pump_protocol适配

* protocol node 执行action不应携带自身device id

* 添加了一套简易双八通阀工作站JSON,亲测能跑

* 修复了很多protocol,亲测能跑

* 添加了run column和filter through的protocol,亲测能跑

* fix mock_reactor

* 修改了大图和小图的json,但是在前端上没看到改变

---------

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

* 更新workstation注册表

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

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

* fix workstation registry

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

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

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

* add work_station protocol handles (ports)

* fix workstation action handle

---------

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

* 新增注册表补全功能,修复Protocol执行失败

* 支持通过导入方式补全注册表,新增工作流unilabos_device_id字段

* 修复不启用注册表补充就无法启动的bug

* 修复部分识别error

* 修复静态方法识别get status,注册表支持python类型

* status types对于嵌套类型返回的对象,暂时处理成字符串,无法直接进行转换

* 支持通过list[int],list[float]进行Int64MultiArray,Float64MultiArray的替换

* 成功动态导入的不再需要使用静态导入

* Fix handle names (#55)

* fix handle names

* improve evacuateAndRefill gas source finding

* add camera and dependency (#56)

* 修复auto-的Action在protocol node下错误注册

* 匹配init param schema格式

* Add channel_sources config in conda_build_config.yaml (#58)

* 修复任务执行传参

* Create 5 new protocols & bump version 0.9.8 (#59)

* 添加了5个缺失的protocol,验证了可以运行

* bump version to 0.9.8

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

---------

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

* 转换到ros消息时,要进行基础类型转换

* Update work_station.yaml (#60)

* Update work_station.yaml

* Checklist里面有XDL跟protocol之间没对齐的问题,工作量有点大找时间写完

* Create prcxi.py

* Update prcxi.py

* Update Prcxi

* 更新中析仪器,以及启动示例

* 修改moveit_interface,并在mqtt上报时发送一个时间戳,方便网页端对数据的筛选 (#62)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* unilab添加moveit启动

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

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

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

---------

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

* 更新实例

* 更新实例

* 更新实例

* 修正prcxi启动

* 更新PRCXI配置,修改主机地址和设置状态,并添加示例用法

* add pickup tips for prcxi

* 任意执行错误都应该返回failed

* 任意执行错误都应该返回failed

* Add plateT6 to PRCXI configuration and enhance error handling in liquid handling

* prcxi blending

* assert blending_times > 0

* update prcxi

* update prcxi registry

* Update prcxi.py to fit the function in unilabos.

* 不生成已配置action的动作,增加prcxi的debug模式

* 增加注册表版本参数,支持将auto-指令人工检查后非auto,不生成人工已检查的指令,取消不必要的description生成

* 增加注册表版本参数,支持将auto-指令人工检查后非auto,不生成人工已检查的指令,取消不必要的description生成

* Update prcxi.py

* 修复了部分的protocol因为XDL更新导致的问题 (#61)

* 修复了部分的protocol因为XDL更新导致的问题

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

* 补充了四个action

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

* bump version to 0.9.9

* fix bugs from new actions

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

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

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

* 补充了剩下的几个protocol

---------

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

* 修复action移除时的报错,更新注册表

* Update prcxi.py

* Update prcxi.py

* 新增simulator

* Update prcxi.py

* Update trash

* Update prcxi.py

* Update prcxi.py

* Update for discard tips

* Update prcxi.py

* Update PRCXI

* 更新axis等参数

* Update 9320

* get_well_container&get_tip_rack

* update

* Update 9320

* update

* deck

* 更新注册表&增加资源,parent应为resources字段

* Update 9320

* update

* 新增set liquid方法

* 新增set liquid方法

* action to resource & 0.9.12 (#64)

* action to resource & 0.9.12

* stir和adjustph的中的bug修不好

* modify prcxi

* 0.9.12 update registry

* update

* update

* registry upadte

* Update

* update

* container_for_nothing

* mix

* registry fix

* registry fix

* registry fix

* Update

* Update prcxi.py

* SET TIP RACK

* bump version

* update registry version & category

* update set tip rack

* yaml dump支持ordered dict,支持config_info

* fix devices

* fix resource check serialize

* fix: Protocol node resource run (#65)

* stir和adjustph的中的bug修不好

* fix sub-resource query in protocol node compiling

* add resource placeholder to vessels

* add the rest yaml

* Update work_station.yaml

---------

Co-authored-by: KCFeng425 <2100011801@stu.pku.edu.cn>

* 采用http报送resource

* 采用http报送resource

* update

* Update .gitignore

* bump version to 0.10.0

* default param simulator

* slim

* Update

* Update for prcxi

* Update

* Update

* Refactor PRCXI9300Deck initialization and update plate configurations

- Changed deck name from "PRCXI_Deck" to "PRCXI_Deck_9300".
- Updated plate4 initialization to use get_well_container instead of get_tip_rack.
- Modified plate4 material details with new UUID, code, and name.
- Renamed output JSON file to "deck_9300_new.json".
- Uncommented and adjusted liquid handling operations for clarity and future use.

* test

* update

* Update prcxi_9300.json

This one is good

* update

* fix protocol_node communication transfer

* 修复注册表handles类型错误的问题

* 物料添加失败应该直接raise ValueError,不要等待

* 更正注册表中的数字类型

* Delete unnecessary files.

* 新增lab_id直接传入

* fix vessel_id param passing in protocols

* 新增dll预载,保证部分设备可正常使用unilabos_msgs

* 修复可能的web template找不到的问题
新增联网获取json启动
删除非-g传入启动json的方式
兼容传参参数名短横线与下划线

* 修复可能的web template找不到的问题
新增联网获取json启动
删除非-g传入启动json的方式
兼容传参参数名短横线与下划线
更新版本到0.10.1
修复Upload Registry镜像不匹配

* 新增用户引导

* Device visualization (#67)

* Update README and MQTTClient for installation instructions and code improvements

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

* add: registry description

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

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

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

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

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

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* Device visualization (#14)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

---------

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

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

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

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

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

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* add 3d visualization

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

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

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

* 完成TF发布

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

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

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

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

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

* 完成启动OT并联动rviz

* 修复rviz位置问题,

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

* fix: running logic

* fix: running logic

* fix: missing ot

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

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

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

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

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

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

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

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

This reverts commit 498c997ad7.

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

This reverts commit 3a60d2ae81.

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

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

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

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

* unilab添加moveit启动

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

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

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

This reverts commit 56d45b94f5.

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

This reverts commit 07d9db20c3.

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

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

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

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

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

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

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

* 添加机械臂和移液站

* 添加

* 添加硬件

* update

* 添加

---------

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: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: Kongchang Feng <2100011801@stu.pku.edu.cn>
Co-authored-by: hh. <103566763+Mile-Away@users.noreply.github.com>
Co-authored-by: quehh <scienceol@outlook.com>
Co-authored-by: Harvey Que <quehaohui@dp.tech>
Co-authored-by: Junhan Chang <changjh@dp.tech>
Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>
This commit is contained in:
Xuwznln
2025-08-01 01:25:58 +08:00
committed by GitHub
parent 4224008a92
commit 280d83db57
289 changed files with 254629 additions and 12143 deletions

View File

@@ -3,7 +3,7 @@ import logging
from typing import Dict, Any, Optional
class VirtualColumn:
"""Virtual column device for RunColumn protocol"""
"""Virtual column device for RunColumn protocol 🏛️"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
@@ -25,45 +25,77 @@ class VirtualColumn:
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 ===")
print(f"🏛️ === 虚拟色谱柱 {self.device_id} 已创建 === ✨")
print(f"📏 柱参数: 流速={self._max_flow_rate}mL/min | 长度={self._column_length}cm | 直径={self._column_diameter}cm 🔬")
async def initialize(self) -> bool:
"""Initialize virtual column"""
self.logger.info(f"Initializing virtual column {self.device_id}")
"""Initialize virtual column 🚀"""
self.logger.info(f"🔧 初始化虚拟色谱柱 {self.device_id}")
self.data.update({
"status": "Idle",
"column_state": "Ready",
"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"
"current_status": "Ready for separation"
})
self.logger.info(f"✅ 色谱柱 {self.device_id} 初始化完成 🏛️")
self.logger.info(f"📊 设备规格: 最大流速 {self._max_flow_rate}mL/min | 柱长 {self._column_length}cm 📏")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual column"""
self.logger.info(f"Cleaning up virtual column {self.device_id}")
"""Cleanup virtual column 🧹"""
self.logger.info(f"🧹 清理虚拟色谱柱 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"column_state": "Offline",
"current_status": "System offline"
})
self.logger.info(f"✅ 色谱柱 {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}")
async def run_column(self, from_vessel: str, to_vessel: str, column: str, **kwargs) -> bool:
"""Execute column chromatography run - matches RunColumn action 🏛️"""
# 提取额外参数
rf = kwargs.get('rf', '0.3')
solvent1 = kwargs.get('solvent1', 'ethyl_acetate')
solvent2 = kwargs.get('solvent2', 'hexane')
ratio = kwargs.get('ratio', '30:70')
self.logger.info(f"🏛️ 开始柱层析分离: {from_vessel}{to_vessel} 🚰")
self.logger.info(f" 🧪 使用色谱柱: {column}")
self.logger.info(f" 🎯 Rf值: {rf}")
self.logger.info(f" 🧪 洗脱溶剂: {solvent1}:{solvent2} ({ratio}) 💧")
# 更新设备状态
self.data.update({
"status": "Running",
"column_state": "Separating",
"current_status": "Column separation in progress",
"current_status": "🏛️ Column separation in progress",
"progress": 0.0,
"processed_volume": 0.0
"processed_volume": 0.0,
"current_from_vessel": from_vessel,
"current_to_vessel": to_vessel,
"current_column": column,
"current_rf": rf,
"current_solvents": f"{solvent1}:{solvent2} ({ratio})"
})
# 模拟柱层析分离过程
# 假设处理时间基于流速和柱子长度
separation_time = (self._column_length * 2) / self._max_flow_rate # 简化计算
base_time = (self._column_length * 2) / self._max_flow_rate # 简化计算
separation_time = max(base_time, 20.0) # 最少20秒
self.logger.info(f"⏱️ 预计分离时间: {separation_time:.1f}秒 ⌛")
self.logger.info(f"📏 柱参数: 长度 {self._column_length}cm | 流速 {self._max_flow_rate}mL/min 🌊")
steps = 20 # 分20个步骤模拟分离过程
step_time = separation_time / steps
@@ -74,34 +106,65 @@ class VirtualColumn:
progress = (i + 1) / steps * 100
volume_processed = (i + 1) * 5.0 # 假设每步处理5mL
# 不同阶段的状态描述
if progress <= 25:
phase = "🌊 样品上柱阶段"
phase_emoji = "📥"
elif progress <= 50:
phase = "🧪 洗脱开始"
phase_emoji = "💧"
elif progress <= 75:
phase = "⚗️ 成分分离中"
phase_emoji = "🔄"
else:
phase = "🎯 收集产物"
phase_emoji = "📤"
# 更新状态
status_msg = f"{phase_emoji} {phase}: {progress:.1f}% | 💧 已处理: {volume_processed:.1f}mL"
self.data.update({
"progress": progress,
"processed_volume": volume_processed,
"current_status": f"Column separation: {progress:.1f}% - Processing {volume_processed:.1f}mL"
"current_status": status_msg,
"current_phase": phase
})
self.logger.info(f"Column separation progress: {progress:.1f}%")
# 进度日志每25%打印一次)
if progress >= 25 and (i + 1) % 5 == 0: # 每5步25%)打印一次
self.logger.info(f"📊 分离进度: {progress:.0f}% | {phase} | 💧 {volume_processed:.1f}mL 完成 ✨")
# 分离完成
final_status = f"✅ 柱层析分离完成: {from_vessel}{to_vessel} | 💧 共处理 {volume_processed:.1f}mL"
self.data.update({
"status": "Idle",
"column_state": "Ready",
"current_status": "Column separation completed",
"progress": 100.0
"current_status": final_status,
"progress": 100.0,
"final_volume": volume_processed
})
self.logger.info(f"Column separation completed: {from_vessel} -> {to_vessel}")
self.logger.info(f"🎉 柱层析分离完成! ✨")
self.logger.info(f"📊 分离结果:")
self.logger.info(f" 🥽 源容器: {from_vessel}")
self.logger.info(f" 🥽 目标容器: {to_vessel}")
self.logger.info(f" 🏛️ 使用色谱柱: {column}")
self.logger.info(f" 💧 处理体积: {volume_processed:.1f}mL")
self.logger.info(f" 🧪 洗脱条件: {solvent1}:{solvent2} ({ratio})")
self.logger.info(f" 🎯 Rf值: {rf}")
self.logger.info(f" ⏱️ 总耗时: {separation_time:.1f}秒 🏁")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
return self.data.get("status", "Unknown")
@property
def column_state(self) -> str:
return self.data.get("column_state", "Unknown")
return self.data.get("column_state", "Unknown")
@property
def current_flow_rate(self) -> float:
@@ -129,4 +192,12 @@ class VirtualColumn:
@property
def current_status(self) -> str:
return self.data.get("current_status", "Ready")
return self.data.get("current_status", "📋 Ready")
@property
def current_phase(self) -> str:
return self.data.get("current_phase", "🏠 待机中")
@property
def final_volume(self) -> float:
return self.data.get("final_volume", 0.0)

View File

@@ -5,7 +5,7 @@ from typing import Dict, Any, Optional
class VirtualFilter:
"""Virtual filter device - 完全按照 Filter.action 规范"""
"""Virtual filter device - 完全按照 Filter.action 规范 🌊"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
if device_id is None and 'id' in kwargs:
@@ -31,8 +31,8 @@ class VirtualFilter:
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual filter"""
self.logger.info(f"Initializing virtual filter {self.device_id}")
"""Initialize virtual filter 🚀"""
self.logger.info(f"🔧 初始化虚拟过滤器 {self.device_id}")
# 按照 Filter.action 的 feedback 字段初始化
self.data.update({
@@ -43,17 +43,21 @@ class VirtualFilter:
"current_status": "Ready for filtration", # Filter.action feedback
"message": "Ready for filtration"
})
self.logger.info(f"✅ 过滤器 {self.device_id} 初始化完成 🌊")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual filter"""
self.logger.info(f"Cleaning up virtual filter {self.device_id}")
"""Cleanup virtual filter 🧹"""
self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"current_status": "System offline",
"message": "System offline"
})
self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤")
return True
async def filter(
@@ -66,64 +70,82 @@ class VirtualFilter:
continue_heatchill: bool = False,
volume: float = 0.0
) -> bool:
"""Execute filter action - 完全按照 Filter.action 参数"""
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}")
self.logger.info(f" stir={stir}, stir_speed={stir_speed}, temp={temp}")
self.logger.info(f" continue_heatchill={continue_heatchill}, volume={volume}")
"""Execute filter action - 完全按照 Filter.action 参数 🌊"""
# 🔧 新增:温度自动调整
original_temp = temp
if temp == 0.0:
temp = 25.0 # 0度自动设置为室温
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (室温) 🏠")
elif temp < 4.0:
temp = 4.0 # 小于4度自动设置为4度
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️")
self.logger.info(f"🌊 开始过滤操作: {vessel}{filtrate_vessel} 🚰")
self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)")
self.logger.info(f" 🌡️ 温度: {temp}°C")
self.logger.info(f" 💧 体积: {volume}mL")
self.logger.info(f" 🔥 保持加热: {continue_heatchill}")
# 验证参数
if temp > self._max_temp or temp < 4.0:
error_msg = f"温度 {temp}°C 超出范围 (4-{self._max_temp}°C)"
self.logger.error(error_msg)
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (4-{self._max_temp}°C) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"status": f"Error: 温度超出范围 ⚠️",
"current_status": f"Error: 温度超出范围 ⚠️",
"message": error_msg
})
return False
if stir and stir_speed > self._max_stir_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM)"
self.logger.error(error_msg)
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"status": f"Error: 搅拌速度超出范围 ⚠️",
"current_status": f"Error: 搅拌速度超出范围 ⚠️",
"message": error_msg
})
return False
if volume > self._max_volume:
error_msg = f"过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL)"
self.logger.error(error_msg)
error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"status": f"Error: 体积超出范围 ⚠️",
"current_status": f"Error: 体积超出范围 ⚠️",
"message": error_msg
})
return False
# 开始过滤
filter_volume = volume if volume > 0 else 50.0
self.logger.info(f"🚀 开始过滤 {filter_volume}mL 液体 💧")
self.data.update({
"status": f"过滤中: {vessel}",
"status": f"🌊 过滤中: {vessel}",
"current_temp": temp,
"filtered_volume": 0.0,
"progress": 0.0,
"current_status": f"Filtering {vessel}{filtrate_vessel}",
"message": f"Starting filtration: {vessel}{filtrate_vessel}"
"current_status": f"🌊 Filtering {vessel}{filtrate_vessel}",
"message": f"🚀 Starting filtration: {vessel}{filtrate_vessel}"
})
try:
# 过滤过程 - 实时更新进度
start_time = time_module.time()
# 根据体积和搅拌估算过滤时间
base_time = filter_volume / 5.0 # 5mL/s 基础速度
if stir:
base_time *= 0.8 # 搅拌加速过滤
self.logger.info(f"🌪️ 搅拌加速过滤预计时间减少20% ⚡")
if temp > 50.0:
base_time *= 0.7 # 高温加速过滤
self.logger.info(f"🔥 高温加速过滤预计时间减少30% ⚡")
filter_time = max(base_time, 10.0) # 最少10秒
self.logger.info(f"⏱️ 预计过滤时间: {filter_time:.1f}秒 ⌛")
while True:
current_time = time_module.time()
@@ -133,20 +155,24 @@ class VirtualFilter:
current_filtered = (progress / 100.0) * filter_volume
# 更新状态 - 按照 Filter.action feedback 字段
status_msg = f"过滤中: {vessel}"
status_msg = f"🌊 过滤中: {vessel}"
if stir:
status_msg += f" | 搅拌: {stir_speed} RPM"
status_msg += f" | {temp}°C | {progress:.1f}% | 已过滤: {current_filtered:.1f}mL"
status_msg += f" | 🌪️ 搅拌: {stir_speed} RPM"
status_msg += f" | 🌡️ {temp}°C | 📊 {progress:.1f}% | 💧 已过滤: {current_filtered:.1f}mL"
self.data.update({
"progress": progress, # Filter.action feedback
"current_temp": temp, # Filter.action feedback
"filtered_volume": current_filtered, # Filter.action feedback
"current_status": f"Filtering: {progress:.1f}% complete", # Filter.action feedback
"current_status": f"🌊 Filtering: {progress:.1f}% complete", # Filter.action feedback
"status": status_msg,
"message": f"Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
"message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
})
# 进度日志每25%打印一次)
if progress >= 25 and progress % 25 < 1:
self.logger.info(f"📊 过滤进度: {progress:.0f}% | 💧 {current_filtered:.1f}mL 完成 ✨")
if remaining <= 0:
break
@@ -154,54 +180,57 @@ class VirtualFilter:
# 过滤完成
final_temp = temp if continue_heatchill else 25.0
final_status = f"过滤完成: {vessel} | {filter_volume}mL → {filtrate_vessel}"
final_status = f"过滤完成: {vessel} | 💧 {filter_volume}mL → {filtrate_vessel}"
if continue_heatchill:
final_status += " | 继续加热搅拌"
final_status += " | 🔥 继续加热搅拌"
self.logger.info(f"🔥 继续保持加热搅拌状态 🌪️")
self.data.update({
"status": final_status,
"progress": 100.0, # Filter.action feedback
"current_temp": final_temp, # Filter.action feedback
"filtered_volume": filter_volume, # Filter.action feedback
"current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback
"message": f"Filtration completed: {filter_volume}mL filtered from {vessel}"
"current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback
"message": f"Filtration completed: {filter_volume}mL filtered from {vessel}"
})
self.logger.info(f"Filtration completed: {filter_volume}mL from {vessel} to {filtrate_vessel}")
self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL {vessel} 过滤到 {filtrate_vessel}")
self.logger.info(f"📊 最终状态: 温度 {final_temp}°C | 进度 100% | 体积 {filter_volume}mL 🏁")
return True
except Exception as e:
self.logger.error(f"Error during filtration: {str(e)}")
error_msg = f"过滤过程中发生错误: {str(e)} 💥"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"过滤错误: {str(e)}",
"current_status": f"Filtration failed: {str(e)}",
"message": f"Filtration failed: {str(e)}"
"status": f"过滤错误: {str(e)}",
"current_status": f"Filtration failed: {str(e)}",
"message": f"Filtration failed: {str(e)}"
})
return False
# === 核心状态属性 - 按照 Filter.action feedback 字段 ===
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
return self.data.get("status", "Unknown")
@property
def progress(self) -> float:
"""Filter.action feedback 字段"""
"""Filter.action feedback 字段 📊"""
return self.data.get("progress", 0.0)
@property
def current_temp(self) -> float:
"""Filter.action feedback 字段"""
"""Filter.action feedback 字段 🌡️"""
return self.data.get("current_temp", 25.0)
@property
def filtered_volume(self) -> float:
"""Filter.action feedback 字段"""
"""Filter.action feedback 字段 💧"""
return self.data.get("filtered_volume", 0.0)
@property
def current_status(self) -> str:
"""Filter.action feedback 字段"""
"""Filter.action feedback 字段 📋"""
return self.data.get("current_status", "")
@property

View File

@@ -4,7 +4,7 @@ import time as time_module # 重命名time模块避免与参数冲突
from typing import Dict, Any
class VirtualHeatChill:
"""Virtual heat chill device for HeatChillProtocol testing"""
"""Virtual heat chill device for HeatChillProtocol testing 🌡️"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
@@ -31,94 +31,149 @@ class VirtualHeatChill:
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
print(f"🌡️ === 虚拟温控设备 {self.device_id} 已创建 === ✨")
print(f"🔥 温度范围: {self._min_temp}°C ~ {self._max_temp}°C | 🌪️ 最大搅拌: {self._max_stir_speed} RPM")
async def initialize(self) -> bool:
"""Initialize virtual heat chill"""
self.logger.info(f"Initializing virtual heat chill {self.device_id}")
"""Initialize virtual heat chill 🚀"""
self.logger.info(f"🔧 初始化虚拟温控设备 {self.device_id}")
# 初始化状态信息
self.data.update({
"status": "Idle",
"status": "🏠 待机中",
"operation_mode": "Idle",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0,
})
self.logger.info(f"✅ 温控设备 {self.device_id} 初始化完成 🌡️")
self.logger.info(f"📊 设备规格: 温度范围 {self._min_temp}°C ~ {self._max_temp}°C | 搅拌范围 0 ~ {self._max_stir_speed} RPM")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual heat chill"""
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
"""Cleanup virtual heat chill 🧹"""
self.logger.info(f"🧹 清理虚拟温控设备 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"status": "💤 离线",
"operation_mode": "Offline",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0
})
self.logger.info(f"✅ 温控设备 {self.device_id} 清理完成 💤")
return True
async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
async def heat_chill(self, vessel: str, temp: float, time, stir: bool,
stir_speed: float, purpose: str) -> bool:
"""Execute heat chill action - 按实际时间运行,实时更新剩余时间"""
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}")
"""Execute heat chill action - 🔧 修复:确保参数类型正确"""
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
self.logger.error(error_msg)
# 🔧 关键修复:确保所有参数类型正确
try:
temp = float(temp)
time_value = float(time) # 强制转换为浮点数
stir_speed = float(stir_speed)
stir = bool(stir)
vessel = str(vessel)
purpose = str(purpose)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: temp={temp}({type(temp)}), time={time}({type(time)}), error={str(e)}"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: {error_msg}",
"operation_mode": "Error"
})
return False
# 确定温度操作emoji
if temp > 25.0:
temp_emoji = "🔥"
operation_mode = "Heating"
status_action = "加热"
elif temp < 25.0:
temp_emoji = "❄️"
operation_mode = "Cooling"
status_action = "冷却"
else:
temp_emoji = "🌡️"
operation_mode = "Maintaining"
status_action = "保温"
self.logger.info(f"🌡️ 开始温控操作: {vessel}{temp}°C {temp_emoji}")
self.logger.info(f" 🥽 容器: {vessel}")
self.logger.info(f" 🎯 目标温度: {temp}°C {temp_emoji}")
self.logger.info(f" ⏰ 持续时间: {time_value}s")
self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)")
self.logger.info(f" 📝 目的: {purpose}")
# 验证参数范围
if temp > self._max_temp or temp < self._min_temp:
error_msg = f"🌡️ 温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"❌ 错误: 温度超出范围 ⚠️",
"operation_mode": "Error"
})
return False
if stir and stir_speed > self._max_stir_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM"
self.logger.error(error_msg)
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 搅拌速度超出范围 ⚠️",
"operation_mode": "Error"
})
return False
# 确定操作模式
if temp > 25.0:
operation_mode = "Heating"
status_action = "加热"
elif temp < 25.0:
operation_mode = "Cooling"
status_action = "冷却"
else:
operation_mode = "Maintaining"
status_action = "保温"
if time_value <= 0:
error_msg = f"⏰ 时间 {time_value}s 必须大于0 ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"❌ 错误: 时间参数无效 ⚠️",
"operation_mode": "Error"
})
return False
# **修复**: 使用重命名的time模块
# 🔧 修复:使用转换后的时间值
start_time = time_module.time()
total_time = time
total_time = time_value # 使用转换后的浮点数
self.logger.info(f"🚀 开始{status_action}程序! 预计用时 {total_time:.1f}秒 ⏱️")
# 开始操作
stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
self.data.update({
"status": f"运行中: {status_action} {vessel}{temp}°C | 剩余: {total_time:.0f}s{stir_info}",
"status": f"{temp_emoji} 运行中: {status_action} {vessel}{temp}°C | 剩余: {total_time:.0f}s{stir_info}",
"operation_mode": operation_mode,
"is_stirring": stir,
"stir_speed": stir_speed if stir else 0.0,
"remaining_time": total_time,
})
# **修复**: 在等待过程中每秒更新剩余时间
# 在等待过程中每秒更新剩余时间
last_logged_time = 0
while True:
current_time = time_module.time() # 使用重命名的time模块
current_time = time_module.time()
elapsed = current_time - start_time
remaining = max(0, total_time - elapsed)
progress = (elapsed / total_time) * 100 if total_time > 0 else 100
# 更新剩余时间和状态
self.data.update({
"remaining_time": remaining,
"status": f"运行中: {status_action} {vessel}{temp}°C | 剩余: {remaining:.0f}s{stir_info}"
"status": f"{temp_emoji} 运行中: {status_action} {vessel}{temp}°C | 剩余: {remaining:.0f}s{stir_info}",
"progress": progress
})
# 进度日志每25%打印一次)
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_time:
self.logger.info(f"📊 {status_action}进度: {progress:.0f}% | ⏰ 剩余: {remaining:.0f}s | {temp_emoji} 目标: {temp}°C ✨")
last_logged_time = int(progress)
# 如果时间到了,退出循环
if remaining <= 0:
break
@@ -127,71 +182,114 @@ class VirtualHeatChill:
await asyncio.sleep(1.0)
# 操作完成
final_stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
self.data.update({
"status": f"完成: {vessel} 已达到 {temp}°C | 用时: {total_time:.0f}s{final_stir_info}",
"status": f"完成: {vessel} 已达到 {temp}°C {temp_emoji} | ⏱️ 用时: {total_time:.0f}s{final_stir_info}",
"operation_mode": "Completed",
"remaining_time": 0.0,
"is_stirring": False,
"stir_speed": 0.0
"stir_speed": 0.0,
"progress": 100.0
})
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C after {total_time}s")
self.logger.info(f"🎉 温控操作完成! ✨")
self.logger.info(f"📊 操作结果:")
self.logger.info(f" 🥽 容器: {vessel}")
self.logger.info(f" 🌡️ 达到温度: {temp}°C {temp_emoji}")
self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s")
if stir:
self.logger.info(f" 🌪️ 搅拌速度: {stir_speed} RPM")
self.logger.info(f" 📝 操作目的: {purpose} 🏁")
return True
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
"""Start continuous heat chill"""
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C")
"""Start continuous heat chill 🔄"""
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
self.logger.error(error_msg)
# 🔧 添加类型转换
try:
temp = float(temp)
vessel = str(vessel)
purpose = str(purpose)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: {error_msg}",
"operation_mode": "Error"
})
return False
# 确定操作模式
# 确定温度操作emoji
if temp > 25.0:
temp_emoji = "🔥"
operation_mode = "Heating"
status_action = "持续加热"
elif temp < 25.0:
temp_emoji = "❄️"
operation_mode = "Cooling"
status_action = "持续冷却"
else:
temp_emoji = "🌡️"
operation_mode = "Maintaining"
status_action = "恒温保持"
self.logger.info(f"🔄 启动持续温控: {vessel}{temp}°C {temp_emoji}")
self.logger.info(f" 🥽 容器: {vessel}")
self.logger.info(f" 🎯 目标温度: {temp}°C {temp_emoji}")
self.logger.info(f" 🔄 模式: {status_action}")
self.logger.info(f" 📝 目的: {purpose}")
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
error_msg = f"🌡️ 温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"❌ 错误: 温度超出范围 ⚠️",
"operation_mode": "Error"
})
return False
self.data.update({
"status": f"启动: {status_action} {vessel}{temp}°C | 持续运行",
"status": f"🔄 启动: {status_action} {vessel}{temp}°C {temp_emoji} | ♾️ 持续运行",
"operation_mode": operation_mode,
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": -1.0, # -1 表示持续运行
})
self.logger.info(f"✅ 持续温控已启动! {temp_emoji} {status_action}模式 🚀")
return True
async def heat_chill_stop(self, vessel: str) -> bool:
"""Stop heat chill"""
self.logger.info(f"HeatChillStop: vessel={vessel}")
"""Stop heat chill 🛑"""
# 🔧 添加类型转换
try:
vessel = str(vessel)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(f"{error_msg}")
return False
self.logger.info(f"🛑 停止温控: {vessel}")
self.data.update({
"status": f"已停止: {vessel} 温控停止",
"status": f"🛑 已停止: {vessel} 温控停止",
"operation_mode": "Stopped",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0,
})
self.logger.info(f"✅ 温控设备已停止 {vessel} 的温度控制 🏁")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Idle")
return self.data.get("status", "🏠 待机中")
@property
def operation_mode(self) -> str:
@@ -207,4 +305,20 @@ class VirtualHeatChill:
@property
def remaining_time(self) -> float:
return self.data.get("remaining_time", 0.0)
return self.data.get("remaining_time", 0.0)
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def max_temp(self) -> float:
return self._max_temp
@property
def min_temp(self) -> float:
return self._min_temp
@property
def max_stir_speed(self) -> float:
return self._max_stir_speed

View File

@@ -1,16 +1,20 @@
import time
import logging
from typing import Union, Dict, Optional
class VirtualMultiwayValve:
"""
虚拟九通阀门 - 0号位连接transfer pump1-8号位连接其他设备
虚拟九通阀门 - 0号位连接transfer pump1-8号位连接其他设备 🔄
"""
def __init__(self, port: str = "VIRTUAL", positions: int = 8):
self.port = port
self.max_positions = positions # 1-8号位
self.total_positions = positions + 1 # 0-8号位共9个位置
# 添加日志记录器
self.logger = logging.getLogger(f"VirtualMultiwayValve.{port}")
# 状态属性
self._status = "Idle"
self._valve_state = "Ready"
@@ -29,6 +33,10 @@ class VirtualMultiwayValve:
7: "port_7", # 7号位
8: "port_8" # 8号位
}
print(f"🔄 === 虚拟多通阀门已创建 === ✨")
print(f"🎯 端口: {port} | 📊 位置范围: 0-{self.max_positions} | 🏠 初始位置: 0 (transfer_pump)")
self.logger.info(f"🔧 多通阀门初始化: 端口={port}, 最大位置={self.max_positions}")
@property
def status(self) -> str:
@@ -47,31 +55,67 @@ class VirtualMultiwayValve:
return self._target_position
def get_current_position(self) -> int:
"""获取当前阀门位置"""
"""获取当前阀门位置 📍"""
return self._current_position
def get_current_port(self) -> str:
"""获取当前连接的端口名称"""
"""获取当前连接的端口名称 🔌"""
return self.position_map.get(self._current_position, "unknown")
def set_position(self, command: Union[int, str]):
"""
设置阀门位置 - 支持0-8位置
设置阀门位置 - 支持0-8位置 🎯
Args:
command: 目标位置 (0-8) 或位置字符串
0: transfer pump位置
1-8: 其他设备位置
'default': 默认位置0号位
"""
try:
# 如果是字符串形式的位置,先转换为数字
# 🔧 处理特殊字符串命令
if isinstance(command, str):
pos = int(command)
command_lower = command.lower().strip()
# 处理特殊命令
if command_lower in ['default', 'pump', 'transfer_pump', 'home']:
pos = 0 # 默认位置为0号位transfer pump
self.logger.info(f"🔧 特殊命令 '{command}' 映射到位置 {pos}")
elif command_lower in ['open']:
pos = 0 # open命令也映射到0号位
self.logger.info(f"🔧 OPEN命令映射到位置 {pos}")
elif command_lower in ['close', 'closed']:
# 关闭命令保持当前位置
pos = self._current_position
self.logger.info(f"🔧 CLOSE命令保持当前位置 {pos}")
else:
# 尝试转换为数字
try:
pos = int(command)
except ValueError:
error_msg = f"无法识别的命令: '{command}'"
self.logger.error(f"{error_msg}")
raise ValueError(error_msg)
else:
pos = int(command)
if pos < 0 or pos > self.max_positions:
raise ValueError(f"Position must be between 0 and {self.max_positions}")
error_msg = f"位置必须在 0-{self.max_positions} 范围内"
self.logger.error(f"{error_msg}: 请求位置={pos}")
raise ValueError(error_msg)
# 获取位置描述emoji
if pos == 0:
pos_emoji = "🚰"
pos_desc = "泵位置"
else:
pos_emoji = "🔌"
pos_desc = f"端口{pos}"
old_position = self._current_position
old_port = self.get_current_port()
self.logger.info(f"🔄 阀门切换: {old_position}({old_port}) → {pos}({self.position_map.get(pos, 'unknown')}) {pos_emoji}")
self._status = "Busy"
self._valve_state = "Moving"
@@ -79,104 +123,139 @@ class VirtualMultiwayValve:
# 模拟阀门切换时间
switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.5秒
time.sleep(switch_time)
if switch_time > 0:
self.logger.info(f"⏱️ 阀门移动中... 预计用时: {switch_time:.1f}秒 🔄")
time.sleep(switch_time)
self._current_position = pos
self._status = "Idle"
self._valve_state = "Ready"
current_port = self.get_current_port()
return f"Position set to {pos} ({current_port})"
success_msg = f"✅ 阀门已切换到位置 {pos} ({current_port}) {pos_emoji}"
self.logger.info(success_msg)
return success_msg
except ValueError as e:
error_msg = f"❌ 阀门切换失败: {str(e)}"
self._status = "Error"
self._valve_state = "Error"
return f"Error: {str(e)}"
self.logger.error(error_msg)
return error_msg
def set_to_pump_position(self):
"""切换到transfer pump位置0号位"""
"""切换到transfer pump位置0号位🚰"""
self.logger.info(f"🚰 切换到泵位置...")
return self.set_position(0)
def set_to_port(self, port_number: int):
"""
切换到指定端口位置
切换到指定端口位置 🔌
Args:
port_number: 端口号 (1-8)
"""
if port_number < 1 or port_number > self.max_positions:
raise ValueError(f"Port number must be between 1 and {self.max_positions}")
error_msg = f"端口号必须在 1-{self.max_positions} 范围内"
self.logger.error(f"{error_msg}: 请求端口={port_number}")
raise ValueError(error_msg)
self.logger.info(f"🔌 切换到端口 {port_number}...")
return self.set_position(port_number)
def open(self):
"""打开阀门 - 设置到transfer pump位置0号位"""
"""打开阀门 - 设置到transfer pump位置0号位🔓"""
self.logger.info(f"🔓 打开阀门,设置到泵位置...")
return self.set_to_pump_position()
def close(self):
"""关闭阀门 - 对于多通阀门,设置到一个"关闭"状态"""
"""关闭阀门 - 对于多通阀门,设置到一个"关闭"状态 🔒"""
self.logger.info(f"🔒 关闭阀门...")
self._status = "Busy"
self._valve_state = "Closing"
time.sleep(0.5)
# 可以选择保持当前位置或设置特殊关闭状态
self._status = "Idle"
self._valve_state = "Closed"
return f"Valve closed at position {self._current_position}"
close_msg = f"🔒 阀门已关闭,保持在位置 {self._current_position} ({self.get_current_port()})"
self.logger.info(close_msg)
return close_msg
def get_valve_position(self) -> int:
"""获取阀门位置 - 兼容性方法"""
"""获取阀门位置 - 兼容性方法 📍"""
return self._current_position
def is_at_position(self, position: int) -> bool:
"""检查是否在指定位置"""
return self._current_position == position
"""检查是否在指定位置 🎯"""
result = self._current_position == position
# 删除debug日志self.logger.debug(f"🎯 位置检查: 当前={self._current_position}, 目标={position}, 匹配={result}")
return result
def is_at_pump_position(self) -> bool:
"""检查是否在transfer pump位置"""
return self._current_position == 0
"""检查是否在transfer pump位置 🚰"""
result = self._current_position == 0
# 删除debug日志pump_status = "是" if result else "否"
# 删除debug日志self.logger.debug(f"🚰 泵位置检查: {pump_status} (当前位置: {self._current_position})")
return result
def is_at_port(self, port_number: int) -> bool:
"""检查是否在指定端口位置"""
return self._current_position == port_number
"""检查是否在指定端口位置 🔌"""
result = self._current_position == port_number
# 删除debug日志port_status = "是" if result else "否"
# 删除debug日志self.logger.debug(f"🔌 端口{port_number}检查: {port_status} (当前位置: {self._current_position})")
return result
def get_available_positions(self) -> list:
"""获取可用位置列表"""
return list(range(0, self.max_positions + 1))
"""获取可用位置列表 📋"""
positions = list(range(0, self.max_positions + 1))
# 删除debug日志self.logger.debug(f"📋 可用位置: {positions}")
return positions
def get_available_ports(self) -> Dict[int, str]:
"""获取可用端口映射"""
"""获取可用端口映射 🗺️"""
# 删除debug日志self.logger.debug(f"🗺️ 端口映射: {self.position_map}")
return self.position_map.copy()
def reset(self):
"""重置阀门到transfer pump位置0号位"""
"""重置阀门到transfer pump位置0号位🔄"""
self.logger.info(f"🔄 重置阀门到泵位置...")
return self.set_position(0)
def switch_between_pump_and_port(self, port_number: int):
"""
在transfer pump位置和指定端口之间切换
在transfer pump位置和指定端口之间切换 🔄
Args:
port_number: 目标端口号 (1-8)
"""
if self._current_position == 0:
# 当前在pump位置切换到指定端口
self.logger.info(f"🔄 从泵位置切换到端口 {port_number}...")
return self.set_to_port(port_number)
else:
# 当前在某个端口切换到pump位置
self.logger.info(f"🔄 从端口 {self._current_position} 切换到泵位置...")
return self.set_to_pump_position()
def get_flow_path(self) -> str:
"""获取当前流路路径描述"""
"""获取当前流路路径描述 🌊"""
current_port = self.get_current_port()
if self._current_position == 0:
return f"Transfer pump connected (position {self._current_position})"
flow_path = f"🚰 转移泵已连接 (位置 {self._current_position})"
else:
return f"Port {self._current_position} connected ({current_port})"
flow_path = f"🔌 端口 {self._current_position} 已连接 ({current_port})"
# 删除debug日志self.logger.debug(f"🌊 当前流路: {flow_path}")
return flow_path
def get_info(self) -> dict:
"""获取阀门详细信息"""
return {
"""获取阀门详细信息 📊"""
info = {
"port": self.port,
"max_positions": self.max_positions,
"total_positions": self.total_positions,
@@ -188,18 +267,25 @@ class VirtualMultiwayValve:
"flow_path": self.get_flow_path(),
"position_map": self.position_map
}
# 删除debug日志self.logger.debug(f"📊 阀门信息: 位置={self._current_position}, 状态={self._status}, 端口={self.get_current_port()}")
return info
def __str__(self):
return f"VirtualMultiwayValve(Position: {self._current_position}/{self.max_positions}, Port: {self.get_current_port()}, Status: {self._status})"
current_port = self.get_current_port()
status_emoji = "" if self._status == "Idle" else "🔄" if self._status == "Busy" else ""
return f"🔄 VirtualMultiwayValve({status_emoji} 位置: {self._current_position}/{self.max_positions}, 端口: {current_port}, 状态: {self._status})"
def set_valve_position(self, command: Union[int, str]):
"""
设置阀门位置 - 兼容pump_protocol调用
设置阀门位置 - 兼容pump_protocol调用 🎯
这是set_position的别名方法用于兼容pump_protocol.py
Args:
command: 目标位置 (0-8) 或位置字符串
"""
# 删除debug日志self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})")
return self.set_position(command)
@@ -207,25 +293,35 @@ class VirtualMultiwayValve:
if __name__ == "__main__":
valve = VirtualMultiwayValve()
print("=== 虚拟九通阀门测试 ===")
print(f"初始状态: {valve}")
print(f"当前流路: {valve.get_flow_path()}")
print("🔄 === 虚拟九通阀门测试 ===")
print(f"🏠 初始状态: {valve}")
print(f"🌊 当前流路: {valve.get_flow_path()}")
# 切换到试剂瓶11号位
print(f"\n切换到1号位: {valve.set_position(1)}")
print(f"当前状态: {valve}")
print(f"\n🔌 切换到1号位: {valve.set_position(1)}")
print(f"📍 当前状态: {valve}")
# 切换到transfer pump位置0号位
print(f"\n切换到pump位置: {valve.set_to_pump_position()}")
print(f"当前状态: {valve}")
print(f"\n🚰 切换到pump位置: {valve.set_to_pump_position()}")
print(f"📍 当前状态: {valve}")
# 切换到试剂瓶22号位
print(f"\n切换到2号位: {valve.set_to_port(2)}")
print(f"当前状态: {valve}")
print(f"\n🔌 切换到2号位: {valve.set_to_port(2)}")
print(f"📍 当前状态: {valve}")
# 显示所有可用位置
print(f"\n可用位置: {valve.get_available_positions()}")
print(f"端口映射: {valve.get_available_ports()}")
print(f"\n📋 可用位置: {valve.get_available_positions()}")
print(f"🗺️ 端口映射: {valve.get_available_ports()}")
# 获取详细信息
print(f"\n详细信息: {valve.get_info()}")
print(f"\n📊 详细信息: {valve.get_info()}")
# 测试切换功能
print(f"\n🔄 智能切换测试:")
print(f"当前位置: {valve._current_position}")
print(f"切换结果: {valve.switch_between_pump_and_port(3)}")
print(f"新位置: {valve._current_position}")
# 重置测试
print(f"\n🔄 重置测试: {valve.reset()}")
print(f"📍 重置后状态: {valve}")

View File

@@ -3,9 +3,12 @@ import logging
import time as time_module
from typing import Dict, Any, Optional
def debug_print(message):
"""调试输出 🔍"""
print(f"🌪️ [ROTAVAP] {message}", flush=True)
class VirtualRotavap:
"""Virtual rotary evaporator device - 简化版,只保留核心功能"""
"""Virtual rotary evaporator device - 简化版,只保留核心功能 🌪️"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
# 处理可能的不同调用方式
@@ -32,13 +35,16 @@ class VirtualRotavap:
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
print(f"🌪️ === 虚拟旋转蒸发仪 {self.device_id} 已创建 === ✨")
print(f"🔥 温度范围: 10°C ~ {self._max_temp}°C | 🌀 转速范围: 10 ~ {self._max_rotation_speed} RPM")
async def initialize(self) -> bool:
"""Initialize virtual rotary evaporator"""
self.logger.info(f"Initializing virtual rotary evaporator {self.device_id}")
"""Initialize virtual rotary evaporator 🚀"""
self.logger.info(f"🔧 初始化虚拟旋转蒸发仪 {self.device_id}")
# 只保留核心状态
self.data.update({
"status": "Idle",
"status": "🏠 待机中",
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
"current_temp": 25.0,
"target_temp": 25.0,
@@ -47,22 +53,27 @@ class VirtualRotavap:
"evaporated_volume": 0.0,
"progress": 0.0,
"remaining_time": 0.0,
"message": "Ready for evaporation"
"message": "🌪️ Ready for evaporation"
})
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 初始化完成 🌪️")
self.logger.info(f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual rotary evaporator"""
self.logger.info(f"Cleaning up virtual rotary evaporator {self.device_id}")
"""Cleanup virtual rotary evaporator 🧹"""
self.logger.info(f"🧹 清理虚拟旋转蒸发仪 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"status": "💤 离线",
"rotavap_state": "Offline",
"current_temp": 25.0,
"rotation_speed": 0.0,
"vacuum_pressure": 1.0,
"message": "System offline"
"message": "💤 System offline"
})
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 清理完成 💤")
return True
async def evaporate(
@@ -70,46 +81,102 @@ class VirtualRotavap:
vessel: str,
pressure: float = 0.1,
temp: float = 60.0,
time: float = 1800.0, # 30分钟默认
stir_speed: float = 100.0
time: float = 180.0,
stir_speed: float = 100.0,
solvent: str = "",
**kwargs
) -> bool:
"""Execute evaporate action - 简化的蒸发流程"""
self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM")
"""Execute evaporate action - 简化版 🌪️"""
# 🔧 新增确保time参数是数值类型
if isinstance(time, str):
try:
time = float(time)
except ValueError:
self.logger.error(f"❌ 无法转换时间参数 '{time}' 为数值使用默认值180.0秒")
time = 180.0
elif not isinstance(time, (int, float)):
self.logger.error(f"❌ 时间参数类型无效: {type(time)}使用默认值180.0秒")
time = 180.0
# 确保time是float类型
time = float(time)
# 🔧 简化处理如果vessel就是设备自己直接操作
if vessel == self.device_id:
debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作")
actual_vessel = self.device_id
else:
actual_vessel = vessel
# 参数预处理
if solvent:
self.logger.info(f"🧪 识别到溶剂: {solvent}")
# 根据溶剂调整参数
solvent_lower = solvent.lower()
if any(s in solvent_lower for s in ['water', 'aqueous']):
temp = max(temp, 80.0)
pressure = max(pressure, 0.2)
self.logger.info(f"💧 水系溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
temp = min(temp, 50.0)
pressure = min(pressure, 0.05)
self.logger.info(f"⚡ 易挥发溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
self.logger.info(f"🌪️ 开始蒸发操作: {actual_vessel}")
self.logger.info(f" 🥽 容器: {actual_vessel}")
self.logger.info(f" 🌡️ 温度: {temp}°C")
self.logger.info(f" 💨 真空度: {pressure} bar")
self.logger.info(f" ⏰ 时间: {time}s")
self.logger.info(f" 🌀 转速: {stir_speed} RPM")
if solvent:
self.logger.info(f" 🧪 溶剂: {solvent}")
# 验证参数
if temp > self._max_temp or temp < 10.0:
error_msg = f"温度 {temp}°C 超出范围 (10-{self._max_temp}°C)"
self.logger.error(error_msg)
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (10-{self._max_temp}°C) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 温度超出范围",
"rotavap_state": "Error",
"current_temp": 25.0,
"progress": 0.0,
"evaporated_volume": 0.0,
"message": error_msg
})
return False
if stir_speed > self._max_rotation_speed or stir_speed < 10.0:
error_msg = f"旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM)"
self.logger.error(error_msg)
error_msg = f"🌀 旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 转速超出范围",
"rotavap_state": "Error",
"current_temp": 25.0,
"progress": 0.0,
"evaporated_volume": 0.0,
"message": error_msg
})
return False
if pressure < 0.01 or pressure > 1.0:
error_msg = f"真空度 {pressure} bar 超出范围 (0.01-1.0 bar)"
self.logger.error(error_msg)
error_msg = f"💨 真空度 {pressure} bar 超出范围 (0.01-1.0 bar) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 压力超出范围",
"rotavap_state": "Error",
"current_temp": 25.0,
"progress": 0.0,
"evaporated_volume": 0.0,
"message": error_msg
})
return False
# 开始蒸发
# 开始蒸发 - 🔧 现在time已经确保是float类型
self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️")
self.data.update({
"status": f"蒸发中: {vessel}",
"status": f"🌪️ 蒸发中: {actual_vessel}",
"rotavap_state": "Evaporating",
"current_temp": temp,
"target_temp": temp,
@@ -118,13 +185,14 @@ class VirtualRotavap:
"remaining_time": time,
"progress": 0.0,
"evaporated_volume": 0.0,
"message": f"Evaporating {vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM"
"message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM"
})
try:
# 蒸发过程 - 实时更新进度
start_time = time_module.time()
total_time = time
last_logged_progress = 0
while True:
current_time = time_module.time()
@@ -132,18 +200,31 @@ class VirtualRotavap:
remaining = max(0, total_time - elapsed)
progress = min(100.0, (elapsed / total_time) * 100)
# 模拟蒸发体积
evaporated_vol = progress * 0.8 # 假设最多蒸发80mL
# 模拟蒸发体积 - 根据溶剂类型调整
if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']):
evaporated_vol = progress * 0.6 # 水系溶剂蒸发慢
elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']):
evaporated_vol = progress * 1.0 # 易挥发溶剂蒸发快
else:
evaporated_vol = progress * 0.8 # 默认蒸发量
# 🔧 更新状态 - 确保包含所有必需字段
status_msg = f"🌪️ 蒸发中: {actual_vessel} | 🌡️ {temp}°C | 💨 {pressure} bar | 🌀 {stir_speed} RPM | 📊 {progress:.1f}% | ⏰ 剩余: {remaining:.0f}s"
# 更新状态
self.data.update({
"remaining_time": remaining,
"progress": progress,
"evaporated_volume": evaporated_vol,
"status": f"蒸发中: {vessel} | {temp}°C | {pressure} bar | {progress:.1f}% | 剩余: {remaining:.0f}s",
"message": f"Evaporating: {progress:.1f}% complete, {remaining:.0f}s remaining"
"current_temp": temp,
"status": status_msg,
"message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining"
})
# 进度日志每25%打印一次)
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_progress:
self.logger.info(f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨")
last_logged_progress = int(progress)
# 时间到了,退出循环
if remaining <= 0:
break
@@ -152,40 +233,59 @@ class VirtualRotavap:
await asyncio.sleep(1.0)
# 蒸发完成
final_evaporated = 80.0
if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']):
final_evaporated = 60.0 # 水系溶剂
elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']):
final_evaporated = 100.0 # 易挥发溶剂
else:
final_evaporated = 80.0 # 默认
self.data.update({
"status": f"蒸发完成: {vessel} | 蒸发量: {final_evaporated:.1f}mL",
"status": f"蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL",
"rotavap_state": "Completed",
"evaporated_volume": final_evaporated,
"progress": 100.0,
"current_temp": temp,
"remaining_time": 0.0,
"current_temp": 25.0, # 冷却下来
"rotation_speed": 0.0, # 停止旋转
"vacuum_pressure": 1.0, # 恢复大气压
"message": f"Evaporation completed: {final_evaporated}mL evaporated from {vessel}"
"rotation_speed": 0.0,
"vacuum_pressure": 1.0,
"message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}"
})
self.logger.info(f"Evaporation completed: {final_evaporated}mL evaporated from {vessel}")
self.logger.info(f"🎉 蒸发操作完成! ✨")
self.logger.info(f"📊 蒸发结果:")
self.logger.info(f" 🥽 容器: {actual_vessel}")
self.logger.info(f" 💧 蒸发量: {final_evaporated:.1f}mL")
self.logger.info(f" 🌡️ 蒸发温度: {temp}°C")
self.logger.info(f" 💨 真空度: {pressure} bar")
self.logger.info(f" 🌀 旋转速度: {stir_speed} RPM")
self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s")
if solvent:
self.logger.info(f" 🧪 处理溶剂: {solvent} 🏁")
return True
except Exception as e:
# 出错处理
self.logger.error(f"Error during evaporation: {str(e)}")
error_msg = f"蒸发过程中发生错误: {str(e)} 💥"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"蒸发错误: {str(e)}",
"status": f"蒸发错误: {str(e)}",
"rotavap_state": "Error",
"current_temp": 25.0,
"progress": 0.0,
"evaporated_volume": 0.0,
"rotation_speed": 0.0,
"vacuum_pressure": 1.0,
"message": f"Evaporation failed: {str(e)}"
"message": f"Evaporation failed: {str(e)}"
})
return False
# === 核心状态属性 ===
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
return self.data.get("status", "Unknown")
@property
def rotavap_state(self) -> str:

View File

@@ -43,10 +43,25 @@ class VirtualSolenoidValve:
def is_open(self) -> bool:
return self._is_open
def get_valve_position(self) -> str:
@property
def valve_position(self) -> str:
"""获取阀门位置状态"""
return "OPEN" if self._is_open else "CLOSED"
@property
def state(self) -> dict:
"""获取阀门完整状态"""
return {
"device_id": self.device_id,
"port": self.port,
"voltage": self.voltage,
"response_time": self.response_time,
"is_open": self._is_open,
"valve_state": self._valve_state,
"status": self._status,
"position": self.valve_position
}
async def set_valve_position(self, command: str = None, **kwargs):
"""
设置阀门位置 - ROS动作接口
@@ -91,7 +106,7 @@ class VirtualSolenoidValve:
return {
"success": True,
"message": result_msg,
"valve_position": self.get_valve_position()
"valve_position": self.valve_position
}
async def open(self, **kwargs):
@@ -102,21 +117,25 @@ class VirtualSolenoidValve:
"""关闭电磁阀 - ROS动作接口"""
return await self.set_valve_position(command="CLOSED")
async def set_state(self, command: Union[bool, str], **kwargs):
async def set_status(self, string: str = None, **kwargs):
"""
设置阀门状态 - 兼容 SendCmd 类型
设置阀门状态 - 兼容 StrSingleInput 类型
Args:
command: True/False"open"/"close"
string: "ON"/"OFF""OPEN"/"CLOSED"
"""
if isinstance(command, bool):
cmd_str = "OPEN" if command else "CLOSED"
elif isinstance(command, str):
cmd_str = command
else:
return {"success": False, "message": "Invalid command type"}
if string is None:
return {"success": False, "message": "Missing string parameter"}
return await self.set_valve_position(command=cmd_str)
# 将 string 参数转换为 command 参数
if string.upper() in ["ON", "OPEN"]:
command = "OPEN"
elif string.upper() in ["OFF", "CLOSED"]:
command = "CLOSED"
else:
command = string
return await self.set_valve_position(command=command)
def toggle(self):
"""切换阀门状态"""
@@ -129,19 +148,6 @@ class VirtualSolenoidValve:
"""检查阀门是否关闭"""
return not self._is_open
def get_state(self) -> dict:
"""获取阀门完整状态"""
return {
"device_id": self.device_id,
"port": self.port,
"voltage": self.voltage,
"response_time": self.response_time,
"is_open": self._is_open,
"valve_state": self._valve_state,
"status": self._status,
"position": self.get_valve_position()
}
async def reset(self):
"""重置阀门到关闭状态"""
return await self.close()

View File

@@ -0,0 +1,389 @@
import asyncio
import logging
import re
from typing import Dict, Any, Optional
class VirtualSolidDispenser:
"""
虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 ⚗️
特点:
- 高兼容性:缺少参数不报错 ✅
- 智能识别:自动查找固体试剂瓶 🔍
- 简单反馈:成功/失败 + 消息 📊
"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
self.device_id = device_id or "virtual_solid_dispenser"
self.config = config or {}
# 设备参数
self.max_capacity = float(self.config.get('max_capacity', 100.0)) # 最大加样量 (g)
self.precision = float(self.config.get('precision', 0.001)) # 精度 (g)
# 状态变量
self._status = "Idle"
self._current_reagent = ""
self._dispensed_amount = 0.0
self._total_operations = 0
self.logger = logging.getLogger(f"VirtualSolidDispenser.{self.device_id}")
print(f"⚗️ === 虚拟固体分配器 {self.device_id} 创建成功! === ✨")
print(f"📊 设备规格: 最大容量 {self.max_capacity}g | 精度 {self.precision}g 🎯")
async def initialize(self) -> bool:
"""初始化固体加样器 🚀"""
self.logger.info(f"🔧 初始化固体分配器 {self.device_id}")
self._status = "Ready"
self._current_reagent = ""
self._dispensed_amount = 0.0
self.logger.info(f"✅ 固体分配器 {self.device_id} 初始化完成 ⚗️")
return True
async def cleanup(self) -> bool:
"""清理固体加样器 🧹"""
self.logger.info(f"🧹 清理固体分配器 {self.device_id} 🔚")
self._status = "Idle"
self.logger.info(f"✅ 固体分配器 {self.device_id} 清理完成 💤")
return True
def parse_mass_string(self, mass_str: str) -> float:
"""
解析质量字符串为数值 (g) ⚖️
支持格式: "2.9 g", "19.3g", "4.5 mg", "1.2 kg"
"""
if not mass_str or not isinstance(mass_str, str):
return 0.0
# 移除空格并转小写
mass_clean = mass_str.strip().lower()
# 正则匹配数字和单位
pattern = r'(\d+(?:\.\d+)?)\s*([a-z]*)'
match = re.search(pattern, mass_clean)
if not match:
self.logger.debug(f"🔍 无法解析质量字符串: {mass_str}")
return 0.0
try:
value = float(match.group(1))
unit = match.group(2) or 'g' # 默认单位 g
# 单位转换为 g
unit_multipliers = {
'g': 1.0,
'gram': 1.0,
'grams': 1.0,
'mg': 0.001,
'milligram': 0.001,
'milligrams': 0.001,
'kg': 1000.0,
'kilogram': 1000.0,
'kilograms': 1000.0,
'μg': 0.000001,
'ug': 0.000001,
'microgram': 0.000001,
'micrograms': 0.000001,
}
multiplier = unit_multipliers.get(unit, 1.0)
result = value * multiplier
self.logger.debug(f"⚖️ 质量解析: {mass_str}{result:.6f}g (原值: {value} {unit})")
return result
except (ValueError, TypeError):
self.logger.warning(f"⚠️ 无法解析质量字符串: {mass_str}")
return 0.0
def parse_mol_string(self, mol_str: str) -> float:
"""
解析摩尔数字符串为数值 (mol) 🧮
支持格式: "0.12 mol", "16.2 mmol", "25.2mmol"
"""
if not mol_str or not isinstance(mol_str, str):
return 0.0
# 移除空格并转小写
mol_clean = mol_str.strip().lower()
# 正则匹配数字和单位
pattern = r'(\d+(?:\.\d+)?)\s*(m?mol)'
match = re.search(pattern, mol_clean)
if not match:
self.logger.debug(f"🔍 无法解析摩尔数字符串: {mol_str}")
return 0.0
try:
value = float(match.group(1))
unit = match.group(2)
# 单位转换为 mol
if unit == 'mmol':
result = value * 0.001
else: # mol
result = value
self.logger.debug(f"🧮 摩尔数解析: {mol_str}{result:.6f}mol (原值: {value} {unit})")
return result
except (ValueError, TypeError):
self.logger.warning(f"⚠️ 无法解析摩尔数字符串: {mol_str}")
return 0.0
def find_solid_reagent_bottle(self, reagent_name: str) -> str:
"""
查找固体试剂瓶 🔍
这是一个简化版本,实际使用时应该连接到系统的设备图
"""
if not reagent_name:
self.logger.debug(f"🔍 未指定试剂名称,使用默认瓶")
return "unknown_solid_bottle"
# 可能的固体试剂瓶命名模式
possible_names = [
f"solid_bottle_{reagent_name}",
f"reagent_solid_{reagent_name}",
f"powder_{reagent_name}",
f"{reagent_name}_solid",
f"{reagent_name}_powder",
f"solid_{reagent_name}",
]
# 这里简化处理,实际应该查询设备图
selected_bottle = possible_names[0]
self.logger.debug(f"🔍 为试剂 {reagent_name} 选择试剂瓶: {selected_bottle}")
return selected_bottle
async def add_solid(
self,
vessel: str,
reagent: str,
mass: str = "",
mol: str = "",
purpose: str = "",
**kwargs # 兼容额外参数
) -> Dict[str, Any]:
"""
添加固体试剂的主要方法 ⚗️
Args:
vessel: 目标容器
reagent: 试剂名称
mass: 质量字符串 (如 "2.9 g")
mol: 摩尔数字符串 (如 "0.12 mol")
purpose: 添加目的
**kwargs: 其他兼容参数
Returns:
Dict: 操作结果
"""
try:
self.logger.info(f"⚗️ === 开始固体加样操作 === ✨")
self.logger.info(f" 🥽 目标容器: {vessel}")
self.logger.info(f" 🧪 试剂: {reagent}")
self.logger.info(f" ⚖️ 质量: {mass}")
self.logger.info(f" 🧮 摩尔数: {mol}")
self.logger.info(f" 📝 目的: {purpose}")
# 参数验证 - 宽松处理
if not vessel:
vessel = "main_reactor" # 默认容器
self.logger.warning(f"⚠️ 未指定容器,使用默认容器: {vessel} 🏠")
if not reagent:
error_msg = "❌ 错误: 必须指定试剂名称"
self.logger.error(error_msg)
return {
"success": False,
"message": error_msg,
"return_info": "missing_reagent"
}
# 解析质量和摩尔数
mass_value = self.parse_mass_string(mass)
mol_value = self.parse_mol_string(mol)
self.logger.info(f"📊 解析结果 - 质量: {mass_value:.6f}g | 摩尔数: {mol_value:.6f}mol")
# 确定实际加样量
if mass_value > 0:
actual_amount = mass_value
amount_unit = "g"
amount_emoji = "⚖️"
self.logger.info(f"⚖️ 按质量加样: {actual_amount:.6f} {amount_unit}")
elif mol_value > 0:
# 简化处理假设分子量为100 g/mol
assumed_mw = 100.0
actual_amount = mol_value * assumed_mw
amount_unit = "g (from mol)"
amount_emoji = "🧮"
self.logger.info(f"🧮 按摩尔数加样: {mol_value:.6f} mol → {actual_amount:.6f} g (假设分子量 {assumed_mw})")
else:
# 没有指定量,使用默认值
actual_amount = 1.0
amount_unit = "g (default)"
amount_emoji = "🎯"
self.logger.warning(f"⚠️ 未指定质量或摩尔数,使用默认值: {actual_amount} {amount_unit} 🎯")
# 检查容量限制
if actual_amount > self.max_capacity:
error_msg = f"❌ 错误: 请求量 {actual_amount:.3f}g 超过最大容量 {self.max_capacity}g"
self.logger.error(error_msg)
return {
"success": False,
"message": error_msg,
"return_info": "exceeds_capacity"
}
# 查找试剂瓶
reagent_bottle = self.find_solid_reagent_bottle(reagent)
self.logger.info(f"🔍 使用试剂瓶: {reagent_bottle}")
# 模拟加样过程
self._status = "Dispensing"
self._current_reagent = reagent
# 计算操作时间 (基于质量)
operation_time = max(0.5, actual_amount * 0.1) # 每克0.1秒最少0.5秒
self.logger.info(f"🚀 开始加样,预计时间: {operation_time:.1f}秒 ⏱️")
# 显示进度的模拟
steps = max(3, int(operation_time))
step_time = operation_time / steps
for i in range(steps):
progress = (i + 1) / steps * 100
await asyncio.sleep(step_time)
if i % 2 == 0: # 每隔一步显示进度
self.logger.debug(f"📊 加样进度: {progress:.0f}% | {amount_emoji} 正在分配 {reagent}...")
# 更新状态
self._dispensed_amount = actual_amount
self._total_operations += 1
self._status = "Ready"
# 成功结果
success_message = f"✅ 成功添加 {reagent} {actual_amount:.6f} {amount_unit}{vessel}"
self.logger.info(f"🎉 === 固体加样完成 === ✨")
self.logger.info(f"📊 操作结果:")
self.logger.info(f"{success_message}")
self.logger.info(f" 🧪 试剂瓶: {reagent_bottle}")
self.logger.info(f" ⏱️ 用时: {operation_time:.1f}")
self.logger.info(f" 🎯 总操作次数: {self._total_operations} 🏁")
return {
"success": True,
"message": success_message,
"return_info": f"dispensed_{actual_amount:.6f}g",
"dispensed_amount": actual_amount,
"reagent": reagent,
"vessel": vessel
}
except Exception as e:
error_message = f"❌ 固体加样失败: {str(e)} 💥"
self.logger.error(error_message)
self._status = "Error"
return {
"success": False,
"message": error_message,
"return_info": "operation_failed"
}
# 状态属性
@property
def status(self) -> str:
return self._status
@property
def current_reagent(self) -> str:
return self._current_reagent
@property
def dispensed_amount(self) -> float:
return self._dispensed_amount
@property
def total_operations(self) -> int:
return self._total_operations
def get_device_info(self) -> Dict[str, Any]:
"""获取设备状态信息 📊"""
info = {
"device_id": self.device_id,
"status": self._status,
"current_reagent": self._current_reagent,
"last_dispensed_amount": self._dispensed_amount,
"total_operations": self._total_operations,
"max_capacity": self.max_capacity,
"precision": self.precision
}
self.logger.debug(f"📊 设备信息: 状态={self._status}, 试剂={self._current_reagent}, 加样量={self._dispensed_amount:.6f}g")
return info
def __str__(self):
status_emoji = "" if self._status == "Ready" else "🔄" if self._status == "Dispensing" else "" if self._status == "Error" else "🏠"
return f"⚗️ VirtualSolidDispenser({status_emoji} {self.device_id}: {self._status}, 最后加样 {self._dispensed_amount:.3f}g)"
# 测试函数
async def test_solid_dispenser():
"""测试固体加样器 🧪"""
print("⚗️ === 固体加样器测试开始 === 🧪")
dispenser = VirtualSolidDispenser("test_dispenser")
await dispenser.initialize()
# 测试1: 按质量加样
print(f"\n🧪 测试1: 按质量加样...")
result1 = await dispenser.add_solid(
vessel="main_reactor",
reagent="magnesium",
mass="2.9 g"
)
print(f"📊 测试1结果: {result1}")
# 测试2: 按摩尔数加样
print(f"\n🧮 测试2: 按摩尔数加样...")
result2 = await dispenser.add_solid(
vessel="main_reactor",
reagent="sodium_nitrite",
mol="0.28 mol"
)
print(f"📊 测试2结果: {result2}")
# 测试3: 缺少参数
print(f"\n⚠️ 测试3: 缺少参数测试...")
result3 = await dispenser.add_solid(
reagent="test_compound"
)
print(f"📊 测试3结果: {result3}")
# 测试4: 超容量测试
print(f"\n❌ 测试4: 超容量测试...")
result4 = await dispenser.add_solid(
vessel="main_reactor",
reagent="heavy_compound",
mass="150 g" # 超过100g限制
)
print(f"📊 测试4结果: {result4}")
print(f"\n📊 最终设备信息: {dispenser.get_device_info()}")
print(f"✅ === 测试完成 === 🎉")
if __name__ == "__main__":
asyncio.run(test_solid_dispenser())

View File

@@ -4,7 +4,7 @@ import time as time_module
from typing import Dict, Any
class VirtualStirrer:
"""Virtual stirrer device for StirProtocol testing - 功能完整版"""
"""Virtual stirrer device for StirProtocol testing - 功能完整版 🌪️"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
@@ -30,45 +30,69 @@ class VirtualStirrer:
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
print(f"🌪️ === 虚拟搅拌器 {self.device_id} 已创建 === ✨")
print(f"🔧 速度范围: {self._min_speed} ~ {self._max_speed} RPM | 📱 端口: {self.port}")
async def initialize(self) -> bool:
"""Initialize virtual stirrer"""
self.logger.info(f"Initializing virtual stirrer {self.device_id}")
"""Initialize virtual stirrer 🚀"""
self.logger.info(f"🔧 初始化虚拟搅拌器 {self.device_id}")
# 初始化状态信息
self.data.update({
"status": "Idle",
"status": "🏠 待机中",
"operation_mode": "Idle", # 操作模式: Idle, Stirring, Settling, Completed, Error
"current_vessel": "", # 当前搅拌的容器
"current_speed": 0.0, # 当前搅拌速度
"is_stirring": False, # 是否正在搅拌
"remaining_time": 0.0, # 剩余时间
})
self.logger.info(f"✅ 搅拌器 {self.device_id} 初始化完成 🌪️")
self.logger.info(f"📊 设备规格: 速度范围 {self._min_speed} ~ {self._max_speed} RPM")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual stirrer"""
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
"""Cleanup virtual stirrer 🧹"""
self.logger.info(f"🧹 清理虚拟搅拌器 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"status": "💤 离线",
"operation_mode": "Offline",
"current_vessel": "",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": 0.0,
})
self.logger.info(f"✅ 搅拌器 {self.device_id} 清理完成 💤")
return True
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
"""Execute stir action - 定时搅拌 + 沉降"""
self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
async def stir(self, stir_time: float, stir_speed: float, settling_time: float, **kwargs) -> bool:
"""Execute stir action - 定时搅拌 + 沉降 🌪️"""
# 🔧 类型转换 - 确保所有参数都是数字类型
try:
stir_time = float(stir_time)
stir_speed = float(stir_speed)
settling_time = float(settling_time)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换失败: stir_time={stir_time}, stir_speed={stir_speed}, settling_time={settling_time}, error={e}"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"❌ 错误: {error_msg}",
"operation_mode": "Error"
})
return False
self.logger.info(f"🌪️ 开始搅拌操作: 速度 {stir_speed} RPM | 时间 {stir_time}s | 沉降 {settling_time}s")
# 验证参数
if stir_speed > self._max_speed or stir_speed < self._min_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
self.logger.error(error_msg)
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 速度超出范围",
"operation_mode": "Error"
})
return False
@@ -77,8 +101,10 @@ class VirtualStirrer:
start_time = time_module.time()
total_stir_time = stir_time
self.logger.info(f"🚀 开始搅拌阶段: {stir_speed} RPM × {total_stir_time}s ⏱️")
self.data.update({
"status": f"搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s",
"status": f"🌪️ 搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s",
"operation_mode": "Stirring",
"current_speed": stir_speed,
"is_stirring": True,
@@ -86,30 +112,41 @@ class VirtualStirrer:
})
# 搅拌过程 - 实时更新剩余时间
last_logged_time = 0
while True:
current_time = time_module.time()
elapsed = current_time - start_time
remaining = max(0, total_stir_time - elapsed)
progress = (elapsed / total_stir_time) * 100 if total_stir_time > 0 else 100
# 更新状态
self.data.update({
"remaining_time": remaining,
"status": f"搅拌中: {stir_speed} RPM | 剩余: {remaining:.0f}s"
"status": f"🌪️ 搅拌中: {stir_speed} RPM | 剩余: {remaining:.0f}s"
})
# 进度日志每25%打印一次)
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_time:
self.logger.info(f"📊 搅拌进度: {progress:.0f}% | 🌪️ {stir_speed} RPM | ⏰ 剩余: {remaining:.0f}s ✨")
last_logged_time = int(progress)
# 搅拌时间到了
if remaining <= 0:
break
await asyncio.sleep(1.0)
self.logger.info(f"✅ 搅拌阶段完成! 🌪️ {stir_speed} RPM × {stir_time}s")
# === 第二阶段:沉降(如果需要)===
if settling_time > 0:
start_settling_time = time_module.time()
total_settling_time = settling_time
self.logger.info(f"🛑 开始沉降阶段: 停止搅拌 × {total_settling_time}s ⏱️")
self.data.update({
"status": f"沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s",
"status": f"🛑 沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s",
"operation_mode": "Settling",
"current_speed": 0.0,
"is_stirring": False,
@@ -117,52 +154,87 @@ class VirtualStirrer:
})
# 沉降过程 - 实时更新剩余时间
last_logged_settling = 0
while True:
current_time = time_module.time()
elapsed = current_time - start_settling_time
remaining = max(0, total_settling_time - elapsed)
progress = (elapsed / total_settling_time) * 100 if total_settling_time > 0 else 100
# 更新状态
self.data.update({
"remaining_time": remaining,
"status": f"沉降中: 停止搅拌 | 剩余: {remaining:.0f}s"
"status": f"🛑 沉降中: 停止搅拌 | 剩余: {remaining:.0f}s"
})
# 进度日志每25%打印一次)
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_settling:
self.logger.info(f"📊 沉降进度: {progress:.0f}% | 🛑 静置中 | ⏰ 剩余: {remaining:.0f}s ✨")
last_logged_settling = int(progress)
# 沉降时间到了
if remaining <= 0:
break
await asyncio.sleep(1.0)
self.logger.info(f"✅ 沉降阶段完成! 🛑 静置 {settling_time}s")
# === 操作完成 ===
settling_info = f" | 沉降: {settling_time:.0f}s" if settling_time > 0 else ""
settling_info = f" | 🛑 沉降: {settling_time:.0f}s" if settling_time > 0 else ""
self.data.update({
"status": f"完成: 搅拌 {stir_speed} RPM, {stir_time:.0f}s{settling_info}",
"status": f"完成: 🌪️ 搅拌 {stir_speed} RPM × {stir_time:.0f}s{settling_info}",
"operation_mode": "Completed",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": 0.0,
})
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s + settling {settling_time}s")
self.logger.info(f"🎉 搅拌操作完成! ✨")
self.logger.info(f"📊 操作总结:")
self.logger.info(f" 🌪️ 搅拌: {stir_speed} RPM × {stir_time}s")
if settling_time > 0:
self.logger.info(f" 🛑 沉降: {settling_time}s")
self.logger.info(f" ⏱️ 总用时: {(stir_time + settling_time):.0f}s 🏁")
return True
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
"""Start stir action - 开始持续搅拌"""
self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
async def start_stir(self, vessel: str, stir_speed: float, purpose: str = "") -> bool:
"""Start stir action - 开始持续搅拌 🔄"""
# 验证参数
if stir_speed > self._max_speed or stir_speed < self._min_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
self.logger.error(error_msg)
# 🔧 类型转换
try:
stir_speed = float(stir_speed)
vessel = str(vessel)
purpose = str(purpose)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: {error_msg}",
"operation_mode": "Error"
})
return False
self.logger.info(f"🔄 启动持续搅拌: {vessel} | 🌪️ {stir_speed} RPM")
if purpose:
self.logger.info(f"📝 搅拌目的: {purpose}")
# 验证参数
if stir_speed > self._max_speed or stir_speed < self._min_speed:
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"❌ 错误: 速度超出范围",
"operation_mode": "Error"
})
return False
purpose_info = f" | 📝 {purpose}" if purpose else ""
self.data.update({
"status": f"启动: 持续搅拌 {vessel} at {stir_speed} RPM | {purpose}",
"status": f"🔄 启动: 持续搅拌 {vessel} | 🌪️ {stir_speed} RPM{purpose_info}",
"operation_mode": "Stirring",
"current_vessel": vessel,
"current_speed": stir_speed,
@@ -170,16 +242,28 @@ class VirtualStirrer:
"remaining_time": -1.0, # -1 表示持续运行
})
self.logger.info(f"✅ 持续搅拌已启动! 🌪️ {stir_speed} RPM × ♾️ 🚀")
return True
async def stop_stir(self, vessel: str) -> bool:
"""Stop stir action - 停止搅拌"""
self.logger.info(f"StopStir: vessel={vessel}")
"""Stop stir action - 停止搅拌 🛑"""
# 🔧 类型转换
try:
vessel = str(vessel)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(f"{error_msg}")
return False
current_speed = self.data.get("current_speed", 0.0)
self.logger.info(f"🛑 停止搅拌: {vessel}")
if current_speed > 0:
self.logger.info(f"🌪️ 之前搅拌速度: {current_speed} RPM")
self.data.update({
"status": f"已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM",
"status": f"🛑 已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM",
"operation_mode": "Stopped",
"current_vessel": "",
"current_speed": 0.0,
@@ -187,12 +271,13 @@ class VirtualStirrer:
"remaining_time": 0.0,
})
self.logger.info(f"✅ 搅拌器已停止 {vessel} 的搅拌操作 🏁")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Idle")
return self.data.get("status", "🏠 待机中")
@property
def operation_mode(self) -> str:
@@ -212,4 +297,33 @@ class VirtualStirrer:
@property
def remaining_time(self) -> float:
return self.data.get("remaining_time", 0.0)
return self.data.get("remaining_time", 0.0)
@property
def max_speed(self) -> float:
return self._max_speed
@property
def min_speed(self) -> float:
return self._min_speed
def get_device_info(self) -> Dict[str, Any]:
"""获取设备状态信息 📊"""
info = {
"device_id": self.device_id,
"status": self.status,
"operation_mode": self.operation_mode,
"current_vessel": self.current_vessel,
"current_speed": self.current_speed,
"is_stirring": self.is_stirring,
"remaining_time": self.remaining_time,
"max_speed": self._max_speed,
"min_speed": self._min_speed
}
self.logger.debug(f"📊 设备信息: 模式={self.operation_mode}, 速度={self.current_speed} RPM, 搅拌={self.is_stirring}")
return info
def __str__(self):
status_emoji = "" if self.operation_mode == "Idle" else "🌪️" if self.operation_mode == "Stirring" else "🛑" if self.operation_mode == "Settling" else ""
return f"🌪️ VirtualStirrer({status_emoji} {self.device_id}: {self.operation_mode}, {self.current_speed} RPM)"

View File

@@ -12,7 +12,7 @@ class VirtualPumpMode(Enum):
class VirtualTransferPump:
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件"""
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件 🚰"""
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
"""
@@ -42,20 +42,31 @@ class VirtualTransferPump:
self._max_velocity = 5.0 # float
self._current_volume = 0.0 # float
# 🚀 新增:快速模式设置 - 大幅缩短执行时间
self._fast_mode = True # 是否启用快速模式
self._fast_move_time = 1.0 # 快速移动时间(秒)
self._fast_dispense_time = 1.0 # 快速喷射时间(秒)
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
print(f"🚰 === 虚拟转移泵 {self.device_id} 已创建 === ✨")
print(f"💨 快速模式: {'启用' if self._fast_mode else '禁用'} | 移动时间: {self._fast_move_time}s | 喷射时间: {self._fast_dispense_time}s")
print(f"📊 最大容量: {self.max_volume}mL | 端口: {self.port}")
async def initialize(self) -> bool:
"""初始化虚拟泵"""
self.logger.info(f"Initializing virtual pump {self.device_id}")
"""初始化虚拟泵 🚀"""
self.logger.info(f"🔧 初始化虚拟转移泵 {self.device_id}")
self._status = "Idle"
self._position = 0.0
self._current_volume = 0.0
self.logger.info(f"✅ 转移泵 {self.device_id} 初始化完成 🚰")
return True
async def cleanup(self) -> bool:
"""清理虚拟泵"""
self.logger.info(f"Cleaning up virtual pump {self.device_id}")
"""清理虚拟泵 🧹"""
self.logger.info(f"🧹 清理虚拟转移泵 {self.device_id} 🔚")
self._status = "Idle"
self.logger.info(f"✅ 转移泵 {self.device_id} 清理完成 💤")
return True
# 基本属性
@@ -65,12 +76,12 @@ class VirtualTransferPump:
@property
def position(self) -> float:
"""当前柱塞位置 (ml)"""
"""当前柱塞位置 (ml) 📍"""
return self._position
@property
def current_volume(self) -> float:
"""当前注射器中的体积 (ml)"""
"""当前注射器中的体积 (ml) 💧"""
return self._current_volume
@property
@@ -82,22 +93,50 @@ class VirtualTransferPump:
return self._transfer_rate
def set_max_velocity(self, velocity: float):
"""设置最大速度 (ml/s)"""
"""设置最大速度 (ml/s) 🌊"""
self._max_velocity = max(0.1, min(50.0, velocity)) # 限制在合理范围内
self.logger.info(f"Set max velocity to {self._max_velocity} ml/s")
self.logger.info(f"🌊 设置最大速度为 {self._max_velocity} mL/s")
def get_status(self) -> str:
"""获取泵状态"""
"""获取泵状态 📋"""
return self._status
async def _simulate_operation(self, duration: float):
"""模拟操作延时"""
"""模拟操作延时 ⏱️"""
self._status = "Busy"
await asyncio.sleep(duration)
self._status = "Idle"
def _calculate_duration(self, volume: float, velocity: float = None) -> float:
"""计算操作持续时间"""
"""
计算操作持续时间 ⏰
🚀 快速模式:保留计算逻辑用于日志显示,但实际使用固定的快速时间
"""
if velocity is None:
velocity = self._max_velocity
# 📊 计算理论时间(用于日志显示)
theoretical_duration = abs(volume) / velocity
# 🚀 如果启用快速模式,使用固定的快速时间
if self._fast_mode:
# 根据操作类型选择快速时间
if abs(volume) > 0.1: # 大于0.1mL的操作
actual_duration = self._fast_move_time
else: # 很小的操作
actual_duration = 0.5
self.logger.debug(f"⚡ 快速模式: 理论时间 {theoretical_duration:.2f}s → 实际时间 {actual_duration:.2f}s")
return actual_duration
else:
# 正常模式使用理论时间
return theoretical_duration
def _calculate_display_duration(self, volume: float, velocity: float = None) -> float:
"""
计算显示用的持续时间(用于日志) 📊
这个函数返回理论计算时间,用于日志显示
"""
if velocity is None:
velocity = self._max_velocity
return abs(volume) / velocity
@@ -105,7 +144,7 @@ class VirtualTransferPump:
# 新的set_position方法 - 专门用于SetPumpPosition动作
async def set_position(self, position: float, max_velocity: float = None):
"""
移动到绝对位置 - 专门用于SetPumpPosition动作
移动到绝对位置 - 专门用于SetPumpPosition动作 🎯
Args:
position (float): 目标位置 (ml)
@@ -122,56 +161,107 @@ class VirtualTransferPump:
# 限制位置在有效范围内
target_position = max(0.0, min(float(self.max_volume), target_position))
# 计算移动距离和时间
# 计算移动距离
volume_to_move = abs(target_position - self._position)
duration = self._calculate_duration(volume_to_move, velocity)
self.logger.info(f"SET_POSITION: Moving to {target_position} ml (current: {self._position} ml), velocity: {velocity} ml/s")
# 📊 计算显示用的时间(用于日志)
display_duration = self._calculate_display_duration(volume_to_move, velocity)
# 模拟移动过程
start_position = self._position
steps = 10 if duration > 0.1 else 1 # 如果移动距离很小只用1步
step_duration = duration / steps if steps > 1 else duration
# ⚡ 计算实际执行时间(快速模式)
actual_duration = self._calculate_duration(volume_to_move, velocity)
for i in range(steps + 1):
# 计算当前位置和进度
progress = (i / steps) * 100 if steps > 0 else 100
current_pos = start_position + (target_position - start_position) * (i / steps) if steps > 0 else target_position
# 🎯 确定操作类型和emoji
if target_position > self._position:
operation_type = "吸液"
operation_emoji = "📥"
elif target_position < self._position:
operation_type = "排液"
operation_emoji = "📤"
else:
operation_type = "保持"
operation_emoji = "📍"
self.logger.info(f"🎯 SET_POSITION: {operation_type} {operation_emoji}")
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {target_position:.2f}mL (移动 {volume_to_move:.2f}mL)")
self.logger.info(f" 🌊 速度: {velocity:.2f} mL/s")
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
if self._fast_mode:
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
# 🚀 模拟移动过程
if volume_to_move > 0.01: # 只有当移动距离足够大时才显示进度
start_position = self._position
steps = 5 if actual_duration > 0.5 else 2 # 根据实际时间调整步数
step_duration = actual_duration / steps
# 更新状态
self._status = "Moving" if i < steps else "Idle"
self._position = current_pos
self._current_volume = current_pos
self.logger.info(f"🚀 开始{operation_type}... {operation_emoji}")
# 等待一小步时间
if i < steps and step_duration > 0:
await asyncio.sleep(step_duration)
for i in range(steps + 1):
# 计算当前位置和进度
progress = (i / steps) * 100 if steps > 0 else 100
current_pos = start_position + (target_position - start_position) * (i / steps) if steps > 0 else target_position
# 更新状态
if i < steps:
self._status = f"{operation_type}"
status_emoji = "🔄"
else:
self._status = "Idle"
status_emoji = ""
self._position = current_pos
self._current_volume = current_pos
# 显示进度每25%或最后一步)
if i == 0:
self.logger.debug(f" 🔄 {operation_type}开始: {progress:.0f}%")
elif progress >= 50 and i == steps // 2:
self.logger.debug(f" 🔄 {operation_type}进度: {progress:.0f}%")
elif i == steps:
self.logger.info(f"{operation_type}完成: {progress:.0f}% | 当前位置: {current_pos:.2f}mL")
# 等待一小步时间
if i < steps and step_duration > 0:
await asyncio.sleep(step_duration)
else:
# 移动距离很小,直接完成
self._position = target_position
self._current_volume = target_position
self.logger.info(f" 📍 微调完成: {target_position:.2f}mL")
# 确保最终位置准确
self._position = target_position
self._current_volume = target_position
self._status = "Idle"
self.logger.info(f"SET_POSITION: Reached position {self._position} ml, current volume: {self._current_volume} ml")
# 📊 最终状态日志
if volume_to_move > 0.01:
self.logger.info(f"🎉 SET_POSITION 完成! 📍 最终位置: {self._position:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
# 返回符合action定义的结果
return {
"success": True,
"message": f"Successfully moved to position {self._position} ml"
"message": f"✅ 成功移动到位置 {self._position:.2f}mL ({operation_type})",
"final_position": self._position,
"final_volume": self._current_volume,
"operation_type": operation_type
}
except Exception as e:
error_msg = f"Failed to set position: {str(e)}"
error_msg = f"❌ 设置位置失败: {str(e)}"
self.logger.error(error_msg)
return {
"success": False,
"message": error_msg
"message": error_msg,
"final_position": self._position,
"final_volume": self._current_volume
}
# 其他泵操作方法
async def pull_plunger(self, volume: float, velocity: float = None):
"""
拉取柱塞(吸液)
拉取柱塞(吸液) 📥
Args:
volume (float): 要拉取的体积 (ml)
@@ -181,23 +271,29 @@ class VirtualTransferPump:
actual_volume = new_position - self._position
if actual_volume <= 0:
self.logger.warning("Cannot pull - already at maximum volume")
self.logger.warning("⚠️ 无法吸液 - 已达到最大容量")
return
duration = self._calculate_duration(actual_volume, velocity)
display_duration = self._calculate_display_duration(actual_volume, velocity)
actual_duration = self._calculate_duration(actual_volume, velocity)
self.logger.info(f"Pulling {actual_volume} ml (from {self._position} to {new_position})")
self.logger.info(f"📥 开始吸液: {actual_volume:.2f}mL")
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {new_position:.2f}mL")
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
await self._simulate_operation(duration)
if self._fast_mode:
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
await self._simulate_operation(actual_duration)
self._position = new_position
self._current_volume = new_position
self.logger.info(f"Pulled {actual_volume} ml, current volume: {self._current_volume} ml")
self.logger.info(f"✅ 吸液完成: {actual_volume:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
async def push_plunger(self, volume: float, velocity: float = None):
"""
推出柱塞(排液)
推出柱塞(排液) 📤
Args:
volume (float): 要推出的体积 (ml)
@@ -207,35 +303,44 @@ class VirtualTransferPump:
actual_volume = self._position - new_position
if actual_volume <= 0:
self.logger.warning("Cannot push - already at minimum volume")
self.logger.warning("⚠️ 无法排液 - 已达到最小容量")
return
duration = self._calculate_duration(actual_volume, velocity)
display_duration = self._calculate_display_duration(actual_volume, velocity)
actual_duration = self._calculate_duration(actual_volume, velocity)
self.logger.info(f"Pushing {actual_volume} ml (from {self._position} to {new_position})")
self.logger.info(f"📤 开始排液: {actual_volume:.2f}mL")
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {new_position:.2f}mL")
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
await self._simulate_operation(duration)
if self._fast_mode:
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
await self._simulate_operation(actual_duration)
self._position = new_position
self._current_volume = new_position
self.logger.info(f"Pushed {actual_volume} ml, current volume: {self._current_volume} ml")
self.logger.info(f"✅ 排液完成: {actual_volume:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
# 便捷操作方法
async def aspirate(self, volume: float, velocity: float = None):
"""吸液操作"""
"""吸液操作 📥"""
await self.pull_plunger(volume, velocity)
async def dispense(self, volume: float, velocity: float = None):
"""排液操作"""
"""排液操作 📤"""
await self.push_plunger(volume, velocity)
async def transfer(self, volume: float, aspirate_velocity: float = None, dispense_velocity: float = None):
"""转移操作(先吸后排)"""
"""转移操作(先吸后排) 🔄"""
self.logger.info(f"🔄 开始转移操作: {volume:.2f}mL")
# 吸液
await self.aspirate(volume, aspirate_velocity)
# 短暂停顿
self.logger.debug("⏸️ 短暂停顿...")
await asyncio.sleep(0.1)
# 排液