update docs, test examples
fix liquid_handler init bug
@@ -1,3 +1,4 @@
|
|||||||
|
recursive-include unilabos/test *
|
||||||
recursive-include unilabos/registry *.yaml
|
recursive-include unilabos/registry *.yaml
|
||||||
recursive-include unilabos/app/web/static *
|
recursive-include unilabos/app/web/static *
|
||||||
recursive-include unilabos/app/web/templates *
|
recursive-include unilabos/app/web/templates *
|
||||||
|
|||||||
746
docs/advanced_usage/configuration.md
Normal file
@@ -0,0 +1,746 @@
|
|||||||
|
# Uni-Lab 配置指南
|
||||||
|
|
||||||
|
本文档详细介绍 Uni-Lab 配置文件的结构、配置项、命令行覆盖和环境变量的使用方法。
|
||||||
|
|
||||||
|
## 配置文件概述
|
||||||
|
|
||||||
|
Uni-Lab 使用 Python 格式的配置文件(`.py`),默认为 `unilabos_data/local_config.py`。配置文件采用类属性的方式定义各种配置项,比 YAML 或 JSON 提供更多的灵活性,包括支持注释、条件逻辑和复杂数据结构。
|
||||||
|
|
||||||
|
## 获取实验室密钥
|
||||||
|
|
||||||
|
在配置文件或启动命令中,您需要提供实验室的访问密钥(ak)和私钥(sk)。
|
||||||
|
|
||||||
|
**获取方式:**
|
||||||
|
|
||||||
|
进入 [Uni-Lab 实验室](https://uni-lab.bohrium.com),点击左下角的头像,在实验室详情中获取所在实验室的 ak 和 sk:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 配置文件格式
|
||||||
|
|
||||||
|
### 默认配置示例
|
||||||
|
|
||||||
|
首次使用时,系统会自动创建一个基础配置文件 `local_config.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# unilabos的配置文件
|
||||||
|
|
||||||
|
class BasicConfig:
|
||||||
|
ak = "" # 实验室网页给您提供的ak代码
|
||||||
|
sk = "" # 实验室网页给您提供的sk代码
|
||||||
|
|
||||||
|
|
||||||
|
# WebSocket配置,一般无需调整
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 5 # 重连间隔(秒)
|
||||||
|
max_reconnect_attempts = 999 # 最大重连次数
|
||||||
|
ping_interval = 30 # ping间隔(秒)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整配置示例
|
||||||
|
|
||||||
|
您可以根据需要添加更多配置选项:
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
"""Uni-Lab 配置文件"""
|
||||||
|
|
||||||
|
# 基础配置
|
||||||
|
class BasicConfig:
|
||||||
|
ak = "" # 实验室访问密钥
|
||||||
|
sk = "" # 实验室私钥
|
||||||
|
working_dir = "" # 工作目录(通常自动设置)
|
||||||
|
config_path = "" # 配置文件路径(自动设置)
|
||||||
|
is_host_mode = True # 是否为主站模式
|
||||||
|
slave_no_host = False # 从站模式下是否跳过等待主机服务
|
||||||
|
upload_registry = False # 是否上传注册表
|
||||||
|
machine_name = "undefined" # 机器名称(自动获取)
|
||||||
|
vis_2d_enable = False # 是否启用2D可视化
|
||||||
|
enable_resource_load = True # 是否启用资源加载
|
||||||
|
communication_protocol = "websocket" # 通信协议
|
||||||
|
log_level = "DEBUG" # 日志级别:TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||||
|
|
||||||
|
# WebSocket配置
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 5 # 重连间隔(秒)
|
||||||
|
max_reconnect_attempts = 999 # 最大重连次数
|
||||||
|
ping_interval = 30 # ping间隔(秒)
|
||||||
|
|
||||||
|
# OSS上传配置
|
||||||
|
class OSSUploadConfig:
|
||||||
|
api_host = "" # API主机地址
|
||||||
|
authorization = "" # 授权信息
|
||||||
|
init_endpoint = "" # 初始化端点
|
||||||
|
complete_endpoint = "" # 完成端点
|
||||||
|
max_retries = 3 # 最大重试次数
|
||||||
|
|
||||||
|
# HTTP配置
|
||||||
|
class HTTPConfig:
|
||||||
|
remote_addr = "https://uni-lab.bohrium.com/api/v1" # 远程服务器地址
|
||||||
|
|
||||||
|
# ROS配置
|
||||||
|
class ROSConfig:
|
||||||
|
modules = [
|
||||||
|
"std_msgs.msg",
|
||||||
|
"geometry_msgs.msg",
|
||||||
|
"control_msgs.msg",
|
||||||
|
"control_msgs.action",
|
||||||
|
"nav2_msgs.action",
|
||||||
|
"unilabos_msgs.msg",
|
||||||
|
"unilabos_msgs.action",
|
||||||
|
] # 需要加载的ROS模块
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置优先级
|
||||||
|
|
||||||
|
配置项的生效优先级从高到低为:
|
||||||
|
|
||||||
|
1. **命令行参数**:最高优先级
|
||||||
|
2. **环境变量**:中等优先级
|
||||||
|
3. **配置文件**:基础优先级
|
||||||
|
|
||||||
|
这意味着命令行参数会覆盖环境变量和配置文件,环境变量会覆盖配置文件。
|
||||||
|
|
||||||
|
## 推荐配置方式
|
||||||
|
|
||||||
|
根据参数特性,不同配置项有不同的推荐配置方式:
|
||||||
|
|
||||||
|
### 建议通过命令行指定的参数(不需要写入配置文件)
|
||||||
|
|
||||||
|
以下参数推荐通过命令行或环境变量指定,**一般不需要在配置文件中配置**:
|
||||||
|
|
||||||
|
| 参数 | 命令行参数 | 原因 |
|
||||||
|
| ----------------- | ------------------- | ------------------------------------ |
|
||||||
|
| `ak` / `sk` | `--ak` / `--sk` | **安全考虑**:避免敏感信息泄露 |
|
||||||
|
| `working_dir` | `--working_dir` | **灵活性**:不同环境可能使用不同目录 |
|
||||||
|
| `is_host_mode` | `--is_slave` | **运行模式**:由启动场景决定,不固定 |
|
||||||
|
| `slave_no_host` | `--slave_no_host` | **运行模式**:从站特殊配置,按需使用 |
|
||||||
|
| `upload_registry` | `--upload_registry` | **临时操作**:仅首次启动或更新时需要 |
|
||||||
|
| `vis_2d_enable` | `--2d_vis` | **调试功能**:按需临时启用 |
|
||||||
|
| `remote_addr` | `--addr` | **环境切换**:测试/生产环境快速切换 |
|
||||||
|
|
||||||
|
**推荐用法示例:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 标准启动命令(所有必要参数通过命令行指定)
|
||||||
|
unilab --ak your_ak --sk your_sk -g graph.json
|
||||||
|
|
||||||
|
# 测试环境
|
||||||
|
unilab --addr test --ak your_ak --sk your_sk -g graph.json
|
||||||
|
|
||||||
|
# 从站模式
|
||||||
|
unilab --is_slave --ak your_ak --sk your_sk
|
||||||
|
|
||||||
|
# 首次启动上传注册表
|
||||||
|
unilab --ak your_ak --sk your_sk -g graph.json --upload_registry
|
||||||
|
```
|
||||||
|
|
||||||
|
### 适合在配置文件中配置的参数
|
||||||
|
|
||||||
|
以下参数适合在配置文件中配置,通常不会频繁更改:
|
||||||
|
|
||||||
|
| 参数 | 配置类 | 说明 |
|
||||||
|
| ------------------------ | ----------- | ---------------------- |
|
||||||
|
| `log_level` | BasicConfig | 日志级别配置 |
|
||||||
|
| `reconnect_interval` | WSConfig | WebSocket 重连间隔 |
|
||||||
|
| `max_reconnect_attempts` | WSConfig | WebSocket 最大重连次数 |
|
||||||
|
| `ping_interval` | WSConfig | WebSocket 心跳间隔 |
|
||||||
|
| `modules` | ROSConfig | ROS 模块列表 |
|
||||||
|
|
||||||
|
**配置文件示例(推荐最小配置):**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# unilabos的配置文件
|
||||||
|
|
||||||
|
class BasicConfig:
|
||||||
|
log_level = "INFO" # 生产环境建议 INFO,调试时用 DEBUG
|
||||||
|
|
||||||
|
# WebSocket配置,一般保持默认即可
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 5
|
||||||
|
max_reconnect_attempts = 999
|
||||||
|
ping_interval = 30
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:** `ak` 和 `sk` 不建议写在配置文件中,始终通过命令行参数或环境变量传递。
|
||||||
|
|
||||||
|
## 命令行参数覆盖配置
|
||||||
|
|
||||||
|
Uni-Lab 允许通过命令行参数覆盖配置文件中的设置,提供更灵活的配置方式。
|
||||||
|
|
||||||
|
### 支持命令行覆盖的配置项
|
||||||
|
|
||||||
|
| 配置类 | 配置字段 | 命令行参数 | 说明 |
|
||||||
|
| ------------- | ----------------- | ------------------- | -------------------------------- |
|
||||||
|
| `BasicConfig` | `ak` | `--ak` | 实验室访问密钥 |
|
||||||
|
| `BasicConfig` | `sk` | `--sk` | 实验室私钥 |
|
||||||
|
| `BasicConfig` | `working_dir` | `--working_dir` | 工作目录路径 |
|
||||||
|
| `BasicConfig` | `is_host_mode` | `--is_slave` | 主站模式(参数为从站模式,取反) |
|
||||||
|
| `BasicConfig` | `slave_no_host` | `--slave_no_host` | 从站模式下跳过等待主机服务 |
|
||||||
|
| `BasicConfig` | `upload_registry` | `--upload_registry` | 启动时上传注册表信息 |
|
||||||
|
| `BasicConfig` | `vis_2d_enable` | `--2d_vis` | 启用 2D 可视化 |
|
||||||
|
| `HTTPConfig` | `remote_addr` | `--addr` | 远程服务地址 |
|
||||||
|
|
||||||
|
### 特殊命令行参数
|
||||||
|
|
||||||
|
除了直接覆盖配置项的参数外,还有一些特殊的命令行参数:
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
| ------------------- | ------------------------------------ |
|
||||||
|
| `--config` | 指定配置文件路径 |
|
||||||
|
| `--port` | Web 服务端口(不影响配置文件) |
|
||||||
|
| `--disable_browser` | 禁用自动打开浏览器(不影响配置文件) |
|
||||||
|
| `--visual` | 可视化工具选择(不影响配置文件) |
|
||||||
|
| `--skip_env_check` | 跳过环境检查(不影响配置文件) |
|
||||||
|
|
||||||
|
### 命令行覆盖使用示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 通过命令行覆盖认证信息
|
||||||
|
unilab --ak "new_access_key" --sk "new_secret_key" -g graph.json
|
||||||
|
|
||||||
|
# 覆盖服务器地址
|
||||||
|
unilab --ak ak --sk sk --addr "https://custom.server.com/api/v1" -g graph.json
|
||||||
|
|
||||||
|
# 启用从站模式并跳过等待主机
|
||||||
|
unilab --is_slave --slave_no_host --ak ak --sk sk
|
||||||
|
|
||||||
|
# 启用上传注册表和2D可视化
|
||||||
|
unilab --upload_registry --2d_vis --ak ak --sk sk -g graph.json
|
||||||
|
|
||||||
|
# 组合使用多个覆盖参数
|
||||||
|
unilab --ak "key" --sk "secret" --addr "test" --upload_registry --2d_vis -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 预设环境地址
|
||||||
|
|
||||||
|
`--addr` 参数支持以下预设值,会自动转换为对应的完整 URL:
|
||||||
|
|
||||||
|
- `test` → `https://uni-lab.test.bohrium.com/api/v1`
|
||||||
|
- `uat` → `https://uni-lab.uat.bohrium.com/api/v1`
|
||||||
|
- `local` → `http://127.0.0.1:48197/api/v1`
|
||||||
|
- 其他值 → 直接使用作为完整 URL
|
||||||
|
|
||||||
|
## 配置选项详解
|
||||||
|
|
||||||
|
### 1. BasicConfig - 基础配置
|
||||||
|
|
||||||
|
基础配置包含了系统运行的核心参数:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
| ------------------------ | ---- | ------------- | ------------------------------------------ |
|
||||||
|
| `ak` | str | `""` | 实验室访问密钥(必需) |
|
||||||
|
| `sk` | str | `""` | 实验室私钥(必需) |
|
||||||
|
| `working_dir` | str | `""` | 工作目录,通常自动设置 |
|
||||||
|
| `config_path` | str | `""` | 配置文件路径,自动设置 |
|
||||||
|
| `is_host_mode` | bool | `True` | 是否为主站模式 |
|
||||||
|
| `slave_no_host` | bool | `False` | 从站模式下是否跳过等待主机服务 |
|
||||||
|
| `upload_registry` | bool | `False` | 启动时是否上传注册表信息 |
|
||||||
|
| `machine_name` | str | `"undefined"` | 机器名称,自动从 hostname 获取(不可配置) |
|
||||||
|
| `vis_2d_enable` | bool | `False` | 是否启用 2D 可视化 |
|
||||||
|
| `enable_resource_load` | bool | `True` | 是否启用资源加载 |
|
||||||
|
| `communication_protocol` | str | `"websocket"` | 通信协议,固定为 websocket |
|
||||||
|
| `log_level` | str | `"DEBUG"` | 日志级别 |
|
||||||
|
|
||||||
|
#### 日志级别选项
|
||||||
|
|
||||||
|
- `TRACE` - 追踪级别(最详细)
|
||||||
|
- `DEBUG` - 调试级别(默认)
|
||||||
|
- `INFO` - 信息级别
|
||||||
|
- `WARNING` - 警告级别
|
||||||
|
- `ERROR` - 错误级别
|
||||||
|
- `CRITICAL` - 严重错误级别(最简略)
|
||||||
|
|
||||||
|
#### 认证配置(ak / sk)
|
||||||
|
|
||||||
|
`ak` 和 `sk` 是必需的认证参数:
|
||||||
|
|
||||||
|
1. **获取方式**:在 [Uni-Lab 官网](https://uni-lab.bohrium.com) 注册实验室后获得
|
||||||
|
2. **配置方式**:
|
||||||
|
- **命令行参数**:`--ak "your_key" --sk "your_secret"`(最高优先级,推荐)
|
||||||
|
- **环境变量**:`UNILABOS_BASICCONFIG_AK` 和 `UNILABOS_BASICCONFIG_SK`
|
||||||
|
- **配置文件**:在 `BasicConfig` 类中设置(不推荐,安全风险)
|
||||||
|
3. **安全注意**:请妥善保管您的密钥信息,不要提交到版本控制
|
||||||
|
|
||||||
|
**推荐做法**:
|
||||||
|
|
||||||
|
- **开发环境**:使用命令行参数或环境变量
|
||||||
|
- **生产环境**:使用环境变量
|
||||||
|
- **临时测试**:使用命令行参数
|
||||||
|
|
||||||
|
### 2. WSConfig - WebSocket 配置
|
||||||
|
|
||||||
|
WebSocket 是 Uni-Lab 的主要通信方式:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
| ------------------------ | ---- | ------ | ------------------ |
|
||||||
|
| `reconnect_interval` | int | `5` | 断线重连间隔(秒) |
|
||||||
|
| `max_reconnect_attempts` | int | `999` | 最大重连次数 |
|
||||||
|
| `ping_interval` | int | `30` | 心跳检测间隔(秒) |
|
||||||
|
|
||||||
|
### 3. HTTPConfig - HTTP 配置
|
||||||
|
|
||||||
|
HTTP 客户端配置用于与云端服务通信:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
| ------------- | ---- | -------------------------------------- | ------------ |
|
||||||
|
| `remote_addr` | str | `"https://uni-lab.bohrium.com/api/v1"` | 远程服务地址 |
|
||||||
|
|
||||||
|
**预设环境地址**:
|
||||||
|
|
||||||
|
- 生产环境:`https://uni-lab.bohrium.com/api/v1`(默认)
|
||||||
|
- 测试环境:`https://uni-lab.test.bohrium.com/api/v1`
|
||||||
|
- UAT 环境:`https://uni-lab.uat.bohrium.com/api/v1`
|
||||||
|
- 本地环境:`http://127.0.0.1:48197/api/v1`
|
||||||
|
|
||||||
|
### 4. OSSUploadConfig - OSS 上传配置
|
||||||
|
|
||||||
|
对象存储服务配置,用于文件上传功能:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
| ------------------- | ---- | ------ | -------------------- |
|
||||||
|
| `api_host` | str | `""` | OSS API 主机地址 |
|
||||||
|
| `authorization` | str | `""` | 授权认证信息 |
|
||||||
|
| `init_endpoint` | str | `""` | 上传初始化端点 |
|
||||||
|
| `complete_endpoint` | str | `""` | 上传完成端点 |
|
||||||
|
| `max_retries` | int | `3` | 上传失败最大重试次数 |
|
||||||
|
|
||||||
|
### 5. ROSConfig - ROS 配置
|
||||||
|
|
||||||
|
配置 ROS 消息转换器需要加载的模块:
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
| --------- | ---- | ---------- | ------------ |
|
||||||
|
| `modules` | list | 见下方示例 | ROS 模块列表 |
|
||||||
|
|
||||||
|
**默认模块列表:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ROSConfig:
|
||||||
|
modules = [
|
||||||
|
"std_msgs.msg", # 标准消息类型
|
||||||
|
"geometry_msgs.msg", # 几何消息类型
|
||||||
|
"control_msgs.msg", # 控制消息类型
|
||||||
|
"control_msgs.action", # 控制动作类型
|
||||||
|
"nav2_msgs.action", # 导航动作类型
|
||||||
|
"unilabos_msgs.msg", # UniLab 自定义消息类型
|
||||||
|
"unilabos_msgs.action", # UniLab 自定义动作类型
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
您可以根据实际使用的设备和功能添加其他 ROS 模块。
|
||||||
|
|
||||||
|
## 环境变量配置
|
||||||
|
|
||||||
|
Uni-Lab 支持通过环境变量覆盖配置文件中的设置。
|
||||||
|
|
||||||
|
### 环境变量命名规则
|
||||||
|
|
||||||
|
```
|
||||||
|
UNILABOS_<配置类名>_<配置项名>
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:**
|
||||||
|
|
||||||
|
- 环境变量名不区分大小写
|
||||||
|
- 配置类名和配置项名都会转换为大写进行匹配
|
||||||
|
|
||||||
|
### 设置环境变量
|
||||||
|
|
||||||
|
#### Linux / macOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 临时设置(当前终端)
|
||||||
|
export UNILABOS_BASICCONFIG_LOG_LEVEL=INFO
|
||||||
|
export UNILABOS_BASICCONFIG_AK="your_access_key"
|
||||||
|
export UNILABOS_BASICCONFIG_SK="your_secret_key"
|
||||||
|
|
||||||
|
# 永久设置(添加到 ~/.bashrc 或 ~/.zshrc)
|
||||||
|
echo 'export UNILABOS_BASICCONFIG_LOG_LEVEL=INFO' >> ~/.bashrc
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows (cmd)
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
# 临时设置
|
||||||
|
set UNILABOS_BASICCONFIG_LOG_LEVEL=INFO
|
||||||
|
set UNILABOS_BASICCONFIG_AK=your_access_key
|
||||||
|
|
||||||
|
# 永久设置(系统环境变量)
|
||||||
|
setx UNILABOS_BASICCONFIG_LOG_LEVEL INFO
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows (PowerShell)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 临时设置
|
||||||
|
$env:UNILABOS_BASICCONFIG_LOG_LEVEL="INFO"
|
||||||
|
$env:UNILABOS_BASICCONFIG_AK="your_access_key"
|
||||||
|
|
||||||
|
# 永久设置
|
||||||
|
[Environment]::SetEnvironmentVariable("UNILABOS_BASICCONFIG_LOG_LEVEL", "INFO", "User")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 环境变量类型转换
|
||||||
|
|
||||||
|
系统会根据配置项的原始类型自动转换环境变量值:
|
||||||
|
|
||||||
|
| 原始类型 | 转换规则 |
|
||||||
|
| -------- | --------------------------------------- |
|
||||||
|
| `bool` | "true", "1", "yes" → True;其他 → False |
|
||||||
|
| `int` | 转换为整数 |
|
||||||
|
| `float` | 转换为浮点数 |
|
||||||
|
| `str` | 直接使用字符串值 |
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 布尔值
|
||||||
|
export UNILABOS_BASICCONFIG_IS_HOST_MODE=true # 将设置为 True
|
||||||
|
export UNILABOS_BASICCONFIG_IS_HOST_MODE=false # 将设置为 False
|
||||||
|
|
||||||
|
# 整数
|
||||||
|
export UNILABOS_WSCONFIG_RECONNECT_INTERVAL=10 # 将设置为 10
|
||||||
|
|
||||||
|
# 字符串
|
||||||
|
export UNILABOS_BASICCONFIG_LOG_LEVEL=INFO # 将设置为 "INFO"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 环境变量示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置基础配置
|
||||||
|
export UNILABOS_BASICCONFIG_AK="your_access_key"
|
||||||
|
export UNILABOS_BASICCONFIG_SK="your_secret_key"
|
||||||
|
export UNILABOS_BASICCONFIG_IS_HOST_MODE="true"
|
||||||
|
|
||||||
|
# 设置WebSocket配置
|
||||||
|
export UNILABOS_WSCONFIG_RECONNECT_INTERVAL="10"
|
||||||
|
export UNILABOS_WSCONFIG_MAX_RECONNECT_ATTEMPTS="500"
|
||||||
|
|
||||||
|
# 设置HTTP配置
|
||||||
|
export UNILABOS_HTTPCONFIG_REMOTE_ADDR="https://uni-lab.test.bohrium.com/api/v1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置文件使用方法
|
||||||
|
|
||||||
|
### 1. 使用默认配置文件(推荐)
|
||||||
|
|
||||||
|
系统会自动查找并加载配置文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 直接启动,使用默认的 unilabos_data/local_config.py
|
||||||
|
unilab --ak your_ak --sk your_sk -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
查找顺序:
|
||||||
|
|
||||||
|
1. 环境变量 `UNILABOS_BASICCONFIG_CONFIG_PATH` 指定的路径
|
||||||
|
2. 工作目录下的 `local_config.py`
|
||||||
|
3. 首次使用时会引导创建配置文件
|
||||||
|
|
||||||
|
### 2. 指定配置文件启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用指定配置文件启动
|
||||||
|
unilab --config /path/to/your/config.py --ak ak --sk sk -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置文件验证
|
||||||
|
|
||||||
|
系统启动时会自动验证配置文件:
|
||||||
|
|
||||||
|
- **语法检查**:确保 Python 语法正确
|
||||||
|
- **类型检查**:验证配置项类型是否匹配
|
||||||
|
- **加载确认**:控制台输出加载成功信息
|
||||||
|
|
||||||
|
## 常用配置场景
|
||||||
|
|
||||||
|
### 场景 1:调整日志级别
|
||||||
|
|
||||||
|
**配置文件方式:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
class BasicConfig:
|
||||||
|
log_level = "INFO" # 生产环境建议使用 INFO 或 WARNING
|
||||||
|
```
|
||||||
|
|
||||||
|
**环境变量方式:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export UNILABOS_BASICCONFIG_LOG_LEVEL=INFO
|
||||||
|
unilab --ak ak --sk sk -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**命令行方式**(需要配置文件已包含):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 配置文件无直接命令行参数,需通过环境变量
|
||||||
|
UNILABOS_BASICCONFIG_LOG_LEVEL=INFO unilab --ak ak --sk sk -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 2:配置 WebSocket 重连
|
||||||
|
|
||||||
|
**配置文件方式:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 10 # 增加重连间隔到 10 秒
|
||||||
|
max_reconnect_attempts = 100 # 减少最大重连次数到 100 次
|
||||||
|
```
|
||||||
|
|
||||||
|
**环境变量方式:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export UNILABOS_WSCONFIG_RECONNECT_INTERVAL=10
|
||||||
|
export UNILABOS_WSCONFIG_MAX_RECONNECT_ATTEMPTS=100
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 3:切换服务器环境
|
||||||
|
|
||||||
|
**配置文件方式:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
class HTTPConfig:
|
||||||
|
remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
|
||||||
|
```
|
||||||
|
|
||||||
|
**环境变量方式:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export UNILABOS_HTTPCONFIG_REMOTE_ADDR=https://uni-lab.test.bohrium.com/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
**命令行方式(推荐):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --addr test --ak your_ak --sk your_sk -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 4:从站模式配置
|
||||||
|
|
||||||
|
**配置文件方式:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
class BasicConfig:
|
||||||
|
is_host_mode = False # 从站模式
|
||||||
|
slave_no_host = True # 不等待主机服务
|
||||||
|
```
|
||||||
|
|
||||||
|
**命令行方式(推荐):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --is_slave --slave_no_host --ak your_ak --sk your_sk
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 安全配置
|
||||||
|
|
||||||
|
**不要在配置文件中存储敏感信息**
|
||||||
|
|
||||||
|
- ❌ **不推荐**:在配置文件中明文存储 ak/sk
|
||||||
|
- ✅ **推荐**:使用环境变量或命令行参数
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 生产环境 - 使用环境变量(推荐)
|
||||||
|
export UNILABOS_BASICCONFIG_AK="your_access_key"
|
||||||
|
export UNILABOS_BASICCONFIG_SK="your_secret_key"
|
||||||
|
unilab -g graph.json
|
||||||
|
|
||||||
|
# 或使用命令行参数
|
||||||
|
unilab --ak "your_access_key" --sk "your_secret_key" -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**其他安全建议:**
|
||||||
|
|
||||||
|
- 不要将包含密钥的配置文件提交到版本控制系统
|
||||||
|
- 限制配置文件权限:`chmod 600 local_config.py`
|
||||||
|
- 定期更换访问密钥
|
||||||
|
- 使用 `.gitignore` 排除配置文件
|
||||||
|
|
||||||
|
### 2. 多环境配置
|
||||||
|
|
||||||
|
为不同环境创建不同的配置文件:
|
||||||
|
|
||||||
|
```
|
||||||
|
configs/
|
||||||
|
├── base_config.py # 基础配置(非敏感)
|
||||||
|
├── dev_config.py # 开发环境
|
||||||
|
├── test_config.py # 测试环境
|
||||||
|
├── prod_config.py # 生产环境
|
||||||
|
└── example_config.py # 示例配置
|
||||||
|
```
|
||||||
|
|
||||||
|
**环境切换示例**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 本地开发环境
|
||||||
|
unilab --config configs/dev_config.py --addr local --ak ak --sk sk -g graph.json
|
||||||
|
|
||||||
|
# 测试环境
|
||||||
|
unilab --config configs/test_config.py --addr test --ak ak --sk sk --upload_registry -g graph.json
|
||||||
|
|
||||||
|
# 生产环境
|
||||||
|
unilab --config configs/prod_config.py --ak "$PROD_AK" --sk "$PROD_SK" -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置管理
|
||||||
|
|
||||||
|
**配置文件最佳实践:**
|
||||||
|
|
||||||
|
- 保持配置文件简洁,只包含需要修改的配置项
|
||||||
|
- 为配置项添加注释说明其作用
|
||||||
|
- 定期检查和更新配置文件
|
||||||
|
- 版本控制仅保存示例配置,不包含实际密钥
|
||||||
|
|
||||||
|
**命令行参数优先使用场景:**
|
||||||
|
|
||||||
|
- 临时测试不同配置
|
||||||
|
- CI/CD 流水线中的动态配置
|
||||||
|
- 不同环境间快速切换
|
||||||
|
- 敏感信息的安全传递
|
||||||
|
|
||||||
|
### 4. 灵活配置策略
|
||||||
|
|
||||||
|
**基础配置文件 + 命令行覆盖**的推荐方式:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# base_config.py - 基础配置(非敏感信息)
|
||||||
|
class BasicConfig:
|
||||||
|
# 非敏感配置写在文件中
|
||||||
|
is_host_mode = True
|
||||||
|
upload_registry = False
|
||||||
|
vis_2d_enable = False
|
||||||
|
log_level = "INFO"
|
||||||
|
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 5
|
||||||
|
max_reconnect_attempts = 999
|
||||||
|
ping_interval = 30
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动时通过命令行覆盖关键参数
|
||||||
|
unilab --config base_config.py \
|
||||||
|
--ak "$AK" \
|
||||||
|
--sk "$SK" \
|
||||||
|
--addr "test" \
|
||||||
|
--upload_registry \
|
||||||
|
--2d_vis \
|
||||||
|
-g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 1. 配置文件加载失败
|
||||||
|
|
||||||
|
**错误信息**:`[ENV] 配置文件 xxx 不存在`
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 确认配置文件路径正确
|
||||||
|
- 检查文件权限是否可读
|
||||||
|
- 确保配置文件是 `.py` 格式
|
||||||
|
- 使用绝对路径或相对于当前目录的路径
|
||||||
|
|
||||||
|
### 2. 语法错误
|
||||||
|
|
||||||
|
**错误信息**:`[ENV] 加载配置文件 xxx 失败`
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 检查 Python 语法是否正确
|
||||||
|
- 确认类名和字段名拼写正确
|
||||||
|
- 验证缩进是否正确(使用空格而非制表符)
|
||||||
|
- 确保字符串使用引号包裹
|
||||||
|
|
||||||
|
### 3. 认证失败
|
||||||
|
|
||||||
|
**错误信息**:`后续运行必须拥有一个实验室`
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 确认 `ak` 和 `sk` 已正确配置
|
||||||
|
- 检查密钥是否有效(未过期或撤销)
|
||||||
|
- 确认网络连接正常
|
||||||
|
- 验证密钥是否来自正确的实验室
|
||||||
|
|
||||||
|
### 4. 环境变量不生效
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 确认环境变量名格式正确(`UNILABOS_<类名>_<字段名>`)
|
||||||
|
- 检查环境变量是否已正确设置(`echo $VARIABLE_NAME`)
|
||||||
|
- 重启终端或重新加载环境变量
|
||||||
|
- 确认环境变量值的类型正确
|
||||||
|
|
||||||
|
### 5. 命令行参数不生效
|
||||||
|
|
||||||
|
**错误现象**:设置了命令行参数但配置没有生效
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 确认参数名拼写正确(如 `--ak` 而不是 `--access_key`)
|
||||||
|
- 检查参数格式是否正确(布尔参数如 `--is_slave` 不需要值)
|
||||||
|
- 确认参数位置正确(所有参数都应在 `unilab` 之后)
|
||||||
|
- 查看启动日志确认参数是否被正确解析
|
||||||
|
- 检查是否有配置文件或环境变量与之冲突
|
||||||
|
|
||||||
|
### 6. 配置优先级混淆
|
||||||
|
|
||||||
|
**错误现象**:不确定哪个配置生效
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 记住优先级:**命令行参数 > 环境变量 > 配置文件**
|
||||||
|
- 使用 `--ak` 和 `--sk` 参数时会看到提示信息:"传入了 ak 参数,优先采用传入参数!"
|
||||||
|
- 检查启动日志中的配置加载信息
|
||||||
|
- 临时移除低优先级配置来测试高优先级配置是否生效
|
||||||
|
- 使用 `printenv | grep UNILABOS` 查看所有相关环境变量
|
||||||
|
|
||||||
|
## 配置验证
|
||||||
|
|
||||||
|
### 检查配置是否生效
|
||||||
|
|
||||||
|
启动 Uni-Lab 时,控制台会输出配置加载信息:
|
||||||
|
|
||||||
|
```
|
||||||
|
[ENV] 配置文件 /path/to/config.py 加载成功
|
||||||
|
[ENV] 设置 BasicConfig.log_level = INFO
|
||||||
|
传入了ak参数,优先采用传入参数!
|
||||||
|
传入了sk参数,优先采用传入参数!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常见配置错误
|
||||||
|
|
||||||
|
1. **配置文件格式错误**
|
||||||
|
|
||||||
|
```
|
||||||
|
[ENV] 加载配置文件 /path/to/config.py 失败
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案**:检查 Python 语法,确保配置类定义正确
|
||||||
|
|
||||||
|
2. **环境变量格式错误**
|
||||||
|
|
||||||
|
```
|
||||||
|
[ENV] 环境变量格式不正确:UNILABOS_INVALID_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案**:确保环境变量遵循 `UNILABOS_<类名>_<字段名>` 格式
|
||||||
|
|
||||||
|
3. **类或字段不存在**
|
||||||
|
```
|
||||||
|
[ENV] 未找到类:UNKNOWNCONFIG
|
||||||
|
[ENV] 类 BasicConfig 中未找到字段:UNKNOWN_FIELD
|
||||||
|
```
|
||||||
|
**解决方案**:检查配置类名和字段名是否正确
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [工作目录详解](working_directory.md)
|
||||||
|
- [启动参数详解](../user_guide/launch.md)
|
||||||
|
- [快速安装指南](../user_guide/quick_install_guide.md)
|
||||||
BIN
docs/advanced_usage/image/copy_aksk.gif
Normal file
|
After Width: | Height: | Size: 526 KiB |
218
docs/advanced_usage/working_directory.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# 工作目录详解
|
||||||
|
|
||||||
|
本文档详细介绍 Uni-Lab 工作目录(`working_dir`)的判断逻辑和详细用法。
|
||||||
|
|
||||||
|
## 什么是工作目录
|
||||||
|
|
||||||
|
工作目录是 Uni-Lab 存储配置文件、日志和运行数据的目录。默认情况下,工作目录为 `当前目录/unilabos_data`。
|
||||||
|
|
||||||
|
## 工作目录判断逻辑
|
||||||
|
|
||||||
|
系统按以下决策树自动确定工作目录:
|
||||||
|
|
||||||
|
### 第一步:初始判断
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 检查当前目录
|
||||||
|
if 当前目录以 "unilabos_data" 结尾:
|
||||||
|
working_dir = 当前目录的绝对路径
|
||||||
|
else:
|
||||||
|
working_dir = 当前目录/unilabos_data
|
||||||
|
```
|
||||||
|
|
||||||
|
**解释:**
|
||||||
|
- 如果您已经在 `unilabos_data` 目录内启动,系统直接使用当前目录
|
||||||
|
- 否则,系统会在当前目录下创建或使用 `unilabos_data` 子目录
|
||||||
|
|
||||||
|
### 第二步:处理 `--working_dir` 参数
|
||||||
|
|
||||||
|
如果用户指定了 `--working_dir` 参数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
working_dir = 用户指定的路径
|
||||||
|
```
|
||||||
|
|
||||||
|
此时还会检查配置文件:
|
||||||
|
- 如果同时指定了 `--config` 但该文件不存在
|
||||||
|
- 系统会尝试在 `working_dir/local_config.py` 查找
|
||||||
|
- 如果仍未找到,报错退出
|
||||||
|
|
||||||
|
### 第三步:处理 `--config` 参数
|
||||||
|
|
||||||
|
如果用户指定了 `--config` 且文件存在:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 工作目录改为配置文件所在目录
|
||||||
|
working_dir = config_path 的父目录
|
||||||
|
```
|
||||||
|
|
||||||
|
**重要:** 这意味着配置文件的位置会影响工作目录的判断。
|
||||||
|
|
||||||
|
## 使用场景示例
|
||||||
|
|
||||||
|
### 场景 1:默认场景(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 当前目录:/home/user/project
|
||||||
|
unilab --ak your_ak --sk your_sk -g graph.json
|
||||||
|
|
||||||
|
# 结果:
|
||||||
|
# working_dir = /home/user/project/unilabos_data
|
||||||
|
# config_path = /home/user/project/unilabos_data/local_config.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 2:在 unilabos_data 目录内启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/user/project/unilabos_data
|
||||||
|
unilab --ak your_ak --sk your_sk -g graph.json
|
||||||
|
|
||||||
|
# 结果:
|
||||||
|
# working_dir = /home/user/project/unilabos_data
|
||||||
|
# config_path = /home/user/project/unilabos_data/local_config.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 3:手动指定工作目录
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --working_dir /custom/path --ak your_ak --sk your_sk -g graph.json
|
||||||
|
|
||||||
|
# 结果:
|
||||||
|
# working_dir = /custom/path
|
||||||
|
# config_path = /custom/path/local_config.py (如果存在)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 4:通过配置文件路径推断工作目录
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --config /data/lab_a/local_config.py --ak your_ak --sk your_sk -g graph.json
|
||||||
|
|
||||||
|
# 结果:
|
||||||
|
# working_dir = /data/lab_a
|
||||||
|
# config_path = /data/lab_a/local_config.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法:管理多个实验室配置
|
||||||
|
|
||||||
|
### 方法 1:使用不同的工作目录
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 实验室 A
|
||||||
|
unilab --working_dir ~/labs/lab_a --ak ak_a --sk sk_a -g graph_a.json
|
||||||
|
|
||||||
|
# 实验室 B
|
||||||
|
unilab --working_dir ~/labs/lab_b --ak ak_b --sk sk_b -g graph_b.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法 2:使用不同的配置文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 实验室 A
|
||||||
|
unilab --config ~/labs/lab_a/config.py --ak ak_a --sk sk_a -g graph_a.json
|
||||||
|
|
||||||
|
# 实验室 B
|
||||||
|
unilab --config ~/labs/lab_b/config.py --ak ak_b --sk sk_b -g graph_b.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法 3:使用shell脚本管理
|
||||||
|
|
||||||
|
创建 `start_lab_a.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
cd ~/labs/lab_a
|
||||||
|
unilab --ak your_ak_a --sk your_sk_a -g graph_a.json
|
||||||
|
```
|
||||||
|
|
||||||
|
创建 `start_lab_b.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
cd ~/labs/lab_b
|
||||||
|
unilab --ak your_ak_b --sk your_sk_b -g graph_b.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整决策流程图
|
||||||
|
|
||||||
|
```
|
||||||
|
开始
|
||||||
|
↓
|
||||||
|
判断当前目录是否以 unilabos_data 结尾?
|
||||||
|
├─ 是 → working_dir = 当前目录
|
||||||
|
└─ 否 → working_dir = 当前目录/unilabos_data
|
||||||
|
↓
|
||||||
|
用户是否指定 --working_dir?
|
||||||
|
└─ 是 → working_dir = 指定路径
|
||||||
|
↓
|
||||||
|
用户是否指定 --config 且文件存在?
|
||||||
|
└─ 是 → working_dir = config 文件所在目录
|
||||||
|
↓
|
||||||
|
检查 working_dir/local_config.py 是否存在?
|
||||||
|
├─ 是 → 加载配置文件 → 继续启动
|
||||||
|
└─ 否 → 询问是否首次使用
|
||||||
|
├─ 是 → 创建目录和配置文件 → 继续启动
|
||||||
|
└─ 否 → 退出程序
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 如何查看当前使用的工作目录?
|
||||||
|
|
||||||
|
启动 Uni-Lab 时,系统会在控制台输出:
|
||||||
|
|
||||||
|
```
|
||||||
|
当前工作目录为 /path/to/working_dir
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 可以在同一台机器上运行多个实验室吗?
|
||||||
|
|
||||||
|
可以。使用不同的工作目录或配置文件即可:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 终端 1
|
||||||
|
unilab --working_dir ~/lab1 --ak ak1 --sk sk1 -g graph1.json
|
||||||
|
|
||||||
|
# 终端 2
|
||||||
|
unilab --working_dir ~/lab2 --ak ak2 --sk sk2 -g graph2.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 工作目录中存储了什么?
|
||||||
|
|
||||||
|
- `local_config.py` - 配置文件
|
||||||
|
- 日志文件
|
||||||
|
- 临时运行数据
|
||||||
|
- 缓存文件
|
||||||
|
|
||||||
|
### 4. 可以删除工作目录吗?
|
||||||
|
|
||||||
|
可以,但会丢失:
|
||||||
|
- 配置文件(需要重新创建)
|
||||||
|
- 历史日志
|
||||||
|
- 缓存数据
|
||||||
|
|
||||||
|
建议定期备份配置文件。
|
||||||
|
|
||||||
|
### 5. 如何迁移到新的工作目录?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 复制旧的工作目录
|
||||||
|
cp -r ~/old_path/unilabos_data ~/new_path/unilabos_data
|
||||||
|
|
||||||
|
# 2. 在新位置启动
|
||||||
|
cd ~/new_path
|
||||||
|
unilab --ak your_ak --sk your_sk -g graph.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **使用默认工作目录**:对于单一实验室,使用默认的 `./unilabos_data` 即可
|
||||||
|
2. **组织多实验室**:为每个实验室创建独立的目录结构
|
||||||
|
3. **版本控制**:将配置文件纳入版本控制,但排除日志和缓存
|
||||||
|
4. **备份配置**:定期备份 `local_config.py` 文件
|
||||||
|
5. **使用脚本**:为不同实验室创建启动脚本,简化操作
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [配置文件指南](configuration.md)
|
||||||
|
- [启动参数详解](../user_guide/launch.md)
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
(instructions)=
|
(instructions)=
|
||||||
# 设备抽象、指令集与通信中间件
|
# 设备抽象、指令集与通信中间件
|
||||||
|
|
||||||
Uni-Lab 操作系统的目的是将不同类型和厂家的实验仪器进行抽象统一,对应用层提供服务。因此,理清实验室设备之间的业务逻辑至关重要。
|
Uni-Lab-OS的目的是将不同类型和厂家的实验仪器进行抽象统一,对应用层提供服务。因此,理清实验室设备之间的业务逻辑至关重要。
|
||||||
|
|
||||||
## 设备间通信模式
|
## 设备间通信模式
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# 添加新设备
|
# 添加设备:编写驱动
|
||||||
|
|
||||||
在 Uni-Lab 中,设备(Device)是实验操作的基础单元。Uni-Lab 使用**注册表机制**来兼容管理种类繁多的设备驱动程序。回顾 {ref}`instructions` 中的概念,抽象的设备对外拥有【话题】【服务】【动作】三种通信机制,因此将设备添加进 Uni-Lab,实际上是将设备驱动中的三种机制映射到 Uni-Lab 标准指令集上。
|
在 Uni-Lab 中,设备(Device)是实验操作的基础单元。Uni-Lab 使用**注册表机制**来兼容管理种类繁多的设备驱动程序。抽象的设备对外拥有【话题】【服务】【动作】三种通信机制,因此将设备添加进 Uni-Lab,实际上是将设备驱动中的这三种机制映射到 Uni-Lab 标准指令集上。
|
||||||
|
|
||||||
能被 Uni-Lab 添加的驱动程序类型有以下种类:
|
> **💡 提示:** 本文档介绍如何使用已有的设备驱动(SDK)。若设备没有现成的驱动程序,需要自己开发驱动,请参考 {doc}`add_old_device`。
|
||||||
|
|
||||||
1. Python Class,如
|
## 支持的驱动类型
|
||||||
|
|
||||||
|
Uni-Lab 支持以下两种驱动程序:
|
||||||
|
|
||||||
|
### 1. Python Class(推荐)
|
||||||
|
|
||||||
|
Python 类设备驱动在完成注册表后可以直接在 Uni-Lab 中使用,无需额外编译。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class MockGripper:
|
class MockGripper:
|
||||||
@@ -31,12 +39,11 @@ class MockGripper:
|
|||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
# 会被自动识别的设备动作,接入 Uni-Lab 时会作为 ActionServer 接受任意控制者的指令
|
|
||||||
@status.setter
|
@status.setter
|
||||||
def status(self, target):
|
def status(self, target):
|
||||||
self._status = target
|
self._status = target
|
||||||
|
|
||||||
# 需要在注册表添加的设备动作,接入 Uni-Lab 时会作为 ActionServer 接受任意控制者的指令
|
# 会被自动识别的设备动作,接入 Uni-Lab 时会作为 ActionServer 接受任意控制者的指令
|
||||||
def push_to(self, position: float, torque: float, velocity: float = 0.0):
|
def push_to(self, position: float, torque: float, velocity: float = 0.0):
|
||||||
self._status = "Running"
|
self._status = "Running"
|
||||||
current_pos = self.position
|
current_pos = self.position
|
||||||
@@ -53,9 +60,11 @@ class MockGripper:
|
|||||||
self._status = "Idle"
|
self._status = "Idle"
|
||||||
```
|
```
|
||||||
|
|
||||||
Python 类设备驱动在完成注册表后可以直接在 Uni-Lab 使用。
|
### 2. C# Class
|
||||||
|
|
||||||
2. C# Class,如
|
C# 驱动设备在完成注册表后,需要调用 Uni-Lab C# 编译后才能使用(仅需一次)。
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using System;
|
using System;
|
||||||
@@ -84,7 +93,7 @@ public class MockGripper
|
|||||||
position = currentPos + (Position - currentPos) / 20 * (i + 1);
|
position = currentPos + (Position - currentPos) / 20 * (i + 1);
|
||||||
torque = Torque / (20 - i);
|
torque = Torque / (20 - i);
|
||||||
velocity = Velocity;
|
velocity = Velocity;
|
||||||
await Task.Delay((int)(moveTime * 1000 / 20)); // Convert seconds to milliseconds
|
await Task.Delay((int)(moveTime * 1000 / 20));
|
||||||
}
|
}
|
||||||
torque = Torque;
|
torque = Torque;
|
||||||
status = "Idle";
|
status = "Idle";
|
||||||
@@ -92,12 +101,16 @@ public class MockGripper
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
C# 驱动设备在完成注册表后,需要调用 Uni-Lab C# 编译后才能使用,但只需一次。
|
---
|
||||||
|
|
||||||
## 快速开始:使用注册表编辑器(推荐)
|
## 快速开始:两种方式添加设备
|
||||||
|
|
||||||
|
### 方式 1:使用注册表编辑器(推荐)
|
||||||
|
|
||||||
推荐使用 Uni-Lab-OS 自带的可视化编辑器,它能自动分析您的设备驱动并生成大部分配置:
|
推荐使用 Uni-Lab-OS 自带的可视化编辑器,它能自动分析您的设备驱动并生成大部分配置:
|
||||||
|
|
||||||
|
**步骤:**
|
||||||
|
|
||||||
1. 启动 Uni-Lab-OS
|
1. 启动 Uni-Lab-OS
|
||||||
2. 在浏览器中打开"注册表编辑器"页面
|
2. 在浏览器中打开"注册表编辑器"页面
|
||||||
3. 选择您的 Python 设备驱动文件
|
3. 选择您的 Python 设备驱动文件
|
||||||
@@ -106,13 +119,18 @@ C# 驱动设备在完成注册表后,需要调用 Uni-Lab C# 编译后才能
|
|||||||
6. 点击"生成注册表",复制生成的内容
|
6. 点击"生成注册表",复制生成的内容
|
||||||
7. 保存到 `devices/` 目录下
|
7. 保存到 `devices/` 目录下
|
||||||
|
|
||||||
---
|
**优点:**
|
||||||
|
|
||||||
## 手动编写注册表(简化版)
|
- 自动识别设备属性和方法
|
||||||
|
- 可视化界面,易于操作
|
||||||
|
- 自动生成完整配置
|
||||||
|
- 减少手动配置错误
|
||||||
|
|
||||||
|
### 方式 2:手动编写注册表(简化版)
|
||||||
|
|
||||||
如果需要手动编写,只需要提供两个必需字段,系统会自动补全其余内容:
|
如果需要手动编写,只需要提供两个必需字段,系统会自动补全其余内容:
|
||||||
|
|
||||||
### 最小配置示例
|
**最小配置示例:**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
my_device: # 设备唯一标识符
|
my_device: # 设备唯一标识符
|
||||||
@@ -121,22 +139,22 @@ my_device: # 设备唯一标识符
|
|||||||
type: python # 驱动类型
|
type: python # 驱动类型
|
||||||
```
|
```
|
||||||
|
|
||||||
### 注册表文件位置
|
**注册表文件位置:**
|
||||||
|
|
||||||
- 默认路径:`unilabos/registry/devices`
|
- 默认路径:`unilabos/registry/devices`
|
||||||
- 自定义路径:启动时使用 `--registry` 参数指定
|
- 自定义路径:启动时使用 `--registry_path` 参数指定
|
||||||
- 可将多个设备写在同一个 yaml 文件中
|
- 可将多个设备写在同一个 YAML 文件中
|
||||||
|
|
||||||
### 系统自动生成的内容
|
**系统自动生成的内容:**
|
||||||
|
|
||||||
系统会自动分析您的 Python 驱动类并生成:
|
系统会自动分析您的 Python 驱动类并生成:
|
||||||
|
|
||||||
- `status_types`:从 `get_*` 方法自动识别状态属性
|
- `status_types`:从 `@property` 装饰的方法自动识别状态属性
|
||||||
- `action_value_mappings`:从类方法自动生成动作映射
|
- `action_value_mappings`:从类方法自动生成动作映射
|
||||||
- `init_param_schema`:从 `__init__` 方法分析初始化参数
|
- `init_param_schema`:从 `__init__` 方法分析初始化参数
|
||||||
- `schema`:前端显示用的属性类型定义
|
- `schema`:前端显示用的属性类型定义
|
||||||
|
|
||||||
### 完整结构概览
|
**完整结构概览:**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
my_device:
|
my_device:
|
||||||
@@ -151,4 +169,848 @@ my_device:
|
|||||||
schema: {} # 自动生成
|
schema: {} # 自动生成
|
||||||
```
|
```
|
||||||
|
|
||||||
详细的注册表编写指南和高级配置,请参考{doc}`yaml 注册表编写指南 <add_yaml>`。
|
> 💡 **提示:** 详细的注册表编写指南和高级配置,请参考 {doc}`03_add_device_registry`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Python 类结构要求
|
||||||
|
|
||||||
|
Uni-Lab 设备驱动是一个 Python 类,需要遵循以下结构:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
class MyDevice:
|
||||||
|
"""设备类文档字符串
|
||||||
|
|
||||||
|
说明设备的功能、连接方式等
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
"""初始化设备
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: 配置字典,来自图文件或注册表
|
||||||
|
"""
|
||||||
|
self.port = config.get('port', '/dev/ttyUSB0')
|
||||||
|
self.baudrate = config.get('baudrate', 9600)
|
||||||
|
self._status = "idle"
|
||||||
|
# 初始化硬件连接
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
"""设备状态(会自动广播)"""
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def my_action(self, param: float) -> Dict[str, Any]:
|
||||||
|
"""执行动作
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: 参数说明
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{"success": True, "result": ...}
|
||||||
|
"""
|
||||||
|
# 执行设备操作
|
||||||
|
return {"success": True}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 状态属性 vs 动作方法
|
||||||
|
|
||||||
|
### 状态属性(@property)
|
||||||
|
|
||||||
|
状态属性会被自动识别并定期广播:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@property
|
||||||
|
def temperature(self) -> float:
|
||||||
|
"""当前温度"""
|
||||||
|
return self._read_temperature()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
"""设备状态: idle, running, error"""
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_ready(self) -> bool:
|
||||||
|
"""设备是否就绪"""
|
||||||
|
return self._status == "idle"
|
||||||
|
```
|
||||||
|
|
||||||
|
**特点**:
|
||||||
|
|
||||||
|
- 使用`@property`装饰器
|
||||||
|
- 只读,不能有参数
|
||||||
|
- 自动添加到注册表的`status_types`
|
||||||
|
- 定期发布到 ROS2 topic
|
||||||
|
|
||||||
|
### 动作方法
|
||||||
|
|
||||||
|
动作方法是设备可以执行的操作:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def start_heating(self, target_temp: float, rate: float = 1.0) -> Dict[str, Any]:
|
||||||
|
"""开始加热
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_temp: 目标温度(°C)
|
||||||
|
rate: 升温速率(°C/min)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{"success": bool, "message": str}
|
||||||
|
"""
|
||||||
|
self._status = "heating"
|
||||||
|
self._target_temp = target_temp
|
||||||
|
# 发送命令到硬件
|
||||||
|
return {"success": True, "message": f"Heating to {target_temp}°C"}
|
||||||
|
|
||||||
|
async def async_operation(self, duration: float) -> Dict[str, Any]:
|
||||||
|
"""异步操作(长时间运行)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
duration: 持续时间(秒)
|
||||||
|
"""
|
||||||
|
# 使用 self.sleep 而不是 asyncio.sleep(ROS2 异步机制)
|
||||||
|
await self.sleep(duration)
|
||||||
|
return {"success": True}
|
||||||
|
```
|
||||||
|
|
||||||
|
**特点**:
|
||||||
|
|
||||||
|
- 普通方法或 async 方法
|
||||||
|
- 返回 Dict 类型的结果
|
||||||
|
- 自动注册为 ROS2 Action
|
||||||
|
- 支持参数和返回值
|
||||||
|
|
||||||
|
### 返回值设计指南
|
||||||
|
|
||||||
|
> **⚠️ 重要:返回值会自动显示在前端**
|
||||||
|
>
|
||||||
|
> 动作方法的返回值(字典)会自动显示在 Web 界面的工作流执行结果中。因此,**强烈建议**设计结构化、可读的返回值字典。
|
||||||
|
|
||||||
|
**推荐的返回值结构:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def my_action(self, param: float) -> Dict[str, Any]:
|
||||||
|
"""执行操作"""
|
||||||
|
try:
|
||||||
|
# 执行操作...
|
||||||
|
result = self._do_something(param)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True, # 必需:操作是否成功
|
||||||
|
"message": "操作完成", # 推荐:用户友好的消息
|
||||||
|
"result": result, # 可选:具体结果数据
|
||||||
|
"param_used": param, # 可选:记录使用的参数
|
||||||
|
# 其他有用的信息...
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"message": "操作失败"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**最佳实践示例(参考 `host_node.test_latency`):**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_latency(self) -> Dict[str, Any]:
|
||||||
|
"""测试网络延迟
|
||||||
|
|
||||||
|
返回值会在前端显示,包含详细的测试结果
|
||||||
|
"""
|
||||||
|
# 执行测试...
|
||||||
|
avg_rtt_ms = 25.5
|
||||||
|
avg_time_diff_ms = 10.2
|
||||||
|
test_count = 5
|
||||||
|
|
||||||
|
# 返回结构化的测试结果
|
||||||
|
return {
|
||||||
|
"status": "success", # 状态标识
|
||||||
|
"avg_rtt_ms": avg_rtt_ms, # 平均往返时间
|
||||||
|
"avg_time_diff_ms": avg_time_diff_ms, # 平均时间差
|
||||||
|
"max_time_error_ms": 5.3, # 最大误差
|
||||||
|
"task_delay_ms": 15.7, # 任务延迟
|
||||||
|
"test_count": test_count, # 测试次数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**前端显示效果:**
|
||||||
|
|
||||||
|
当用户在 Web 界面执行工作流时,返回的字典会以 JSON 格式显示在结果面板中:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"avg_rtt_ms": 25.5,
|
||||||
|
"avg_time_diff_ms": 10.2,
|
||||||
|
"max_time_error_ms": 5.3,
|
||||||
|
"task_delay_ms": 15.7,
|
||||||
|
"test_count": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**返回值设计建议:**
|
||||||
|
|
||||||
|
1. **始终包含 `success` 字段**:布尔值,表示操作是否成功
|
||||||
|
2. **包含 `message` 字段**:字符串,提供用户友好的描述
|
||||||
|
3. **使用有意义的键名**:使用描述性的键名(如 `avg_rtt_ms` 而不是 `v1`)
|
||||||
|
4. **包含单位**:在键名中包含单位(如 `_ms`、`_ml`、`_celsius`)
|
||||||
|
5. **记录重要参数**:返回使用的关键参数值,便于追溯
|
||||||
|
6. **错误信息详细**:失败时包含 `error` 字段和详细的错误描述
|
||||||
|
7. **避免返回大数据**:不要返回大型数组或二进制数据,这会影响前端性能
|
||||||
|
|
||||||
|
**错误处理示例:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def risky_operation(self, param: float) -> Dict[str, Any]:
|
||||||
|
"""可能失败的操作"""
|
||||||
|
if param < 0:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "参数不能为负数",
|
||||||
|
"message": f"无效参数: {param}",
|
||||||
|
"param": param
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self._execute(param)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "操作成功",
|
||||||
|
"result": result,
|
||||||
|
"param": param
|
||||||
|
}
|
||||||
|
except IOError as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "通信错误",
|
||||||
|
"message": str(e),
|
||||||
|
"device_status": self._status
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 特殊参数类型:ResourceSlot 和 DeviceSlot
|
||||||
|
|
||||||
|
Uni-Lab 提供特殊的参数类型,用于在方法中声明需要选择资源或设备。
|
||||||
|
|
||||||
|
### 导入类型
|
||||||
|
|
||||||
|
```python
|
||||||
|
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
|
||||||
|
from typing import List
|
||||||
|
```
|
||||||
|
|
||||||
|
### ResourceSlot - 资源选择
|
||||||
|
|
||||||
|
用于需要选择物料资源的场景:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def pipette_liquid(
|
||||||
|
self,
|
||||||
|
source: ResourceSlot, # 单个源容器
|
||||||
|
target: ResourceSlot, # 单个目标容器
|
||||||
|
volume: float
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""从源容器吸取液体到目标容器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source: 源容器(前端会显示资源选择下拉框)
|
||||||
|
target: 目标容器(前端会显示资源选择下拉框)
|
||||||
|
volume: 体积(μL)
|
||||||
|
"""
|
||||||
|
print(f"Pipetting {volume}μL from {source.id} to {target.id}")
|
||||||
|
return {"success": True}
|
||||||
|
```
|
||||||
|
|
||||||
|
**多选示例**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def mix_multiple(
|
||||||
|
self,
|
||||||
|
containers: List[ResourceSlot], # 多个容器选择
|
||||||
|
speed: float
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""混合多个容器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
containers: 容器列表(前端会显示多选下拉框)
|
||||||
|
speed: 混合速度
|
||||||
|
"""
|
||||||
|
for container in containers:
|
||||||
|
print(f"Mixing {container.name}")
|
||||||
|
return {"success": True}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DeviceSlot - 设备选择
|
||||||
|
|
||||||
|
用于需要选择其他设备的场景:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def coordinate_with_device(
|
||||||
|
self,
|
||||||
|
other_device: DeviceSlot, # 单个设备选择
|
||||||
|
command: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""与另一个设备协同工作
|
||||||
|
|
||||||
|
Args:
|
||||||
|
other_device: 协同设备(前端会显示设备选择下拉框)
|
||||||
|
command: 命令
|
||||||
|
"""
|
||||||
|
print(f"Coordinating with {other_device.name}")
|
||||||
|
return {"success": True}
|
||||||
|
```
|
||||||
|
|
||||||
|
**多设备示例**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def sync_devices(
|
||||||
|
self,
|
||||||
|
devices: List[DeviceSlot], # 多个设备选择
|
||||||
|
sync_signal: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""同步多个设备
|
||||||
|
|
||||||
|
Args:
|
||||||
|
devices: 设备列表(前端会显示多选下拉框)
|
||||||
|
sync_signal: 同步信号
|
||||||
|
"""
|
||||||
|
for dev in devices:
|
||||||
|
print(f"Syncing {dev.name}")
|
||||||
|
return {"success": True}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整示例:液体处理工作站
|
||||||
|
|
||||||
|
```python
|
||||||
|
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
class LiquidHandler:
|
||||||
|
"""液体处理工作站"""
|
||||||
|
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
self.simulation = config.get('simulation', False)
|
||||||
|
self._status = "idle"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def transfer_liquid(
|
||||||
|
self,
|
||||||
|
source: ResourceSlot, # 源容器选择
|
||||||
|
target: ResourceSlot, # 目标容器选择
|
||||||
|
volume: float,
|
||||||
|
tip: ResourceSlot = None # 可选的枪头选择
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""转移液体
|
||||||
|
|
||||||
|
前端效果:
|
||||||
|
- source: 下拉框,列出所有可用容器
|
||||||
|
- target: 下拉框,列出所有可用容器
|
||||||
|
- volume: 数字输入框
|
||||||
|
- tip: 下拉框(可选),列出所有枪头
|
||||||
|
"""
|
||||||
|
self._status = "transferring"
|
||||||
|
|
||||||
|
# source和target会被解析为实际的资源对象
|
||||||
|
print(f"Transferring {volume}μL")
|
||||||
|
print(f" From: {source.id} ({source.name})")
|
||||||
|
print(f" To: {target.id} ({target.name})")
|
||||||
|
|
||||||
|
if tip:
|
||||||
|
print(f" Using tip: {tip.id}")
|
||||||
|
|
||||||
|
# 执行实际的液体转移
|
||||||
|
# ...
|
||||||
|
|
||||||
|
self._status = "idle"
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"volume_transferred": volume,
|
||||||
|
"source_id": source.id,
|
||||||
|
"target_id": target.id
|
||||||
|
}
|
||||||
|
|
||||||
|
def multi_dispense(
|
||||||
|
self,
|
||||||
|
source: ResourceSlot, # 单个源
|
||||||
|
targets: List[ResourceSlot], # 多个目标
|
||||||
|
volumes: List[float]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""从一个源分配到多个目标
|
||||||
|
|
||||||
|
前端效果:
|
||||||
|
- source: 单选下拉框
|
||||||
|
- targets: 多选下拉框(可选择多个容器)
|
||||||
|
- volumes: 数组输入(每个目标对应一个体积)
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
for target, vol in zip(targets, volumes):
|
||||||
|
print(f"Dispensing {vol}μL to {target.name}")
|
||||||
|
results.append({
|
||||||
|
"target": target.id,
|
||||||
|
"volume": vol
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"dispense_results": results
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_with_balance(
|
||||||
|
self,
|
||||||
|
target: ResourceSlot, # 容器
|
||||||
|
balance: DeviceSlot # 天平设备
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""使用天平测量容器
|
||||||
|
|
||||||
|
前端效果:
|
||||||
|
- target: 容器选择下拉框
|
||||||
|
- balance: 设备选择下拉框(仅显示天平类型)
|
||||||
|
"""
|
||||||
|
print(f"Weighing {target.name} on {balance.name}")
|
||||||
|
|
||||||
|
# 可以调用balance的方法
|
||||||
|
# weight = balance.get_weight()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"container": target.id,
|
||||||
|
"balance_used": balance.id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工作原理
|
||||||
|
|
||||||
|
#### 1. 类型识别
|
||||||
|
|
||||||
|
注册表扫描方法签名时:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def my_method(self, resource: ResourceSlot, device: DeviceSlot):
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
系统识别到`ResourceSlot`和`DeviceSlot`类型。
|
||||||
|
|
||||||
|
#### 2. 自动添加 placeholder_keys
|
||||||
|
|
||||||
|
在注册表中自动生成:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
my_device:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
my_method:
|
||||||
|
goal:
|
||||||
|
resource: resource
|
||||||
|
device: device
|
||||||
|
placeholder_keys:
|
||||||
|
resource: unilabos_resources # 自动添加!
|
||||||
|
device: unilabos_devices # 自动添加!
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 前端 UI 生成
|
||||||
|
|
||||||
|
- `unilabos_resources`: 渲染为资源选择下拉框
|
||||||
|
- `unilabos_devices`: 渲染为设备选择下拉框
|
||||||
|
|
||||||
|
#### 4. 运行时解析
|
||||||
|
|
||||||
|
用户选择资源/设备后,实际调用时会传入完整的资源/设备对象:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 用户在前端选择了 plate_1
|
||||||
|
# 运行时,source参数会收到完整的Resource对象
|
||||||
|
source.id # "plate_1"
|
||||||
|
source.name # "96孔板"
|
||||||
|
source.type # "resource"
|
||||||
|
source.class_ # "corning_96_wellplate_360ul_flat"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 支持的通信方式
|
||||||
|
|
||||||
|
### 1. 串口(Serial)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import serial
|
||||||
|
|
||||||
|
class SerialDevice:
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
self.port = config['port']
|
||||||
|
self.baudrate = config.get('baudrate', 9600)
|
||||||
|
self.ser = serial.Serial(
|
||||||
|
port=self.port,
|
||||||
|
baudrate=self.baudrate,
|
||||||
|
timeout=1
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_command(self, cmd: str) -> str:
|
||||||
|
"""发送命令并读取响应"""
|
||||||
|
self.ser.write(f"{cmd}\r\n".encode())
|
||||||
|
response = self.ser.readline().decode().strip()
|
||||||
|
return response
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if hasattr(self, 'ser') and self.ser.is_open:
|
||||||
|
self.ser.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. TCP/IP Socket
|
||||||
|
|
||||||
|
```python
|
||||||
|
import socket
|
||||||
|
|
||||||
|
class TCPDevice:
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
self.host = config['host']
|
||||||
|
self.port = config['port']
|
||||||
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.sock.connect((self.host, self.port))
|
||||||
|
|
||||||
|
def send_command(self, cmd: str) -> str:
|
||||||
|
self.sock.sendall(cmd.encode())
|
||||||
|
response = self.sock.recv(1024).decode()
|
||||||
|
return response
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Modbus
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pymodbus.client import ModbusTcpClient
|
||||||
|
|
||||||
|
class ModbusDevice:
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
self.host = config['host']
|
||||||
|
self.port = config.get('port', 502)
|
||||||
|
self.client = ModbusTcpClient(self.host, port=self.port)
|
||||||
|
self.client.connect()
|
||||||
|
|
||||||
|
def read_register(self, address: int) -> int:
|
||||||
|
result = self.client.read_holding_registers(address, 1)
|
||||||
|
return result.registers[0]
|
||||||
|
|
||||||
|
def write_register(self, address: int, value: int):
|
||||||
|
self.client.write_register(address, value)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. OPC UA
|
||||||
|
|
||||||
|
```python
|
||||||
|
from opcua import Client
|
||||||
|
|
||||||
|
class OPCUADevice:
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
self.url = config['url']
|
||||||
|
self.client = Client(self.url)
|
||||||
|
self.client.connect()
|
||||||
|
|
||||||
|
def read_node(self, node_id: str):
|
||||||
|
node = self.client.get_node(node_id)
|
||||||
|
return node.get_value()
|
||||||
|
|
||||||
|
def write_node(self, node_id: str, value):
|
||||||
|
node = self.client.get_node(node_id)
|
||||||
|
node.set_value(value)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. HTTP/RPC
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class HTTPDevice:
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
self.base_url = config['url']
|
||||||
|
self.auth_token = config.get('token')
|
||||||
|
|
||||||
|
def send_command(self, endpoint: str, data: Dict) -> Dict:
|
||||||
|
url = f"{self.base_url}/{endpoint}"
|
||||||
|
headers = {'Authorization': f'Bearer {self.auth_token}'}
|
||||||
|
response = requests.post(url, json=data, headers=headers)
|
||||||
|
return response.json()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 异步 vs 同步方法
|
||||||
|
|
||||||
|
### 同步方法(适合快速操作)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def quick_operation(self, param: float) -> Dict[str, Any]:
|
||||||
|
"""快速操作,立即返回"""
|
||||||
|
result = self._do_something(param)
|
||||||
|
return {"success": True, "result": result}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异步方法(适合耗时操作)
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def long_operation(self, duration: float) -> Dict[str, Any]:
|
||||||
|
"""长时间运行的操作"""
|
||||||
|
self._status = "running"
|
||||||
|
|
||||||
|
# 使用 ROS2 提供的 sleep 方法(而不是 asyncio.sleep)
|
||||||
|
await self.sleep(duration)
|
||||||
|
|
||||||
|
# 可以在过程中发送feedback
|
||||||
|
# 需要配合ROS2 Action的feedback机制
|
||||||
|
|
||||||
|
self._status = "idle"
|
||||||
|
return {"success": True, "duration": duration}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **⚠️ 重要提示:ROS2 异步机制 vs Python asyncio**
|
||||||
|
>
|
||||||
|
> Uni-Lab 的设备驱动虽然使用 `async def` 语法,但**底层是 ROS2 的异步机制,而不是 Python 的 asyncio**。
|
||||||
|
>
|
||||||
|
> **不能使用的 asyncio 功能:**
|
||||||
|
>
|
||||||
|
> - ❌ `asyncio.sleep()` - 会导致 ROS2 事件循环阻塞
|
||||||
|
> - ❌ `asyncio.create_task()` - 任务不会被 ROS2 正确调度
|
||||||
|
> - ❌ `asyncio.gather()` - 无法与 ROS2 集成
|
||||||
|
> - ❌ 其他 asyncio 标准库函数
|
||||||
|
>
|
||||||
|
> **应该使用的方法(继承自 BaseROS2DeviceNode):**
|
||||||
|
>
|
||||||
|
> - ✅ `await self.sleep(seconds)` - ROS2 兼容的睡眠
|
||||||
|
> - ✅ `await self.create_task(func, **kwargs)` - ROS2 兼容的任务创建
|
||||||
|
> - ✅ ROS2 的 Action/Service 回调机制
|
||||||
|
>
|
||||||
|
> **示例:**
|
||||||
|
>
|
||||||
|
> ```python
|
||||||
|
> async def complex_operation(self, duration: float) -> Dict[str, Any]:
|
||||||
|
> """正确使用 ROS2 异步方法"""
|
||||||
|
> self._status = "processing"
|
||||||
|
>
|
||||||
|
> # ✅ 正确:使用 self.sleep
|
||||||
|
> await self.sleep(duration)
|
||||||
|
>
|
||||||
|
> # ✅ 正确:创建并发任务
|
||||||
|
> task = await self.create_task(self._background_work)
|
||||||
|
>
|
||||||
|
> # ❌ 错误:不要使用 asyncio
|
||||||
|
> # await asyncio.sleep(duration) # 这会导致问题!
|
||||||
|
> # task = asyncio.create_task(...) # 这也不行!
|
||||||
|
>
|
||||||
|
> self._status = "idle"
|
||||||
|
> return {"success": True}
|
||||||
|
>
|
||||||
|
> async def _background_work(self):
|
||||||
|
> """后台任务"""
|
||||||
|
> await self.sleep(1.0)
|
||||||
|
> self.lab_logger().info("Background work completed")
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> **为什么不能混用?**
|
||||||
|
>
|
||||||
|
> ROS2 使用 `rclpy` 的事件循环来管理所有异步操作。如果使用 `asyncio` 的函数,这些操作会在不同的事件循环中运行,导致:
|
||||||
|
>
|
||||||
|
> - ROS2 回调无法正确执行
|
||||||
|
> - 任务可能永远不会完成
|
||||||
|
> - 程序可能死锁或崩溃
|
||||||
|
>
|
||||||
|
> **参考实现:**
|
||||||
|
>
|
||||||
|
> `BaseROS2DeviceNode` 提供的方法定义(`base_device_node.py:563-572`):
|
||||||
|
>
|
||||||
|
> ```python
|
||||||
|
> async def sleep(self, rel_time: float, callback_group=None):
|
||||||
|
> """ROS2 兼容的异步睡眠"""
|
||||||
|
> if callback_group is None:
|
||||||
|
> callback_group = self.callback_group
|
||||||
|
> await ROS2DeviceNode.async_wait_for(self, rel_time, callback_group)
|
||||||
|
>
|
||||||
|
> @classmethod
|
||||||
|
> async def create_task(cls, func, trace_error=True, **kwargs) -> Task:
|
||||||
|
> """ROS2 兼容的任务创建"""
|
||||||
|
> return ROS2DeviceNode.run_async_func(func, trace_error, **kwargs)
|
||||||
|
> ```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
### 基本错误处理
|
||||||
|
|
||||||
|
```python
|
||||||
|
def operation_with_error_handling(self, param: float) -> Dict[str, Any]:
|
||||||
|
"""带错误处理的操作"""
|
||||||
|
try:
|
||||||
|
result = self._risky_operation(param)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"result": result
|
||||||
|
}
|
||||||
|
except ValueError as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Invalid parameter",
|
||||||
|
"message": str(e)
|
||||||
|
}
|
||||||
|
except IOError as e:
|
||||||
|
self._status = "error"
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Communication error",
|
||||||
|
"message": str(e)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义异常
|
||||||
|
|
||||||
|
```python
|
||||||
|
class DeviceError(Exception):
|
||||||
|
"""设备错误基类"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DeviceNotReadyError(DeviceError):
|
||||||
|
"""设备未就绪"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DeviceTimeoutError(DeviceError):
|
||||||
|
"""设备超时"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MyDevice:
|
||||||
|
def operation(self) -> Dict[str, Any]:
|
||||||
|
if self._status != "idle":
|
||||||
|
raise DeviceNotReadyError(f"Device is {self._status}")
|
||||||
|
|
||||||
|
# 执行操作
|
||||||
|
return {"success": True}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 类型注解
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
|
||||||
|
def method(
|
||||||
|
self,
|
||||||
|
param1: float,
|
||||||
|
param2: str,
|
||||||
|
optional_param: Optional[int] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""完整的类型注解有助于自动生成注册表"""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 文档字符串
|
||||||
|
|
||||||
|
```python
|
||||||
|
def method(self, param: float) -> Dict[str, Any]:
|
||||||
|
"""方法简短描述
|
||||||
|
|
||||||
|
更详细的说明...
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: 参数说明,包括单位和范围
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict包含:
|
||||||
|
- success (bool): 是否成功
|
||||||
|
- result (Any): 结果数据
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
DeviceError: 错误情况说明
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置验证
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, config: Dict[str, Any]):
|
||||||
|
# 验证必需参数
|
||||||
|
required = ['port', 'baudrate']
|
||||||
|
for key in required:
|
||||||
|
if key not in config:
|
||||||
|
raise ValueError(f"Missing required config: {key}")
|
||||||
|
|
||||||
|
self.port = config['port']
|
||||||
|
self.baudrate = config['baudrate']
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 资源清理
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __del__(self):
|
||||||
|
"""析构函数,清理资源"""
|
||||||
|
if hasattr(self, 'connection') and self.connection:
|
||||||
|
self.connection.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 设计前端友好的返回值
|
||||||
|
|
||||||
|
**记住:返回值会直接显示在 Web 界面**
|
||||||
|
|
||||||
|
```python
|
||||||
|
import time
|
||||||
|
|
||||||
|
def measure_temperature(self) -> Dict[str, Any]:
|
||||||
|
"""测量温度
|
||||||
|
|
||||||
|
✅ 好的返回值设计:
|
||||||
|
- 包含 success 状态
|
||||||
|
- 使用描述性键名
|
||||||
|
- 在键名中包含单位
|
||||||
|
- 记录测量时间
|
||||||
|
"""
|
||||||
|
temp = self._read_temperature()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"temperature_celsius": temp, # 键名包含单位
|
||||||
|
"timestamp": time.time(), # 记录时间
|
||||||
|
"sensor_status": "normal", # 额外状态信息
|
||||||
|
"message": f"温度测量完成: {temp}°C" # 用户友好的消息
|
||||||
|
}
|
||||||
|
|
||||||
|
def bad_example(self) -> Dict[str, Any]:
|
||||||
|
"""❌ 不好的返回值设计"""
|
||||||
|
return {
|
||||||
|
"s": True, # ❌ 键名不明确
|
||||||
|
"v": 25.5, # ❌ 没有说明单位
|
||||||
|
"t": 1234567890, # ❌ 不清楚是什么时间戳
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**参考 `host_node.test_latency` 方法**(第 1216-1340 行),它返回详细的测试结果,在前端清晰显示:
|
||||||
|
|
||||||
|
```python
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"avg_rtt_ms": 25.5, # 有意义的键名 + 单位
|
||||||
|
"avg_time_diff_ms": 10.2,
|
||||||
|
"max_time_error_ms": 5.3,
|
||||||
|
"task_delay_ms": 15.7,
|
||||||
|
"test_count": 5, # 记录重要信息
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
看完本文档后,建议继续阅读:
|
||||||
|
|
||||||
|
- {doc}`add_action` - 了解如何添加新的动作指令
|
||||||
|
- {doc}`add_yaml` - 学习如何编写和完善 YAML 注册表
|
||||||
|
|
||||||
|
进阶主题:
|
||||||
|
|
||||||
|
- {doc}`03_add_device_registry` - 了解如何配置注册表
|
||||||
|
- {doc}`04_add_device_testing` - 学习如何测试设备
|
||||||
|
- {doc}`add_old_device` - 没有 SDK 时如何开发设备驱动
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
|
||||||
|
- [Python 类型注解](https://docs.python.org/3/library/typing.html)
|
||||||
|
- [ROS2 rclpy 异步编程](https://docs.ros.org/en/humble/Tutorials/Intermediate/Writing-an-Action-Server-Client/Py.html) - Uni-Lab 使用 ROS2 的异步机制
|
||||||
|
- [串口通信](https://pyserial.readthedocs.io/)
|
||||||
|
|
||||||
|
> **注意:** 虽然设备驱动使用 `async def` 语法,但请**不要参考** Python 标准的 [asyncio 文档](https://docs.python.org/3/library/asyncio.html)。Uni-Lab 使用的是 ROS2 的异步机制,两者不兼容。请使用 `self.sleep()` 和 `self.create_task()` 等 BaseROS2DeviceNode 提供的方法。
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# 设备 Driver 开发
|
# 设备 Driver 开发(无 SDK 设备)
|
||||||
|
|
||||||
我们对设备 Driver 的定义,是一个 Python/C++/C# 类,类的方法可以用于获取传感器数据、执行设备动作、更新物料信息。它们经过 Uni-Lab 的通信中间件包装,就能成为高效分布式通信的设备节点。
|
我们对设备 Driver 的定义,是一个 Python/C++/C# 类,类的方法可以用于获取传感器数据、执行设备动作、更新物料信息。它们经过 Uni-Lab 的通信中间件包装,就能成为高效分布式通信的设备节点。
|
||||||
|
|
||||||
因此,若已有设备的 SDK (Driver),可以直接 [添加进 Uni-Lab](add_device.md)。仅当没有 SDK (Driver) 时,请参考本章作开发。
|
因此,若已有设备的 SDK (Driver),可以直接 [添加进 Uni-Lab](add_device.md)。**仅当没有 SDK (Driver) 时,请参考本章进行驱动开发。**
|
||||||
|
|
||||||
|
> **💡 提示:** 本文档介绍如何为没有现成驱动的老设备开发驱动程序。如果您的设备已经有 SDK 或驱动,请直接参考 {doc}`add_device`。
|
||||||
|
|
||||||
## 有串口字符串指令集文档的设备:Python 串口通信(常见 RS485, RS232, USB)
|
## 有串口字符串指令集文档的设备:Python 串口通信(常见 RS485, RS232, USB)
|
||||||
|
|
||||||
@@ -12,13 +14,13 @@
|
|||||||
|
|
||||||
Modbus 与 RS485、RS232 不一样的地方在于,会有更多直接寄存器的读写,以及涉及字节序转换(Big Endian, Little Endian)。
|
Modbus 与 RS485、RS232 不一样的地方在于,会有更多直接寄存器的读写,以及涉及字节序转换(Big Endian, Little Endian)。
|
||||||
|
|
||||||
Uni-Lab 开发团队在仓库中提供了3个样例:
|
Uni-Lab 开发团队在仓库中提供了 3 个样例:
|
||||||
|
|
||||||
* 单一机械设备**电夹爪**,通讯协议可见 [增广夹爪通讯协议](https://doc.rmaxis.com/docs/communication/fieldbus/),驱动代码位于 `unilabos/devices/gripper/rmaxis_v4.py`
|
- 单一机械设备**电夹爪**,通讯协议可见 [增广夹爪通讯协议](https://doc.rmaxis.com/docs/communication/fieldbus/),驱动代码位于 `unilabos/devices/gripper/rmaxis_v4.py`
|
||||||
* 单一通信设备**IO板卡**,驱动代码位于 `unilabos/device_comms/gripper/SRND_16_IO.py`
|
- 单一通信设备**IO 板卡**,驱动代码位于 `unilabos/device_comms/gripper/SRND_16_IO.py`
|
||||||
* 执行多设备复杂任务逻辑的**PLC**,Uni-Lab 提供了基于地址表的接入方式和点动工作流编写,测试代码位于 `unilabos/device_comms/modbus_plc/test/test_workflow.py`
|
- 执行多设备复杂任务逻辑的**PLC**,Uni-Lab 提供了基于地址表的接入方式和点动工作流编写,测试代码位于 `unilabos/device_comms/modbus_plc/test/test_workflow.py`
|
||||||
|
|
||||||
****
|
---
|
||||||
|
|
||||||
## 其他工业通信协议:CANopen, Ethernet, OPCUA...
|
## 其他工业通信协议:CANopen, Ethernet, OPCUA...
|
||||||
|
|
||||||
@@ -26,32 +28,32 @@ Uni-Lab 开发团队在仓库中提供了3个样例:
|
|||||||
|
|
||||||
## 没有接口的老设备老软件:使用 PyWinAuto
|
## 没有接口的老设备老软件:使用 PyWinAuto
|
||||||
|
|
||||||
**pywinauto**是一个 Python 库,用于自动化Windows GUI操作。它可以模拟用户的鼠标点击、键盘输入、窗口操作等,广泛应用于自动化测试、GUI自动化等场景。它支持通过两个后端进行操作:
|
**pywinauto**是一个 Python 库,用于自动化 Windows GUI 操作。它可以模拟用户的鼠标点击、键盘输入、窗口操作等,广泛应用于自动化测试、GUI 自动化等场景。它支持通过两个后端进行操作:
|
||||||
|
|
||||||
* **win32**后端:适用于大多数Windows应用程序,使用native Win32 API。(pywinauto_recorder默认使用win32后端)
|
- **win32**后端:适用于大多数 Windows 应用程序,使用 native Win32 API。(pywinauto_recorder 默认使用 win32 后端)
|
||||||
* **uia**后端:基于Microsoft UI Automation,适用于较新的应用程序,特别是基于WPF或UWP的应用程序。(在win10上,会有更全的目录,有的窗口win32会识别不到)
|
- **uia**后端:基于 Microsoft UI Automation,适用于较新的应用程序,特别是基于 WPF 或 UWP 的应用程序。(在 win10 上,会有更全的目录,有的窗口 win32 会识别不到)
|
||||||
|
|
||||||
### windows平台安装pywinauto和pywinauto_recorder
|
### windows 平台安装 pywinauto 和 pywinauto_recorder
|
||||||
|
|
||||||
直接安装会造成环境崩溃,需要下载并解压已经修改好的文件。
|
直接安装会造成环境崩溃,需要下载并解压已经修改好的文件。
|
||||||
|
|
||||||
cd到对应目录,执行安装
|
cd 到对应目录,执行安装
|
||||||
|
|
||||||
`pip install . -i ``https://pypi.tuna.tsinghua.edu.cn/simple`
|
` pip install . -i ``https://pypi.tuna.tsinghua.edu.cn/simple `
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
windows平台测试 python pywinauto_recorder.py,退出使用两次ctrl+alt+r取消选中,关闭命令提示符。
|
windows 平台测试 python pywinauto_recorder.py,退出使用两次 ctrl+alt+r 取消选中,关闭命令提示符。
|
||||||
|
|
||||||
### 计算器例子
|
### 计算器例子
|
||||||
|
|
||||||
你可以先打开windows的计算器,然后在ilab的环境中运行下面的代码片段,可观察到得到结果,通过这一案例,你需要掌握的pywinauto用法:
|
你可以先打开 windows 的计算器,然后在 ilab 的环境中运行下面的代码片段,可观察到得到结果,通过这一案例,你需要掌握的 pywinauto 用法:
|
||||||
|
|
||||||
* 连接到指定进程
|
- 连接到指定进程
|
||||||
* 利用dump_tree查找需要的窗口
|
- 利用 dump_tree 查找需要的窗口
|
||||||
* 获取某个位置的信息
|
- 获取某个位置的信息
|
||||||
* 模拟点击
|
- 模拟点击
|
||||||
* 模拟输入
|
- 模拟输入
|
||||||
|
|
||||||
#### 代码学习
|
#### 代码学习
|
||||||
|
|
||||||
@@ -74,39 +76,39 @@ window.dump_tree(depth=3)
|
|||||||
Dialog - '计算器' (L-419, T773, R-73, B1287)
|
Dialog - '计算器' (L-419, T773, R-73, B1287)
|
||||||
['计算器Dialog', 'Dialog', '计算器', '计算器Dialog0', '计算器Dialog1', 'Dialog0', 'Dialog1', '计算器0', '计算器1']
|
['计算器Dialog', 'Dialog', '计算器', '计算器Dialog0', '计算器Dialog1', 'Dialog0', 'Dialog1', '计算器0', '计算器1']
|
||||||
child_window(title="计算器", control_type="Window")
|
child_window(title="计算器", control_type="Window")
|
||||||
|
|
|
|
||||||
| Dialog - '计算器' (L-269, T774, R-81, B806)
|
| Dialog - '计算器' (L-269, T774, R-81, B806)
|
||||||
| ['计算器Dialog2', 'Dialog2', '计算器2']
|
| ['计算器Dialog2', 'Dialog2', '计算器2']
|
||||||
| child_window(title="计算器", auto_id="TitleBar", control_type="Window")
|
| child_window(title="计算器", auto_id="TitleBar", control_type="Window")
|
||||||
| |
|
| |
|
||||||
| | Menu - '系统' (L0, T0, R0, B0)
|
| | Menu - '系统' (L0, T0, R0, B0)
|
||||||
| | ['Menu', '系统', '系统Menu', '系统0', '系统1']
|
| | ['Menu', '系统', '系统Menu', '系统0', '系统1']
|
||||||
| | child_window(title="系统", auto_id="SystemMenuBar", control_type="MenuBar")
|
| | child_window(title="系统", auto_id="SystemMenuBar", control_type="MenuBar")
|
||||||
| |
|
| |
|
||||||
| | Button - '最小化 计算器' (L-219, T774, R-173, B806)
|
| | Button - '最小化 计算器' (L-219, T774, R-173, B806)
|
||||||
| | ['Button', '最小化 计算器Button', '最小化 计算器', 'Button0', 'Button1']
|
| | ['Button', '最小化 计算器Button', '最小化 计算器', 'Button0', 'Button1']
|
||||||
| | child_window(title="最小化 计算器", auto_id="Minimize", control_type="Button")
|
| | child_window(title="最小化 计算器", auto_id="Minimize", control_type="Button")
|
||||||
| |
|
| |
|
||||||
| | Button - '使 计算器 最大化' (L-173, T774, R-127, B806)
|
| | Button - '使 计算器 最大化' (L-173, T774, R-127, B806)
|
||||||
| | ['Button2', '使 计算器 最大化', '使 计算器 最大化Button']
|
| | ['Button2', '使 计算器 最大化', '使 计算器 最大化Button']
|
||||||
| | child_window(title="使 计算器 最大化", auto_id="Maximize", control_type="Button")
|
| | child_window(title="使 计算器 最大化", auto_id="Maximize", control_type="Button")
|
||||||
| |
|
| |
|
||||||
| | Button - '关闭 计算器' (L-127, T774, R-81, B806)
|
| | Button - '关闭 计算器' (L-127, T774, R-81, B806)
|
||||||
| | ['Button3', '关闭 计算器Button', '关闭 计算器']
|
| | ['Button3', '关闭 计算器Button', '关闭 计算器']
|
||||||
| | child_window(title="关闭 计算器", auto_id="Close", control_type="Button")
|
| | child_window(title="关闭 计算器", auto_id="Close", control_type="Button")
|
||||||
|
|
|
|
||||||
| Dialog - '计算器' (L-411, T774, R-81, B1279)
|
| Dialog - '计算器' (L-411, T774, R-81, B1279)
|
||||||
| ['计算器Dialog3', 'Dialog3', '计算器3']
|
| ['计算器Dialog3', 'Dialog3', '计算器3']
|
||||||
| child_window(title="计算器", control_type="Window")
|
| child_window(title="计算器", control_type="Window")
|
||||||
| |
|
| |
|
||||||
| | Static - '计算器' (L-363, T782, R-327, B798)
|
| | Static - '计算器' (L-363, T782, R-327, B798)
|
||||||
| | ['计算器Static', 'Static', '计算器4', 'Static0', 'Static1']
|
| | ['计算器Static', 'Static', '计算器4', 'Static0', 'Static1']
|
||||||
| | child_window(title="计算器", auto_id="AppName", control_type="Text")
|
| | child_window(title="计算器", auto_id="AppName", control_type="Text")
|
||||||
| |
|
| |
|
||||||
| | Custom - '' (L-411, T806, R-81, B1279)
|
| | Custom - '' (L-411, T806, R-81, B1279)
|
||||||
| | ['Custom', '计算器Custom']
|
| | ['Custom', '计算器Custom']
|
||||||
| | child_window(auto_id="NavView", control_type="Custom")
|
| | child_window(auto_id="NavView", control_type="Custom")
|
||||||
|
|
|
|
||||||
| Pane - '' (L-411, T806, R-81, B1279)
|
| Pane - '' (L-411, T806, R-81, B1279)
|
||||||
| ['Pane', '计算器Pane']
|
| ['Pane', '计算器Pane']
|
||||||
"""
|
"""
|
||||||
@@ -122,58 +124,58 @@ target_window.dump_tree(depth=3)
|
|||||||
Custom - '' (L-411, T806, R-81, B1279)
|
Custom - '' (L-411, T806, R-81, B1279)
|
||||||
['标准Custom', 'Custom']
|
['标准Custom', 'Custom']
|
||||||
child_window(auto_id="NavView", control_type="Custom")
|
child_window(auto_id="NavView", control_type="Custom")
|
||||||
|
|
|
|
||||||
| Button - '打开导航' (L-407, T812, R-367, B848)
|
| Button - '打开导航' (L-407, T812, R-367, B848)
|
||||||
| ['打开导航Button', '打开导航', 'Button', 'Button0', 'Button1']
|
| ['打开导航Button', '打开导航', 'Button', 'Button0', 'Button1']
|
||||||
| child_window(title="打开导航", auto_id="TogglePaneButton", control_type="Button")
|
| child_window(title="打开导航", auto_id="TogglePaneButton", control_type="Button")
|
||||||
| |
|
| |
|
||||||
| | Static - '' (L0, T0, R0, B0)
|
| | Static - '' (L0, T0, R0, B0)
|
||||||
| | ['Static', 'Static0', 'Static1']
|
| | ['Static', 'Static0', 'Static1']
|
||||||
| | child_window(auto_id="PaneTitleTextBlock", control_type="Text")
|
| | child_window(auto_id="PaneTitleTextBlock", control_type="Text")
|
||||||
|
|
|
|
||||||
| GroupBox - '' (L-411, T814, R-81, B1275)
|
| GroupBox - '' (L-411, T814, R-81, B1275)
|
||||||
| ['标准GroupBox', 'GroupBox', 'GroupBox0', 'GroupBox1']
|
| ['标准GroupBox', 'GroupBox', 'GroupBox0', 'GroupBox1']
|
||||||
| |
|
| |
|
||||||
| | Static - '表达式为 ' (L0, T0, R0, B0)
|
| | Static - '表达式为 ' (L0, T0, R0, B0)
|
||||||
| | ['表达式为 ', 'Static2', '表达式为 Static']
|
| | ['表达式为 ', 'Static2', '表达式为 Static']
|
||||||
| | child_window(title="表达式为 ", auto_id="CalculatorExpression", control_type="Text")
|
| | child_window(title="表达式为 ", auto_id="CalculatorExpression", control_type="Text")
|
||||||
| |
|
| |
|
||||||
| | Static - '显示为 0' (L-411, T875, R-81, B947)
|
| | Static - '显示为 0' (L-411, T875, R-81, B947)
|
||||||
| | ['显示为 0Static', '显示为 0', 'Static3']
|
| | ['显示为 0Static', '显示为 0', 'Static3']
|
||||||
| | child_window(title="显示为 0", auto_id="CalculatorResults", control_type="Text")
|
| | child_window(title="显示为 0", auto_id="CalculatorResults", control_type="Text")
|
||||||
| |
|
| |
|
||||||
| | Button - '打开历史记录浮出控件' (L-121, T814, R-89, B846)
|
| | Button - '打开历史记录浮出控件' (L-121, T814, R-89, B846)
|
||||||
| | ['打开历史记录浮出控件', '打开历史记录浮出控件Button', 'Button2']
|
| | ['打开历史记录浮出控件', '打开历史记录浮出控件Button', 'Button2']
|
||||||
| | child_window(title="打开历史记录浮出控件", auto_id="HistoryButton", control_type="Button")
|
| | child_window(title="打开历史记录浮出控件", auto_id="HistoryButton", control_type="Button")
|
||||||
| |
|
| |
|
||||||
| | GroupBox - '记忆控件' (L-407, T948, R-85, B976)
|
| | GroupBox - '记忆控件' (L-407, T948, R-85, B976)
|
||||||
| | ['记忆控件', '记忆控件GroupBox', 'GroupBox2']
|
| | ['记忆控件', '记忆控件GroupBox', 'GroupBox2']
|
||||||
| | child_window(title="记忆控件", auto_id="MemoryPanel", control_type="Group")
|
| | child_window(title="记忆控件", auto_id="MemoryPanel", control_type="Group")
|
||||||
| |
|
| |
|
||||||
| | GroupBox - '显示控件' (L-407, T978, R-85, B1026)
|
| | GroupBox - '显示控件' (L-407, T978, R-85, B1026)
|
||||||
| | ['显示控件', 'GroupBox3', '显示控件GroupBox']
|
| | ['显示控件', 'GroupBox3', '显示控件GroupBox']
|
||||||
| | child_window(title="显示控件", auto_id="DisplayControls", control_type="Group")
|
| | child_window(title="显示控件", auto_id="DisplayControls", control_type="Group")
|
||||||
| |
|
| |
|
||||||
| | GroupBox - '标准函数' (L-407, T1028, R-166, B1076)
|
| | GroupBox - '标准函数' (L-407, T1028, R-166, B1076)
|
||||||
| | ['标准函数', '标准函数GroupBox', 'GroupBox4']
|
| | ['标准函数', '标准函数GroupBox', 'GroupBox4']
|
||||||
| | child_window(title="标准函数", auto_id="StandardFunctions", control_type="Group")
|
| | child_window(title="标准函数", auto_id="StandardFunctions", control_type="Group")
|
||||||
| |
|
| |
|
||||||
| | GroupBox - '标准运算符' (L-164, T1028, R-85, B1275)
|
| | GroupBox - '标准运算符' (L-164, T1028, R-85, B1275)
|
||||||
| | ['标准运算符', '标准运算符GroupBox', 'GroupBox5']
|
| | ['标准运算符', '标准运算符GroupBox', 'GroupBox5']
|
||||||
| | child_window(title="标准运算符", auto_id="StandardOperators", control_type="Group")
|
| | child_window(title="标准运算符", auto_id="StandardOperators", control_type="Group")
|
||||||
| |
|
| |
|
||||||
| | GroupBox - '数字键盘' (L-407, T1078, R-166, B1275)
|
| | GroupBox - '数字键盘' (L-407, T1078, R-166, B1275)
|
||||||
| | ['GroupBox6', '数字键盘', '数字键盘GroupBox']
|
| | ['GroupBox6', '数字键盘', '数字键盘GroupBox']
|
||||||
| | child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
| | child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
||||||
| |
|
| |
|
||||||
| | Button - '正负' (L-407, T1228, R-328, B1275)
|
| | Button - '正负' (L-407, T1228, R-328, B1275)
|
||||||
| | ['Button32', '正负Button', '正负']
|
| | ['Button32', '正负Button', '正负']
|
||||||
| | child_window(title="正负", auto_id="negateButton", control_type="Button")
|
| | child_window(title="正负", auto_id="negateButton", control_type="Button")
|
||||||
|
|
|
|
||||||
| Static - '标准' (L-363, T815, R-322, B842)
|
| Static - '标准' (L-363, T815, R-322, B842)
|
||||||
| ['标准', '标准Static', 'Static4']
|
| ['标准', '标准Static', 'Static4']
|
||||||
| child_window(title="标准", auto_id="Header", control_type="Text")
|
| child_window(title="标准", auto_id="Header", control_type="Text")
|
||||||
|
|
|
|
||||||
| Button - '始终置顶' (L-312, T814, R-280, B846)
|
| Button - '始终置顶' (L-312, T814, R-280, B846)
|
||||||
| ['始终置顶Button', '始终置顶', 'Button33']
|
| ['始终置顶Button', '始终置顶', 'Button33']
|
||||||
| child_window(title="始终置顶", auto_id="NormalAlwaysOnTopButton", control_type="Button")
|
| child_window(title="始终置顶", auto_id="NormalAlwaysOnTopButton", control_type="Button")
|
||||||
@@ -187,47 +189,47 @@ numpad.dump_tree(depth=2)
|
|||||||
GroupBox - '数字键盘' (L-334, T1350, R-93, B1547)
|
GroupBox - '数字键盘' (L-334, T1350, R-93, B1547)
|
||||||
['GroupBox', '数字键盘', '数字键盘GroupBox']
|
['GroupBox', '数字键盘', '数字键盘GroupBox']
|
||||||
child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
||||||
|
|
|
|
||||||
| Button - '零' (L-253, T1500, R-174, B1547)
|
| Button - '零' (L-253, T1500, R-174, B1547)
|
||||||
| ['零Button', 'Button', '零', 'Button0', 'Button1']
|
| ['零Button', 'Button', '零', 'Button0', 'Button1']
|
||||||
| child_window(title="零", auto_id="num0Button", control_type="Button")
|
| child_window(title="零", auto_id="num0Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '一' (L-334, T1450, R-255, B1498)
|
| Button - '一' (L-334, T1450, R-255, B1498)
|
||||||
| ['一Button', 'Button2', '一']
|
| ['一Button', 'Button2', '一']
|
||||||
| child_window(title="一", auto_id="num1Button", control_type="Button")
|
| child_window(title="一", auto_id="num1Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '二' (L-253, T1450, R-174, B1498)
|
| Button - '二' (L-253, T1450, R-174, B1498)
|
||||||
| ['Button3', '二', '二Button']
|
| ['Button3', '二', '二Button']
|
||||||
| child_window(title="二", auto_id="num2Button", control_type="Button")
|
| child_window(title="二", auto_id="num2Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '三' (L-172, T1450, R-93, B1498)
|
| Button - '三' (L-172, T1450, R-93, B1498)
|
||||||
| ['Button4', '三', '三Button']
|
| ['Button4', '三', '三Button']
|
||||||
| child_window(title="三", auto_id="num3Button", control_type="Button")
|
| child_window(title="三", auto_id="num3Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '四' (L-334, T1400, R-255, B1448)
|
| Button - '四' (L-334, T1400, R-255, B1448)
|
||||||
| ['四', 'Button5', '四Button']
|
| ['四', 'Button5', '四Button']
|
||||||
| child_window(title="四", auto_id="num4Button", control_type="Button")
|
| child_window(title="四", auto_id="num4Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '五' (L-253, T1400, R-174, B1448)
|
| Button - '五' (L-253, T1400, R-174, B1448)
|
||||||
| ['Button6', '五Button', '五']
|
| ['Button6', '五Button', '五']
|
||||||
| child_window(title="五", auto_id="num5Button", control_type="Button")
|
| child_window(title="五", auto_id="num5Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '六' (L-172, T1400, R-93, B1448)
|
| Button - '六' (L-172, T1400, R-93, B1448)
|
||||||
| ['六Button', 'Button7', '六']
|
| ['六Button', 'Button7', '六']
|
||||||
| child_window(title="六", auto_id="num6Button", control_type="Button")
|
| child_window(title="六", auto_id="num6Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '七' (L-334, T1350, R-255, B1398)
|
| Button - '七' (L-334, T1350, R-255, B1398)
|
||||||
| ['Button8', '七Button', '七']
|
| ['Button8', '七Button', '七']
|
||||||
| child_window(title="七", auto_id="num7Button", control_type="Button")
|
| child_window(title="七", auto_id="num7Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '八' (L-253, T1350, R-174, B1398)
|
| Button - '八' (L-253, T1350, R-174, B1398)
|
||||||
| ['八', 'Button9', '八Button']
|
| ['八', 'Button9', '八Button']
|
||||||
| child_window(title="八", auto_id="num8Button", control_type="Button")
|
| child_window(title="八", auto_id="num8Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '九' (L-172, T1350, R-93, B1398)
|
| Button - '九' (L-172, T1350, R-93, B1398)
|
||||||
| ['Button10', '九', '九Button']
|
| ['Button10', '九', '九Button']
|
||||||
| child_window(title="九", auto_id="num9Button", control_type="Button")
|
| child_window(title="九", auto_id="num9Button", control_type="Button")
|
||||||
|
|
|
|
||||||
| Button - '十进制分隔符' (L-172, T1500, R-93, B1547)
|
| Button - '十进制分隔符' (L-172, T1500, R-93, B1547)
|
||||||
| ['十进制分隔符Button', 'Button11', '十进制分隔符']
|
| ['十进制分隔符Button', 'Button11', '十进制分隔符']
|
||||||
| child_window(title="十进制分隔符", auto_id="decimalSeparatorButton", control_type="Button")
|
| child_window(title="十进制分隔符", auto_id="decimalSeparatorButton", control_type="Button")
|
||||||
@@ -262,13 +264,13 @@ r, g, b = pyautogui.pixel(point_x, point_y)
|
|||||||
|
|
||||||
### pywinauto_recorder
|
### pywinauto_recorder
|
||||||
|
|
||||||
pywinauto_recorder是一个配合 pywinauto 使用的工具,用于录制用户的操作,并生成相应的 pywinauto 脚本。这对于一些暂时无法直接调用DLL的函数并且需要模拟用户操作的场景非常有用。同时,可以省去仅用pywinauto的一些查找UI步骤。
|
pywinauto_recorder 是一个配合 pywinauto 使用的工具,用于录制用户的操作,并生成相应的 pywinauto 脚本。这对于一些暂时无法直接调用 DLL 的函数并且需要模拟用户操作的场景非常有用。同时,可以省去仅用 pywinauto 的一些查找 UI 步骤。
|
||||||
|
|
||||||
#### 运行尝试
|
#### 运行尝试
|
||||||
|
|
||||||
请参照 上手尝试-环境创建-3 开启pywinauto_recorder
|
请参照 上手尝试-环境创建-3 开启 pywinauto_recorder
|
||||||
|
|
||||||
例如我们这里先启动一个windows自带的计算器软件
|
例如我们这里先启动一个 windows 自带的计算器软件
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -286,7 +288,7 @@ with UIPath(u"计算器||Window"):
|
|||||||
click(u"九||Button")
|
click(u"九||Button")
|
||||||
```
|
```
|
||||||
|
|
||||||
执行该python脚本,可以观察到新开启的计算器被点击了数字9
|
执行该 python 脚本,可以观察到新开启的计算器被点击了数字 9
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -308,23 +310,38 @@ window.dump_tree(depth=[int类型数字], filename=None)
|
|||||||
GroupBox - '数字键盘' (L-334, T1350, R-93, B1547)
|
GroupBox - '数字键盘' (L-334, T1350, R-93, B1547)
|
||||||
['GroupBox', '数字键盘', '数字键盘GroupBox']
|
['GroupBox', '数字键盘', '数字键盘GroupBox']
|
||||||
child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
child_window(title="数字键盘", auto_id="NumberPad", control_type="Group")
|
||||||
|
|
|
|
||||||
| Button - '零' (L-253, T1500, R-174, B1547)
|
| Button - '零' (L-253, T1500, R-174, B1547)
|
||||||
| ['零Button', 'Button', '零', 'Button0', 'Button1']
|
| ['零Button', 'Button', '零', 'Button0', 'Button1']
|
||||||
| child_window(title="零", auto_id="num0Button", control_type="Button")
|
| child_window(title="零", auto_id="num0Button", control_type="Button")
|
||||||
"""
|
"""
|
||||||
```
|
```
|
||||||
|
|
||||||
这里以上面计算器的例子对dump_tree进行解读
|
这里以上面计算器的例子对 dump_tree 进行解读
|
||||||
|
|
||||||
2~4行为当前对象的窗口
|
2~4 行为当前对象的窗口
|
||||||
|
|
||||||
* 第2行分别是窗体的类型 `GroupBox`,窗体的题目 `数字键盘`,窗体的矩形区域坐标,对应的是屏幕上的位置(左、上、右、下)
|
- 第 2 行分别是窗体的类型 `GroupBox`,窗体的题目 `数字键盘`,窗体的矩形区域坐标,对应的是屏幕上的位置(左、上、右、下)
|
||||||
* 第3行是 `['GroupBox', '数字键盘', '数字键盘GroupBox']`,为控件的标识符列表,可以选择任意一个,使用 `child_window(best_match="标识符")`来获取该窗口
|
- 第 3 行是 `['GroupBox', '数字键盘', '数字键盘GroupBox']`,为控件的标识符列表,可以选择任意一个,使用 `child_window(best_match="标识符")`来获取该窗口
|
||||||
* 第4行是获取该控件的方法,请注意该方法不能保证获取唯一,`title`如果是变化的,也需要删除 `title`参数
|
- 第 4 行是获取该控件的方法,请注意该方法不能保证获取唯一,`title`如果是变化的,也需要删除 `title`参数
|
||||||
|
|
||||||
6~8行为当前对象窗口所包含的子窗口信息,信息类型对应2~4行
|
6~8 行为当前对象窗口所包含的子窗口信息,信息类型对应 2~4 行
|
||||||
|
|
||||||
### 窗口获取注意事项
|
### 窗口获取注意事项
|
||||||
|
|
||||||
1. 在 `child_window`的时候,并不会立刻报错,只有在执行窗口的信息获取时才会调用,查询窗口是否存在,因此要想确定 `child_window`是否正确,可以调用子窗口对象的属性 `element_info`,来保证窗口存在
|
1. 在 `child_window`的时候,并不会立刻报错,只有在执行窗口的信息获取时才会调用,查询窗口是否存在,因此要想确定 `child_window`是否正确,可以调用子窗口对象的属性 `element_info`,来保证窗口存在
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
完成设备驱动开发后,建议继续阅读:
|
||||||
|
|
||||||
|
- {doc}`add_device` - 了解如何将驱动添加到 Uni-Lab 中
|
||||||
|
- {doc}`add_action` - 学习如何添加新的动作指令
|
||||||
|
- {doc}`add_yaml` - 编写和完善 YAML 注册表
|
||||||
|
|
||||||
|
进阶主题:
|
||||||
|
|
||||||
|
- {doc}`03_add_device_registry` - 详细的注册表配置
|
||||||
|
- {doc}`04_add_device_testing` - 设备测试指南
|
||||||
1118
docs/developer_guide/add_registry.md
Normal file
@@ -1,6 +1,17 @@
|
|||||||
# 电池装配工站接入(PLC)
|
# 实例:电池装配工站接入(PLC控制)
|
||||||
|
|
||||||
本指南将引导你完成电池装配工站(以 PLC 控制为例)的接入流程,包括新建工站文件、编写驱动与寄存器读写、生成注册表、上传及注意事项。
|
> **文档类型**:实际应用案例
|
||||||
|
> **适用场景**:使用 PLC 控制的电池装配工站接入
|
||||||
|
> **前置知识**:{doc}`../add_device` | {doc}`../add_registry`
|
||||||
|
|
||||||
|
本指南以电池装配工站为实际案例,引导你完成 PLC 控制设备的完整接入流程,包括新建工站文件、编写驱动与寄存器读写、生成注册表、上传及注意事项。
|
||||||
|
|
||||||
|
## 案例概述
|
||||||
|
|
||||||
|
**设备类型**:电池装配工站
|
||||||
|
**通信方式**:Modbus TCP (PLC)
|
||||||
|
**工站基类**:`WorkstationBase`
|
||||||
|
**主要功能**:电池组装、寄存器读写、数据采集
|
||||||
|
|
||||||
## 1. 新建工站文件
|
## 1. 新建工站文件
|
||||||
|
|
||||||
@@ -93,10 +104,12 @@ python unilabos\app\main.py -g celljson.json --ak <user的AK> --sk <user的SK>
|
|||||||
```
|
```
|
||||||
|
|
||||||
点击注册表编辑,进入注册表编辑页面
|
点击注册表编辑,进入注册表编辑页面
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
按照图示步骤填写自动生成注册表信息:
|
按照图示步骤填写自动生成注册表信息:
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
步骤说明:
|
步骤说明:
|
||||||
1. 选择新增的工站`coin_cell_assembly.py`文件
|
1. 选择新增的工站`coin_cell_assembly.py`文件
|
||||||
@@ -107,8 +120,9 @@ python unilabos\app\main.py -g celljson.json --ak <user的AK> --sk <user的SK>
|
|||||||
6. 填写新的工站注册表备注信息
|
6. 填写新的工站注册表备注信息
|
||||||
7. 生成注册表
|
7. 生成注册表
|
||||||
|
|
||||||
以上操作步骤完成,则会生成的新的注册表ymal文件,如下图:
|
以上操作步骤完成,则会生成的新的注册表YAML文件,如下图:
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -134,14 +148,60 @@ python unilabos\app\main.py -g celljson.json --ak <user的AK> --sk <user的SK> -
|
|||||||
|
|
||||||
## 4. 注意事项
|
## 4. 注意事项
|
||||||
|
|
||||||
- 在新生成的 YAML 中,确认 `module` 指向新工站类,本例中需检查`coincellassemblyworkstation_device.yaml`文件中是否指向了`coin_cell_assembly.py`文件中定义的`CoinCellAssemblyWorkstation`类文件:
|
### 4.1 验证模块路径
|
||||||
|
|
||||||
```
|
在新生成的 YAML 中,确认 `module` 指向新工站类。本例中需检查 `coincellassemblyworkstation_device.yaml` 文件中是否正确指向了 `CoinCellAssemblyWorkstation` 类:
|
||||||
|
|
||||||
|
```yaml
|
||||||
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
||||||
```
|
```
|
||||||
|
|
||||||
- 首次新增设备(或资源)需要在网页端新增注册表信息,`--complete_registry`补全注册表,`--upload_registry`上传注册表信息。
|
### 4.2 首次接入流程
|
||||||
|
|
||||||
- 如果不是新增设备(或资源),仅对工站驱动的.py文件进行了修改,则不需要在网页端新增注册表信息。只需要运行补全注册表信息之后,上传注册表即可。
|
首次新增设备(或资源)需要完整流程:
|
||||||
|
1. ✅ 在网页端生成注册表信息
|
||||||
|
2. ✅ 使用 `--complete_registry` 补全注册表
|
||||||
|
3. ✅ 使用 `--upload_registry` 上传注册表信息
|
||||||
|
|
||||||
|
### 4.3 驱动更新流程
|
||||||
|
|
||||||
|
如果不是新增设备,仅修改了工站驱动的 `.py` 文件:
|
||||||
|
1. ✅ 运行 `--complete_registry` 补全注册表
|
||||||
|
2. ✅ 运行 `--upload_registry` 上传注册表
|
||||||
|
3. ❌ 不需要在网页端重新生成注册表
|
||||||
|
|
||||||
|
### 4.4 PLC通信注意事项
|
||||||
|
|
||||||
|
- **握手机制**:若需参数下发,建议在 PLC 端设置标志寄存器并完成握手复位,避免粘连与竞争
|
||||||
|
- **字节序**:FLOAT32 等多字节数据类型需要正确指定字节序(如 `WorderOrder.LITTLE`)
|
||||||
|
- **寄存器映射**:确保 CSV 文件中的寄存器地址与 PLC 实际配置一致
|
||||||
|
- **连接稳定性**:在初始化时检查 PLC 连接状态,建议添加重连机制
|
||||||
|
|
||||||
|
## 5. 扩展阅读
|
||||||
|
|
||||||
|
### 相关文档
|
||||||
|
|
||||||
|
- {doc}`../add_device` - 设备驱动编写通用指南
|
||||||
|
- {doc}`../add_registry` - 注册表配置完整指南
|
||||||
|
- {doc}`../workstation_architecture` - 工站架构详解
|
||||||
|
|
||||||
|
### 技术要点
|
||||||
|
|
||||||
|
- **Modbus TCP 通信**:PLC 通信协议和寄存器读写
|
||||||
|
- **WorkstationBase**:工站基类的继承和使用
|
||||||
|
- **寄存器映射**:CSV 格式的寄存器配置
|
||||||
|
- **注册表生成**:自动化工具使用
|
||||||
|
|
||||||
|
## 6. 总结
|
||||||
|
|
||||||
|
通过本案例,你应该掌握:
|
||||||
|
|
||||||
|
1. ✅ 如何创建 PLC 控制的工站驱动
|
||||||
|
2. ✅ Modbus TCP 通信和寄存器读写
|
||||||
|
3. ✅ 使用可视化编辑器生成注册表
|
||||||
|
4. ✅ 注册表的补全和上传流程
|
||||||
|
5. ✅ 新增设备与更新驱动的区别
|
||||||
|
|
||||||
|
这个案例展示了完整的 PLC 设备接入流程,可以作为其他类似设备接入的参考模板。
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 428 KiB After Width: | Height: | Size: 428 KiB |
|
Before Width: | Height: | Size: 310 KiB After Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
@@ -1,4 +1,8 @@
|
|||||||
# 物料构建指南
|
# 实例:物料构建指南
|
||||||
|
|
||||||
|
> **文档类型**:物料系统实战指南
|
||||||
|
> **适用场景**:工作站物料系统构建、Deck/Warehouse/Carrier/Bottle 配置
|
||||||
|
> **前置知识**:PyLabRobot 基础 | 资源管理概念
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
# 物料教程(Resource)
|
# 实例:物料教程(Resource)
|
||||||
|
|
||||||
本教程面向 Uni-Lab-OS 的开发者,讲解“物料”的核心概念、3种物料格式(UniLab、PyLabRobot、奔耀Bioyond)及其相互转换方法,并说明4种 children 结构表现形式及使用场景。
|
> **文档类型**:物料系统完整教程
|
||||||
|
> **适用场景**:物料格式转换、多系统物料对接、资源结构理解
|
||||||
|
> **前置知识**:Python 基础 | JSON 数据结构
|
||||||
|
|
||||||
|
本教程面向 Uni-Lab-OS 的开发者,讲解"物料"的核心概念、3种物料格式(UniLab、PyLabRobot、奔耀Bioyond)及其相互转换方法,并说明4种 children 结构表现形式及使用场景。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
# 工作站模板架构设计与对接指南
|
# 实例:工作站模板架构设计与对接指南
|
||||||
|
|
||||||
|
> **文档类型**:架构设计指南与实战案例
|
||||||
|
> **适用场景**:大型工作站接入、子设备管理、物料系统集成
|
||||||
|
> **前置知识**:{doc}`../add_device` | {doc}`../add_registry`
|
||||||
|
|
||||||
## 0. 问题简介
|
## 0. 问题简介
|
||||||
|
|
||||||
@@ -6,9 +10,9 @@
|
|||||||
|
|
||||||
### 0.1 自研常量有机工站:最重要的是子设备管理和通信转发
|
### 0.1 自研常量有机工站:最重要的是子设备管理和通信转发
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
这类工站由开发者自研,组合所有子设备和实验耗材、希望让他们在工作站这一级协调配合;
|
这类工站由开发者自研,组合所有子设备和实验耗材、希望让他们在工作站这一级协调配合;
|
||||||
|
|
||||||
@@ -18,7 +22,7 @@
|
|||||||
|
|
||||||
### 0.2 移液工作站:物料系统和工作流模板管理
|
### 0.2 移液工作站:物料系统和工作流模板管理
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
1. 绝大多数情况没有子设备,有时候选配恒温震荡等模块时,接口也由工作站提供
|
1. 绝大多数情况没有子设备,有时候选配恒温震荡等模块时,接口也由工作站提供
|
||||||
2. 所有任务系统均由工作站本身实现并下发指令,有统一的抽象函数可实现(pick_up_tips, aspirate, dispense, transfer 等)。有时需要将这些指令组合、转化为工作站的脚本语言,再统一下发。因此会形成大量固定的 protocols。
|
2. 所有任务系统均由工作站本身实现并下发指令,有统一的抽象函数可实现(pick_up_tips, aspirate, dispense, transfer 等)。有时需要将这些指令组合、转化为工作站的脚本语言,再统一下发。因此会形成大量固定的 protocols。
|
||||||
@@ -26,7 +30,7 @@
|
|||||||
|
|
||||||
### 0.3 厂家开发的定制大型工站
|
### 0.3 厂家开发的定制大型工站
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
由厂家开发,具备完善的物料系统、任务系统甚至调度系统;由 PLC 或 OpenAPI TCP 协议统一通信
|
由厂家开发,具备完善的物料系统、任务系统甚至调度系统;由 PLC 或 OpenAPI TCP 协议统一通信
|
||||||
|
|
||||||
595
docs/developer_guide/networking_overview.md
Normal file
@@ -0,0 +1,595 @@
|
|||||||
|
# 组网部署与主从模式配置
|
||||||
|
|
||||||
|
本文档介绍 Uni-Lab-OS 的组网架构、部署方式和主从模式的详细配置。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- [架构概览](#架构概览)
|
||||||
|
- [节点类型](#节点类型)
|
||||||
|
- [通信机制](#通信机制)
|
||||||
|
- [典型拓扑](#典型拓扑)
|
||||||
|
- [主从模式配置](#主从模式配置)
|
||||||
|
- [网络配置](#网络配置)
|
||||||
|
- [示例:多房间部署](#示例多房间部署)
|
||||||
|
- [故障处理](#故障处理)
|
||||||
|
- [监控和维护](#监控和维护)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架构概览
|
||||||
|
|
||||||
|
Uni-Lab-OS 支持多种部署模式:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ Cloud Platform/Self-hosted Platform │
|
||||||
|
│ uni-lab.bohrium.com │
|
||||||
|
│ (Resource Management, Task Scheduling, │
|
||||||
|
│ Monitoring) │
|
||||||
|
└────────────────────┬─────────────────────────┘
|
||||||
|
│ WebSocket / HTTP
|
||||||
|
│
|
||||||
|
┌──────────┴──────────┐
|
||||||
|
│ │
|
||||||
|
┌────▼─────┐ ┌────▼─────┐
|
||||||
|
│ Master │◄──ROS2──►│ Slave │
|
||||||
|
│ Node │ │ Node │
|
||||||
|
│ (Host) │ │ (Slave) │
|
||||||
|
└────┬─────┘ └────┬─────┘
|
||||||
|
│ │
|
||||||
|
┌────┴────┐ ┌────┴────┐
|
||||||
|
│ Device A│ │ Device B│
|
||||||
|
│ Device C│ │ Device D│
|
||||||
|
└─────────┘ └─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 节点类型
|
||||||
|
|
||||||
|
### 主节点(Host Node)
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
|
||||||
|
- 创建和管理全局资源
|
||||||
|
- 提供 host_node 服务
|
||||||
|
- 连接云端平台
|
||||||
|
- 协调多个从节点
|
||||||
|
- 提供 Web 管理界面
|
||||||
|
|
||||||
|
**启动命令**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --ak your_ak --sk your_sk -g host_devices.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 从节点(Slave Node)
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
|
||||||
|
- 管理本地设备
|
||||||
|
- 不连接云端(可选)
|
||||||
|
- 向主节点注册
|
||||||
|
- 执行分配的任务
|
||||||
|
|
||||||
|
**启动命令**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --ak your_ak --sk your_sk -g slave_devices.json --is_slave
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 通信机制
|
||||||
|
|
||||||
|
### ROS2 通信
|
||||||
|
|
||||||
|
**用途**: 节点间实时通信
|
||||||
|
|
||||||
|
**通信方式**:
|
||||||
|
|
||||||
|
- **Topic**: 状态广播(设备状态、传感器数据)
|
||||||
|
- **Service**: 同步请求(资源查询、配置获取)
|
||||||
|
- **Action**: 异步任务(设备操作、长时间运行)
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看ROS2节点
|
||||||
|
ros2 node list
|
||||||
|
|
||||||
|
# 查看topic
|
||||||
|
ros2 topic list
|
||||||
|
|
||||||
|
# 查看action
|
||||||
|
ros2 action list
|
||||||
|
```
|
||||||
|
|
||||||
|
### WebSocket 通信
|
||||||
|
|
||||||
|
**用途**: 主节点与云端通信
|
||||||
|
|
||||||
|
**特点**:
|
||||||
|
|
||||||
|
- 实时双向通信
|
||||||
|
- 自动重连
|
||||||
|
- 心跳保持
|
||||||
|
|
||||||
|
**配置**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# local_config.py
|
||||||
|
BasicConfig.ak = "your_ak"
|
||||||
|
BasicConfig.sk = "your_sk"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 典型拓扑
|
||||||
|
|
||||||
|
### 单节点模式
|
||||||
|
|
||||||
|
**适用场景**: 小型实验室、开发测试
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┐
|
||||||
|
│ Uni-Lab Node │
|
||||||
|
│ ┌────────────┐ │
|
||||||
|
│ │ Device A │ │
|
||||||
|
│ │ Device B │ │
|
||||||
|
│ │ Device C │ │
|
||||||
|
│ └────────────┘ │
|
||||||
|
└──────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
|
||||||
|
- 配置简单
|
||||||
|
- 无网络延迟
|
||||||
|
- 适合快速原型
|
||||||
|
|
||||||
|
**启动**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --ak your_ak --sk your_sk -g all_devices.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 主从模式
|
||||||
|
|
||||||
|
**适用场景**: 多房间、分布式设备
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌──────────────┐
|
||||||
|
│ Master Node │◄────►│ Slave Node 1 │
|
||||||
|
│ Coordinator │ │ Liquid │
|
||||||
|
│ Web UI │ │ Handling │
|
||||||
|
└──────┬──────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
│ ┌──────────────┐
|
||||||
|
└────────────►│ Slave Node 2 │
|
||||||
|
│ Analytical │
|
||||||
|
│ (NMR/GC) │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
|
||||||
|
- 物理分隔
|
||||||
|
- 独立故障域
|
||||||
|
- 易于扩展
|
||||||
|
|
||||||
|
**适用场景**:
|
||||||
|
|
||||||
|
- 设备物理位置分散
|
||||||
|
- 不同房间的设备
|
||||||
|
- 需要独立故障域
|
||||||
|
- 分阶段扩展系统
|
||||||
|
|
||||||
|
**主节点**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --ak your_ak --sk your_sk -g host.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**从节点**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab --ak your_ak --sk your_sk -g slave1.json --is_slave
|
||||||
|
unilab --ak your_ak --sk your_sk -g slave2.json --is_slave --port 8003
|
||||||
|
```
|
||||||
|
|
||||||
|
### 云端集成模式
|
||||||
|
|
||||||
|
**适用场景**: 远程监控、多实验室协作
|
||||||
|
|
||||||
|
```
|
||||||
|
Cloud Platform
|
||||||
|
│
|
||||||
|
┌───────┴────────┐
|
||||||
|
│ │
|
||||||
|
Laboratory A Laboratory B
|
||||||
|
(Master Node) (Master Node)
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
|
||||||
|
- 远程访问
|
||||||
|
- 数据同步
|
||||||
|
- 任务调度
|
||||||
|
|
||||||
|
**启动**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 实验室A
|
||||||
|
unilab --ak your_ak --sk your_sk --upload_registry --use_remote_resource
|
||||||
|
|
||||||
|
# 实验室B
|
||||||
|
unilab --ak your_ak --sk your_sk --upload_registry --use_remote_resource
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 主从模式配置
|
||||||
|
|
||||||
|
### 主节点配置
|
||||||
|
|
||||||
|
#### 1. 创建主节点设备图
|
||||||
|
|
||||||
|
`host.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nodes": [],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 启动主节点
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基本启动
|
||||||
|
unilab --ak your_ak --sk your_sk -g host.json
|
||||||
|
|
||||||
|
# 带云端集成
|
||||||
|
unilab --ak your_ak --sk your_sk -g host.json --upload_registry
|
||||||
|
|
||||||
|
# 指定端口
|
||||||
|
unilab --ak your_ak --sk your_sk -g host.json --port 8002
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 验证主节点
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查ROS2节点
|
||||||
|
ros2 node list
|
||||||
|
# 应该看到 /host_node
|
||||||
|
|
||||||
|
# 检查服务
|
||||||
|
ros2 service list | grep host_node
|
||||||
|
|
||||||
|
# Web界面
|
||||||
|
# 访问 http://localhost:8002
|
||||||
|
```
|
||||||
|
|
||||||
|
### 从节点配置
|
||||||
|
|
||||||
|
#### 1. 创建从节点设备图
|
||||||
|
|
||||||
|
`slave1.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "liquid_handler_1",
|
||||||
|
"name": "液体处理工作站",
|
||||||
|
"type": "device",
|
||||||
|
"class": "liquid_handler",
|
||||||
|
"config": {
|
||||||
|
"simulation": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 启动从节点
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基本从节点启动
|
||||||
|
unilab --ak your_ak --sk your_sk -g slave1.json --is_slave
|
||||||
|
|
||||||
|
# 指定不同端口(如果多个从节点在同一台机器)
|
||||||
|
unilab --ak your_ak --sk your_sk -g slave1.json --is_slave --port 8003
|
||||||
|
|
||||||
|
# 跳过等待主节点(独立测试)
|
||||||
|
unilab --ak your_ak --sk your_sk -g slave1.json --is_slave --slave_no_host
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 验证从节点
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查节点连接
|
||||||
|
ros2 node list
|
||||||
|
|
||||||
|
# 检查设备状态
|
||||||
|
ros2 topic echo /liquid_handler_1/status
|
||||||
|
```
|
||||||
|
|
||||||
|
### 跨节点通信
|
||||||
|
|
||||||
|
#### 资源访问
|
||||||
|
|
||||||
|
主节点可以访问从节点的资源:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在主节点或其他节点调用从节点设备
|
||||||
|
ros2 action send_goal /liquid_handler_1/transfer_liquid \
|
||||||
|
unilabos_msgs/action/TransferLiquid \
|
||||||
|
"{source: {...}, target: {...}, volume: 100.0}"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 状态监控
|
||||||
|
|
||||||
|
主节点监控所有从节点状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 订阅从节点状态
|
||||||
|
ros2 topic echo /liquid_handler_1/status
|
||||||
|
|
||||||
|
# 查看所有设备状态
|
||||||
|
ros2 topic list | grep status
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 网络配置
|
||||||
|
|
||||||
|
### ROS2 DDS 配置
|
||||||
|
|
||||||
|
确保主从节点在同一网络:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查网络可达性
|
||||||
|
ping <slave_node_ip>
|
||||||
|
|
||||||
|
# 设置ROS_DOMAIN_ID(可选,用于隔离)
|
||||||
|
export ROS_DOMAIN_ID=42
|
||||||
|
```
|
||||||
|
|
||||||
|
### 防火墙配置
|
||||||
|
|
||||||
|
**建议做法**:
|
||||||
|
|
||||||
|
为了确保 ROS2 DDS 通信正常,建议直接关闭防火墙,而不是配置特定端口。ROS2 使用动态端口范围,配置特定端口可能导致通信问题。
|
||||||
|
|
||||||
|
**Linux**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 关闭防火墙
|
||||||
|
sudo ufw disable
|
||||||
|
|
||||||
|
# 或者临时停止防火墙
|
||||||
|
sudo systemctl stop ufw
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows**:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 在Windows安全中心关闭防火墙
|
||||||
|
# 控制面板 -> 系统和安全 -> Windows Defender 防火墙 -> 启用或关闭Windows Defender防火墙
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证网络连通性
|
||||||
|
|
||||||
|
在配置完成后,使用 ROS2 自带的 demo 节点来验证跨节点通信是否正常:
|
||||||
|
|
||||||
|
**在主节点机器上**(激活 unilab 环境后):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动talker
|
||||||
|
ros2 run demo_nodes_cpp talker
|
||||||
|
|
||||||
|
# 同时在另一个终端启动listener
|
||||||
|
ros2 run demo_nodes_cpp listener
|
||||||
|
```
|
||||||
|
|
||||||
|
**在从节点机器上**(激活 unilab 环境后):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动talker
|
||||||
|
ros2 run demo_nodes_cpp talker
|
||||||
|
|
||||||
|
# 同时在另一个终端启动listener
|
||||||
|
ros2 run demo_nodes_cpp listener
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**:必须在两台机器上**互相启动** talker 和 listener,否则可能出现只能收不能发的单向通信问题。
|
||||||
|
|
||||||
|
**预期结果**:
|
||||||
|
|
||||||
|
- 每台机器的 listener 应该能同时接收到本地和远程 talker 发送的消息
|
||||||
|
- 如果只能看到本地消息,说明网络配置有问题
|
||||||
|
- 如果两台机器都能互相收发消息,则组网配置正确
|
||||||
|
|
||||||
|
### 本地网络要求
|
||||||
|
|
||||||
|
**ROS2 通信**:
|
||||||
|
|
||||||
|
- 同一局域网或 VPN
|
||||||
|
- 端口:默认 DDS 端口(7400-7500)
|
||||||
|
- 组播支持(或配置 unicast)
|
||||||
|
|
||||||
|
**检查连通性**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ping测试
|
||||||
|
ping <target_ip>
|
||||||
|
|
||||||
|
# ROS2节点发现
|
||||||
|
ros2 node list
|
||||||
|
ros2 daemon stop && ros2 daemon start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 云端连接
|
||||||
|
|
||||||
|
**要求**:
|
||||||
|
|
||||||
|
- HTTPS (443)
|
||||||
|
- WebSocket 支持
|
||||||
|
- 稳定的互联网连接
|
||||||
|
|
||||||
|
**测试连接**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 测试云端连接
|
||||||
|
curl https://uni-lab.bohrium.com/api/v1/health
|
||||||
|
|
||||||
|
# 测试WebSocket
|
||||||
|
# 启动Uni-Lab后查看日志
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 示例:多房间部署
|
||||||
|
|
||||||
|
### 场景描述
|
||||||
|
|
||||||
|
- **房间 A**: 主控室,有 Web 界面
|
||||||
|
- **房间 B**: 液体处理室
|
||||||
|
- **房间 C**: 分析仪器室
|
||||||
|
|
||||||
|
### 房间 A - 主节点
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# host.json
|
||||||
|
unilab --ak your_ak --sk your_sk -g host.json --port 8002
|
||||||
|
```
|
||||||
|
|
||||||
|
### 房间 B - 从节点 1
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# liquid_handler.json
|
||||||
|
unilab --ak your_ak --sk your_sk -g liquid_handler.json --is_slave --port 8003
|
||||||
|
```
|
||||||
|
|
||||||
|
### 房间 C - 从节点 2
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# analytical.json
|
||||||
|
unilab --ak your_ak --sk your_sk -g analytical.json --is_slave --port 8004
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 故障处理
|
||||||
|
|
||||||
|
### 节点离线
|
||||||
|
|
||||||
|
**检测**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 node list # 查看在线节点
|
||||||
|
```
|
||||||
|
|
||||||
|
**处理**:
|
||||||
|
|
||||||
|
1. 检查网络连接
|
||||||
|
2. 重启节点
|
||||||
|
3. 检查日志
|
||||||
|
|
||||||
|
### 从节点无法连接主节点
|
||||||
|
|
||||||
|
1. 检查网络:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ping <host_ip>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 检查 ROS_DOMAIN_ID:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo $ROS_DOMAIN_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 使用`--slave_no_host`测试:
|
||||||
|
```bash
|
||||||
|
unilab --ak your_ak --sk your_sk -g slave.json --is_slave --slave_no_host
|
||||||
|
```
|
||||||
|
|
||||||
|
### 通信延迟
|
||||||
|
|
||||||
|
**排查**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 网络延迟
|
||||||
|
ping <node_ip>
|
||||||
|
|
||||||
|
# ROS2话题延迟
|
||||||
|
ros2 topic hz /device_status
|
||||||
|
ros2 topic bw /device_status
|
||||||
|
```
|
||||||
|
|
||||||
|
**优化**:
|
||||||
|
|
||||||
|
- 减少发布频率
|
||||||
|
- 使用 QoS 配置
|
||||||
|
- 优化网络带宽
|
||||||
|
|
||||||
|
### 数据同步失败
|
||||||
|
|
||||||
|
**检查**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看日志
|
||||||
|
tail -f unilabos_data/logs/unilab.log | grep sync
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
|
||||||
|
- 检查云端连接
|
||||||
|
- 验证 AK/SK
|
||||||
|
- 手动触发同步
|
||||||
|
|
||||||
|
### 资源不可见
|
||||||
|
|
||||||
|
检查资源注册:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 service call /host_node/resource_list \
|
||||||
|
unilabos_msgs/srv/ResourceList
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 监控和维护
|
||||||
|
|
||||||
|
### 节点状态监控
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看所有节点
|
||||||
|
ros2 node list
|
||||||
|
|
||||||
|
# 查看话题
|
||||||
|
ros2 topic list
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [最佳实践指南](../user_guide/best_practice.md) - 完整的实验室搭建流程
|
||||||
|
- [安装指南](../user_guide/installation.md) - 环境安装步骤
|
||||||
|
- [启动参数详解](../user_guide/launch.md) - 启动参数说明
|
||||||
|
- [添加设备驱动](add_device.md) - 自定义设备开发
|
||||||
|
- [工作站架构](workstation_architecture.md) - 复杂工作站搭建
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- [ROS2 网络配置](https://docs.ros.org/en/humble/Tutorials/Advanced/Networking.html)
|
||||||
|
- [DDS 配置](https://fast-dds.docs.eprosima.com/)
|
||||||
|
- Uni-Lab 云平台文档
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Uni-Lab 项目文档
|
# Uni-Lab-OS 项目文档
|
||||||
|
|
||||||
欢迎来到项目文档的首页!
|
Uni-Lab-OS 是一个开源的实验室自动化操作系统,提供统一的设备接口、工作流管理和分布式部署能力。
|
||||||
|
|
||||||
```{toctree}
|
```{toctree}
|
||||||
:maxdepth: 3
|
:maxdepth: 3
|
||||||
|
|||||||
@@ -10,35 +10,51 @@ concepts/01-communication-instruction.md
|
|||||||
concepts/02-topology-and-chemputer-compile.md
|
concepts/02-topology-and-chemputer-compile.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## **用户指南**
|
## 用户指南
|
||||||
|
|
||||||
本指南将带你了解如何使用项目的功能。
|
快速上手、系统配置与使用说明。
|
||||||
|
|
||||||
```{toctree}
|
```{toctree}
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
user_guide/best_practice.md
|
||||||
user_guide/installation.md
|
user_guide/installation.md
|
||||||
user_guide/configuration.md
|
|
||||||
user_guide/launch.md
|
user_guide/launch.md
|
||||||
|
user_guide/graph_files.md
|
||||||
boot_examples/index.md
|
boot_examples/index.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 进阶配置
|
||||||
|
|
||||||
|
高级配置和系统管理。
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
advanced_usage/configuration.md
|
||||||
|
advanced_usage/working_directory.md
|
||||||
|
```
|
||||||
|
|
||||||
## 开发者指南
|
## 开发者指南
|
||||||
|
|
||||||
```{toctree}
|
设备开发、系统扩展与架构说明。
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
developer_guide/device_driver
|
developer_guide/networking_overview.md
|
||||||
developer_guide/add_device
|
developer_guide/add_device.md
|
||||||
developer_guide/add_action
|
developer_guide/add_old_device.md
|
||||||
developer_guide/actions
|
developer_guide/add_registry.md
|
||||||
developer_guide/workstation_architecture
|
developer_guide/add_yaml.md
|
||||||
developer_guide/add_protocol
|
developer_guide/add_action.md
|
||||||
developer_guide/add_batteryPLC
|
developer_guide/actions.md
|
||||||
developer_guide/materials_tutorial
|
developer_guide/action_includes.md
|
||||||
developer_guide/materials_construction_guide
|
developer_guide/add_protocol.md
|
||||||
|
developer_guide/examples/workstation_architecture.md
|
||||||
|
developer_guide/examples/materials_construction_guide.md
|
||||||
|
developer_guide/examples/materials_tutorial.md
|
||||||
|
developer_guide/examples/battery_plc_workstation.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## 接口文档
|
## 接口文档
|
||||||
|
|||||||
1837
docs/user_guide/best_practice.md
Normal file
@@ -1,442 +0,0 @@
|
|||||||
# Uni-Lab 配置指南
|
|
||||||
|
|
||||||
Uni-Lab 支持通过 Python 配置文件进行灵活的系统配置。本指南将帮助您理解配置选项并设置您的 Uni-Lab 环境。
|
|
||||||
|
|
||||||
## 配置文件格式
|
|
||||||
|
|
||||||
Uni-Lab 支持 Python 格式的配置文件,它比 YAML 或 JSON 提供更多的灵活性,包括支持注释、条件逻辑和复杂数据结构。
|
|
||||||
|
|
||||||
### 默认配置示例
|
|
||||||
|
|
||||||
首次使用时,系统会自动创建一个基础配置文件 `local_config.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# unilabos的配置文件
|
|
||||||
|
|
||||||
class BasicConfig:
|
|
||||||
ak = "" # 实验室网页给您提供的ak代码,您可以在配置文件中指定,也可以通过运行unilabos时以 --ak 传入,优先按照传入参数解析
|
|
||||||
sk = "" # 实验室网页给您提供的sk代码,您可以在配置文件中指定,也可以通过运行unilabos时以 --sk 传入,优先按照传入参数解析
|
|
||||||
|
|
||||||
|
|
||||||
# WebSocket配置,一般无需调整
|
|
||||||
class WSConfig:
|
|
||||||
reconnect_interval = 5 # 重连间隔(秒)
|
|
||||||
max_reconnect_attempts = 999 # 最大重连次数
|
|
||||||
ping_interval = 30 # ping间隔(秒)
|
|
||||||
```
|
|
||||||
您可以进入实验室,点击左下角的头像在实验室详情中获取所在实验室的ak sk
|
|
||||||

|
|
||||||
|
|
||||||
### 完整配置示例
|
|
||||||
|
|
||||||
您可以根据需要添加更多配置选项:
|
|
||||||
|
|
||||||
```python
|
|
||||||
#!/usr/bin/env python
|
|
||||||
# coding=utf-8
|
|
||||||
"""Uni-Lab 配置文件"""
|
|
||||||
|
|
||||||
# 基础配置
|
|
||||||
class BasicConfig:
|
|
||||||
ak = "your_access_key" # 实验室访问密钥
|
|
||||||
sk = "your_secret_key" # 实验室私钥
|
|
||||||
working_dir = "" # 工作目录(通常自动设置)
|
|
||||||
config_path = "" # 配置文件路径(自动设置)
|
|
||||||
is_host_mode = True # 是否为主站模式
|
|
||||||
slave_no_host = False # 从站模式下是否跳过等待主机服务
|
|
||||||
upload_registry = False # 是否上传注册表
|
|
||||||
machine_name = "undefined" # 机器名称(自动获取)
|
|
||||||
vis_2d_enable = False # 是否启用2D可视化
|
|
||||||
enable_resource_load = True # 是否启用资源加载
|
|
||||||
communication_protocol = "websocket" # 通信协议
|
|
||||||
|
|
||||||
# WebSocket配置
|
|
||||||
class WSConfig:
|
|
||||||
reconnect_interval = 5 # 重连间隔(秒)
|
|
||||||
max_reconnect_attempts = 999 # 最大重连次数
|
|
||||||
ping_interval = 30 # ping间隔(秒)
|
|
||||||
|
|
||||||
# OSS上传配置
|
|
||||||
class OSSUploadConfig:
|
|
||||||
api_host = "" # API主机地址
|
|
||||||
authorization = "" # 授权信息
|
|
||||||
init_endpoint = "" # 初始化端点
|
|
||||||
complete_endpoint = "" # 完成端点
|
|
||||||
max_retries = 3 # 最大重试次数
|
|
||||||
|
|
||||||
# HTTP配置
|
|
||||||
class HTTPConfig:
|
|
||||||
remote_addr = "http://127.0.0.1:48197/api/v1" # 远程地址
|
|
||||||
|
|
||||||
# ROS配置
|
|
||||||
class ROSConfig:
|
|
||||||
modules = [
|
|
||||||
"std_msgs.msg",
|
|
||||||
"geometry_msgs.msg",
|
|
||||||
"control_msgs.msg",
|
|
||||||
"control_msgs.action",
|
|
||||||
"nav2_msgs.action",
|
|
||||||
"unilabos_msgs.msg",
|
|
||||||
"unilabos_msgs.action",
|
|
||||||
] # 需要加载的ROS模块
|
|
||||||
```
|
|
||||||
|
|
||||||
## 命令行参数覆盖配置
|
|
||||||
|
|
||||||
Uni-Lab 允许通过命令行参数覆盖配置文件中的设置,提供更灵活的配置方式。命令行参数的优先级高于配置文件。
|
|
||||||
|
|
||||||
### 支持命令行覆盖的配置项
|
|
||||||
|
|
||||||
以下配置项可以通过命令行参数进行覆盖:
|
|
||||||
|
|
||||||
| 配置类 | 配置字段 | 命令行参数 | 说明 |
|
|
||||||
| ------------- | ----------------- | ------------------- | -------------------------------- |
|
|
||||||
| `BasicConfig` | `ak` | `--ak` | 实验室访问密钥 |
|
|
||||||
| `BasicConfig` | `sk` | `--sk` | 实验室私钥 |
|
|
||||||
| `BasicConfig` | `working_dir` | `--working_dir` | 工作目录路径 |
|
|
||||||
| `BasicConfig` | `is_host_mode` | `--is_slave` | 主站模式(参数为从站模式,取反) |
|
|
||||||
| `BasicConfig` | `slave_no_host` | `--slave_no_host` | 从站模式下跳过等待主机服务 |
|
|
||||||
| `BasicConfig` | `upload_registry` | `--upload_registry` | 启动时上传注册表信息 |
|
|
||||||
| `BasicConfig` | `vis_2d_enable` | `--2d_vis` | 启用 2D 可视化 |
|
|
||||||
| `HTTPConfig` | `remote_addr` | `--addr` | 远程服务地址 |
|
|
||||||
|
|
||||||
### 特殊命令行参数
|
|
||||||
|
|
||||||
除了直接覆盖配置项的参数外,还有一些特殊的命令行参数:
|
|
||||||
|
|
||||||
| 参数 | 说明 |
|
|
||||||
| ------------------- | ------------------------------------ |
|
|
||||||
| `--config` | 指定配置文件路径 |
|
|
||||||
| `--port` | Web 服务端口(不影响配置文件) |
|
|
||||||
| `--disable_browser` | 禁用自动打开浏览器(不影响配置文件) |
|
|
||||||
| `--visual` | 可视化工具选择(不影响配置文件) |
|
|
||||||
| `--skip_env_check` | 跳过环境检查(不影响配置文件) |
|
|
||||||
|
|
||||||
### 配置优先级
|
|
||||||
|
|
||||||
配置项的生效优先级从高到低为:
|
|
||||||
|
|
||||||
1. **命令行参数**:最高优先级
|
|
||||||
2. **环境变量**:中等优先级
|
|
||||||
3. **配置文件**:基础优先级
|
|
||||||
|
|
||||||
### 使用示例
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 通过命令行覆盖认证信息
|
|
||||||
unilab --ak "new_access_key" --sk "new_secret_key"
|
|
||||||
|
|
||||||
# 覆盖服务器地址
|
|
||||||
unilab --addr "https://custom.server.com/api/v1"
|
|
||||||
|
|
||||||
# 启用从站模式并跳过等待主机
|
|
||||||
unilab --is_slave --slave_no_host
|
|
||||||
|
|
||||||
# 启用上传注册表和2D可视化
|
|
||||||
unilab --upload_registry --2d_vis
|
|
||||||
|
|
||||||
# 组合使用多个覆盖参数
|
|
||||||
unilab --ak "key" --sk "secret" --addr "test" --upload_registry --2d_vis
|
|
||||||
```
|
|
||||||
|
|
||||||
### 预设环境地址
|
|
||||||
|
|
||||||
`--addr` 参数支持以下预设值,会自动转换为对应的完整 URL:
|
|
||||||
|
|
||||||
- `test` → `https://uni-lab.test.bohrium.com/api/v1`
|
|
||||||
- `uat` → `https://uni-lab.uat.bohrium.com/api/v1`
|
|
||||||
- `local` → `http://127.0.0.1:48197/api/v1`
|
|
||||||
- 其他值 → 直接使用作为完整 URL
|
|
||||||
|
|
||||||
## 配置选项详解
|
|
||||||
|
|
||||||
### 基础配置 (BasicConfig)
|
|
||||||
|
|
||||||
基础配置包含了系统运行的核心参数:
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
| ------------------------ | ---- | ------------- | ------------------------------------------ |
|
|
||||||
| `ak` | str | `""` | 实验室访问密钥(必需) |
|
|
||||||
| `sk` | str | `""` | 实验室私钥(必需) |
|
|
||||||
| `working_dir` | str | `""` | 工作目录,通常自动设置 |
|
|
||||||
| `is_host_mode` | bool | `True` | 是否为主站模式 |
|
|
||||||
| `slave_no_host` | bool | `False` | 从站模式下是否跳过等待主机服务 |
|
|
||||||
| `upload_registry` | bool | `False` | 启动时是否上传注册表信息 |
|
|
||||||
| `machine_name` | str | `"undefined"` | 机器名称,自动从 hostname 获取(不可配置) |
|
|
||||||
| `vis_2d_enable` | bool | `False` | 是否启用 2D 可视化 |
|
|
||||||
| `communication_protocol` | str | `"websocket"` | 通信协议,固定为 websocket |
|
|
||||||
|
|
||||||
#### 认证配置
|
|
||||||
|
|
||||||
`ak` 和 `sk` 是必需的认证参数:
|
|
||||||
|
|
||||||
1. **获取方式**:在 [Uni-Lab 官网](https://uni-lab.bohrium.com) 注册实验室后获得
|
|
||||||
2. **配置方式**:
|
|
||||||
- **命令行参数**:`--ak "your_key" --sk "your_secret"`(最高优先级)
|
|
||||||
- **配置文件**:在 `BasicConfig` 类中设置
|
|
||||||
- **环境变量**:`UNILABOS_BASICCONFIG_AK` 和 `UNILABOS_BASICCONFIG_SK`
|
|
||||||
3. **优先级顺序**:命令行参数 > 环境变量 > 配置文件
|
|
||||||
4. **安全注意**:请妥善保管您的密钥信息
|
|
||||||
|
|
||||||
**推荐做法**:
|
|
||||||
|
|
||||||
- 开发环境:使用配置文件
|
|
||||||
- 生产环境:使用环境变量或命令行参数
|
|
||||||
- 临时测试:使用命令行参数
|
|
||||||
|
|
||||||
### WebSocket 配置 (WSConfig)
|
|
||||||
|
|
||||||
WebSocket 是 Uni-Lab 的主要通信方式:
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
| ------------------------ | ---- | ------ | ------------------ |
|
|
||||||
| `reconnect_interval` | int | `5` | 断线重连间隔(秒) |
|
|
||||||
| `max_reconnect_attempts` | int | `999` | 最大重连次数 |
|
|
||||||
| `ping_interval` | int | `30` | 心跳检测间隔(秒) |
|
|
||||||
|
|
||||||
### HTTP 配置 (HTTPConfig)
|
|
||||||
|
|
||||||
HTTP 客户端配置用于与云端服务通信:
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
| ------------- | ---- | --------------------------------- | ------------ |
|
|
||||||
| `remote_addr` | str | `"http://127.0.0.1:48197/api/v1"` | 远程服务地址 |
|
|
||||||
|
|
||||||
**预设环境地址**:
|
|
||||||
|
|
||||||
- 生产环境:`https://uni-lab.bohrium.com/api/v1`
|
|
||||||
- 测试环境:`https://uni-lab.test.bohrium.com/api/v1`
|
|
||||||
- UAT 环境:`https://uni-lab.uat.bohrium.com/api/v1`
|
|
||||||
- 本地环境:`http://127.0.0.1:48197/api/v1`
|
|
||||||
|
|
||||||
### ROS 配置 (ROSConfig)
|
|
||||||
|
|
||||||
配置 ROS 消息转换器需要加载的模块:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ROSConfig:
|
|
||||||
modules = [
|
|
||||||
"std_msgs.msg", # 标准消息类型
|
|
||||||
"geometry_msgs.msg", # 几何消息类型
|
|
||||||
"control_msgs.msg", # 控制消息类型
|
|
||||||
"control_msgs.action", # 控制动作类型
|
|
||||||
"nav2_msgs.action", # 导航动作类型
|
|
||||||
"unilabos_msgs.msg", # UniLab 自定义消息类型
|
|
||||||
"unilabos_msgs.action", # UniLab 自定义动作类型
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
您可以根据实际使用的设备和功能添加其他 ROS 模块。
|
|
||||||
|
|
||||||
### OSS 上传配置 (OSSUploadConfig)
|
|
||||||
|
|
||||||
对象存储服务配置,用于文件上传功能:
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
| ------------------- | ---- | ------ | -------------------- |
|
|
||||||
| `api_host` | str | `""` | OSS API 主机地址 |
|
|
||||||
| `authorization` | str | `""` | 授权认证信息 |
|
|
||||||
| `init_endpoint` | str | `""` | 上传初始化端点 |
|
|
||||||
| `complete_endpoint` | str | `""` | 上传完成端点 |
|
|
||||||
| `max_retries` | int | `3` | 上传失败最大重试次数 |
|
|
||||||
|
|
||||||
## 环境变量支持
|
|
||||||
|
|
||||||
Uni-Lab 支持通过环境变量覆盖配置文件中的设置。环境变量格式为:
|
|
||||||
|
|
||||||
```
|
|
||||||
UNILABOS_{配置类名}_{字段名}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 环境变量示例
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 设置基础配置
|
|
||||||
export UNILABOS_BASICCONFIG_AK="your_access_key"
|
|
||||||
export UNILABOS_BASICCONFIG_SK="your_secret_key"
|
|
||||||
export UNILABOS_BASICCONFIG_IS_HOST_MODE="true"
|
|
||||||
|
|
||||||
# 设置WebSocket配置
|
|
||||||
export UNILABOS_WSCONFIG_RECONNECT_INTERVAL="10"
|
|
||||||
export UNILABOS_WSCONFIG_MAX_RECONNECT_ATTEMPTS="500"
|
|
||||||
|
|
||||||
# 设置HTTP配置
|
|
||||||
export UNILABOS_HTTPCONFIG_REMOTE_ADDR="https://uni-lab.bohrium.com/api/v1"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 环境变量类型转换
|
|
||||||
|
|
||||||
- **布尔值**:`"true"`, `"1"`, `"yes"` → `True`;其他 → `False`
|
|
||||||
- **整数**:自动转换为 `int` 类型
|
|
||||||
- **浮点数**:自动转换为 `float` 类型
|
|
||||||
- **字符串**:保持原值
|
|
||||||
|
|
||||||
## 配置文件使用方法
|
|
||||||
|
|
||||||
### 1. 指定配置文件启动
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 使用指定配置文件启动
|
|
||||||
unilab --config /path/to/your/config.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 使用默认配置文件
|
|
||||||
|
|
||||||
如果不指定配置文件,系统会按以下顺序查找:
|
|
||||||
|
|
||||||
1. 环境变量 `UNILABOS_BASICCONFIG_CONFIG_PATH` 指定的路径
|
|
||||||
2. 工作目录下的 `local_config.py`
|
|
||||||
3. 首次使用时会引导创建配置文件
|
|
||||||
|
|
||||||
### 3. 配置文件验证
|
|
||||||
|
|
||||||
系统启动时会自动验证配置文件:
|
|
||||||
|
|
||||||
- **语法检查**:确保 Python 语法正确
|
|
||||||
- **类型检查**:验证配置项类型是否匹配
|
|
||||||
- **必需项检查**:确保 `ak` 和 `sk` 已配置
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 安全配置
|
|
||||||
|
|
||||||
- 不要将包含密钥的配置文件提交到版本控制系统
|
|
||||||
- 使用环境变量或命令行参数在生产环境中配置敏感信息
|
|
||||||
- 定期更换访问密钥
|
|
||||||
- **推荐配置方式**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 生产环境 - 使用环境变量
|
|
||||||
export UNILABOS_BASICCONFIG_AK="your_access_key"
|
|
||||||
export UNILABOS_BASICCONFIG_SK="your_secret_key"
|
|
||||||
unilab
|
|
||||||
|
|
||||||
# 或使用命令行参数
|
|
||||||
unilab --ak "your_access_key" --sk "your_secret_key"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 多环境配置
|
|
||||||
|
|
||||||
为不同环境创建不同的配置文件并结合命令行参数:
|
|
||||||
|
|
||||||
```
|
|
||||||
configs/
|
|
||||||
├── local_config.py # 本地开发
|
|
||||||
├── test_config.py # 测试环境
|
|
||||||
├── prod_config.py # 生产环境
|
|
||||||
└── example_config.py # 示例配置
|
|
||||||
```
|
|
||||||
|
|
||||||
**环境切换示例**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 本地开发环境
|
|
||||||
unilab --config configs/local_config.py --addr local
|
|
||||||
|
|
||||||
# 测试环境
|
|
||||||
unilab --config configs/test_config.py --addr test --upload_registry
|
|
||||||
|
|
||||||
# 生产环境
|
|
||||||
unilab --config configs/prod_config.py --ak "$PROD_AK" --sk "$PROD_SK"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 配置管理
|
|
||||||
|
|
||||||
- 保持配置文件简洁,只包含需要修改的配置项
|
|
||||||
- 为配置项添加注释说明其作用
|
|
||||||
- 定期检查和更新配置文件
|
|
||||||
- **命令行参数优先使用场景**:
|
|
||||||
- 临时测试不同配置
|
|
||||||
- CI/CD 流水线中的动态配置
|
|
||||||
- 不同环境间快速切换
|
|
||||||
- 敏感信息的安全传递
|
|
||||||
|
|
||||||
### 4. 灵活配置策略
|
|
||||||
|
|
||||||
**基础配置文件 + 命令行覆盖**的推荐方式:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# base_config.py - 基础配置
|
|
||||||
class BasicConfig:
|
|
||||||
# 非敏感配置写在文件中
|
|
||||||
is_host_mode = True
|
|
||||||
upload_registry = False
|
|
||||||
vis_2d_enable = False
|
|
||||||
|
|
||||||
class WSConfig:
|
|
||||||
reconnect_interval = 5
|
|
||||||
max_reconnect_attempts = 999
|
|
||||||
ping_interval = 30
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启动时通过命令行覆盖关键参数
|
|
||||||
unilab --config base_config.py \
|
|
||||||
--ak "$AK" \
|
|
||||||
--sk "$SK" \
|
|
||||||
--addr "test" \
|
|
||||||
--upload_registry \
|
|
||||||
--2d_vis
|
|
||||||
```
|
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
|
|
||||||
### 1. 配置文件加载失败
|
|
||||||
|
|
||||||
**错误信息**:`[ENV] 配置文件 xxx 不存在`
|
|
||||||
|
|
||||||
**解决方法**:
|
|
||||||
|
|
||||||
- 确认配置文件路径正确
|
|
||||||
- 检查文件权限是否可读
|
|
||||||
- 确保配置文件是 `.py` 格式
|
|
||||||
|
|
||||||
### 2. 语法错误
|
|
||||||
|
|
||||||
**错误信息**:`[ENV] 加载配置文件 xxx 失败`
|
|
||||||
|
|
||||||
**解决方法**:
|
|
||||||
|
|
||||||
- 检查 Python 语法是否正确
|
|
||||||
- 确认类名和字段名拼写正确
|
|
||||||
- 验证缩进是否正确(使用空格而非制表符)
|
|
||||||
|
|
||||||
### 3. 认证失败
|
|
||||||
|
|
||||||
**错误信息**:`后续运行必须拥有一个实验室`
|
|
||||||
|
|
||||||
**解决方法**:
|
|
||||||
|
|
||||||
- 确认 `ak` 和 `sk` 已正确配置
|
|
||||||
- 检查密钥是否有效
|
|
||||||
- 确认网络连接正常
|
|
||||||
|
|
||||||
### 4. 环境变量不生效
|
|
||||||
|
|
||||||
**解决方法**:
|
|
||||||
|
|
||||||
- 确认环境变量名格式正确(`UNILABOS_CLASS_FIELD`)
|
|
||||||
- 检查环境变量是否已正确设置
|
|
||||||
- 重启系统或重新加载环境变量
|
|
||||||
|
|
||||||
### 5. 命令行参数不生效
|
|
||||||
|
|
||||||
**错误现象**:设置了命令行参数但配置没有生效
|
|
||||||
|
|
||||||
**解决方法**:
|
|
||||||
|
|
||||||
- 确认参数名拼写正确(如 `--ak` 而不是 `--access_key`)
|
|
||||||
- 检查参数格式是否正确(布尔参数如 `--is_slave` 不需要值)
|
|
||||||
- 确认参数位置正确(所有参数都应在 `unilab` 之后)
|
|
||||||
- 查看启动日志确认参数是否被正确解析
|
|
||||||
|
|
||||||
### 6. 配置优先级混淆
|
|
||||||
|
|
||||||
**错误现象**:不确定哪个配置生效
|
|
||||||
|
|
||||||
**解决方法**:
|
|
||||||
|
|
||||||
- 记住优先级:命令行参数 > 环境变量 > 配置文件
|
|
||||||
- 使用 `--ak` 和 `--sk` 参数时会看到提示信息
|
|
||||||
- 检查启动日志中的配置加载信息
|
|
||||||
- 临时移除低优先级配置来测试高优先级配置是否生效
|
|
||||||
860
docs/user_guide/graph_files.md
Normal file
@@ -0,0 +1,860 @@
|
|||||||
|
# 设备图文件说明
|
||||||
|
|
||||||
|
设备图文件定义了实验室中所有设备、资源及其连接关系。本文档说明如何创建和使用设备图文件。
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
设备图文件采用 JSON 格式,节点定义基于 **`ResourceDict`** 标准模型(定义在 `unilabos.ros.nodes.resource_tracker`)。系统会自动处理旧格式并转换为标准格式,确保向后兼容性。
|
||||||
|
|
||||||
|
**核心概念**:
|
||||||
|
|
||||||
|
- **Nodes(节点)**: 代表设备或资源,通过 `parent` 字段建立层级关系
|
||||||
|
- **Links(连接)**: 可选的连接关系定义,用于展示设备间的物理或通信连接
|
||||||
|
- **UUID**: 全局唯一标识符,用于跨系统的资源追踪
|
||||||
|
- **自动转换**: 旧格式会通过 `ResourceDictInstance.get_resource_instance_from_dict()` 自动转换
|
||||||
|
|
||||||
|
## 文件格式
|
||||||
|
|
||||||
|
Uni-Lab 支持两种格式的设备图文件:
|
||||||
|
|
||||||
|
### JSON 格式(推荐)
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
|
||||||
|
- 易于编辑和阅读
|
||||||
|
- 支持注释(使用预处理)
|
||||||
|
- 与 Web 界面完全兼容
|
||||||
|
- 便于版本控制
|
||||||
|
|
||||||
|
**示例**: `workshop1.json`
|
||||||
|
|
||||||
|
### GraphML 格式
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
|
||||||
|
- 可用图形化工具编辑(如 yEd)
|
||||||
|
- 适合复杂拓扑可视化
|
||||||
|
|
||||||
|
**示例**: `setup.graphml`
|
||||||
|
|
||||||
|
## JSON 文件结构
|
||||||
|
|
||||||
|
一个完整的 JSON 设备图文件包含两个主要部分:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
/* 设备和资源节点 */
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
/* 连接关系(可选)*/
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nodes(节点)
|
||||||
|
|
||||||
|
每个节点代表一个设备或资源。节点的定义遵循 `ResourceDict` 标准模型:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "liquid_handler_1",
|
||||||
|
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"name": "液体处理工作站",
|
||||||
|
"type": "device",
|
||||||
|
"class": "liquid_handler",
|
||||||
|
"config": {
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"baudrate": 9600
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 200
|
||||||
|
},
|
||||||
|
"parent": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**字段说明(基于 ResourceDict 标准定义)**:
|
||||||
|
|
||||||
|
| 字段 | 必需 | 说明 | 示例 | 默认值 |
|
||||||
|
| ------------- | ---- | ------------------------ | ---------------------------------------------------- | -------- |
|
||||||
|
| `id` | ✓ | 唯一标识符 | `"pump_1"` | - |
|
||||||
|
| `uuid` | | 全局唯一标识符 (UUID) | `"550e8400-e29b-41d4-a716-446655440000"` | 自动生成 |
|
||||||
|
| `name` | ✓ | 显示名称 | `"主反应泵"` | - |
|
||||||
|
| `type` | ✓ | 节点类型 | `"device"`, `"resource"`, `"container"`, `"deck"` 等 | - |
|
||||||
|
| `class` | ✓ | 设备/资源类别 | `"liquid_handler"`, `"syringepump.runze"` | `""` |
|
||||||
|
| `config` | | Python 类的初始化参数 | `{"port": "COM3"}` | `{}` |
|
||||||
|
| `data` | | 资源的运行状态数据 | `{"status": "Idle", "position": 0.0}` | `{}` |
|
||||||
|
| `position` | | 在图中的位置 | `{"x": 100, "y": 200}` 或完整的 pose 结构 | - |
|
||||||
|
| `pose` | | 完整的 3D 位置信息 | 参见下文 | - |
|
||||||
|
| `parent` | | 父节点 ID | `"deck_1"` | `null` |
|
||||||
|
| `parent_uuid` | | 父节点 UUID | `"550e8400-..."` | `null` |
|
||||||
|
| `children` | | 子节点 ID 列表(旧格式) | `["child1", "child2"]` | - |
|
||||||
|
| `description` | | 资源描述 | `"用于精确控制试剂A的加料速率"` | `""` |
|
||||||
|
| `schema` | | 资源 schema 定义 | `{}` | `{}` |
|
||||||
|
| `model` | | 资源 3D 模型信息 | `{}` | `{}` |
|
||||||
|
| `icon` | | 资源图标 | `"pump.webp"` | `""` |
|
||||||
|
| `extra` | | 额外的自定义数据 | `{"custom_field": "value"}` | `{}` |
|
||||||
|
|
||||||
|
### Position 和 Pose(位置信息)
|
||||||
|
|
||||||
|
**简单格式(旧格式,兼容)**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**完整格式(推荐)**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pose": {
|
||||||
|
"size": {
|
||||||
|
"width": 127.76,
|
||||||
|
"height": 85.48,
|
||||||
|
"depth": 10.0
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"x": 1.0,
|
||||||
|
"y": 1.0,
|
||||||
|
"z": 1.0
|
||||||
|
},
|
||||||
|
"layout": "x-y",
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"position3d": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"cross_section_type": "rectangle"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Links(连接)
|
||||||
|
|
||||||
|
定义节点之间的连接关系(可选,主要用于物理连接或通信关系的可视化):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": "pump_1",
|
||||||
|
"target": "reactor_1",
|
||||||
|
"sourceHandle": "output",
|
||||||
|
"targetHandle": "input",
|
||||||
|
"type": "physical"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**字段说明**:
|
||||||
|
|
||||||
|
| 字段 | 必需 | 说明 | 示例 |
|
||||||
|
| -------------- | ---- | ---------------- | ---------------------------------------- |
|
||||||
|
| `source` | ✓ | 源节点 ID | `"pump_1"` |
|
||||||
|
| `target` | ✓ | 目标节点 ID | `"reactor_1"` |
|
||||||
|
| `sourceHandle` | | 源节点的连接点 | `"output"` |
|
||||||
|
| `targetHandle` | | 目标节点的连接点 | `"input"` |
|
||||||
|
| `type` | | 连接类型 | `"physical"`, `"communication"` |
|
||||||
|
| `port` | | 端口映射信息 | `{"source": "port1", "target": "port2"}` |
|
||||||
|
|
||||||
|
**注意**: Links 主要用于图形化展示和文档说明,父子关系通过 `parent` 字段定义,不依赖 links。
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
### 示例 1:液体处理工作站(PRCXI9300)
|
||||||
|
|
||||||
|
这是一个真实的液体处理工作站配置,包含设备、工作台和多个板资源。
|
||||||
|
|
||||||
|
**文件位置**: `test/experiments/prcxi_9300.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "PRCXI9300",
|
||||||
|
"name": "PRCXI9300",
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "liquid_handler.prcxi",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"deck": {
|
||||||
|
"_resource_child_name": "PRCXI_Deck_9300",
|
||||||
|
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck"
|
||||||
|
},
|
||||||
|
"host": "10.181.214.132",
|
||||||
|
"port": 9999,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"axis": "Left",
|
||||||
|
"channel_num": 8,
|
||||||
|
"setup": false,
|
||||||
|
"debug": true,
|
||||||
|
"simulator": true,
|
||||||
|
"matrix_id": "71593"
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": ["PRCXI_Deck_9300"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PRCXI_Deck_9300",
|
||||||
|
"name": "PRCXI_Deck_9300",
|
||||||
|
"parent": "PRCXI9300",
|
||||||
|
"type": "deck",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Deck",
|
||||||
|
"size_x": 100,
|
||||||
|
"size_y": 100,
|
||||||
|
"size_z": 100,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "deck"
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": [
|
||||||
|
"RackT1",
|
||||||
|
"PlateT2",
|
||||||
|
"trash",
|
||||||
|
"PlateT4",
|
||||||
|
"PlateT5",
|
||||||
|
"PlateT6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "RackT1",
|
||||||
|
"name": "RackT1",
|
||||||
|
"parent": "PRCXI_Deck_9300",
|
||||||
|
"type": "tip_rack",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "TipRack",
|
||||||
|
"size_x": 127.76,
|
||||||
|
"size_y": 85.48,
|
||||||
|
"size_z": 100
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键点**:
|
||||||
|
|
||||||
|
- 使用 `parent` 字段建立层级关系(PRCXI9300 → Deck → Rack/Plate)
|
||||||
|
- 使用 `children` 字段(旧格式)列出子节点
|
||||||
|
- `config` 中包含设备特定的连接参数
|
||||||
|
- `data` 存储运行时状态
|
||||||
|
- `position` 使用简单的 x/y/z 坐标
|
||||||
|
|
||||||
|
### 示例 2:有机合成工作站(带 Links)
|
||||||
|
|
||||||
|
这是一个格林纳德反应的流动化学工作站配置,展示了完整的设备连接和通信关系。
|
||||||
|
|
||||||
|
**文件位置**: `test/experiments/Grignard_flow_batchreact_single_pumpvalve.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "YugongStation",
|
||||||
|
"name": "愚公常量合成工作站",
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"PumpTransferProtocol",
|
||||||
|
"CleanProtocol",
|
||||||
|
"SeparateProtocol",
|
||||||
|
"EvaporateProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": [
|
||||||
|
"serial_pump",
|
||||||
|
"pump_reagents",
|
||||||
|
"flask_CH2Cl2",
|
||||||
|
"reactor",
|
||||||
|
"pump_workup",
|
||||||
|
"separator_controller",
|
||||||
|
"flask_separator",
|
||||||
|
"rotavap",
|
||||||
|
"column"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "serial_pump",
|
||||||
|
"name": "serial_pump",
|
||||||
|
"parent": "YugongStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "serial",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "COM7",
|
||||||
|
"baudrate": 9600
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pump_reagents",
|
||||||
|
"name": "pump_reagents",
|
||||||
|
"parent": "YugongStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "syringepump.runze",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "/devices/PumpBackbone/Serial/serialwrite",
|
||||||
|
"address": "1",
|
||||||
|
"max_volume": 25.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"max_velocity": 1.0,
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle",
|
||||||
|
"valve_position": "0"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "reactor",
|
||||||
|
"parent": "YugongStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 430.4087301587302,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {},
|
||||||
|
"data": {},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "pump_reagents",
|
||||||
|
"target": "serial_pump",
|
||||||
|
"type": "communication",
|
||||||
|
"port": {
|
||||||
|
"pump_reagents": "port",
|
||||||
|
"serial_pump": "port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_workup",
|
||||||
|
"target": "serial_pump",
|
||||||
|
"type": "communication",
|
||||||
|
"port": {
|
||||||
|
"pump_workup": "port",
|
||||||
|
"serial_pump": "port"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键点**:
|
||||||
|
|
||||||
|
- 多级设备层次:工作站包含多个子设备和容器
|
||||||
|
- `links` 定义通信关系(泵通过串口连接)
|
||||||
|
- `data` 字段存储设备状态(如泵的位置、速度等)
|
||||||
|
- `class` 可以使用点号分层(如 `"syringepump.runze"`)
|
||||||
|
- 容器的 `class` 可以为 `null`
|
||||||
|
|
||||||
|
## 格式兼容性和转换
|
||||||
|
|
||||||
|
### 旧格式自动转换
|
||||||
|
|
||||||
|
Uni-Lab 使用 `ResourceDictInstance.get_resource_instance_from_dict()` 方法自动处理旧格式的节点数据,确保向后兼容性。
|
||||||
|
|
||||||
|
**自动转换规则**:
|
||||||
|
|
||||||
|
1. **自动生成缺失字段**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 如果缺少 id,使用 name 作为 id
|
||||||
|
if "id" not in content:
|
||||||
|
content["id"] = content["name"]
|
||||||
|
|
||||||
|
# 如果缺少 uuid,自动生成
|
||||||
|
if "uuid" not in content:
|
||||||
|
content["uuid"] = str(uuid.uuid4())
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Position 格式转换**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 旧格式:简单的 x/y 坐标
|
||||||
|
"position": {"x": 100, "y": 200}
|
||||||
|
|
||||||
|
# 自动转换为新格式
|
||||||
|
"position": {
|
||||||
|
"position": {"x": 100, "y": 200}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **默认值填充**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 自动填充空字段
|
||||||
|
if not content.get("class"):
|
||||||
|
content["class"] = ""
|
||||||
|
if not content.get("config"):
|
||||||
|
content["config"] = {}
|
||||||
|
if not content.get("data"):
|
||||||
|
content["data"] = {}
|
||||||
|
if not content.get("extra"):
|
||||||
|
content["extra"] = {}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Pose 字段同步**:
|
||||||
|
```python
|
||||||
|
# 如果没有 pose,使用 position
|
||||||
|
if "pose" not in content:
|
||||||
|
content["pose"] = content.get("position", {})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
from unilabos.ros.nodes.resource_tracker import ResourceDictInstance
|
||||||
|
|
||||||
|
# 旧格式节点
|
||||||
|
old_format_node = {
|
||||||
|
"name": "pump_1",
|
||||||
|
"type": "device",
|
||||||
|
"class": "syringepump",
|
||||||
|
"position": {"x": 100, "y": 200}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 自动转换为标准格式
|
||||||
|
instance = ResourceDictInstance.get_resource_instance_from_dict(old_format_node)
|
||||||
|
|
||||||
|
# 访问标准化后的数据
|
||||||
|
print(instance.res_content.id) # "pump_1"
|
||||||
|
print(instance.res_content.uuid) # 自动生成的 UUID
|
||||||
|
print(instance.res_content.config) # {}
|
||||||
|
print(instance.res_content.data) # {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 格式迁移建议
|
||||||
|
|
||||||
|
虽然系统会自动处理旧格式,但建议在新文件中使用完整的标准格式:
|
||||||
|
|
||||||
|
| 字段 | 旧格式(兼容) | 新格式(推荐) |
|
||||||
|
| ------ | ---------------------------------- | ------------------------------------------------ |
|
||||||
|
| 标识符 | 仅 `id` 或仅 `name` | `id` + `uuid` |
|
||||||
|
| 位置 | `"position": {"x": 100, "y": 200}` | 完整的 `pose` 结构 |
|
||||||
|
| 父节点 | `"parent": "parent_id"` | `"parent": "parent_id"` + `"parent_uuid": "..."` |
|
||||||
|
| 配置 | 可省略 | 显式设置为 `{}` |
|
||||||
|
| 数据 | 可省略 | 显式设置为 `{}` |
|
||||||
|
|
||||||
|
## 节点类型详解
|
||||||
|
|
||||||
|
### Device 节点
|
||||||
|
|
||||||
|
设备节点代表实际的硬件设备:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "device_id",
|
||||||
|
"name": "设备名称",
|
||||||
|
"type": "device",
|
||||||
|
"class": "设备类别",
|
||||||
|
"parent": null,
|
||||||
|
"config": {
|
||||||
|
"port": "COM3"
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**常见设备类别**:
|
||||||
|
|
||||||
|
- `liquid_handler`: 液体处理工作站
|
||||||
|
- `liquid_handler.prcxi`: PRCXI 液体处理工作站
|
||||||
|
- `syringepump`: 注射泵
|
||||||
|
- `syringepump.runze`: 润泽注射泵
|
||||||
|
- `heaterstirrer`: 加热搅拌器
|
||||||
|
- `balance`: 天平
|
||||||
|
- `reactor_vessel`: 反应釜
|
||||||
|
- `serial`: 串口通信设备
|
||||||
|
- `workstation`: 自动化工作站
|
||||||
|
|
||||||
|
### Resource 节点
|
||||||
|
|
||||||
|
资源节点代表物料容器、载具等:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "resource_id",
|
||||||
|
"name": "资源名称",
|
||||||
|
"type": "resource",
|
||||||
|
"class": "资源类别",
|
||||||
|
"parent": "父节点ID",
|
||||||
|
"config": {
|
||||||
|
"size_x": 127.76,
|
||||||
|
"size_y": 85.48,
|
||||||
|
"size_z": 100
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**常见资源类型**:
|
||||||
|
|
||||||
|
- `deck`: 工作台/甲板
|
||||||
|
- `plate`: 板(96 孔板等)
|
||||||
|
- `tip_rack`: 枪头架
|
||||||
|
- `tube`: 试管
|
||||||
|
- `container`: 容器
|
||||||
|
- `well`: 孔位
|
||||||
|
- `bottle_carrier`: 瓶架
|
||||||
|
|
||||||
|
## Handle(连接点)
|
||||||
|
|
||||||
|
每个设备和资源可以有多个连接点(handles),用于定义可以连接的接口。
|
||||||
|
|
||||||
|
### 查看可用 handles
|
||||||
|
|
||||||
|
设备和资源的可用 handles 定义在注册表中:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 设备注册表示例
|
||||||
|
liquid_handler:
|
||||||
|
handles:
|
||||||
|
- handler_key: pipette
|
||||||
|
io_type: source
|
||||||
|
- handler_key: deck
|
||||||
|
io_type: target
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常见 handles
|
||||||
|
|
||||||
|
| 设备类型 | Source Handles | Target Handles |
|
||||||
|
| ---------- | -------------- | -------------- |
|
||||||
|
| 泵 | output | input |
|
||||||
|
| 反应釜 | output, vessel | input |
|
||||||
|
| 液体处理器 | pipette | deck |
|
||||||
|
| 板 | wells | access |
|
||||||
|
|
||||||
|
## 使用 Web 界面创建图文件
|
||||||
|
|
||||||
|
Uni-Lab 提供 Web 界面来可视化创建和编辑设备图:
|
||||||
|
|
||||||
|
### 1. 启动 Uni-Lab
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unilab
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 访问 Web 界面
|
||||||
|
|
||||||
|
打开浏览器访问 `http://localhost:8002`
|
||||||
|
|
||||||
|
### 3. 图形化编辑
|
||||||
|
|
||||||
|
- 拖拽添加设备和资源
|
||||||
|
- 连线建立连接关系
|
||||||
|
- 编辑节点属性
|
||||||
|
- 保存为 JSON 文件
|
||||||
|
|
||||||
|
### 4. 导出图文件
|
||||||
|
|
||||||
|
点击"导出"按钮,下载 JSON 文件到本地。
|
||||||
|
|
||||||
|
## 从云端获取图文件
|
||||||
|
|
||||||
|
如果不指定`-g`参数,Uni-Lab 会自动从云端获取:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用云端配置
|
||||||
|
unilab
|
||||||
|
|
||||||
|
# 日志会显示:
|
||||||
|
# [INFO] 未指定设备加载文件路径,尝试从HTTP获取...
|
||||||
|
# [INFO] 联网获取设备加载文件成功
|
||||||
|
```
|
||||||
|
|
||||||
|
**云端图文件管理**:
|
||||||
|
|
||||||
|
1. 登录 https://uni-lab.bohrium.com
|
||||||
|
2. 进入"设备配置"
|
||||||
|
3. 创建或编辑配置
|
||||||
|
4. 保存到云端
|
||||||
|
|
||||||
|
本地启动时会自动同步最新配置。
|
||||||
|
|
||||||
|
## 调试图文件
|
||||||
|
|
||||||
|
### 验证 JSON 格式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用Python验证
|
||||||
|
python -c "import json; json.load(open('workshop1.json'))"
|
||||||
|
|
||||||
|
# 使用在线工具
|
||||||
|
# https://jsonlint.com/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检查节点引用
|
||||||
|
|
||||||
|
确保:
|
||||||
|
|
||||||
|
- 所有`links`中的`source`和`target`都存在于`nodes`中
|
||||||
|
- `parent`字段指向的节点存在
|
||||||
|
- `class`字段对应的设备/资源在注册表中存在
|
||||||
|
|
||||||
|
### 启动时验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Uni-Lab启动时会验证图文件
|
||||||
|
unilab -g workshop1.json
|
||||||
|
|
||||||
|
# 查看日志中的错误或警告
|
||||||
|
# [ERROR] 节点 xxx 的source端点 yyy 不存在
|
||||||
|
# [WARNING] 节点 zzz missing 'name', defaulting to ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 命名规范
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "pump_reagent_1", // 小写+下划线,描述性
|
||||||
|
"name": "试剂进料泵A", // 中文显示名称
|
||||||
|
"class": "syringepump" // 使用注册表中的精确名称
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 层级组织
|
||||||
|
|
||||||
|
```
|
||||||
|
host_node (主节点)
|
||||||
|
└── liquid_handler_1 (设备)
|
||||||
|
└── deck_1 (资源)
|
||||||
|
├── tiprack_1 (资源)
|
||||||
|
├── plate_1 (资源)
|
||||||
|
└── reservoir_1 (资源)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置分离
|
||||||
|
|
||||||
|
将设备特定配置放在`config`中:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "pump_1",
|
||||||
|
"class": "syringepump",
|
||||||
|
"config": {
|
||||||
|
"port": "COM3", // 设备特定
|
||||||
|
"max_flow_rate": 10, // 设备特定
|
||||||
|
"volume": 50 // 设备特定
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 版本控制
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用Git管理图文件
|
||||||
|
git add workshop1.json
|
||||||
|
git commit -m "Add new liquid handler configuration"
|
||||||
|
|
||||||
|
# 使用有意义的文件名
|
||||||
|
workshop_v1.json
|
||||||
|
workshop_production.json
|
||||||
|
workshop_test.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 注释(通过描述字段)
|
||||||
|
|
||||||
|
虽然 JSON 不支持注释,但可以使用`description`字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "pump_1",
|
||||||
|
"name": "进料泵",
|
||||||
|
"description": "用于精确控制试剂A的加料速率,最大流速10mL/min",
|
||||||
|
"class": "syringepump"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例文件位置
|
||||||
|
|
||||||
|
Uni-Lab 在安装时已预置了 **40+ 个真实的设备图文件示例**,位于 `unilabos/test/experiments/` 目录。这些都是真实项目中使用的配置文件,可以直接使用或作为参考。
|
||||||
|
|
||||||
|
### 📁 主要示例文件
|
||||||
|
|
||||||
|
```
|
||||||
|
test/experiments/
|
||||||
|
├── workshop.json # 综合工作台(推荐新手)
|
||||||
|
├── empty_devices.json # 空设备配置(最小化)
|
||||||
|
├── prcxi_9300.json # PRCXI液体处理工作站(本文示例1)
|
||||||
|
├── prcxi_9320.json # PRCXI 9320工作站
|
||||||
|
├── biomek.json # Biomek液体处理工作站
|
||||||
|
├── Grignard_flow_batchreact_single_pumpvalve.json # 格林纳德反应工作站(本文示例2)
|
||||||
|
├── dispensing_station_bioyond.json # Bioyond配液站
|
||||||
|
├── reaction_station_bioyond.json # Bioyond反应站
|
||||||
|
├── HPLC.json # HPLC分析系统
|
||||||
|
├── plr_test.json # PyLabRobot测试配置
|
||||||
|
├── lidocaine-graph.json # 利多卡因合成工作站
|
||||||
|
├── opcua_example.json # OPC UA设备集成示例
|
||||||
|
│
|
||||||
|
├── mock_devices/ # 虚拟设备(用于离线测试)
|
||||||
|
│ ├── mock_all.json # 完整虚拟设备集
|
||||||
|
│ ├── mock_pump.json # 虚拟泵
|
||||||
|
│ ├── mock_stirrer.json # 虚拟搅拌器
|
||||||
|
│ ├── mock_heater.json # 虚拟加热器
|
||||||
|
│ └── ... # 更多虚拟设备
|
||||||
|
│
|
||||||
|
├── Protocol_Test_Station/ # 协议测试工作站
|
||||||
|
│ ├── pumptransfer_test_station.json # 泵转移协议测试
|
||||||
|
│ ├── heatchill_protocol_test_station.json # 加热冷却协议测试
|
||||||
|
│ ├── filter_protocol_test_station.json # 过滤协议测试
|
||||||
|
│ └── ... # 更多协议测试
|
||||||
|
│
|
||||||
|
└── comprehensive_protocol/ # 综合协议示例
|
||||||
|
├── comprehensive_station.json # 综合工作站
|
||||||
|
└── comprehensive_slim.json # 精简版综合工作站
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🚀 快速使用
|
||||||
|
|
||||||
|
无需下载或创建,直接使用 `-g` 参数指定路径:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用简单工作台(推荐新手)
|
||||||
|
unilab --ak your_ak --sk your_sk -g test/experiments/workshop.json
|
||||||
|
|
||||||
|
# 使用虚拟设备(无需真实硬件)
|
||||||
|
unilab --ak your_ak --sk your_sk -g test/experiments/mock_devices/mock_all.json
|
||||||
|
|
||||||
|
# 使用 PRCXI 液体处理工作站
|
||||||
|
unilab --ak your_ak --sk your_sk -g test/experiments/prcxi_9300.json
|
||||||
|
|
||||||
|
# 使用格林纳德反应工作站
|
||||||
|
unilab --ak your_ak --sk your_sk -g test/experiments/Grignard_flow_batchreact_single_pumpvalve.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📚 文件分类
|
||||||
|
|
||||||
|
| 类别 | 说明 | 文件数量 |
|
||||||
|
| ------------ | ------------------------ | -------- |
|
||||||
|
| **主工作站** | 完整的实验工作站配置 | 15+ |
|
||||||
|
| **虚拟设备** | 用于开发测试的 mock 设备 | 10+ |
|
||||||
|
| **协议测试** | 各种实验协议的测试配置 | 12+ |
|
||||||
|
| **综合示例** | 包含多种协议的综合工作站 | 3+ |
|
||||||
|
|
||||||
|
这些文件展示了不同场景下的设备图配置,涵盖液体处理、有机合成、分析检测等多个领域,是学习和创建自己配置的绝佳参考。
|
||||||
|
|
||||||
|
## 快速参考:ResourceDict 完整字段列表
|
||||||
|
|
||||||
|
基于 `unilabos.ros.nodes.resource_tracker.ResourceDict` 的完整字段定义:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ResourceDict(BaseModel):
|
||||||
|
# === 基础标识 ===
|
||||||
|
id: str # 资源ID(必需)
|
||||||
|
uuid: str # 全局唯一标识符(自动生成)
|
||||||
|
name: str # 显示名称(必需)
|
||||||
|
|
||||||
|
# === 类型和分类 ===
|
||||||
|
type: Union[Literal["device"], str] # 节点类型(必需)
|
||||||
|
klass: str # 资源类别(alias="class",必需)
|
||||||
|
|
||||||
|
# === 层级关系 ===
|
||||||
|
parent: Optional[ResourceDict] # 父资源对象(不序列化)
|
||||||
|
parent_uuid: Optional[str] # 父资源UUID
|
||||||
|
|
||||||
|
# === 位置和姿态 ===
|
||||||
|
position: ResourceDictPosition # 位置信息
|
||||||
|
pose: ResourceDictPosition # 姿态信息(推荐使用)
|
||||||
|
|
||||||
|
# === 配置和数据 ===
|
||||||
|
config: Dict[str, Any] # 设备配置参数
|
||||||
|
data: Dict[str, Any] # 运行时状态数据
|
||||||
|
extra: Dict[str, Any] # 额外自定义数据
|
||||||
|
|
||||||
|
# === 元数据 ===
|
||||||
|
description: str # 资源描述
|
||||||
|
resource_schema: Dict[str, Any] # schema定义(alias="schema")
|
||||||
|
model: Dict[str, Any] # 3D模型信息
|
||||||
|
icon: str # 图标路径
|
||||||
|
```
|
||||||
|
|
||||||
|
**Position/Pose 结构**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ResourceDictPosition(BaseModel):
|
||||||
|
size: ResourceDictPositionSize # width, height, depth
|
||||||
|
scale: ResourceDictPositionScale # x, y, z
|
||||||
|
layout: Literal["2d", "x-y", "z-y", "x-z"]
|
||||||
|
position: ResourceDictPositionObject # x, y, z
|
||||||
|
position3d: ResourceDictPositionObject # x, y, z
|
||||||
|
rotation: ResourceDictPositionObject # x, y, z
|
||||||
|
cross_section_type: Literal["rectangle", "circle", "rounded_rectangle"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- {doc}`../boot_examples/index` - 查看完整启动示例
|
||||||
|
- {doc}`../developer_guide/add_device` - 了解如何添加新设备
|
||||||
|
- {doc}`06_troubleshooting` - 图文件相关问题排查
|
||||||
|
- 源码参考: `unilabos/ros/nodes/resource_tracker.py` - ResourceDict 标准定义
|
||||||
|
|
||||||
|
## 获取帮助
|
||||||
|
|
||||||
|
- 在 Web 界面中使用模板创建
|
||||||
|
- 参考示例文件:`test/experiments/` 目录
|
||||||
|
- 查看 ResourceDict 源码了解完整定义
|
||||||
|
- [GitHub 讨论区](https://github.com/dptech-corp/Uni-Lab-OS/discussions)
|
||||||
BIN
docs/user_guide/image/test_latency_result.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/user_guide/image/test_latency_running.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/user_guide/image/test_latency_select_device.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
@@ -1,43 +1,555 @@
|
|||||||
# **Uni-Lab 安装**
|
# Uni-Lab-OS 安装指南
|
||||||
|
|
||||||
## 快速开始
|
本指南提供 Uni-Lab-OS 的完整安装说明,涵盖从快速一键安装到完整开发环境配置的所有方式。
|
||||||
|
|
||||||
1. **配置 Conda 环境**
|
## 系统要求
|
||||||
|
|
||||||
Uni-Lab-OS 建议使用 `mamba` 管理环境。创建新的环境:
|
- **操作系统**: Windows 10/11, Linux (Ubuntu 20.04+), macOS (10.15+)
|
||||||
|
- **内存**: 最小 4GB,推荐 8GB 以上
|
||||||
|
- **磁盘空间**: 至少 10GB 可用空间
|
||||||
|
- **网络**: 稳定的互联网连接(用于下载软件包)
|
||||||
|
- **其他**:
|
||||||
|
- 已安装 Conda/Miniconda/Miniforge/Mamba
|
||||||
|
- 开发者需要 Git 和基本的 Python 开发知识
|
||||||
|
- 自定义 msgs 需要 GitHub 账号
|
||||||
|
|
||||||
```shell
|
## 安装方式选择
|
||||||
|
|
||||||
|
根据您的使用场景,选择合适的安装方式:
|
||||||
|
|
||||||
|
| 安装方式 | 适用人群 | 特点 | 安装时间 |
|
||||||
|
| ---------------------- | -------------------- | ------------------------------ | ---------------------------- |
|
||||||
|
| **方式一:一键安装** | 实验室用户、快速体验 | 预打包环境,离线可用,无需配置 | 5-10 分钟 (网络良好的情况下) |
|
||||||
|
| **方式二:手动安装** | 标准用户、生产环境 | 灵活配置,版本可控 | 10-20 分钟 |
|
||||||
|
| **方式三:开发者安装** | 开发者、需要修改源码 | 可编辑模式,支持自定义 msgs | 20-30 分钟 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 方式一:一键安装(推荐新用户)
|
||||||
|
|
||||||
|
使用预打包的 conda 环境,最快速的安装方法。
|
||||||
|
|
||||||
|
### 前置条件
|
||||||
|
|
||||||
|
确保已安装 Conda/Miniconda/Miniforge/Mamba。
|
||||||
|
|
||||||
|
### 安装步骤
|
||||||
|
|
||||||
|
#### 第一步:下载预打包环境
|
||||||
|
|
||||||
|
1. 访问 [GitHub Actions - Conda Pack Build](https://github.com/dptech-corp/Uni-Lab-OS/actions/workflows/conda-pack-build.yml)
|
||||||
|
|
||||||
|
2. 选择最新的成功构建记录(绿色勾号 ✓)
|
||||||
|
|
||||||
|
3. 在页面底部的 "Artifacts" 部分,下载对应你操作系统的压缩包:
|
||||||
|
- Windows: `unilab-pack-win-64-{branch}.zip`
|
||||||
|
- macOS (Intel): `unilab-pack-osx-64-{branch}.tar.gz`
|
||||||
|
- macOS (Apple Silicon): `unilab-pack-osx-arm64-{branch}.tar.gz`
|
||||||
|
- Linux: `unilab-pack-linux-64-{branch}.tar.gz`
|
||||||
|
|
||||||
|
#### 第二步:解压并运行安装脚本
|
||||||
|
|
||||||
|
**Windows**:
|
||||||
|
|
||||||
|
```batch
|
||||||
|
REM 使用 Windows 资源管理器解压下载的 zip 文件
|
||||||
|
REM 或使用命令行:
|
||||||
|
tar -xzf unilab-pack-win-64-dev.zip
|
||||||
|
|
||||||
|
REM 进入解压后的目录
|
||||||
|
cd unilab-pack-win-64-dev
|
||||||
|
|
||||||
|
REM 双击运行 install_unilab.bat
|
||||||
|
REM 或在命令行中执行:
|
||||||
|
install_unilab.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**macOS**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 解压下载的压缩包
|
||||||
|
tar -xzf unilab-pack-osx-arm64-dev.tar.gz
|
||||||
|
|
||||||
|
# 进入解压后的目录
|
||||||
|
cd unilab-pack-osx-arm64-dev
|
||||||
|
|
||||||
|
# 运行安装脚本
|
||||||
|
bash install_unilab.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 解压下载的压缩包
|
||||||
|
tar -xzf unilab-pack-linux-64-dev.tar.gz
|
||||||
|
|
||||||
|
# 进入解压后的目录
|
||||||
|
cd unilab-pack-linux-64-dev
|
||||||
|
|
||||||
|
# 添加执行权限(如果需要)
|
||||||
|
chmod +x install_unilab.sh
|
||||||
|
|
||||||
|
# 运行安装脚本
|
||||||
|
./install_unilab.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第三步:激活环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
conda activate unilab
|
||||||
|
```
|
||||||
|
|
||||||
|
激活后,您的命令行提示符应该会显示 `(unilab)` 前缀。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 方式二:手动安装(标准用户)
|
||||||
|
|
||||||
|
适合生产环境和需要灵活配置的用户。
|
||||||
|
|
||||||
|
### 第一步:安装 Mamba 环境管理器
|
||||||
|
|
||||||
|
Mamba 是 Conda 的快速替代品,我们强烈推荐使用 Mamba 来管理 Uni-Lab 环境。
|
||||||
|
|
||||||
|
#### Windows
|
||||||
|
|
||||||
|
下载并安装 Miniforge(包含 Mamba):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 访问 https://github.com/conda-forge/miniforge/releases
|
||||||
|
# 下载 Miniforge3-Windows-x86_64.exe
|
||||||
|
# 运行安装程序
|
||||||
|
|
||||||
|
# 也可以使用镜像站 https://mirrors.tuna.tsinghua.edu.cn/github-release/conda-forge/miniforge/LatestRelease/
|
||||||
|
# 下载 Miniforge3-Windows-x86_64.exe
|
||||||
|
# 运行安装程序
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux/macOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 下载 Miniforge 安装脚本
|
||||||
|
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
|
||||||
|
|
||||||
|
# 运行安装
|
||||||
|
bash Miniforge3-$(uname)-$(uname -m).sh
|
||||||
|
|
||||||
|
# 按照提示完成安装,建议选择 yes 来初始化
|
||||||
|
```
|
||||||
|
|
||||||
|
安装完成后,重新打开终端使 Mamba 生效。
|
||||||
|
|
||||||
|
### 第二步:创建 Uni-Lab 环境
|
||||||
|
|
||||||
|
使用以下命令创建 Uni-Lab 专用环境:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mamba create -n unilab python=3.11.11 # 目前ros2组件依赖版本大多为3.11.11
|
||||||
|
mamba activate unilab
|
||||||
|
mamba install -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
|
||||||
|
- `-n unilab`: 创建名为 "unilab" 的环境
|
||||||
|
- `uni-lab::unilabos`: 从 uni-lab channel 安装 unilabos 包
|
||||||
|
- `-c robostack-staging -c conda-forge`: 添加额外的软件源
|
||||||
|
|
||||||
|
**如果遇到网络问题**,可以使用清华镜像源加速下载:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 配置清华镜像源
|
||||||
|
mamba config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
|
||||||
|
mamba config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
|
||||||
|
mamba config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
|
||||||
|
|
||||||
|
# 然后重新执行安装命令
|
||||||
|
mamba create -n unilab uni-lab::unilabos -c robostack-staging
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第三步:激活环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
conda activate unilab
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 方式三:开发者安装
|
||||||
|
|
||||||
|
适用于需要修改 Uni-Lab 源代码或开发新设备驱动的开发者。
|
||||||
|
|
||||||
|
### 前置条件
|
||||||
|
|
||||||
|
- 已安装 Git
|
||||||
|
- 已安装 Mamba/Conda
|
||||||
|
- 有 GitHub 账号(如需自定义 msgs)
|
||||||
|
- 基本的 Python 开发知识
|
||||||
|
|
||||||
|
### 第一步:克隆仓库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
|
||||||
|
cd Uni-Lab-OS
|
||||||
|
```
|
||||||
|
|
||||||
|
如果您需要贡献代码,建议先 Fork 仓库:
|
||||||
|
|
||||||
|
1. 访问 https://github.com/dptech-corp/Uni-Lab-OS
|
||||||
|
2. 点击右上角的 "Fork" 按钮
|
||||||
|
3. Clone 您的 Fork 版本:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR_USERNAME/Uni-Lab-OS.git
|
||||||
|
cd Uni-Lab-OS
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第二步:安装基础环境
|
||||||
|
|
||||||
|
**推荐方式**:先通过**方式一(一键安装)**或**方式二(手动安装)**完成基础环境的安装,这将包含所有必需的依赖项(ROS2、msgs 等)。
|
||||||
|
|
||||||
|
#### 选项 A:通过一键安装(推荐)
|
||||||
|
|
||||||
|
参考上文"方式一:一键安装",完成基础环境的安装后,激活环境:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
conda activate unilab
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 选项 B:通过手动安装
|
||||||
|
|
||||||
|
参考上文"方式二:手动安装",创建并安装环境:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mamba create -n unilab python=3.11.11
|
||||||
|
conda activate unilab
|
||||||
|
mamba install -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**:这会安装包括 Python 3.11.11、ROS2 Humble、ros-humble-unilabos-msgs 和所有必需依赖
|
||||||
|
|
||||||
|
### 第三步:切换到开发版本
|
||||||
|
|
||||||
|
现在你已经有了一个完整可用的 Uni-Lab 环境,接下来将 unilabos 包切换为开发版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 确保环境已激活
|
||||||
|
conda activate unilab
|
||||||
|
|
||||||
|
# 卸载 pip 安装的 unilabos(保留所有 conda 依赖)
|
||||||
|
pip uninstall unilabos -y
|
||||||
|
|
||||||
|
# 克隆 dev 分支(如果还未克隆)
|
||||||
|
cd /path/to/your/workspace
|
||||||
|
git clone -b dev https://github.com/dptech-corp/Uni-Lab-OS.git
|
||||||
|
# 或者如果已经克隆,切换到 dev 分支
|
||||||
|
cd Uni-Lab-OS
|
||||||
|
git checkout dev
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# 以可编辑模式安装开发版 unilabos
|
||||||
|
pip install -e . -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
|
||||||
|
- `-e`: editable mode(可编辑模式),代码修改立即生效,无需重新安装
|
||||||
|
- `-i`: 使用清华镜像源加速下载
|
||||||
|
- `pip uninstall unilabos`: 只卸载 pip 安装的 unilabos 包,不影响 conda 安装的其他依赖(如 ROS2、msgs 等)
|
||||||
|
|
||||||
|
### 第四步:安装或自定义 ros-humble-unilabos-msgs(可选)
|
||||||
|
|
||||||
|
Uni-Lab 使用 ROS2 消息系统进行设备间通信。如果你使用方式一或方式二安装,msgs 包已经自动安装。
|
||||||
|
|
||||||
|
#### 使用已安装的 msgs(大多数用户)
|
||||||
|
|
||||||
|
如果你不需要修改 msgs,可以跳过此步骤,直接使用已安装的 msgs 包。验证安装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 列出所有 unilabos_msgs 接口
|
||||||
|
ros2 interface list | grep unilabos_msgs
|
||||||
|
|
||||||
|
# 查看特定 action 定义
|
||||||
|
ros2 interface show unilabos_msgs/action/DeviceCmd
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 自定义 msgs(高级用户)
|
||||||
|
|
||||||
|
如果你需要:
|
||||||
|
|
||||||
|
- 添加新的 ROS2 action 定义
|
||||||
|
- 修改现有 msg/srv/action 接口
|
||||||
|
- 为特定设备定制通信协议
|
||||||
|
|
||||||
|
请参考 **[添加新动作指令(Action)指南](../developer_guide/add_action.md)**,该指南详细介绍了如何:
|
||||||
|
|
||||||
|
- 编写新的 Action 定义
|
||||||
|
- 在线构建 Action(通过 GitHub Actions)
|
||||||
|
- 下载并安装自定义的 msgs 包
|
||||||
|
- 测试和验证新的 Action
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装自定义构建的 msgs 包
|
||||||
|
mamba remove --force ros-humble-unilabos-msgs
|
||||||
|
mamba config set safety_checks disabled # 关闭 md5 检查
|
||||||
|
mamba install /path/to/ros-humble-unilabos-msgs-*.conda --offline
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第五步:验证开发环境
|
||||||
|
|
||||||
|
完成上述步骤后,验证开发环境是否正确配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 确保环境已激活
|
||||||
|
conda activate unilab
|
||||||
|
|
||||||
|
# 检查 ROS2 环境
|
||||||
|
ros2 --version
|
||||||
|
|
||||||
|
# 检查 msgs 包
|
||||||
|
ros2 interface list | grep unilabos_msgs
|
||||||
|
|
||||||
|
# 检查 Python 可以导入 unilabos
|
||||||
|
python -c "import unilabos; print(f'Uni-Lab版本: {unilabos.__version__}')"
|
||||||
|
|
||||||
|
# 检查 unilab 命令
|
||||||
|
unilab --help
|
||||||
|
```
|
||||||
|
|
||||||
|
如果所有命令都正常输出,说明开发环境配置成功!
|
||||||
|
|
||||||
|
### 开发工具推荐
|
||||||
|
|
||||||
|
#### IDE
|
||||||
|
|
||||||
|
- **PyCharm Professional**: 强大的 Python IDE,支持远程调试
|
||||||
|
- **VS Code**: 轻量级,配合 Python 扩展使用
|
||||||
|
- **Vim/Emacs**: 适合终端开发
|
||||||
|
|
||||||
|
#### 推荐的 VS Code 扩展
|
||||||
|
|
||||||
|
- Python
|
||||||
|
- Pylance
|
||||||
|
- ROS
|
||||||
|
- URDF
|
||||||
|
- YAML
|
||||||
|
|
||||||
|
#### 调试工具
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装调试工具
|
||||||
|
pip install ipdb pytest pytest-cov -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
||||||
|
|
||||||
|
# 代码质量检查
|
||||||
|
pip install black flake8 mypy -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置 pre-commit 钩子(可选)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 pre-commit
|
||||||
|
pip install pre-commit -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
||||||
|
|
||||||
|
# 设置钩子
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
|
# 手动运行检查
|
||||||
|
pre-commit run --all-files
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验证安装
|
||||||
|
|
||||||
|
无论使用哪种安装方式,都应该验证安装是否成功。
|
||||||
|
|
||||||
|
### 基本验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 确保已激活环境
|
||||||
|
conda activate unilab # 或 unilab-dev
|
||||||
|
|
||||||
|
# 检查 unilab 命令
|
||||||
|
unilab --help
|
||||||
|
```
|
||||||
|
|
||||||
|
您应该看到类似以下的输出:
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: unilab [-h] [-g GRAPH] [-c CONTROLLERS] [--registry_path REGISTRY_PATH]
|
||||||
|
[--working_dir WORKING_DIR] [--backend {ros,simple,automancer}]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检查版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -c "import unilabos; print(f'Uni-Lab版本: {unilabos.__version__}')"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用验证脚本(方式一)
|
||||||
|
|
||||||
|
如果使用一键安装,可以运行预打包的验证脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 确保已激活环境
|
||||||
|
conda activate unilab
|
||||||
|
|
||||||
|
# 运行验证脚本
|
||||||
|
python verify_installation.py
|
||||||
|
```
|
||||||
|
|
||||||
|
如果看到 "✓ All checks passed!",说明安装成功!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题 1: 找不到 unilab 命令
|
||||||
|
|
||||||
|
**原因**: 环境未正确激活或 PATH 未设置
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 确保激活了正确的环境
|
||||||
|
conda activate unilab
|
||||||
|
|
||||||
|
# 检查 unilab 是否在 PATH 中
|
||||||
|
which unilab # Linux/macOS
|
||||||
|
where unilab # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题 2: 包冲突或依赖错误
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 删除旧环境重新创建
|
||||||
|
conda deactivate
|
||||||
|
conda env remove -n unilab
|
||||||
mamba create -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge
|
mamba create -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **安装开发版 Uni-Lab-OS**
|
### 问题 3: 下载速度慢
|
||||||
|
|
||||||
```shell
|
**解决方案**: 使用国内镜像源(清华、中科大等)
|
||||||
# 配置好conda环境后,克隆仓库
|
|
||||||
git clone https://github.com/dptech-corp/Uni-Lab-OS.git -b dev
|
|
||||||
cd Uni-Lab-OS
|
|
||||||
|
|
||||||
# 安装 Uni-Lab-OS
|
```bash
|
||||||
pip install -e .
|
# 查看当前 channel 配置
|
||||||
|
conda config --show channels
|
||||||
|
|
||||||
|
# 添加清华镜像
|
||||||
|
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **安装开发版 ros-humble-unilabos-msgs**
|
### 问题 4: 权限错误
|
||||||
|
|
||||||
**卸载老版本:**
|
**Windows 解决方案**: 以管理员身份运行命令提示符
|
||||||
```shell
|
|
||||||
|
**Linux/macOS 解决方案**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 不要使用 sudo 安装 conda 包
|
||||||
|
# 如果 conda 安装在需要权限的位置,考虑重新安装 conda 到用户目录
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题 5: 安装脚本找不到 conda(方式一)
|
||||||
|
|
||||||
|
**解决方案**: 确保你已经安装了 conda/miniconda/miniforge,并且安装在标准位置:
|
||||||
|
|
||||||
|
- **Windows**:
|
||||||
|
|
||||||
|
- `%USERPROFILE%\miniforge3`
|
||||||
|
- `%USERPROFILE%\miniconda3`
|
||||||
|
- `%USERPROFILE%\anaconda3`
|
||||||
|
- `C:\ProgramData\miniforge3`
|
||||||
|
|
||||||
|
- **macOS/Linux**:
|
||||||
|
- `~/miniforge3`
|
||||||
|
- `~/miniconda3`
|
||||||
|
- `~/anaconda3`
|
||||||
|
- `/opt/conda`
|
||||||
|
|
||||||
|
如果安装在其他位置,可以先激活 conda base 环境,然后手动运行安装脚本。
|
||||||
|
|
||||||
|
### 问题 6: 安装后激活环境提示找不到?
|
||||||
|
|
||||||
|
**解决方案**: 尝试以下方法:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 方法 1: 使用 conda activate
|
||||||
conda activate unilab
|
conda activate unilab
|
||||||
conda remove --force ros-humble-unilabos-msgs
|
|
||||||
```
|
|
||||||
有时相同的安装包版本会由于dev构建得到的md5不一样,触发安全检查,可输入 `config set safety_checks disabled` 来关闭安全检查。
|
|
||||||
|
|
||||||
**安装新版本:**
|
# 方法 2: 使用完整路径激活(Windows)
|
||||||
|
call C:\Users\{YourUsername}\miniforge3\envs\unilab\Scripts\activate.bat
|
||||||
|
|
||||||
访问 https://github.com/dptech-corp/Uni-Lab-OS/actions/workflows/multi-platform-build.yml 选择最新的构建,下载对应平台的压缩包(仅解压一次,得到.conda文件)使用如下指令:
|
# 方法 2: 使用完整路径激活(Unix)
|
||||||
```shell
|
source ~/miniforge3/envs/unilab/bin/activate
|
||||||
conda activate base
|
|
||||||
conda install ros-humble-unilabos-msgs-<version>-<platform>.conda --offline -n <环境名>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **启动 Uni-Lab 系统**
|
### 问题 7: conda-unpack 失败怎么办?(方式一)
|
||||||
|
|
||||||
请参见{doc}`启动样例 <../boot_examples/index>`或{doc}`启动指南 <launch>`了解详细的启动方法。
|
**解决方案**: 尝试手动运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windows
|
||||||
|
cd %CONDA_PREFIX%\envs\unilab
|
||||||
|
.\Scripts\conda-unpack.exe
|
||||||
|
|
||||||
|
# macOS/Linux
|
||||||
|
cd $CONDA_PREFIX/envs/unilab
|
||||||
|
./bin/conda-unpack
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题 8: 环境很大,有办法减小吗?
|
||||||
|
|
||||||
|
**解决方案**: 预打包的环境包含所有依赖,通常较大(压缩后 2-5GB)。这是为了确保离线安装和完整功能。如果空间有限,考虑使用方式二手动安装,只安装需要的组件。
|
||||||
|
|
||||||
|
### 问题 9: 如何更新到最新版本?
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
|
||||||
|
**方式一用户**: 重新下载最新的预打包环境,运行安装脚本时选择覆盖现有环境。
|
||||||
|
|
||||||
|
**方式二/三用户**: 在现有环境中更新:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
conda activate unilab
|
||||||
|
|
||||||
|
# 更新 unilabos
|
||||||
|
cd /path/to/Uni-Lab-OS
|
||||||
|
git pull
|
||||||
|
pip install -e . --upgrade -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
||||||
|
|
||||||
|
# 更新 ros-humble-unilabos-msgs
|
||||||
|
mamba update ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
安装完成后,请继续:
|
||||||
|
|
||||||
|
- **快速启动**: 学习如何首次启动 Uni-Lab
|
||||||
|
- **配置指南**: 配置您的实验室环境和设备
|
||||||
|
- **运行示例**: 查看启动示例和最佳实践
|
||||||
|
- **开发指南**:
|
||||||
|
- 添加新设备驱动
|
||||||
|
- 添加新物料资源
|
||||||
|
- 了解工作站架构
|
||||||
|
|
||||||
|
## 需要帮助?
|
||||||
|
|
||||||
|
- **故障排查**: 查看更详细的故障排查信息
|
||||||
|
- **GitHub Issues**: [报告问题](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
|
- **开发者文档**: 查看开发者指南获取更多技术细节
|
||||||
|
- **社区讨论**: [GitHub Discussions](https://github.com/dptech-corp/Uni-Lab-OS/discussions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**提示**:
|
||||||
|
|
||||||
|
- 生产环境推荐使用方式二(手动安装)的稳定版本
|
||||||
|
- 开发和测试推荐使用方式三(开发者安装)
|
||||||
|
- 快速体验和演示推荐使用方式一(一键安装)
|
||||||
|
|||||||
@@ -132,15 +132,14 @@ unilab --config path/to/your/config.py
|
|||||||
|
|
||||||
使用 `-c` 传入控制逻辑配置。
|
使用 `-c` 传入控制逻辑配置。
|
||||||
|
|
||||||
不管使用哪一种初始化方式,设备/物料字典均需包含 `class` 属性,用于查找注册表信息。默认查找范围都是 Uni-Lab 内部注册表 `unilabos/registry/{devices,device_comms,resources}`。要添加额外的注册表路径,可以使用 `--registry_path` 加入 `<your-registry-path>/{devices,device_comms,resources}`。
|
不管使用哪一种初始化方式,设备/物料字典均需包含 `class` 属性,用于查找注册表信息。默认查找范围都是 Uni-Lab 内部注册表 `unilabos/registry/{devices,device_comms,resources}`。要添加额外的注册表路径,可以使用 `--registry_path` 加入 `<your-registry-path>/{devices,device_comms,resources}`,只输入<your-registry-path>即可,支持多次--registry_path指定多个目录。
|
||||||
|
|
||||||
## 通信中间件 `--backend`
|
## 通信中间件 `--backend`
|
||||||
|
|
||||||
目前 Uni-Lab 支持以下通信中间件:
|
目前 Uni-Lab 支持以下通信中间件:
|
||||||
|
|
||||||
- **ros** (默认):基于 ROS2 的通信
|
- **ros** (默认):基于 ROS2 的通信
|
||||||
- **simple**:简化通信模式
|
- **automancer**:Automancer 兼容模式 (实验性)
|
||||||
- **automancer**:Automancer 兼容模式
|
|
||||||
|
|
||||||
## 端云桥接 `--app_bridges`
|
## 端云桥接 `--app_bridges`
|
||||||
|
|
||||||
@@ -169,7 +168,7 @@ unilab --config path/to/your/config.py
|
|||||||
通过 `--visual` 参数选择:
|
通过 `--visual` 参数选择:
|
||||||
|
|
||||||
- **rviz**:使用 RViz 进行 3D 可视化
|
- **rviz**:使用 RViz 进行 3D 可视化
|
||||||
- **web**:使用 Web 界面进行可视化
|
- **web**:使用 Web 界面进行可视化 (基于Pylabrobot)
|
||||||
- **disable** (默认):禁用可视化
|
- **disable** (默认):禁用可视化
|
||||||
|
|
||||||
## 实验室管理
|
## 实验室管理
|
||||||
@@ -245,78 +244,3 @@ unilab --ak your_ak --sk your_sk --port 8080 --disable_browser
|
|||||||
- 检查图谱文件格式是否正确
|
- 检查图谱文件格式是否正确
|
||||||
- 验证设备连接和端点配置
|
- 验证设备连接和端点配置
|
||||||
- 确保注册表路径正确
|
- 确保注册表路径正确
|
||||||
|
|
||||||
## 页面操作
|
|
||||||
|
|
||||||
### 1. 启动成功
|
|
||||||
当您启动成功后,可以看到物料列表,节点模版和组态图如图展示
|
|
||||||

|
|
||||||
|
|
||||||
### 2. 根据需求创建设备和物料
|
|
||||||
我们可以做一个简单的案例
|
|
||||||
* 在容器1中加入水
|
|
||||||
* 通过传输泵将容器1中的水转移到容器2中
|
|
||||||
#### 2.1 添加所需的设备和物料
|
|
||||||
仪器设备work_station中的workstation 数量x1
|
|
||||||
仪器设备virtual_device中的virtual_transfer_pump 数量x1
|
|
||||||
物料耗材container中的container 数量x2
|
|
||||||
|
|
||||||
#### 2.2 将设备和物料根据父子关系进行关联
|
|
||||||
当我们添加设备时,仪器耗材模块的物料列表也会实时更新
|
|
||||||
我们需要将设备和物料拖拽到workstation中并在画布上将它们连接起来,就像真实的设备操作一样
|
|
||||||

|
|
||||||
|
|
||||||
### 3. 创建工作流
|
|
||||||
进入工作流模块 → 点击"我创建的" → 新建工作流
|
|
||||||

|
|
||||||
|
|
||||||
#### 3.1 新增工作流节点
|
|
||||||
我们可以进入指定工作流,在空白处右键
|
|
||||||
* 选择Laboratory→host_node中的creat_resource
|
|
||||||
* 选择Laboratory→workstation中的PumpTransferProtocol
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
#### 3.2 配置节点参数
|
|
||||||
根据案例,工作流包含两个步骤:
|
|
||||||
1. 使用creat_resource在容器中创建水
|
|
||||||
2. 通过泵传输协议将水传输到另一个容器
|
|
||||||
|
|
||||||
我们点击creat_resource卡片上的编辑按钮来配置参数⭐️
|
|
||||||
class_name :container
|
|
||||||
device_id : workstation
|
|
||||||
liquid_input_slot : 0或-1均可
|
|
||||||
liquid_type : water
|
|
||||||
liquid_volume : 根据需求填写即可,默认单位ml,这里举例50
|
|
||||||
parent : workstation
|
|
||||||
res_id : containe
|
|
||||||
关联设备名称(原unilabos_device_id) : 这里就填写host_node
|
|
||||||
**配置完成后点击底部保存按钮**
|
|
||||||
|
|
||||||
我们点击PumpTransferProtocol卡片上的编辑按钮来配置参数⭐️
|
|
||||||
event : transfer_liquid
|
|
||||||
from_vessel : water
|
|
||||||
to_vessel : container1
|
|
||||||
volume : 根据需求填写即可,默认单位ml,这里举例50
|
|
||||||
关联设备名称(原unilabos_device_id) : 这里就填写workstation
|
|
||||||
**配置完成后点击底部保存按钮**
|
|
||||||
|
|
||||||
#### 3.3 运行工作流
|
|
||||||
1. 连接两个节点卡片
|
|
||||||
2. 点击底部保存按钮
|
|
||||||
3. 点击运行按钮执行工作流
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 运行监控
|
|
||||||
* 运行状态和消息实时显示在底部控制台
|
|
||||||
* 如有报错,可点击查看详细信息
|
|
||||||
|
|
||||||
### 结果验证
|
|
||||||
工作流完成后,返回仪器耗材模块:
|
|
||||||
* 点击 container1卡片查看详情
|
|
||||||
* 确认其中包含参数指定的水和容量
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,197 +0,0 @@
|
|||||||
# Uni-Lab-OS 一键安装快速指南
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
本指南提供最快速的 Uni-Lab-OS 安装方法,使用预打包的 conda 环境,无需手动配置依赖。
|
|
||||||
|
|
||||||
## 前置要求
|
|
||||||
|
|
||||||
- 已安装 Conda/Miniconda/Miniforge/Mamba
|
|
||||||
- 至少 10GB 可用磁盘空间
|
|
||||||
- Windows 10+, macOS 10.14+, 或 Linux (Ubuntu 20.04+)
|
|
||||||
|
|
||||||
## 安装步骤
|
|
||||||
|
|
||||||
### 第一步:下载预打包环境
|
|
||||||
|
|
||||||
1. 访问 [GitHub Actions - Conda Pack Build](https://github.com/dptech-corp/Uni-Lab-OS/actions/workflows/conda-pack-build.yml)
|
|
||||||
|
|
||||||
2. 选择最新的成功构建记录(绿色勾号 ✓)
|
|
||||||
|
|
||||||
3. 在页面底部的 "Artifacts" 部分,下载对应你操作系统的压缩包:
|
|
||||||
- Windows: `unilab-pack-win-64-{branch}.zip`
|
|
||||||
- macOS (Intel): `unilab-pack-osx-64-{branch}.tar.gz`
|
|
||||||
- macOS (Apple Silicon): `unilab-pack-osx-arm64-{branch}.tar.gz`
|
|
||||||
- Linux: `unilab-pack-linux-64-{branch}.tar.gz`
|
|
||||||
|
|
||||||
### 第二步:解压并运行安装脚本
|
|
||||||
|
|
||||||
#### Windows
|
|
||||||
|
|
||||||
```batch
|
|
||||||
REM 使用 Windows 资源管理器解压下载的 zip 文件
|
|
||||||
REM 或使用命令行:
|
|
||||||
tar -xzf unilab-pack-win-64-dev.zip
|
|
||||||
|
|
||||||
REM 进入解压后的目录
|
|
||||||
cd unilab-pack-win-64-dev
|
|
||||||
|
|
||||||
REM 双击运行 install_unilab.bat
|
|
||||||
REM 或在命令行中执行:
|
|
||||||
install_unilab.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
#### macOS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 解压下载的压缩包
|
|
||||||
tar -xzf unilab-pack-osx-arm64-dev.tar.gz
|
|
||||||
|
|
||||||
# 进入解压后的目录
|
|
||||||
cd unilab-pack-osx-arm64-dev
|
|
||||||
|
|
||||||
# 运行安装脚本
|
|
||||||
bash install_unilab.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 解压下载的压缩包
|
|
||||||
tar -xzf unilab-pack-linux-64-dev.tar.gz
|
|
||||||
|
|
||||||
# 进入解压后的目录
|
|
||||||
cd unilab-pack-linux-64-dev
|
|
||||||
|
|
||||||
# 添加执行权限(如果需要)
|
|
||||||
chmod +x install_unilab.sh
|
|
||||||
|
|
||||||
# 运行安装脚本
|
|
||||||
./install_unilab.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 第三步:激活环境
|
|
||||||
|
|
||||||
```bash
|
|
||||||
conda activate unilab
|
|
||||||
```
|
|
||||||
|
|
||||||
### 第四步:验证安装(推荐)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 确保已激活环境
|
|
||||||
conda activate unilab
|
|
||||||
|
|
||||||
# 运行验证脚本
|
|
||||||
python verify_installation.py
|
|
||||||
```
|
|
||||||
|
|
||||||
如果看到 "✓ All checks passed!",说明安装成功!
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### Q: 安装脚本找不到 conda?
|
|
||||||
|
|
||||||
**A:** 确保你已经安装了 conda/miniconda/miniforge,并且安装在标准位置:
|
|
||||||
|
|
||||||
- **Windows**:
|
|
||||||
|
|
||||||
- `%USERPROFILE%\miniforge3`
|
|
||||||
- `%USERPROFILE%\miniconda3`
|
|
||||||
- `%USERPROFILE%\anaconda3`
|
|
||||||
- `C:\ProgramData\miniforge3`
|
|
||||||
|
|
||||||
- **macOS/Linux**:
|
|
||||||
- `~/miniforge3`
|
|
||||||
- `~/miniconda3`
|
|
||||||
- `~/anaconda3`
|
|
||||||
- `/opt/conda`
|
|
||||||
|
|
||||||
如果安装在其他位置,可以先激活 conda base 环境,然后手动运行安装脚本。
|
|
||||||
|
|
||||||
### Q: 安装后激活环境提示找不到?
|
|
||||||
|
|
||||||
**A:** 尝试以下方法:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 方法 1: 使用 conda activate
|
|
||||||
conda activate unilab
|
|
||||||
|
|
||||||
# 方法 2: 使用完整路径激活(Windows)
|
|
||||||
call C:\Users\{YourUsername}\miniforge3\envs\unilab\Scripts\activate.bat
|
|
||||||
|
|
||||||
# 方法 2: 使用完整路径激活(Unix)
|
|
||||||
source ~/miniforge3/envs/unilab/bin/activate
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: conda-unpack 失败怎么办?
|
|
||||||
|
|
||||||
**A:** 尝试手动运行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Windows
|
|
||||||
cd %CONDA_PREFIX%\envs\unilab
|
|
||||||
.\Scripts\conda-unpack.exe
|
|
||||||
|
|
||||||
# macOS/Linux
|
|
||||||
cd $CONDA_PREFIX/envs/unilab
|
|
||||||
./bin/conda-unpack
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: 验证脚本报错?
|
|
||||||
|
|
||||||
**A:** 首先确认环境已激活:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 检查当前环境
|
|
||||||
conda env list
|
|
||||||
|
|
||||||
# 应该看到 unilab 前面有 * 标记
|
|
||||||
```
|
|
||||||
|
|
||||||
如果仍有问题,查看具体报错信息,可能需要:
|
|
||||||
|
|
||||||
- 重新运行安装脚本
|
|
||||||
- 检查磁盘空间
|
|
||||||
- 查看详细文档
|
|
||||||
|
|
||||||
### Q: 环境很大,有办法减小吗?
|
|
||||||
|
|
||||||
**A:** 预打包的环境包含所有依赖,通常较大(压缩后 2-5GB)。这是为了确保离线安装和完整功能。如果空间有限,考虑使用手动安装方式,只安装需要的组件。
|
|
||||||
|
|
||||||
### Q: 如何更新到最新版本?
|
|
||||||
|
|
||||||
**A:** 重新下载最新的预打包环境,运行安装脚本时选择覆盖现有环境。
|
|
||||||
|
|
||||||
或者在现有环境中更新:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
conda activate unilab
|
|
||||||
|
|
||||||
# 更新 unilabos
|
|
||||||
cd /path/to/Uni-Lab-OS
|
|
||||||
git pull
|
|
||||||
pip install -e . --upgrade
|
|
||||||
|
|
||||||
# 更新 ros-humble-unilabos-msgs
|
|
||||||
mamba update ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge
|
|
||||||
```
|
|
||||||
|
|
||||||
## 下一步
|
|
||||||
|
|
||||||
安装完成后,你可以:
|
|
||||||
|
|
||||||
1. **查看启动指南**: {doc}`launch`
|
|
||||||
2. **运行示例**: {doc}`../boot_examples/index`
|
|
||||||
3. **配置设备**: 编辑 `unilabos_data/startup_config.json`
|
|
||||||
4. **阅读开发文档**: {doc}`../developer_guide/workstation_architecture`
|
|
||||||
|
|
||||||
## 需要帮助?
|
|
||||||
|
|
||||||
- **文档**: [docs/user_guide/installation.md](installation.md)
|
|
||||||
- **问题反馈**: [GitHub Issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
|
||||||
- **开发版安装**: 参考 {doc}`installation` 的方式二
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**提示**: 这个预打包环境包含了从指定分支(通常是 `dev`)构建的最新代码。如果需要稳定版本,请使用方式二手动安装 release 版本。
|
|
||||||
@@ -302,6 +302,11 @@ def main():
|
|||||||
graph, resource_tree_set, resource_links = read_node_link_json(request_startup_json)
|
graph, resource_tree_set, resource_links = read_node_link_json(request_startup_json)
|
||||||
else:
|
else:
|
||||||
file_path = args_dict["graph"]
|
file_path = args_dict["graph"]
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
temp_file_path = os.path.abspath(str(os.path.join(__file__, "..", "..", file_path)))
|
||||||
|
if os.path.isfile(temp_file_path):
|
||||||
|
print_status(f"使用相对路径{temp_file_path}", "info")
|
||||||
|
file_path = temp_file_path
|
||||||
if file_path.endswith(".json"):
|
if file_path.endswith(".json"):
|
||||||
graph, resource_tree_set, resource_links = read_node_link_json(file_path)
|
graph, resource_tree_set, resource_links = read_node_link_json(file_path)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -31,15 +31,17 @@ from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|||||||
|
|
||||||
|
|
||||||
class LiquidHandlerMiddleware(LiquidHandler):
|
class LiquidHandlerMiddleware(LiquidHandler):
|
||||||
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8, total_height: float = 310, **kwargs):
|
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8, **kwargs):
|
||||||
self._simulator = simulator
|
self._simulator = simulator
|
||||||
self.channel_num = channel_num
|
self.channel_num = channel_num
|
||||||
joint_config = kwargs.get("joint_config", None)
|
joint_config = kwargs.get("joint_config", None)
|
||||||
if simulator:
|
if simulator:
|
||||||
self._simulate_backend = UniLiquidHandlerRvizBackend(channel_num,total_height, joint_config=joint_config, lh_device_id = deck.name)
|
if joint_config:
|
||||||
|
self._simulate_backend = UniLiquidHandlerRvizBackend(channel_num, kwargs["total_height"],
|
||||||
|
joint_config=joint_config, lh_device_id=deck.name)
|
||||||
|
else:
|
||||||
|
self._simulate_backend = LiquidHandlerChatterboxBackend(channel_num)
|
||||||
self._simulate_handler = LiquidHandlerAbstract(self._simulate_backend, deck, False)
|
self._simulate_handler = LiquidHandlerAbstract(self._simulate_backend, deck, False)
|
||||||
if hasattr(backend, "total_height"):
|
|
||||||
backend.total_height = total_height
|
|
||||||
super().__init__(backend, deck)
|
super().__init__(backend, deck)
|
||||||
|
|
||||||
async def setup(self, **backend_kwargs):
|
async def setup(self, **backend_kwargs):
|
||||||
@@ -544,51 +546,16 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
support_touch_tip = True
|
support_touch_tip = True
|
||||||
_ros_node: BaseROS2DeviceNode
|
_ros_node: BaseROS2DeviceNode
|
||||||
|
|
||||||
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool=False, channel_num:int = 8,total_height: float = 310,**backend_kwargs):
|
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool=False, channel_num:int = 8):
|
||||||
"""Initialize a LiquidHandler.
|
"""Initialize a LiquidHandler.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
backend: Backend to use.
|
backend: Backend to use.
|
||||||
deck: Deck to use.
|
deck: Deck to use.
|
||||||
"""
|
"""
|
||||||
backend_type = None
|
|
||||||
if isinstance(backend, dict) and "type" in backend:
|
|
||||||
backend_dict = backend.copy()
|
|
||||||
type_str = backend_dict.pop("type")
|
|
||||||
try:
|
|
||||||
# Try to get class from string using globals (current module), or fallback to pylabrobot or unilabos namespaces
|
|
||||||
backend_cls = None
|
|
||||||
if type_str in globals():
|
|
||||||
backend_cls = globals()[type_str]
|
|
||||||
else:
|
|
||||||
# Try resolving dotted notation, e.g. "xxx.yyy.ClassName"
|
|
||||||
components = type_str.split(".")
|
|
||||||
mod = None
|
|
||||||
if len(components) > 1:
|
|
||||||
module_name = ".".join(components[:-1])
|
|
||||||
try:
|
|
||||||
import importlib
|
|
||||||
mod = importlib.import_module(module_name)
|
|
||||||
except ImportError:
|
|
||||||
mod = None
|
|
||||||
if mod is not None:
|
|
||||||
backend_cls = getattr(mod, components[-1], None)
|
|
||||||
if backend_cls is None:
|
|
||||||
# Try pylabrobot style import (if available)
|
|
||||||
try:
|
|
||||||
import pylabrobot
|
|
||||||
backend_cls = getattr(pylabrobot, type_str, None)
|
|
||||||
except Exception:
|
|
||||||
backend_cls = None
|
|
||||||
if backend_cls is not None and isinstance(backend_cls, type):
|
|
||||||
backend_type = backend_cls(**backend_dict) # pass the rest of dict as kwargs
|
|
||||||
except Exception as exc:
|
|
||||||
raise RuntimeError(f"Failed to convert backend type '{type_str}' to class: {exc}")
|
|
||||||
else:
|
|
||||||
backend_type = backend
|
|
||||||
self._simulator = simulator
|
self._simulator = simulator
|
||||||
self.group_info = dict()
|
self.group_info = dict()
|
||||||
super().__init__(backend_type, deck, simulator, channel_num,total_height,**backend_kwargs)
|
super().__init__(backend, deck, simulator, channel_num)
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||||
self._ros_node = ros_node
|
self._ros_node = ros_node
|
||||||
|
|||||||