From ec4e6c6cfd040c094190f7ac33f8114f9206881e Mon Sep 17 00:00:00 2001
From: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Date: Fri, 23 May 2025 10:06:30 +0800
Subject: [PATCH 1/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=8B=B1=E6=96=87readme?=
=?UTF-8?q?=E6=8F=8F=E8=BF=B0=20(#33)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 67 ++++++++++++++++++++-------------------
README_zh.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 124 insertions(+), 32 deletions(-)
create mode 100644 README_zh.md
diff --git a/README.md b/README.md
index 0c1d9b1..02b6d06 100644
--- a/README.md
+++ b/README.md
@@ -4,83 +4,86 @@
# Uni-Lab-OS
+
+**English** | [中文](README_zh.md)
+
[](https://github.com/dptech-corp/Uni-Lab-OS/stargazers)
[](https://github.com/dptech-corp/Uni-Lab-OS/network/members)
[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
[](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
-Uni-Lab 操作系统是一个用于实验室自动化的综合平台,旨在连接和控制各种实验设备,实现实验流程的自动化和标准化。
+Uni-Lab Operating System is a platform for laboratory automation, designed to connect and control various experimental equipment, enabling automation and standardization of experimental workflows.
-## 核心特点
+## Key Features
-- 多设备集成管理
-- 自动化实验流程
-- 云端连接能力
-- 灵活的配置系统
-- 支持多种实验协议
+- Multi-device integration management
+- Automated experimental workflows
+- Cloud connectivity capabilities
+- Flexible configuration system
+- Support for multiple experimental protocols
-## 文档
+## Documentation
-详细文档可在以下位置找到:
+Detailed documentation can be found at:
-- [在线文档](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
+- [Online Documentation](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
-## 快速开始
+## Quick Start
-1. 配置Conda环境
+1. Configure Conda Environment
-Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适当的环境文件:
+Uni-Lab-OS recommends using `mamba` for environment management. Choose the appropriate environment file for your operating system:
```bash
-# 创建新环境
+# Create new environment
mamba env create -f unilabos-[YOUR_OS].yaml
mamba activate unilab
-# 或更新现有环境
-# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。
-conda env update --file unilabos-[YOUR_OS].yml -n 环境名
+# Or update existing environment
+# Where `[YOUR_OS]` can be `win64`, `linux-64`, `osx-64`, or `osx-arm64`.
+conda env update --file unilabos-[YOUR_OS].yml -n environment_name
-# 现阶段,需要安装 `unilabos_msgs` 包
-# 可以前往 Release 页面下载系统对应的包进行安装
+# Currently, you need to install the `unilabos_msgs` package
+# You can download the system-specific package from the Release page
conda install ros-humble-unilabos-msgs-0.9.0-xxxxx.tar.bz2
-# 安装PyLabRobot等前置
+# Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
cd plr_repo
pip install .[opentrons]
```
-2. 安装 Uni-Lab-OS:
+2. Install Uni-Lab-OS:
```bash
-# 克隆仓库
+# Clone the repository
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
cd Uni-Lab-OS
-# 安装 Uni-Lab-OS
+# Install Uni-Lab-OS
pip install .
```
-3. 启动 Uni-Lab 系统:
+3. Start Uni-Lab System:
-请见[文档-启动样例](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
+Please refer to [Documentation - Boot Examples](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
-## 消息格式
+## Message Format
-Uni-Lab-OS 使用预构建的 `unilabos_msgs` 进行系统通信。您可以在 [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) 页面找到已构建的版本。
+Uni-Lab-OS uses pre-built `unilabos_msgs` for system communication. You can find the built versions on the [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) page.
-## 许可证
+## License
-此项目采用 GPL-3.0 许可 - 详情请参阅 [LICENSE](LICENSE) 文件。
+This project is licensed under GPL-3.0 - see the [LICENSE](LICENSE) file for details.
-## 项目统计
+## Project Statistics
-### Stars 趋势
+### Stars Trend
-## 联系我们
+## Contact Us
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
\ No newline at end of file
diff --git a/README_zh.md b/README_zh.md
new file mode 100644
index 0000000..ce660cd
--- /dev/null
+++ b/README_zh.md
@@ -0,0 +1,89 @@
+
+

+
+
+# Uni-Lab-OS
+
+
+[English](README.md) | **中文**
+
+[](https://github.com/dptech-corp/Uni-Lab-OS/stargazers)
+[](https://github.com/dptech-corp/Uni-Lab-OS/network/members)
+[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
+[](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
+
+Uni-Lab 操作系统是一个用于实验室自动化的综合平台,旨在连接和控制各种实验设备,实现实验流程的自动化和标准化。
+
+## 核心特点
+
+- 多设备集成管理
+- 自动化实验流程
+- 云端连接能力
+- 灵活的配置系统
+- 支持多种实验协议
+
+## 文档
+
+详细文档可在以下位置找到:
+
+- [在线文档](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
+
+## 快速开始
+
+1. 配置Conda环境
+
+Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适当的环境文件:
+
+```bash
+# 创建新环境
+mamba env create -f unilabos-[YOUR_OS].yaml
+mamba activate unilab
+
+# 或更新现有环境
+# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。
+conda env update --file unilabos-[YOUR_OS].yml -n 环境名
+
+# 现阶段,需要安装 `unilabos_msgs` 包
+# 可以前往 Release 页面下载系统对应的包进行安装
+conda install ros-humble-unilabos-msgs-0.9.0-xxxxx.tar.bz2
+
+# 安装PyLabRobot等前置
+git clone https://github.com/PyLabRobot/pylabrobot plr_repo
+cd plr_repo
+pip install .[opentrons]
+```
+
+2. 安装 Uni-Lab-OS:
+
+```bash
+# 克隆仓库
+git clone https://github.com/dptech-corp/Uni-Lab-OS.git
+cd Uni-Lab-OS
+
+# 安装 Uni-Lab-OS
+pip install .
+```
+
+3. 启动 Uni-Lab 系统:
+
+请见[文档-启动样例](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
+
+## 消息格式
+
+Uni-Lab-OS 使用预构建的 `unilabos_msgs` 进行系统通信。您可以在 [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) 页面找到已构建的版本。
+
+## 许可证
+
+此项目采用 GPL-3.0 许可 - 详情请参阅 [LICENSE](LICENSE) 文件。
+
+## 项目统计
+
+### Stars 趋势
+
+
+
+
+
+## 联系我们
+
+- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
\ No newline at end of file
From bfcb214b5374973efbf0d93e2d0a6420fde264a4 Mon Sep 17 00:00:00 2001
From: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Date: Thu, 29 May 2025 20:40:16 +0800
Subject: [PATCH 2/2] 24 high level liquidhandler (#28)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* unify liquid_handler definition
* remove default values
* Dev Sync (#25)
* Update README and MQTTClient for installation instructions and code improvements
* feat: 支持local_config启动
add: 增加对crt path的说明,为传入config.py的相对路径
move: web component
* add: registry description
* add 3d visualization
* 完成在main中启动设备可视化
完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model
添加物料模型管理类,遍历物料与resource_model,完成TF数据收集
* 完成TF发布
* 修改模型方向,在yaml中添加变换属性
* 添加物料tf变化时,发送topic到前端
另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题
* 添加关节发布节点与物料可视化节点进入unilab
* 使用json启动plr与3D模型仿真
* feat: node_info_update srv
fix: OTDeck cant create
* close #12
feat: slave node registry
* feat: show machine name
fix: host node registry not uploaded
* feat: add hplc registry
* feat: add hplc registry
* fix: hplc status typo
* fix: devices/
* 完成启动OT并联动rviz
* add 3d visualization
* 完成在main中启动设备可视化
完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model
添加物料模型管理类,遍历物料与resource_model,完成TF数据收集
* 完成TF发布
* 修改模型方向,在yaml中添加变换属性
* 添加物料tf变化时,发送topic到前端
另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题
* 添加关节发布节点与物料可视化节点进入unilab
* 使用json启动plr与3D模型仿真
* 完成启动OT并联动rviz
* fix: device.class possible null
* fix: HPLC additions with online service
* fix: slave mode spin not working
* fix: slave mode spin not working
* 修复rviz位置问题,
修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法
* feat: 多ProtocolNode 允许子设备ID相同
feat: 上报发现的ActionClient
feat: Host重启动,通过discover机制要求slaveNode重新注册,实现信息及时上报
* feat: 支持env设置config
* fix: running logic
* fix: running logic
* fix: missing ot
* 在main中直接初始化republisher和物料的mesh节点
* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中
* Device visualization (#14)
* add 3d visualization
* 完成在main中启动设备可视化
完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model
添加物料模型管理类,遍历物料与resource_model,完成TF数据收集
* 完成TF发布
* 修改模型方向,在yaml中添加变换属性
* 添加物料tf变化时,发送topic到前端
另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题
* 添加关节发布节点与物料可视化节点进入unilab
* 使用json启动plr与3D模型仿真
* 完成启动OT并联动rviz
* add 3d visualization
* 完成在main中启动设备可视化
完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model
添加物料模型管理类,遍历物料与resource_model,完成TF数据收集
* 完成TF发布
* 修改模型方向,在yaml中添加变换属性
* 添加物料tf变化时,发送topic到前端
另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题
* 添加关节发布节点与物料可视化节点进入unilab
* 使用json启动plr与3D模型仿真
* 完成启动OT并联动rviz
* 修复rviz位置问题,
修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法
* fix: running logic
* fix: running logic
* fix: missing ot
* 在main中直接初始化republisher和物料的mesh节点
* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中
---------
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>
* fix: missing hostname in devices_names
fix: upload_file for model file
* fix: missing paho-mqtt package
bump version to 0.9.0
* fix startup
add ResourceCreateFromOuter.action
* fix type hint
* update actions
* update actions
* host node add_resource_from_outer
fix cmake list
* pass device config to device class
* add: bind_parent_ids to resource create action
fix: message convert string
* fix: host node should not be re_discovered
* feat: resource tracker support dict
* feat: add more necessary params
* feat: fix boolean null in registry action data
* feat: add outer resource
* 编写mesh添加action
* feat: append resource
* add action
* feat: vis 2d for plr
* fix
* fix: browser on rviz
* fix: cloud bridge error fallback to local
* fix: salve auto run rviz
* 初始化两个plate
* Device visualization (#22)
* add 3d visualization
* 完成在main中启动设备可视化
完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model
添加物料模型管理类,遍历物料与resource_model,完成TF数据收集
* 完成TF发布
* 修改模型方向,在yaml中添加变换属性
* 添加物料tf变化时,发送topic到前端
另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题
* 添加关节发布节点与物料可视化节点进入unilab
* 使用json启动plr与3D模型仿真
* 完成启动OT并联动rviz
* add 3d visualization
* 完成在main中启动设备可视化
完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model
添加物料模型管理类,遍历物料与resource_model,完成TF数据收集
* 完成TF发布
* 修改模型方向,在yaml中添加变换属性
* 添加物料tf变化时,发送topic到前端
另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题
* 添加关节发布节点与物料可视化节点进入unilab
* 使用json启动plr与3D模型仿真
* 完成启动OT并联动rviz
* 修复rviz位置问题,
修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法
* fix: running logic
* fix: running logic
* fix: missing ot
* 在main中直接初始化republisher和物料的mesh节点
* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中
* 编写mesh添加action
* add action
* fix
* fix: browser on rviz
* fix: cloud bridge error fallback to local
* fix: salve auto run rviz
* 初始化两个plate
---------
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>
* fix: multi channel
* fix: aspirate
* fix: aspirate
* fix: aspirate
* fix: aspirate
* 提交
* fix: jobadd
* fix: jobadd
* fix: msg converter
* tijiao
* add resource creat easy action
* identify debug msg
* mq client id
---------
Co-authored-by: Harvey Que
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
* remove default behavior for visualization
* change liquidhandler name
* resource create from outer easy
* add easy resource creation
* easy resource creation logic
* remove wrongly debug msg from others
* remove wrongly debug msg from others
* add missing action clients
* fix device_id
* fix slot_on_deck
* fix registry typo
* complete require packages
msg converter support array string
implements create resource logic
* 修复port输入
* fix: remove dirty actions
---------
Co-authored-by: Junhan Chang
Co-authored-by: Harvey Que
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
---
test/commands/resource_add.md | 2 +-
unilabos-linux-64.yaml | 2 +
unilabos-osx-64.yaml | 2 +
unilabos-osx-arm64.yaml | 2 +
unilabos-win64.yaml | 2 +
unilabos/app/main.py | 8 +-
unilabos/app/mq.py | 4 +-
unilabos/devices/laiyu_add_solid/__init__.py | 0
...finition.py => liquid_handler_abstract.py} | 2 +-
unilabos/registry/devices/liquid_handler.yaml | 208 ++++++++----------
unilabos/registry/registry.py | 35 ++-
.../resources/opentrons/reservoirs.yaml | 12 +-
unilabos/resources/graphio.py | 11 +-
unilabos/ros/msgs/message_converter.py | 9 +-
unilabos/ros/nodes/base_device_node.py | 45 +++-
unilabos/ros/nodes/presets/host_node.py | 54 ++++-
unilabos_msgs/CMakeLists.txt | 15 +-
.../action/DPLiquidHandlerCustomDelay.action | 6 -
.../action/DPLiquidHandlerSetTiprack.action | 5 -
.../action/DPLiquidHandlerTouchTip.action | 5 -
.../DPLiquidHandlerTransferLiquid.action | 25 ---
...dLiquid.action => LiquidHandlerAdd.action} | 0
.../action/LiquidHandlerAspirate.action | 2 +-
.../action/LiquidHandlerDispense.action | 2 +-
...dlerMix.action => LiquidHandlerMix.action} | 0
...veTo.action => LiquidHandlerMoveTo.action} | 0
...quid.action => LiquidHandlerRemove.action} | 0
.../action/LiquidHandlerTransfer.action | 29 ++-
.../action/ResourceCreateFromOuterEasy.action | 12 +
29 files changed, 276 insertions(+), 223 deletions(-)
create mode 100644 unilabos/devices/laiyu_add_solid/__init__.py
rename unilabos/devices/liquid_handling/{action_definition.py => liquid_handler_abstract.py} (99%)
delete mode 100644 unilabos_msgs/action/DPLiquidHandlerCustomDelay.action
delete mode 100644 unilabos_msgs/action/DPLiquidHandlerSetTiprack.action
delete mode 100644 unilabos_msgs/action/DPLiquidHandlerTouchTip.action
delete mode 100644 unilabos_msgs/action/DPLiquidHandlerTransferLiquid.action
rename unilabos_msgs/action/{DPLiquidHandlerAddLiquid.action => LiquidHandlerAdd.action} (100%)
rename unilabos_msgs/action/{DPLiquidHandlerMix.action => LiquidHandlerMix.action} (100%)
rename unilabos_msgs/action/{DPLiquidHandlerMoveTo.action => LiquidHandlerMoveTo.action} (100%)
rename unilabos_msgs/action/{DPLiquidHandlerRemoveLiquid.action => LiquidHandlerRemove.action} (100%)
create mode 100644 unilabos_msgs/action/ResourceCreateFromOuterEasy.action
diff --git a/test/commands/resource_add.md b/test/commands/resource_add.md
index 9d5fe38..d80e155 100644
--- a/test/commands/resource_add.md
+++ b/test/commands/resource_add.md
@@ -1,5 +1,5 @@
使用plr_test.json启动,将Well加入Plate中
```bash
-ros2 action send_goal /devices/host_node/add_resource_from_outer unilabos_msgs/action/_resource_create_from_outer/ResourceCreateFromOuter "{ resources: [ { 'category': '', 'children': [], 'config': { 'type': 'Well', 'size_x': 6.86, 'size_y': 6.86, 'size_z': 10.67, 'rotation': { 'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation' }, 'category': 'well', 'model': null, 'max_volume': 360, 'material_z_thickness': 0.5, 'compute_volume_from_height': null, 'compute_height_from_volume': null, 'bottom_type': 'flat', 'cross_section_type': 'circle' }, 'data': { 'liquids': [], 'pending_liquids': [], 'liquid_history': [] }, 'id': 'plate_well_11_7', 'name': 'plate_well_11_7', 'pose': { 'orientation': { 'w': 1.0, 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'position': { 'x': 0.0, 'y': 0.0, 'z': 0.0 } }, 'sample_id': '', 'parent': 'plate', 'type': 'device' } ], device_ids: [ 'PLR_STATION' ], bind_parent_ids: [ 'plate' ], bind_locations: [ { 'x': 0.0, 'y': 0.0, 'z': 0.0 } ], other_calling_params: [ '{}' ] }"
+ros2 action send_goal /devices/host_node/create_resource_detailed unilabos_msgs/action/_resource_create_from_outer/ResourceCreateFromOuter "{ resources: [ { 'category': '', 'children': [], 'config': { 'type': 'Well', 'size_x': 6.86, 'size_y': 6.86, 'size_z': 10.67, 'rotation': { 'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation' }, 'category': 'well', 'model': null, 'max_volume': 360, 'material_z_thickness': 0.5, 'compute_volume_from_height': null, 'compute_height_from_volume': null, 'bottom_type': 'flat', 'cross_section_type': 'circle' }, 'data': { 'liquids': [], 'pending_liquids': [], 'liquid_history': [] }, 'id': 'plate_well_11_7', 'name': 'plate_well_11_7', 'pose': { 'orientation': { 'w': 1.0, 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'position': { 'x': 0.0, 'y': 0.0, 'z': 0.0 } }, 'sample_id': '', 'parent': 'plate', 'type': 'device' } ], device_ids: [ 'PLR_STATION' ], bind_parent_ids: [ 'plate' ], bind_locations: [ { 'x': 0.0, 'y': 0.0, 'z': 0.0 } ], other_calling_params: [ '{}' ] }"
```
\ No newline at end of file
diff --git a/unilabos-linux-64.yaml b/unilabos-linux-64.yaml
index 3f5b91c..aeac636 100644
--- a/unilabos-linux-64.yaml
+++ b/unilabos-linux-64.yaml
@@ -56,6 +56,8 @@ dependencies:
- ros-humble-moveit-servo
# simulation
- ros-humble-simulation
+ - ros-humble-tf-transformations
+ - transforms3d
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments
# - ros-humble-unilabos-msgs
diff --git a/unilabos-osx-64.yaml b/unilabos-osx-64.yaml
index 38981f0..72ffb4c 100644
--- a/unilabos-osx-64.yaml
+++ b/unilabos-osx-64.yaml
@@ -56,6 +56,8 @@ dependencies:
# - ros-humble-moveit-servo
# simulation
- ros-humble-simulation
+ - ros-humble-tf-transformations
+ - transforms3d
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments
# - ros-humble-unilabos-msgs
diff --git a/unilabos-osx-arm64.yaml b/unilabos-osx-arm64.yaml
index 05333a3..83ffe1b 100644
--- a/unilabos-osx-arm64.yaml
+++ b/unilabos-osx-arm64.yaml
@@ -58,6 +58,8 @@ dependencies:
- ros-humble-moveit-servo
# simulation
- ros-humble-simulation
+ - ros-humble-tf-transformations
+ - transforms3d
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments
# - ros-humble-unilabos-msgs
diff --git a/unilabos-win64.yaml b/unilabos-win64.yaml
index 2e26fa3..19a4008 100644
--- a/unilabos-win64.yaml
+++ b/unilabos-win64.yaml
@@ -56,6 +56,8 @@ dependencies:
- ros-humble-moveit-servo
# simulation
- ros-humble-simulation # ignored because of NO python3.11 package in WIN64
+ - ros-humble-tf-transformations
+ - transforms3d
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments
# ros-humble-unilabos-msgs
diff --git a/unilabos/app/main.py b/unilabos/app/main.py
index ebee015..0db290a 100644
--- a/unilabos/app/main.py
+++ b/unilabos/app/main.py
@@ -18,7 +18,6 @@ if unilabos_dir not in sys.path:
from unilabos.config.config import load_config, BasicConfig, _update_config_from_env
from unilabos.utils.banner_print import print_status, print_unilab_banner
-from unilabos.device_mesh.resource_visalization import ResourceVisualization
def parse_args():
@@ -188,11 +187,12 @@ def main():
if args_dict["visual"] != "disable":
enable_rviz = args_dict["visual"] == "rviz"
if devices_and_resources is not None:
+ from unilabos.device_mesh.resource_visalization import ResourceVisualization # 此处开启后,logger会变更为INFO,有需要请调整
resource_visualization = ResourceVisualization(devices_and_resources, args_dict["resources_config"] ,enable_rviz=enable_rviz)
args_dict["resources_mesh_config"] = resource_visualization.resource_model
start_backend(**args_dict)
server_thread = threading.Thread(target=start_server, kwargs=dict(
- open_browser=not args_dict["disable_browser"]
+ open_browser=not args_dict["disable_browser"], port=args_dict["port"],
))
server_thread.start()
asyncio.set_event_loop(asyncio.new_event_loop())
@@ -201,10 +201,10 @@ def main():
time.sleep(1)
else:
start_backend(**args_dict)
- start_server(open_browser=not args_dict["disable_browser"])
+ start_server(open_browser=not args_dict["disable_browser"], port=args_dict["port"],)
else:
start_backend(**args_dict)
- start_server(open_browser=not args_dict["disable_browser"])
+ start_server(open_browser=not args_dict["disable_browser"], port=args_dict["port"],)
if __name__ == "__main__":
diff --git a/unilabos/app/mq.py b/unilabos/app/mq.py
index 018c65c..0bac96f 100644
--- a/unilabos/app/mq.py
+++ b/unilabos/app/mq.py
@@ -26,6 +26,7 @@ class MQTTClient:
def __init__(self):
self.mqtt_disable = not MQConfig.lab_id
self.client_id = f"{MQConfig.group_id}@@@{MQConfig.lab_id}{uuid.uuid4()}"
+ logger.info("[MQTT] Client_id: " + self.client_id)
self.client = mqtt.Client(CallbackAPIVersion.VERSION2, client_id=self.client_id, protocol=mqtt.MQTTv5)
self._setup_callbacks()
@@ -52,10 +53,7 @@ class MQTTClient:
try:
payload_str = msg.payload.decode("utf-8")
payload_json = json.loads(payload_str)
- logger.debug(f"Topic: {msg.topic}")
- logger.debug("Payload:", json.dumps(payload_json, indent=2, ensure_ascii=False))
if msg.topic == f"labs/{MQConfig.lab_id}/job/start/":
- logger.debug("job_add", type(payload_json), payload_json)
if "data" not in payload_json:
payload_json["data"] = {}
if "action" in payload_json:
diff --git a/unilabos/devices/laiyu_add_solid/__init__.py b/unilabos/devices/laiyu_add_solid/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/unilabos/devices/liquid_handling/action_definition.py b/unilabos/devices/liquid_handling/liquid_handler_abstract.py
similarity index 99%
rename from unilabos/devices/liquid_handling/action_definition.py
rename to unilabos/devices/liquid_handling/liquid_handler_abstract.py
index 530703a..c349403 100644
--- a/unilabos/devices/liquid_handling/action_definition.py
+++ b/unilabos/devices/liquid_handling/liquid_handler_abstract.py
@@ -14,7 +14,7 @@ from pylabrobot.resources import (
Well
)
-class DPLiquidHandler(LiquidHandler):
+class LiquidHandlerAbstract(LiquidHandler):
"""Extended LiquidHandler with additional operations."""
# ---------------------------------------------------------------
diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml
index ba921c7..bcddae5 100644
--- a/unilabos/registry/devices/liquid_handler.yaml
+++ b/unilabos/registry/devices/liquid_handler.yaml
@@ -1,11 +1,96 @@
liquid_handler:
description: Liquid handler device controlled by pylabrobot
class:
- module: pylabrobot.liquid_handling:LiquidHandler
+ module: unilabos.devices.liquid_handling.liquid_handler_abstract:LiquidHandlerAbstract
type: python
status_types:
name: String
action_value_mappings:
+ remove:
+ type: LiquidHandlerRemove
+ goal:
+ vols: vols
+ sources: sources
+ waste_liquid: waste_liquid
+ use_channels: use_channels
+ flow_rates: flow_rates
+ offsets: offsets
+ liquid_height: liquid_height
+ blow_out_air_volume: blow_out_air_volume
+ spread: spread
+ delays: delays
+ is_96_well: is_96_well
+ top: top
+ none_keys: none_keys
+ feedback: { }
+ result: { }
+ add_liquid:
+ type: LiquidHandlerAdd
+ goal:
+ asp_vols: asp_vols
+ dis_vols: dis_vols
+ reagent_sources: reagent_sources
+ targets: targets
+ use_channels: use_channels
+ flow_rates: flow_rates
+ offsets: offsets
+ liquid_height: liquid_height
+ blow_out_air_volume: blow_out_air_volume
+ spread: spread
+ is_96_well: is_96_well
+ mix_time: mix_time
+ mix_vol: mix_vol
+ mix_rate: mix_rate
+ mix_liquid_height: mix_liquid_height
+ none_keys: none_keys
+ feedback: { }
+ result: { }
+ transfer_liquid:
+ type: LiquidHandlerTransfer
+ goal:
+ asp_vols: asp_vols
+ dis_vols: dis_vols
+ sources: sources
+ targets: targets
+ tip_racks: tip_racks
+ use_channels: use_channels
+ asp_flow_rates: asp_flow_rates
+ dis_flow_rates: dis_flow_rates
+ offsets: offsets
+ touch_tip: touch_tip
+ liquid_height: liquid_height
+ blow_out_air_volume: blow_out_air_volume
+ spread: spread
+ is_96_well: is_96_well
+ mix_stage: mix_stage
+ mix_times: mix_times
+ mix_vol: mix_vol
+ mix_rate: mix_rate
+ mix_liquid_height: mix_liquid_height
+ delays: delays
+ none_keys: none_keys
+ feedback: { }
+ result: { }
+ mix:
+ type: LiquidHandlerMix
+ goal:
+ targets: targets
+ mix_time: mix_time
+ mix_vol: mix_vol
+ height_to_bottom: height_to_bottom
+ offsets: offsets
+ mix_rate: mix_rate
+ none_keys: none_keys
+ feedback: { }
+ result: { }
+ move_to:
+ type: LiquidHandlerMoveTo
+ goal:
+ well: well
+ dis_to_top: dis_to_top
+ channel: channel
+ feedback: { }
+ result: { }
aspirate:
type: LiquidHandlerAspirate
goal:
@@ -170,127 +255,6 @@ liquid_handler:
- name
additionalProperties: false
-dp_liquid_handler:
- description: 通用液体处理
- class:
- module: unilabos.devices.liquid_handling.action_definition:DPLiquidHandler
- type: python
- status_types:
- status: String
- action_value_mappings:
- remove_liquid:
- type: DPLiquidHandlerRemoveLiquid
- goal:
- vols: vols
- sources: sources
- waste_liquid: waste_liquid
- use_channels: use_channels
- flow_rates: flow_rates
- offsets: offsets
- liquid_height: liquid_height
- blow_out_air_volume: blow_out_air_volume
- spread: spread
- delays: delays
- is_96_well: is_96_well
- top: top
- none_keys: none_keys
- feedback: {}
- result: {}
- add_liquid:
- type: DPLiquidHandlerAddLiquid
- goal:
- asp_vols: asp_vols
- dis_vols: dis_vols
- reagent_sources: reagent_sources
- targets: targets
- use_channels: use_channels
- flow_rates: flow_rates
- offsets: offsets
- liquid_height: liquid_height
- blow_out_air_volume: blow_out_air_volume
- spread: spread
- is_96_well: is_96_well
- mix_time: mix_time
- mix_vol: mix_vol
- mix_rate: mix_rate
- mix_liquid_height: mix_liquid_height
- none_keys: none_keys
- feedback: {}
- result: {}
- transfer_liquid:
- type: DPLiquidHandlerTransferLiquid
- goal:
- asp_vols: asp_vols
- dis_vols: dis_vols
- sources: sources
- targets: targets
- tip_racks: tip_racks
- use_channels: use_channels
- asp_flow_rates: asp_flow_rates
- dis_flow_rates: dis_flow_rates
- offsets: offsets
- touch_tip: touch_tip
- liquid_height: liquid_height
- blow_out_air_volume: blow_out_air_volume
- spread: spread
- is_96_well: is_96_well
- mix_stage: mix_stage
- mix_times: mix_times
- mix_vol: mix_vol
- mix_rate: mix_rate
- mix_liquid_height: mix_liquid_height
- delays: delays
- none_keys: none_keys
- feedback: {}
- result: {}
- custom_delay:
- type: DPLiquidHandlerCustomDelay
- goal:
- seconds: seconds
- msg: msg
- feedback: {}
- result: {}
- touch_tip:
- type: DPLiquidHandlerTouchTip
- goal:
- targets: targets
- feedback: {}
- result: {}
- mix:
- type: DPLiquidHandlerMix
- goal:
- targets: targets
- mix_time: mix_time
- mix_vol: mix_vol
- height_to_bottom: height_to_bottom
- offsets: offsets
- mix_rate: mix_rate
- none_keys: none_keys
- feedback: {}
- result: {}
- set_tiprack:
- type: DPLiquidHandlerSetTiprack
- goal:
- tip_racks: tip_racks
- feedback: {}
- result: {}
- move_to:
- type: DPLiquidHandlerMoveTo
- goal:
- well: well
- dis_to_top: dis_to_top
- channel: channel
- feedback: {}
- result: {}
- schema:
- type: object
- properties:
- name:
- type: string
- description: 物料名
- required:
- - name
-
liquid_handler.revvity:
class:
module: unilabos.devices.liquid_handling.revvity:Revvity
diff --git a/unilabos/registry/registry.py b/unilabos/registry/registry.py
index 6381e02..91789b7 100644
--- a/unilabos/registry/registry.py
+++ b/unilabos/registry/registry.py
@@ -1,5 +1,4 @@
import io
-import json
import os
import sys
from pathlib import Path
@@ -7,10 +6,9 @@ from typing import Any
import yaml
-from unilabos.utils import logger
from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema
+from unilabos.utils import logger
from unilabos.utils.decorator import singleton
-from unilabos.utils.type_check import TypeEncoder
DEFAULT_PATHS = [Path(__file__).absolute().parent]
@@ -21,10 +19,12 @@ class Registry:
self.registry_paths = DEFAULT_PATHS.copy() # 使用copy避免修改默认值
if registry_paths:
self.registry_paths.extend(registry_paths)
- action_type = self._replace_type_with_class(
- "ResourceCreateFromOuter", "host_node", f"动作 add_resource_from_outer"
+ self.ResourceCreateFromOuter = self._replace_type_with_class(
+ "ResourceCreateFromOuter", "host_node", f"动作 create_resource_detailed"
+ )
+ self.ResourceCreateFromOuterEasy = self._replace_type_with_class(
+ "ResourceCreateFromOuterEasy", "host_node", f"动作 create_resource"
)
- schema = ros_action_to_json_schema(action_type)
self.device_type_registry = {
"host_node": {
"description": "UniLabOS主机节点",
@@ -33,7 +33,7 @@ class Registry:
"type": "python",
"status_types": {},
"action_value_mappings": {
- "add_resource_from_outer": {
+ "create_resource_detailed": {
"type": msg_converter_manager.search_class("ResourceCreateFromOuter"),
"goal": {
"resources": "resources",
@@ -46,7 +46,26 @@ class Registry:
"result": {
"success": "success"
},
- "schema": schema
+ "schema": ros_action_to_json_schema(self.ResourceCreateFromOuter)
+ },
+ "create_resource": {
+ "type": msg_converter_manager.search_class("ResourceCreateFromOuterEasy"),
+ "goal": {
+ "res_id": "res_id",
+ "class_name": "class_name",
+ "parent": "parent",
+ "device_id": "device_id",
+ "bind_locations": "bind_locations",
+ "liquid_input_slot": "liquid_input_slot[]",
+ "liquid_type": "liquid_type[]",
+ "liquid_volume": "liquid_volume[]",
+ "slot_on_deck": "slot_on_deck",
+ },
+ "feedback": {},
+ "result": {
+ "success": "success"
+ },
+ "schema": ros_action_to_json_schema(self.ResourceCreateFromOuterEasy)
}
}
},
diff --git a/unilabos/registry/resources/opentrons/reservoirs.yaml b/unilabos/registry/resources/opentrons/reservoirs.yaml
index f966f0b..fbc8490 100644
--- a/unilabos/registry/resources/opentrons/reservoirs.yaml
+++ b/unilabos/registry/resources/opentrons/reservoirs.yaml
@@ -1,35 +1,35 @@
agilent_1_reservoir_290ml:
description: Agilent 1 reservoir 290ml
class:
- module: pylabrobot.resources.opentrons.reserviors:agilent_1_reservoir_290ml
+ module: pylabrobot.resources.opentrons.reservoirs:agilent_1_reservoir_290ml
type: pylabrobot
axygen_1_reservoir_90ml:
description: Axygen 1 reservoir 90ml
class:
- module: pylabrobot.resources.opentrons.reserviors:axygen_1_reservoir_90ml
+ module: pylabrobot.resources.opentrons.reservoirs:axygen_1_reservoir_90ml
type: pylabrobot
nest_12_reservoir_15ml:
description: Nest 12 reservoir 15ml
class:
- module: pylabrobot.resources.opentrons.reserviors:nest_12_reservoir_15ml
+ module: pylabrobot.resources.opentrons.reservoirs:nest_12_reservoir_15ml
type: pylabrobot
nest_1_reservoir_195ml:
description: Nest 1 reservoir 195ml
class:
- module: pylabrobot.resources.opentrons.reserviors:nest_1_reservoir_195ml
+ module: pylabrobot.resources.opentrons.reservoirs:nest_1_reservoir_195ml
type: pylabrobot
nest_1_reservoir_290ml:
description: Nest 1 reservoir 290ml
class:
- module: pylabrobot.resources.opentrons.reserviors:nest_1_reservoir_290ml
+ module: pylabrobot.resources.opentrons.reservoirs:nest_1_reservoir_290ml
type: pylabrobot
usascientific_12_reservoir_22ml:
description: USAScientific 12 reservoir 22ml
class:
- module: pylabrobot.resources.opentrons.reserviors:usascientific_12_reservoir_22ml
+ module: pylabrobot.resources.opentrons.reservoirs:usascientific_12_reservoir_22ml
type: pylabrobot
diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py
index 9621b5d..9a31add 100644
--- a/unilabos/resources/graphio.py
+++ b/unilabos/resources/graphio.py
@@ -189,6 +189,7 @@ def dict_from_graph(graph: nx.Graph) -> dict:
def dict_to_tree(nodes: dict, devices_only: bool = False) -> list[dict]:
# 将节点转换为字典,以便通过 ID 快速查找
nodes_list = [node for node in nodes.values() if node.get("type") == "device" or not devices_only]
+ id_list = [node["id"] for node in nodes_list]
# 初始化每个节点的 children 为包含节点字典的列表
for node in nodes_list:
@@ -196,7 +197,7 @@ def dict_to_tree(nodes: dict, devices_only: bool = False) -> list[dict]:
# 找到根节点并返回
root_nodes = [
- node for node in nodes_list if len(nodes_list) == 1 or node.get("parent", node.get("parent_name")) in [None, "", "None", np.nan]
+ node for node in nodes_list if len(nodes_list) == 1 or node.get("parent", node.get("parent_name")) in [None, "", "None", np.nan] or node.get("parent", node.get("parent_name")) not in id_list
]
# 如果存在多个根节点,返回所有根节点
@@ -421,7 +422,7 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None):
return r
-def initialize_resource(resource_config: dict, lab_registry: dict) -> list[dict]:
+def initialize_resource(resource_config: dict) -> list[dict]:
"""Initializes a resource based on its configuration.
If the config is detailed, then do nothing;
@@ -433,6 +434,7 @@ def initialize_resource(resource_config: dict, lab_registry: dict) -> list[dict]
Returns:
None
"""
+ from unilabos.registry.registry import lab_registry
resource_class_config = resource_config.get("class", None)
if resource_class_config is None:
return [resource_config]
@@ -476,11 +478,8 @@ def initialize_resources(resources_config) -> list[dict]:
None
"""
- from unilabos.registry.registry import lab_registry
resources = []
for resource_config in resources_config:
- if resource_config["parent"] == "tip_rack" or resource_config["parent"] == "plate_well":
- continue
- resources.extend(initialize_resource(resource_config, lab_registry))
+ resources.extend(initialize_resource(resource_config))
return resources
diff --git a/unilabos/ros/msgs/message_converter.py b/unilabos/ros/msgs/message_converter.py
index 4f85a11..11c7afd 100644
--- a/unilabos/ros/msgs/message_converter.py
+++ b/unilabos/ros/msgs/message_converter.py
@@ -348,10 +348,16 @@ def convert_to_ros_msg(ros_msg_type: Union[Type, Any], obj: Any) -> Any:
if isinstance(td, NamespacedType):
target_class = msg_converter_manager.get_class(f"{'.'.join(td.namespaces)}.{td.name}")
setattr(ros_msg, key, [convert_to_ros_msg(target_class, v) for v in value])
+ elif isinstance(td, UnboundedString):
+ setattr(ros_msg, key, value)
else:
+ logger.warning(f"Not Supported type: {td}")
setattr(ros_msg, key, []) # FIXME
elif "array.array" in str(type(attr)):
- setattr(ros_msg, key, value)
+ if attr.typecode == "f":
+ setattr(ros_msg, key, [float(i) for i in value])
+ else:
+ setattr(ros_msg, key, value)
else:
nested_ros_msg = convert_to_ros_msg(type(attr)(), value)
setattr(ros_msg, key, nested_ros_msg)
@@ -574,6 +580,7 @@ basic_type_map = {
'int64': {'type': 'integer'},
'uint64': {'type': 'integer', 'minimum': 0},
'double': {'type': 'number'},
+ 'float': {'type': 'number'},
'float32': {'type': 'number'},
'float64': {'type': 'number'},
'string': {'type': 'string'},
diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py
index f79dddc..28b67aa 100644
--- a/unilabos/ros/nodes/base_device_node.py
+++ b/unilabos/ros/nodes/base_device_node.py
@@ -1,3 +1,4 @@
+import copy
import functools
import json
import threading
@@ -20,7 +21,7 @@ from unilabos_msgs.action import SendCmd
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
from unilabos.resources.graphio import convert_resources_to_type, convert_resources_from_type, resource_ulab_to_plr, \
- initialize_resources
+ initialize_resources, list_to_nested_dict, dict_to_tree, resource_plr_to_ulab, tree_to_list
from unilabos.ros.msgs.message_converter import (
convert_to_ros_msg,
convert_from_ros_msg,
@@ -312,7 +313,10 @@ class BaseROS2DeviceNode(Node, Generic[T]):
# 物料传输到对应的node节点
rclient = self.create_client(ResourceAdd, "/resources/add")
rclient.wait_for_service()
+ rclient2 = self.create_client(ResourceAdd, "/resources/add")
+ rclient2.wait_for_service()
request = ResourceAdd.Request()
+ request2 = ResourceAdd.Request()
command_json = json.loads(req.command)
namespace = command_json["namespace"]
bind_parent_id = command_json["bind_parent_id"]
@@ -321,11 +325,23 @@ class BaseROS2DeviceNode(Node, Generic[T]):
other_calling_param = command_json["other_calling_param"]
resources = command_json["resource"]
initialize_full = other_calling_param.pop("initialize_full", False)
+ # 用来增加液体
+ ADD_LIQUID_TYPE = other_calling_param.pop("ADD_LIQUID_TYPE", [])
+ LIQUID_VOLUME = other_calling_param.pop("LIQUID_VOLUME", [])
+ LIQUID_INPUT_SLOT = other_calling_param.pop("LIQUID_INPUT_SLOT", [])
+ slot = other_calling_param.pop("slot", -1)
+ if slot >= 0: # slot为负数的时候采用assign方法
+ other_calling_param["slot"] = slot
# 本地拿到这个物料,可能需要先做初始化?
if isinstance(resources, list):
- if initialize_full:
+ if len(resources) == 1 and isinstance(resources[0], list) and not initialize_full: # 取消,不存在的情况
+ # 预先initialize过,以整组的形式传入
+ request.resources = [convert_to_ros_msg(Resource, resource_) for resource_ in resources[0]]
+ elif initialize_full:
resources = initialize_resources(resources)
- request.resources = [convert_to_ros_msg(Resource, resource) for resource in resources]
+ request.resources = [convert_to_ros_msg(Resource, resource) for resource in resources]
+ else:
+ request.resources = [convert_to_ros_msg(Resource, resource) for resource in resources]
else:
if initialize_full:
resources = initialize_resources([resources])
@@ -335,20 +351,31 @@ class BaseROS2DeviceNode(Node, Generic[T]):
res.response = "OK"
# 接下来该根据bind_parent_id进行assign了,目前只有plr可以进行assign,不然没有办法输入到物料系统中
resource = self.resource_tracker.figure_resource({"name": bind_parent_id})
- request.resources = [convert_to_ros_msg(Resource, resources)]
+ # request.resources = [convert_to_ros_msg(Resource, resources)]
try:
from pylabrobot.resources.resource import Resource as ResourcePLR
from pylabrobot.resources.deck import Deck
from pylabrobot.resources import Coordinate
from pylabrobot.resources import OTDeck
+ from pylabrobot.resources import Plate
contain_model = not isinstance(resource, Deck)
if isinstance(resource, ResourcePLR):
# resources.list()
- plr_instance = resource_ulab_to_plr(resources, contain_model)
+ resources_tree = dict_to_tree(copy.deepcopy({r["id"]: r for r in resources}))
+ plr_instance = resource_ulab_to_plr(resources_tree[0], contain_model)
+ if isinstance(plr_instance, Plate):
+ empty_liquid_info_in = [(None, 0)] * plr_instance.num_items
+ for liquid_type, liquid_volume, liquid_input_slot in zip(ADD_LIQUID_TYPE, LIQUID_VOLUME, LIQUID_INPUT_SLOT):
+ empty_liquid_info_in[liquid_input_slot] = (liquid_type, liquid_volume)
+ plr_instance.set_well_liquids(empty_liquid_info_in)
if isinstance(resource, OTDeck) and "slot" in other_calling_param:
resource.assign_child_at_slot(plr_instance, **other_calling_param)
- resource.assign_child_resource(plr_instance, Coordinate(location["x"], location["y"], location["z"]), **other_calling_param)
+ else:
+ _discard_slot = other_calling_param.pop("slot", -1)
+ resource.assign_child_resource(plr_instance, Coordinate(location["x"], location["y"], location["z"]), **other_calling_param)
+ request2.resources = [convert_to_ros_msg(Resource, r) for r in tree_to_list([resource_plr_to_ulab(resource)])]
+ rclient2.call(request2)
# 发送给ResourceMeshManager
action_client = ActionClient(
self, SendCmd, "/devices/resource_mesh_manager/add_resource_mesh", callback_group=self.callback_group
@@ -526,7 +553,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
action_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
self.lab_logger().debug(f"接收到原始目标: {action_kwargs}")
# 向Host查询物料当前状态,如果是host本身的增加物料的请求,则直接跳过
- if action_name != "add_resource_from_outer":
+ if action_name not in ["create_resource_detailed", "create_resource"]:
for k, v in goal.get_fields_and_field_types().items():
if v in ["unilabos_msgs/Resource", "sequence"]:
self.lab_logger().info(f"查询资源状态: Key: {k} Type: {v}")
@@ -625,7 +652,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
del future
# 向Host更新物料当前状态
- if action_name != "add_resource_from_outer":
+ if action_name not in ["create_resource_detailed", "create_resource"]:
for k, v in goal.get_fields_and_field_types().items():
if v not in ["unilabos_msgs/Resource", "sequence"]:
continue
@@ -764,7 +791,7 @@ class ROS2DeviceNode:
self.resource_tracker = DeviceNodeResourceTracker()
# use_pylabrobot_creator 使用 cls的包路径检测
- use_pylabrobot_creator = driver_class.__module__.startswith("pylabrobot") or driver_class.__name__ == "DPLiquidHandler"
+ use_pylabrobot_creator = driver_class.__module__.startswith("pylabrobot") or driver_class.__name__ == "LiquidHandlerAbstract"
# TODO: 要在创建之前预先请求服务器是否有当前id的物料,放到resource_tracker中,让pylabrobot进行创建
# 创建设备类实例
diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py
index cb1e1c6..a8a3299 100644
--- a/unilabos/ros/nodes/presets/host_node.py
+++ b/unilabos/ros/nodes/presets/host_node.py
@@ -17,6 +17,7 @@ from unilabos_msgs.srv import ResourceAdd, ResourceGet, ResourceDelete, Resource
from unique_identifier_msgs.msg import UUID
from unilabos.registry.registry import lab_registry
+from unilabos.resources.graphio import initialize_resource
from unilabos.resources.registry import add_schema
from unilabos.ros.initialize_device import initialize_device_from_dict
from unilabos.ros.msgs.message_converter import (
@@ -100,7 +101,14 @@ class HostNode(BaseROS2DeviceNode):
self.devices_names: Dict[str, str] = {device_id: self.namespace} # 存储设备名称和命名空间的映射
self.devices_instances: Dict[str, ROS2DeviceNode] = {} # 存储设备实例
self.device_machine_names: Dict[str, str] = {device_id: "本地", } # 存储设备ID到机器名称的映射
- self._action_clients: Dict[str, ActionClient] = {} # 用来存储多个ActionClient实例
+ self._action_clients: Dict[str, ActionClient] = { # 为了方便了解实际的数据类型,host的默认写好
+ "/devices/host_node/create_resource": ActionClient(
+ self, lab_registry.ResourceCreateFromOuterEasy, "/devices/host_node/create_resource", callback_group=self.callback_group
+ ),
+ "/devices/host_node/create_resource_detailed": ActionClient(
+ self, lab_registry.ResourceCreateFromOuter, "/devices/host_node/create_resource_detailed", callback_group=self.callback_group
+ )
+ } # 用来存储多个ActionClient实例
self._action_value_mappings: Dict[str, Dict] = {} # 用来存储多个ActionClient的type, goal, feedback, result的变量名映射关系
self._goals: Dict[str, Any] = {} # 用来存储多个目标的状态
self._online_devices: Set[str] = {f"{self.namespace}/{device_id}"} # 用于跟踪在线设备
@@ -141,6 +149,21 @@ class HostNode(BaseROS2DeviceNode):
].items():
controller_config["update_rate"] = update_rate
self.initialize_controller(controller_id, controller_config)
+ resources_config.insert(0, {
+ "id": "host_node",
+ "name": "host_node",
+ "parent": None,
+ "type": "device",
+ "class": "host_node",
+ "position": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "config": {},
+ "data": {},
+ "children": []
+ })
resource_with_parent_name = []
resource_ids_to_instance = {i["id"]: i for i in resources_config}
for res in resources_config:
@@ -278,7 +301,7 @@ class HostNode(BaseROS2DeviceNode):
except Exception as e:
self.lab_logger().error(f"[Host Node] Failed to create ActionClient for {action_id}: {str(e)}")
- def add_resource_from_outer(self, resources: list["Resource"], device_ids: list[str], bind_parent_ids: list[str], bind_locations: list[Point], other_calling_params: list[str]):
+ def create_resource_detailed(self, resources: list["Resource"], device_ids: list[str], bind_parent_ids: list[str], bind_locations: list[Point], other_calling_params: list[str]):
for resource, device_id, bind_parent_id, bind_location, other_calling_param in zip(resources, device_ids, bind_parent_ids, bind_locations, other_calling_params):
# 这里要求device_id传入必须是edge_device_id
namespace = "/devices/" + device_id
@@ -287,7 +310,7 @@ class HostNode(BaseROS2DeviceNode):
sclient.wait_for_service()
request = SerialCommand.Request()
request.command = json.dumps({
- "resource": resource,
+ "resource": resource, # 单个/单组 可为 list[list[Resource]]
"namespace": namespace,
"edge_device_id": device_id,
"bind_parent_id": bind_parent_id,
@@ -302,6 +325,31 @@ class HostNode(BaseROS2DeviceNode):
pass
pass
+ def create_resource(self, device_id: str, res_id: str, class_name: str, parent: str, bind_locations: Point, liquid_input_slot: list[int], liquid_type: list[str], liquid_volume: list[int], slot_on_deck: int):
+ init_new_res = initialize_resource({
+ "name": res_id,
+ "class": class_name,
+ "parent": parent,
+ "position": {
+ "x": bind_locations.x,
+ "y": bind_locations.y,
+ "z": bind_locations.z,
+ }
+ }) # flatten的格式
+ resources = [init_new_res]
+ device_id = [device_id]
+ bind_parent_id = [parent]
+ bind_location = [bind_locations]
+ other_calling_param = [json.dumps({
+ "ADD_LIQUID_TYPE": liquid_type,
+ "LIQUID_VOLUME": liquid_volume,
+ "LIQUID_INPUT_SLOT": liquid_input_slot,
+ "initialize_full": False,
+ "slot": slot_on_deck
+ })]
+
+ return self.create_resource_detailed(resources, device_id, bind_parent_id, bind_location, other_calling_param)
+
def initialize_device(self, device_id: str, device_config: Dict[str, Any]) -> None:
"""
根据配置初始化设备,
diff --git a/unilabos_msgs/CMakeLists.txt b/unilabos_msgs/CMakeLists.txt
index 69fbaa3..1ca6722 100644
--- a/unilabos_msgs/CMakeLists.txt
+++ b/unilabos_msgs/CMakeLists.txt
@@ -43,14 +43,10 @@ set(action_files
"action/LiquidHandlerStamp.action"
"action/LiquidHandlerTransfer.action"
- "action/DPLiquidHandlerAddLiquid.action"
- "action/DPLiquidHandlerCustomDelay.action"
- "action/DPLiquidHandlerMix.action"
- "action/DPLiquidHandlerMoveTo.action"
- "action/DPLiquidHandlerRemoveLiquid.action"
- "action/DPLiquidHandlerSetTiprack.action"
- "action/DPLiquidHandlerTouchTip.action"
- "action/DPLiquidHandlerTransferLiquid.action"
+ "action/LiquidHandlerAdd.action"
+ "action/LiquidHandlerMix.action"
+ "action/LiquidHandlerMoveTo.action"
+ "action/LiquidHandlerRemove.action"
"action/EmptyIn.action"
"action/FloatSingleInput.action"
@@ -59,9 +55,10 @@ set(action_files
"action/Point3DSeparateInput.action"
"action/ResourceCreateFromOuter.action"
+ "action/ResourceCreateFromOuterEasy.action"
"action/SolidDispenseAddPowderTube.action"
-
+
"action/PumpTransfer.action"
"action/Clean.action"
"action/Separate.action"
diff --git a/unilabos_msgs/action/DPLiquidHandlerCustomDelay.action b/unilabos_msgs/action/DPLiquidHandlerCustomDelay.action
deleted file mode 100644
index 29f9b45..0000000
--- a/unilabos_msgs/action/DPLiquidHandlerCustomDelay.action
+++ /dev/null
@@ -1,6 +0,0 @@
-float64 seconds
-string msg
----
-bool success
----
-# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/DPLiquidHandlerSetTiprack.action b/unilabos_msgs/action/DPLiquidHandlerSetTiprack.action
deleted file mode 100644
index 437d3e3..0000000
--- a/unilabos_msgs/action/DPLiquidHandlerSetTiprack.action
+++ /dev/null
@@ -1,5 +0,0 @@
-Resource[] tip_racks
----
-bool success
----
-# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/DPLiquidHandlerTouchTip.action b/unilabos_msgs/action/DPLiquidHandlerTouchTip.action
deleted file mode 100644
index e0c3104..0000000
--- a/unilabos_msgs/action/DPLiquidHandlerTouchTip.action
+++ /dev/null
@@ -1,5 +0,0 @@
-Resource[] targets
----
-bool success
----
-# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/DPLiquidHandlerTransferLiquid.action b/unilabos_msgs/action/DPLiquidHandlerTransferLiquid.action
deleted file mode 100644
index 39df59b..0000000
--- a/unilabos_msgs/action/DPLiquidHandlerTransferLiquid.action
+++ /dev/null
@@ -1,25 +0,0 @@
-float64[] asp_vols
-float64[] dis_vols
-Resource[] sources
-Resource[] targets
-Resource[] tip_racks
-int32[] use_channels
-float64[] asp_flow_rates
-float64[] dis_flow_rates
-geometry_msgs/Point[] offsets
-bool touch_tip
-float64[] liquid_height
-float64[] blow_out_air_volume
-string spread
-bool is_96_well
-string mix_stage
-int32[] mix_times
-int32 mix_vol
-int32 mix_rate
-float64 mix_liquid_height
-int32[] delays
-string[] none_keys
----
-bool success
----
-# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/DPLiquidHandlerAddLiquid.action b/unilabos_msgs/action/LiquidHandlerAdd.action
similarity index 100%
rename from unilabos_msgs/action/DPLiquidHandlerAddLiquid.action
rename to unilabos_msgs/action/LiquidHandlerAdd.action
diff --git a/unilabos_msgs/action/LiquidHandlerAspirate.action b/unilabos_msgs/action/LiquidHandlerAspirate.action
index f03ad07..9ba1706 100644
--- a/unilabos_msgs/action/LiquidHandlerAspirate.action
+++ b/unilabos_msgs/action/LiquidHandlerAspirate.action
@@ -5,7 +5,7 @@ float64[] flow_rates
geometry_msgs/Point[] offsets
float64[] liquid_height
float64[] blow_out_air_volume
-string spread="wide"
+string spread
---
bool success
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerDispense.action b/unilabos_msgs/action/LiquidHandlerDispense.action
index ba5360a..73c4d0f 100644
--- a/unilabos_msgs/action/LiquidHandlerDispense.action
+++ b/unilabos_msgs/action/LiquidHandlerDispense.action
@@ -5,7 +5,7 @@ int32[] use_channels
float64[] flow_rates
geometry_msgs/Point[] offsets
int32[] blow_out_air_volume
-string spread="wide"
+string spread
---
# 结果字段
bool success
diff --git a/unilabos_msgs/action/DPLiquidHandlerMix.action b/unilabos_msgs/action/LiquidHandlerMix.action
similarity index 100%
rename from unilabos_msgs/action/DPLiquidHandlerMix.action
rename to unilabos_msgs/action/LiquidHandlerMix.action
diff --git a/unilabos_msgs/action/DPLiquidHandlerMoveTo.action b/unilabos_msgs/action/LiquidHandlerMoveTo.action
similarity index 100%
rename from unilabos_msgs/action/DPLiquidHandlerMoveTo.action
rename to unilabos_msgs/action/LiquidHandlerMoveTo.action
diff --git a/unilabos_msgs/action/DPLiquidHandlerRemoveLiquid.action b/unilabos_msgs/action/LiquidHandlerRemove.action
similarity index 100%
rename from unilabos_msgs/action/DPLiquidHandlerRemoveLiquid.action
rename to unilabos_msgs/action/LiquidHandlerRemove.action
diff --git a/unilabos_msgs/action/LiquidHandlerTransfer.action b/unilabos_msgs/action/LiquidHandlerTransfer.action
index b6e3be3..d815a6c 100644
--- a/unilabos_msgs/action/LiquidHandlerTransfer.action
+++ b/unilabos_msgs/action/LiquidHandlerTransfer.action
@@ -1,11 +1,26 @@
# Bio
-Resource source
+float64[] asp_vols
+float64[] dis_vols
+Resource[] sources
Resource[] targets
-float64 source_vol
-float64[] ratios
-float64[] target_vols
-float64 aspiration_flow_rate
-float64[] dispense_flow_rates
+Resource[] tip_racks
+int32[] use_channels
+float64[] asp_flow_rates
+float64[] dis_flow_rates
+geometry_msgs/Point[] offsets
+bool touch_tip
+float64[] liquid_height
+float64[] blow_out_air_volume
+string spread
+bool is_96_well
+string mix_stage
+int32[] mix_times
+int32 mix_vol
+int32 mix_rate
+float64 mix_liquid_height
+int32[] delays
+string[] none_keys
---
bool success
----
\ No newline at end of file
+---
+# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/ResourceCreateFromOuterEasy.action b/unilabos_msgs/action/ResourceCreateFromOuterEasy.action
new file mode 100644
index 0000000..cc832a7
--- /dev/null
+++ b/unilabos_msgs/action/ResourceCreateFromOuterEasy.action
@@ -0,0 +1,12 @@
+string res_id
+string device_id
+string class_name
+string parent
+geometry_msgs/Point bind_locations
+int32[] liquid_input_slot
+string[] liquid_type
+float32[] liquid_volume
+int32 slot_on_deck
+---
+bool success
+---