# Uni-Lab-OS 最佳实践指南

## 关于本指南

本指南将引导您从零开始完成 Uni-Lab-OS 实验室系统的完整搭建，从环境安装到高级设备开发。无论您是初次接触 Uni-Lab-OS 的用户，还是希望深入定制开发的开发者，都能在本指南中找到清晰的步骤和实用建议。

### 适用对象

- **实验室管理员**：负责实验室系统部署和维护
- **实验操作人员**：日常使用系统进行实验操作
- **设备开发者**：为实验室添加自定义设备和功能
- **系统集成商**：集成多个实验室系统

### 完整流程概览

```
环境准备 → 创建实验室账号 → 系统启动 → 上传注册表
    ↓
在线创建设备图 → 测试系统运行 → 运行工作流
    ↓
多节点部署（可选）→ 自定义设备开发 → 复杂工作站搭建
```

---

## 第一部分：快速上手

### 1. 环境准备（主机 Host）

#### 1.1 安装 Uni-Lab-OS

详细的安装步骤请参考 [安装指南](installation.md)。

**选择合适的安装包：**

| 安装包 | 适用场景 | 包含组件 |
|--------|----------|----------|
| `unilabos` | **推荐大多数用户**，生产部署 | 完整安装包，开箱即用 |
| `unilabos-env` | 开发者（可编辑安装） | 仅环境依赖，通过 pip 安装 unilabos |
| `unilabos-full` | 仿真/可视化 | unilabos + 完整 ROS2 桌面版 + Gazebo + MoveIt |

**关键步骤：**

```bash
# 1. 安装 Mamba（如果尚未安装）
# 下载 Miniforge: https://github.com/conda-forge/miniforge/releases

# 2. 创建 Conda 环境
mamba create -n unilab python=3.11.14

# 3. 激活环境
mamba activate unilab

# 4. 安装 Uni-Lab-OS（选择其一）

# 方案 A：标准安装（推荐大多数用户）
mamba install uni-lab::unilabos -c robostack-staging -c conda-forge

# 方案 B：开发者环境（可编辑模式开发）
mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge
pip install -e /path/to/Uni-Lab-OS  # 可编辑安装
uv pip install -r unilabos/utils/requirements.txt  # 安装 pip 依赖

# 方案 C：完整版（仿真/可视化）
mamba install uni-lab::unilabos-full -c robostack-staging -c conda-forge
```

**选择建议：**
- **日常使用/生产部署**：使用 `unilabos`（推荐），完整功能，开箱即用
- **开发者**：使用 `unilabos-env` + `pip install -e .` + `uv pip install -r unilabos/utils/requirements.txt`，代码修改立即生效
- **仿真/可视化**：使用 `unilabos-full`，含 Gazebo、rviz2、MoveIt

#### 1.2 验证安装

```bash
# 检查 unilabos 是否安装成功
python -c "import unilabos; print(unilabos.__version__)"

# 验证 ROS 消息包
python -c "from unilabos_msgs.msg import Resource; print('ROS msgs OK')"
```

如果两条命令都正常输出，说明安装成功。

---

### 2. 创建您的第一个实验室

#### 2.1 注册实验室账号

