mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-07 07:25:15 +00:00
Compare commits
3 Commits
v0.9.0
...
ec4e6c6cfd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec4e6c6cfd | ||
|
|
53b6457a88 | ||
|
|
133dbf77bb |
67
README.md
67
README.md
@@ -4,83 +4,86 @@
|
|||||||
|
|
||||||
# Uni-Lab-OS
|
# Uni-Lab-OS
|
||||||
|
|
||||||
|
<!-- Language switcher -->
|
||||||
|
**English** | [中文](README_zh.md)
|
||||||
|
|
||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/stargazers)
|
[](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/network/members)
|
||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
|
[](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
|
```bash
|
||||||
# 创建新环境
|
# Create new environment
|
||||||
mamba env create -f unilabos-[YOUR_OS].yaml
|
mamba env create -f unilabos-[YOUR_OS].yaml
|
||||||
mamba activate unilab
|
mamba activate unilab
|
||||||
|
|
||||||
# 或更新现有环境
|
# Or update existing environment
|
||||||
# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。
|
# Where `[YOUR_OS]` can be `win64`, `linux-64`, `osx-64`, or `osx-arm64`.
|
||||||
conda env update --file unilabos-[YOUR_OS].yml -n 环境名
|
conda env update --file unilabos-[YOUR_OS].yml -n environment_name
|
||||||
|
|
||||||
# 现阶段,需要安装 `unilabos_msgs` 包
|
# Currently, you need to install the `unilabos_msgs` package
|
||||||
# 可以前往 Release 页面下载系统对应的包进行安装
|
# You can download the system-specific package from the Release page
|
||||||
conda install ros-humble-unilabos-msgs-0.9.0-xxxxx.tar.bz2
|
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
|
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||||
cd plr_repo
|
cd plr_repo
|
||||||
pip install .[opentrons]
|
pip install .[opentrons]
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 安装 Uni-Lab-OS:
|
2. Install Uni-Lab-OS:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆仓库
|
# Clone the repository
|
||||||
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
|
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
|
||||||
cd Uni-Lab-OS
|
cd Uni-Lab-OS
|
||||||
|
|
||||||
# 安装 Uni-Lab-OS
|
# Install Uni-Lab-OS
|
||||||
pip install .
|
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
|
||||||
|
|
||||||
<a href="https://star-history.com/#dptech-corp/Uni-Lab-OS&Date">
|
<a href="https://star-history.com/#dptech-corp/Uni-Lab-OS&Date">
|
||||||
<img src="https://api.star-history.com/svg?repos=dptech-corp/Uni-Lab-OS&type=Date" alt="Star History Chart" width="600">
|
<img src="https://api.star-history.com/svg?repos=dptech-corp/Uni-Lab-OS&type=Date" alt="Star History Chart" width="600">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## 联系我们
|
## Contact Us
|
||||||
|
|
||||||
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
89
README_zh.md
Normal file
89
README_zh.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<div align="center">
|
||||||
|
<img src="docs/logo.png" alt="Uni-Lab Logo" width="200"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
# Uni-Lab-OS
|
||||||
|
|
||||||
|
<!-- Language switcher -->
|
||||||
|
[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 趋势
|
||||||
|
|
||||||
|
<a href="https://star-history.com/#dptech-corp/Uni-Lab-OS&Date">
|
||||||
|
<img src="https://api.star-history.com/svg?repos=dptech-corp/Uni-Lab-OS&type=Date" alt="Star History Chart" width="600">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## 联系我们
|
||||||
|
|
||||||
|
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
@@ -42,7 +42,7 @@ def get_host_node_info() -> Dict[str, Any]:
|
|||||||
host_info["subscribed_topics"] = sorted(list(host_node._subscribed_topics))
|
host_info["subscribed_topics"] = sorted(list(host_node._subscribed_topics))
|
||||||
# 获取动作客户端信息
|
# 获取动作客户端信息
|
||||||
for action_id, client in host_node._action_clients.items():
|
for action_id, client in host_node._action_clients.items():
|
||||||
host_info["action_clients"] = {action_id: get_action_info(client, full_name=action_id)}
|
host_info["action_clients"][action_id] = get_action_info(client, full_name=action_id)
|
||||||
|
|
||||||
# 获取设备状态
|
# 获取设备状态
|
||||||
host_info["device_status"] = host_node.device_status
|
host_info["device_status"] = host_node.device_status
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ separator.homemade:
|
|||||||
goal:
|
goal:
|
||||||
stir_time: stir_time,
|
stir_time: stir_time,
|
||||||
stir_speed: stir_speed
|
stir_speed: stir_speed
|
||||||
settling_time": settling_time
|
settling_time: settling_time
|
||||||
feedback:
|
feedback:
|
||||||
status: status
|
status: status
|
||||||
result:
|
result:
|
||||||
|
|||||||
@@ -3,6 +3,25 @@ vacuum_pump.mock:
|
|||||||
class:
|
class:
|
||||||
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
|
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
|
||||||
type: python
|
type: python
|
||||||
|
status_types:
|
||||||
|
status: String
|
||||||
|
action_value_mappings:
|
||||||
|
open:
|
||||||
|
type: EmptyIn
|
||||||
|
goal: {}
|
||||||
|
feedback: {}
|
||||||
|
result: {}
|
||||||
|
close:
|
||||||
|
type: EmptyIn
|
||||||
|
goal: {}
|
||||||
|
feedback: {}
|
||||||
|
result: {}
|
||||||
|
set_status:
|
||||||
|
type: StrSingleInput
|
||||||
|
goal:
|
||||||
|
string: string
|
||||||
|
feedback: {}
|
||||||
|
result: {}
|
||||||
|
|
||||||
gas_source.mock:
|
gas_source.mock:
|
||||||
description: Mock gas source
|
description: Mock gas source
|
||||||
|
|||||||
@@ -169,8 +169,13 @@ class Registry:
|
|||||||
action_config["type"] = self._replace_type_with_class(
|
action_config["type"] = self._replace_type_with_class(
|
||||||
action_config["type"], device_id, f"动作 {action_name}"
|
action_config["type"], device_id, f"动作 {action_name}"
|
||||||
)
|
)
|
||||||
action_config["goal_default"] = yaml.safe_load(io.StringIO(get_yaml_from_goal_type(action_config["type"].Goal)))
|
if action_config["type"] is not None:
|
||||||
action_config["schema"] = ros_action_to_json_schema(action_config["type"])
|
action_config["goal_default"] = yaml.safe_load(io.StringIO(get_yaml_from_goal_type(action_config["type"].Goal)))
|
||||||
|
action_config["schema"] = ros_action_to_json_schema(action_config["type"])
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"[UniLab Registry] 设备 {device_id} 的动作 {action_name} 类型为空,跳过替换"
|
||||||
|
)
|
||||||
|
|
||||||
self.device_type_registry.update(data)
|
self.device_type_registry.update(data)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import functools
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -404,6 +405,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
# 加入全局注册表
|
# 加入全局注册表
|
||||||
registered_devices[self.device_id] = device_info
|
registered_devices[self.device_id] = device_info
|
||||||
from unilabos.config.config import BasicConfig
|
from unilabos.config.config import BasicConfig
|
||||||
|
from unilabos.ros.nodes.presets.host_node import HostNode
|
||||||
if not BasicConfig.is_host_mode:
|
if not BasicConfig.is_host_mode:
|
||||||
sclient = self.create_client(SerialCommand, "/node_info_update")
|
sclient = self.create_client(SerialCommand, "/node_info_update")
|
||||||
# 启动线程执行发送任务
|
# 启动线程执行发送任务
|
||||||
@@ -413,6 +415,10 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
daemon=True,
|
daemon=True,
|
||||||
name=f"ROSDevice{self.device_id}_send_slave_node_info"
|
name=f"ROSDevice{self.device_id}_send_slave_node_info"
|
||||||
).start()
|
).start()
|
||||||
|
else:
|
||||||
|
host_node = HostNode.get_instance(0)
|
||||||
|
if host_node is not None:
|
||||||
|
host_node.device_machine_names[self.device_id] = "本地"
|
||||||
|
|
||||||
def send_slave_node_info(self, sclient):
|
def send_slave_node_info(self, sclient):
|
||||||
sclient.wait_for_service()
|
sclient.wait_for_service()
|
||||||
@@ -481,6 +487,17 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
|
|
||||||
self.lab_logger().debug(f"发布动作: {action_name}, 类型: {str_action_type}")
|
self.lab_logger().debug(f"发布动作: {action_name}, 类型: {str_action_type}")
|
||||||
|
|
||||||
|
def get_real_function(self, instance, attr_name):
|
||||||
|
if hasattr(instance.__class__, attr_name):
|
||||||
|
obj = getattr(instance.__class__, attr_name)
|
||||||
|
if isinstance(obj, property):
|
||||||
|
return lambda *args, **kwargs: obj.fset(instance, *args, **kwargs), get_type_hints(obj.fset)
|
||||||
|
obj = getattr(instance, attr_name)
|
||||||
|
return obj, get_type_hints(obj)
|
||||||
|
else:
|
||||||
|
obj = getattr(instance, attr_name)
|
||||||
|
return obj, get_type_hints(obj)
|
||||||
|
|
||||||
def _create_execute_callback(self, action_name, action_value_mapping):
|
def _create_execute_callback(self, action_name, action_value_mapping):
|
||||||
"""创建动作执行回调函数"""
|
"""创建动作执行回调函数"""
|
||||||
|
|
||||||
@@ -495,17 +512,16 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
for i, action in enumerate(self._action_value_mappings["sequence"]):
|
for i, action in enumerate(self._action_value_mappings["sequence"]):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
self.lab_logger().info(f"执行序列动作第一步: {action}")
|
self.lab_logger().info(f"执行序列动作第一步: {action}")
|
||||||
getattr(self.driver_instance, action)(**kwargs)
|
self.get_real_function(self.driver_instance, action)[0](**kwargs)
|
||||||
else:
|
else:
|
||||||
self.lab_logger().info(f"执行序列动作后续步骤: {action}")
|
self.lab_logger().info(f"执行序列动作后续步骤: {action}")
|
||||||
getattr(self.driver_instance, action)()
|
self.get_real_function(self.driver_instance, action)[0]()
|
||||||
|
|
||||||
action_paramtypes = get_type_hints(
|
action_paramtypes = get_type_hints(
|
||||||
getattr(self.driver_instance, self._action_value_mappings["sequence"][0])
|
self.get_real_function(self.driver_instance, self._action_value_mappings["sequence"][0])
|
||||||
)
|
)[1]
|
||||||
else:
|
else:
|
||||||
ACTION = getattr(self.driver_instance, action_name)
|
ACTION, action_paramtypes = self.get_real_function(self.driver_instance, action_name)
|
||||||
action_paramtypes = get_type_hints(ACTION)
|
|
||||||
|
|
||||||
action_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
|
action_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
|
||||||
self.lab_logger().debug(f"接收到原始目标: {action_kwargs}")
|
self.lab_logger().debug(f"接收到原始目标: {action_kwargs}")
|
||||||
|
|||||||
@@ -141,12 +141,22 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
].items():
|
].items():
|
||||||
controller_config["update_rate"] = update_rate
|
controller_config["update_rate"] = update_rate
|
||||||
self.initialize_controller(controller_id, controller_config)
|
self.initialize_controller(controller_id, controller_config)
|
||||||
|
resource_with_parent_name = []
|
||||||
|
resource_ids_to_instance = {i["id"]: i for i in resources_config}
|
||||||
|
for res in resources_config:
|
||||||
|
if res.get("parent") and res.get("type") == "device" and res.get("class"):
|
||||||
|
parent_id = res.get("parent")
|
||||||
|
parent_res = resource_ids_to_instance[parent_id]
|
||||||
|
if parent_res.get("type") == "device" and parent_res.get("class"):
|
||||||
|
resource_with_parent_name.append(copy.deepcopy(res))
|
||||||
|
resource_with_parent_name[-1]["id"] = f"{parent_res['id']}/{res['id']}"
|
||||||
|
continue
|
||||||
|
resource_with_parent_name.append(copy.deepcopy(res))
|
||||||
try:
|
try:
|
||||||
for bridge in self.bridges:
|
for bridge in self.bridges:
|
||||||
if hasattr(bridge, "resource_add"):
|
if hasattr(bridge, "resource_add"):
|
||||||
self.lab_logger().info("[Host Node-Resource] Adding resources to bridge.")
|
self.lab_logger().info("[Host Node-Resource] Adding resources to bridge.")
|
||||||
bridge.resource_add(add_schema(resources_config))
|
resource_add_res = bridge.resource_add(add_schema(resource_with_parent_name))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
|
self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
|
||||||
self.lab_logger().error(traceback.format_exc())
|
self.lab_logger().error(traceback.format_exc())
|
||||||
@@ -191,7 +201,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
|
|
||||||
# 如果是新设备,记录并创建ActionClient
|
# 如果是新设备,记录并创建ActionClient
|
||||||
if edge_device_id not in self.devices_names:
|
if edge_device_id not in self.devices_names:
|
||||||
self.lab_logger().info(f"[Host Node] Discovered new device: {device_key}")
|
self.lab_logger().info(f"[Host Node] Discovered new device: {edge_device_id}")
|
||||||
self.devices_names[edge_device_id] = namespace
|
self.devices_names[edge_device_id] = namespace
|
||||||
self._create_action_clients_for_device(device_id, namespace)
|
self._create_action_clients_for_device(device_id, namespace)
|
||||||
self._online_devices.add(device_key)
|
self._online_devices.add(device_key)
|
||||||
|
|||||||
Reference in New Issue
Block a user