1. 访问 [https://uni-lab.bohrium.com](https://uni-lab.bohrium.com)
2. 注册账号并登录
3. 创建新实验室

#### 2.2 获取 Access Key 和 Secret Key

1. 在实验室管理界面，找到"密钥管理"
2. 创建新的 AK/SK 密钥对
3. **妥善保管这些密钥**（类似于密码，不要泄露）

![复制 AK/SK](image/copy_aksk.gif)

**重要提示：**

- AK (Access Key)：实验室的唯一标识
- SK (Secret Key)：实验室的访问密码
- 这两个密钥在后续启动命令中都需要使用

#### 2.3 首次启动配置

首次启动时，系统会自动引导您创建配置文件：

```bash
# 首次启动
conda activate unilab
unilab --ak your_ak --sk your_sk
```

系统会询问是否创建配置文件，输入 `Y` 确认。配置文件会自动生成在 `./unilabos_data/local_config.py`。

更多配置选项请参考 [配置指南](../advanced_usage/configuration.md)。

---

### 3. 启动系统

详细的启动参数说明请参考 [启动指南](launch.md)。

#### 3.1 基本启动命令

```bash
conda activate unilab
unilab --ak your_ak --sk your_sk -g path/to/graph.json
```

**关键参数说明：**

- `--ak`：您的 Access Key（必需）
- `--sk`：您的 Secret Key（必需）
- `-g`：设备组态图文件路径（必需）

#### 3.2 首次启动注意事项

**第一次启动时，如果没有组态图文件：**

由于首次启动需要 `-g` 参数指定组态图，您可以：

**方案 1：使用预先创建好的示例组态图（推荐）**

Uni-Lab-OS 在安装时已经预置了大量真实的设备图文件示例，无需下载或创建，直接使用 `-g` 参数指定即可：

```bash
# 使用简单的工作台示例（推荐新手）
unilab --ak your_ak --sk your_sk -g test/experiments/workshop.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

# 使用 Bioyond 配液站示例
unilab --ak your_ak --sk your_sk -g test/experiments/dispensing_station_bioyond.json

# 使用 HPLC 分析系统示例
unilab --ak your_ak --sk your_sk -g test/experiments/HPLC.json

# 使用空设备配置（最小化配置）
unilab --ak your_ak --sk your_sk -g test/experiments/empty_devices.json
```

**更多可用示例**：系统预置了 40+ 个组态图文件，涵盖液体处理、有机合成、分析检测等多个领域。完整列表请查看 `unilabos/test/experiments/` 目录。

**方案 2：创建一个空的组态图**

如果您想从零开始配置，可以创建一个简单的主机节点配置：

```bash
# 创建 example_empty.json
cat > example_empty.json << 'EOF'
{
  "nodes": [],
  "links": []
}
EOF

# 使用该配置启动
unilab --ak your_ak --sk your_sk -g example_empty.json
```

> **提示**：详细的组态图文件编写指南，请参考 [设备图文件说明](graph_files.md)。

**启动成功标志：**

- 终端显示 `[Host Node] Host node initialized.`
- 自动打开浏览器，显示 Web 管理界面
- 地址：`http://localhost:8002`

#### 3.3 常用启动选项

```bash
# 禁用自动打开浏览器
unilab --ak your_ak --sk your_sk -g graph.json --disable_browser

# 使用不同端口
unilab --ak your_ak --sk your_sk -g graph.json --port 8080

# 测试环境
unilab --addr test --ak your_ak --sk your_sk -g graph.json

# 跳过环境检查（加快启动）
unilab --ak your_ak --sk your_sk -g graph.json --skip_env_check
```

---

### 4. 上传注册表（一次性操作）

#### 4.1 什么是注册表上传？

注册表包含您的设备和物料的完整定义。上传到云端后，在线界面才能识别和使用这些设备。

#### 4.2 何时需要上传？

**必须上传的情况：**

- 首次启动实验室
- 添加了新的设备类型
- 修改了设备的注册表定义

#### 4.3 如何上传注册表

```bash
unilab --ak your_ak --sk your_sk -g graph.json --upload_registry
```

**性能影响说明：**

- 上传注册表会增加启动时间（通常 5-15 秒）
- 上传时间取决于：
  - 设备和物料的数量
  - 网络速度
- 建议：开发调试时首次上传，后续本地测试可省略

**验证上传成功：**

在 Web 界面的"仪器设备"或"物料耗材"模块中，应该能看到您的设备和物料列表。

---

### 5. 在线创建您的第一个设备图

#### 5.1 使用预设组态图（推荐新手）

Uni-Lab-OS 在安装时已经包含了大量真实的设备图文件，位于 `unilabos/test/experiments/` 目录，涵盖：

- 液体处理：PRCXI、Biomek 等自动化移液工作站
- 有机合成：格林纳德反应、流动化学等工作站
- 分析检测：HPLC、光谱仪等分析仪器
- 生物实验：Bioyond 配液站、反应站等
- 协议测试：各种实验协议的测试工作站
- 虚拟设备：用于开发调试的 mock 设备

**常用示例组态图：**

| 组态图文件                                       | 说明                 | 适合场景             |
| ------------------------------------------------ | -------------------- | -------------------- |
| `workshop.json`                                  | 综合工作台           | 新手入门推荐         |
| `empty_devices.json`                             | 空设备配置           | 最小化配置，快速启动 |
| `prcxi_9300.json`                                | PRCXI 液体处理工作站 | 液体处理自动化       |
| `Grignard_flow_batchreact_single_pumpvalve.json` | 格林纳德反应工作站   | 有机合成流动化学     |
| `dispensing_station_bioyond.json`                | Bioyond 配液站       | 生物样品配液         |
| `reaction_station_bioyond.json`                  | Bioyond 反应站       | 生物化学反应         |
| `HPLC.json`                                      | HPLC 分析系统        | 色谱分析             |
| `plr_test.json`                                  | PyLabRobot 测试      | PyLabRobot 集成测试  |
| `mock_devices/mock_all.json`                     | 完整虚拟设备集       | 开发调试，离线测试   |

**使用方法：**

```bash
# 使用简单工作台（推荐新手第一次启动）
unilab --ak your_ak --sk your_sk -g test/experiments/workshop.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

# 使用虚拟设备（无需真实硬件，用于学习和测试）
unilab --ak your_ak --sk your_sk -g test/experiments/mock_devices/mock_all.json
```

**注意**：所有组态图文件都是真实项目中使用的配置，可以直接学习和参考。完整文件列表请查看 `unilabos/test/experiments/` 目录。

> 关于设备图文件的结构、字段定义、格式兼容性等完整指南，请参考 [设备图文件说明](graph_files.md)。

#### 5.2 访问 Web 界面

启动系统后，访问[https://uni-lab.bohrium.com](https://uni-lab.bohrium.com)

#### 5.3 添加设备和物料

进入"仪器耗材"模块：

**示例场景：** 创建一个简单的液体转移实验

1. **添加工作站（必需）：**

   - 在"仪器设备"中找到 `work_station`
   - 添加 `workstation` x1

2. **添加虚拟转移泵：**

   - 在"仪器设备"中找到 `virtual_device`
   - 添加 `virtual_transfer_pump` x1

3. **添加容器：**
   - 在"物料耗材"中找到 `container`
   - 添加 `container` x2

![物料列表](image/material.png)

#### 5.4 建立设备连接关系

将设备和物料拖拽到画布上，建立父子关系：

1. 将两个 `container` 拖拽到 `workstation` 中
2. 将 `virtual_transfer_pump` 拖拽到 `workstation` 中
3. 在画布上连接它们（模拟真实的物理连接）

![设备连接](image/links.png)

**连接规则：**

- 所有设备和物料都必须有父节点（除了顶层工作站）
- 连接关系反映真实的物理布局
- 连接后才能在工作流中使用

#### 5.5 保存组态图

1. 点击"保存"按钮
2. 系统会生成 JSON 格式的组态图
3. 可以下载保存为本地文件，下次使用 `-g` 参数启动

---

### 6. 测试实验室运行状态

在运行实际工作流之前，建议先测试系统是否正常运行。

#### 6.1 使用 host_node 的 test_latency

`test_latency` 是 host_node 提供的测试方法，用于：

- 测试网络延迟
- 校准时间误差
- 验证系统响应

**通过 Web 界面测试：**

1. 在 Web 界面找到"工作流"模块
2. 新建空白工作流
3. 右键画布空白处，找到 `laboratory` -> `host_node` 在动作列表中找到 `test_latency`
4. 设置"关联设备名称"为 `host_node`

![选择 host_node](image/test_latency_select_device.png)

5. 点击保存，然后运行工作流

![运行测试](image/test_latency_running.png)

6. 在控制台查看执行结果，点击查看返回值

![查看测试结果](image/test_latency_result.png)

**测试结果解读：**

系统会进行 5 次 ping-pong 测试，返回 JSON 格式的结果，包含以下关键指标：

- **avg_rtt_ms**：平均往返延迟（毫秒），正常应在几十到几百毫秒
- **avg_time_diff_ms**：主机与云端的平均时间差（毫秒）
- **max_time_error_ms**：最大时间误差（毫秒）
- **raw_delay_ms**：原始延迟测量值（毫秒）
- **task_delay_ms**：任务处理延迟（毫秒）
- **test_count**：测试次数
- **status**：测试状态（success 表示成功）

**实际返回结果示例：**

```json
{
  "error": "",
  "return_value": {
    "avg_rtt_ms": 49.64,
    "avg_time_diff_ms": 3872.43,
    "max_time_error_ms": 3885.34,
    "raw_delay_ms": 3900.24,
    "status": "success",
    "task_delay_ms": 27.8,
    "test_count": 5
  },
  "suc": true
}
```

**结果评估：**

- `avg_rtt_ms < 100ms`：网络连接良好
- `avg_rtt_ms 100-500ms`：网络可用，略有延迟
- `avg_rtt_ms > 500ms`：网络延迟较高，建议检查网络配置
- `status: "success"`：测试通过

---

### 7. 运行您的第一个工作流

现在系统已经正常运行，让我们通过一个完整的案例来创建并执行第一个工作流。

#### 7.1 工作流场景

我们将实现一个简单的液体转移实验：

1. 在容器 1 中加入水
2. 通过传输泵将容器 1 中的水转移到容器 2

#### 7.2 添加所需的设备和物料

在创建工作流之前，我们需要先在"仪器耗材"模块中添加必要的设备和物料。

**所需设备和物料清单：**

- 仪器设备 `work_station` 中的 `workstation` 数量 x1
- 仪器设备 `virtual_device` 中的 `virtual_transfer_pump` 数量 x1
- 物料耗材 `container` 中的 `container` 数量 x2

**操作步骤：**

1. 访问 Web 界面，进入"仪器耗材"模块
2. 在"仪器设备"区域找到并添加上述设备
3. 在"物料耗材"区域找到并添加容器
4. 在workstation中配置protocol_type包含PumpTransferProtocol

![添加Protocol类型](image/add_protocol.png)

![物料列表](image/material.png)

#### 7.3 建立设备和物料的关联关系

当我们添加设备时，仪器耗材模块的物料列表会实时更新。我们需要将设备和物料拖拽到 workstation 中并在画布上将它们连接起来，就像真实的设备操作一样。

**操作步骤：**

1. 将两个 `container` 拖拽到 `workstation` 中
2. 将 `virtual_multiway_valve` 拖拽到 `workstation` 中
3. 将 `virtual_transfer_pump` 拖拽到 `workstation` 中
4. 在画布上连接它们（建立父子关系）

![设备连接](image/links.png)

**连接规则：**

- 所有设备和物料都必须有父节点（除了顶层工作站）
- 连接关系反映真实的物理布局
- 连接后才能在工作流中使用

#### 7.4 创建工作流

1. 进入"工作流"模块
2. 点击"我创建的"
3. 点击"新建工作流"

![新建工作流](image/new.png)

#### 7.5 新增工作流节点

我们可以进入指定工作流，在空白处右键，添加以下节点：

- 选择 **Laboratory → host_node** 中的 `creat_resource`
- 选择 **Laboratory → workstation** 中的 `PumpTransferProtocol`

![创建工作流](image/creatworkfollow.gif)

#### 7.6 配置节点参数

根据案例，工作流包含两个步骤：

1. 使用 `creat_resource` 在容器中创建水
2. 通过泵传输协议将水传输到另一个容器

**配置 creat_resource 节点：**

点击 `creat_resource` 卡片上的编辑按钮，配置参数：

```
class_name: container
device_id: workstation
liquid_input_slot: 0 或 -1 均可
liquid_type: water
liquid_volume: 50（根据需求填写，默认单位 ml，这里举例 50）
parent: workstation
res_id: container
关联设备名称(原 unilabos_device_id): host_node
```

配置完成后点击底部保存按钮。

**配置 PumpTransferProtocol 节点：**

点击 `PumpTransferProtocol` 卡片上的编辑按钮，配置参数：

```
event: transfer_liquid
from_vessel: water
to_vessel: container1
volume: 50（根据需求填写，默认单位 ml，这里举例 50）
关联设备名称(原 unilabos_device_id): workstation
```

配置完成后点击底部保存按钮。

#### 7.7 运行工作流

1. 连接两个节点卡片（creat_resource → PumpTransferProtocol）
2. 点击底部保存按钮
3. 点击运行按钮执行工作流

![连接并运行](image/linksandrun.png)

#### 7.8 运行监控

- 运行状态和消息实时显示在底部控制台
- 如有报错，可点击查看详细信息
- 查看日志文件：`unilabos_data/logs/unilab.log`

#### 7.9 结果验证

工作流完成后，返回"仪器耗材"模块：

1. 点击 `container1` 卡片查看详情
2. 确认其中包含参数指定的水和容量（50ml 的水）

至此，您已经成功运行了第一个工作流。

---

## 第二部分：进阶部署

### 8. 多节点部署（Slave 模式）

详细的组网部署和主从模式配置请参考 [组网部署与主从模式配置](../developer_guide/networking_overview.md)。

#### 8.1 主从模式概述

**适用场景：**

- 设备物理位置分散在不同房间
- 需要独立的故障隔离域
- 逐步扩展实验室规模
- 分布式实验操作

**架构：**

```
主节点（Host）
    ├── 本地设备A
    ├── 本地设备B
    └── ROS2 通信 ←→ 从节点（Slave）
                        ├── 远程设备C
                        └── 远程设备D
```

#### 8.2 准备工作

**主节点准备：**

1. 确保主节点已正常启动
2. 记录主节点的 IP 地址
3. 确保网络互通（同一局域网或 VPN）

**网络要求：**

- 主从节点在同一局域网
- 可以互相 ping 通
- 确保 ROS2 通信端口畅通

**验证网络连通性：**

在配置完成后，使用 ROS2 自带的 demo 节点来验证跨节点通信是否正常：

**在主节点机器上**（激活 unilab 环境后）：

```bash
# 终端1：启动 talker
ros2 run demo_nodes_cpp talker

# 终端2：启动 listener
ros2 run demo_nodes_cpp listener
```

**在从节点机器上**（激活 unilab 环境后）：

```bash
# 终端1：启动 talker
ros2 run demo_nodes_cpp talker

# 终端2：启动 listener
ros2 run demo_nodes_cpp listener
```

**注意**：必须在两台机器上**互相启动** talker 和 listener，否则可能出现只能收不能发的单向通信问题。

**预期结果：**

- 每台机器的 listener 应该能同时接收到本地和远程 talker 发送的消息
- 如果只能看到本地消息，说明网络配置有问题
- 如果两台机器都能互相收发消息，则组网配置正确

**如果验证失败，尝试关闭防火墙：**

为了确保 ROS2 DDS 通信正常，建议直接关闭防火墙，而不是配置特定端口。ROS2 使用动态端口范围，配置特定端口可能导致通信问题。

**Linux：**

```bash
# 关闭防火墙
sudo ufw disable

# 或者临时停止防火墙
sudo systemctl stop ufw
```

**Windows：**

```powershell
# 在 Windows 安全中心关闭防火墙
# 控制面板 -> 系统和安全 -> Windows Defender 防火墙 -> 启用或关闭 Windows Defender 防火墙
```

#### 8.3 启动主节点

```bash
# 在主机（Host）上启动
conda activate unilab
unilab --ak your_ak --sk your_sk -g host.json --upload_registry
```

**主节点职责：**

- 与云端通信
- 管理全局资源
- 协调所有节点

#### 8.4 启动从节点

在另一台机器上：

```bash
# 在从机（Slave）上启动
conda activate unilab
unilab --ak your_ak --sk your_sk -g slave.json --is_slave
```

**从节点职责：**

- 管理本地设备
- 向主节点注册
- 执行分配的任务
- 不直接与云端通信

**重要：** 从节点使用相同的 AK/SK，因为它们属于同一个实验室。

#### 8.5 判断连接成功

**方法 1：检查 ROS2 节点列表**

在任一节点上运行：

```bash
ros2 node list
```

应该看到来自两个节点的设备：

```
/devices/host_node
/devices/workstation
/devices/liquid_handler_1  # 从节点的设备
```

**方法 2：查看主节点设备列表**

在主节点的 Web 界面：

1. 进入"仪器耗材"模块
2. 查看设备列表
3. 从节点的设备应显示为"在线"状态
4. 设备卡片上会显示来源机器名称

**方法 3：查看日志**

主节点日志会显示：

```
[Host Node] Node info update request received
[Host Node] Slave node registered: liquid_handler_1 from Machine_B
```

从节点日志会显示：

```
Slave node info updated.
Resource tree synchronized with host.
```

#### 8.6 注册机制说明

从节点启动时会自动向主节点注册：

**注册流程（代码逻辑）：**

1. **从节点启动**（`base_device_node.py` 的 `register_device` 方法）：

   ```python
   # 检测到非 Host 模式
   if not BasicConfig.is_host_mode:
       # 创建服务客户端
       sclient = self.create_client(SerialCommand, "/node_info_update")
       # 发送注册信息
       self.send_slave_node_info(sclient)
   ```

2. **发送注册信息**（`main_slave_run.py`）：

   ```python
   request.command = json.dumps({
       "machine_name": BasicConfig.machine_name,
       "type": "slave",
       "devices_config": devices_config.dump(),
       "registry_config": registry_config,
   })
   ```

3. **主节点接收**（`host_node.py` 的 `_node_info_update_callback` 方法）：
   ```python
   def _node_info_update_callback(self, request, response):
       info = json.loads(request.command)
       machine_name = info["machine_name"]
       edge_device_id = info["edge_device_id"]
       # 记录设备来源机器
       self.device_machine_names[edge_device_id] = machine_name
   ```

**关键点：**

- 注册是自动的，无需手动配置
- 主节点会记录每个设备来自哪台机器
- 从节点需要等待主节点的服务可用

#### 8.7 常见问题

**问题 1：从节点启动卡住**

```
Waiting for host service...
```

**解决方案：**

- 检查主节点是否已启动
- 验证网络连通性
- 使用 `--slave_no_host` 参数跳过等待（仅用于测试）

**问题 2：节点看不到彼此**

**解决方案：**

- 确保在同一网络
- 关闭防火墙
- 设置相同的 `ROS_DOMAIN_ID`（可选）

**问题 3：注册失败**

**解决方案：**

- 检查 AK/SK 是否正确
- 查看主节点日志
- 重启从节点

---

## 第三部分：自定义开发

### 9. 创建您的第一个自定义设备

详细的设备驱动编写指南请参考 [添加设备驱动](../developer_guide/add_device.md)。

#### 9.1 开发环境准备

**推荐使用 `unilabos-env` + `pip install -e .` + `uv pip install`** 进行设备开发：

```bash
# 1. 创建环境并安装 unilabos-env（ROS2 + conda 依赖 + uv）
mamba create -n unilab python=3.11.14
conda activate unilab
mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge

# 2. 克隆代码
git clone https://github.com/deepmodeling/Uni-Lab-OS.git
cd Uni-Lab-OS

# 3. 以可编辑模式安装（推荐使用脚本，自动检测中文环境）
python scripts/dev_install.py

# 或手动安装：
pip install -e .
uv pip install -r unilabos/utils/requirements.txt
```

**为什么使用这种方式？**
- `unilabos-env` 提供 ROS2 核心组件和 uv（通过 conda 安装，避免编译）
- `unilabos/utils/requirements.txt` 包含所有运行时需要的 pip 依赖
- `dev_install.py` 自动检测中文环境，中文系统自动使用清华镜像
- 使用 `uv` 替代 `pip`，安装速度更快
- 可编辑模式：代码修改**立即生效**，无需重新安装

**如果安装失败或速度太慢**，可以手动执行（使用清华镜像）：

```bash
pip install -e . -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
uv pip install -r unilabos/utils/requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
```

#### 9.2 为什么需要自定义设备？

Uni-Lab-OS 内置了常见设备，但您的实验室可能有特殊设备需要集成：

- 自研仪器
- 定制化改造的商业设备
- 特殊的实验流程
- 第三方设备集成

#### 9.3 创建 Python 包

为了方便开发和管理，建议为您的实验室创建独立的 Python 包。

**包结构：**

```
my_lab_devices/
├── setup.py
├── README.md
├── my_lab_devices/
│   ├── __init__.py
│   ├── devices/
│   │   ├── __init__.py
│   │   ├── my_pump.py
│   │   └── my_sensor.py
│   └── registry/
│       ├── devices/
│       │   ├── my_pump.yaml
│       │   └── my_sensor.yaml
│       └── resources/
│           └── my_container.yaml
```

**创建步骤：**

```bash
# 1. 创建包目录
mkdir -p my_lab_devices/my_lab_devices/devices
mkdir -p my_lab_devices/my_lab_devices/registry/devices
mkdir -p my_lab_devices/my_lab_devices/registry/resources

# 2. 创建 __init__.py
touch my_lab_devices/my_lab_devices/__init__.py
touch my_lab_devices/my_lab_devices/devices/__init__.py
```

#### 9.4 创建 setup.py

```python
# my_lab_devices/setup.py

from setuptools import setup, find_packages

setup(
    name="my_lab_devices",
    version="0.1.0",
    description="My Laboratory Custom Devices",
    author="Your Name",
    author_email="your.email@example.com",
    packages=find_packages(),
    install_requires=[
        "unilabos",  # 依赖 Uni-Lab-OS
        # 添加其他依赖
        # "pyserial",
        # "requests",
    ],
    python_requires=">=3.11",
    # 包含注册表文件
    package_data={
        "my_lab_devices": [
            "registry/devices/*.yaml",
            "registry/resources/*.yaml",
        ],
    },
)
```

#### 9.5 开发安装

使用 `-e` 参数进行可编辑安装，这样代码修改后立即生效：

```bash
cd my_lab_devices
pip install -e . -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
```

**优势：**

- 代码修改实时生效，无需重新安装
- 方便调试和测试
- 支持版本控制（git）

#### 9.6 编写设备驱动

创建设备驱动文件：

```python
# my_lab_devices/my_lab_devices/devices/my_pump.py

from typing import Dict, Any
from unilabos.registry.placeholder_type import ResourceSlot

class MyPump:
    """自定义注射泵驱动

    支持功能：
    - 注射液体
    - 抽取液体
    - 速率控制
    """

    def __init__(self, config: Dict[str, Any]):
        """初始化设备

        Args:
            config: 配置字典，包含端口、波特率等
        """
        self.port = config.get('port', 'COM1')
        self.baudrate = config.get('baudrate', 9600)
        self._status = "idle"
        self._current_volume = 0.0

        # 初始化串口连接
        # self.serial = serial.Serial(self.port, self.baudrate)
        print(f"MyPump initialized on {self.port}")

    @property
    def status(self) -> str:
        """设备状态"""
        return self._status

    @property
    def current_volume(self) -> float:
        """当前注射器内液体体积（ml）"""
        return self._current_volume

    def infuse(
        self,
        target: ResourceSlot,
        volume: float,
        rate: float = 1.0
    ) -> Dict[str, Any]:
        """注射液体到目标容器

        Args:
            target: 目标容器（前端显示资源选择器）
            volume: 注射体积（ml）
            rate: 注射速率（ml/min）

        Returns:
            执行结果
        """
        self._status = "infusing"

        print(f"Infusing {volume}ml to {target.name} at {rate}ml/min")

        # 发送命令到硬件
        # self.serial.write(f"INF {volume} {rate}\r\n".encode())

        # 模拟执行
        import time
        time.sleep(volume / rate * 60)  # 模拟注射时间

        self._current_volume -= volume
        self._status = "idle"

        return {
            "success": True,
            "volume_infused": volume,
            "target": target.id,
            "message": f"Successfully infused {volume}ml"
        }

    def withdraw(
        self,
        source: ResourceSlot,
        volume: float,
        rate: float = 1.0
    ) -> Dict[str, Any]:
        """从源容器抽取液体

        Args:
            source: 源容器（前端显示资源选择器）
            volume: 抽取体积（ml）
            rate: 抽取速率（ml/min）

        Returns:
            执行结果
        """
        self._status = "withdrawing"

        print(f"Withdrawing {volume}ml from {source.name} at {rate}ml/min")

        # 发送命令到硬件
        # self.serial.write(f"WDR {volume} {rate}\r\n".encode())

        # 模拟执行
        import time
        time.sleep(volume / rate * 60)

        self._current_volume += volume
        self._status = "idle"

        return {
            "success": True,
            "volume_withdrawn": volume,
            "source": source.id,
            "message": f"Successfully withdrawn {volume}ml"
        }

    def set_rate(self, rate: float) -> Dict[str, Any]:
        """设置流速

        Args:
            rate: 流速（ml/min）
        """
        print(f"Setting rate to {rate}ml/min")
        # self.serial.write(f"RAT {rate}\r\n".encode())

        return {
            "success": True,
            "rate": rate,
            "message": f"Rate set to {rate}ml/min"
        }
```

**关键点：**

- **类型注解**：使用 `ResourceSlot` 等特殊类型
- **@property**：状态属性会自动广播
- **返回 Dict**：所有动作方法返回字典类型
- **文档字符串**：详细说明参数和功能

#### 9.7 测试设备驱动

创建简单的测试脚本：

```python
# test_my_pump.py

from my_lab_devices.devices.my_pump import MyPump

# 创建设备实例
config = {
    "port": "COM1",
    "baudrate": 9600
}
pump = MyPump(config)

# 测试状态属性
print(f"Status: {pump.status}")
print(f"Current volume: {pump.current_volume}ml")

# 模拟注射（不使用 ResourceSlot）
class MockResource:
    id = "test_container"
    name = "测试容器"

result = pump.infuse(MockResource(), volume=5.0, rate=2.0)
print(f"Result: {result}")
```

运行测试：

```bash
python test_my_pump.py
```

---

### 10. 生成和完善设备注册表

注册表（Registry）是 Uni-Lab-OS 的核心配置文件，定义了设备的动作、状态、参数等信息。系统提供了自动生成功能，帮助您快速完成基础配置。

> 完整的注册表编写、字段说明、高级配置等内容，请参考 [添加设备注册表](../developer_guide/add_registry.md)。

#### 10.1 注册表目录结构

Uni-Lab-OS 使用三类注册表：

```
registry/
├── devices/          # 设备注册表（仪器设备，如泵、加热器、机械臂等）
├── device_comms/     # 通信驱动注册表（如 Modbus、OPC UA）
└── resources/        # 物料注册表（耗材、容器，如孔板、试管、试剂等）
```

#### 10.2 生成注册表（两种方式）

**方式 1：使用 Web 界面注册表编辑器（推荐）**

1. 访问 Web 界面，进入"注册表编辑器"
2. 上传您的 Python 设备驱动文件
3. 点击"分析文件"，系统自动生成注册表
4. 填写描述、图标等元数据
5. 下载生成的 YAML 文件到您的注册表目录

**方式 2：使用 `--complete_registry` 参数（命令行）**

```bash
unilab --ak your_ak --sk your_sk -g graph.json \
       --registry_path ./my_lab_devices/registry \
       --complete_registry
```

系统会自动：

- 扫描 Python 设备类
- 分析方法签名和类型注解
- 识别 `ResourceSlot`、`DeviceSlot` 等特殊类型
- 生成完整的 YAML 注册表文件

#### 10.3 生成的注册表示例

自动生成的注册表大致如下（已简化）：

```yaml
my_pump:
  class:
    module: my_lab_devices.devices.my_pump:MyPump
    type: python

    status_types: # 自动提取的状态
      status: str
      current_volume: float

    action_value_mappings: # 自动生成的动作
      infuse:
        type: UniLabJsonCommand
        goal:
          target: target
          volume: volume
        placeholder_keys:
          target: unilabos_resources # 自动识别 ResourceSlot
        result:
          success: success

  description: '自定义注射泵驱动'
  version: '1.0.0'
```

系统会自动识别特殊类型并生成前端选择器：

| Python 类型          | 前端效果       |
| -------------------- | -------------- |
| `ResourceSlot`       | 资源单选下拉框 |
| `List[ResourceSlot]` | 资源多选下拉框 |
| `DeviceSlot`         | 设备单选下拉框 |
| `List[DeviceSlot]`   | 设备多选下拉框 |

#### 10.4 使用自定义注册表

启动时指定注册表路径：

```bash
unilab --ak your_ak --sk your_sk -g graph.json \
       --registry_path ./my_lab_devices/registry \
       --upload_registry
```

**支持多个注册表路径**（按顺序查找）：

```bash
unilab --ak your_ak --sk your_sk -g graph.json \
       --registry_path ./my_lab_devices/registry \
       --registry_path ./another_lab/registry
```

#### 10.5 验证注册表

**验证方法：**

1. **查看启动日志**：确认设备被正确加载
2. **访问 Web 界面**："仪器设备"模块中应能看到您的自定义设备
3. **测试动作调用**：在工作流中测试设备动作是否正常

#### 10.6 手动完善注册表（可选）

自动生成的注册表已经可用，但您可以手动添加以下内容来提升用户体验：

- **description**：设备详细描述
- **icon**：设备图标（支持 Base64 或 URL）
- **category**：设备分类标签（用于前端分组显示）
- **handles**：设备连接点定义（用于可视化连接）

**示例：**

```yaml
my_pump:
  description: '高精度注射泵，支持 0.001-100 ml/min 流速范围'
  icon: 'pump_icon.webp'
  category:
    - pump
    - liquid_handling
  # ... 其他配置
```

> 关于注册表的高级配置、字段详解、最佳实践等，请参考 [添加设备注册表](../developer_guide/add_registry.md)。

---

### 11. 创建复杂工作站

详细的工作站架构请参考 [工作站架构](../developer_guide/examples/workstation_architecture.md)。

#### 11.1 什么是复杂工作站？

复杂工作站是多个设备和物料的集合，协同完成特定实验流程。

**示例场景：**

- **液体处理工作站**：移液器 + 孔板 + 试剂槽 + 枪头架
- **有机合成工作站**：反应釜 + 注射泵 + 加热搅拌器 + 冷凝器
- **分析工作站**：色谱仪 + 进样器 + 流动相泵

#### 11.2 设计工作站

**设计原则：**

1. **模块化**：每个设备独立，可单独控制
2. **层次化**：使用父子关系组织设备
3. **标准化**：使用统一的接口和命名

**工作站结构：**

```
Workstation（工作站根节点）
├── Deck（甲板/平台）
│   ├── PlateCarrier_1（板位）
│   │   └── Plate_96（96孔板）
│   ├── PlateCarrier_2
│   │   └── Plate_384（384孔板）
│   └── TipRack（枪头架）
├── Pipette_8（8通道移液器）
└── WashStation（清洗站）
```

#### 11.3 组合多个设备

在组态图中：

1. 创建工作站根节点（通常是 `workstation`）
2. 添加子设备
3. 建立连接关系
4. 配置每个设备的参数

**JSON 组态示例：**

```json
{
  "nodes": [
    {
      "id": "liquid_handler_ws",
      "name": "液体处理工作站",
      "type": "device",
      "class": "workstation",
      "config": {}
    },
    {
      "id": "pipette_8ch",
      "name": "8通道移液器",
      "type": "device",
      "class": "pipette_8_channel",
      "config": {
        "volume_range": [0.5, 300]
      }
    },
    {
      "id": "plate_carrier_1",
      "name": "板位1",
      "type": "resource",
      "class": "plate_carrier",
      "config": {}
    }
  ],
  "links": [
    {
      "source": "liquid_handler_ws",
      "target": "pipette_8ch",
      "type": "contains"
    },
    {
      "source": "liquid_handler_ws",
      "target": "plate_carrier_1",
      "type": "contains"
    }
  ]
}
```

#### 11.4 设计工作流程

**复杂工作站的工作流特点：**

- 多步骤协同
- 设备间依赖
- 状态管理
- 错误恢复

**示例：96 孔板液体分配流程**

```
1. 准备阶段
   ├── 检查枪头架
   ├── 检查孔板位置
   └── 检查试剂体积

2. 执行阶段
   ├── 装载枪头
   ├── 从试剂槽吸取
   ├── 移动到目标孔板
   ├── 分配液体
   └── 退出枪头

3. 清理阶段
   ├── 清洗移液器
   └── 复位到初始位置
```

**在 Uni-Lab 中实现：**

1. 创建工作流
2. 添加各步骤节点
3. 配置每个节点的设备和参数
4. 连接节点形成流程
5. 添加错误处理分支

#### 11.5 通信共享机制

在复杂工作站中，多个设备可能需要共享同一个通信端口（如串口、PLC），但大多数通信端口只能被一个实例占用。通信共享机制解决了这个问题，允许灵活控制连接到同一通信端口的多个设备。

##### 11.5.1 应用场景

**典型场景：**

- **多泵阀共享 PLC 串口**：所有泵阀通过同一个 PLC 控制，泵阀数量可灵活增减
- **多传感器共享 Modbus**：多个传感器通过同一个 Modbus 设备读取数据
- **多设备共享 TCP/IP**：多台设备共享一个 TCP/IP 通信接口

**解决思路：**

创建一个**通信设备作为代理**，所有需要使用该端口的设备通过代理进行通信，而不是直接访问端口。

##### 11.5.2 配置步骤

**步骤 1：定义 workstation 并指定 protocol_type**

在组态图中，workstation 的 `config` 需要添加 `protocol_type`（协议类型列表，可为空）：

```json
{
  "id": "simple_station",
  "name": "简单工作站",
  "type": "device",
  "class": "workstation",
  "parent": null,
  "config": {
    "protocol_type": ["PumpTransferProtocol", "CleanProtocol"]
  },
  "children": ["serial_pump", "pump_1", "pump_2", "pump_3"]
}
```

> **说明**：`children` 列表中，**通信设备必须排在最前面**（如 `"serial_pump"`），确保它先于其他设备初始化。

**步骤 2：创建通信设备（作为代理）**

在 workstation 的子设备中，**首先定义通信设备**（必须在其他设备之前初始化，因此放到启动 json 靠前的位置）：

```json
{
  "id": "serial_pump",
  "type": "device",
  "class": "serial",
  "parent": "simple_station",
  "config": {
    "port": "COM7",
    "baudrate": 9600
  }
}
```

**步骤 3：配置使用该通信的设备**

其他设备的 `config` 中指向通信设备（字段名根据设备注册表定义，如 `port` 或 `io_device_port`，他们在驱动中）：

```json
{
  "id": "pump_1",
  "type": "device",
  "class": "syringe_pump_with_valve.runze.SY03B-T06",
  "parent": "simple_station",
  "config": {
    "port": "serial_pump", // 直接使用通信设备的设备名，最终能够通过__init__实现self.hardware_interface变量设定为serial_pump的字符串
    "address": "1",
    "max_volume": 25.0
  }
}
```

> 配置字段的值直接是**通信设备的设备名**（如 `"serial_pump"`），不是完整路径。系统会自动进行变量替换。

**步骤 4：建立通信连接关系**

在组态图的 `links` 中，建立 `communication` 类型的连接：

```json
{
  "links": [
    {
      "source": "pump_1",
      "target": "serial_pump",
      "type": "communication",
      "port": {
        "serial_pump": "1" // 通信端口号或地址
      }
    },
    {
      "source": "pump_2",
      "target": "serial_pump",
      "type": "communication",
      "port": {
        "serial_pump": "2"
      }
    }
  ]
}
```

> **说明**：`port` 字段中的值是通信设备上的端口号或地址，具体含义取决于通信协议。

##### 11.5.3 注册表配置

**通信设备的注册表**（例如`device_comms/modbus_ioboard.yaml`）：

```yaml
io_snrd:
  class:
    module: unilabos.device_comms.SRND_16_IO:SRND_16_IO
    type: python

    # hardware_interface.name 对象是实际的通信实例（如 ModbusClient()）
    # 通过 Python 的 __init__ 方法初始化
    hardware_interface:
      name: modbus_client
      read: read_io_coil
      write: write_io_coil

  description: 'IO Board with 16 IOs via Modbus'
```

**使用通信的设备注册表**（`devices/pump_and_valve.yaml`）：

```yaml
syringe_pump_with_valve.runze.SY03B-T06:
  class:
    module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
    type: python

    # hardware_interface.name 初始化时是通信设备的设备名（字符串）
    # 通过启动 JSON 的 config 传入（如 "port": "serial_pump"）
    # 系统会自动将字符串替换为实际的通信实例
    hardware_interface:
      name: hardware_interface
      read: send_command
      write: send_command

    action_value_mappings:
      # 设备的动作定义

solenoid_valve:
  class:
    module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
    type: python

    # 使用 io_device_port 字段接收通信设备名
    hardware_interface:
      name: io_device_port # 例如当前电磁阀修改了name，从默认的hardware_interface改成了io_device_port，那么启动json中就应该对__init__中的io_device_port进行赋值
      read: read_io_coil
      write: write_io_coil
```

**关键机制：**

| 设备类型           | hardware_interface.name | 初始化方式                                    | 运行时对象                          |
| ------------------ | ----------------------- | --------------------------------------------- | ----------------------------------- |
| **通信设备**       | `modbus_client` 等      | Python `__init__` 中创建实例                  | `ModbusClient()`、`serial.Serial()` |
| **使用通信的设备** | `hardware_interface` 等 | config 中传入设备名字符串（如 `"io_device"`） | **系统自动替换**为实际通信实例      |

**自动替换流程：**

1. 设备初始化时，`self.hardware_interface = "io_device"`（字符串）
2. workstation 检测到 `config` 中有通信设备名
3. 系统自动将 `self.hardware_interface` 替换为 `io_device` 的实际通信实例
4. 设备可以直接调用 `self.hardware_interface.read_io_coil()` 等方法

**完整示例：Modbus IO 板控制多个电磁阀**

```json
{
  "nodes": [
    {
      "id": "WorkStationTestMinimumSystem",
      "name": "工作站节点",
      "children": [
        "io_device",
        "Product1_Solenoid_Valve",
        "Water_Solenoid_Valve"
      ],
      "parent": null,
      "type": "device",
      "class": "workstation",
      "config": {
        "protocol_type": []
      }
    },
    {
      "id": "io_device",
      "class": "io_snrd", // 通信设备类型
      "name": "io_device",
      "config": {
        "address": "192.168.1.7:23", // Modbus TCP 地址
        "slave_id": "1"
      },
      "parent": "WorkStationTestMinimumSystem",
      "type": "device"
    },
    {
      "id": "Product1_Solenoid_Valve",
      "class": "solenoid_valve",
      "name": "Product1_Solenoid_Valve",
      "config": {
        "io_device_port": "io_device" // 通信设备的设备名（字符串）
      },
      "parent": "WorkStationTestMinimumSystem",
      "type": "device"
    },
    {
      "id": "Water_Solenoid_Valve",
      "class": "solenoid_valve",
      "name": "Water_Solenoid_Valve",
      "config": {
        "io_device_port": "io_device" // 同样使用这个通信设备
      },
      "parent": "WorkStationTestMinimumSystem",
      "type": "device"
    }
  ],
  "links": []
}
```

**工作原理：**

1. `io_device` 初始化时创建 `ModbusClient("192.168.1.7:23")`
2. 两个电磁阀的 `self.io_device_port` 初始化为字符串 `"io_device"`
3. workstation 自动将 `self.io_device_port` 替换为实际的 `ModbusClient` 实例
4. 电磁阀可以通过 `self.io_device_port.read_io_coil(15)` 或 `write_io_coil(14)` 操作

##### 11.5.4 验证通信共享

**启动成功标志：**

系统启动时，在日志中查看以下提示信息：

```
通信代理：为子设备 pump_1 配置通信代理 serial_pump
通信代理：为子设备 pump_2 配置通信代理 serial_pump
通信代理：为子设备 pump_3 配置通信代理 serial_pump
```

看到此提示说明通信共享配置成功。

**测试步骤：**

1. 启动系统，查看日志确认通信代理配置成功
2. 在 Web 界面中，分别控制 `pump_1`、`pump_2`、`pump_3`
3. 确认所有泵都能正常响应（通过同一个串口）
4. 查看通信设备的状态，确认消息正常传递

---

### 12. 撰写物料定义

详细的物料编写指南请参考：

- [物料构建指南](../developer_guide/examples/materials_construction_guide.md)
- [物料教程](../developer_guide/examples/materials_tutorial.md)

#### 12.1 物料（Resource）概述

物料是实验中使用的消耗品和容器：

- **容器**：孔板、试管、烧杯、反应瓶
- **耗材**：枪头、移液管、过滤器
- **试剂**：溶剂、试剂、样品

#### 12.2 创建自定义物料类型

**场景：** 创建实验室特殊规格的孔板

```python
# my_lab_devices/my_lab_devices/resources/custom_plate.py

from pylabrobot.resources import Plate, Well
from typing import List

class CustomPlate48(Plate):
    """自定义 48 孔板

    规格：
    - 6行 x 8列
    - 孔体积：2ml
    - 孔间距：18mm
    """

    def __init__(self, name: str):
        super().__init__(
            name=name,
            size_x=127.76,  # mm
            size_y=85.48,   # mm
            size_z=14.35,   # mm
            model="custom_48_wellplate_2000ul"
        )

        # 定义孔位
        self._create_wells()

    def _create_wells(self):
        """创建 48 个孔位"""
        rows = "ABCDEF"
        cols = range(1, 9)

        well_size_x = 16.0  # mm
        well_size_y = 16.0
        well_depth = 12.0
        spacing_x = 18.0
        spacing_y = 18.0

        for row_idx, row in enumerate(rows):
            for col in cols:
                well_name = f"{row}{col}"
                x = (col - 1) * spacing_x + 10  # 10mm 边距
                y = row_idx * spacing_y + 10

                well = Well(
                    name=well_name,
                    size_x=well_size_x,
                    size_y=well_size_y,
                    size_z=well_depth,
                    max_volume=2000,  # μL
                )
                well.location = (x, y, 0)
                self.children.append(well)
```

#### 12.3 物料注册表

创建物料注册表：

```yaml
# my_lab_devices/my_lab_devices/registry/resources/custom_plate.yaml

custom_48_wellplate_2000ul:
  class:
    module: my_lab_devices.resources.custom_plate:CustomPlate48
    type: python

  description: '自定义 48 孔板，2ml 孔体积'
  version: '1.0.0'
  category:
    - plate
    - container

  # 物料属性
  properties:
    rows: 6
    columns: 8
    well_count: 48
    well_volume: 2000 # μL
    well_shape: 'round'
    material: 'polystyrene'

  # 尺寸信息
  dimensions:
    size_x: 127.76
    size_y: 85.48
    size_z: 14.35
    unit: 'mm'

  # 兼容性
  compatible_with:
    - 'plate_carrier'
    - 'thermal_cycler'

  icon: ''
```

#### 12.4 物料属性定义

**常见属性：**

```yaml
properties:
  # 容量相关
  max_volume: 2000 # μL
  min_volume: 10 # μL
  dead_volume: 5 # μL（无法吸取的残留体积）

  # 几何形状
  shape: 'round' # round, square, v_bottom
  diameter: 16.0 # mm
  depth: 12.0 # mm

  # 材质
  material: 'polystyrene' # 聚苯乙烯
  transparency: true # 透明
  sterile: false # 无菌

  # 温度范围
  min_temperature: -80 # °C
  max_temperature: 121 # °C

  # 化学兼容性
  chemical_compatibility:
    - 'water'
    - 'ethanol'
    - 'dmso'

  # 条形码
  barcode_support: true
  barcode_type: '1D'
```

#### 12.5 在系统中使用自定义物料

1. **注册物料**：

```bash
unilab --ak your_ak --sk your_sk -g graph.json \
       --registry_path ./my_lab_devices/my_lab_devices/registry \
       --upload_registry
```

2. **在组态图中添加**：

在 Web 界面的"物料耗材"模块中，找到 `custom_48_wellplate_2000ul`，添加到实验室。

3. **在工作流中使用**：

工作流节点的参数中，涉及容器选择的地方会显示您的自定义物料。

---

## 第四部分：最佳实践总结

### 13. 开发流程建议

```
需求分析 → 环境搭建 → 原型验证 → 迭代开发 → 测试部署 → 生产运行
```

**详细步骤：**

1. **需求分析**：

   - 明确实验流程
   - 列出所需设备和物料
   - 设计工作流程图

2. **环境搭建**：

   - 安装 Uni-Lab-OS
   - 创建实验室账号
   - 准备开发工具（IDE、Git）

3. **原型验证**：

   - 使用虚拟设备测试流程
   - 验证工作流逻辑
   - 调整参数

4. **迭代开发**：

   - 实现自定义设备驱动（同时撰写单点函数测试）
   - 编写注册表
   - 单元测试
   - 集成测试

5. **测试部署**：

   - 连接真实硬件
   - 空跑测试
   - 小规模试验

6. **生产运行**：
   - 全量部署
   - 监控运行状态
   - 持续优化

---

### 14. 进一步学习资源

#### 14.1 官方文档

- **安装指南**：[installation.md](installation.md)
- **启动指南**：[launch.md](launch.md)
- **配置指南**：[../advanced_usage/configuration.md](../advanced_usage/configuration.md)

#### 14.2 开发者文档

- **组网部署与主从模式**：[../developer_guide/networking_overview.md](../developer_guide/networking_overview.md)
- **添加设备驱动**：[../developer_guide/add_device.md](../developer_guide/add_device.md)
- **老设备驱动开发**：[../developer_guide/add_old_device.md](../developer_guide/add_old_device.md)
- **添加动作指令**：[../developer_guide/add_action.md](../developer_guide/add_action.md)
- **YAML 注册表编写**：[../developer_guide/add_yaml.md](../developer_guide/add_yaml.md)
- **添加设备注册表**：[../developer_guide/add_registry.md](../developer_guide/add_registry.md)
- **工作站架构**：[../developer_guide/examples/workstation_architecture.md](../developer_guide/examples/workstation_architecture.md)
- **物料构建指南**：[../developer_guide/examples/materials_construction_guide.md](../developer_guide/examples/materials_construction_guide.md)

#### 14.3 进阶主题

- **工作目录详解**：[../advanced_usage/working_directory.md](../advanced_usage/working_directory.md)
<!-- 以下文档待补充：
- **远程资源管理**：[../deployment/03_remote_resources.md](../deployment/03_remote_resources.md)
- **云端集成**：[../deployment/04_cloud_integration.md](../deployment/04_cloud_integration.md)
- **生产环境部署**：[../deployment/05_production_deployment.md](../deployment/05_production_deployment.md)
  -->

#### 14.4 外部资源

- **ROS 2 文档**：[https://docs.ros.org/en/humble/](https://docs.ros.org/en/humble/)
- **PyLabRobot**：[https://github.com/PyLabRobot/pylabrobot](https://github.com/PyLabRobot/pylabrobot)
- **Python 类型注解**：[https://docs.python.org/3/library/typing.html](https://docs.python.org/3/library/typing.html)
- **YAML 语法**：[https://yaml.org/](https://yaml.org/)

#### 14.5 社区支持

- **GitHub Issues**：[https://github.com/deepmodeling/Uni-Lab-OS/issues](https://github.com/deepmodeling/Uni-Lab-OS/issues)
- **官方网站**：[https://uni-lab.bohrium.com](https://uni-lab.bohrium.com)

---

## 结语

通过本指南，您应该已经掌握了：

- 安装和配置 Uni-Lab-OS 环境
- 创建和管理实验室
- 启动系统并运行工作流
- 部署多节点分布式系统
- 开发自定义设备驱动
- 创建和注册物料定义
- 构建复杂工作站

**下一步建议：**

1. 从简单的工作流开始实践，逐步尝试更复杂的场景
2. 在 GitHub 上提问和分享经验
3. 关注文档更新和新功能发布
4. 为 Uni-Lab-OS 社区贡献您的设备驱动和最佳实践

---

_本指南最后更新：2025-11_
_Uni-Lab-OS 版本：最新稳定版_
