mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-24 15:49:16 +00:00
Compare commits
20 Commits
dev
...
d85ff540c4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d85ff540c4 | ||
|
|
5f45a0b81b | ||
|
|
6bf9a319c7 | ||
|
|
74f0d5ee65 | ||
|
|
2596d48a2f | ||
|
|
2ac1a3242a | ||
|
|
5d208c832b | ||
|
|
786498904d | ||
|
|
a9ea9f425d | ||
|
|
b3bc951cae | ||
|
|
01df4f1115 | ||
|
|
e1074f06d2 | ||
|
|
0dc273f366 | ||
|
|
2e5fac26b3 | ||
|
|
5c2da9b793 | ||
|
|
45efbfcd12 | ||
|
|
8da6fdfd0b | ||
|
|
29ea9909a5 | ||
|
|
ee6307a568 | ||
|
|
8a0116c852 |
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
package:
|
package:
|
||||||
name: unilabos
|
name: unilabos
|
||||||
version: 0.10.19
|
version: 0.10.18
|
||||||
|
|
||||||
source:
|
source:
|
||||||
path: ../../unilabos
|
path: ../../unilabos
|
||||||
@@ -54,7 +54,7 @@ requirements:
|
|||||||
- pymodbus
|
- pymodbus
|
||||||
- matplotlib
|
- matplotlib
|
||||||
- pylibftdi
|
- pylibftdi
|
||||||
- uni-lab::unilabos-env ==0.10.19
|
- uni-lab::unilabos-env ==0.10.18
|
||||||
|
|
||||||
about:
|
about:
|
||||||
repository: https://github.com/deepmodeling/Uni-Lab-OS
|
repository: https://github.com/deepmodeling/Uni-Lab-OS
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package:
|
package:
|
||||||
name: unilabos-env
|
name: unilabos-env
|
||||||
version: 0.10.19
|
version: 0.10.18
|
||||||
|
|
||||||
build:
|
build:
|
||||||
noarch: generic
|
noarch: generic
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
package:
|
package:
|
||||||
name: unilabos-full
|
name: unilabos-full
|
||||||
version: 0.10.19
|
version: 0.10.18
|
||||||
|
|
||||||
build:
|
build:
|
||||||
noarch: generic
|
noarch: generic
|
||||||
@@ -11,7 +11,7 @@ build:
|
|||||||
requirements:
|
requirements:
|
||||||
run:
|
run:
|
||||||
# Base unilabos package (includes unilabos-env)
|
# Base unilabos package (includes unilabos-env)
|
||||||
- uni-lab::unilabos ==0.10.19
|
- uni-lab::unilabos ==0.10.18
|
||||||
# Documentation tools
|
# Documentation tools
|
||||||
- sphinx
|
- sphinx
|
||||||
- sphinx_rtd_theme
|
- sphinx_rtd_theme
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
---
|
|
||||||
name: add-device
|
|
||||||
description: Guide for adding new devices to Uni-Lab-OS (接入新设备). Uses @device decorator + AST auto-scanning instead of manual YAML. Walks through device category, communication protocol, driver creation with decorators, and graph file setup. Use when the user wants to add/integrate a new device, create a device driver, write a device class, or mentions 接入设备/添加设备/设备驱动/物模型.
|
|
||||||
---
|
|
||||||
|
|
||||||
# 添加新设备到 Uni-Lab-OS
|
|
||||||
|
|
||||||
**第一步:** 使用 Read 工具读取 `docs/ai_guides/add_device.md`,获取完整的设备接入指南。
|
|
||||||
|
|
||||||
该指南包含设备类别(物模型)列表、通信协议模板、常见错误检查清单等。搜索 `unilabos/devices/` 获取已有设备的实现参考。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 装饰器参考
|
|
||||||
|
|
||||||
### @device — 设备类装饰器
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import device
|
|
||||||
|
|
||||||
# 单设备
|
|
||||||
@device(
|
|
||||||
id="my_device.vendor", # 注册表唯一标识(必填)
|
|
||||||
category=["temperature"], # 分类标签列表(必填)
|
|
||||||
description="设备描述", # 设备描述
|
|
||||||
display_name="显示名称", # UI 显示名称(默认用 id)
|
|
||||||
icon="DeviceIcon.webp", # 图标文件名
|
|
||||||
version="1.0.0", # 版本号
|
|
||||||
device_type="python", # "python" 或 "ros2"
|
|
||||||
handles=[...], # 端口列表(InputHandle / OutputHandle)
|
|
||||||
model={...}, # 3D 模型配置
|
|
||||||
hardware_interface=HardwareInterface(...), # 硬件通信接口
|
|
||||||
)
|
|
||||||
|
|
||||||
# 多设备(同一个类注册多个设备 ID,各自有不同的 handles 等配置)
|
|
||||||
@device(
|
|
||||||
ids=["pump.vendor.model_A", "pump.vendor.model_B"],
|
|
||||||
id_meta={
|
|
||||||
"pump.vendor.model_A": {"handles": [...], "description": "型号 A"},
|
|
||||||
"pump.vendor.model_B": {"handles": [...], "description": "型号 B"},
|
|
||||||
},
|
|
||||||
category=["pump_and_valve"],
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### @action — 动作方法装饰器
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import action
|
|
||||||
|
|
||||||
@action # 无参:注册为 UniLabJsonCommand 动作
|
|
||||||
@action() # 同上
|
|
||||||
@action(description="执行操作") # 带描述
|
|
||||||
@action(
|
|
||||||
action_type=HeatChill, # 指定 ROS Action 消息类型
|
|
||||||
goal={"temperature": "temp"}, # Goal 字段映射
|
|
||||||
feedback={}, # Feedback 字段映射
|
|
||||||
result={}, # Result 字段映射
|
|
||||||
handles=[...], # 动作级别端口
|
|
||||||
goal_default={"temp": 25.0}, # Goal 默认值
|
|
||||||
placeholder_keys={...}, # 参数占位符
|
|
||||||
always_free=True, # 不受排队限制
|
|
||||||
auto_prefix=True, # 强制使用 auto- 前缀
|
|
||||||
parent=True, # 从父类 MRO 获取参数签名
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**自动识别规则:**
|
|
||||||
- 带 `@action` 的公开方法 → 注册为动作(方法名即动作名)
|
|
||||||
- **不带 `@action` 的公开方法** → 自动注册为 `auto-{方法名}` 动作
|
|
||||||
- `_` 开头的方法 → 不扫描
|
|
||||||
- `@not_action` 标记的方法 → 排除
|
|
||||||
|
|
||||||
### @topic_config — 状态属性配置
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import topic_config
|
|
||||||
|
|
||||||
@property
|
|
||||||
@topic_config(
|
|
||||||
period=5.0, # 发布周期(秒),默认 5.0
|
|
||||||
print_publish=False, # 是否打印发布日志
|
|
||||||
qos=10, # QoS 深度,默认 10
|
|
||||||
name="custom_name", # 自定义发布名称(默认用属性名)
|
|
||||||
)
|
|
||||||
def temperature(self) -> float:
|
|
||||||
return self.data.get("temperature", 0.0)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 辅助装饰器
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import not_action, always_free
|
|
||||||
|
|
||||||
@not_action # 标记为非动作(post_init、辅助方法等)
|
|
||||||
@always_free # 标记为不受排队限制(查询类操作)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 设备模板
|
|
||||||
|
|
||||||
```python
|
|
||||||
import logging
|
|
||||||
from typing import Any, Dict, Optional
|
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
from unilabos.registry.decorators import device, action, topic_config, not_action
|
|
||||||
|
|
||||||
@device(id="my_device", category=["my_category"], description="设备描述")
|
|
||||||
class MyDevice:
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
||||||
self.device_id = device_id or "my_device"
|
|
||||||
self.config = config or {}
|
|
||||||
self.logger = logging.getLogger(f"MyDevice.{self.device_id}")
|
|
||||||
self.data: Dict[str, Any] = {"status": "Idle"}
|
|
||||||
|
|
||||||
@not_action
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode) -> None:
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
@action
|
|
||||||
async def initialize(self) -> bool:
|
|
||||||
self.data["status"] = "Ready"
|
|
||||||
return True
|
|
||||||
|
|
||||||
@action
|
|
||||||
async def cleanup(self) -> bool:
|
|
||||||
self.data["status"] = "Offline"
|
|
||||||
return True
|
|
||||||
|
|
||||||
@action(description="执行操作")
|
|
||||||
def my_action(self, param: float = 0.0, name: str = "") -> Dict[str, Any]:
|
|
||||||
"""带 @action 装饰器 → 注册为 'my_action' 动作"""
|
|
||||||
return {"success": True}
|
|
||||||
|
|
||||||
def get_info(self) -> Dict[str, Any]:
|
|
||||||
"""无 @action → 自动注册为 'auto-get_info' 动作"""
|
|
||||||
return {"device_id": self.device_id}
|
|
||||||
|
|
||||||
@property
|
|
||||||
@topic_config()
|
|
||||||
def status(self) -> str:
|
|
||||||
return self.data.get("status", "Idle")
|
|
||||||
|
|
||||||
@property
|
|
||||||
@topic_config(period=2.0)
|
|
||||||
def temperature(self) -> float:
|
|
||||||
return self.data.get("temperature", 0.0)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 要点
|
|
||||||
|
|
||||||
- `_ros_node: BaseROS2DeviceNode` 类型标注放在类体顶部
|
|
||||||
- `__init__` 签名固定为 `(self, device_id=None, config=None, **kwargs)`
|
|
||||||
- `post_init` 用 `@not_action` 标记,参数类型标注为 `BaseROS2DeviceNode`
|
|
||||||
- 运行时状态存储在 `self.data` 字典中
|
|
||||||
- 设备文件放在 `unilabos/devices/<category>/` 目录下
|
|
||||||
@@ -1,351 +0,0 @@
|
|||||||
---
|
|
||||||
name: add-resource
|
|
||||||
description: Guide for adding new resources (materials, bottles, carriers, decks, warehouses) to Uni-Lab-OS (添加新物料/资源). Uses @resource decorator for AST auto-scanning. Covers Bottle, Carrier, Deck, WareHouse definitions. Use when the user wants to add resources, define materials, create a deck layout, add bottles/carriers/plates, or mentions 物料/资源/resource/bottle/carrier/deck/plate/warehouse.
|
|
||||||
---
|
|
||||||
|
|
||||||
# 添加新物料资源
|
|
||||||
|
|
||||||
Uni-Lab-OS 的资源体系基于 PyLabRobot,通过扩展实现 Bottle、Carrier、WareHouse、Deck 等实验室物料管理。使用 `@resource` 装饰器注册,AST 自动扫描生成注册表条目。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 资源类型
|
|
||||||
|
|
||||||
| 类型 | 基类 | 用途 | 示例 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| **Bottle** | `Well` (PyLabRobot) | 单个容器(瓶、小瓶、烧杯、反应器) | 试剂瓶、粉末瓶 |
|
|
||||||
| **BottleCarrier** | `ItemizedCarrier` | 多槽位载架(放多个 Bottle) | 6 位试剂架、枪头盒 |
|
|
||||||
| **WareHouse** | `ItemizedCarrier` | 堆栈/仓库(放多个 Carrier) | 4x4 堆栈 |
|
|
||||||
| **Deck** | `Deck` (PyLabRobot) | 工作站台面(放多个 WareHouse) | 反应站 Deck |
|
|
||||||
|
|
||||||
**层级关系:** `Deck` → `WareHouse` → `BottleCarrier` → `Bottle`
|
|
||||||
|
|
||||||
WareHouse 本质上和 Site 是同一概念 — 都是定义一组固定的放置位(slot),只不过 WareHouse 多嵌套了一层 Deck。两者都需要开发者根据实际物理尺寸自行计算各 slot 的偏移坐标。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## @resource 装饰器
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import resource
|
|
||||||
|
|
||||||
@resource(
|
|
||||||
id="my_resource_id", # 注册表唯一标识(必填)
|
|
||||||
category=["bottles"], # 分类标签列表(必填)
|
|
||||||
description="资源描述",
|
|
||||||
icon="", # 图标
|
|
||||||
version="1.0.0",
|
|
||||||
handles=[...], # 端口列表(InputHandle / OutputHandle)
|
|
||||||
model={...}, # 3D 模型配置
|
|
||||||
class_type="pylabrobot", # "python" / "pylabrobot" / "unilabos"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 创建规范
|
|
||||||
|
|
||||||
### 命名规则
|
|
||||||
|
|
||||||
1. **`name` 参数作为前缀**:所有工厂函数必须接受 `name: str` 参数,创建子物料时以 `name` 作为前缀,确保实例名在运行时全局唯一
|
|
||||||
2. **Bottle 命名约定**:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
|
||||||
3. **函数名 = `@resource(id=...)`**:工厂函数名与注册表 id 保持一致
|
|
||||||
|
|
||||||
### 子物料命名示例
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Carrier 内部的 sites 用 name 前缀
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}" # "堆栈1左_A01", "堆栈1左_B02" ...
|
|
||||||
|
|
||||||
# Carrier 中放置 Bottle 时用 name 前缀
|
|
||||||
carrier[0] = My_Reagent_Bottle(f"{name}_flask_1") # "堆栈1左_flask_1"
|
|
||||||
carrier[i] = My_Solid_Vial(f"{name}_vial_{ordering[i]}") # "堆栈1左_vial_A1"
|
|
||||||
|
|
||||||
# create_homogeneous_resources 使用 name_prefix
|
|
||||||
sites=create_homogeneous_resources(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
locations=[...],
|
|
||||||
name_prefix=name, # 自动生成 "{name}_0", "{name}_1" ...
|
|
||||||
)
|
|
||||||
|
|
||||||
# Deck setup 中用仓库名称作为 name 传入
|
|
||||||
self.warehouses = {
|
|
||||||
"堆栈1左": my_warehouse_4x4("堆栈1左"), # WareHouse.name = "堆栈1左"
|
|
||||||
"试剂堆栈": my_reagent_stack("试剂堆栈"), # WareHouse.name = "试剂堆栈"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 其他规范
|
|
||||||
|
|
||||||
- **max_volume 单位为 μL**:500mL = 500000
|
|
||||||
- **尺寸单位为 mm**:`diameter`, `height`, `size_x/y/z`, `dx/dy/dz`
|
|
||||||
- **BottleCarrier 必须设置 `num_items_x/y/z`**:用于前端渲染布局
|
|
||||||
- **Deck 的 `__init__` 必须接受 `setup=False`**:图文件中 `config.setup=true` 触发 `setup()`
|
|
||||||
- **按项目分组文件**:同一工作站的资源放在 `unilabos/resources/<project>/` 下
|
|
||||||
- **`__init__` 必须接受 `serialize()` 输出的所有字段**:`serialize()` 输出会作为 `config` 回传到 `__init__`,因此必须通过显式参数或 `**kwargs` 接受,否则反序列化会报错
|
|
||||||
- **持久化运行时状态用 `serialize_state()`**:通过 `_unilabos_state` 字典存储可变信息(如物料内容、液体量),只存 JSON 可序列化的基本类型
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 资源模板
|
|
||||||
|
|
||||||
### Bottle
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import resource
|
|
||||||
from unilabos.resources.itemized_carrier import Bottle
|
|
||||||
|
|
||||||
|
|
||||||
@resource(id="My_Reagent_Bottle", category=["bottles"], description="我的试剂瓶")
|
|
||||||
def My_Reagent_Bottle(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 70.0,
|
|
||||||
height: float = 120.0,
|
|
||||||
max_volume: float = 500000.0,
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="My_Reagent_Bottle",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Bottle 参数:**
|
|
||||||
- `name`: 实例名称(运行时唯一,由上层 Carrier 以前缀方式传入)
|
|
||||||
- `diameter`: 瓶体直径 (mm)
|
|
||||||
- `height`: 瓶体高度 (mm)
|
|
||||||
- `max_volume`: 最大容积(**μL**,500mL = 500000)
|
|
||||||
- `barcode`: 条形码(可选)
|
|
||||||
|
|
||||||
### BottleCarrier
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pylabrobot.resources import ResourceHolder
|
|
||||||
from pylabrobot.resources.carrier import create_ordered_items_2d
|
|
||||||
from unilabos.resources.itemized_carrier import BottleCarrier
|
|
||||||
from unilabos.registry.decorators import resource
|
|
||||||
|
|
||||||
|
|
||||||
@resource(id="My_6SlotCarrier", category=["bottle_carriers"], description="六槽位载架")
|
|
||||||
def My_6SlotCarrier(name: str) -> BottleCarrier:
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=3, num_items_y=2,
|
|
||||||
dx=10.0, dy=10.0, dz=5.0,
|
|
||||||
item_dx=42.0, item_dy=35.0,
|
|
||||||
size_x=20.0, size_y=20.0, size_z=50.0,
|
|
||||||
)
|
|
||||||
# 子 site 用 name 作为前缀
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name, size_x=146.0, size_y=80.0, size_z=55.0,
|
|
||||||
sites=sites, model="My_6SlotCarrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 3
|
|
||||||
carrier.num_items_y = 2
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
|
|
||||||
# 放置 Bottle 时用 name 作为前缀
|
|
||||||
ordering = ["A1", "B1", "A2", "B2", "A3", "B3"]
|
|
||||||
for i in range(6):
|
|
||||||
carrier[i] = My_Reagent_Bottle(f"{name}_vial_{ordering[i]}")
|
|
||||||
return carrier
|
|
||||||
```
|
|
||||||
|
|
||||||
### WareHouse / Deck 放置位
|
|
||||||
|
|
||||||
WareHouse 和 Site 本质上是同一概念:都是定义一组固定放置位(slot),根据物理尺寸自行批量计算偏移坐标。WareHouse 只是多嵌套了一层 Deck 而已。推荐开发者直接根据实物测量数据计算各 slot 偏移量。
|
|
||||||
|
|
||||||
#### WareHouse(使用 warehouse_factory)
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.resources.warehouse import warehouse_factory
|
|
||||||
from unilabos.registry.decorators import resource
|
|
||||||
|
|
||||||
|
|
||||||
@resource(id="my_warehouse_4x4", category=["warehouse"], description="4x4 堆栈仓库")
|
|
||||||
def my_warehouse_4x4(name: str) -> "WareHouse":
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=4, num_items_y=4, num_items_z=1,
|
|
||||||
dx=10.0, dy=10.0, dz=10.0, # 第一个 slot 的起始偏移
|
|
||||||
item_dx=147.0, item_dy=106.0, item_dz=130.0, # slot 间距
|
|
||||||
resource_size_x=127.0, resource_size_y=85.0, resource_size_z=100.0, # slot 尺寸
|
|
||||||
model="my_warehouse_4x4",
|
|
||||||
col_offset=0, # 列标签起始偏移(0 → A01, 4 → A05)
|
|
||||||
layout="row-major", # "row-major" 行优先 / "col-major" 列优先 / "vertical-col-major" 竖向
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
`warehouse_factory` 参数说明:
|
|
||||||
- `dx/dy/dz`:第一个 slot 相对 WareHouse 原点的偏移(mm)
|
|
||||||
- `item_dx/item_dy/item_dz`:相邻 slot 间距(mm),需根据实际物理间距测量
|
|
||||||
- `resource_size_x/y/z`:每个 slot 的可放置区域尺寸
|
|
||||||
- `layout`:影响 slot 标签和坐标映射
|
|
||||||
- `"row-major"`:A01,A02,...,B01,B02,...(行优先,适合横向排列)
|
|
||||||
- `"col-major"`:A01,B01,...,A02,B02,...(列优先)
|
|
||||||
- `"vertical-col-major"`:竖向排列,y 坐标反向
|
|
||||||
|
|
||||||
#### Deck 组装 WareHouse
|
|
||||||
|
|
||||||
Deck 通过 `setup()` 将多个 WareHouse 放置到指定坐标:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pylabrobot.resources import Deck, Coordinate
|
|
||||||
from unilabos.registry.decorators import resource
|
|
||||||
|
|
||||||
|
|
||||||
@resource(id="MyStation_Deck", category=["deck"], description="我的工作站 Deck")
|
|
||||||
class MyStation_Deck(Deck):
|
|
||||||
def __init__(self, name="MyStation_Deck", size_x=2700.0, size_y=1080.0, size_z=1500.0,
|
|
||||||
category="deck", setup=False, **kwargs) -> None:
|
|
||||||
super().__init__(name=name, size_x=size_x, size_y=size_y, size_z=size_z)
|
|
||||||
if setup:
|
|
||||||
self.setup()
|
|
||||||
|
|
||||||
def setup(self) -> None:
|
|
||||||
self.warehouses = {
|
|
||||||
"堆栈1左": my_warehouse_4x4("堆栈1左"),
|
|
||||||
"堆栈1右": my_warehouse_4x4("堆栈1右"),
|
|
||||||
}
|
|
||||||
self.warehouse_locations = {
|
|
||||||
"堆栈1左": Coordinate(-200.0, 400.0, 0.0), # 自行测量计算
|
|
||||||
"堆栈1右": Coordinate(2350.0, 400.0, 0.0),
|
|
||||||
}
|
|
||||||
for wh_name, wh in self.warehouses.items():
|
|
||||||
self.assign_child_resource(wh, location=self.warehouse_locations[wh_name])
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Site 模式(前端定向放置)
|
|
||||||
|
|
||||||
适用于有固定孔位/槽位的设备(如移液站 PRCXI 9300),Deck 通过 `sites` 列表定义前端展示的放置位,前端据此渲染可拖拽的孔位布局:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import collections
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
from pylabrobot.resources import Deck, Resource, Coordinate
|
|
||||||
from unilabos.registry.decorators import resource
|
|
||||||
|
|
||||||
|
|
||||||
@resource(id="MyLabDeck", category=["deck"], description="带 Site 定向放置的 Deck")
|
|
||||||
class MyLabDeck(Deck):
|
|
||||||
# 根据设备台面实测批量计算各 slot 坐标偏移
|
|
||||||
_DEFAULT_SITE_POSITIONS = [
|
|
||||||
(0, 0, 0), (138, 0, 0), (276, 0, 0), (414, 0, 0), # T1-T4
|
|
||||||
(0, 96, 0), (138, 96, 0), (276, 96, 0), (414, 96, 0), # T5-T8
|
|
||||||
]
|
|
||||||
_DEFAULT_SITE_SIZE = {"width": 128.0, "height": 86.0, "depth": 0}
|
|
||||||
_DEFAULT_CONTENT_TYPE = ["plate", "tip_rack", "tube_rack", "adaptor"]
|
|
||||||
|
|
||||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
|
||||||
sites: Optional[List[Dict[str, Any]]] = None, **kwargs):
|
|
||||||
super().__init__(size_x, size_y, size_z, name)
|
|
||||||
if sites is not None:
|
|
||||||
self.sites = [dict(s) for s in sites]
|
|
||||||
else:
|
|
||||||
self.sites = []
|
|
||||||
for i, (x, y, z) in enumerate(self._DEFAULT_SITE_POSITIONS):
|
|
||||||
self.sites.append({
|
|
||||||
"label": f"T{i + 1}", # 前端显示的槽位标签
|
|
||||||
"visible": True, # 是否在前端可见
|
|
||||||
"position": {"x": x, "y": y, "z": z}, # 槽位物理坐标
|
|
||||||
"size": dict(self._DEFAULT_SITE_SIZE), # 槽位尺寸
|
|
||||||
"content_type": list(self._DEFAULT_CONTENT_TYPE), # 允许放入的物料类型
|
|
||||||
})
|
|
||||||
self._ordering = collections.OrderedDict(
|
|
||||||
(site["label"], None) for site in self.sites
|
|
||||||
)
|
|
||||||
|
|
||||||
def assign_child_resource(self, resource: Resource,
|
|
||||||
location: Optional[Coordinate] = None,
|
|
||||||
reassign: bool = True,
|
|
||||||
spot: Optional[int] = None):
|
|
||||||
idx = spot
|
|
||||||
if spot is None:
|
|
||||||
for i, site in enumerate(self.sites):
|
|
||||||
if site.get("label") == resource.name:
|
|
||||||
idx = i
|
|
||||||
break
|
|
||||||
if idx is None:
|
|
||||||
for i in range(len(self.sites)):
|
|
||||||
if self._get_site_resource(i) is None:
|
|
||||||
idx = i
|
|
||||||
break
|
|
||||||
if idx is None:
|
|
||||||
raise ValueError(f"No available site for '{resource.name}'")
|
|
||||||
loc = Coordinate(**self.sites[idx]["position"])
|
|
||||||
super().assign_child_resource(resource, location=loc, reassign=reassign)
|
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
data = super().serialize()
|
|
||||||
sites_out = []
|
|
||||||
for i, site in enumerate(self.sites):
|
|
||||||
occupied = self._get_site_resource(i)
|
|
||||||
sites_out.append({
|
|
||||||
"label": site["label"],
|
|
||||||
"visible": site.get("visible", True),
|
|
||||||
"occupied_by": occupied.name if occupied else None,
|
|
||||||
"position": site["position"],
|
|
||||||
"size": site["size"],
|
|
||||||
"content_type": site["content_type"],
|
|
||||||
})
|
|
||||||
data["sites"] = sites_out
|
|
||||||
return data
|
|
||||||
```
|
|
||||||
|
|
||||||
**Site 字段说明:**
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| `label` | str | 槽位标签(如 `"T1"`),前端显示名称,也用于匹配 resource.name |
|
|
||||||
| `visible` | bool | 是否在前端可见 |
|
|
||||||
| `position` | dict | 物理坐标 `{x, y, z}`(mm),需自行测量计算偏移 |
|
|
||||||
| `size` | dict | 槽位尺寸 `{width, height, depth}`(mm) |
|
|
||||||
| `content_type` | list | 允许放入的物料类型,如 `["plate", "tip_rack", "tube_rack", "adaptor"]` |
|
|
||||||
|
|
||||||
**参考实现:** `unilabos/devices/liquid_handling/prcxi/prcxi.py` 中的 `PRCXI9300Deck`(4x4 共 16 个 site)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 文件位置
|
|
||||||
|
|
||||||
```
|
|
||||||
unilabos/resources/
|
|
||||||
├── <project>/ # 按项目分组
|
|
||||||
│ ├── bottles.py # Bottle 工厂函数
|
|
||||||
│ ├── bottle_carriers.py # Carrier 工厂函数
|
|
||||||
│ ├── warehouses.py # WareHouse 工厂函数
|
|
||||||
│ └── decks.py # Deck 类定义
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 验证
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 资源可导入
|
|
||||||
python -c "from unilabos.resources.my_project.bottles import My_Reagent_Bottle; print(My_Reagent_Bottle('test'))"
|
|
||||||
|
|
||||||
# 启动测试(AST 自动扫描)
|
|
||||||
unilab -g <graph>.json
|
|
||||||
```
|
|
||||||
|
|
||||||
仅在以下情况仍需 YAML:第三方库资源(如 pylabrobot 内置资源,无 `@resource` 装饰器)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 关键路径
|
|
||||||
|
|
||||||
| 内容 | 路径 |
|
|
||||||
|------|------|
|
|
||||||
| Bottle/Carrier 基类 | `unilabos/resources/itemized_carrier.py` |
|
|
||||||
| WareHouse 基类 + 工厂 | `unilabos/resources/warehouse.py` |
|
|
||||||
| PLR 注册 | `unilabos/resources/plr_additional_res_reg.py` |
|
|
||||||
| 装饰器定义 | `unilabos/registry/decorators.py` |
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
# 资源高级参考
|
|
||||||
|
|
||||||
本文件是 SKILL.md 的补充,包含类继承体系、序列化/反序列化、Bioyond 物料同步、非瓶类资源和仓库工厂模式。Agent 在需要实现这些功能时按需阅读。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 类继承体系
|
|
||||||
|
|
||||||
```
|
|
||||||
PyLabRobot
|
|
||||||
├── Resource (PLR 基类)
|
|
||||||
│ ├── Well
|
|
||||||
│ │ └── Bottle (unilabos) → 瓶/小瓶/烧杯/反应器
|
|
||||||
│ ├── Deck
|
|
||||||
│ │ └── 自定义 Deck 类 (unilabos) → 工作站台面
|
|
||||||
│ ├── ResourceHolder → 槽位占位符
|
|
||||||
│ └── Container
|
|
||||||
│ └── Battery (unilabos) → 组装好的电池
|
|
||||||
│
|
|
||||||
├── ItemizedCarrier (unilabos, 继承 Resource)
|
|
||||||
│ ├── BottleCarrier (unilabos) → 瓶载架
|
|
||||||
│ └── WareHouse (unilabos) → 堆栈仓库
|
|
||||||
│
|
|
||||||
├── ItemizedResource (PLR)
|
|
||||||
│ └── MagazineHolder (unilabos) → 子弹夹载架
|
|
||||||
│
|
|
||||||
└── ResourceStack (PLR)
|
|
||||||
└── Magazine (unilabos) → 子弹夹洞位
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bottle 类细节
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Bottle(Well):
|
|
||||||
def __init__(self, name, diameter, height, max_volume,
|
|
||||||
size_x=0.0, size_y=0.0, size_z=0.0,
|
|
||||||
barcode=None, category="container", model=None, **kwargs):
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=diameter, # PLR 用 diameter 作为 size_x/size_y
|
|
||||||
size_y=diameter,
|
|
||||||
size_z=height, # PLR 用 height 作为 size_z
|
|
||||||
max_volume=max_volume,
|
|
||||||
category=category,
|
|
||||||
model=model,
|
|
||||||
bottom_type="flat",
|
|
||||||
cross_section_type="circle"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
注意 `size_x = size_y = diameter`,`size_z = height`。
|
|
||||||
|
|
||||||
### ItemizedCarrier 核心方法
|
|
||||||
|
|
||||||
| 方法 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `__getitem__(identifier)` | 通过索引或 Excel 标识(如 `"A01"`)访问槽位 |
|
|
||||||
| `__setitem__(identifier, resource)` | 向槽位放入资源 |
|
|
||||||
| `get_child_identifier(child)` | 获取子资源的标识符 |
|
|
||||||
| `capacity` | 总槽位数 |
|
|
||||||
| `sites` | 所有槽位字典 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 序列化与反序列化
|
|
||||||
|
|
||||||
### PLR ↔ UniLab 转换
|
|
||||||
|
|
||||||
| 函数 | 位置 | 方向 |
|
|
||||||
|------|------|------|
|
|
||||||
| `ResourceTreeSet.from_plr_resources(resources)` | `resource_tracker.py` | PLR → UniLab |
|
|
||||||
| `ResourceTreeSet.to_plr_resources()` | `resource_tracker.py` | UniLab → PLR |
|
|
||||||
|
|
||||||
### `from_plr_resources` 流程
|
|
||||||
|
|
||||||
```
|
|
||||||
PLR Resource
|
|
||||||
↓ build_uuid_mapping (递归生成 UUID)
|
|
||||||
↓ resource.serialize() → dict
|
|
||||||
↓ resource.serialize_all_state() → states
|
|
||||||
↓ resource_plr_inner (递归构建 ResourceDictInstance)
|
|
||||||
ResourceTreeSet
|
|
||||||
```
|
|
||||||
|
|
||||||
关键:每个 PLR 资源通过 `unilabos_uuid` 属性携带 UUID,`unilabos_extra` 携带扩展数据(如 `class` 名)。
|
|
||||||
|
|
||||||
### `to_plr_resources` 流程
|
|
||||||
|
|
||||||
```
|
|
||||||
ResourceTreeSet
|
|
||||||
↓ collect_node_data (收集 UUID、状态、扩展数据)
|
|
||||||
↓ node_to_plr_dict (转为 PLR 字典格式)
|
|
||||||
↓ find_subclass(type_name, PLRResource) (查找 PLR 子类)
|
|
||||||
↓ sub_cls.deserialize(plr_dict) (反序列化)
|
|
||||||
↓ loop_set_uuid, loop_set_extra (递归设置 UUID 和扩展)
|
|
||||||
PLR Resource
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bottle 序列化
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Bottle(Well):
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
data = super().serialize()
|
|
||||||
return {**data, "diameter": self.diameter, "height": self.height}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def deserialize(cls, data: dict, allow_marshal=False):
|
|
||||||
barcode_data = data.pop("barcode", None)
|
|
||||||
instance = super().deserialize(data, allow_marshal=allow_marshal)
|
|
||||||
if barcode_data and isinstance(barcode_data, str):
|
|
||||||
instance.barcode = barcode_data
|
|
||||||
return instance
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Bioyond 物料同步
|
|
||||||
|
|
||||||
### 双向转换函数
|
|
||||||
|
|
||||||
| 函数 | 位置 | 方向 |
|
|
||||||
|------|------|------|
|
|
||||||
| `resource_bioyond_to_plr(materials, type_mapping, deck)` | `graphio.py` | Bioyond → PLR |
|
|
||||||
| `resource_plr_to_bioyond(resources, type_mapping, warehouse_mapping)` | `graphio.py` | PLR → Bioyond |
|
|
||||||
|
|
||||||
### `resource_bioyond_to_plr` 流程
|
|
||||||
|
|
||||||
```
|
|
||||||
Bioyond 物料列表
|
|
||||||
↓ reverse_type_mapping: {typeName → (model, UUID)}
|
|
||||||
↓ 对每个物料:
|
|
||||||
typeName → 查映射 → model (如 "BIOYOND_PolymerStation_Reactor")
|
|
||||||
initialize_resource({"name": unique_name, "class": model})
|
|
||||||
↓ 设置 unilabos_extra (material_bioyond_id, material_bioyond_name 等)
|
|
||||||
↓ 处理 detail (子物料/坐标)
|
|
||||||
↓ 按 locationName 放入 deck.warehouses 对应槽位
|
|
||||||
PLR 资源列表
|
|
||||||
```
|
|
||||||
|
|
||||||
### `resource_plr_to_bioyond` 流程
|
|
||||||
|
|
||||||
```
|
|
||||||
PLR 资源列表
|
|
||||||
↓ 遍历每个资源:
|
|
||||||
载架(capacity > 1): 生成 details 子物料 + 坐标
|
|
||||||
单瓶: 直接映射
|
|
||||||
↓ type_mapping 查找 typeId
|
|
||||||
↓ warehouse_mapping 查找位置 UUID
|
|
||||||
↓ 组装 Bioyond 格式 (name, typeName, typeId, quantity, Parameters, locations)
|
|
||||||
Bioyond 物料列表
|
|
||||||
```
|
|
||||||
|
|
||||||
### BioyondResourceSynchronizer
|
|
||||||
|
|
||||||
工作站通过 `ResourceSynchronizer` 自动同步物料:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class BioyondResourceSynchronizer(ResourceSynchronizer):
|
|
||||||
def sync_from_external(self) -> bool:
|
|
||||||
all_data = []
|
|
||||||
all_data.extend(api_client.stock_material('{"typeMode": 0}')) # 耗材
|
|
||||||
all_data.extend(api_client.stock_material('{"typeMode": 1}')) # 样品
|
|
||||||
all_data.extend(api_client.stock_material('{"typeMode": 2}')) # 试剂
|
|
||||||
unilab_resources = resource_bioyond_to_plr(
|
|
||||||
all_data,
|
|
||||||
type_mapping=self.workstation.bioyond_config["material_type_mappings"],
|
|
||||||
deck=self.workstation.deck
|
|
||||||
)
|
|
||||||
# 更新 deck 上的资源
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 非瓶类资源
|
|
||||||
|
|
||||||
### ElectrodeSheet(极片)
|
|
||||||
|
|
||||||
路径:`unilabos/resources/battery/electrode_sheet.py`
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ElectrodeSheet(ResourcePLR):
|
|
||||||
"""片状材料(极片、隔膜、弹片、垫片等)"""
|
|
||||||
_unilabos_state = {
|
|
||||||
"diameter": 0.0,
|
|
||||||
"thickness": 0.0,
|
|
||||||
"mass": 0.0,
|
|
||||||
"material_type": "",
|
|
||||||
"color": "",
|
|
||||||
"info": "",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
工厂函数:`PositiveCan`, `PositiveElectrode`, `NegativeCan`, `NegativeElectrode`, `SpringWasher`, `FlatWasher`, `AluminumFoil`
|
|
||||||
|
|
||||||
### Battery(电池)
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Battery(Container):
|
|
||||||
"""组装好的电池"""
|
|
||||||
_unilabos_state = {
|
|
||||||
"color": "",
|
|
||||||
"electrolyte_name": "",
|
|
||||||
"open_circuit_voltage": 0.0,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Magazine / MagazineHolder(子弹夹)
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Magazine(ResourceStack):
|
|
||||||
"""子弹夹洞位,可堆叠 ElectrodeSheet"""
|
|
||||||
# direction, max_sheets
|
|
||||||
|
|
||||||
class MagazineHolder(ItemizedResource):
|
|
||||||
"""多洞位子弹夹"""
|
|
||||||
# hole_diameter, hole_depth, max_sheets_per_hole
|
|
||||||
```
|
|
||||||
|
|
||||||
工厂函数 `magazine_factory()` 用 `create_homogeneous_resources` 生成洞位,可选预填 `ElectrodeSheet` 或 `Battery`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 仓库工厂模式参考
|
|
||||||
|
|
||||||
### 实际 warehouse 工厂函数示例
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 行优先 4x4 仓库
|
|
||||||
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=4, num_items_y=4, num_items_z=1,
|
|
||||||
dx=10.0, dy=10.0, dz=10.0,
|
|
||||||
item_dx=147.0, item_dy=106.0, item_dz=130.0,
|
|
||||||
layout="row-major", # A01,A02,A03,A04, B01,...
|
|
||||||
)
|
|
||||||
|
|
||||||
# 右侧 4x4 仓库(列名偏移)
|
|
||||||
def bioyond_warehouse_1x4x4_right(name: str) -> WareHouse:
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=4, num_items_y=4, num_items_z=1,
|
|
||||||
dx=10.0, dy=10.0, dz=10.0,
|
|
||||||
item_dx=147.0, item_dy=106.0, item_dz=130.0,
|
|
||||||
col_offset=4, # A05,A06,A07,A08
|
|
||||||
layout="row-major",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 竖向仓库(站内试剂存放)
|
|
||||||
def bioyond_warehouse_reagent_storage(name: str) -> WareHouse:
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=1, num_items_y=2, num_items_z=1,
|
|
||||||
dx=10.0, dy=10.0, dz=10.0,
|
|
||||||
item_dx=147.0, item_dy=106.0, item_dz=130.0,
|
|
||||||
layout="vertical-col-major",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 行偏移(F 行开始)
|
|
||||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=3, num_items_y=5, num_items_z=1,
|
|
||||||
dx=10.0, dy=10.0, dz=10.0,
|
|
||||||
item_dx=159.0, item_dy=183.0, item_dz=130.0,
|
|
||||||
row_offset=row_offset, # 0→A行起,5→F行起
|
|
||||||
layout="row-major",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### layout 类型说明
|
|
||||||
|
|
||||||
| layout | 命名顺序 | 适用场景 |
|
|
||||||
|--------|---------|---------|
|
|
||||||
| `col-major` (默认) | A01,B01,C01,D01, A02,B02,... | 列优先,标准堆栈 |
|
|
||||||
| `row-major` | A01,A02,A03,A04, B01,B02,... | 行优先,Bioyond 前端展示 |
|
|
||||||
| `vertical-col-major` | 竖向排列,标签从底部开始 | 竖向仓库(试剂存放、测密度) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 关键路径
|
|
||||||
|
|
||||||
| 内容 | 路径 |
|
|
||||||
|------|------|
|
|
||||||
| Bottle/Carrier 基类 | `unilabos/resources/itemized_carrier.py` |
|
|
||||||
| WareHouse 类 + 工厂 | `unilabos/resources/warehouse.py` |
|
|
||||||
| ResourceTreeSet 转换 | `unilabos/resources/resource_tracker.py` |
|
|
||||||
| Bioyond 物料转换 | `unilabos/resources/graphio.py` |
|
|
||||||
| Bioyond 仓库定义 | `unilabos/resources/bioyond/warehouses.py` |
|
|
||||||
| 电池资源 | `unilabos/resources/battery/` |
|
|
||||||
| PLR 注册 | `unilabos/resources/plr_additional_res_reg.py` |
|
|
||||||
@@ -1,626 +0,0 @@
|
|||||||
---
|
|
||||||
name: add-workstation
|
|
||||||
description: Guide for adding new workstations to Uni-Lab-OS (接入新工作站). Uses @device decorator + AST auto-scanning. Walks through workstation type, sub-device composition, driver creation, deck setup, and graph file. Use when the user wants to add a workstation, create a workstation driver, configure a station with sub-devices, or mentions 工作站/工站/station/workstation.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Uni-Lab-OS 工作站接入指南
|
|
||||||
|
|
||||||
工作站(workstation)是组合多个子设备的大型设备,拥有独立的物料管理系统和工作流引擎。使用 `@device` 装饰器注册,AST 自动扫描生成注册表。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 工作站类型
|
|
||||||
|
|
||||||
| 类型 | 基类 | 适用场景 |
|
|
||||||
| ------------------- | ----------------- | ---------------------------------- |
|
|
||||||
| **Protocol 工作站** | `ProtocolNode` | 标准化学操作协议(泵转移、过滤等) |
|
|
||||||
| **外部系统工作站** | `WorkstationBase` | 与外部 LIMS/MES 对接 |
|
|
||||||
| **硬件控制工作站** | `WorkstationBase` | 直接控制 PLC/硬件 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## @device 装饰器(工作站)
|
|
||||||
|
|
||||||
工作站也使用 `@device` 装饰器注册,参数与普通设备一致:
|
|
||||||
|
|
||||||
```python
|
|
||||||
@device(
|
|
||||||
id="my_workstation", # 注册表唯一标识(必填)
|
|
||||||
category=["workstation"], # 分类标签
|
|
||||||
description="我的工作站",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
如果一个工作站类支持多个具体变体,可使用 `ids` / `id_meta`,与设备的用法相同(参见 add-device SKILL)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 工作站驱动模板
|
|
||||||
|
|
||||||
### 模板 A:基于外部系统的工作站
|
|
||||||
|
|
||||||
```python
|
|
||||||
import logging
|
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
from pylabrobot.resources import Deck
|
|
||||||
|
|
||||||
from unilabos.registry.decorators import device, topic_config, not_action
|
|
||||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
|
||||||
|
|
||||||
try:
|
|
||||||
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
|
||||||
except ImportError:
|
|
||||||
ROS2WorkstationNode = None
|
|
||||||
|
|
||||||
|
|
||||||
@device(id="my_workstation", category=["workstation"], description="我的工作站")
|
|
||||||
class MyWorkstation(WorkstationBase):
|
|
||||||
_ros_node: "ROS2WorkstationNode"
|
|
||||||
|
|
||||||
def __init__(self, config=None, deck=None, protocol_type=None, **kwargs):
|
|
||||||
super().__init__(deck=deck, **kwargs)
|
|
||||||
self.config = config or {}
|
|
||||||
self.logger = logging.getLogger("MyWorkstation")
|
|
||||||
self.api_host = self.config.get("api_host", "")
|
|
||||||
self._status = "Idle"
|
|
||||||
|
|
||||||
@not_action
|
|
||||||
def post_init(self, ros_node: "ROS2WorkstationNode"):
|
|
||||||
super().post_init(ros_node)
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def scheduler_start(self, **kwargs) -> Dict[str, Any]:
|
|
||||||
"""注册为工作站动作"""
|
|
||||||
return {"success": True}
|
|
||||||
|
|
||||||
async def create_order(self, json_str: str, **kwargs) -> Dict[str, Any]:
|
|
||||||
"""注册为工作站动作"""
|
|
||||||
return {"success": True}
|
|
||||||
|
|
||||||
@property
|
|
||||||
@topic_config()
|
|
||||||
def workflow_sequence(self) -> str:
|
|
||||||
return "[]"
|
|
||||||
|
|
||||||
@property
|
|
||||||
@topic_config()
|
|
||||||
def material_info(self) -> str:
|
|
||||||
return "{}"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 模板 B:Protocol 工作站
|
|
||||||
|
|
||||||
直接使用 `ProtocolNode`,通常不需要自定义驱动类:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.devices.workstation.workstation_base import ProtocolNode
|
|
||||||
```
|
|
||||||
|
|
||||||
在图文件中配置 `protocol_type` 即可。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 子设备访问(sub_devices)
|
|
||||||
|
|
||||||
工站初始化子设备后,所有子设备实例存储在 `self._ros_node.sub_devices` 字典中(key 为设备 id,value 为 `ROS2DeviceNode` 实例)。工站的驱动类可以直接获取子设备实例来调用其方法:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 在工站驱动类的方法中访问子设备
|
|
||||||
sub = self._ros_node.sub_devices["pump_1"]
|
|
||||||
|
|
||||||
# .driver_instance — 子设备的驱动实例(即设备 Python 类的实例)
|
|
||||||
sub.driver_instance.some_method(arg1, arg2)
|
|
||||||
|
|
||||||
# .ros_node_instance — 子设备的 ROS2 节点实例
|
|
||||||
sub.ros_node_instance._action_value_mappings # 查看子设备支持的 action
|
|
||||||
```
|
|
||||||
|
|
||||||
**常见用法**:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class MyWorkstation(WorkstationBase):
|
|
||||||
def my_protocol(self, **kwargs):
|
|
||||||
# 获取子设备驱动实例
|
|
||||||
pump = self._ros_node.sub_devices["pump_1"].driver_instance
|
|
||||||
heater = self._ros_node.sub_devices["heater_1"].driver_instance
|
|
||||||
|
|
||||||
# 直接调用子设备方法
|
|
||||||
pump.aspirate(volume=100)
|
|
||||||
heater.set_temperature(80)
|
|
||||||
```
|
|
||||||
|
|
||||||
> 参考实现:`unilabos/devices/workstation/bioyond_studio/reaction_station/reaction_station.py` 中通过 `self._ros_node.sub_devices.get(reactor_id)` 获取子反应器实例并更新数据。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 硬件通信接口(hardware_interface)
|
|
||||||
|
|
||||||
硬件控制型工作站通常需要通过串口(Serial)、Modbus 等通信协议控制多个子设备。Uni-Lab-OS 通过 **通信设备代理** 机制实现端口共享:一个串口只创建一个 `serial` 节点,多个子设备共享这个通信实例。
|
|
||||||
|
|
||||||
### 工作原理
|
|
||||||
|
|
||||||
`ROS2WorkstationNode` 初始化时分两轮遍历子设备(`workstation.py`):
|
|
||||||
|
|
||||||
**第一轮 — 初始化所有子设备**:按 `children` 顺序调用 `initialize_device()`,通信设备(`serial_` / `io_` 开头的 id)优先完成初始化,创建 `serial.Serial()` 实例。其他子设备此时 `self.hardware_interface = "serial_pump"`(字符串)。
|
|
||||||
|
|
||||||
**第二轮 — 代理替换**:遍历所有已初始化的子设备,读取子设备的 `_hardware_interface` 配置:
|
|
||||||
|
|
||||||
```
|
|
||||||
hardware_interface = d.ros_node_instance._hardware_interface
|
|
||||||
# → {"name": "hardware_interface", "read": "send_command", "write": "send_command"}
|
|
||||||
```
|
|
||||||
|
|
||||||
1. 取 `name` 字段对应的属性值:`name_value = getattr(driver, hardware_interface["name"])`
|
|
||||||
- 如果 `name_value` 是字符串且该字符串是某个子设备的 id → 触发代理替换
|
|
||||||
2. 从通信设备获取真正的 `read`/`write` 方法
|
|
||||||
3. 用 `setattr(driver, read_method, _read)` 将通信设备的方法绑定到子设备上
|
|
||||||
|
|
||||||
因此:
|
|
||||||
|
|
||||||
- **通信设备 id 必须与子设备 config 中填的字符串完全一致**(如 `"serial_pump"`)
|
|
||||||
- **通信设备 id 必须以 `serial_` 或 `io_` 开头**(否则第一轮不会被识别为通信设备)
|
|
||||||
- **通信设备必须在 `children` 列表中排在最前面**,确保先初始化
|
|
||||||
|
|
||||||
### HardwareInterface 参数说明
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import HardwareInterface
|
|
||||||
|
|
||||||
HardwareInterface(
|
|
||||||
name="hardware_interface", # __init__ 中接收通信实例的属性名
|
|
||||||
read="send_command", # 通信设备上暴露的读方法名
|
|
||||||
write="send_command", # 通信设备上暴露的写方法名
|
|
||||||
extra_info=["list_ports"], # 可选:额外暴露的方法
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**`name` 字段的含义**:对应设备类 `__init__` 中,用于保存通信实例的**属性名**。系统据此知道要替换哪个属性。大部分设备直接用 `"hardware_interface"`,也可以自定义(如 `"io_device_port"`)。
|
|
||||||
|
|
||||||
### 示例 1:泵(name="hardware_interface")
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import device, HardwareInterface
|
|
||||||
|
|
||||||
@device(
|
|
||||||
id="my_pump",
|
|
||||||
category=["pump_and_valve"],
|
|
||||||
hardware_interface=HardwareInterface(
|
|
||||||
name="hardware_interface",
|
|
||||||
read="send_command",
|
|
||||||
write="send_command",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
class MyPump:
|
|
||||||
def __init__(self, port=None, address="1", **kwargs):
|
|
||||||
# name="hardware_interface" → 系统替换 self.hardware_interface
|
|
||||||
self.hardware_interface = port # 初始为字符串 "serial_pump",启动后被替换为 Serial 实例
|
|
||||||
self.address = address
|
|
||||||
|
|
||||||
def send_command(self, command: str):
|
|
||||||
full_command = f"/{self.address}{command}\r\n"
|
|
||||||
self.hardware_interface.write(bytearray(full_command, "ascii"))
|
|
||||||
return self.hardware_interface.read_until(b"\n")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 示例 2:电磁阀(name="io_device_port",自定义属性名)
|
|
||||||
|
|
||||||
```python
|
|
||||||
@device(
|
|
||||||
id="solenoid_valve",
|
|
||||||
category=["pump_and_valve"],
|
|
||||||
hardware_interface=HardwareInterface(
|
|
||||||
name="io_device_port", # 自定义属性名 → 系统替换 self.io_device_port
|
|
||||||
read="read_io_coil",
|
|
||||||
write="write_io_coil",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
class SolenoidValve:
|
|
||||||
def __init__(self, io_device_port: str = None, **kwargs):
|
|
||||||
# name="io_device_port" → 图文件 config 中用 "io_device_port": "io_board_1"
|
|
||||||
self.io_device_port = io_device_port # 初始为字符串,系统替换为 Modbus 实例
|
|
||||||
```
|
|
||||||
|
|
||||||
### Serial 通信设备(class="serial")
|
|
||||||
|
|
||||||
`serial` 是 Uni-Lab-OS 内置的通信代理设备,代码位于 `unilabos/ros/nodes/presets/serial_node.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from serial import Serial, SerialException
|
|
||||||
from threading import Lock
|
|
||||||
|
|
||||||
class ROS2SerialNode(BaseROS2DeviceNode):
|
|
||||||
def __init__(self, device_id, registry_name, port: str, baudrate: int = 9600, **kwargs):
|
|
||||||
self.port = port
|
|
||||||
self.baudrate = baudrate
|
|
||||||
self._hardware_interface = {
|
|
||||||
"name": "hardware_interface",
|
|
||||||
"write": "send_command",
|
|
||||||
"read": "read_data",
|
|
||||||
}
|
|
||||||
self._query_lock = Lock()
|
|
||||||
|
|
||||||
self.hardware_interface = Serial(baudrate=baudrate, port=port)
|
|
||||||
|
|
||||||
BaseROS2DeviceNode.__init__(
|
|
||||||
self, driver_instance=self, registry_name=registry_name,
|
|
||||||
device_id=device_id, status_types={}, action_value_mappings={},
|
|
||||||
hardware_interface=self._hardware_interface, print_publish=False,
|
|
||||||
)
|
|
||||||
self.create_service(SerialCommand, "serialwrite", self.handle_serial_request)
|
|
||||||
|
|
||||||
def send_command(self, command: str):
|
|
||||||
with self._query_lock:
|
|
||||||
self.hardware_interface.write(bytearray(f"{command}\n", "ascii"))
|
|
||||||
return self.hardware_interface.read_until(b"\n").decode()
|
|
||||||
|
|
||||||
def read_data(self):
|
|
||||||
with self._query_lock:
|
|
||||||
return self.hardware_interface.read_until(b"\n").decode()
|
|
||||||
```
|
|
||||||
|
|
||||||
在图文件中使用 `"class": "serial"` 即可创建串口代理:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "serial_pump",
|
|
||||||
"class": "serial",
|
|
||||||
"parent": "my_station",
|
|
||||||
"config": { "port": "COM7", "baudrate": 9600 }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 图文件配置
|
|
||||||
|
|
||||||
**通信设备必须在 `children` 列表中排在最前面**,确保先于其他子设备初始化:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "my_station",
|
|
||||||
"class": "workstation",
|
|
||||||
"children": ["serial_pump", "pump_1", "pump_2"],
|
|
||||||
"config": { "protocol_type": ["PumpTransferProtocol"] }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "serial_pump",
|
|
||||||
"class": "serial",
|
|
||||||
"parent": "my_station",
|
|
||||||
"config": { "port": "COM7", "baudrate": 9600 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "pump_1",
|
|
||||||
"class": "syringe_pump_with_valve.runze.SY03B-T08",
|
|
||||||
"parent": "my_station",
|
|
||||||
"config": { "port": "serial_pump", "address": "1", "max_volume": 25.0 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "pump_2",
|
|
||||||
"class": "syringe_pump_with_valve.runze.SY03B-T08",
|
|
||||||
"parent": "my_station",
|
|
||||||
"config": { "port": "serial_pump", "address": "2", "max_volume": 25.0 }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"source": "pump_1",
|
|
||||||
"target": "serial_pump",
|
|
||||||
"type": "communication",
|
|
||||||
"port": { "pump_1": "port", "serial_pump": "port" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "pump_2",
|
|
||||||
"target": "serial_pump",
|
|
||||||
"type": "communication",
|
|
||||||
"port": { "pump_2": "port", "serial_pump": "port" }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 通信协议速查
|
|
||||||
|
|
||||||
| 协议 | config 参数 | 依赖包 | 通信设备 class |
|
|
||||||
| -------------------- | ------------------------------ | ---------- | -------------------------- |
|
|
||||||
| Serial (RS232/RS485) | `port`, `baudrate` | `pyserial` | `serial` |
|
|
||||||
| Modbus RTU | `port`, `baudrate`, `slave_id` | `pymodbus` | `device_comms/modbus_plc/` |
|
|
||||||
| Modbus TCP | `host`, `port`, `slave_id` | `pymodbus` | `device_comms/modbus_plc/` |
|
|
||||||
| TCP Socket | `host`, `port` | stdlib | 自定义 |
|
|
||||||
| HTTP API | `url`, `token` | `requests` | `device_comms/rpc.py` |
|
|
||||||
|
|
||||||
参考实现:`unilabos/test/experiments/Grignard_flow_batchreact_single_pumpvalve.json`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Deck 与物料生命周期
|
|
||||||
|
|
||||||
### 1. Deck 入参与两种初始化模式
|
|
||||||
|
|
||||||
系统根据设备节点 `config.deck` 的写法,自动反序列化 Deck 实例后传入 `__init__` 的 `deck` 参数。目前 `deck` 是固定字段名,只支持一个主 Deck。建议一个设备拥有一个台面,台面上抽象二级、三级子物料。
|
|
||||||
|
|
||||||
有两种初始化模式:
|
|
||||||
|
|
||||||
#### init 初始化(推荐)
|
|
||||||
|
|
||||||
`config.deck` 直接包含 `_resource_type` + `_resource_child_name`,系统先用 Deck 节点的 `config` 调用 Deck 类的 `__init__` 反序列化,再将实例传入设备的 `deck` 参数。子物料随 Deck 的 `children` 一起反序列化。
|
|
||||||
|
|
||||||
```json
|
|
||||||
"config": {
|
|
||||||
"deck": {
|
|
||||||
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck",
|
|
||||||
"_resource_child_name": "PRCXI_Deck"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### deserialize 初始化
|
|
||||||
|
|
||||||
`config.deck` 用 `data` 包裹一层,系统走 `deserialize` 路径,可传入更多参数(如 `allow_marshal` 等):
|
|
||||||
|
|
||||||
```json
|
|
||||||
"config": {
|
|
||||||
"deck": {
|
|
||||||
"data": {
|
|
||||||
"_resource_child_name": "YB_Bioyond_Deck",
|
|
||||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
没有特殊需求时推荐 init 初始化。
|
|
||||||
|
|
||||||
#### config.deck 字段说明
|
|
||||||
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `_resource_type` | Deck 类的完整模块路径(`module:ClassName`) |
|
|
||||||
| `_resource_child_name` | 对应图文件中 Deck 节点的 `id`,建立父子关联 |
|
|
||||||
|
|
||||||
#### 设备 __init__ 接收
|
|
||||||
|
|
||||||
```python
|
|
||||||
def __init__(self, config=None, deck=None, protocol_type=None, **kwargs):
|
|
||||||
super().__init__(deck=deck, **kwargs)
|
|
||||||
# deck 已经是反序列化后的 Deck 实例
|
|
||||||
# → PRCXI9300Deck / BIOYOND_YB_Deck 等
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Deck 节点(图文件中)
|
|
||||||
|
|
||||||
Deck 节点作为设备的 `children` 之一,`parent` 指向设备 id:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "PRCXI_Deck",
|
|
||||||
"parent": "PRCXI",
|
|
||||||
"type": "deck",
|
|
||||||
"class": "",
|
|
||||||
"children": [],
|
|
||||||
"config": {
|
|
||||||
"type": "PRCXI9300Deck",
|
|
||||||
"size_x": 542, "size_y": 374, "size_z": 0,
|
|
||||||
"category": "deck",
|
|
||||||
"sites": [...]
|
|
||||||
},
|
|
||||||
"data": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `config` 中的字段会传入 Deck 类的 `__init__`(因此 `__init__` 必须能接受所有 `serialize()` 输出的字段)
|
|
||||||
- `children` 初始为空时,由同步器或手动初始化填充
|
|
||||||
- `config.type` 填 Deck 类名
|
|
||||||
|
|
||||||
### 2. Deck 为空时自行初始化
|
|
||||||
|
|
||||||
如果 Deck 节点的 `children` 为空,工作站需在 `post_init` 或首次同步时自行初始化内容:
|
|
||||||
|
|
||||||
```python
|
|
||||||
@not_action
|
|
||||||
def post_init(self, ros_node):
|
|
||||||
super().post_init(ros_node)
|
|
||||||
if self.deck and not self.deck.children:
|
|
||||||
self._initialize_default_deck()
|
|
||||||
|
|
||||||
def _initialize_default_deck(self):
|
|
||||||
from my_labware import My_TipRack, My_Plate
|
|
||||||
self.deck.assign_child_resource(My_TipRack("T1"), spot=0)
|
|
||||||
self.deck.assign_child_resource(My_Plate("T2"), spot=1)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 物料双向同步
|
|
||||||
|
|
||||||
当工作站对接外部系统(LIMS/MES)时,需要实现 `ResourceSynchronizer` 处理双向物料同步:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.devices.workstation.workstation_base import ResourceSynchronizer
|
|
||||||
|
|
||||||
class MyResourceSynchronizer(ResourceSynchronizer):
|
|
||||||
def sync_from_external(self) -> bool:
|
|
||||||
"""从外部系统同步到 self.workstation.deck"""
|
|
||||||
external_data = self._query_external_materials()
|
|
||||||
# 以外部工站为准:根据外部数据反向创建 PLR 资源实例
|
|
||||||
for item in external_data:
|
|
||||||
cls = self._resolve_resource_class(item["type"])
|
|
||||||
resource = cls(name=item["name"], **item["params"])
|
|
||||||
self.workstation.deck.assign_child_resource(resource, spot=item["slot"])
|
|
||||||
return True
|
|
||||||
|
|
||||||
def sync_to_external(self, resource) -> bool:
|
|
||||||
"""将 UniLab 侧物料变更同步到外部系统"""
|
|
||||||
# 以 UniLab 为准:将 PLR 资源转为外部格式并推送
|
|
||||||
external_format = self._convert_to_external(resource)
|
|
||||||
return self._push_to_external(external_format)
|
|
||||||
|
|
||||||
def handle_external_change(self, change_info) -> bool:
|
|
||||||
"""处理外部系统主动推送的变更"""
|
|
||||||
return True
|
|
||||||
```
|
|
||||||
|
|
||||||
同步策略取决于业务场景:
|
|
||||||
|
|
||||||
- **以外部工站为准**:从外部 API 查询物料数据,反向创建对应的 PLR 资源实例放到 Deck 上
|
|
||||||
- **以 UniLab 为准**:UniLab 侧的物料变更通过 `sync_to_external` 推送到外部系统
|
|
||||||
|
|
||||||
在工作站 `post_init` 中初始化同步器:
|
|
||||||
|
|
||||||
```python
|
|
||||||
@not_action
|
|
||||||
def post_init(self, ros_node):
|
|
||||||
super().post_init(ros_node)
|
|
||||||
self.resource_synchronizer = MyResourceSynchronizer(self)
|
|
||||||
self.resource_synchronizer.sync_from_external()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 序列化与持久化(serialize / serialize_state)
|
|
||||||
|
|
||||||
资源类需正确实现序列化,系统据此完成持久化和前端同步。
|
|
||||||
|
|
||||||
**`serialize()`** — 输出资源的结构信息(`config` 层),反序列化时作为 `__init__` 的入参回传。因此 **`__init__` 必须通过 `**kwargs`接受`serialize()` 输出的所有字段\*\*,即使当前不使用:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class MyDeck(Deck):
|
|
||||||
def __init__(self, name, size_x, size_y, size_z,
|
|
||||||
sites=None, # serialize() 输出的字段
|
|
||||||
rotation=None, # serialize() 输出的字段
|
|
||||||
barcode=None, # serialize() 输出的字段
|
|
||||||
**kwargs): # 兜底:接受所有未知的 serialize 字段
|
|
||||||
super().__init__(size_x, size_y, size_z, name)
|
|
||||||
# ...
|
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
data = super().serialize()
|
|
||||||
data["sites"] = [...] # 自定义字段
|
|
||||||
return data
|
|
||||||
```
|
|
||||||
|
|
||||||
**`serialize_state()`** — 输出资源的运行时状态(`data` 层),用于持久化可变信息。`data` 中的内容会被正确保存和恢复:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class MyPlate(Plate):
|
|
||||||
def __init__(self, name, size_x, size_y, size_z,
|
|
||||||
material_info=None, **kwargs):
|
|
||||||
super().__init__(name, size_x, size_y, size_z, **kwargs)
|
|
||||||
self._unilabos_state = {}
|
|
||||||
if material_info:
|
|
||||||
self._unilabos_state["Material"] = material_info
|
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Any]:
|
|
||||||
data = super().serialize_state()
|
|
||||||
data.update(self._unilabos_state)
|
|
||||||
return data
|
|
||||||
```
|
|
||||||
|
|
||||||
关键要点:
|
|
||||||
|
|
||||||
- `serialize()` 输出的所有字段都会作为 `config` 回传到 `__init__`,所以 `__init__` 必须能接受它们(显式声明或 `**kwargs`)
|
|
||||||
- `serialize_state()` 输出的 `data` 用于持久化运行时状态(如物料信息、液体量等)
|
|
||||||
- `_unilabos_state` 中只存可 JSON 序列化的基本类型(str, int, float, bool, list, dict, None)
|
|
||||||
|
|
||||||
### 5. 子物料自动同步
|
|
||||||
|
|
||||||
子物料(Bottle、Plate、TipRack 等)放到 Deck 上后,系统会自动将其同步到前端的 Deck 视图。只需保证资源类正确实现了 `serialize()` / `serialize_state()` 和反序列化即可。
|
|
||||||
|
|
||||||
### 6. 图文件配置(参考 prcxi_9320_slim.json)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "my_station",
|
|
||||||
"type": "device",
|
|
||||||
"class": "my_workstation",
|
|
||||||
"config": {
|
|
||||||
"deck": {
|
|
||||||
"_resource_type": "unilabos.resources.my_module:MyDeck",
|
|
||||||
"_resource_child_name": "my_deck"
|
|
||||||
},
|
|
||||||
"host": "10.20.30.1",
|
|
||||||
"port": 9999
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "my_deck",
|
|
||||||
"parent": "my_station",
|
|
||||||
"type": "deck",
|
|
||||||
"class": "",
|
|
||||||
"children": [],
|
|
||||||
"config": {
|
|
||||||
"type": "MyLabDeck",
|
|
||||||
"size_x": 542,
|
|
||||||
"size_y": 374,
|
|
||||||
"size_z": 0,
|
|
||||||
"category": "deck",
|
|
||||||
"sites": [
|
|
||||||
{
|
|
||||||
"label": "T1",
|
|
||||||
"visible": true,
|
|
||||||
"occupied_by": null,
|
|
||||||
"position": { "x": 0, "y": 0, "z": 0 },
|
|
||||||
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
|
||||||
"content_type": ["plate", "tip_rack", "tube_rack", "adaptor"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"data": {}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"edges": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Deck 节点要点:
|
|
||||||
|
|
||||||
- `config.type` 填 Deck 类名(如 `"PRCXI9300Deck"`)
|
|
||||||
- `config.sites` 完整列出所有 site(从 Deck 类的 `serialize()` 输出获取)
|
|
||||||
- `children` 初始为空(由同步器或手动初始化填充)
|
|
||||||
- 设备节点 `config.deck._resource_type` 指向 Deck 类的完整模块路径
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 子设备
|
|
||||||
|
|
||||||
子设备按标准设备接入流程创建(参见 add-device SKILL),使用 `@device` 装饰器。
|
|
||||||
|
|
||||||
子设备约束:
|
|
||||||
|
|
||||||
- 图文件中 `parent` 指向工作站 ID
|
|
||||||
- 在工作站 `children` 数组中列出
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 关键规则
|
|
||||||
|
|
||||||
1. **`__init__` 必须接受 `deck` 和 `**kwargs`** — `WorkstationBase.**init**`需要`deck` 参数
|
|
||||||
2. **Deck 通过 `config.deck._resource_type` 反序列化传入** — 不要在 `__init__` 中手动创建 Deck
|
|
||||||
3. **Deck 为空时自行初始化内容** — 在 `post_init` 中检查并填充默认物料
|
|
||||||
4. **外部同步实现 `ResourceSynchronizer`** — `sync_from_external` / `sync_to_external`
|
|
||||||
5. **通过 `self._children` 访问子设备** — 不要自行维护子设备引用
|
|
||||||
6. **`post_init` 中启动后台服务** — 不要在 `__init__` 中启动网络连接
|
|
||||||
7. **异步方法使用 `await self._ros_node.sleep()`** — 禁止 `time.sleep()` 和 `asyncio.sleep()`
|
|
||||||
8. **使用 `@not_action` 标记非动作方法** — `post_init`, `initialize`, `cleanup`
|
|
||||||
9. **子物料保证正确 serialize/deserialize** — 系统自动同步到前端 Deck 视图
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 验证
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 模块可导入
|
|
||||||
python -c "from unilabos.devices.workstation.<name>.<name> import <ClassName>"
|
|
||||||
|
|
||||||
# 启动测试(AST 自动扫描)
|
|
||||||
unilab -g <graph>.json
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 现有工作站参考
|
|
||||||
|
|
||||||
| 工作站 | 驱动类 | 类型 |
|
|
||||||
| -------------- | ----------------------------- | -------- |
|
|
||||||
| Protocol 通用 | `ProtocolNode` | Protocol |
|
|
||||||
| Bioyond 反应站 | `BioyondReactionStation` | 外部系统 |
|
|
||||||
| 纽扣电池组装 | `CoinCellAssemblyWorkstation` | 硬件控制 |
|
|
||||||
|
|
||||||
参考路径:`unilabos/devices/workstation/` 目录下各工作站实现。
|
|
||||||
@@ -1,371 +0,0 @@
|
|||||||
# 工作站高级模式参考
|
|
||||||
|
|
||||||
本文件是 SKILL.md 的补充,包含外部系统集成、物料同步、配置结构等高级模式。
|
|
||||||
Agent 在需要实现这些功能时按需阅读。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 外部系统集成模式
|
|
||||||
|
|
||||||
### 1.1 RPC 客户端
|
|
||||||
|
|
||||||
与外部 LIMS/MES 系统通信的标准模式。继承 `BaseRequest`,所有接口统一用 POST。
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.device_comms.rpc import BaseRequest
|
|
||||||
|
|
||||||
|
|
||||||
class MySystemRPC(BaseRequest):
|
|
||||||
"""外部系统 RPC 客户端"""
|
|
||||||
|
|
||||||
def __init__(self, host: str, api_key: str):
|
|
||||||
super().__init__(host)
|
|
||||||
self.api_key = api_key
|
|
||||||
|
|
||||||
def _request(self, endpoint: str, data: dict = None) -> dict:
|
|
||||||
return self.post(
|
|
||||||
url=f"{self.host}/api/{endpoint}",
|
|
||||||
params={
|
|
||||||
"apiKey": self.api_key,
|
|
||||||
"requestTime": self.get_current_time_iso8601(),
|
|
||||||
"data": data or {},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def query_status(self) -> dict:
|
|
||||||
return self._request("status/query")
|
|
||||||
|
|
||||||
def create_order(self, order_data: dict) -> dict:
|
|
||||||
return self._request("order/create", order_data)
|
|
||||||
```
|
|
||||||
|
|
||||||
参考:`unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py`(`BioyondV1RPC`)
|
|
||||||
|
|
||||||
### 1.2 HTTP 回调服务
|
|
||||||
|
|
||||||
接收外部系统报送的标准模式。使用 `WorkstationHTTPService`,在 `post_init` 中启动。
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
|
||||||
|
|
||||||
|
|
||||||
class MyWorkstation(WorkstationBase):
|
|
||||||
def __init__(self, config=None, deck=None, **kwargs):
|
|
||||||
super().__init__(deck=deck, **kwargs)
|
|
||||||
self.config = config or {}
|
|
||||||
http_cfg = self.config.get("http_service_config", {})
|
|
||||||
self._http_service_config = {
|
|
||||||
"host": http_cfg.get("http_service_host", "127.0.0.1"),
|
|
||||||
"port": http_cfg.get("http_service_port", 8080),
|
|
||||||
}
|
|
||||||
self.http_service = None
|
|
||||||
|
|
||||||
def post_init(self, ros_node):
|
|
||||||
super().post_init(ros_node)
|
|
||||||
self.http_service = WorkstationHTTPService(
|
|
||||||
workstation_instance=self,
|
|
||||||
host=self._http_service_config["host"],
|
|
||||||
port=self._http_service_config["port"],
|
|
||||||
)
|
|
||||||
self.http_service.start()
|
|
||||||
```
|
|
||||||
|
|
||||||
**HTTP 服务路由**(固定端点,由 `WorkstationHTTPHandler` 自动分发):
|
|
||||||
|
|
||||||
| 端点 | 调用的工作站方法 |
|
|
||||||
|------|-----------------|
|
|
||||||
| `/report/step_finish` | `process_step_finish_report(report_request)` |
|
|
||||||
| `/report/sample_finish` | `process_sample_finish_report(report_request)` |
|
|
||||||
| `/report/order_finish` | `process_order_finish_report(report_request, used_materials)` |
|
|
||||||
| `/report/material_change` | `process_material_change_report(report_data)` |
|
|
||||||
| `/report/error_handling` | `handle_external_error(error_data)` |
|
|
||||||
|
|
||||||
实现对应方法即可接收回调:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def process_step_finish_report(self, report_request) -> Dict[str, Any]:
|
|
||||||
"""处理步骤完成报告"""
|
|
||||||
step_name = report_request.data.get("stepName")
|
|
||||||
return {"success": True, "message": f"步骤 {step_name} 已处理"}
|
|
||||||
|
|
||||||
def process_order_finish_report(self, report_request, used_materials) -> Dict[str, Any]:
|
|
||||||
"""处理订单完成报告"""
|
|
||||||
order_code = report_request.data.get("orderCode")
|
|
||||||
return {"success": True}
|
|
||||||
```
|
|
||||||
|
|
||||||
参考:`unilabos/devices/workstation/workstation_http_service.py`
|
|
||||||
|
|
||||||
### 1.3 连接监控
|
|
||||||
|
|
||||||
独立线程周期性检测外部系统连接状态,状态变化时发布 ROS 事件。
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ConnectionMonitor:
|
|
||||||
def __init__(self, workstation, check_interval=30):
|
|
||||||
self.workstation = workstation
|
|
||||||
self.check_interval = check_interval
|
|
||||||
self._running = False
|
|
||||||
self._thread = None
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self._running = True
|
|
||||||
self._thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
|
||||||
self._thread.start()
|
|
||||||
|
|
||||||
def _monitor_loop(self):
|
|
||||||
while self._running:
|
|
||||||
try:
|
|
||||||
# 调用外部系统接口检测连接
|
|
||||||
self.workstation.hardware_interface.ping()
|
|
||||||
status = "online"
|
|
||||||
except Exception:
|
|
||||||
status = "offline"
|
|
||||||
time.sleep(self.check_interval)
|
|
||||||
```
|
|
||||||
|
|
||||||
参考:`unilabos/devices/workstation/bioyond_studio/station.py`(`ConnectionMonitor`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Config 结构模式
|
|
||||||
|
|
||||||
工作站的 `config` 在图文件中定义,传入 `__init__`。以下是常见字段模式:
|
|
||||||
|
|
||||||
### 2.1 外部系统连接
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"api_host": "http://192.168.1.100:8080",
|
|
||||||
"api_key": "YOUR_API_KEY"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 HTTP 回调服务
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"http_service_config": {
|
|
||||||
"http_service_host": "127.0.0.1",
|
|
||||||
"http_service_port": 8080
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 物料类型映射
|
|
||||||
|
|
||||||
将 PLR 资源类名映射到外部系统的物料类型(名称 + UUID)。用于双向物料转换。
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"material_type_mappings": {
|
|
||||||
"PLR_ResourceClassName": ["外部系统显示名", "external-type-uuid"],
|
|
||||||
"BIOYOND_PolymerStation_Reactor": ["反应器", "3a14233b-902d-0d7b-..."]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 仓库映射
|
|
||||||
|
|
||||||
将仓库名映射到外部系统的仓库 UUID 和库位 UUID。用于入库/出库操作。
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"warehouse_mapping": {
|
|
||||||
"仓库名": {
|
|
||||||
"uuid": "warehouse-uuid",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "site-uuid-A01",
|
|
||||||
"A02": "site-uuid-A02"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.5 工作流映射
|
|
||||||
|
|
||||||
将内部工作流名映射到外部系统的工作流 ID。
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"workflow_mappings": {
|
|
||||||
"internal_workflow_name": "external-workflow-uuid"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.6 物料默认参数
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"material_default_parameters": {
|
|
||||||
"NMP": {
|
|
||||||
"unit": "毫升",
|
|
||||||
"density": "1.03",
|
|
||||||
"densityUnit": "g/mL",
|
|
||||||
"description": "N-甲基吡咯烷酮"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 资源同步机制
|
|
||||||
|
|
||||||
### 3.1 ResourceSynchronizer
|
|
||||||
|
|
||||||
抽象基类,用于与外部物料系统双向同步。定义在 `workstation_base.py`。
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.devices.workstation.workstation_base import ResourceSynchronizer
|
|
||||||
|
|
||||||
|
|
||||||
class MyResourceSynchronizer(ResourceSynchronizer):
|
|
||||||
def __init__(self, workstation, api_client):
|
|
||||||
super().__init__(workstation)
|
|
||||||
self.api_client = api_client
|
|
||||||
|
|
||||||
def sync_from_external(self) -> bool:
|
|
||||||
"""从外部系统拉取物料到 deck"""
|
|
||||||
external_materials = self.api_client.list_materials()
|
|
||||||
for material in external_materials:
|
|
||||||
plr_resource = self._convert_to_plr(material)
|
|
||||||
self.workstation.deck.assign_child_resource(plr_resource, coordinate)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def sync_to_external(self, plr_resource) -> bool:
|
|
||||||
"""将 deck 中的物料变更推送到外部系统"""
|
|
||||||
external_data = self._convert_from_plr(plr_resource)
|
|
||||||
self.api_client.update_material(external_data)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def handle_external_change(self, change_info) -> bool:
|
|
||||||
"""处理外部系统推送的物料变更"""
|
|
||||||
return True
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 update_resource — 上传资源树到云端
|
|
||||||
|
|
||||||
将 PLR Deck 序列化后通过 ROS 服务上传。典型使用场景:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 在 post_init 中上传初始 deck
|
|
||||||
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
|
|
||||||
|
|
||||||
ROS2DeviceNode.run_async_func(
|
|
||||||
self._ros_node.update_resource, True,
|
|
||||||
**{"resources": [self.deck]}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 在动作方法中更新特定资源
|
|
||||||
ROS2DeviceNode.run_async_func(
|
|
||||||
self._ros_node.update_resource, True,
|
|
||||||
**{"resources": [updated_plate]}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 工作流序列管理
|
|
||||||
|
|
||||||
工作站通过 `workflow_sequence` 属性管理任务队列(JSON 字符串形式)。
|
|
||||||
|
|
||||||
```python
|
|
||||||
class MyWorkstation(WorkstationBase):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self._workflow_sequence = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def workflow_sequence(self) -> str:
|
|
||||||
"""返回 JSON 字符串,ROS 自动发布"""
|
|
||||||
import json
|
|
||||||
return json.dumps(self._workflow_sequence)
|
|
||||||
|
|
||||||
async def append_to_workflow_sequence(self, workflow_name: str) -> Dict[str, Any]:
|
|
||||||
"""添加工作流到队列"""
|
|
||||||
self._workflow_sequence.append({
|
|
||||||
"name": workflow_name,
|
|
||||||
"status": "pending",
|
|
||||||
"created_at": time.time(),
|
|
||||||
})
|
|
||||||
return {"success": True}
|
|
||||||
|
|
||||||
async def clear_workflows(self) -> Dict[str, Any]:
|
|
||||||
"""清空工作流队列"""
|
|
||||||
self._workflow_sequence = []
|
|
||||||
return {"success": True}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 站间物料转移
|
|
||||||
|
|
||||||
工作站之间转移物料的模式。通过 ROS ActionClient 调用目标站的动作。
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def transfer_materials_to_another_station(
|
|
||||||
self,
|
|
||||||
target_device_id: str,
|
|
||||||
transfer_groups: list,
|
|
||||||
**kwargs,
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""将物料转移到另一个工作站"""
|
|
||||||
target_node = self._children.get(target_device_id)
|
|
||||||
if not target_node:
|
|
||||||
# 通过 ROS 节点查找非子设备的目标站
|
|
||||||
pass
|
|
||||||
|
|
||||||
for group in transfer_groups:
|
|
||||||
resource = self.find_resource_by_name(group["resource_name"])
|
|
||||||
# 从本站 deck 移除
|
|
||||||
resource.unassign()
|
|
||||||
# 调用目标站的接收方法
|
|
||||||
# ...
|
|
||||||
|
|
||||||
return {"success": True, "transferred": len(transfer_groups)}
|
|
||||||
```
|
|
||||||
|
|
||||||
参考:`BioyondDispensingStation.transfer_materials_to_reaction_station`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. post_init 完整模式
|
|
||||||
|
|
||||||
`post_init` 是工作站初始化的关键阶段,此时 ROS 节点和子设备已就绪。
|
|
||||||
|
|
||||||
```python
|
|
||||||
def post_init(self, ros_node):
|
|
||||||
super().post_init(ros_node)
|
|
||||||
|
|
||||||
# 1. 初始化外部系统客户端(此时 config 已可用)
|
|
||||||
self.rpc_client = MySystemRPC(
|
|
||||||
host=self.config.get("api_host"),
|
|
||||||
api_key=self.config.get("api_key"),
|
|
||||||
)
|
|
||||||
self.hardware_interface = self.rpc_client
|
|
||||||
|
|
||||||
# 2. 启动连接监控
|
|
||||||
self.connection_monitor = ConnectionMonitor(self)
|
|
||||||
self.connection_monitor.start()
|
|
||||||
|
|
||||||
# 3. 启动 HTTP 回调服务
|
|
||||||
if hasattr(self, '_http_service_config'):
|
|
||||||
self.http_service = WorkstationHTTPService(
|
|
||||||
workstation_instance=self,
|
|
||||||
host=self._http_service_config["host"],
|
|
||||||
port=self._http_service_config["port"],
|
|
||||||
)
|
|
||||||
self.http_service.start()
|
|
||||||
|
|
||||||
# 4. 上传 deck 到云端
|
|
||||||
ROS2DeviceNode.run_async_func(
|
|
||||||
self._ros_node.update_resource, True,
|
|
||||||
**{"resources": [self.deck]}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 5. 初始化资源同步器(可选)
|
|
||||||
self.resource_synchronizer = MyResourceSynchronizer(self, self.rpc_client)
|
|
||||||
```
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
---
|
|
||||||
name: batch-insert-reagent
|
|
||||||
description: Batch insert reagents into Uni-Lab platform — add chemicals with CAS, SMILES, supplier info. Use when the user wants to add reagents, insert chemicals, batch register reagents, or mentions 录入试剂/添加试剂/试剂入库/reagent.
|
|
||||||
---
|
|
||||||
|
|
||||||
# 批量录入试剂 Skill
|
|
||||||
|
|
||||||
通过云端 API 批量录入试剂信息,支持逐条或批量操作。
|
|
||||||
|
|
||||||
## 前置条件(缺一不可)
|
|
||||||
|
|
||||||
使用本 skill 前,**必须**先确认以下信息。如果缺少任何一项,**立即向用户询问并终止**,等补齐后再继续。
|
|
||||||
|
|
||||||
### 1. ak / sk → AUTH
|
|
||||||
|
|
||||||
询问用户的启动参数,从 `--ak` `--sk` 或 config.py 中获取。
|
|
||||||
|
|
||||||
生成 AUTH token(任选一种方式):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 方式一:Python 一行生成
|
|
||||||
python -c "import base64,sys; print('Authorization: Lab ' + base64.b64encode(f'{sys.argv[1]}:{sys.argv[2]}'.encode()).decode())" <ak> <sk>
|
|
||||||
|
|
||||||
# 方式二:手动计算
|
|
||||||
# base64(ak:sk) → Authorization: Lab <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. --addr → BASE URL
|
|
||||||
|
|
||||||
| `--addr` 值 | BASE |
|
|
||||||
|-------------|------|
|
|
||||||
| `test` | `https://uni-lab.test.bohrium.com` |
|
|
||||||
| `uat` | `https://uni-lab.uat.bohrium.com` |
|
|
||||||
| `local` | `http://127.0.0.1:48197` |
|
|
||||||
| 不传(默认) | `https://uni-lab.bohrium.com` |
|
|
||||||
|
|
||||||
确认后设置:
|
|
||||||
```bash
|
|
||||||
BASE="<根据 addr 确定的 URL>"
|
|
||||||
AUTH="Authorization: Lab <gen_auth.py 输出的 token>"
|
|
||||||
```
|
|
||||||
|
|
||||||
**两项全部就绪后才可发起 API 请求。**
|
|
||||||
|
|
||||||
## Session State
|
|
||||||
|
|
||||||
- `lab_uuid` — 实验室 UUID(首次通过 API #1 自动获取,**不需要问用户**)
|
|
||||||
|
|
||||||
## 请求约定
|
|
||||||
|
|
||||||
所有请求使用 `curl -s`,POST 需加 `Content-Type: application/json`。
|
|
||||||
|
|
||||||
> **Windows 平台**必须使用 `curl.exe`(而非 PowerShell 的 `curl` 别名),示例中的 `curl` 均指 `curl.exe`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
### 1. 获取实验室信息(自动获取 lab_uuid)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -s -X GET "$BASE/api/v1/edge/lab/info" -H "$AUTH"
|
|
||||||
```
|
|
||||||
|
|
||||||
返回:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"code": 0, "data": {"uuid": "xxx", "name": "实验室名称"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
记住 `data.uuid` 为 `lab_uuid`。
|
|
||||||
|
|
||||||
### 2. 录入试剂
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -s -X POST "$BASE/api/v1/lab/reagent" \
|
|
||||||
-H "$AUTH" -H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"lab_uuid": "<lab_uuid>",
|
|
||||||
"cas": "<CAS号>",
|
|
||||||
"name": "<试剂名称>",
|
|
||||||
"molecular_formula": "<分子式>",
|
|
||||||
"smiles": "<SMILES>",
|
|
||||||
"stock_in_quantity": <入库数量>,
|
|
||||||
"unit": "<单位字符串>",
|
|
||||||
"supplier": "<供应商>",
|
|
||||||
"production_date": "<生产日期 ISO 8601>",
|
|
||||||
"expiry_date": "<过期日期 ISO 8601>"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
返回成功时包含试剂 UUID:
|
|
||||||
```json
|
|
||||||
{"code": 0, "data": {"uuid": "xxx", ...}}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 试剂字段说明
|
|
||||||
|
|
||||||
| 字段 | 类型 | 必填 | 说明 | 示例 |
|
|
||||||
|------|------|------|------|------|
|
|
||||||
| `lab_uuid` | string | 是 | 实验室 UUID(从 API #1 获取) | `"8511c672-..."` |
|
|
||||||
| `cas` | string | 是 | CAS 注册号 | `"7732-18-3"` |
|
|
||||||
| `name` | string | 是 | 试剂中文/英文名称 | `"水"` |
|
|
||||||
| `molecular_formula` | string | 是 | 分子式 | `"H2O"` |
|
|
||||||
| `smiles` | string | 是 | SMILES 表示 | `"O"` |
|
|
||||||
| `stock_in_quantity` | number | 是 | 入库数量 | `10` |
|
|
||||||
| `unit` | string | 是 | 单位(字符串,见下表) | `"mL"` |
|
|
||||||
| `supplier` | string | 否 | 供应商名称 | `"国药集团"` |
|
|
||||||
| `production_date` | string | 否 | 生产日期(ISO 8601) | `"2025-11-18T00:00:00Z"` |
|
|
||||||
| `expiry_date` | string | 否 | 过期日期(ISO 8601) | `"2026-11-18T00:00:00Z"` |
|
|
||||||
|
|
||||||
### unit 单位值
|
|
||||||
|
|
||||||
| 值 | 单位 |
|
|
||||||
|------|------|
|
|
||||||
| `"mL"` | 毫升 |
|
|
||||||
| `"L"` | 升 |
|
|
||||||
| `"g"` | 克 |
|
|
||||||
| `"kg"` | 千克 |
|
|
||||||
| `"瓶"` | 瓶 |
|
|
||||||
|
|
||||||
> 根据试剂状态选择:液体用 `"mL"` / `"L"`,固体用 `"g"` / `"kg"`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 批量录入策略
|
|
||||||
|
|
||||||
### 方式一:用户提供 JSON 数组
|
|
||||||
|
|
||||||
用户一次性给出多条试剂数据:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"cas": "7732-18-3", "name": "水", "molecular_formula": "H2O", "smiles": "O", "stock_in_quantity": 10, "unit": "mL"},
|
|
||||||
{"cas": "64-17-5", "name": "乙醇", "molecular_formula": "C2H6O", "smiles": "CCO", "stock_in_quantity": 5, "unit": "L"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Agent 自动为每条补充 `lab_uuid`、`production_date`、`expiry_date` 等字段后逐条提交。
|
|
||||||
|
|
||||||
Agent 循环调用 API #2 逐条录入,每条记录一次 API 调用。
|
|
||||||
|
|
||||||
### 方式二:用户逐个描述
|
|
||||||
|
|
||||||
用户口头描述试剂(如「帮我录入 500mL 的无水乙醇,Sigma 的」),agent 自行补全字段:
|
|
||||||
|
|
||||||
1. 根据名称查找 CAS 号、分子式、SMILES(参考下方速查表或自行推断)
|
|
||||||
2. 构建完整的请求体
|
|
||||||
3. 向用户确认后提交
|
|
||||||
|
|
||||||
### 方式三:从 CSV/表格批量导入
|
|
||||||
|
|
||||||
用户提供 CSV 或表格文件路径,agent 读取并解析:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 期望的 CSV 格式(首行为表头)
|
|
||||||
cas,name,molecular_formula,smiles,stock_in_quantity,unit,supplier,production_date,expiry_date
|
|
||||||
7732-18-3,水,H2O,O,10,mL,农夫山泉,2025-11-18T00:00:00Z,2026-11-18T00:00:00Z
|
|
||||||
```
|
|
||||||
|
|
||||||
### 执行与汇报
|
|
||||||
|
|
||||||
每次 API 调用后:
|
|
||||||
1. 检查返回 `code`(0 = 成功)
|
|
||||||
2. 记录成功/失败数量
|
|
||||||
3. 全部完成后汇总:「共录入 N 条试剂,成功 X 条,失败 Y 条」
|
|
||||||
4. 如有失败,列出失败的试剂名称和错误信息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 常见试剂速查表
|
|
||||||
|
|
||||||
| 名称 | CAS | 分子式 | SMILES |
|
|
||||||
|------|-----|--------|--------|
|
|
||||||
| 水 | 7732-18-3 | H2O | O |
|
|
||||||
| 乙醇 | 64-17-5 | C2H6O | CCO |
|
|
||||||
| 甲醇 | 67-56-1 | CH4O | CO |
|
|
||||||
| 丙酮 | 67-64-1 | C3H6O | CC(C)=O |
|
|
||||||
| 二甲基亚砜(DMSO) | 67-68-5 | C2H6OS | CS(C)=O |
|
|
||||||
| 乙酸乙酯 | 141-78-6 | C4H8O2 | CCOC(C)=O |
|
|
||||||
| 二氯甲烷 | 75-09-2 | CH2Cl2 | ClCCl |
|
|
||||||
| 四氢呋喃(THF) | 109-99-9 | C4H8O | C1CCOC1 |
|
|
||||||
| N,N-二甲基甲酰胺(DMF) | 68-12-2 | C3H7NO | CN(C)C=O |
|
|
||||||
| 氯仿 | 67-66-3 | CHCl3 | ClC(Cl)Cl |
|
|
||||||
| 乙腈 | 75-05-8 | C2H3N | CC#N |
|
|
||||||
| 甲苯 | 108-88-3 | C7H8 | Cc1ccccc1 |
|
|
||||||
| 正己烷 | 110-54-3 | C6H14 | CCCCCC |
|
|
||||||
| 异丙醇 | 67-63-0 | C3H8O | CC(C)O |
|
|
||||||
| 盐酸 | 7647-01-0 | HCl | Cl |
|
|
||||||
| 硫酸 | 7664-93-9 | H2SO4 | OS(O)(=O)=O |
|
|
||||||
| 氢氧化钠 | 1310-73-2 | NaOH | [Na]O |
|
|
||||||
| 碳酸钠 | 497-19-8 | Na2CO3 | [Na]OC([O-])=O.[Na+] |
|
|
||||||
| 氯化钠 | 7647-14-5 | NaCl | [Na]Cl |
|
|
||||||
| 乙二胺四乙酸(EDTA) | 60-00-4 | C10H16N2O8 | OC(=O)CN(CCN(CC(O)=O)CC(O)=O)CC(O)=O |
|
|
||||||
|
|
||||||
> 此表仅供快速参考。对于不在表中的试剂,agent 应根据化学知识推断或提示用户补充。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 完整工作流 Checklist
|
|
||||||
|
|
||||||
```
|
|
||||||
Task Progress:
|
|
||||||
- [ ] Step 1: 确认 ak/sk → 生成 AUTH token
|
|
||||||
- [ ] Step 2: 确认 --addr → 设置 BASE URL
|
|
||||||
- [ ] Step 3: GET /edge/lab/info → 获取 lab_uuid
|
|
||||||
- [ ] Step 4: 收集试剂信息(用户提供列表/逐个描述/CSV文件)
|
|
||||||
- [ ] Step 5: 补全缺失字段(CAS、分子式、SMILES 等)
|
|
||||||
- [ ] Step 6: 向用户确认待录入的试剂列表
|
|
||||||
- [ ] Step 7: 循环调用 POST /lab/reagent 逐条录入(每条需含 lab_uuid)
|
|
||||||
- [ ] Step 8: 汇总结果(成功/失败数量及详情)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 完整示例
|
|
||||||
|
|
||||||
用户说:「帮我录入 3 种试剂:500mL 无水乙醇、1kg 氯化钠、2L 去离子水」
|
|
||||||
|
|
||||||
Agent 构建的请求序列:
|
|
||||||
|
|
||||||
```json
|
|
||||||
// 第 1 条
|
|
||||||
{"lab_uuid": "8511c672-...", "cas": "64-17-5", "name": "无水乙醇", "molecular_formula": "C2H6O", "smiles": "CCO", "stock_in_quantity": 500, "unit": "mL", "supplier": "国药集团", "production_date": "2025-01-01T00:00:00Z", "expiry_date": "2026-01-01T00:00:00Z"}
|
|
||||||
|
|
||||||
// 第 2 条
|
|
||||||
{"lab_uuid": "8511c672-...", "cas": "7647-14-5", "name": "氯化钠", "molecular_formula": "NaCl", "smiles": "[Na]Cl", "stock_in_quantity": 1, "unit": "kg", "supplier": "", "production_date": "2025-01-01T00:00:00Z", "expiry_date": "2026-01-01T00:00:00Z"}
|
|
||||||
|
|
||||||
// 第 3 条
|
|
||||||
{"lab_uuid": "8511c672-...", "cas": "7732-18-3", "name": "去离子水", "molecular_formula": "H2O", "smiles": "O", "stock_in_quantity": 2, "unit": "L", "supplier": "", "production_date": "2025-01-01T00:00:00Z", "expiry_date": "2026-01-01T00:00:00Z"}
|
|
||||||
```
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
---
|
|
||||||
name: create-device-skill
|
|
||||||
description: Create a skill for any Uni-Lab device by extracting action schemas from the device registry. Use when the user wants to create a new device skill, add device API documentation, or set up action schemas for a device.
|
|
||||||
---
|
|
||||||
|
|
||||||
# 创建设备 Skill 指南
|
|
||||||
|
|
||||||
本 meta-skill 教你如何为任意 Uni-Lab-OS 设备创建完整的 API 操作技能(参考 `unilab-device-api` 的成功案例)。
|
|
||||||
|
|
||||||
## 数据源
|
|
||||||
|
|
||||||
- **设备注册表**: `unilabos_data/req_device_registry_upload.json`
|
|
||||||
- **结构**: `{ "resources": [{ "id": "<device_id>", "class": { "module": "<python_module:ClassName>", "action_value_mappings": { ... } } }] }`
|
|
||||||
- **生成时机**: `unilab` 启动并完成注册表上传后自动生成
|
|
||||||
- **module 字段**: 格式 `unilabos.devices.xxx.yyy:ClassName`,可转为源码路径 `unilabos/devices/xxx/yyy.py`,阅读源码可了解参数含义和设备行为
|
|
||||||
|
|
||||||
## 创建流程
|
|
||||||
|
|
||||||
### Step 0 — 收集必备信息(缺一不可,否则询问后终止)
|
|
||||||
|
|
||||||
开始前**必须**确认以下 4 项信息全部就绪。如果用户未提供任何一项,**立即询问并终止当前流程**,等用户补齐后再继续。
|
|
||||||
|
|
||||||
向用户提问:「请提供你的 unilab 启动参数,我需要以下信息:」
|
|
||||||
|
|
||||||
#### 必备项 ①:ak / sk(认证凭据)
|
|
||||||
|
|
||||||
来源:启动命令的 `--ak` `--sk` 参数,或 config.py 中的 `ak = "..."` `sk = "..."`。
|
|
||||||
|
|
||||||
获取后立即生成 AUTH token:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python ./scripts/gen_auth.py <ak> <sk>
|
|
||||||
# 或从 config.py 提取
|
|
||||||
python ./scripts/gen_auth.py --config <config.py>
|
|
||||||
```
|
|
||||||
|
|
||||||
认证算法:`base64(ak:sk)` → `Authorization: Lab <token>`
|
|
||||||
|
|
||||||
#### 必备项 ②:--addr(目标环境)
|
|
||||||
|
|
||||||
决定 API 请求发往哪个服务器。从启动命令的 `--addr` 参数获取:
|
|
||||||
|
|
||||||
| `--addr` 值 | BASE URL |
|
|
||||||
|-------------|----------|
|
|
||||||
| `test` | `https://uni-lab.test.bohrium.com` |
|
|
||||||
| `uat` | `https://uni-lab.uat.bohrium.com` |
|
|
||||||
| `local` | `http://127.0.0.1:48197` |
|
|
||||||
| 不传(默认) | `https://uni-lab.bohrium.com` |
|
|
||||||
| 其他自定义 URL | 直接使用该 URL |
|
|
||||||
|
|
||||||
#### 必备项 ③:req_device_registry_upload.json(设备注册表)
|
|
||||||
|
|
||||||
数据文件由 `unilab` 启动时自动生成,需要定位它:
|
|
||||||
|
|
||||||
**推断 working_dir**(即 `unilabos_data` 所在目录):
|
|
||||||
|
|
||||||
| 条件 | working_dir 取值 |
|
|
||||||
|------|------------------|
|
|
||||||
| 传了 `--working_dir` | `<working_dir>/unilabos_data/`(若子目录已存在则直接用) |
|
|
||||||
| 仅传了 `--config` | `<config 文件所在目录>/unilabos_data/` |
|
|
||||||
| 都没传 | `<当前工作目录>/unilabos_data/` |
|
|
||||||
|
|
||||||
**按优先级搜索文件**:
|
|
||||||
|
|
||||||
```
|
|
||||||
<推断的 working_dir>/unilabos_data/req_device_registry_upload.json
|
|
||||||
<推断的 working_dir>/req_device_registry_upload.json
|
|
||||||
<workspace 根目录>/unilabos_data/req_device_registry_upload.json
|
|
||||||
```
|
|
||||||
|
|
||||||
也可以直接 Glob 搜索:`**/req_device_registry_upload.json`
|
|
||||||
|
|
||||||
找到后**必须检查文件修改时间**并告知用户:「找到注册表文件 `<路径>`,生成于 `<时间>`。请确认这是最近一次启动生成的。」超过 1 天提醒用户是否需要重新启动 `unilab`。
|
|
||||||
|
|
||||||
**如果文件不存在** → 告知用户先运行 `unilab` 启动命令,等日志出现 `注册表响应数据已保存` 后再执行本流程。**终止。**
|
|
||||||
|
|
||||||
#### 必备项 ④:目标设备
|
|
||||||
|
|
||||||
用户需要明确要为哪个设备创建 skill。可以是设备名称(如「PRCXI 移液站」)或 device_id(如 `liquid_handler.prcxi`)。
|
|
||||||
|
|
||||||
如果用户不确定,运行提取脚本列出所有设备供选择:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python ./scripts/extract_device_actions.py --registry <找到的文件路径>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 完整示例
|
|
||||||
|
|
||||||
用户提供:
|
|
||||||
|
|
||||||
```
|
|
||||||
--ak a1fd9d4e-xxxx-xxxx-xxxx-d9a69c09f0fd
|
|
||||||
--sk 136ff5c6-xxxx-xxxx-xxxx-a03e301f827b
|
|
||||||
--addr test
|
|
||||||
--port 8003
|
|
||||||
--disable_browser
|
|
||||||
```
|
|
||||||
|
|
||||||
从中提取:
|
|
||||||
- ✅ ak/sk → 运行 `gen_auth.py` 得到 `AUTH="Authorization: Lab YTFmZDlk..."`
|
|
||||||
- ✅ addr=test → `BASE=https://uni-lab.test.bohrium.com`
|
|
||||||
- ✅ 搜索 `unilabos_data/req_device_registry_upload.json` → 找到并确认时间
|
|
||||||
- ✅ 用户指明目标设备 → 如 `liquid_handler.prcxi`
|
|
||||||
|
|
||||||
**四项全部就绪后才进入 Step 1。**
|
|
||||||
|
|
||||||
### Step 1 — 列出可用设备
|
|
||||||
|
|
||||||
运行提取脚本,列出所有设备及 action 数量和 Python 源码路径,让用户选择:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 自动搜索(默认在 unilabos_data/ 和当前目录查找)
|
|
||||||
python ./scripts/extract_device_actions.py
|
|
||||||
|
|
||||||
# 指定注册表文件路径
|
|
||||||
python ./scripts/extract_device_actions.py --registry <path/to/req_device_registry_upload.json>
|
|
||||||
```
|
|
||||||
|
|
||||||
脚本输出包含每个设备的 **Python 源码路径**(从 `class.module` 转换),可用于后续阅读源码理解参数含义。
|
|
||||||
|
|
||||||
### Step 2 — 提取 Action Schema
|
|
||||||
|
|
||||||
用户选择设备后,运行提取脚本:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python ./scripts/extract_device_actions.py [--registry <path>] <device_id> ./skills/<skill-name>/actions/
|
|
||||||
```
|
|
||||||
|
|
||||||
脚本会显示设备的 Python 源码路径和类名,方便阅读源码了解参数含义。
|
|
||||||
|
|
||||||
每个 action 生成一个 JSON 文件,包含:
|
|
||||||
- `type` — 作为 API 调用的 `action_type`
|
|
||||||
- `schema` — 完整 JSON Schema(含 `properties.goal.properties` 参数定义)
|
|
||||||
- `goal` — goal 字段映射(含占位符 `$placeholder`)
|
|
||||||
- `goal_default` — 默认值
|
|
||||||
|
|
||||||
### Step 3 — 写 action-index.md
|
|
||||||
|
|
||||||
按模板为每个 action 写条目:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
### `<action_name>`
|
|
||||||
|
|
||||||
<用途描述(一句话)>
|
|
||||||
|
|
||||||
- **Schema**: [`actions/<filename>.json`](actions/<filename>.json)
|
|
||||||
- **核心参数**: `param1`, `param2`(从 schema.required 获取)
|
|
||||||
- **可选参数**: `param3`, `param4`
|
|
||||||
- **占位符字段**: `field`(需填入物料信息,值以 `$` 开头)
|
|
||||||
```
|
|
||||||
|
|
||||||
描述规则:
|
|
||||||
- 从 `schema.properties` 读参数列表(schema 已提升为 goal 内容)
|
|
||||||
- 从 `schema.required` 区分核心/可选参数
|
|
||||||
- 按功能分类(移液、枪头、外设等)
|
|
||||||
- 标注 `placeholder_keys` 中的字段类型:
|
|
||||||
- `unilabos_resources` → **ResourceSlot**,填入 `{id, name, uuid}`(id 是路径格式,从资源树取物料节点)
|
|
||||||
- `unilabos_devices` → **DeviceSlot**,填入路径字符串如 `"/host_node"`(从资源树筛选 type=device)
|
|
||||||
- `unilabos_nodes` → **NodeSlot**,填入路径字符串如 `"/PRCXI/PRCXI_Deck"`(资源树中任意节点)
|
|
||||||
- `unilabos_class` → **ClassSlot**,填入类名字符串如 `"container"`(从注册表查找)
|
|
||||||
- array 类型字段 → `[{id, name, uuid}, ...]`
|
|
||||||
- 特殊:`create_resource` 的 `res_id`(ResourceSlot)可填不存在的路径
|
|
||||||
|
|
||||||
### Step 4 — 写 SKILL.md
|
|
||||||
|
|
||||||
直接复用 `unilab-device-api` 的 API 模板(10 个 endpoint),修改:
|
|
||||||
- 设备名称
|
|
||||||
- Action 数量
|
|
||||||
- 目录列表
|
|
||||||
- Session state 中的 `device_name`
|
|
||||||
- **AUTH 头** — 使用 Step 0 中 `gen_auth.py` 生成的 `Authorization: Lab <token>`(不要硬编码 `Api` 类型的 key)
|
|
||||||
- **Python 源码路径** — 在 SKILL.md 开头注明设备对应的源码文件,方便参考参数含义
|
|
||||||
- **Slot 字段表** — 列出本设备哪些 action 的哪些字段需要填入 Slot(物料/设备/节点/类名)
|
|
||||||
|
|
||||||
API 模板结构:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## 设备信息
|
|
||||||
- device_id, Python 源码路径, 设备类名
|
|
||||||
|
|
||||||
## 前置条件(缺一不可)
|
|
||||||
- ak/sk → AUTH, --addr → BASE URL
|
|
||||||
|
|
||||||
## Session State
|
|
||||||
- lab_uuid(通过 API #1 自动匹配,不要问用户), device_name
|
|
||||||
|
|
||||||
## API Endpoints (10 个)
|
|
||||||
# 注意:
|
|
||||||
# - #1 获取 lab 列表 + 自动匹配 lab_uuid(遍历 is_admin 的 lab,
|
|
||||||
# 调用 /lab/info/{uuid} 比对 access_key == ak)
|
|
||||||
# - #2 创建工作流用 POST /lab/workflow
|
|
||||||
# - #10 获取资源树路径含 lab_uuid: /lab/material/download/{lab_uuid}
|
|
||||||
|
|
||||||
## Placeholder Slot 填写规则
|
|
||||||
- unilabos_resources → ResourceSlot → {"id":"/path/name","name":"name","uuid":"xxx"}
|
|
||||||
- unilabos_devices → DeviceSlot → "/parent/device" 路径字符串
|
|
||||||
- unilabos_nodes → NodeSlot → "/parent/node" 路径字符串
|
|
||||||
- unilabos_class → ClassSlot → "class_name" 字符串
|
|
||||||
- 特例:create_resource 的 res_id 允许填不存在的路径
|
|
||||||
- 列出本设备所有 Slot 字段、类型及含义
|
|
||||||
|
|
||||||
## 渐进加载策略
|
|
||||||
## 完整工作流 Checklist
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5 — 验证
|
|
||||||
|
|
||||||
检查文件完整性:
|
|
||||||
- [ ] `SKILL.md` 包含 10 个 API endpoint
|
|
||||||
- [ ] `SKILL.md` 包含 Placeholder Slot 填写规则(ResourceSlot / DeviceSlot / NodeSlot / ClassSlot + create_resource 特例)和本设备的 Slot 字段表
|
|
||||||
- [ ] `action-index.md` 列出所有 action 并有描述
|
|
||||||
- [ ] `actions/` 目录中每个 action 有对应 JSON 文件
|
|
||||||
- [ ] JSON 文件包含 `type`, `schema`(已提升为 goal 内容), `goal`, `goal_default`, `placeholder_keys` 字段
|
|
||||||
- [ ] 描述能让 agent 判断该用哪个 action
|
|
||||||
|
|
||||||
## Action JSON 文件结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "LiquidHandlerTransfer", // → API 的 action_type
|
|
||||||
"goal": { // goal 字段映射
|
|
||||||
"sources": "sources",
|
|
||||||
"targets": "targets",
|
|
||||||
"tip_racks": "tip_racks",
|
|
||||||
"asp_vols": "asp_vols"
|
|
||||||
},
|
|
||||||
"schema": { // ← 直接是 goal 的 schema(已提升)
|
|
||||||
"type": "object",
|
|
||||||
"properties": { // 参数定义(即请求中 goal 的字段)
|
|
||||||
"sources": { "type": "array", "items": { "type": "object" } },
|
|
||||||
"targets": { "type": "array", "items": { "type": "object" } },
|
|
||||||
"asp_vols": { "type": "array", "items": { "type": "number" } }
|
|
||||||
},
|
|
||||||
"required": [...],
|
|
||||||
"_unilabos_placeholder_info": { // ← Slot 类型标记
|
|
||||||
"sources": "unilabos_resources",
|
|
||||||
"targets": "unilabos_resources",
|
|
||||||
"tip_racks": "unilabos_resources"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"goal_default": { ... }, // 默认值
|
|
||||||
"placeholder_keys": { // ← 汇总所有 Slot 字段
|
|
||||||
"sources": "unilabos_resources", // ResourceSlot
|
|
||||||
"targets": "unilabos_resources",
|
|
||||||
"tip_racks": "unilabos_resources",
|
|
||||||
"target_device_id": "unilabos_devices" // DeviceSlot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **注意**:`schema` 已由脚本从原始 `schema.properties.goal` 提升为顶层,直接包含参数定义。
|
|
||||||
> `schema.properties` 中的字段即为 API 请求 `param.goal` 中的字段。
|
|
||||||
|
|
||||||
## Placeholder Slot 类型体系
|
|
||||||
|
|
||||||
`placeholder_keys` / `_unilabos_placeholder_info` 中有 4 种值,对应不同的填写方式:
|
|
||||||
|
|
||||||
| placeholder 值 | Slot 类型 | 填写格式 | 选取范围 |
|
|
||||||
|---------------|-----------|---------|---------|
|
|
||||||
| `unilabos_resources` | ResourceSlot | `{"id": "/path/name", "name": "name", "uuid": "xxx"}` | 仅**物料**节点(不含设备) |
|
|
||||||
| `unilabos_devices` | DeviceSlot | `"/parent/device_name"` | 仅**设备**节点(type=device),路径字符串 |
|
|
||||||
| `unilabos_nodes` | NodeSlot | `"/parent/node_name"` | **设备 + 物料**,即所有节点,路径字符串 |
|
|
||||||
| `unilabos_class` | ClassSlot | `"class_name"` | 注册表中已上报的资源类 name |
|
|
||||||
|
|
||||||
### ResourceSlot(`unilabos_resources`)
|
|
||||||
|
|
||||||
最常见的类型。从资源树中选取**物料**节点(孔板、枪头盒、试剂槽等):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"id": "/workstation/container1", "name": "container1", "uuid": "ff149a9a-2cb8-419d-8db5-d3ba056fb3c2"}
|
|
||||||
```
|
|
||||||
|
|
||||||
- 单个(schema type=object):`{"id": "/path/name", "name": "name", "uuid": "xxx"}`
|
|
||||||
- 数组(schema type=array):`[{"id": "/path/a", "name": "a", "uuid": "xxx"}, ...]`
|
|
||||||
- `id` 本身是从 parent 计算的路径格式
|
|
||||||
- 根据 action 语义选择正确的物料(如 `sources` = 液体来源,`targets` = 目标位置)
|
|
||||||
|
|
||||||
> **特例**:`create_resource` 的 `res_id` 字段,目标物料可能**尚不存在**,此时直接填写期望的路径(如 `"/workstation/container1"`),不需要 uuid。
|
|
||||||
|
|
||||||
### DeviceSlot(`unilabos_devices`)
|
|
||||||
|
|
||||||
填写**设备路径字符串**。从资源树中筛选 type=device 的节点,从 parent 计算路径:
|
|
||||||
|
|
||||||
```
|
|
||||||
"/host_node"
|
|
||||||
"/bioyond_cell/reaction_station"
|
|
||||||
```
|
|
||||||
|
|
||||||
- 只填路径字符串,不需要 `{id, uuid}` 对象
|
|
||||||
- 根据 action 语义选择正确的设备(如 `target_device_id` = 目标设备)
|
|
||||||
|
|
||||||
### NodeSlot(`unilabos_nodes`)
|
|
||||||
|
|
||||||
范围 = 设备 + 物料。即资源树中**所有节点**都可以选,填写**路径字符串**:
|
|
||||||
|
|
||||||
```
|
|
||||||
"/PRCXI/PRCXI_Deck"
|
|
||||||
```
|
|
||||||
|
|
||||||
- 使用场景:当参数既可能指向物料也可能指向设备时(如 `PumpTransferProtocol` 的 `from_vessel`/`to_vessel`,`create_resource` 的 `parent`)
|
|
||||||
|
|
||||||
### ClassSlot(`unilabos_class`)
|
|
||||||
|
|
||||||
填写注册表中已上报的**资源类 name**。从本地 `req_resource_registry_upload.json` 中查找:
|
|
||||||
|
|
||||||
```
|
|
||||||
"container"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 通过 API #10 获取资源树
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -s -X GET "$BASE/api/v1/lab/material/download/$lab_uuid" -H "$AUTH"
|
|
||||||
```
|
|
||||||
|
|
||||||
注意 `lab_uuid` 在路径中(不是查询参数)。资源树返回所有节点,每个节点包含 `id`(路径格式)、`name`、`uuid`、`type`、`parent` 等字段。填写 Slot 时需根据 placeholder 类型筛选正确的节点。
|
|
||||||
|
|
||||||
## 最终目录结构
|
|
||||||
|
|
||||||
```
|
|
||||||
./<skill-name>/
|
|
||||||
├── SKILL.md # API 端点 + 渐进加载指引
|
|
||||||
├── action-index.md # 动作索引:描述/用途/核心参数
|
|
||||||
└── actions/ # 每个 action 的完整 JSON Schema
|
|
||||||
├── action1.json
|
|
||||||
├── action2.json
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
从 req_device_registry_upload.json 中提取指定设备的 action schema。
|
|
||||||
|
|
||||||
用法:
|
|
||||||
# 列出所有设备及 action 数量(自动搜索注册表文件)
|
|
||||||
python extract_device_actions.py
|
|
||||||
|
|
||||||
# 指定注册表文件路径
|
|
||||||
python extract_device_actions.py --registry <path/to/req_device_registry_upload.json>
|
|
||||||
|
|
||||||
# 提取指定设备的 action 到目录
|
|
||||||
python extract_device_actions.py <device_id> <output_dir>
|
|
||||||
python extract_device_actions.py --registry <path> <device_id> <output_dir>
|
|
||||||
|
|
||||||
示例:
|
|
||||||
python extract_device_actions.py --registry unilabos_data/req_device_registry_upload.json
|
|
||||||
python extract_device_actions.py liquid_handler.prcxi .cursor/skills/unilab-device-api/actions/
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
REGISTRY_FILENAME = "req_device_registry_upload.json"
|
|
||||||
|
|
||||||
def find_registry(explicit_path=None):
|
|
||||||
"""
|
|
||||||
查找 req_device_registry_upload.json 文件。
|
|
||||||
|
|
||||||
搜索优先级:
|
|
||||||
1. 用户通过 --registry 显式指定的路径
|
|
||||||
2. <cwd>/unilabos_data/req_device_registry_upload.json
|
|
||||||
3. <cwd>/req_device_registry_upload.json
|
|
||||||
4. <script所在目录>/../../.. (workspace根) 下的 unilabos_data/
|
|
||||||
5. 向上逐级搜索父目录(最多 5 层)
|
|
||||||
"""
|
|
||||||
if explicit_path:
|
|
||||||
if os.path.isfile(explicit_path):
|
|
||||||
return explicit_path
|
|
||||||
if os.path.isdir(explicit_path):
|
|
||||||
fp = os.path.join(explicit_path, REGISTRY_FILENAME)
|
|
||||||
if os.path.isfile(fp):
|
|
||||||
return fp
|
|
||||||
print(f"警告: 指定的路径不存在: {explicit_path}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
candidates = [
|
|
||||||
os.path.join("unilabos_data", REGISTRY_FILENAME),
|
|
||||||
REGISTRY_FILENAME,
|
|
||||||
]
|
|
||||||
|
|
||||||
for c in candidates:
|
|
||||||
if os.path.isfile(c):
|
|
||||||
return c
|
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
workspace_root = os.path.normpath(os.path.join(script_dir, "..", "..", ".."))
|
|
||||||
for c in candidates:
|
|
||||||
path = os.path.join(workspace_root, c)
|
|
||||||
if os.path.isfile(path):
|
|
||||||
return path
|
|
||||||
|
|
||||||
cwd = os.getcwd()
|
|
||||||
for _ in range(5):
|
|
||||||
parent = os.path.dirname(cwd)
|
|
||||||
if parent == cwd:
|
|
||||||
break
|
|
||||||
cwd = parent
|
|
||||||
for c in candidates:
|
|
||||||
path = os.path.join(cwd, c)
|
|
||||||
if os.path.isfile(path):
|
|
||||||
return path
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def load_registry(path):
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
return json.load(f)
|
|
||||||
|
|
||||||
def list_devices(data):
|
|
||||||
"""列出所有包含 action_value_mappings 的设备,同时返回 module 路径"""
|
|
||||||
resources = data.get('resources', [])
|
|
||||||
devices = []
|
|
||||||
for res in resources:
|
|
||||||
rid = res.get('id', '')
|
|
||||||
cls = res.get('class', {})
|
|
||||||
avm = cls.get('action_value_mappings', {})
|
|
||||||
module = cls.get('module', '')
|
|
||||||
if avm:
|
|
||||||
devices.append((rid, len(avm), module))
|
|
||||||
return devices
|
|
||||||
|
|
||||||
def flatten_schema_to_goal(action_data):
|
|
||||||
"""将 schema 中嵌套的 goal 内容提升为顶层 schema,去掉 feedback/result 包装"""
|
|
||||||
schema = action_data.get('schema', {})
|
|
||||||
goal_schema = schema.get('properties', {}).get('goal', {})
|
|
||||||
if goal_schema:
|
|
||||||
action_data = dict(action_data)
|
|
||||||
action_data['schema'] = goal_schema
|
|
||||||
return action_data
|
|
||||||
|
|
||||||
|
|
||||||
def extract_actions(data, device_id, output_dir):
|
|
||||||
"""提取指定设备的 action schema 到独立 JSON 文件"""
|
|
||||||
resources = data.get('resources', [])
|
|
||||||
for res in resources:
|
|
||||||
if res.get('id') == device_id:
|
|
||||||
cls = res.get('class', {})
|
|
||||||
module = cls.get('module', '')
|
|
||||||
avm = cls.get('action_value_mappings', {})
|
|
||||||
if not avm:
|
|
||||||
print(f"设备 {device_id} 没有 action_value_mappings")
|
|
||||||
return []
|
|
||||||
|
|
||||||
if module:
|
|
||||||
py_path = module.split(":")[0].replace(".", "/") + ".py"
|
|
||||||
class_name = module.split(":")[-1] if ":" in module else ""
|
|
||||||
print(f"Python 源码: {py_path}")
|
|
||||||
if class_name:
|
|
||||||
print(f"设备类: {class_name}")
|
|
||||||
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
written = []
|
|
||||||
for action_name in sorted(avm.keys()):
|
|
||||||
action_data = flatten_schema_to_goal(avm[action_name])
|
|
||||||
filename = action_name.replace('-', '_') + '.json'
|
|
||||||
filepath = os.path.join(output_dir, filename)
|
|
||||||
with open(filepath, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(action_data, f, indent=2, ensure_ascii=False)
|
|
||||||
written.append(filename)
|
|
||||||
print(f" {filepath}")
|
|
||||||
return written
|
|
||||||
|
|
||||||
print(f"设备 {device_id} 未找到")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = sys.argv[1:]
|
|
||||||
explicit_registry = None
|
|
||||||
|
|
||||||
if "--registry" in args:
|
|
||||||
idx = args.index("--registry")
|
|
||||||
if idx + 1 < len(args):
|
|
||||||
explicit_registry = args[idx + 1]
|
|
||||||
args = args[:idx] + args[idx + 2:]
|
|
||||||
else:
|
|
||||||
print("错误: --registry 需要指定路径")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
registry_path = find_registry(explicit_registry)
|
|
||||||
if not registry_path:
|
|
||||||
print(f"错误: 找不到 {REGISTRY_FILENAME}")
|
|
||||||
print()
|
|
||||||
print("解决方法:")
|
|
||||||
print(" 1. 先运行 unilab 启动命令,等待注册表生成")
|
|
||||||
print(" 2. 用 --registry 指定文件路径:")
|
|
||||||
print(f" python {sys.argv[0]} --registry <path/to/{REGISTRY_FILENAME}>")
|
|
||||||
print()
|
|
||||||
print("搜索过的路径:")
|
|
||||||
for p in [
|
|
||||||
os.path.join("unilabos_data", REGISTRY_FILENAME),
|
|
||||||
REGISTRY_FILENAME,
|
|
||||||
os.path.join("<workspace_root>", "unilabos_data", REGISTRY_FILENAME),
|
|
||||||
]:
|
|
||||||
print(f" - {p}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print(f"注册表: {registry_path}")
|
|
||||||
mtime = os.path.getmtime(registry_path)
|
|
||||||
gen_time = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
size_mb = os.path.getsize(registry_path) / (1024 * 1024)
|
|
||||||
print(f"生成时间: {gen_time} (文件大小: {size_mb:.1f} MB)")
|
|
||||||
data = load_registry(registry_path)
|
|
||||||
|
|
||||||
if len(args) == 0:
|
|
||||||
devices = list_devices(data)
|
|
||||||
print(f"\n找到 {len(devices)} 个设备:")
|
|
||||||
print(f"{'设备 ID':<50} {'Actions':>7} {'Python 模块'}")
|
|
||||||
print("-" * 120)
|
|
||||||
for did, count, module in sorted(devices, key=lambda x: x[0]):
|
|
||||||
py_path = module.split(":")[0].replace(".", "/") + ".py" if module else ""
|
|
||||||
print(f"{did:<50} {count:>7} {py_path}")
|
|
||||||
|
|
||||||
elif len(args) == 2:
|
|
||||||
device_id = args[0]
|
|
||||||
output_dir = args[1]
|
|
||||||
print(f"\n提取 {device_id} 的 actions 到 {output_dir}/")
|
|
||||||
written = extract_actions(data, device_id, output_dir)
|
|
||||||
if written:
|
|
||||||
print(f"\n共写入 {len(written)} 个 action 文件")
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("用法:")
|
|
||||||
print(" python extract_device_actions.py [--registry <path>] # 列出设备")
|
|
||||||
print(" python extract_device_actions.py [--registry <path>] <device_id> <dir> # 提取 actions")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
从 ak/sk 生成 UniLab API Authorization header。
|
|
||||||
|
|
||||||
算法: base64(ak:sk) → "Authorization: Lab <token>"
|
|
||||||
|
|
||||||
用法:
|
|
||||||
python gen_auth.py <ak> <sk>
|
|
||||||
python gen_auth.py --config <config.py>
|
|
||||||
|
|
||||||
示例:
|
|
||||||
python gen_auth.py myak mysk
|
|
||||||
python gen_auth.py --config experiments/config.py
|
|
||||||
"""
|
|
||||||
import base64
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def gen_auth(ak: str, sk: str) -> str:
|
|
||||||
token = base64.b64encode(f"{ak}:{sk}".encode("utf-8")).decode("utf-8")
|
|
||||||
return token
|
|
||||||
|
|
||||||
|
|
||||||
def extract_from_config(config_path: str) -> tuple:
|
|
||||||
"""从 config.py 中提取 ak 和 sk"""
|
|
||||||
with open(config_path, "r", encoding="utf-8") as f:
|
|
||||||
content = f.read()
|
|
||||||
ak_match = re.search(r'''ak\s*=\s*["']([^"']+)["']''', content)
|
|
||||||
sk_match = re.search(r'''sk\s*=\s*["']([^"']+)["']''', content)
|
|
||||||
if not ak_match or not sk_match:
|
|
||||||
return None, None
|
|
||||||
return ak_match.group(1), sk_match.group(1)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = sys.argv[1:]
|
|
||||||
|
|
||||||
if len(args) == 2 and args[0] == "--config":
|
|
||||||
ak, sk = extract_from_config(args[1])
|
|
||||||
if not ak or not sk:
|
|
||||||
print(f"错误: 在 {args[1]} 中未找到 ak/sk 配置")
|
|
||||||
print("期望格式: ak = \"xxx\" sk = \"xxx\"")
|
|
||||||
sys.exit(1)
|
|
||||||
print(f"配置文件: {args[1]}")
|
|
||||||
elif len(args) == 2:
|
|
||||||
ak, sk = args
|
|
||||||
else:
|
|
||||||
print("用法:")
|
|
||||||
print(" python gen_auth.py <ak> <sk>")
|
|
||||||
print(" python gen_auth.py --config <config.py>")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
token = gen_auth(ak, sk)
|
|
||||||
print(f"ak: {ak}")
|
|
||||||
print(f"sk: {sk}")
|
|
||||||
print()
|
|
||||||
print(f"Authorization header:")
|
|
||||||
print(f" Authorization: Lab {token}")
|
|
||||||
print()
|
|
||||||
print(f"curl 用法:")
|
|
||||||
print(f' curl -H "Authorization: Lab {token}" ...')
|
|
||||||
print()
|
|
||||||
print(f"Shell 变量:")
|
|
||||||
print(f' AUTH="Authorization: Lab {token}"')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
2
.github/workflows/ci-check.yml
vendored
2
.github/workflows/ci-check.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
|||||||
uv pip uninstall enum34 || echo enum34 not installed, skipping
|
uv pip uninstall enum34 || echo enum34 not installed, skipping
|
||||||
uv pip install .
|
uv pip install .
|
||||||
|
|
||||||
- name: Run check mode (AST registry validation)
|
- name: Run check mode (complete_registry)
|
||||||
run: |
|
run: |
|
||||||
call conda activate check-env
|
call conda activate check-env
|
||||||
echo Running check mode...
|
echo Running check mode...
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,7 +5,6 @@ output/
|
|||||||
unilabos_data/
|
unilabos_data/
|
||||||
pyrightconfig.json
|
pyrightconfig.json
|
||||||
.cursorignore
|
.cursorignore
|
||||||
device_package*/
|
|
||||||
## Python
|
## Python
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
|||||||
87
AGENTS.md
87
AGENTS.md
@@ -1,87 +0,0 @@
|
|||||||
# AGENTS.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
Also follow the monorepo-level rules in `../AGENTS.md`.
|
|
||||||
|
|
||||||
## Build & Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install in editable mode (requires mamba env with python 3.11)
|
|
||||||
pip install -e .
|
|
||||||
uv pip install -r unilabos/utils/requirements.txt
|
|
||||||
|
|
||||||
# Run with a device graph
|
|
||||||
unilab --graph <graph.json> --config <config.py> --backend ros
|
|
||||||
unilab --graph <graph.json> --config <config.py> --backend simple # no ROS2 needed
|
|
||||||
|
|
||||||
# Common CLI flags
|
|
||||||
unilab --app_bridges websocket fastapi # communication bridges
|
|
||||||
unilab --test_mode # simulate hardware, no real execution
|
|
||||||
unilab --check_mode # CI validation of registry imports
|
|
||||||
unilab --skip_env_check # skip auto-install of dependencies
|
|
||||||
unilab --visual rviz|web|disable # visualization mode
|
|
||||||
unilab --is_slave # run as slave node
|
|
||||||
|
|
||||||
# Workflow upload subcommand
|
|
||||||
unilab workflow_upload -f <workflow.json> -n <name> --tags tag1 tag2
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
pytest tests/ # all tests
|
|
||||||
pytest tests/resources/test_resourcetreeset.py # single test file
|
|
||||||
pytest tests/resources/test_resourcetreeset.py::TestClassName::test_method # single test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Startup Flow
|
|
||||||
|
|
||||||
`unilab` CLI → `unilabos/app/main.py:main()` → loads config → builds registry → reads device graph (JSON/GraphML) → starts backend thread (ROS2/simple) → starts FastAPI web server + WebSocket client.
|
|
||||||
|
|
||||||
### Core Layers
|
|
||||||
|
|
||||||
**Registry** (`unilabos/registry/`): Singleton `Registry` class discovers and catalogs all device types, resource types, and communication devices from YAML definitions. Device types live in `registry/devices/*.yaml`, resources in `registry/resources/`, comms in `registry/device_comms/`. The registry resolves class paths to actual Python classes via `utils/import_manager.py`.
|
|
||||||
|
|
||||||
**Resource Tracking** (`unilabos/resources/resource_tracker.py`): Pydantic-based `ResourceDict` → `ResourceDictInstance` → `ResourceTreeSet` hierarchy. `ResourceTreeSet` is the canonical in-memory representation of all devices and resources, used throughout the system. Graph I/O is in `resources/graphio.py` (reads JSON/GraphML device topology files into `nx.Graph` + `ResourceTreeSet`).
|
|
||||||
|
|
||||||
**Device Drivers** (`unilabos/devices/`): 30+ hardware drivers organized by device type (liquid_handling, hplc, balance, arm, etc.). Each driver is a Python class that gets wrapped by `ros/device_node_wrapper.py:ros2_device_node()` to become a ROS2 node with publishers, subscribers, and action servers.
|
|
||||||
|
|
||||||
**ROS2 Layer** (`unilabos/ros/`): `device_node_wrapper.py` dynamically wraps any device class into `ROS2DeviceNode` (defined in `ros/nodes/base_device_node.py`). Preset node types in `ros/nodes/presets/` include `host_node`, `controller_node`, `workstation`, `serial_node`, `camera`. Messages use custom `unilabos_msgs` (pre-built, distributed via releases).
|
|
||||||
|
|
||||||
**Protocol Compilation** (`unilabos/compile/`): 20+ protocol compilers (add, centrifuge, dissolve, filter, heatchill, stir, pump, etc.) that transform YAML protocol definitions into executable sequences.
|
|
||||||
|
|
||||||
**Communication** (`unilabos/device_comms/`): Hardware communication adapters — OPC-UA client, Modbus PLC, RPC, and a universal driver. `app/communication.py` provides a factory pattern for WebSocket client connections to the cloud.
|
|
||||||
|
|
||||||
**Web/API** (`unilabos/app/web/`): FastAPI server with REST API (`api.py`), Jinja2 template pages (`pages.py`), and HTTP client for cloud communication (`client.py`). Runs on port 8002 by default.
|
|
||||||
|
|
||||||
### Configuration System
|
|
||||||
|
|
||||||
- **Config classes** in `unilabos/config/config.py`: `BasicConfig`, `WSConfig`, `HTTPConfig`, `ROSConfig` — all class-level attributes, loaded from Python config files
|
|
||||||
- Config files are `.py` files with matching class names (see `config/example_config.py`)
|
|
||||||
- Environment variables override with prefix `UNILABOS_` (e.g., `UNILABOS_BASICCONFIG_PORT=9000`)
|
|
||||||
- Device topology defined in graph files (JSON with node-link format, or GraphML)
|
|
||||||
|
|
||||||
### Key Data Flow
|
|
||||||
|
|
||||||
1. Graph file → `graphio.read_node_link_json()` → `(nx.Graph, ResourceTreeSet, resource_links)`
|
|
||||||
2. `ResourceTreeSet` + `Registry` → `initialize_device.initialize_device_from_dict()` → `ROS2DeviceNode` instances
|
|
||||||
3. Device nodes communicate via ROS2 topics/actions or direct Python calls (simple backend)
|
|
||||||
4. Cloud sync via WebSocket (`app/ws_client.py`) and HTTP (`app/web/client.py`)
|
|
||||||
|
|
||||||
### Test Data
|
|
||||||
|
|
||||||
Example device graphs and experiment configs are in `unilabos/test/experiments/` (not `tests/`). Registry test fixtures in `unilabos/test/registry/`.
|
|
||||||
|
|
||||||
## Code Conventions
|
|
||||||
|
|
||||||
- Code comments and log messages in simplified Chinese
|
|
||||||
- Python 3.11+, type hints expected
|
|
||||||
- Pydantic models for data validation (`resource_tracker.py`)
|
|
||||||
- Singleton pattern via `@singleton` decorator (`utils/decorator.py`)
|
|
||||||
- Dynamic class loading via `utils/import_manager.py` — device classes resolved at runtime from registry YAML paths
|
|
||||||
- CLI argument dashes auto-converted to underscores for consistency
|
|
||||||
|
|
||||||
## Licensing
|
|
||||||
|
|
||||||
- Framework code: GPL-3.0
|
|
||||||
- Device drivers (`unilabos/devices/`): DP Technology Proprietary License — do not redistribute
|
|
||||||
@@ -15,9 +15,6 @@ Python 类设备驱动在完成注册表后可以直接在 Uni-Lab 中使用,
|
|||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from unilabos.registry.decorators import device, topic_config
|
|
||||||
|
|
||||||
@device(id="mock_gripper", category=["gripper"], description="Mock Gripper")
|
|
||||||
class MockGripper:
|
class MockGripper:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._position: float = 0.0
|
self._position: float = 0.0
|
||||||
@@ -26,23 +23,19 @@ class MockGripper:
|
|||||||
self._status = "Idle"
|
self._status = "Idle"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config() # 添加 @topic_config 才会定时广播
|
|
||||||
def position(self) -> float:
|
def position(self) -> float:
|
||||||
return self._position
|
return self._position
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def velocity(self) -> float:
|
def velocity(self) -> float:
|
||||||
return self._velocity
|
return self._velocity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def torque(self) -> float:
|
def torque(self) -> float:
|
||||||
return self._torque
|
return self._torque
|
||||||
|
|
||||||
# 使用 @topic_config 装饰的属性,接入 Uni-Lab 时会定时对外广播
|
# 会被自动识别的设备属性,接入 Uni-Lab 时会定时对外广播
|
||||||
@property
|
@property
|
||||||
@topic_config(period=2.0) # 可自定义发布周期
|
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
@@ -156,7 +149,7 @@ my_device: # 设备唯一标识符
|
|||||||
|
|
||||||
系统会自动分析您的 Python 驱动类并生成:
|
系统会自动分析您的 Python 驱动类并生成:
|
||||||
|
|
||||||
- `status_types`:从 `@topic_config` 装饰的 `@property` 或方法自动识别状态属性
|
- `status_types`:从 `@property` 装饰的方法自动识别状态属性
|
||||||
- `action_value_mappings`:从类方法自动生成动作映射
|
- `action_value_mappings`:从类方法自动生成动作映射
|
||||||
- `init_param_schema`:从 `__init__` 方法分析初始化参数
|
- `init_param_schema`:从 `__init__` 方法分析初始化参数
|
||||||
- `schema`:前端显示用的属性类型定义
|
- `schema`:前端显示用的属性类型定义
|
||||||
@@ -186,9 +179,7 @@ Uni-Lab 设备驱动是一个 Python 类,需要遵循以下结构:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from unilabos.registry.decorators import device, topic_config
|
|
||||||
|
|
||||||
@device(id="my_device", category=["general"], description="My Device")
|
|
||||||
class MyDevice:
|
class MyDevice:
|
||||||
"""设备类文档字符串
|
"""设备类文档字符串
|
||||||
|
|
||||||
@@ -207,9 +198,8 @@ class MyDevice:
|
|||||||
# 初始化硬件连接
|
# 初始化硬件连接
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config() # 必须添加 @topic_config 才会广播
|
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
"""设备状态(通过 @topic_config 广播)"""
|
"""设备状态(会自动广播)"""
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
def my_action(self, param: float) -> Dict[str, Any]:
|
def my_action(self, param: float) -> Dict[str, Any]:
|
||||||
@@ -227,61 +217,34 @@ class MyDevice:
|
|||||||
|
|
||||||
## 状态属性 vs 动作方法
|
## 状态属性 vs 动作方法
|
||||||
|
|
||||||
### 状态属性(@property + @topic_config)
|
### 状态属性(@property)
|
||||||
|
|
||||||
状态属性需要同时使用 `@property` 和 `@topic_config` 装饰器才会被识别并定期广播:
|
状态属性会被自动识别并定期广播:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from unilabos.registry.decorators import topic_config
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config() # 必须添加,否则不会广播
|
|
||||||
def temperature(self) -> float:
|
def temperature(self) -> float:
|
||||||
"""当前温度"""
|
"""当前温度"""
|
||||||
return self._read_temperature()
|
return self._read_temperature()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config(period=2.0) # 可自定义发布周期(秒)
|
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
"""设备状态: idle, running, error"""
|
"""设备状态: idle, running, error"""
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config(name="ready") # 可自定义发布名称
|
|
||||||
def is_ready(self) -> bool:
|
def is_ready(self) -> bool:
|
||||||
"""设备是否就绪"""
|
"""设备是否就绪"""
|
||||||
return self._status == "idle"
|
return self._status == "idle"
|
||||||
```
|
```
|
||||||
|
|
||||||
也可以使用普通方法(非 @property)配合 `@topic_config`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
@topic_config(period=10.0)
|
|
||||||
def get_sensor_data(self) -> Dict[str, float]:
|
|
||||||
"""获取传感器数据(get_ 前缀会自动去除,发布名为 sensor_data)"""
|
|
||||||
return {"temp": self._temp, "humidity": self._humidity}
|
|
||||||
```
|
|
||||||
|
|
||||||
**`@topic_config` 参数**:
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| `period` | float | 5.0 | 发布周期(秒) |
|
|
||||||
| `print_publish` | bool | 节点默认 | 是否打印发布日志 |
|
|
||||||
| `qos` | int | 10 | QoS 深度 |
|
|
||||||
| `name` | str | None | 自定义发布名称 |
|
|
||||||
|
|
||||||
**发布名称优先级**:`@topic_config(name=...)` > `get_` 前缀去除 > 方法名
|
|
||||||
|
|
||||||
**特点**:
|
**特点**:
|
||||||
|
|
||||||
- 必须使用 `@topic_config` 装饰器
|
- 使用`@property`装饰器
|
||||||
- 支持 `@property` 和普通方法
|
- 只读,不能有参数
|
||||||
- 添加到注册表的 `status_types`
|
- 自动添加到注册表的`status_types`
|
||||||
- 定期发布到 ROS2 topic
|
- 定期发布到 ROS2 topic
|
||||||
|
|
||||||
> **⚠️ 重要:** 仅有 `@property` 装饰器而没有 `@topic_config` 的属性**不会**被广播。这是一个 Breaking Change。
|
|
||||||
|
|
||||||
### 动作方法
|
### 动作方法
|
||||||
|
|
||||||
动作方法是设备可以执行的操作:
|
动作方法是设备可以执行的操作:
|
||||||
@@ -534,7 +497,6 @@ class LiquidHandler:
|
|||||||
self._status = "idle"
|
self._status = "idle"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
@@ -924,52 +886,7 @@ class MyDevice:
|
|||||||
|
|
||||||
## 最佳实践
|
## 最佳实践
|
||||||
|
|
||||||
### 1. 使用 `@device` 装饰器标识设备类
|
### 1. 类型注解
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import device
|
|
||||||
|
|
||||||
@device(id="my_device", category=["heating"], description="My Heating Device", icon="heater.webp")
|
|
||||||
class MyDevice:
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
- `id`:设备唯一标识符,用于注册表匹配
|
|
||||||
- `category`:分类列表,前端用于分组显示
|
|
||||||
- `description`:设备描述
|
|
||||||
- `icon`:图标文件名(可选)
|
|
||||||
|
|
||||||
### 2. 使用 `@topic_config` 声明需要广播的状态
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import topic_config
|
|
||||||
|
|
||||||
# ✓ @property + @topic_config → 会广播
|
|
||||||
@property
|
|
||||||
@topic_config(period=2.0)
|
|
||||||
def temperature(self) -> float:
|
|
||||||
return self._temp
|
|
||||||
|
|
||||||
# ✓ 普通方法 + @topic_config → 会广播(get_ 前缀自动去除)
|
|
||||||
@topic_config(period=10.0)
|
|
||||||
def get_sensor_data(self) -> Dict[str, float]:
|
|
||||||
return {"temp": self._temp}
|
|
||||||
|
|
||||||
# ✓ 使用 name 参数自定义发布名称
|
|
||||||
@property
|
|
||||||
@topic_config(name="ready")
|
|
||||||
def is_ready(self) -> bool:
|
|
||||||
return self._status == "idle"
|
|
||||||
|
|
||||||
# ✗ 仅有 @property,没有 @topic_config → 不会广播
|
|
||||||
@property
|
|
||||||
def internal_state(self) -> str:
|
|
||||||
return self._state
|
|
||||||
```
|
|
||||||
|
|
||||||
> **注意:** 与 `@property` 连用时,`@topic_config` 必须放在 `@property` 下面。
|
|
||||||
|
|
||||||
### 3. 类型注解
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from typing import Dict, Any, Optional, List
|
from typing import Dict, Any, Optional, List
|
||||||
@@ -984,7 +901,7 @@ def method(
|
|||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 文档字符串
|
### 2. 文档字符串
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def method(self, param: float) -> Dict[str, Any]:
|
def method(self, param: float) -> Dict[str, Any]:
|
||||||
@@ -1006,7 +923,7 @@ def method(self, param: float) -> Dict[str, Any]:
|
|||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 配置验证
|
### 3. 配置验证
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def __init__(self, config: Dict[str, Any]):
|
def __init__(self, config: Dict[str, Any]):
|
||||||
@@ -1020,7 +937,7 @@ def __init__(self, config: Dict[str, Any]):
|
|||||||
self.baudrate = config['baudrate']
|
self.baudrate = config['baudrate']
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. 资源清理
|
### 4. 资源清理
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
@@ -1029,7 +946,7 @@ def __del__(self):
|
|||||||
self.connection.close()
|
self.connection.close()
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. 设计前端友好的返回值
|
### 5. 设计前端友好的返回值
|
||||||
|
|
||||||
**记住:返回值会直接显示在 Web 界面**
|
**记住:返回值会直接显示在 Web 界面**
|
||||||
|
|
||||||
|
|||||||
@@ -422,20 +422,18 @@ placeholder_keys:
|
|||||||
|
|
||||||
### status_types
|
### status_types
|
||||||
|
|
||||||
系统会扫描你的 Python 类,从带有 `@topic_config` 装饰器的 `@property` 或方法自动生成这部分:
|
系统会扫描你的 Python 类,从状态方法(property 或 get\_方法)自动生成这部分:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
status_types:
|
status_types:
|
||||||
current_temperature: float # 从 @topic_config 装饰的 @property 或方法
|
current_temperature: float # 从 get_current_temperature() 或 @property current_temperature
|
||||||
is_heating: bool
|
is_heating: bool # 从 get_is_heating() 或 @property is_heating
|
||||||
status: str
|
status: str # 从 get_status() 或 @property status
|
||||||
```
|
```
|
||||||
|
|
||||||
**注意事项**:
|
**注意事项**:
|
||||||
|
|
||||||
- 仅有带 `@topic_config` 装饰器的 `@property` 或方法才会被识别为状态属性
|
- 系统会查找所有 `get_` 开头的方法和 `@property` 装饰的属性
|
||||||
- 没有 `@topic_config` 的 `@property` 不会生成 status_types,也不会广播
|
|
||||||
- `get_` 前缀的方法名会自动去除前缀(如 `get_temperature` → `temperature`)
|
|
||||||
- 类型会自动转成相应的类型(如 `str`、`float`、`bool`)
|
- 类型会自动转成相应的类型(如 `str`、`float`、`bool`)
|
||||||
- 如果类型是 `Any`、`None` 或未知的,默认使用 `String`
|
- 如果类型是 `Any`、`None` 或未知的,默认使用 `String`
|
||||||
|
|
||||||
@@ -539,13 +537,11 @@ class AdvancedLiquidHandler:
|
|||||||
self._temperature = 25.0
|
self._temperature = 25.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
"""设备状态"""
|
"""设备状态"""
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def temperature(self) -> float:
|
def temperature(self) -> float:
|
||||||
"""当前温度"""
|
"""当前温度"""
|
||||||
return self._temperature
|
return self._temperature
|
||||||
@@ -813,23 +809,21 @@ my_temperature_controller:
|
|||||||
你的设备类需要符合以下要求:
|
你的设备类需要符合以下要求:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from unilabos.registry.decorators import device, topic_config
|
from unilabos.common.device_base import DeviceBase
|
||||||
|
|
||||||
@device(id="my_device", category=["temperature"], description="My Device")
|
class MyDevice(DeviceBase):
|
||||||
class MyDevice:
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
"""初始化,参数会自动分析到 init_param_schema.config"""
|
"""初始化,参数会自动分析到 init_param_schema.config"""
|
||||||
|
super().__init__(config)
|
||||||
self.port = config.get('port', '/dev/ttyUSB0')
|
self.port = config.get('port', '/dev/ttyUSB0')
|
||||||
|
|
||||||
# 状态方法(必须添加 @topic_config 才会生成到 status_types 并广播)
|
# 状态方法(会自动生成到 status_types)
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def status(self):
|
def status(self):
|
||||||
"""返回设备状态"""
|
"""返回设备状态"""
|
||||||
return "idle"
|
return "idle"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def temperature(self):
|
def temperature(self):
|
||||||
"""返回当前温度"""
|
"""返回当前温度"""
|
||||||
return 25.0
|
return 25.0
|
||||||
@@ -1045,34 +1039,7 @@ resource.type # "resource"
|
|||||||
|
|
||||||
### 代码规范
|
### 代码规范
|
||||||
|
|
||||||
1. **使用 `@device` 装饰器标识设备类**
|
1. **始终使用类型注解**
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import device
|
|
||||||
|
|
||||||
@device(id="my_device", category=["heating"], description="My Device")
|
|
||||||
class MyDevice:
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **使用 `@topic_config` 声明广播属性**
|
|
||||||
|
|
||||||
```python
|
|
||||||
from unilabos.registry.decorators import topic_config
|
|
||||||
|
|
||||||
# ✓ 需要广播的状态属性
|
|
||||||
@property
|
|
||||||
@topic_config(period=2.0)
|
|
||||||
def temperature(self) -> float:
|
|
||||||
return self._temp
|
|
||||||
|
|
||||||
# ✗ 仅有 @property 不会广播
|
|
||||||
@property
|
|
||||||
def internal_counter(self) -> int:
|
|
||||||
return self._counter
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **始终使用类型注解**
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# ✓ 好
|
# ✓ 好
|
||||||
@@ -1084,7 +1051,7 @@ def method(self, resource, device):
|
|||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **提供有意义的参数名**
|
2. **提供有意义的参数名**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# ✓ 好 - 清晰的参数名
|
# ✓ 好 - 清晰的参数名
|
||||||
@@ -1096,7 +1063,7 @@ def transfer(self, r1: ResourceSlot, r2: ResourceSlot):
|
|||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **使用 Optional 表示可选参数**
|
3. **使用 Optional 表示可选参数**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@@ -1109,7 +1076,7 @@ def method(
|
|||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **添加详细的文档字符串**
|
4. **添加详细的文档字符串**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def method(
|
def method(
|
||||||
@@ -1129,13 +1096,13 @@ def method(
|
|||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
7. **方法命名规范**
|
5. **方法命名规范**
|
||||||
|
|
||||||
- 状态方法使用 `@property` + `@topic_config` 装饰器,或普通方法 + `@topic_config`
|
- 状态方法使用 `@property` 装饰器或 `get_` 前缀
|
||||||
- 动作方法使用动词开头
|
- 动作方法使用动词开头
|
||||||
- 保持命名清晰、一致
|
- 保持命名清晰、一致
|
||||||
|
|
||||||
8. **完善的错误处理**
|
6. **完善的错误处理**
|
||||||
- 实现完善的错误处理
|
- 实现完善的错误处理
|
||||||
- 添加日志记录
|
- 添加日志记录
|
||||||
- 提供有意义的错误信息
|
- 提供有意义的错误信息
|
||||||
|
|||||||
@@ -221,10 +221,10 @@ Laboratory A Laboratory B
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 实验室A
|
# 实验室A
|
||||||
unilab --ak your_ak --sk your_sk --upload_registry
|
unilab --ak your_ak --sk your_sk --upload_registry --use_remote_resource
|
||||||
|
|
||||||
# 实验室B
|
# 实验室B
|
||||||
unilab --ak your_ak --sk your_sk --upload_registry
|
unilab --ak your_ak --sk your_sk --upload_registry --use_remote_resource
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ options:
|
|||||||
--is_slave Run the backend as slave node (without host privileges).
|
--is_slave Run the backend as slave node (without host privileges).
|
||||||
--slave_no_host Skip waiting for host service in slave mode
|
--slave_no_host Skip waiting for host service in slave mode
|
||||||
--upload_registry Upload registry information when starting unilab
|
--upload_registry Upload registry information when starting unilab
|
||||||
|
--use_remote_resource Use remote resources when starting unilab
|
||||||
--config CONFIG Configuration file path, supports .py format Python config files
|
--config CONFIG Configuration file path, supports .py format Python config files
|
||||||
--port PORT Port for web service information page
|
--port PORT Port for web service information page
|
||||||
--disable_browser Disable opening information page on startup
|
--disable_browser Disable opening information page on startup
|
||||||
@@ -84,7 +85,7 @@ Uni-Lab 的启动过程分为以下几个阶段:
|
|||||||
支持两种方式:
|
支持两种方式:
|
||||||
|
|
||||||
- **本地文件**:使用 `-g` 指定图谱文件(支持 JSON 和 GraphML 格式)
|
- **本地文件**:使用 `-g` 指定图谱文件(支持 JSON 和 GraphML 格式)
|
||||||
- **远程资源**:不指定本地文件即可
|
- **远程资源**:使用 `--use_remote_resource` 从云端获取
|
||||||
|
|
||||||
### 7. 注册表构建
|
### 7. 注册表构建
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ unilab --config path/to/your/config.py
|
|||||||
unilab --ak your_ak --sk your_sk -g path/to/graph.json --upload_registry
|
unilab --ak your_ak --sk your_sk -g path/to/graph.json --upload_registry
|
||||||
|
|
||||||
# 使用远程资源启动
|
# 使用远程资源启动
|
||||||
unilab --ak your_ak --sk your_sk
|
unilab --ak your_ak --sk your_sk --use_remote_resource
|
||||||
|
|
||||||
# 更新注册表
|
# 更新注册表
|
||||||
unilab --ak your_ak --sk your_sk --complete_registry
|
unilab --ak your_ak --sk your_sk --complete_registry
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: ros-humble-unilabos-msgs
|
name: ros-humble-unilabos-msgs
|
||||||
version: 0.10.19
|
version: 0.10.18
|
||||||
source:
|
source:
|
||||||
path: ../../unilabos_msgs
|
path: ../../unilabos_msgs
|
||||||
target_directory: src
|
target_directory: src
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: unilabos
|
name: unilabos
|
||||||
version: "0.10.19"
|
version: "0.10.18"
|
||||||
|
|
||||||
source:
|
source:
|
||||||
path: ../..
|
path: ../..
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=package_name,
|
name=package_name,
|
||||||
version='0.10.19',
|
version='0.10.18',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['setuptools'],
|
install_requires=['setuptools'],
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.10.19"
|
__version__ = "0.10.18"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -26,84 +25,6 @@ from unilabos.config.config import load_config, BasicConfig, HTTPConfig
|
|||||||
_restart_requested: bool = False
|
_restart_requested: bool = False
|
||||||
_restart_reason: str = ""
|
_restart_reason: str = ""
|
||||||
|
|
||||||
RESTART_EXIT_CODE = 42
|
|
||||||
|
|
||||||
|
|
||||||
def _build_child_argv():
|
|
||||||
"""Build sys.argv for child process, stripping supervisor-only arguments."""
|
|
||||||
result = []
|
|
||||||
skip_next = False
|
|
||||||
for arg in sys.argv:
|
|
||||||
if skip_next:
|
|
||||||
skip_next = False
|
|
||||||
continue
|
|
||||||
if arg in ("--restart_mode", "--restart-mode"):
|
|
||||||
continue
|
|
||||||
if arg in ("--auto_restart_count", "--auto-restart-count"):
|
|
||||||
skip_next = True
|
|
||||||
continue
|
|
||||||
if arg.startswith("--auto_restart_count=") or arg.startswith("--auto-restart-count="):
|
|
||||||
continue
|
|
||||||
result.append(arg)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _run_as_supervisor(max_restarts: int):
|
|
||||||
"""
|
|
||||||
Supervisor process that spawns and monitors child processes.
|
|
||||||
|
|
||||||
Similar to Uvicorn's --reload: the supervisor itself does no heavy work,
|
|
||||||
it only launches the real process as a child and restarts it when the child
|
|
||||||
exits with RESTART_EXIT_CODE.
|
|
||||||
"""
|
|
||||||
child_argv = [sys.executable] + _build_child_argv()
|
|
||||||
restart_count = 0
|
|
||||||
|
|
||||||
print_status(
|
|
||||||
f"[Supervisor] Restart mode enabled (max restarts: {max_restarts}), "
|
|
||||||
f"child command: {' '.join(child_argv)}",
|
|
||||||
"info",
|
|
||||||
)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
print_status(
|
|
||||||
f"[Supervisor] Launching process (restart {restart_count}/{max_restarts})...",
|
|
||||||
"info",
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
process = subprocess.Popen(child_argv)
|
|
||||||
exit_code = process.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print_status("[Supervisor] Interrupted, terminating child process...", "info")
|
|
||||||
process.terminate()
|
|
||||||
try:
|
|
||||||
process.wait(timeout=10)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
process.kill()
|
|
||||||
process.wait()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if exit_code == RESTART_EXIT_CODE:
|
|
||||||
restart_count += 1
|
|
||||||
if restart_count > max_restarts:
|
|
||||||
print_status(
|
|
||||||
f"[Supervisor] Maximum restart count ({max_restarts}) reached, exiting",
|
|
||||||
"warning",
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
print_status(
|
|
||||||
f"[Supervisor] Child requested restart ({restart_count}/{max_restarts}), restarting in 2s...",
|
|
||||||
"info",
|
|
||||||
)
|
|
||||||
time.sleep(2)
|
|
||||||
else:
|
|
||||||
if exit_code != 0:
|
|
||||||
print_status(f"[Supervisor] Child exited with code {exit_code}", "warning")
|
|
||||||
else:
|
|
||||||
print_status("[Supervisor] Child exited normally", "info")
|
|
||||||
sys.exit(exit_code)
|
|
||||||
|
|
||||||
|
|
||||||
def load_config_from_file(config_path):
|
def load_config_from_file(config_path):
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
@@ -145,13 +66,6 @@ def parse_args():
|
|||||||
action="append",
|
action="append",
|
||||||
help="Path to the registry directory",
|
help="Path to the registry directory",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--devices",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
action="append",
|
|
||||||
help="Path to Python code directory for AST-based device/resource scanning",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--working_dir",
|
"--working_dir",
|
||||||
type=str,
|
type=str,
|
||||||
@@ -241,18 +155,18 @@ def parse_args():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Skip environment dependency check on startup",
|
help="Skip environment dependency check on startup",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--complete_registry",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Complete registry information",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--check_mode",
|
"--check_mode",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help="Run in check mode for CI: validates registry imports and ensures no file changes",
|
help="Run in check mode for CI: validates registry imports and ensures no file changes",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--complete_registry",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="Complete and rewrite YAML registry files using AST analysis results",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--no_update_feedback",
|
"--no_update_feedback",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -264,30 +178,6 @@ def parse_args():
|
|||||||
default=False,
|
default=False,
|
||||||
help="Test mode: all actions simulate execution and return mock results without running real hardware",
|
help="Test mode: all actions simulate execution and return mock results without running real hardware",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--external_devices_only",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="Only load external device packages (--devices), skip built-in unilabos/devices/ scanning and YAML device registry",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--extra_resource",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="Load extra lab_ prefixed labware resources (529 auto-generated definitions from lab_resources.py)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--restart_mode",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="Enable supervisor mode: automatically restart the process when triggered via WebSocket",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--auto_restart_count",
|
|
||||||
type=int,
|
|
||||||
default=500,
|
|
||||||
help="Maximum number of automatic restarts in restart mode (default: 500)",
|
|
||||||
)
|
|
||||||
# workflow upload subcommand
|
# workflow upload subcommand
|
||||||
workflow_parser = subparsers.add_parser(
|
workflow_parser = subparsers.add_parser(
|
||||||
"workflow_upload",
|
"workflow_upload",
|
||||||
@@ -338,28 +228,16 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
args_dict = vars(args)
|
args_dict = vars(args)
|
||||||
|
|
||||||
# Supervisor mode: spawn child processes and monitor for restart
|
|
||||||
if args_dict.get("restart_mode", False):
|
|
||||||
_run_as_supervisor(args_dict.get("auto_restart_count", 5))
|
|
||||||
return
|
|
||||||
|
|
||||||
# 环境检查 - 检查并自动安装必需的包 (可选)
|
# 环境检查 - 检查并自动安装必需的包 (可选)
|
||||||
skip_env_check = args_dict.get("skip_env_check", False)
|
skip_env_check = args_dict.get("skip_env_check", False)
|
||||||
check_mode = args_dict.get("check_mode", False)
|
check_mode = args_dict.get("check_mode", False)
|
||||||
|
|
||||||
if not skip_env_check:
|
if not skip_env_check:
|
||||||
from unilabos.utils.environment_check import check_environment, check_device_package_requirements
|
from unilabos.utils.environment_check import check_environment
|
||||||
|
|
||||||
if not check_environment(auto_install=True):
|
if not check_environment(auto_install=True):
|
||||||
print_status("环境检查失败,程序退出", "error")
|
print_status("环境检查失败,程序退出", "error")
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
# 第一次设备包依赖检查:build_registry 之前,确保 import map 可用
|
|
||||||
devices_dirs_for_req = args_dict.get("devices", None)
|
|
||||||
if devices_dirs_for_req:
|
|
||||||
if not check_device_package_requirements(devices_dirs_for_req):
|
|
||||||
print_status("设备包依赖检查失败,程序退出", "error")
|
|
||||||
os._exit(1)
|
|
||||||
else:
|
else:
|
||||||
print_status("跳过环境依赖检查", "warning")
|
print_status("跳过环境依赖检查", "warning")
|
||||||
|
|
||||||
@@ -480,9 +358,6 @@ def main():
|
|||||||
BasicConfig.test_mode = args_dict.get("test_mode", False)
|
BasicConfig.test_mode = args_dict.get("test_mode", False)
|
||||||
if BasicConfig.test_mode:
|
if BasicConfig.test_mode:
|
||||||
print_status("启用测试模式:所有动作将模拟执行,不调用真实硬件", "warning")
|
print_status("启用测试模式:所有动作将模拟执行,不调用真实硬件", "warning")
|
||||||
BasicConfig.extra_resource = args_dict.get("extra_resource", False)
|
|
||||||
if BasicConfig.extra_resource:
|
|
||||||
print_status("启用额外资源加载:将加载lab_开头的labware资源定义", "info")
|
|
||||||
BasicConfig.communication_protocol = "websocket"
|
BasicConfig.communication_protocol = "websocket"
|
||||||
machine_name = platform.node()
|
machine_name = platform.node()
|
||||||
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
|
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
|
||||||
@@ -490,53 +365,39 @@ def main():
|
|||||||
BasicConfig.vis_2d_enable = args_dict["2d_vis"]
|
BasicConfig.vis_2d_enable = args_dict["2d_vis"]
|
||||||
BasicConfig.check_mode = check_mode
|
BasicConfig.check_mode = check_mode
|
||||||
|
|
||||||
from unilabos.registry.registry import build_registry
|
|
||||||
|
|
||||||
# 显示启动横幅
|
|
||||||
print_unilab_banner(args_dict)
|
|
||||||
|
|
||||||
# Step 0: AST 分析优先 + YAML 注册表加载
|
|
||||||
# check_mode 和 upload_registry 都会执行实际 import 验证
|
|
||||||
devices_dirs = args_dict.get("devices", None)
|
|
||||||
complete_registry = args_dict.get("complete_registry", False) or check_mode
|
|
||||||
external_only = args_dict.get("external_devices_only", False)
|
|
||||||
lab_registry = build_registry(
|
|
||||||
registry_paths=args_dict["registry_path"],
|
|
||||||
devices_dirs=devices_dirs,
|
|
||||||
upload_registry=BasicConfig.upload_registry,
|
|
||||||
check_mode=check_mode,
|
|
||||||
complete_registry=complete_registry,
|
|
||||||
external_only=external_only,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check mode: 注册表验证完成后直接退出
|
|
||||||
if check_mode:
|
|
||||||
device_count = len(lab_registry.device_type_registry)
|
|
||||||
resource_count = len(lab_registry.resource_type_registry)
|
|
||||||
print_status(f"Check mode: 注册表验证完成 ({device_count} 设备, {resource_count} 资源),退出", "info")
|
|
||||||
os._exit(0)
|
|
||||||
|
|
||||||
# 以下导入依赖 ROS2 环境,check_mode 已退出不需要
|
|
||||||
from unilabos.resources.graphio import (
|
from unilabos.resources.graphio import (
|
||||||
read_node_link_json,
|
read_node_link_json,
|
||||||
read_graphml,
|
read_graphml,
|
||||||
dict_from_graph,
|
dict_from_graph,
|
||||||
modify_to_backend_format,
|
|
||||||
)
|
)
|
||||||
from unilabos.app.communication import get_communication_client
|
from unilabos.app.communication import get_communication_client
|
||||||
|
from unilabos.registry.registry import build_registry
|
||||||
from unilabos.app.backend import start_backend
|
from unilabos.app.backend import start_backend
|
||||||
from unilabos.app.web import http_client
|
from unilabos.app.web import http_client
|
||||||
from unilabos.app.web import start_server
|
from unilabos.app.web import start_server
|
||||||
from unilabos.app.register import register_devices_and_resources
|
from unilabos.app.register import register_devices_and_resources
|
||||||
|
from unilabos.resources.graphio import modify_to_backend_format
|
||||||
from unilabos.resources.resource_tracker import ResourceTreeSet, ResourceDict
|
from unilabos.resources.resource_tracker import ResourceTreeSet, ResourceDict
|
||||||
|
|
||||||
# Step 1: 上传全部注册表到服务端,同步保存到 unilabos_data
|
# 显示启动横幅
|
||||||
|
print_unilab_banner(args_dict)
|
||||||
|
|
||||||
|
# 注册表 - check_mode 时强制启用 complete_registry
|
||||||
|
complete_registry = args_dict.get("complete_registry", False) or check_mode
|
||||||
|
lab_registry = build_registry(args_dict["registry_path"], complete_registry, BasicConfig.upload_registry)
|
||||||
|
|
||||||
|
# Check mode: complete_registry 完成后直接退出,git diff 检测由 CI workflow 执行
|
||||||
|
if check_mode:
|
||||||
|
print_status("Check mode: complete_registry 完成,退出", "info")
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
if BasicConfig.upload_registry:
|
if BasicConfig.upload_registry:
|
||||||
|
# 设备注册到服务端 - 需要 ak 和 sk
|
||||||
if BasicConfig.ak and BasicConfig.sk:
|
if BasicConfig.ak and BasicConfig.sk:
|
||||||
# print_status("开始注册设备到服务端...", "info")
|
print_status("开始注册设备到服务端...", "info")
|
||||||
try:
|
try:
|
||||||
register_devices_and_resources(lab_registry)
|
register_devices_and_resources(lab_registry)
|
||||||
# print_status("设备注册完成", "info")
|
print_status("设备注册完成", "info")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_status(f"设备注册失败: {e}", "error")
|
print_status(f"设备注册失败: {e}", "error")
|
||||||
else:
|
else:
|
||||||
@@ -621,16 +482,12 @@ def main():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 如果从远端获取了物料信息,则与本地物料进行同步
|
# 如果从远端获取了物料信息,则与本地物料进行同步
|
||||||
if file_path is not None and request_startup_json and "nodes" in request_startup_json:
|
if request_startup_json and "nodes" in request_startup_json:
|
||||||
print_status("开始同步远端物料到本地...", "info")
|
print_status("开始同步远端物料到本地...", "info")
|
||||||
remote_tree_set = ResourceTreeSet.from_raw_dict_list(request_startup_json["nodes"])
|
remote_tree_set = ResourceTreeSet.from_raw_dict_list(request_startup_json["nodes"])
|
||||||
resource_tree_set.merge_remote_resources(remote_tree_set)
|
resource_tree_set.merge_remote_resources(remote_tree_set)
|
||||||
print_status("远端物料同步完成", "info")
|
print_status("远端物料同步完成", "info")
|
||||||
|
|
||||||
# 第二次设备包依赖检查:云端物料同步后,community 包可能引入新的 requirements
|
|
||||||
# TODO: 当 community device package 功能上线后,在这里调用
|
|
||||||
# install_requirements_txt(community_pkg_path / "requirements.txt", label="community.xxx")
|
|
||||||
|
|
||||||
# 使用 ResourceTreeSet 代替 list
|
# 使用 ResourceTreeSet 代替 list
|
||||||
args_dict["resources_config"] = resource_tree_set
|
args_dict["resources_config"] = resource_tree_set
|
||||||
args_dict["devices_config"] = resource_tree_set
|
args_dict["devices_config"] = resource_tree_set
|
||||||
@@ -722,10 +579,6 @@ def main():
|
|||||||
open_browser=not args_dict["disable_browser"],
|
open_browser=not args_dict["disable_browser"],
|
||||||
port=BasicConfig.port,
|
port=BasicConfig.port,
|
||||||
)
|
)
|
||||||
if restart_requested:
|
|
||||||
print_status("[Main] Restart requested, cleaning up...", "info")
|
|
||||||
cleanup_for_restart()
|
|
||||||
os._exit(RESTART_EXIT_CODE)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import json
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Optional, Tuple, Dict, Any
|
||||||
|
|
||||||
from unilabos.utils.log import logger
|
from unilabos.utils.log import logger
|
||||||
from unilabos.utils.tools import normalize_json as _normalize_device
|
from unilabos.utils.type_check import TypeEncoder
|
||||||
|
|
||||||
|
|
||||||
def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]:
|
def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]:
|
||||||
@@ -10,63 +11,50 @@ def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[
|
|||||||
注册设备和资源到服务器(仅支持HTTP)
|
注册设备和资源到服务器(仅支持HTTP)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 注册资源信息 - 使用HTTP方式
|
||||||
from unilabos.app.web.client import http_client
|
from unilabos.app.web.client import http_client
|
||||||
|
|
||||||
logger.info("[UniLab Register] 开始注册设备和资源...")
|
logger.info("[UniLab Register] 开始注册设备和资源...")
|
||||||
|
|
||||||
|
# 注册设备信息
|
||||||
devices_to_register = {}
|
devices_to_register = {}
|
||||||
for device_info in lab_registry.obtain_registry_device_info():
|
for device_info in lab_registry.obtain_registry_device_info():
|
||||||
devices_to_register[device_info["id"]] = _normalize_device(device_info)
|
devices_to_register[device_info["id"]] = json.loads(
|
||||||
logger.trace(f"[UniLab Register] 收集设备: {device_info['id']}")
|
json.dumps(device_info, ensure_ascii=False, cls=TypeEncoder)
|
||||||
|
)
|
||||||
|
logger.debug(f"[UniLab Register] 收集设备: {device_info['id']}")
|
||||||
|
|
||||||
resources_to_register = {}
|
resources_to_register = {}
|
||||||
for resource_info in lab_registry.obtain_registry_resource_info():
|
for resource_info in lab_registry.obtain_registry_resource_info():
|
||||||
resources_to_register[resource_info["id"]] = resource_info
|
resources_to_register[resource_info["id"]] = resource_info
|
||||||
logger.trace(f"[UniLab Register] 收集资源: {resource_info['id']}")
|
logger.debug(f"[UniLab Register] 收集资源: {resource_info['id']}")
|
||||||
|
|
||||||
if gather_only:
|
if gather_only:
|
||||||
return devices_to_register, resources_to_register
|
return devices_to_register, resources_to_register
|
||||||
|
# 注册设备
|
||||||
if devices_to_register:
|
if devices_to_register:
|
||||||
try:
|
try:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
response = http_client.resource_registry(
|
response = http_client.resource_registry({"resources": list(devices_to_register.values())})
|
||||||
{"resources": list(devices_to_register.values())},
|
|
||||||
tag="device_registry",
|
|
||||||
)
|
|
||||||
cost_time = time.time() - start_time
|
cost_time = time.time() - start_time
|
||||||
res_data = response.json() if response.status_code == 200 else {}
|
if response.status_code in [200, 201]:
|
||||||
skipped = res_data.get("data", {}).get("skipped", False)
|
logger.info(f"[UniLab Register] 成功注册 {len(devices_to_register)} 个设备 {cost_time}s")
|
||||||
if skipped:
|
|
||||||
logger.info(
|
|
||||||
f"[UniLab Register] 设备注册跳过(内容未变化)"
|
|
||||||
f" {len(devices_to_register)} 个 {cost_time:.3f}s"
|
|
||||||
)
|
|
||||||
elif response.status_code in [200, 201]:
|
|
||||||
logger.info(f"[UniLab Register] 成功注册 {len(devices_to_register)} 个设备 {cost_time:.3f}s")
|
|
||||||
else:
|
else:
|
||||||
logger.error(f"[UniLab Register] 设备注册失败: {response.status_code}, {response.text} {cost_time:.3f}s")
|
logger.error(f"[UniLab Register] 设备注册失败: {response.status_code}, {response.text} {cost_time}s")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[UniLab Register] 设备注册异常: {e}")
|
logger.error(f"[UniLab Register] 设备注册异常: {e}")
|
||||||
|
|
||||||
|
# 注册资源
|
||||||
if resources_to_register:
|
if resources_to_register:
|
||||||
try:
|
try:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
response = http_client.resource_registry(
|
response = http_client.resource_registry({"resources": list(resources_to_register.values())})
|
||||||
{"resources": list(resources_to_register.values())},
|
|
||||||
tag="resource_registry",
|
|
||||||
)
|
|
||||||
cost_time = time.time() - start_time
|
cost_time = time.time() - start_time
|
||||||
res_data = response.json() if response.status_code == 200 else {}
|
if response.status_code in [200, 201]:
|
||||||
skipped = res_data.get("data", {}).get("skipped", False)
|
logger.info(f"[UniLab Register] 成功注册 {len(resources_to_register)} 个资源 {cost_time}s")
|
||||||
if skipped:
|
|
||||||
logger.info(
|
|
||||||
f"[UniLab Register] 资源注册跳过(内容未变化)"
|
|
||||||
f" {len(resources_to_register)} 个 {cost_time:.3f}s"
|
|
||||||
)
|
|
||||||
elif response.status_code in [200, 201]:
|
|
||||||
logger.info(f"[UniLab Register] 成功注册 {len(resources_to_register)} 个资源 {cost_time:.3f}s")
|
|
||||||
else:
|
else:
|
||||||
logger.error(f"[UniLab Register] 资源注册失败: {response.status_code}, {response.text} {cost_time:.3f}s")
|
logger.error(f"[UniLab Register] 资源注册失败: {response.status_code}, {response.text} {cost_time}s")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[UniLab Register] 资源注册异常: {e}")
|
logger.error(f"[UniLab Register] 资源注册异常: {e}")
|
||||||
|
|
||||||
|
logger.info("[UniLab Register] 设备和资源注册完成.")
|
||||||
|
|||||||
@@ -1052,7 +1052,7 @@ async def handle_file_import(websocket: WebSocket, request_data: dict):
|
|||||||
"result": {},
|
"result": {},
|
||||||
"schema": lab_registry._generate_unilab_json_command_schema(v["args"], k),
|
"schema": lab_registry._generate_unilab_json_command_schema(v["args"], k),
|
||||||
"goal_default": {i["name"]: i["default"] for i in v["args"]},
|
"goal_default": {i["name"]: i["default"] for i in v["args"]},
|
||||||
"handles": {},
|
"handles": [],
|
||||||
}
|
}
|
||||||
# 不生成已配置action的动作
|
# 不生成已配置action的动作
|
||||||
for k, v in enhanced_info["action_methods"].items()
|
for k, v in enhanced_info["action_methods"].items()
|
||||||
@@ -1340,5 +1340,5 @@ def setup_api_routes(app):
|
|||||||
# 启动广播任务
|
# 启动广播任务
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def startup_event():
|
async def startup_event():
|
||||||
asyncio.create_task(broadcast_device_status(), name="web-api-startup-device")
|
asyncio.create_task(broadcast_device_status())
|
||||||
asyncio.create_task(broadcast_status_page_data(), name="web-api-startup-status")
|
asyncio.create_task(broadcast_status_page_data())
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ HTTP客户端模块
|
|||||||
|
|
||||||
提供与远程服务器通信的客户端功能,只有host需要用
|
提供与远程服务器通信的客户端功能,只有host需要用
|
||||||
"""
|
"""
|
||||||
import gzip
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
||||||
from unilabos.utils.tools import fast_dumps as _fast_dumps, fast_dumps_pretty as _fast_dumps_pretty
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from unilabos.resources.resource_tracker import ResourceTreeSet
|
from unilabos.resources.resource_tracker import ResourceTreeSet
|
||||||
from unilabos.utils.log import info
|
from unilabos.utils.log import info
|
||||||
@@ -282,54 +280,22 @@ class HTTPClient:
|
|||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def resource_registry(
|
def resource_registry(self, registry_data: Dict[str, Any] | List[Dict[str, Any]]) -> requests.Response:
|
||||||
self, registry_data: Dict[str, Any] | List[Dict[str, Any]], tag: str = "registry",
|
|
||||||
) -> requests.Response:
|
|
||||||
"""
|
"""
|
||||||
注册资源到服务器,同步保存请求/响应到 unilabos_data
|
注册资源到服务器
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
registry_data: 注册表数据,格式为 {resource_id: resource_info} / [{resource_info}]
|
registry_data: 注册表数据,格式为 {resource_id: resource_info} / [{resource_info}]
|
||||||
tag: 保存文件的标签后缀 (如 "device_registry" / "resource_registry")
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response: API响应对象
|
Response: API响应对象
|
||||||
"""
|
"""
|
||||||
# 序列化一次,同时用于保存和发送
|
|
||||||
json_bytes = _fast_dumps(registry_data)
|
|
||||||
|
|
||||||
# 保存请求数据到 unilabos_data
|
|
||||||
req_path = os.path.join(BasicConfig.working_dir, f"req_{tag}_upload.json")
|
|
||||||
try:
|
|
||||||
os.makedirs(BasicConfig.working_dir, exist_ok=True)
|
|
||||||
with open(req_path, "wb") as f:
|
|
||||||
f.write(_fast_dumps_pretty(registry_data))
|
|
||||||
logger.trace(f"注册表请求数据已保存: {req_path}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"保存注册表请求数据失败: {e}")
|
|
||||||
|
|
||||||
compressed_body = gzip.compress(json_bytes)
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Lab {self.auth}",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Content-Encoding": "gzip",
|
|
||||||
}
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.remote_addr}/lab/resource",
|
f"{self.remote_addr}/lab/resource",
|
||||||
data=compressed_body,
|
json=registry_data,
|
||||||
headers=headers,
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 保存响应数据到 unilabos_data
|
|
||||||
res_path = os.path.join(BasicConfig.working_dir, f"res_{tag}_upload.json")
|
|
||||||
try:
|
|
||||||
with open(res_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(f"{response.status_code}\n{response.text}")
|
|
||||||
logger.trace(f"注册表响应数据已保存: {res_path}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"保存注册表响应数据失败: {e}")
|
|
||||||
|
|
||||||
if response.status_code not in [200, 201]:
|
if response.status_code not in [200, 201]:
|
||||||
logger.error(f"注册资源失败: {response.status_code}, {response.text}")
|
logger.error(f"注册资源失败: {response.status_code}, {response.text}")
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def setup_server() -> FastAPI:
|
|||||||
# 设置页面路由
|
# 设置页面路由
|
||||||
try:
|
try:
|
||||||
setup_web_pages(pages)
|
setup_web_pages(pages)
|
||||||
# info("[Web] 已加载Web UI模块")
|
info("[Web] 已加载Web UI模块")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
info(f"[Web] 未找到Web页面模块: {str(e)}")
|
info(f"[Web] 未找到Web页面模块: {str(e)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -138,7 +138,7 @@ def start_server(host: str = "0.0.0.0", port: int = 8002, open_browser: bool = T
|
|||||||
server_thread = threading.Thread(target=server.run, daemon=True, name="uvicorn_server")
|
server_thread = threading.Thread(target=server.run, daemon=True, name="uvicorn_server")
|
||||||
server_thread.start()
|
server_thread.start()
|
||||||
|
|
||||||
# info("[Web] Server started, monitoring for restart requests...")
|
info("[Web] Server started, monitoring for restart requests...")
|
||||||
|
|
||||||
# 监控重启标志
|
# 监控重启标志
|
||||||
import unilabos.app.main as main_module
|
import unilabos.app.main as main_module
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ from enum import Enum
|
|||||||
from typing_extensions import TypedDict
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from unilabos.app.model import JobAddReq
|
from unilabos.app.model import JobAddReq
|
||||||
from unilabos.resources.resource_tracker import ResourceDictType
|
|
||||||
from unilabos.ros.nodes.presets.host_node import HostNode
|
from unilabos.ros.nodes.presets.host_node import HostNode
|
||||||
from unilabos.utils.type_check import serialize_result_info
|
from unilabos.utils.type_check import serialize_result_info
|
||||||
from unilabos.app.communication import BaseCommunicationClient
|
from unilabos.app.communication import BaseCommunicationClient
|
||||||
@@ -409,7 +408,6 @@ class MessageProcessor:
|
|||||||
# 线程控制
|
# 线程控制
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.thread = None
|
self.thread = None
|
||||||
self._loop = None # asyncio event loop引用,用于外部关闭websocket
|
|
||||||
self.reconnect_count = 0
|
self.reconnect_count = 0
|
||||||
|
|
||||||
logger.info(f"[MessageProcessor] Initialized for URL: {websocket_url}")
|
logger.info(f"[MessageProcessor] Initialized for URL: {websocket_url}")
|
||||||
@@ -436,31 +434,22 @@ class MessageProcessor:
|
|||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""停止消息处理线程"""
|
"""停止消息处理线程"""
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
# 主动关闭websocket以快速中断消息接收循环
|
|
||||||
ws = self.websocket
|
|
||||||
loop = self._loop
|
|
||||||
if ws and loop and loop.is_running():
|
|
||||||
try:
|
|
||||||
asyncio.run_coroutine_threadsafe(ws.close(), loop)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if self.thread and self.thread.is_alive():
|
if self.thread and self.thread.is_alive():
|
||||||
self.thread.join(timeout=2)
|
self.thread.join(timeout=2)
|
||||||
logger.info("[MessageProcessor] Stopped")
|
logger.info("[MessageProcessor] Stopped")
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
"""运行消息处理主循环"""
|
"""运行消息处理主循环"""
|
||||||
self._loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
try:
|
try:
|
||||||
asyncio.set_event_loop(self._loop)
|
asyncio.set_event_loop(loop)
|
||||||
self._loop.run_until_complete(self._connection_handler())
|
loop.run_until_complete(self._connection_handler())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[MessageProcessor] Thread error: {str(e)}")
|
logger.error(f"[MessageProcessor] Thread error: {str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
if self._loop:
|
if loop:
|
||||||
self._loop.close()
|
loop.close()
|
||||||
self._loop = None
|
|
||||||
|
|
||||||
async def _connection_handler(self):
|
async def _connection_handler(self):
|
||||||
"""处理WebSocket连接和重连逻辑"""
|
"""处理WebSocket连接和重连逻辑"""
|
||||||
@@ -477,10 +466,8 @@ class MessageProcessor:
|
|||||||
async with websockets.connect(
|
async with websockets.connect(
|
||||||
self.websocket_url,
|
self.websocket_url,
|
||||||
ssl=ssl_context,
|
ssl=ssl_context,
|
||||||
open_timeout=20,
|
|
||||||
ping_interval=WSConfig.ping_interval,
|
ping_interval=WSConfig.ping_interval,
|
||||||
ping_timeout=10,
|
ping_timeout=10,
|
||||||
close_timeout=5,
|
|
||||||
additional_headers={
|
additional_headers={
|
||||||
"Authorization": f"Lab {BasicConfig.auth_secret()}",
|
"Authorization": f"Lab {BasicConfig.auth_secret()}",
|
||||||
"EdgeSession": f"{self.session_id}",
|
"EdgeSession": f"{self.session_id}",
|
||||||
@@ -491,72 +478,53 @@ class MessageProcessor:
|
|||||||
self.connected = True
|
self.connected = True
|
||||||
self.reconnect_count = 0
|
self.reconnect_count = 0
|
||||||
|
|
||||||
logger.info(f"[MessageProcessor] 已连接到 {self.websocket_url}")
|
logger.trace(f"[MessageProcessor] Connected to {self.websocket_url}")
|
||||||
|
|
||||||
# 启动发送协程
|
# 启动发送协程
|
||||||
send_task = asyncio.create_task(self._send_handler(), name="websocket-send_task")
|
send_task = asyncio.create_task(self._send_handler())
|
||||||
|
|
||||||
# 每次连接(含重连)后重新向服务端注册,
|
|
||||||
# 否则服务端不知道客户端已上线,不会推送消息。
|
|
||||||
if self.websocket_client:
|
|
||||||
self.websocket_client.publish_host_ready()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 接收消息循环
|
# 接收消息循环
|
||||||
await self._message_handler()
|
await self._message_handler()
|
||||||
finally:
|
finally:
|
||||||
# 必须在 async with __aexit__ 之前停止 send_task,
|
|
||||||
# 否则 send_task 会在关闭握手期间继续发送数据,
|
|
||||||
# 干扰 websockets 库的内部清理,导致 task 泄漏。
|
|
||||||
self.connected = False
|
|
||||||
send_task.cancel()
|
send_task.cancel()
|
||||||
try:
|
try:
|
||||||
await send_task
|
await send_task
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
|
self.connected = False
|
||||||
|
|
||||||
except websockets.exceptions.ConnectionClosed:
|
except websockets.exceptions.ConnectionClosed:
|
||||||
logger.warning("[MessageProcessor] 与服务端连接中断")
|
logger.warning("[MessageProcessor] Connection closed")
|
||||||
except TimeoutError:
|
|
||||||
logger.warning(
|
|
||||||
f"[MessageProcessor] 与服务端连接通信超时 (已尝试 {self.reconnect_count + 1} 次),请检查您的网络状况"
|
|
||||||
)
|
|
||||||
except websockets.exceptions.InvalidStatus as e:
|
|
||||||
logger.warning(
|
|
||||||
f"[MessageProcessor] 收到服务端注册码 {e.response.status_code}, 上一进程可能还未退出"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(traceback.format_exc())
|
|
||||||
logger.error(f"[MessageProcessor] 尝试重连时出错 {str(e)}")
|
|
||||||
finally:
|
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[MessageProcessor] Connection error: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
self.connected = False
|
||||||
|
finally:
|
||||||
self.websocket = None
|
self.websocket = None
|
||||||
|
|
||||||
# 重连逻辑
|
# 重连逻辑
|
||||||
if not self.is_running:
|
if self.is_running and self.reconnect_count < WSConfig.max_reconnect_attempts:
|
||||||
break
|
|
||||||
if self.reconnect_count < WSConfig.max_reconnect_attempts:
|
|
||||||
self.reconnect_count += 1
|
self.reconnect_count += 1
|
||||||
backoff = WSConfig.reconnect_interval
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[MessageProcessor] 即将在 {backoff} 秒后重连 (已尝试 {self.reconnect_count}/{WSConfig.max_reconnect_attempts})"
|
f"[MessageProcessor] Reconnecting in {WSConfig.reconnect_interval}s "
|
||||||
|
f"(attempt {self.reconnect_count}/{WSConfig.max_reconnect_attempts})"
|
||||||
)
|
)
|
||||||
await asyncio.sleep(backoff)
|
await asyncio.sleep(WSConfig.reconnect_interval)
|
||||||
else:
|
elif self.reconnect_count >= WSConfig.max_reconnect_attempts:
|
||||||
logger.error("[MessageProcessor] Max reconnection attempts reached")
|
logger.error("[MessageProcessor] Max reconnection attempts reached")
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
self.reconnect_count -= 1
|
||||||
|
|
||||||
async def _message_handler(self):
|
async def _message_handler(self):
|
||||||
"""处理接收到的消息。
|
"""处理接收到的消息"""
|
||||||
|
|
||||||
ConnectionClosed 不在此处捕获,让其向上传播到 _connection_handler,
|
|
||||||
以便 async with websockets.connect() 的 __aexit__ 能感知连接已断,
|
|
||||||
正确清理内部 task,避免 task 泄漏。
|
|
||||||
"""
|
|
||||||
if not self.websocket:
|
if not self.websocket:
|
||||||
logger.error("[MessageProcessor] WebSocket connection is None")
|
logger.error("[MessageProcessor] WebSocket connection is None")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
async for message in self.websocket:
|
async for message in self.websocket:
|
||||||
try:
|
try:
|
||||||
data = json.loads(message)
|
data = json.loads(message)
|
||||||
@@ -580,6 +548,12 @@ class MessageProcessor:
|
|||||||
logger.error(f"[MessageProcessor] Error processing message: {str(e)}")
|
logger.error(f"[MessageProcessor] Error processing message: {str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
except websockets.exceptions.ConnectionClosed:
|
||||||
|
logger.info("[MessageProcessor] Message handler stopped - connection closed")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[MessageProcessor] Message handler error: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
async def _send_handler(self):
|
async def _send_handler(self):
|
||||||
"""处理发送队列中的消息"""
|
"""处理发送队列中的消息"""
|
||||||
logger.trace("[MessageProcessor] Send handler started")
|
logger.trace("[MessageProcessor] Send handler started")
|
||||||
@@ -627,7 +601,6 @@ class MessageProcessor:
|
|||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.debug("[MessageProcessor] Send handler cancelled")
|
logger.debug("[MessageProcessor] Send handler cancelled")
|
||||||
raise
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[MessageProcessor] Fatal error in send handler: {str(e)}")
|
logger.error(f"[MessageProcessor] Fatal error in send handler: {str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
@@ -659,10 +632,6 @@ class MessageProcessor:
|
|||||||
# elif message_type == "session_id":
|
# elif message_type == "session_id":
|
||||||
# self.session_id = message_data.get("session_id")
|
# self.session_id = message_data.get("session_id")
|
||||||
# logger.info(f"[MessageProcessor] Session ID: {self.session_id}")
|
# logger.info(f"[MessageProcessor] Session ID: {self.session_id}")
|
||||||
elif message_type == "add_device":
|
|
||||||
await self._handle_device_manage(message_data, "add")
|
|
||||||
elif message_type == "remove_device":
|
|
||||||
await self._handle_device_manage(message_data, "remove")
|
|
||||||
elif message_type == "request_restart":
|
elif message_type == "request_restart":
|
||||||
await self._handle_request_restart(message_data)
|
await self._handle_request_restart(message_data)
|
||||||
else:
|
else:
|
||||||
@@ -999,37 +968,6 @@ class MessageProcessor:
|
|||||||
)
|
)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
async def _handle_device_manage(self, device_list: list[ResourceDictType], action: str):
|
|
||||||
"""Handle add_device / remove_device from LabGo server."""
|
|
||||||
if not device_list:
|
|
||||||
return
|
|
||||||
|
|
||||||
for item in device_list:
|
|
||||||
target_node_id = item.get("target_node_id", "host_node")
|
|
||||||
|
|
||||||
def _notify(target_id: str, act: str, cfg: ResourceDictType):
|
|
||||||
try:
|
|
||||||
host_node = HostNode.get_instance(timeout=5)
|
|
||||||
if not host_node:
|
|
||||||
logger.error(f"[DeviceManage] HostNode not available for {act}_device")
|
|
||||||
return
|
|
||||||
success = host_node.notify_device_manage(target_id, act, cfg)
|
|
||||||
if success:
|
|
||||||
logger.info(f"[DeviceManage] {act}_device completed on {target_id}")
|
|
||||||
else:
|
|
||||||
logger.warning(f"[DeviceManage] {act}_device failed on {target_id}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[DeviceManage] Error in {act}_device: {e}")
|
|
||||||
logger.error(traceback.format_exc())
|
|
||||||
|
|
||||||
thread = threading.Thread(
|
|
||||||
target=_notify,
|
|
||||||
args=(target_node_id, action, item),
|
|
||||||
daemon=True,
|
|
||||||
name=f"DeviceManage-{action}-{item.get('id', '')}",
|
|
||||||
)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
async def _handle_request_restart(self, data: Dict[str, Any]):
|
async def _handle_request_restart(self, data: Dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
处理重启请求
|
处理重启请求
|
||||||
@@ -1041,7 +979,8 @@ class MessageProcessor:
|
|||||||
logger.info(f"[MessageProcessor] Received restart request, reason: {reason}, delay: {delay}s")
|
logger.info(f"[MessageProcessor] Received restart request, reason: {reason}, delay: {delay}s")
|
||||||
|
|
||||||
# 发送确认消息
|
# 发送确认消息
|
||||||
self.send_message(
|
if self.websocket_client:
|
||||||
|
await self.websocket_client.send_message(
|
||||||
{"action": "restart_acknowledged", "data": {"reason": reason, "delay": delay}}
|
{"action": "restart_acknowledged", "data": {"reason": reason, "delay": delay}}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1145,7 +1084,6 @@ class QueueProcessor:
|
|||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""停止队列处理线程"""
|
"""停止队列处理线程"""
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.queue_update_event.set() # 立即唤醒等待中的线程
|
|
||||||
if self.thread and self.thread.is_alive():
|
if self.thread and self.thread.is_alive():
|
||||||
self.thread.join(timeout=2)
|
self.thread.join(timeout=2)
|
||||||
logger.info("[QueueProcessor] Stopped")
|
logger.info("[QueueProcessor] Stopped")
|
||||||
@@ -1399,8 +1337,8 @@ class WebSocketClient(BaseCommunicationClient):
|
|||||||
message = {"action": "normal_exit", "data": {"session_id": session_id}}
|
message = {"action": "normal_exit", "data": {"session_id": session_id}}
|
||||||
self.message_processor.send_message(message)
|
self.message_processor.send_message(message)
|
||||||
logger.info(f"[WebSocketClient] Sent normal_exit message with session_id: {session_id}")
|
logger.info(f"[WebSocketClient] Sent normal_exit message with session_id: {session_id}")
|
||||||
# send_handler 每100ms检查一次队列,等300ms足以让消息发出
|
# 给一点时间让消息发送出去
|
||||||
time.sleep(0.3)
|
time.sleep(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[WebSocketClient] Failed to send normal_exit message: {str(e)}")
|
logger.warning(f"[WebSocketClient] Failed to send normal_exit message: {str(e)}")
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ class BasicConfig:
|
|||||||
port = 8002 # 本地HTTP服务
|
port = 8002 # 本地HTTP服务
|
||||||
check_mode = False # CI 检查模式,用于验证 registry 导入和文件一致性
|
check_mode = False # CI 检查模式,用于验证 registry 导入和文件一致性
|
||||||
test_mode = False # 测试模式,所有动作不实际执行,返回模拟结果
|
test_mode = False # 测试模式,所有动作不实际执行,返回模拟结果
|
||||||
extra_resource = False # 是否加载lab_开头的额外资源
|
|
||||||
# 'TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
|
# 'TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
|
||||||
log_level: Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "DEBUG"
|
log_level: Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "DEBUG"
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ class BasicConfig:
|
|||||||
class WSConfig:
|
class WSConfig:
|
||||||
reconnect_interval = 5 # 重连间隔(秒)
|
reconnect_interval = 5 # 重连间隔(秒)
|
||||||
max_reconnect_attempts = 999 # 最大重连次数
|
max_reconnect_attempts = 999 # 最大重连次数
|
||||||
ping_interval = 20 # ping间隔(秒)
|
ping_interval = 30 # ping间隔(秒)
|
||||||
|
|
||||||
|
|
||||||
# HTTP配置
|
# HTTP配置
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import inspect
|
import inspect
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -103,7 +103,7 @@ class PRCXI9300Deck(Deck):
|
|||||||
|
|
||||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
||||||
sites: Optional[List[Dict[str, Any]]] = None, **kwargs):
|
sites: Optional[List[Dict[str, Any]]] = None, **kwargs):
|
||||||
super().__init__(size_x, size_y, size_z, name)
|
super().__init__(name, size_x, size_y, size_z)
|
||||||
if sites is not None:
|
if sites is not None:
|
||||||
self.sites: List[Dict[str, Any]] = [dict(s) for s in sites]
|
self.sites: List[Dict[str, Any]] = [dict(s) for s in sites]
|
||||||
else:
|
else:
|
||||||
@@ -120,6 +120,7 @@ class PRCXI9300Deck(Deck):
|
|||||||
self._ordering = collections.OrderedDict(
|
self._ordering = collections.OrderedDict(
|
||||||
(site["label"], None) for site in self.sites
|
(site["label"], None) for site in self.sites
|
||||||
)
|
)
|
||||||
|
self.root = self.get_root()
|
||||||
|
|
||||||
def _get_site_location(self, idx: int) -> Coordinate:
|
def _get_site_location(self, idx: int) -> Coordinate:
|
||||||
pos = self.sites[idx]["position"]
|
pos = self.sites[idx]["position"]
|
||||||
@@ -162,7 +163,10 @@ class PRCXI9300Deck(Deck):
|
|||||||
raise ValueError(f"No available site on deck '{self.name}' for resource '{resource.name}'")
|
raise ValueError(f"No available site on deck '{self.name}' for resource '{resource.name}'")
|
||||||
|
|
||||||
if not reassign and self._get_site_resource(idx) is not None:
|
if not reassign and self._get_site_resource(idx) is not None:
|
||||||
raise ValueError(f"Site {idx} ('{self.sites[idx]['label']}') is already occupied")
|
existing = self.root.get_resource(resource.name)
|
||||||
|
if existing is not resource and existing.parent is not None:
|
||||||
|
existing.parent.unassign_child_resource(existing)
|
||||||
|
|
||||||
|
|
||||||
loc = self._get_site_location(idx)
|
loc = self._get_site_location(idx)
|
||||||
super().assign_child_resource(resource, location=loc, reassign=reassign)
|
super().assign_child_resource(resource, location=loc, reassign=reassign)
|
||||||
@@ -634,7 +638,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
deck: PRCXI9300Deck,
|
deck: Deck,
|
||||||
host: str,
|
host: str,
|
||||||
port: int,
|
port: int,
|
||||||
timeout: float,
|
timeout: float,
|
||||||
@@ -648,11 +652,11 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
is_9320=False,
|
is_9320=False,
|
||||||
):
|
):
|
||||||
tablets_info = []
|
tablets_info = []
|
||||||
for site_id in range(len(deck.sites)):
|
count = 0
|
||||||
child = deck._get_site_resource(site_id)
|
for child in deck.children:
|
||||||
# 如果放其他类型的物料,是不可以的
|
# 如果放其他类型的物料,是不可以的
|
||||||
if hasattr(child, "_unilabos_state") and "Material" in child._unilabos_state:
|
if hasattr(child, "_unilabos_state") and "Material" in child._unilabos_state:
|
||||||
number = site_id + 1
|
number = int(child.name.replace("T", ""))
|
||||||
tablets_info.append(
|
tablets_info.append(
|
||||||
WorkTablets(
|
WorkTablets(
|
||||||
Number=number, Code=f"T{number}", Material=child._unilabos_state["Material"]
|
Number=number, Code=f"T{number}", Material=child._unilabos_state["Material"]
|
||||||
@@ -794,6 +798,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
touch_tip: bool = False,
|
touch_tip: bool = False,
|
||||||
liquid_height: Optional[List[Optional[float]]] = None,
|
liquid_height: Optional[List[Optional[float]]] = None,
|
||||||
blow_out_air_volume: Optional[List[Optional[float]]] = None,
|
blow_out_air_volume: Optional[List[Optional[float]]] = None,
|
||||||
|
blow_out_air_volume_before: Optional[List[Optional[float]]] = None,
|
||||||
spread: Literal["wide", "tight", "custom"] = "wide",
|
spread: Literal["wide", "tight", "custom"] = "wide",
|
||||||
is_96_well: bool = False,
|
is_96_well: bool = False,
|
||||||
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
||||||
@@ -804,7 +809,9 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
delays: Optional[List[int]] = None,
|
delays: Optional[List[int]] = None,
|
||||||
none_keys: List[str] = [],
|
none_keys: List[str] = [],
|
||||||
) -> TransferLiquidReturn:
|
) -> TransferLiquidReturn:
|
||||||
return await super().transfer_liquid(
|
if self.step_mode:
|
||||||
|
await self.create_protocol(f"transfer_liquid{time.time()}")
|
||||||
|
res = await super().transfer_liquid(
|
||||||
sources,
|
sources,
|
||||||
targets,
|
targets,
|
||||||
tip_racks,
|
tip_racks,
|
||||||
@@ -817,6 +824,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
touch_tip=touch_tip,
|
touch_tip=touch_tip,
|
||||||
liquid_height=liquid_height,
|
liquid_height=liquid_height,
|
||||||
blow_out_air_volume=blow_out_air_volume,
|
blow_out_air_volume=blow_out_air_volume,
|
||||||
|
blow_out_air_volume_before=blow_out_air_volume_before,
|
||||||
spread=spread,
|
spread=spread,
|
||||||
is_96_well=is_96_well,
|
is_96_well=is_96_well,
|
||||||
mix_stage=mix_stage,
|
mix_stage=mix_stage,
|
||||||
@@ -827,6 +835,9 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
delays=delays,
|
delays=delays,
|
||||||
none_keys=none_keys,
|
none_keys=none_keys,
|
||||||
)
|
)
|
||||||
|
if self.step_mode:
|
||||||
|
await self.run_protocol()
|
||||||
|
return res
|
||||||
|
|
||||||
async def custom_delay(self, seconds=0, msg=None):
|
async def custom_delay(self, seconds=0, msg=None):
|
||||||
return await super().custom_delay(seconds, msg)
|
return await super().custom_delay(seconds, msg)
|
||||||
@@ -843,9 +854,10 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
offsets: Optional[Coordinate] = None,
|
offsets: Optional[Coordinate] = None,
|
||||||
mix_rate: Optional[float] = None,
|
mix_rate: Optional[float] = None,
|
||||||
none_keys: List[str] = [],
|
none_keys: List[str] = [],
|
||||||
|
use_channels: Optional[List[int]] = [0],
|
||||||
):
|
):
|
||||||
return await self._unilabos_backend.mix(
|
return await self._unilabos_backend.mix(
|
||||||
targets, mix_time, mix_vol, height_to_bottom, offsets, mix_rate, none_keys
|
targets, mix_time, mix_vol, height_to_bottom, offsets, mix_rate, none_keys, use_channels
|
||||||
)
|
)
|
||||||
|
|
||||||
def iter_tips(self, tip_racks: Sequence[TipRack]) -> Iterator[Resource]:
|
def iter_tips(self, tip_racks: Sequence[TipRack]) -> Iterator[Resource]:
|
||||||
@@ -1274,9 +1286,15 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
|||||||
offsets: Optional[Coordinate] = None,
|
offsets: Optional[Coordinate] = None,
|
||||||
mix_rate: Optional[float] = None,
|
mix_rate: Optional[float] = None,
|
||||||
none_keys: List[str] = [],
|
none_keys: List[str] = [],
|
||||||
|
use_channels: Optional[List[int]] = [0],
|
||||||
):
|
):
|
||||||
"""Mix liquid in the specified resources."""
|
"""Mix liquid in the specified resources."""
|
||||||
|
if use_channels == [0]:
|
||||||
|
axis = "Left"
|
||||||
|
elif use_channels == [1]:
|
||||||
|
axis = "Right"
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid use channels: " + str(use_channels))
|
||||||
plate_indexes = []
|
plate_indexes = []
|
||||||
for op in targets:
|
for op in targets:
|
||||||
deck = op.parent.parent.parent
|
deck = op.parent.parent.parent
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
"""虚拟样品演示设备 — 用于前端 sample tracking 功能的极简 demo"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualSampleDemo:
|
|
||||||
"""虚拟样品追踪演示设备,提供两种典型返回模式:
|
|
||||||
- measure_samples: 等长输入输出 (前端按 index 自动对齐)
|
|
||||||
- split_and_measure: 输出比输入长,附带 samples 列标注归属
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
||||||
if device_id is None and "id" in kwargs:
|
|
||||||
device_id = kwargs.pop("id")
|
|
||||||
if config is None and "config" in kwargs:
|
|
||||||
config = kwargs.pop("config")
|
|
||||||
|
|
||||||
self.device_id = device_id or "unknown_sample_demo"
|
|
||||||
self.config = config or {}
|
|
||||||
self.logger = logging.getLogger(f"VirtualSampleDemo.{self.device_id}")
|
|
||||||
self.data: Dict[str, Any] = {"status": "Idle"}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Action 1: 等长输入输出,无 samples 列
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
async def measure_samples(self, concentrations: List[float]) -> Dict[str, Any]:
|
|
||||||
"""模拟光度测量。absorbance = concentration * 0.05 + noise
|
|
||||||
|
|
||||||
入参和出参 list 长度相等,前端按 index 自动对齐。
|
|
||||||
"""
|
|
||||||
self.logger.info(f"measure_samples: concentrations={concentrations}")
|
|
||||||
absorbance = [round(c * 0.05 + random.gauss(0, 0.005), 4) for c in concentrations]
|
|
||||||
return {"concentrations": concentrations, "absorbance": absorbance}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Action 2: 输出比输入长,带 samples 列
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
async def split_and_measure(self, volumes: List[float], split_count: int = 3) -> Dict[str, Any]:
|
|
||||||
"""将每个样品均分为 split_count 份后逐份测量。
|
|
||||||
|
|
||||||
返回的 list 长度 = len(volumes) * split_count,
|
|
||||||
附带 samples 列标注每行属于第几个输入样品 (0-based index)。
|
|
||||||
"""
|
|
||||||
self.logger.info(f"split_and_measure: volumes={volumes}, split_count={split_count}")
|
|
||||||
out_volumes: List[float] = []
|
|
||||||
readings: List[float] = []
|
|
||||||
samples: List[int] = []
|
|
||||||
|
|
||||||
for idx, vol in enumerate(volumes):
|
|
||||||
split_vol = round(vol / split_count, 2)
|
|
||||||
for _ in range(split_count):
|
|
||||||
out_volumes.append(split_vol)
|
|
||||||
readings.append(round(random.uniform(0.1, 1.0), 4))
|
|
||||||
samples.append(idx)
|
|
||||||
|
|
||||||
return {"volumes": out_volumes, "readings": readings, "unilabos_samples": samples}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Action 3: 入参和出参都带 samples 列(不等长)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
async def analyze_readings(self, readings: List[float], samples: List[int]) -> Dict[str, Any]:
|
|
||||||
"""对 split_and_measure 的输出做二次分析。
|
|
||||||
|
|
||||||
入参 readings/samples 长度相同但 > 原始样品数,
|
|
||||||
出参同样带 samples 列,长度与入参一致。
|
|
||||||
"""
|
|
||||||
self.logger.info(f"analyze_readings: readings={readings}, samples={samples}")
|
|
||||||
scores: List[float] = []
|
|
||||||
passed: List[bool] = []
|
|
||||||
threshold = 0.4
|
|
||||||
|
|
||||||
for r in readings:
|
|
||||||
score = round(r * 100 + random.gauss(0, 2), 2)
|
|
||||||
scores.append(score)
|
|
||||||
passed.append(r >= threshold)
|
|
||||||
|
|
||||||
return {"scores": scores, "passed": passed, "unilabos_samples": samples}
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 状态属性
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@property
|
|
||||||
def status(self) -> str:
|
|
||||||
return self.data.get("status", "Idle")
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
Virtual Workbench Device - 模拟工作台设备
|
Virtual Workbench Device - 模拟工作台设备
|
||||||
包含:
|
包含:
|
||||||
- 1个机械臂 (每次操作3s, 独占锁)
|
- 1个机械臂 (每次操作3s, 独占锁)
|
||||||
- 3个加热台 (每次加热10s, 可并行)
|
- 3个加热台 (每次加热10s, 可并行)
|
||||||
|
|
||||||
工作流程:
|
工作流程:
|
||||||
1. A1-A5 物料同时启动, 竞争机械臂
|
1. A1-A5 物料同时启动,竞争机械臂
|
||||||
2. 机械臂将物料移动到空闲加热台
|
2. 机械臂将物料移动到空闲加热台
|
||||||
3. 加热完成后, 机械臂将物料移动到C1-C5
|
3. 加热完成后,机械臂将物料移动到C1-C5
|
||||||
|
|
||||||
注意: 调用来自线程池, 使用 threading.Lock 进行同步
|
注意:调用来自线程池,使用 threading.Lock 进行同步
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -21,11 +21,9 @@ from threading import Lock, RLock
|
|||||||
|
|
||||||
from typing_extensions import TypedDict
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from unilabos.registry.decorators import (
|
|
||||||
device, action, ActionInputHandle, ActionOutputHandle, DataSource, topic_config, not_action
|
|
||||||
)
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||||
from unilabos.resources.resource_tracker import SampleUUIDsType, LabSample
|
from unilabos.utils.decorator import not_action, always_free
|
||||||
|
from unilabos.resources.resource_tracker import SampleUUIDsType, LabSample, RETURN_UNILABOS_SAMPLES
|
||||||
|
|
||||||
|
|
||||||
# ============ TypedDict 返回类型定义 ============
|
# ============ TypedDict 返回类型定义 ============
|
||||||
@@ -59,8 +57,6 @@ class MoveToOutputResult(TypedDict):
|
|||||||
success: bool
|
success: bool
|
||||||
station_id: int
|
station_id: int
|
||||||
material_id: str
|
material_id: str
|
||||||
output_position: str
|
|
||||||
message: str
|
|
||||||
unilabos_samples: List[LabSample]
|
unilabos_samples: List[LabSample]
|
||||||
|
|
||||||
|
|
||||||
@@ -85,9 +81,9 @@ class HeatingStationState(Enum):
|
|||||||
"""加热台状态枚举"""
|
"""加热台状态枚举"""
|
||||||
|
|
||||||
IDLE = "idle" # 空闲
|
IDLE = "idle" # 空闲
|
||||||
OCCUPIED = "occupied" # 已放置物料, 等待加热
|
OCCUPIED = "occupied" # 已放置物料,等待加热
|
||||||
HEATING = "heating" # 加热中
|
HEATING = "heating" # 加热中
|
||||||
COMPLETED = "completed" # 加热完成, 等待取走
|
COMPLETED = "completed" # 加热完成,等待取走
|
||||||
|
|
||||||
|
|
||||||
class ArmState(Enum):
|
class ArmState(Enum):
|
||||||
@@ -109,24 +105,19 @@ class HeatingStation:
|
|||||||
heating_progress: float = 0.0
|
heating_progress: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
@device(
|
|
||||||
id="virtual_workbench",
|
|
||||||
category=["virtual_device"],
|
|
||||||
description="Virtual Workbench with 1 robotic arm and 3 heating stations for concurrent material processing",
|
|
||||||
)
|
|
||||||
class VirtualWorkbench:
|
class VirtualWorkbench:
|
||||||
"""
|
"""
|
||||||
Virtual Workbench Device - 虚拟工作台设备
|
Virtual Workbench Device - 虚拟工作台设备
|
||||||
|
|
||||||
模拟一个包含1个机械臂和3个加热台的工作站
|
模拟一个包含1个机械臂和3个加热台的工作站
|
||||||
- 机械臂操作耗时3秒, 同一时间只能执行一个操作
|
- 机械臂操作耗时3秒,同一时间只能执行一个操作
|
||||||
- 加热台加热耗时10秒, 3个加热台可并行工作
|
- 加热台加热耗时10秒,3个加热台可并行工作
|
||||||
|
|
||||||
工作流:
|
工作流:
|
||||||
1. 物料A1-A5并发启动(线程池), 竞争机械臂使用权
|
1. 物料A1-A5并发启动(线程池),竞争机械臂使用权
|
||||||
2. 获取机械臂后, 查找空闲加热台
|
2. 获取机械臂后,查找空闲加热台
|
||||||
3. 机械臂将物料放入加热台, 开始加热
|
3. 机械臂将物料放入加热台,开始加热
|
||||||
4. 加热完成后, 机械臂将物料移动到目标位置Cn
|
4. 加热完成后,机械臂将物料移动到目标位置Cn
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
_ros_node: BaseROS2DeviceNode
|
||||||
@@ -154,19 +145,19 @@ class VirtualWorkbench:
|
|||||||
self.HEATING_TIME = float(self.config.get("heating_time", self.HEATING_TIME))
|
self.HEATING_TIME = float(self.config.get("heating_time", self.HEATING_TIME))
|
||||||
self.NUM_HEATING_STATIONS = int(self.config.get("num_heating_stations", self.NUM_HEATING_STATIONS))
|
self.NUM_HEATING_STATIONS = int(self.config.get("num_heating_stations", self.NUM_HEATING_STATIONS))
|
||||||
|
|
||||||
# 机械臂状态和锁
|
# 机械臂状态和锁 (使用threading.Lock)
|
||||||
self._arm_lock = Lock()
|
self._arm_lock = Lock()
|
||||||
self._arm_state = ArmState.IDLE
|
self._arm_state = ArmState.IDLE
|
||||||
self._arm_current_task: Optional[str] = None
|
self._arm_current_task: Optional[str] = None
|
||||||
|
|
||||||
# 加热台状态
|
# 加热台状态 (station_id -> HeatingStation) - 立即初始化,不依赖initialize()
|
||||||
self._heating_stations: Dict[int, HeatingStation] = {
|
self._heating_stations: Dict[int, HeatingStation] = {
|
||||||
i: HeatingStation(station_id=i) for i in range(1, self.NUM_HEATING_STATIONS + 1)
|
i: HeatingStation(station_id=i) for i in range(1, self.NUM_HEATING_STATIONS + 1)
|
||||||
}
|
}
|
||||||
self._stations_lock = RLock()
|
self._stations_lock = RLock() # 可重入锁,保护加热台状态
|
||||||
|
|
||||||
# 任务追踪
|
# 任务追踪
|
||||||
self._active_tasks: Dict[str, Dict[str, Any]] = {}
|
self._active_tasks: Dict[str, Dict[str, Any]] = {} # material_id -> task_info
|
||||||
self._tasks_lock = Lock()
|
self._tasks_lock = Lock()
|
||||||
|
|
||||||
# 处理其他kwargs参数
|
# 处理其他kwargs参数
|
||||||
@@ -192,6 +183,7 @@ class VirtualWorkbench:
|
|||||||
"""初始化虚拟工作台"""
|
"""初始化虚拟工作台"""
|
||||||
self.logger.info(f"初始化虚拟工作台 {self.device_id}")
|
self.logger.info(f"初始化虚拟工作台 {self.device_id}")
|
||||||
|
|
||||||
|
# 重置加热台状态 (已在__init__中创建,这里重置为初始状态)
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
for station in self._heating_stations.values():
|
for station in self._heating_stations.values():
|
||||||
station.state = HeatingStationState.IDLE
|
station.state = HeatingStationState.IDLE
|
||||||
@@ -199,6 +191,7 @@ class VirtualWorkbench:
|
|||||||
station.material_number = None
|
station.material_number = None
|
||||||
station.heating_progress = 0.0
|
station.heating_progress = 0.0
|
||||||
|
|
||||||
|
# 初始化状态
|
||||||
self.data.update(
|
self.data.update(
|
||||||
{
|
{
|
||||||
"status": "Ready",
|
"status": "Ready",
|
||||||
@@ -264,7 +257,11 @@ class VirtualWorkbench:
|
|||||||
self.data["message"] = message
|
self.data["message"] = message
|
||||||
|
|
||||||
def _find_available_heating_station(self) -> Optional[int]:
|
def _find_available_heating_station(self) -> Optional[int]:
|
||||||
"""查找空闲的加热台"""
|
"""查找空闲的加热台
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
空闲加热台ID,如果没有则返回None
|
||||||
|
"""
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
for station_id, station in self._heating_stations.items():
|
for station_id, station in self._heating_stations.items():
|
||||||
if station.state == HeatingStationState.IDLE:
|
if station.state == HeatingStationState.IDLE:
|
||||||
@@ -272,12 +269,23 @@ class VirtualWorkbench:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _acquire_arm(self, task_description: str) -> bool:
|
def _acquire_arm(self, task_description: str) -> bool:
|
||||||
"""获取机械臂使用权(阻塞直到获取)"""
|
"""获取机械臂使用权(阻塞直到获取)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_description: 任务描述,用于日志
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功获取
|
||||||
|
"""
|
||||||
self.logger.info(f"[{task_description}] 等待获取机械臂...")
|
self.logger.info(f"[{task_description}] 等待获取机械臂...")
|
||||||
|
|
||||||
|
# 阻塞等待获取锁
|
||||||
self._arm_lock.acquire()
|
self._arm_lock.acquire()
|
||||||
|
|
||||||
self._arm_state = ArmState.BUSY
|
self._arm_state = ArmState.BUSY
|
||||||
self._arm_current_task = task_description
|
self._arm_current_task = task_description
|
||||||
self._update_data_status(f"机械臂执行: {task_description}")
|
self._update_data_status(f"机械臂执行: {task_description}")
|
||||||
|
|
||||||
self.logger.info(f"[{task_description}] 成功获取机械臂使用权")
|
self.logger.info(f"[{task_description}] 成功获取机械臂使用权")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -290,22 +298,6 @@ class VirtualWorkbench:
|
|||||||
self._update_data_status(f"机械臂已释放 (完成: {task})")
|
self._update_data_status(f"机械臂已释放 (完成: {task})")
|
||||||
self.logger.info(f"机械臂已释放 (完成: {task})")
|
self.logger.info(f"机械臂已释放 (完成: {task})")
|
||||||
|
|
||||||
@action(
|
|
||||||
auto_prefix=True,
|
|
||||||
description="批量准备物料 - 虚拟起始节点, 生成A1-A5物料, 输出5个handle供后续节点使用",
|
|
||||||
handles=[
|
|
||||||
ActionOutputHandle(key="channel_1", data_type="workbench_material",
|
|
||||||
label="实验1", data_key="material_1", data_source=DataSource.EXECUTOR),
|
|
||||||
ActionOutputHandle(key="channel_2", data_type="workbench_material",
|
|
||||||
label="实验2", data_key="material_2", data_source=DataSource.EXECUTOR),
|
|
||||||
ActionOutputHandle(key="channel_3", data_type="workbench_material",
|
|
||||||
label="实验3", data_key="material_3", data_source=DataSource.EXECUTOR),
|
|
||||||
ActionOutputHandle(key="channel_4", data_type="workbench_material",
|
|
||||||
label="实验4", data_key="material_4", data_source=DataSource.EXECUTOR),
|
|
||||||
ActionOutputHandle(key="channel_5", data_type="workbench_material",
|
|
||||||
label="实验5", data_key="material_5", data_source=DataSource.EXECUTOR),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def prepare_materials(
|
def prepare_materials(
|
||||||
self,
|
self,
|
||||||
sample_uuids: SampleUUIDsType,
|
sample_uuids: SampleUUIDsType,
|
||||||
@@ -314,14 +306,19 @@ class VirtualWorkbench:
|
|||||||
"""
|
"""
|
||||||
批量准备物料 - 虚拟起始节点
|
批量准备物料 - 虚拟起始节点
|
||||||
|
|
||||||
作为工作流的起始节点, 生成指定数量的物料编号供后续节点使用。
|
作为工作流的起始节点,生成指定数量的物料编号供后续节点使用。
|
||||||
输出5个handle (material_1 ~ material_5), 分别对应实验1~5。
|
输出5个handle (material_1 ~ material_5),分别对应实验1~5。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
count: 待生成的物料数量,默认5 (生成 A1-A5)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PrepareMaterialsResult: 包含 material_1 ~ material_5 用于传递给 move_to_heating_station
|
||||||
"""
|
"""
|
||||||
|
# 生成物料列表 A1 - A{count}
|
||||||
materials = [i for i in range(1, count + 1)]
|
materials = [i for i in range(1, count + 1)]
|
||||||
|
|
||||||
self.logger.info(
|
self.logger.info(f"[准备物料] 生成 {count} 个物料: " f"A1-A{count} -> material_1~material_{count}")
|
||||||
f"[准备物料] 生成 {count} 个物料: A1-A{count} -> material_1~material_{count}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
@@ -332,28 +329,9 @@ class VirtualWorkbench:
|
|||||||
"material_4": materials[3] if len(materials) > 3 else 0,
|
"material_4": materials[3] if len(materials) > 3 else 0,
|
||||||
"material_5": materials[4] if len(materials) > 4 else 0,
|
"material_5": materials[4] if len(materials) > 4 else 0,
|
||||||
"message": f"已准备 {count} 个物料: A1-A{count}",
|
"message": f"已准备 {count} 个物料: A1-A{count}",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for sample_uuid, content in sample_uuids.items()]
|
||||||
LabSample(
|
|
||||||
sample_uuid=sample_uuid,
|
|
||||||
oss_path="",
|
|
||||||
extra={"material_uuid": content} if isinstance(content, str) else (content.serialize() if content else {}),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action(
|
|
||||||
auto_prefix=True,
|
|
||||||
description="将物料从An位置移动到空闲加热台, 返回分配的加热台ID",
|
|
||||||
handles=[
|
|
||||||
ActionInputHandle(key="material_input", data_type="workbench_material",
|
|
||||||
label="物料编号", data_key="material_number", data_source=DataSource.HANDLE),
|
|
||||||
ActionOutputHandle(key="heating_station_output", data_type="workbench_station",
|
|
||||||
label="加热台ID", data_key="station_id", data_source=DataSource.EXECUTOR),
|
|
||||||
ActionOutputHandle(key="material_number_output", data_type="workbench_material",
|
|
||||||
label="物料编号", data_key="material_number", data_source=DataSource.EXECUTOR),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def move_to_heating_station(
|
def move_to_heating_station(
|
||||||
self,
|
self,
|
||||||
sample_uuids: SampleUUIDsType,
|
sample_uuids: SampleUUIDsType,
|
||||||
@@ -362,12 +340,20 @@ class VirtualWorkbench:
|
|||||||
"""
|
"""
|
||||||
将物料从An位置移动到加热台
|
将物料从An位置移动到加热台
|
||||||
|
|
||||||
多线程并发调用时, 会竞争机械臂使用权, 并自动查找空闲加热台
|
多线程并发调用时,会竞争机械臂使用权,并自动查找空闲加热台
|
||||||
|
|
||||||
|
Args:
|
||||||
|
material_number: 物料编号 (1-5)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MoveToHeatingStationResult: 包含 station_id, material_number 等用于传递给下一个节点
|
||||||
"""
|
"""
|
||||||
|
# 根据物料编号生成物料ID
|
||||||
material_id = f"A{material_number}"
|
material_id = f"A{material_number}"
|
||||||
task_desc = f"移动{material_id}到加热台"
|
task_desc = f"移动{material_id}到加热台"
|
||||||
self.logger.info(f"[任务] {task_desc} - 开始执行")
|
self.logger.info(f"[任务] {task_desc} - 开始执行")
|
||||||
|
|
||||||
|
# 记录任务
|
||||||
with self._tasks_lock:
|
with self._tasks_lock:
|
||||||
self._active_tasks[material_id] = {
|
self._active_tasks[material_id] = {
|
||||||
"status": "waiting_for_arm",
|
"status": "waiting_for_arm",
|
||||||
@@ -375,27 +361,33 @@ class VirtualWorkbench:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 步骤1: 等待获取机械臂使用权(竞争)
|
||||||
with self._tasks_lock:
|
with self._tasks_lock:
|
||||||
self._active_tasks[material_id]["status"] = "waiting_for_arm"
|
self._active_tasks[material_id]["status"] = "waiting_for_arm"
|
||||||
self._acquire_arm(task_desc)
|
self._acquire_arm(task_desc)
|
||||||
|
|
||||||
|
# 步骤2: 查找空闲加热台
|
||||||
with self._tasks_lock:
|
with self._tasks_lock:
|
||||||
self._active_tasks[material_id]["status"] = "finding_station"
|
self._active_tasks[material_id]["status"] = "finding_station"
|
||||||
station_id = None
|
station_id = None
|
||||||
|
|
||||||
|
# 循环等待直到找到空闲加热台
|
||||||
while station_id is None:
|
while station_id is None:
|
||||||
station_id = self._find_available_heating_station()
|
station_id = self._find_available_heating_station()
|
||||||
if station_id is None:
|
if station_id is None:
|
||||||
self.logger.info(f"[{material_id}] 没有空闲加热台, 等待中...")
|
self.logger.info(f"[{material_id}] 没有空闲加热台,等待中...")
|
||||||
|
# 释放机械臂,等待后重试
|
||||||
self._release_arm()
|
self._release_arm()
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
self._acquire_arm(task_desc)
|
self._acquire_arm(task_desc)
|
||||||
|
|
||||||
|
# 步骤3: 占用加热台 - 立即标记为OCCUPIED,防止其他任务选择同一加热台
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
self._heating_stations[station_id].state = HeatingStationState.OCCUPIED
|
self._heating_stations[station_id].state = HeatingStationState.OCCUPIED
|
||||||
self._heating_stations[station_id].current_material = material_id
|
self._heating_stations[station_id].current_material = material_id
|
||||||
self._heating_stations[station_id].material_number = material_number
|
self._heating_stations[station_id].material_number = material_number
|
||||||
|
|
||||||
|
# 步骤4: 模拟机械臂移动操作 (3秒)
|
||||||
with self._tasks_lock:
|
with self._tasks_lock:
|
||||||
self._active_tasks[material_id]["status"] = "arm_moving"
|
self._active_tasks[material_id]["status"] = "arm_moving"
|
||||||
self._active_tasks[material_id]["assigned_station"] = station_id
|
self._active_tasks[material_id]["assigned_station"] = station_id
|
||||||
@@ -403,11 +395,11 @@ class VirtualWorkbench:
|
|||||||
|
|
||||||
time.sleep(self.ARM_OPERATION_TIME)
|
time.sleep(self.ARM_OPERATION_TIME)
|
||||||
|
|
||||||
|
# 步骤5: 放入加热台完成
|
||||||
self._update_data_status(f"{material_id}已放入加热台{station_id}")
|
self._update_data_status(f"{material_id}已放入加热台{station_id}")
|
||||||
self.logger.info(
|
self.logger.info(f"[{material_id}] 已放入加热台{station_id} (用时{self.ARM_OPERATION_TIME}s)")
|
||||||
f"[{material_id}] 已放入加热台{station_id} (用时{self.ARM_OPERATION_TIME}s)"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# 释放机械臂
|
||||||
self._release_arm()
|
self._release_arm()
|
||||||
|
|
||||||
with self._tasks_lock:
|
with self._tasks_lock:
|
||||||
@@ -420,16 +412,8 @@ class VirtualWorkbench:
|
|||||||
"material_number": material_number,
|
"material_number": material_number,
|
||||||
"message": f"{material_id}已成功移动到加热台{station_id}",
|
"message": f"{material_id}已成功移动到加热台{station_id}",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -443,33 +427,11 @@ class VirtualWorkbench:
|
|||||||
"material_number": material_number,
|
"material_number": material_number,
|
||||||
"message": f"移动失败: {str(e)}",
|
"message": f"移动失败: {str(e)}",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action(
|
@always_free
|
||||||
auto_prefix=True,
|
|
||||||
always_free=True,
|
|
||||||
description="启动指定加热台的加热程序",
|
|
||||||
handles=[
|
|
||||||
ActionInputHandle(key="station_id_input", data_type="workbench_station",
|
|
||||||
label="加热台ID", data_key="station_id", data_source=DataSource.HANDLE),
|
|
||||||
ActionInputHandle(key="material_number_input", data_type="workbench_material",
|
|
||||||
label="物料编号", data_key="material_number", data_source=DataSource.HANDLE),
|
|
||||||
ActionOutputHandle(key="heating_done_station", data_type="workbench_station",
|
|
||||||
label="加热完成-加热台ID", data_key="station_id", data_source=DataSource.EXECUTOR),
|
|
||||||
ActionOutputHandle(key="heating_done_material", data_type="workbench_material",
|
|
||||||
label="加热完成-物料编号", data_key="material_number", data_source=DataSource.EXECUTOR),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def start_heating(
|
def start_heating(
|
||||||
self,
|
self,
|
||||||
sample_uuids: SampleUUIDsType,
|
sample_uuids: SampleUUIDsType,
|
||||||
@@ -478,6 +440,13 @@ class VirtualWorkbench:
|
|||||||
) -> StartHeatingResult:
|
) -> StartHeatingResult:
|
||||||
"""
|
"""
|
||||||
启动指定加热台的加热程序
|
启动指定加热台的加热程序
|
||||||
|
|
||||||
|
Args:
|
||||||
|
station_id: 加热台ID (1-3),从 move_to_heating_station 的 handle 传入
|
||||||
|
material_number: 物料编号,从 move_to_heating_station 的 handle 传入
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
StartHeatingResult: 包含 station_id, material_number 等用于传递给下一个节点
|
||||||
"""
|
"""
|
||||||
self.logger.info(f"[加热台{station_id}] 开始加热")
|
self.logger.info(f"[加热台{station_id}] 开始加热")
|
||||||
|
|
||||||
@@ -489,16 +458,8 @@ class VirtualWorkbench:
|
|||||||
"material_number": material_number,
|
"material_number": material_number,
|
||||||
"message": f"无效的加热台ID: {station_id}",
|
"message": f"无效的加热台ID: {station_id}",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
@@ -512,16 +473,8 @@ class VirtualWorkbench:
|
|||||||
"material_number": material_number,
|
"material_number": material_number,
|
||||||
"message": f"加热台{station_id}上没有物料",
|
"message": f"加热台{station_id}上没有物料",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if station.state == HeatingStationState.HEATING:
|
if station.state == HeatingStationState.HEATING:
|
||||||
@@ -532,20 +485,13 @@ class VirtualWorkbench:
|
|||||||
"material_number": material_number,
|
"material_number": material_number,
|
||||||
"message": f"加热台{station_id}已经在加热中",
|
"message": f"加热台{station_id}已经在加热中",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
material_id = station.current_material
|
material_id = station.current_material
|
||||||
|
|
||||||
|
# 开始加热
|
||||||
station.state = HeatingStationState.HEATING
|
station.state = HeatingStationState.HEATING
|
||||||
station.heating_start_time = time.time()
|
station.heating_start_time = time.time()
|
||||||
station.heating_progress = 0.0
|
station.heating_progress = 0.0
|
||||||
@@ -556,6 +502,7 @@ class VirtualWorkbench:
|
|||||||
|
|
||||||
self._update_data_status(f"加热台{station_id}开始加热{material_id}")
|
self._update_data_status(f"加热台{station_id}开始加热{material_id}")
|
||||||
|
|
||||||
|
# 打印当前所有正在加热的台位
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
heating_list = [
|
heating_list = [
|
||||||
f"加热台{sid}:{s.current_material}"
|
f"加热台{sid}:{s.current_material}"
|
||||||
@@ -564,6 +511,7 @@ class VirtualWorkbench:
|
|||||||
]
|
]
|
||||||
self.logger.info(f"[并行加热] 当前同时加热中: {', '.join(heating_list)}")
|
self.logger.info(f"[并行加热] 当前同时加热中: {', '.join(heating_list)}")
|
||||||
|
|
||||||
|
# 模拟加热过程
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
last_countdown_log = start_time
|
last_countdown_log = start_time
|
||||||
while True:
|
while True:
|
||||||
@@ -576,6 +524,7 @@ class VirtualWorkbench:
|
|||||||
|
|
||||||
self._update_data_status(f"加热台{station_id}加热中: {progress:.1f}%")
|
self._update_data_status(f"加热台{station_id}加热中: {progress:.1f}%")
|
||||||
|
|
||||||
|
# 每5秒打印一次倒计时
|
||||||
if time.time() - last_countdown_log >= 5.0:
|
if time.time() - last_countdown_log >= 5.0:
|
||||||
self.logger.info(f"[加热台{station_id}] {material_id} 剩余 {remaining:.1f}s")
|
self.logger.info(f"[加热台{station_id}] {material_id} 剩余 {remaining:.1f}s")
|
||||||
last_countdown_log = time.time()
|
last_countdown_log = time.time()
|
||||||
@@ -585,6 +534,7 @@ class VirtualWorkbench:
|
|||||||
|
|
||||||
time.sleep(1.0)
|
time.sleep(1.0)
|
||||||
|
|
||||||
|
# 加热完成
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
self._heating_stations[station_id].state = HeatingStationState.COMPLETED
|
self._heating_stations[station_id].state = HeatingStationState.COMPLETED
|
||||||
self._heating_stations[station_id].heating_progress = 100.0
|
self._heating_stations[station_id].heating_progress = 100.0
|
||||||
@@ -603,28 +553,10 @@ class VirtualWorkbench:
|
|||||||
"material_number": material_number,
|
"material_number": material_number,
|
||||||
"message": f"加热台{station_id}加热完成",
|
"message": f"加热台{station_id}加热完成",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action(
|
|
||||||
auto_prefix=True,
|
|
||||||
description="将物料从加热台移动到输出位置Cn",
|
|
||||||
handles=[
|
|
||||||
ActionInputHandle(key="output_station_input", data_type="workbench_station",
|
|
||||||
label="加热台ID", data_key="station_id", data_source=DataSource.HANDLE),
|
|
||||||
ActionInputHandle(key="output_material_input", data_type="workbench_material",
|
|
||||||
label="物料编号", data_key="material_number", data_source=DataSource.HANDLE),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def move_to_output(
|
def move_to_output(
|
||||||
self,
|
self,
|
||||||
sample_uuids: SampleUUIDsType,
|
sample_uuids: SampleUUIDsType,
|
||||||
@@ -633,8 +565,15 @@ class VirtualWorkbench:
|
|||||||
) -> MoveToOutputResult:
|
) -> MoveToOutputResult:
|
||||||
"""
|
"""
|
||||||
将物料从加热台移动到输出位置Cn
|
将物料从加热台移动到输出位置Cn
|
||||||
|
|
||||||
|
Args:
|
||||||
|
station_id: 加热台ID (1-3),从 start_heating 的 handle 传入
|
||||||
|
material_number: 物料编号,从 start_heating 的 handle 传入,用于确定输出位置 Cn
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MoveToOutputResult: 包含执行结果
|
||||||
"""
|
"""
|
||||||
output_number = material_number
|
output_number = material_number # 物料编号决定输出位置
|
||||||
|
|
||||||
if station_id not in self._heating_stations:
|
if station_id not in self._heating_stations:
|
||||||
return {
|
return {
|
||||||
@@ -644,16 +583,8 @@ class VirtualWorkbench:
|
|||||||
"output_position": f"C{output_number}",
|
"output_position": f"C{output_number}",
|
||||||
"message": f"无效的加热台ID: {station_id}",
|
"message": f"无效的加热台ID: {station_id}",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
@@ -668,16 +599,8 @@ class VirtualWorkbench:
|
|||||||
"output_position": f"C{output_number}",
|
"output_position": f"C{output_number}",
|
||||||
"message": f"加热台{station_id}上没有物料",
|
"message": f"加热台{station_id}上没有物料",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if station.state != HeatingStationState.COMPLETED:
|
if station.state != HeatingStationState.COMPLETED:
|
||||||
@@ -688,16 +611,8 @@ class VirtualWorkbench:
|
|||||||
"output_position": f"C{output_number}",
|
"output_position": f"C{output_number}",
|
||||||
"message": f"加热台{station_id}尚未完成加热 (当前状态: {station.state.value})",
|
"message": f"加热台{station_id}尚未完成加热 (当前状态: {station.state.value})",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output_position = f"C{output_number}"
|
output_position = f"C{output_number}"
|
||||||
@@ -709,17 +624,18 @@ class VirtualWorkbench:
|
|||||||
if material_id in self._active_tasks:
|
if material_id in self._active_tasks:
|
||||||
self._active_tasks[material_id]["status"] = "waiting_for_arm_output"
|
self._active_tasks[material_id]["status"] = "waiting_for_arm_output"
|
||||||
|
|
||||||
|
# 获取机械臂
|
||||||
self._acquire_arm(task_desc)
|
self._acquire_arm(task_desc)
|
||||||
|
|
||||||
with self._tasks_lock:
|
with self._tasks_lock:
|
||||||
if material_id in self._active_tasks:
|
if material_id in self._active_tasks:
|
||||||
self._active_tasks[material_id]["status"] = "arm_moving_to_output"
|
self._active_tasks[material_id]["status"] = "arm_moving_to_output"
|
||||||
|
|
||||||
self.logger.info(
|
# 模拟机械臂操作 (3秒)
|
||||||
f"[{material_id}] 机械臂正在从加热台{station_id}取出并移动到{output_position}..."
|
self.logger.info(f"[{material_id}] 机械臂正在从加热台{station_id}取出并移动到{output_position}...")
|
||||||
)
|
|
||||||
time.sleep(self.ARM_OPERATION_TIME)
|
time.sleep(self.ARM_OPERATION_TIME)
|
||||||
|
|
||||||
|
# 清空加热台
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
self._heating_stations[station_id].state = HeatingStationState.IDLE
|
self._heating_stations[station_id].state = HeatingStationState.IDLE
|
||||||
self._heating_stations[station_id].current_material = None
|
self._heating_stations[station_id].current_material = None
|
||||||
@@ -727,17 +643,17 @@ class VirtualWorkbench:
|
|||||||
self._heating_stations[station_id].heating_progress = 0.0
|
self._heating_stations[station_id].heating_progress = 0.0
|
||||||
self._heating_stations[station_id].heating_start_time = None
|
self._heating_stations[station_id].heating_start_time = None
|
||||||
|
|
||||||
|
# 释放机械臂
|
||||||
self._release_arm()
|
self._release_arm()
|
||||||
|
|
||||||
|
# 任务完成
|
||||||
with self._tasks_lock:
|
with self._tasks_lock:
|
||||||
if material_id in self._active_tasks:
|
if material_id in self._active_tasks:
|
||||||
self._active_tasks[material_id]["status"] = "completed"
|
self._active_tasks[material_id]["status"] = "completed"
|
||||||
self._active_tasks[material_id]["end_time"] = time.time()
|
self._active_tasks[material_id]["end_time"] = time.time()
|
||||||
|
|
||||||
self._update_data_status(f"{material_id}已移动到{output_position}")
|
self._update_data_status(f"{material_id}已移动到{output_position}")
|
||||||
self.logger.info(
|
self.logger.info(f"[{material_id}] 已成功移动到{output_position} (用时{self.ARM_OPERATION_TIME}s)")
|
||||||
f"[{material_id}] 已成功移动到{output_position} (用时{self.ARM_OPERATION_TIME}s)"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
@@ -746,17 +662,8 @@ class VirtualWorkbench:
|
|||||||
"output_position": output_position,
|
"output_position": output_position,
|
||||||
"message": f"{material_id}已成功移动到{output_position}",
|
"message": f"{material_id}已成功移动到{output_position}",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str)
|
|
||||||
else (content.serialize() if content is not None else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -770,105 +677,83 @@ class VirtualWorkbench:
|
|||||||
"output_position": output_position,
|
"output_position": output_position,
|
||||||
"message": f"移动失败: {str(e)}",
|
"message": f"移动失败: {str(e)}",
|
||||||
"unilabos_samples": [
|
"unilabos_samples": [
|
||||||
LabSample(
|
LabSample(sample_uuid=sample_uuid, oss_path="", extra={"material_uuid": content} if isinstance(content, str) else content.serialize()) for
|
||||||
sample_uuid=sample_uuid,
|
sample_uuid, content in sample_uuids.items()]
|
||||||
oss_path="",
|
|
||||||
extra=(
|
|
||||||
{"material_uuid": content}
|
|
||||||
if isinstance(content, str) else (content.serialize() if content else {})
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for sample_uuid, content in sample_uuids.items()
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============ 状态属性 ============
|
# ============ 状态属性 ============
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self.data.get("status", "Unknown")
|
return self.data.get("status", "Unknown")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def arm_state(self) -> str:
|
def arm_state(self) -> str:
|
||||||
return self._arm_state.value
|
return self._arm_state.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def arm_current_task(self) -> str:
|
def arm_current_task(self) -> str:
|
||||||
return self._arm_current_task or ""
|
return self._arm_current_task or ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_1_state(self) -> str:
|
def heating_station_1_state(self) -> str:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(1)
|
station = self._heating_stations.get(1)
|
||||||
return station.state.value if station else "unknown"
|
return station.state.value if station else "unknown"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_1_material(self) -> str:
|
def heating_station_1_material(self) -> str:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(1)
|
station = self._heating_stations.get(1)
|
||||||
return station.current_material or "" if station else ""
|
return station.current_material or "" if station else ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_1_progress(self) -> float:
|
def heating_station_1_progress(self) -> float:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(1)
|
station = self._heating_stations.get(1)
|
||||||
return station.heating_progress if station else 0.0
|
return station.heating_progress if station else 0.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_2_state(self) -> str:
|
def heating_station_2_state(self) -> str:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(2)
|
station = self._heating_stations.get(2)
|
||||||
return station.state.value if station else "unknown"
|
return station.state.value if station else "unknown"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_2_material(self) -> str:
|
def heating_station_2_material(self) -> str:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(2)
|
station = self._heating_stations.get(2)
|
||||||
return station.current_material or "" if station else ""
|
return station.current_material or "" if station else ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_2_progress(self) -> float:
|
def heating_station_2_progress(self) -> float:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(2)
|
station = self._heating_stations.get(2)
|
||||||
return station.heating_progress if station else 0.0
|
return station.heating_progress if station else 0.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_3_state(self) -> str:
|
def heating_station_3_state(self) -> str:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(3)
|
station = self._heating_stations.get(3)
|
||||||
return station.state.value if station else "unknown"
|
return station.state.value if station else "unknown"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_3_material(self) -> str:
|
def heating_station_3_material(self) -> str:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(3)
|
station = self._heating_stations.get(3)
|
||||||
return station.current_material or "" if station else ""
|
return station.current_material or "" if station else ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def heating_station_3_progress(self) -> float:
|
def heating_station_3_progress(self) -> float:
|
||||||
with self._stations_lock:
|
with self._stations_lock:
|
||||||
station = self._heating_stations.get(3)
|
station = self._heating_stations.get(3)
|
||||||
return station.heating_progress if station else 0.0
|
return station.heating_progress if station else 0.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def active_tasks_count(self) -> int:
|
def active_tasks_count(self) -> int:
|
||||||
with self._tasks_lock:
|
with self._tasks_lock:
|
||||||
return len(self._active_tasks)
|
return len(self._active_tasks)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@topic_config()
|
|
||||||
def message(self) -> str:
|
def message(self) -> str:
|
||||||
return self.data.get("message", "")
|
return self.data.get("message", "")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,658 +0,0 @@
|
|||||||
"""
|
|
||||||
装饰器注册表系统
|
|
||||||
|
|
||||||
通过 @device, @action, @resource 装饰器替代 YAML 配置文件来定义设备/动作/资源注册表信息。
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
from unilabos.registry.decorators import (
|
|
||||||
device, action, resource,
|
|
||||||
InputHandle, OutputHandle,
|
|
||||||
ActionInputHandle, ActionOutputHandle,
|
|
||||||
HardwareInterface, Side, DataSource, NodeType,
|
|
||||||
)
|
|
||||||
|
|
||||||
@device(
|
|
||||||
id="solenoid_valve.mock",
|
|
||||||
category=["pump_and_valve"],
|
|
||||||
description="模拟电磁阀设备",
|
|
||||||
handles=[
|
|
||||||
InputHandle(key="in", data_type="fluid", label="in", side=Side.NORTH),
|
|
||||||
OutputHandle(key="out", data_type="fluid", label="out", side=Side.SOUTH),
|
|
||||||
],
|
|
||||||
hardware_interface=HardwareInterface(
|
|
||||||
name="hardware_interface",
|
|
||||||
read="send_command",
|
|
||||||
write="send_command",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
class SolenoidValveMock:
|
|
||||||
@action(action_type=EmptyIn)
|
|
||||||
def close(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
@action(
|
|
||||||
handles=[
|
|
||||||
ActionInputHandle(key="in", data_type="fluid", label="in"),
|
|
||||||
ActionOutputHandle(key="out", data_type="fluid", label="out"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def set_valve_position(self, position):
|
|
||||||
...
|
|
||||||
|
|
||||||
# 无 @action 装饰器 => auto- 前缀动作
|
|
||||||
def is_open(self):
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
from functools import wraps
|
|
||||||
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
|
||||||
|
|
||||||
F = TypeVar("F", bound=Callable[..., Any])
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 枚举
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class Side(str, Enum):
|
|
||||||
"""UI 上 Handle 的显示位置"""
|
|
||||||
|
|
||||||
NORTH = "NORTH"
|
|
||||||
SOUTH = "SOUTH"
|
|
||||||
EAST = "EAST"
|
|
||||||
WEST = "WEST"
|
|
||||||
|
|
||||||
|
|
||||||
class DataSource(str, Enum):
|
|
||||||
"""Handle 的数据来源"""
|
|
||||||
|
|
||||||
HANDLE = "handle" # 从上游 handle 获取数据 (用于 InputHandle)
|
|
||||||
EXECUTOR = "executor" # 从执行器输出数据 (用于 OutputHandle)
|
|
||||||
|
|
||||||
|
|
||||||
class NodeType(str, Enum):
|
|
||||||
"""动作的节点类型(用于区分 ILab 节点和人工确认节点等)"""
|
|
||||||
|
|
||||||
ILAB = "ILab"
|
|
||||||
MANUAL_CONFIRM = "manual_confirm"
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Device / Resource Handle (设备/资源级别端口, 序列化时包含 io_type)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class _DeviceHandleBase(BaseModel):
|
|
||||||
"""设备/资源端口基类 (内部使用)"""
|
|
||||||
|
|
||||||
model_config = ConfigDict(populate_by_name=True)
|
|
||||||
|
|
||||||
key: str = Field(serialization_alias="handler_key")
|
|
||||||
data_type: str
|
|
||||||
label: str
|
|
||||||
side: Optional[Side] = None
|
|
||||||
data_key: Optional[str] = None
|
|
||||||
data_source: Optional[str] = None
|
|
||||||
description: Optional[str] = None
|
|
||||||
|
|
||||||
# 子类覆盖
|
|
||||||
io_type: str = ""
|
|
||||||
|
|
||||||
def to_registry_dict(self) -> Dict[str, Any]:
|
|
||||||
return self.model_dump(by_alias=True, exclude_none=True)
|
|
||||||
|
|
||||||
|
|
||||||
class InputHandle(_DeviceHandleBase):
|
|
||||||
"""
|
|
||||||
输入端口 (io_type="target"), 用于 @device / @resource handles
|
|
||||||
|
|
||||||
Example:
|
|
||||||
InputHandle(key="in", data_type="fluid", label="in", side=Side.NORTH)
|
|
||||||
"""
|
|
||||||
|
|
||||||
io_type: str = "target"
|
|
||||||
|
|
||||||
|
|
||||||
class OutputHandle(_DeviceHandleBase):
|
|
||||||
"""
|
|
||||||
输出端口 (io_type="source"), 用于 @device / @resource handles
|
|
||||||
|
|
||||||
Example:
|
|
||||||
OutputHandle(key="out", data_type="fluid", label="out", side=Side.SOUTH)
|
|
||||||
"""
|
|
||||||
|
|
||||||
io_type: str = "source"
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Action Handle (动作级别端口, 序列化时不含 io_type, 按类型自动分组)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class _ActionHandleBase(BaseModel):
|
|
||||||
"""动作端口基类 (内部使用)"""
|
|
||||||
|
|
||||||
model_config = ConfigDict(populate_by_name=True)
|
|
||||||
|
|
||||||
key: str = Field(serialization_alias="handler_key")
|
|
||||||
data_type: str
|
|
||||||
label: str
|
|
||||||
side: Optional[Side] = None
|
|
||||||
data_key: Optional[str] = None
|
|
||||||
data_source: Optional[str] = None
|
|
||||||
description: Optional[str] = None
|
|
||||||
io_type: Optional[str] = None # source/sink (dataflow) or target/source (device-style)
|
|
||||||
|
|
||||||
def to_registry_dict(self) -> Dict[str, Any]:
|
|
||||||
return self.model_dump(by_alias=True, exclude_none=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ActionInputHandle(_ActionHandleBase):
|
|
||||||
"""
|
|
||||||
动作输入端口, 用于 @action handles, 序列化后归入 "input" 组
|
|
||||||
|
|
||||||
Example:
|
|
||||||
ActionInputHandle(
|
|
||||||
key="material_input", data_type="workbench_material",
|
|
||||||
label="物料编号", data_key="material_number", data_source="handle",
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ActionOutputHandle(_ActionHandleBase):
|
|
||||||
"""
|
|
||||||
动作输出端口, 用于 @action handles, 序列化后归入 "output" 组
|
|
||||||
|
|
||||||
Example:
|
|
||||||
ActionOutputHandle(
|
|
||||||
key="station_output", data_type="workbench_station",
|
|
||||||
label="加热台ID", data_key="station_id", data_source="executor",
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# HardwareInterface
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class HardwareInterface(BaseModel):
|
|
||||||
"""
|
|
||||||
硬件通信接口定义
|
|
||||||
|
|
||||||
描述设备与底层硬件通信的方式 (串口、Modbus 等)。
|
|
||||||
|
|
||||||
Example:
|
|
||||||
HardwareInterface(name="hardware_interface", read="send_command", write="send_command")
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
read: Optional[str] = None
|
|
||||||
write: Optional[str] = None
|
|
||||||
extra_info: Optional[List[str]] = None
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 全局注册表 -- 记录所有被装饰器标记的类/函数
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
_registered_devices: Dict[str, type] = {} # device_id -> class
|
|
||||||
_registered_resources: Dict[str, Any] = {} # resource_id -> class or function
|
|
||||||
|
|
||||||
|
|
||||||
def _device_handles_to_list(
|
|
||||||
handles: Optional[List[_DeviceHandleBase]],
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""将设备/资源 Handle 列表序列化为字典列表 (含 io_type)"""
|
|
||||||
if handles is None:
|
|
||||||
return []
|
|
||||||
return [h.to_registry_dict() for h in handles]
|
|
||||||
|
|
||||||
|
|
||||||
def _action_handles_to_dict(
|
|
||||||
handles: Optional[List[_ActionHandleBase]],
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
将动作 Handle 列表序列化为 {"input": [...], "output": [...]} 格式。
|
|
||||||
|
|
||||||
ActionInputHandle => "input", ActionOutputHandle => "output"
|
|
||||||
"""
|
|
||||||
if handles is None:
|
|
||||||
return {}
|
|
||||||
input_list = [h.to_registry_dict() for h in handles if isinstance(h, ActionInputHandle)]
|
|
||||||
output_list = [h.to_registry_dict() for h in handles if isinstance(h, ActionOutputHandle)]
|
|
||||||
result: Dict[str, Any] = {}
|
|
||||||
if input_list:
|
|
||||||
result["input"] = input_list
|
|
||||||
if output_list:
|
|
||||||
result["output"] = output_list
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# @device 类装饰器
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
def device(
|
|
||||||
id: Optional[str] = None,
|
|
||||||
ids: Optional[List[str]] = None,
|
|
||||||
id_meta: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
||||||
category: Optional[List[str]] = None,
|
|
||||||
description: str = "",
|
|
||||||
display_name: str = "",
|
|
||||||
icon: str = "",
|
|
||||||
version: str = "1.0.0",
|
|
||||||
handles: Optional[List[_DeviceHandleBase]] = None,
|
|
||||||
model: Optional[Dict[str, Any]] = None,
|
|
||||||
device_type: str = "python",
|
|
||||||
hardware_interface: Optional[HardwareInterface] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
设备类装饰器
|
|
||||||
|
|
||||||
将类标记为一个 UniLab-OS 设备,并附加注册表元数据。
|
|
||||||
|
|
||||||
支持两种模式:
|
|
||||||
1. 单设备: id="xxx", category=[...]
|
|
||||||
2. 多设备: ids=["id1","id2"], id_meta={"id1":{handles:[...]}, "id2":{...}}
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id: 单设备时的注册表唯一标识
|
|
||||||
ids: 多设备时的 id 列表,与 id_meta 配合使用
|
|
||||||
id_meta: 每个 device_id 的覆盖元数据 (handles/description/icon/model)
|
|
||||||
category: 设备分类标签列表 (必填)
|
|
||||||
description: 设备描述
|
|
||||||
display_name: 人类可读的设备显示名称,缺失时默认使用 id
|
|
||||||
icon: 图标路径
|
|
||||||
version: 版本号
|
|
||||||
handles: 设备端口列表 (单设备或 id_meta 未覆盖时使用)
|
|
||||||
model: 可选的 3D 模型配置
|
|
||||||
device_type: 设备实现类型 ("python" / "ros2")
|
|
||||||
hardware_interface: 硬件通信接口 (HardwareInterface)
|
|
||||||
"""
|
|
||||||
# Resolve device ids
|
|
||||||
if ids is not None:
|
|
||||||
device_ids = list(ids)
|
|
||||||
if not device_ids:
|
|
||||||
raise ValueError("@device ids 不能为空")
|
|
||||||
id_meta = id_meta or {}
|
|
||||||
elif id is not None:
|
|
||||||
device_ids = [id]
|
|
||||||
id_meta = {}
|
|
||||||
else:
|
|
||||||
raise ValueError("@device 必须提供 id 或 ids")
|
|
||||||
|
|
||||||
if category is None:
|
|
||||||
raise ValueError("@device category 必填")
|
|
||||||
|
|
||||||
base_meta = {
|
|
||||||
"category": category,
|
|
||||||
"description": description,
|
|
||||||
"display_name": display_name,
|
|
||||||
"icon": icon,
|
|
||||||
"version": version,
|
|
||||||
"handles": _device_handles_to_list(handles),
|
|
||||||
"model": model,
|
|
||||||
"device_type": device_type,
|
|
||||||
"hardware_interface": (hardware_interface.model_dump(exclude_none=True) if hardware_interface else None),
|
|
||||||
}
|
|
||||||
|
|
||||||
def decorator(cls):
|
|
||||||
cls._device_registry_meta = base_meta
|
|
||||||
cls._device_registry_id_meta = id_meta
|
|
||||||
cls._device_registry_ids = device_ids
|
|
||||||
|
|
||||||
for did in device_ids:
|
|
||||||
if did in _registered_devices:
|
|
||||||
raise ValueError(f"@device id 重复: '{did}' 已被 {_registered_devices[did]} 注册")
|
|
||||||
_registered_devices[did] = cls
|
|
||||||
|
|
||||||
return cls
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# @action 方法装饰器
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# 区分 "用户没传 action_type" 和 "用户传了 None"
|
|
||||||
_ACTION_TYPE_UNSET = object()
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyShadowingNames
|
|
||||||
def action(
|
|
||||||
action_type: Any = _ACTION_TYPE_UNSET,
|
|
||||||
goal: Optional[Dict[str, str]] = None,
|
|
||||||
feedback: Optional[Dict[str, str]] = None,
|
|
||||||
result: Optional[Dict[str, str]] = None,
|
|
||||||
handles: Optional[List[_ActionHandleBase]] = None,
|
|
||||||
goal_default: Optional[Dict[str, Any]] = None,
|
|
||||||
placeholder_keys: Optional[Dict[str, str]] = None,
|
|
||||||
always_free: bool = False,
|
|
||||||
is_protocol: bool = False,
|
|
||||||
description: str = "",
|
|
||||||
auto_prefix: bool = False,
|
|
||||||
parent: bool = False,
|
|
||||||
node_type: Optional["NodeType"] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
动作方法装饰器
|
|
||||||
|
|
||||||
标记方法为注册表动作。有三种用法:
|
|
||||||
1. @action(action_type=EmptyIn, ...) -- 非 auto, 使用指定 ROS Action 类型
|
|
||||||
2. @action() -- 非 auto, UniLabJsonCommand (从方法签名生成 schema)
|
|
||||||
3. 不加 @action -- auto- 前缀, UniLabJsonCommand
|
|
||||||
|
|
||||||
Protocol 用法:
|
|
||||||
@action(action_type=Add, is_protocol=True)
|
|
||||||
def AddProtocol(self): ...
|
|
||||||
标记该动作为高级协议 (protocol),运行时通过 ROS Action 路由到
|
|
||||||
protocol generator 执行。action_type 指向 unilabos_msgs 的 Action 类型。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
action_type: ROS Action 消息类型 (如 EmptyIn, SendCmd, HeatChill).
|
|
||||||
不传/默认 = UniLabJsonCommand (非 auto).
|
|
||||||
goal: Goal 字段映射 (ROS字段名 -> 设备参数名).
|
|
||||||
protocol 模式下可留空,系统自动生成 identity 映射.
|
|
||||||
feedback: Feedback 字段映射
|
|
||||||
result: Result 字段映射
|
|
||||||
handles: 动作端口列表 (ActionInputHandle / ActionOutputHandle)
|
|
||||||
goal_default: Goal 字段默认值映射 (字段名 -> 默认值), 与自动生成的 goal_default 合并
|
|
||||||
placeholder_keys: 参数占位符配置
|
|
||||||
always_free: 是否为永久闲置动作 (不受排队限制)
|
|
||||||
is_protocol: 是否为工作站协议 (protocol)。True 时运行时走 protocol generator 路径。
|
|
||||||
description: 动作描述
|
|
||||||
auto_prefix: 若为 True,动作名使用 auto-{method_name} 形式(与无 @action 时一致)
|
|
||||||
parent: 若为 True,当方法参数为空 (*args, **kwargs) 时,通过 MRO 从父类获取真实方法参数
|
|
||||||
node_type: 动作的节点类型 (NodeType.ILAB / NodeType.MANUAL_CONFIRM)。
|
|
||||||
不填写时不写入注册表。
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(func: F) -> F:
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
# action_type 为哨兵值 => 用户没传, 视为 None (UniLabJsonCommand)
|
|
||||||
resolved_type = None if action_type is _ACTION_TYPE_UNSET else action_type
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
"action_type": resolved_type,
|
|
||||||
"goal": goal or {},
|
|
||||||
"feedback": feedback or {},
|
|
||||||
"result": result or {},
|
|
||||||
"handles": _action_handles_to_dict(handles),
|
|
||||||
"goal_default": goal_default or {},
|
|
||||||
"placeholder_keys": placeholder_keys or {},
|
|
||||||
"always_free": always_free,
|
|
||||||
"is_protocol": is_protocol,
|
|
||||||
"description": description,
|
|
||||||
"auto_prefix": auto_prefix,
|
|
||||||
"parent": parent,
|
|
||||||
}
|
|
||||||
if node_type is not None:
|
|
||||||
meta["node_type"] = node_type.value if isinstance(node_type, NodeType) else str(node_type)
|
|
||||||
wrapper._action_registry_meta = meta # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
# 设置 _is_always_free 保持与旧 @always_free 装饰器兼容
|
|
||||||
if always_free:
|
|
||||||
wrapper._is_always_free = True # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
return wrapper # type: ignore[return-value]
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def get_action_meta(func) -> Optional[Dict[str, Any]]:
|
|
||||||
"""获取方法上的 @action 装饰器元数据"""
|
|
||||||
return getattr(func, "_action_registry_meta", None)
|
|
||||||
|
|
||||||
|
|
||||||
def has_action_decorator(func) -> bool:
|
|
||||||
"""检查函数是否带有 @action 装饰器"""
|
|
||||||
return hasattr(func, "_action_registry_meta")
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# @resource 类/函数装饰器
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def resource(
|
|
||||||
id: str,
|
|
||||||
category: List[str],
|
|
||||||
description: str = "",
|
|
||||||
icon: str = "",
|
|
||||||
version: str = "1.0.0",
|
|
||||||
handles: Optional[List[_DeviceHandleBase]] = None,
|
|
||||||
model: Optional[Dict[str, Any]] = None,
|
|
||||||
class_type: str = "pylabrobot",
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
资源类/函数装饰器
|
|
||||||
|
|
||||||
将类或工厂函数标记为一个 UniLab-OS 资源,附加注册表元数据。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id: 注册表唯一标识 (必填, 不可重复)
|
|
||||||
category: 资源分类标签列表 (必填)
|
|
||||||
description: 资源描述
|
|
||||||
icon: 图标路径
|
|
||||||
version: 版本号
|
|
||||||
handles: 端口列表 (InputHandle / OutputHandle)
|
|
||||||
model: 可选的 3D 模型配置
|
|
||||||
class_type: 资源实现类型 ("python" / "pylabrobot" / "unilabos")
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(obj):
|
|
||||||
meta = {
|
|
||||||
"resource_id": id,
|
|
||||||
"category": category,
|
|
||||||
"description": description,
|
|
||||||
"icon": icon,
|
|
||||||
"version": version,
|
|
||||||
"handles": _device_handles_to_list(handles),
|
|
||||||
"model": model,
|
|
||||||
"class_type": class_type,
|
|
||||||
}
|
|
||||||
obj._resource_registry_meta = meta
|
|
||||||
|
|
||||||
if id in _registered_resources:
|
|
||||||
raise ValueError(f"@resource id 重复: '{id}' 已被 {_registered_resources[id]} 注册")
|
|
||||||
_registered_resources[id] = obj
|
|
||||||
|
|
||||||
return obj
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def get_device_meta(cls, device_id: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
获取类上的 @device 装饰器元数据。
|
|
||||||
|
|
||||||
当 device_id 存在且类使用 ids+id_meta 时,返回合并后的 meta
|
|
||||||
(base_meta 与 id_meta[device_id] 深度合并)。
|
|
||||||
"""
|
|
||||||
base = getattr(cls, "_device_registry_meta", None)
|
|
||||||
if base is None:
|
|
||||||
return None
|
|
||||||
id_meta = getattr(cls, "_device_registry_id_meta", None) or {}
|
|
||||||
if device_id is None or device_id not in id_meta:
|
|
||||||
result = dict(base)
|
|
||||||
ids = getattr(cls, "_device_registry_ids", None)
|
|
||||||
result["device_id"] = device_id if device_id is not None else (ids[0] if ids else None)
|
|
||||||
return result
|
|
||||||
|
|
||||||
overrides = id_meta[device_id]
|
|
||||||
result = dict(base)
|
|
||||||
result["device_id"] = device_id
|
|
||||||
for key in ["handles", "description", "icon", "model"]:
|
|
||||||
if key in overrides:
|
|
||||||
val = overrides[key]
|
|
||||||
if key == "handles" and isinstance(val, list):
|
|
||||||
# handles 必须是 Handle 对象列表
|
|
||||||
result[key] = [h.to_registry_dict() for h in val]
|
|
||||||
else:
|
|
||||||
result[key] = val
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def get_resource_meta(obj) -> Optional[Dict[str, Any]]:
|
|
||||||
"""获取对象上的 @resource 装饰器元数据"""
|
|
||||||
return getattr(obj, "_resource_registry_meta", None)
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_registered_devices() -> Dict[str, type]:
|
|
||||||
"""获取所有已注册的设备类"""
|
|
||||||
return _registered_devices.copy()
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_registered_resources() -> Dict[str, Any]:
|
|
||||||
"""获取所有已注册的资源"""
|
|
||||||
return _registered_resources.copy()
|
|
||||||
|
|
||||||
|
|
||||||
def clear_registry():
|
|
||||||
"""清空全局注册表 (用于测试)"""
|
|
||||||
_registered_devices.clear()
|
|
||||||
_registered_resources.clear()
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 枚举值归一化
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_enum_value(raw: Any, enum_cls) -> Optional[str]:
|
|
||||||
"""将 AST 提取的枚举成员名 / YAML 值字符串 / 旧格式长路径统一归一化为枚举值。
|
|
||||||
|
|
||||||
适用于 Side、DataSource、NodeType 等继承自 ``str, Enum`` 的装饰器枚举。
|
|
||||||
|
|
||||||
处理以下格式:
|
|
||||||
- "MANUAL_CONFIRM" → NodeType["MANUAL_CONFIRM"].value = "manual_confirm"
|
|
||||||
- "manual_confirm" → NodeType("manual_confirm").value = "manual_confirm"
|
|
||||||
- "HANDLE" → DataSource["HANDLE"].value = "handle"
|
|
||||||
- "NORTH" → Side["NORTH"].value = "NORTH"
|
|
||||||
- 旧缓存长路径 "unilabos...NodeType.MANUAL_CONFIRM" → 先 rsplit 再查找
|
|
||||||
"""
|
|
||||||
if not raw:
|
|
||||||
return None
|
|
||||||
raw_str = str(raw)
|
|
||||||
if "." in raw_str:
|
|
||||||
raw_str = raw_str.rsplit(".", 1)[-1]
|
|
||||||
try:
|
|
||||||
return enum_cls[raw_str].value
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
return enum_cls(raw_str).value
|
|
||||||
except ValueError:
|
|
||||||
return raw_str
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# topic_config / not_action / always_free 装饰器
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def topic_config(
|
|
||||||
period: Optional[float] = None,
|
|
||||||
print_publish: Optional[bool] = None,
|
|
||||||
qos: Optional[int] = None,
|
|
||||||
name: Optional[str] = None,
|
|
||||||
) -> Callable[[F], F]:
|
|
||||||
"""
|
|
||||||
Topic发布配置装饰器
|
|
||||||
|
|
||||||
用于装饰 get_{attr_name} 方法或 @property,控制对应属性的ROS topic发布行为。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
period: 发布周期(秒)。None 表示使用默认值 5.0
|
|
||||||
print_publish: 是否打印发布日志。None 表示使用节点默认配置
|
|
||||||
qos: QoS深度配置。None 表示使用默认值 10
|
|
||||||
name: 自定义发布名称。None 表示使用方法名(去掉 get_ 前缀)
|
|
||||||
|
|
||||||
Note:
|
|
||||||
与 @property 连用时,@topic_config 必须放在 @property 下面,
|
|
||||||
这样装饰器执行顺序为:先 topic_config 添加配置,再 property 包装。
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(func: F) -> F:
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
wrapper._topic_period = period # type: ignore[attr-defined]
|
|
||||||
wrapper._topic_print_publish = print_publish # type: ignore[attr-defined]
|
|
||||||
wrapper._topic_qos = qos # type: ignore[attr-defined]
|
|
||||||
wrapper._topic_name = name # type: ignore[attr-defined]
|
|
||||||
wrapper._has_topic_config = True # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
return wrapper # type: ignore[return-value]
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def get_topic_config(func) -> dict:
|
|
||||||
"""获取函数上的 topic 配置 (period, print_publish, qos, name)"""
|
|
||||||
if hasattr(func, "_has_topic_config") and getattr(func, "_has_topic_config", False):
|
|
||||||
return {
|
|
||||||
"period": getattr(func, "_topic_period", None),
|
|
||||||
"print_publish": getattr(func, "_topic_print_publish", None),
|
|
||||||
"qos": getattr(func, "_topic_qos", None),
|
|
||||||
"name": getattr(func, "_topic_name", None),
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def always_free(func: F) -> F:
|
|
||||||
"""
|
|
||||||
标记动作为永久闲置(不受busy队列限制)的装饰器
|
|
||||||
|
|
||||||
被此装饰器标记的 action 方法,在执行时不会受到设备级别的排队限制,
|
|
||||||
任何时候请求都可以立即执行。适用于查询类、状态读取类等轻量级操作。
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
wrapper._is_always_free = True # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
return wrapper # type: ignore[return-value]
|
|
||||||
|
|
||||||
|
|
||||||
def is_always_free(func) -> bool:
|
|
||||||
"""检查函数是否被标记为永久闲置"""
|
|
||||||
return getattr(func, "_is_always_free", False)
|
|
||||||
|
|
||||||
|
|
||||||
def not_action(func: F) -> F:
|
|
||||||
"""
|
|
||||||
标记方法为非动作的装饰器
|
|
||||||
|
|
||||||
用于装饰 driver 类中的方法,使其在注册表扫描时不被识别为动作。
|
|
||||||
适用于辅助方法、内部工具方法等不应暴露为设备动作的公共方法。
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
wrapper._is_not_action = True # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
return wrapper # type: ignore[return-value]
|
|
||||||
|
|
||||||
|
|
||||||
def is_not_action(func) -> bool:
|
|
||||||
"""检查函数是否被标记为非动作"""
|
|
||||||
return getattr(func, "_is_not_action", False)
|
|
||||||
@@ -13,18 +13,21 @@ Qone_nmr:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -68,6 +71,31 @@ Qone_nmr:
|
|||||||
title: monitor_folder_for_new_content参数
|
title: monitor_folder_for_new_content参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-strings_to_txt:
|
auto-strings_to_txt:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -110,18 +138,21 @@ Qone_nmr:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -136,31 +167,32 @@ Qone_nmr:
|
|||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ bioyond_cell:
|
|||||||
required:
|
required:
|
||||||
- xlsx_path
|
- xlsx_path
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: auto_batch_outbound_from_xlsx参数
|
title: auto_batch_outbound_from_xlsx参数
|
||||||
@@ -491,9 +490,7 @@ bioyond_cell:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
material_names:
|
material_names:
|
||||||
items:
|
|
||||||
type: string
|
type: string
|
||||||
type: array
|
|
||||||
type_id:
|
type_id:
|
||||||
default: 3a190ca0-b2f6-9aeb-8067-547e72c11469
|
default: 3a190ca0-b2f6-9aeb-8067-547e72c11469
|
||||||
type: string
|
type: string
|
||||||
@@ -502,8 +499,7 @@ bioyond_cell:
|
|||||||
type: string
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: create_and_inbound_materials参数
|
title: create_and_inbound_materials参数
|
||||||
@@ -539,8 +535,7 @@ bioyond_cell:
|
|||||||
- type_id
|
- type_id
|
||||||
- warehouse_name
|
- warehouse_name
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: create_material参数
|
title: create_material参数
|
||||||
@@ -561,16 +556,11 @@ bioyond_cell:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
mappings:
|
mappings:
|
||||||
additionalProperties:
|
|
||||||
type: object
|
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- mappings
|
- mappings
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: create_materials参数
|
title: create_materials参数
|
||||||
@@ -602,8 +592,7 @@ bioyond_cell:
|
|||||||
required:
|
required:
|
||||||
- xlsx_path
|
- xlsx_path
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: create_orders参数
|
title: create_orders参数
|
||||||
@@ -635,8 +624,7 @@ bioyond_cell:
|
|||||||
required:
|
required:
|
||||||
- xlsx_path
|
- xlsx_path
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: create_orders_v2参数
|
title: create_orders_v2参数
|
||||||
@@ -677,8 +665,7 @@ bioyond_cell:
|
|||||||
- bottle_type
|
- bottle_type
|
||||||
- location_code
|
- location_code
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: create_sample参数
|
title: create_sample参数
|
||||||
@@ -731,8 +718,7 @@ bioyond_cell:
|
|||||||
type: string
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: order_list_v2参数
|
title: order_list_v2参数
|
||||||
@@ -835,8 +821,7 @@ bioyond_cell:
|
|||||||
required:
|
required:
|
||||||
- material_obj
|
- material_obj
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: report_material_change参数
|
title: report_material_change参数
|
||||||
@@ -890,8 +875,7 @@ bioyond_cell:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: scheduler_continue参数
|
title: scheduler_continue参数
|
||||||
@@ -912,8 +896,7 @@ bioyond_cell:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: scheduler_reset参数
|
title: scheduler_reset参数
|
||||||
@@ -934,8 +917,7 @@ bioyond_cell:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: scheduler_start参数
|
title: scheduler_start参数
|
||||||
@@ -1380,8 +1362,7 @@ bioyond_cell:
|
|||||||
type: string
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: scheduler_start_and_auto_feeding参数
|
title: scheduler_start_and_auto_feeding参数
|
||||||
@@ -1826,8 +1807,7 @@ bioyond_cell:
|
|||||||
type: string
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: scheduler_start_and_auto_feeding_v2参数
|
title: scheduler_start_and_auto_feeding_v2参数
|
||||||
@@ -1848,8 +1828,7 @@ bioyond_cell:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: scheduler_stop参数
|
title: scheduler_stop参数
|
||||||
@@ -1871,15 +1850,12 @@ bioyond_cell:
|
|||||||
properties:
|
properties:
|
||||||
items:
|
items:
|
||||||
items:
|
items:
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
required:
|
required:
|
||||||
- items
|
- items
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: storage_batch_inbound参数
|
title: storage_batch_inbound参数
|
||||||
@@ -1908,8 +1884,7 @@ bioyond_cell:
|
|||||||
- material_id
|
- material_id
|
||||||
- location_id
|
- location_id
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: storage_inbound参数
|
title: storage_inbound参数
|
||||||
@@ -1930,8 +1905,7 @@ bioyond_cell:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: transfer_1_to_2参数
|
title: transfer_1_to_2参数
|
||||||
@@ -1972,8 +1946,7 @@ bioyond_cell:
|
|||||||
type: integer
|
type: integer
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: transfer_3_to_2参数
|
title: transfer_3_to_2参数
|
||||||
@@ -2010,8 +1983,7 @@ bioyond_cell:
|
|||||||
type: integer
|
type: integer
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: transfer_3_to_2_to_1参数
|
title: transfer_3_to_2_to_1参数
|
||||||
@@ -2035,11 +2007,10 @@ bioyond_cell:
|
|||||||
ip:
|
ip:
|
||||||
type: string
|
type: string
|
||||||
port:
|
port:
|
||||||
type: integer
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: update_push_ip参数
|
title: update_push_ip参数
|
||||||
@@ -2068,8 +2039,7 @@ bioyond_cell:
|
|||||||
required:
|
required:
|
||||||
- order_code
|
- order_code
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: wait_for_order_finish参数
|
title: wait_for_order_finish参数
|
||||||
@@ -2102,8 +2072,7 @@ bioyond_cell:
|
|||||||
required:
|
required:
|
||||||
- order_code
|
- order_code
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: wait_for_order_finish_polling参数
|
title: wait_for_order_finish_polling参数
|
||||||
@@ -2135,8 +2104,7 @@ bioyond_cell:
|
|||||||
type: integer
|
type: integer
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: wait_for_transfer_task参数
|
title: wait_for_transfer_task参数
|
||||||
@@ -2144,7 +2112,8 @@ bioyond_cell:
|
|||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
|
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
|
||||||
status_types:
|
status_types:
|
||||||
device_id: ''
|
device_id: String
|
||||||
|
material_info: dict
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: ''
|
description: ''
|
||||||
@@ -2165,7 +2134,11 @@ bioyond_cell:
|
|||||||
properties:
|
properties:
|
||||||
device_id:
|
device_id:
|
||||||
type: string
|
type: string
|
||||||
|
material_info:
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- device_id
|
- device_id
|
||||||
|
- material_info
|
||||||
type: object
|
type: object
|
||||||
|
registry_type: device
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ bioyond_dispensing_station:
|
|||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: brief_step_parameters参数
|
title: brief_step_parameters参数
|
||||||
@@ -54,8 +53,7 @@ bioyond_dispensing_station:
|
|||||||
- report_request
|
- report_request
|
||||||
- used_materials
|
- used_materials
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: process_order_finish_report参数
|
title: process_order_finish_report参数
|
||||||
@@ -80,8 +78,7 @@ bioyond_dispensing_station:
|
|||||||
required:
|
required:
|
||||||
- order_id
|
- order_id
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: project_order_report参数
|
title: project_order_report参数
|
||||||
@@ -131,8 +128,7 @@ bioyond_dispensing_station:
|
|||||||
required:
|
required:
|
||||||
- workflow_id
|
- workflow_id
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: workflow_sample_locations参数
|
title: workflow_sample_locations参数
|
||||||
@@ -148,12 +144,12 @@ bioyond_dispensing_station:
|
|||||||
temperature: temperature
|
temperature: temperature
|
||||||
titration: titration
|
titration: titration
|
||||||
goal_default:
|
goal_default:
|
||||||
delay_time: null
|
delay_time: '600'
|
||||||
hold_m_name: null
|
hold_m_name: ''
|
||||||
liquid_material_name: NMP
|
liquid_material_name: NMP
|
||||||
speed: null
|
speed: '400'
|
||||||
temperature: null
|
temperature: '40'
|
||||||
titration: null
|
titration: ''
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: titration
|
- data_key: titration
|
||||||
@@ -169,16 +165,20 @@ bioyond_dispensing_station:
|
|||||||
handler_key: BATCH_CREATE_RESULT
|
handler_key: BATCH_CREATE_RESULT
|
||||||
io_type: sink
|
io_type: sink
|
||||||
label: Complete Batch Create Result JSON (contains order_codes and order_ids)
|
label: Complete Batch Create Result JSON (contains order_codes and order_ids)
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
schema:
|
schema:
|
||||||
description: 批量创建90%10%小瓶投料任务。从计算节点接收titration数据,包含物料名称、主称固体质量、滴定固体质量和滴定溶剂体积。返回的return_info中包含order_codes和order_ids列表。
|
description: 批量创建90%10%小瓶投料任务。从计算节点接收titration数据,包含物料名称、主称固体质量、滴定固体质量和滴定溶剂体积。返回的return_info中包含order_codes和order_ids列表。
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
title: BatchCreate9010VialFeedingTasks_Feedback
|
title: BatchCreate9010VialFeedingTasks_Feedback
|
||||||
|
type: object
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
delay_time:
|
delay_time:
|
||||||
|
default: '600'
|
||||||
description: 延迟时间(秒),默认600
|
description: 延迟时间(秒),默认600
|
||||||
type: string
|
type: string
|
||||||
hold_m_name:
|
hold_m_name:
|
||||||
@@ -189,9 +189,11 @@ bioyond_dispensing_station:
|
|||||||
description: 10%物料的液体物料名称,默认为"NMP"
|
description: 10%物料的液体物料名称,默认为"NMP"
|
||||||
type: string
|
type: string
|
||||||
speed:
|
speed:
|
||||||
|
default: '400'
|
||||||
description: 搅拌速度,默认400
|
description: 搅拌速度,默认400
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
|
default: '40'
|
||||||
description: 温度(℃),默认40
|
description: 温度(℃),默认40
|
||||||
type: string
|
type: string
|
||||||
titration:
|
titration:
|
||||||
@@ -200,14 +202,21 @@ bioyond_dispensing_station:
|
|||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- titration
|
- titration
|
||||||
|
- hold_m_name
|
||||||
title: BatchCreate9010VialFeedingTasks_Goal
|
title: BatchCreate9010VialFeedingTasks_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
title: BatchCreate9010VialFeedingTasks_Result
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 批量任务创建结果汇总JSON字符串,包含total(总数)、success(成功数)、failed(失败数)、order_codes(任务编码数组)、order_ids(任务ID数组)、details(每个任务的详细信息)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- return_info
|
||||||
|
title: BatchCreate9010VialFeedingTasks_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: batch_create_90_10_vial_feeding_tasks参数
|
title: BatchCreate9010VialFeedingTasks
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
batch_create_diamine_solution_tasks:
|
batch_create_diamine_solution_tasks:
|
||||||
@@ -219,11 +228,11 @@ bioyond_dispensing_station:
|
|||||||
speed: speed
|
speed: speed
|
||||||
temperature: temperature
|
temperature: temperature
|
||||||
goal_default:
|
goal_default:
|
||||||
delay_time: null
|
delay_time: '600'
|
||||||
liquid_material_name: NMP
|
liquid_material_name: NMP
|
||||||
solutions: null
|
solutions: ''
|
||||||
speed: null
|
speed: '400'
|
||||||
temperature: null
|
temperature: '20'
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: solutions
|
- data_key: solutions
|
||||||
@@ -239,16 +248,20 @@ bioyond_dispensing_station:
|
|||||||
handler_key: BATCH_CREATE_RESULT
|
handler_key: BATCH_CREATE_RESULT
|
||||||
io_type: sink
|
io_type: sink
|
||||||
label: Complete Batch Create Result JSON (contains order_codes and order_ids)
|
label: Complete Batch Create Result JSON (contains order_codes and order_ids)
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
schema:
|
schema:
|
||||||
description: 批量创建二胺溶液配置任务。自动为多个二胺样品创建溶液配置任务,每个任务包含固体物料称量、溶剂添加、搅拌混合等步骤。返回的return_info中包含order_codes和order_ids列表。
|
description: 批量创建二胺溶液配置任务。自动为多个二胺样品创建溶液配置任务,每个任务包含固体物料称量、溶剂添加、搅拌混合等步骤。返回的return_info中包含order_codes和order_ids列表。
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
title: BatchCreateDiamineSolutionTasks_Feedback
|
title: BatchCreateDiamineSolutionTasks_Feedback
|
||||||
|
type: object
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
delay_time:
|
delay_time:
|
||||||
|
default: '600'
|
||||||
description: 溶液配置完成后的延迟时间(秒),用于充分混合和溶解,默认600秒
|
description: 溶液配置完成后的延迟时间(秒),用于充分混合和溶解,默认600秒
|
||||||
type: string
|
type: string
|
||||||
liquid_material_name:
|
liquid_material_name:
|
||||||
@@ -262,9 +275,11 @@ bioyond_dispensing_station:
|
|||||||
4.5, "solvent_volume": 18}]'
|
4.5, "solvent_volume": 18}]'
|
||||||
type: string
|
type: string
|
||||||
speed:
|
speed:
|
||||||
|
default: '400'
|
||||||
description: 搅拌速度(rpm),用于混合溶液,默认400转/分钟
|
description: 搅拌速度(rpm),用于混合溶液,默认400转/分钟
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
|
default: '20'
|
||||||
description: 配置温度(℃),溶液配置过程的目标温度,默认20℃(室温)
|
description: 配置温度(℃),溶液配置过程的目标温度,默认20℃(室温)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
@@ -272,11 +287,17 @@ bioyond_dispensing_station:
|
|||||||
title: BatchCreateDiamineSolutionTasks_Goal
|
title: BatchCreateDiamineSolutionTasks_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
title: BatchCreateDiamineSolutionTasks_Result
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 批量任务创建结果汇总JSON字符串,包含total(总数)、success(成功数)、failed(失败数)、order_codes(任务编码数组)、order_ids(任务ID数组)、details(每个任务的详细信息)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- return_info
|
||||||
|
title: BatchCreateDiamineSolutionTasks_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: batch_create_diamine_solution_tasks参数
|
title: BatchCreateDiamineSolutionTasks
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
compute_experiment_design:
|
compute_experiment_design:
|
||||||
@@ -288,7 +309,7 @@ bioyond_dispensing_station:
|
|||||||
wt_percent: wt_percent
|
wt_percent: wt_percent
|
||||||
goal_default:
|
goal_default:
|
||||||
m_tot: '70'
|
m_tot: '70'
|
||||||
ratio: null
|
ratio: ''
|
||||||
titration_percent: '0.03'
|
titration_percent: '0.03'
|
||||||
wt_percent: '0.25'
|
wt_percent: '0.25'
|
||||||
handles:
|
handles:
|
||||||
@@ -317,8 +338,12 @@ bioyond_dispensing_station:
|
|||||||
handler_key: feeding_order
|
handler_key: feeding_order
|
||||||
io_type: sink
|
io_type: sink
|
||||||
label: Feeding Order Data From Calculation Node
|
label: Feeding Order Data From Calculation Node
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
feeding_order: feeding_order
|
||||||
|
return_info: return_info
|
||||||
|
solutions: solutions
|
||||||
|
solvents: solvents
|
||||||
|
titration: titration
|
||||||
schema:
|
schema:
|
||||||
description: 计算实验设计,输出solutions/titration/solvents/feeding_order用于后续节点。
|
description: 计算实验设计,输出solutions/titration/solvents/feeding_order用于后续节点。
|
||||||
properties:
|
properties:
|
||||||
@@ -331,7 +356,7 @@ bioyond_dispensing_station:
|
|||||||
type: string
|
type: string
|
||||||
ratio:
|
ratio:
|
||||||
description: 组分摩尔比的对象,保持输入顺序,如{"MDA":1,"BTDA":1}
|
description: 组分摩尔比的对象,保持输入顺序,如{"MDA":1,"BTDA":1}
|
||||||
type: object
|
type: string
|
||||||
titration_percent:
|
titration_percent:
|
||||||
default: '0.03'
|
default: '0.03'
|
||||||
description: 滴定比例(10%部分)
|
description: 滴定比例(10%部分)
|
||||||
@@ -346,23 +371,14 @@ bioyond_dispensing_station:
|
|||||||
result:
|
result:
|
||||||
properties:
|
properties:
|
||||||
feeding_order:
|
feeding_order:
|
||||||
items: {}
|
|
||||||
title: Feeding Order
|
|
||||||
type: array
|
type: array
|
||||||
return_info:
|
return_info:
|
||||||
title: Return Info
|
|
||||||
type: string
|
type: string
|
||||||
solutions:
|
solutions:
|
||||||
items: {}
|
|
||||||
title: Solutions
|
|
||||||
type: array
|
type: array
|
||||||
solvents:
|
solvents:
|
||||||
additionalProperties: true
|
|
||||||
title: Solvents
|
|
||||||
type: object
|
type: object
|
||||||
titration:
|
titration:
|
||||||
additionalProperties: true
|
|
||||||
title: Titration
|
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- solutions
|
- solutions
|
||||||
@@ -370,11 +386,11 @@ bioyond_dispensing_station:
|
|||||||
- solvents
|
- solvents
|
||||||
- feeding_order
|
- feeding_order
|
||||||
- return_info
|
- return_info
|
||||||
title: ComputeExperimentDesignReturn
|
title: ComputeExperimentDesign_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: compute_experiment_design参数
|
title: ComputeExperimentDesign
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
create_90_10_vial_feeding_task:
|
create_90_10_vial_feeding_task:
|
||||||
@@ -428,18 +444,17 @@ bioyond_dispensing_station:
|
|||||||
speed: ''
|
speed: ''
|
||||||
temperature: ''
|
temperature: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
return_info: return_info
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: DispenStationVialFeed_Feedback
|
title: DispenStationVialFeed_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
delay_time:
|
delay_time:
|
||||||
type: string
|
type: string
|
||||||
@@ -487,13 +502,38 @@ bioyond_dispensing_station:
|
|||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- order_name
|
||||||
|
- percent_90_1_assign_material_name
|
||||||
|
- percent_90_1_target_weigh
|
||||||
|
- percent_90_2_assign_material_name
|
||||||
|
- percent_90_2_target_weigh
|
||||||
|
- percent_90_3_assign_material_name
|
||||||
|
- percent_90_3_target_weigh
|
||||||
|
- percent_10_1_assign_material_name
|
||||||
|
- percent_10_1_target_weigh
|
||||||
|
- percent_10_1_volume
|
||||||
|
- percent_10_1_liquid_material_name
|
||||||
|
- percent_10_2_assign_material_name
|
||||||
|
- percent_10_2_target_weigh
|
||||||
|
- percent_10_2_volume
|
||||||
|
- percent_10_2_liquid_material_name
|
||||||
|
- percent_10_3_assign_material_name
|
||||||
|
- percent_10_3_target_weigh
|
||||||
|
- percent_10_3_volume
|
||||||
|
- percent_10_3_liquid_material_name
|
||||||
|
- speed
|
||||||
|
- temperature
|
||||||
|
- delay_time
|
||||||
|
- hold_m_name
|
||||||
title: DispenStationVialFeed_Goal
|
title: DispenStationVialFeed_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: DispenStationVialFeed_Result
|
title: DispenStationVialFeed_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -524,18 +564,17 @@ bioyond_dispensing_station:
|
|||||||
temperature: ''
|
temperature: ''
|
||||||
volume: ''
|
volume: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
return_info: return_info
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: DispenStationSolnPrep_Feedback
|
title: DispenStationSolnPrep_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
delay_time:
|
delay_time:
|
||||||
type: string
|
type: string
|
||||||
@@ -555,13 +594,24 @@ bioyond_dispensing_station:
|
|||||||
type: string
|
type: string
|
||||||
volume:
|
volume:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- order_name
|
||||||
|
- material_name
|
||||||
|
- target_weigh
|
||||||
|
- volume
|
||||||
|
- liquid_material_name
|
||||||
|
- speed
|
||||||
|
- temperature
|
||||||
|
- delay_time
|
||||||
|
- hold_m_name
|
||||||
title: DispenStationSolnPrep_Goal
|
title: DispenStationSolnPrep_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: DispenStationSolnPrep_Result
|
title: DispenStationSolnPrep_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -574,8 +624,8 @@ bioyond_dispensing_station:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
schema:
|
schema:
|
||||||
description: 启动调度器 - 启动Bioyond配液站的任务调度器,开始执行队列中的任务
|
description: 启动调度器 - 启动Bioyond配液站的任务调度器,开始执行队列中的任务
|
||||||
properties:
|
properties:
|
||||||
@@ -585,6 +635,12 @@ bioyond_dispensing_station:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 调度器启动结果,成功返回1,失败返回0
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: scheduler_start结果
|
title: scheduler_start结果
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -598,8 +654,8 @@ bioyond_dispensing_station:
|
|||||||
target_device_id: target_device_id
|
target_device_id: target_device_id
|
||||||
transfer_groups: transfer_groups
|
transfer_groups: transfer_groups
|
||||||
goal_default:
|
goal_default:
|
||||||
target_device_id: null
|
target_device_id: ''
|
||||||
transfer_groups: null
|
transfer_groups: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
target_device_id: unilabos_devices
|
target_device_id: unilabos_devices
|
||||||
@@ -615,13 +671,32 @@ bioyond_dispensing_station:
|
|||||||
type: string
|
type: string
|
||||||
transfer_groups:
|
transfer_groups:
|
||||||
description: 转移任务组列表,每组包含物料名称、目标堆栈和目标库位,可以添加多组
|
description: 转移任务组列表,每组包含物料名称、目标堆栈和目标库位,可以添加多组
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
materials:
|
||||||
|
description: 物料名称(手动输入,系统将通过RPC查询验证)
|
||||||
|
type: string
|
||||||
|
target_sites:
|
||||||
|
description: 目标库位(手动输入,如"A01")
|
||||||
|
type: string
|
||||||
|
target_stack:
|
||||||
|
description: 目标堆栈名称(从列表选择)
|
||||||
|
enum:
|
||||||
|
- 堆栈1左
|
||||||
|
- 堆栈1右
|
||||||
|
- 站内试剂存放堆栈
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- materials
|
||||||
|
- target_stack
|
||||||
|
- target_sites
|
||||||
|
type: object
|
||||||
type: array
|
type: array
|
||||||
required:
|
required:
|
||||||
- target_device_id
|
- target_device_id
|
||||||
- transfer_groups
|
- transfer_groups
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: transfer_materials_to_reaction_station参数
|
title: transfer_materials_to_reaction_station参数
|
||||||
@@ -634,9 +709,9 @@ bioyond_dispensing_station:
|
|||||||
check_interval: check_interval
|
check_interval: check_interval
|
||||||
timeout: timeout
|
timeout: timeout
|
||||||
goal_default:
|
goal_default:
|
||||||
batch_create_result: null
|
batch_create_result: ''
|
||||||
check_interval: 10
|
check_interval: '10'
|
||||||
timeout: 7200
|
timeout: '7200'
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: batch_create_result
|
- data_key: batch_create_result
|
||||||
@@ -652,35 +727,47 @@ bioyond_dispensing_station:
|
|||||||
handler_key: batch_reports_result
|
handler_key: batch_reports_result
|
||||||
io_type: sink
|
io_type: sink
|
||||||
label: Batch Order Completion Reports
|
label: Batch Order Completion Reports
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
schema:
|
schema:
|
||||||
description: 同时等待多个任务完成并获取所有实验报告。从上游batch_create任务接收包含order_codes和order_ids的结果对象,并行监控所有任务状态并返回每个任务的报告。
|
description: 同时等待多个任务完成并获取所有实验报告。从上游batch_create任务接收包含order_codes和order_ids的结果对象,并行监控所有任务状态并返回每个任务的报告。
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
title: WaitForMultipleOrdersAndGetReports_Feedback
|
title: WaitForMultipleOrdersAndGetReports_Feedback
|
||||||
|
type: object
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
batch_create_result:
|
batch_create_result:
|
||||||
description: 批量创建任务的返回结果对象,包含order_codes和order_ids数组。从上游batch_create节点通过handle传递
|
description: 批量创建任务的返回结果对象,包含order_codes和order_ids数组。从上游batch_create节点通过handle传递
|
||||||
type: string
|
type: string
|
||||||
check_interval:
|
check_interval:
|
||||||
default: 10
|
default: '10'
|
||||||
description: 检查任务状态的时间间隔(秒),默认每10秒检查一次所有待完成任务
|
description: 检查任务状态的时间间隔(秒),默认每10秒检查一次所有待完成任务
|
||||||
type: integer
|
type: string
|
||||||
timeout:
|
timeout:
|
||||||
default: 7200
|
default: '7200'
|
||||||
description: 等待超时时间(秒),默认7200秒(2小时)。超过此时间未完成的任务将标记为timeout
|
description: 等待超时时间(秒),默认7200秒(2小时)。超过此时间未完成的任务将标记为timeout
|
||||||
type: integer
|
type: string
|
||||||
required: []
|
required:
|
||||||
|
- batch_create_result
|
||||||
title: WaitForMultipleOrdersAndGetReports_Goal
|
title: WaitForMultipleOrdersAndGetReports_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 'JSON格式的批量任务完成信息,包含: total(总数), completed(成功数), timeout(超时数),
|
||||||
|
error(错误数), elapsed_time(总耗时), reports(报告数组,每个元素包含order_code,
|
||||||
|
order_id, status, completion_status, report, elapsed_time)'
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: WaitForMultipleOrdersAndGetReports_Result
|
title: WaitForMultipleOrdersAndGetReports_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: wait_for_multiple_orders_and_get_reports参数
|
title: WaitForMultipleOrdersAndGetReports
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.workstation.bioyond_studio.dispensing_station.dispensing_station:BioyondDispensingStation
|
module: unilabos.devices.workstation.bioyond_studio.dispensing_station.dispensing_station:BioyondDispensingStation
|
||||||
|
|||||||
81
unilabos/registry/devices/camera.yaml
Normal file
81
unilabos/registry/devices/camera.yaml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
camera:
|
||||||
|
category:
|
||||||
|
- camera
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-destroy_node:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 用于安全地关闭摄像头设备,释放摄像头资源,停止视频采集和发布服务。调用此函数将清理OpenCV摄像头连接并销毁ROS2节点。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: destroy_node参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-timer_callback:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 定时器回调函数的参数schema。此函数负责定期采集摄像头视频帧,将OpenCV格式的图像转换为ROS Image消息格式,并发布到指定的视频话题。默认以10Hz频率执行,确保视频流的连续性和实时性。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: timer_callback参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
module: unilabos.ros.nodes.presets.camera:VideoPublisher
|
||||||
|
status_types: {}
|
||||||
|
type: ros2
|
||||||
|
config_info: []
|
||||||
|
description: VideoPublisher摄像头设备节点,用于实时视频采集和流媒体发布。该设备通过OpenCV连接本地摄像头(如USB摄像头、内置摄像头等),定时采集视频帧并将其转换为ROS2的sensor_msgs/Image消息格式发布到视频话题。主要用于实验室自动化系统中的视觉监控、图像分析、实时观察等应用场景。支持可配置的摄像头索引、发布频率等参数。
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
camera_index:
|
||||||
|
default: 0
|
||||||
|
type: string
|
||||||
|
device_id:
|
||||||
|
default: video_publisher
|
||||||
|
type: string
|
||||||
|
device_uuid:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
period:
|
||||||
|
default: 0.1
|
||||||
|
type: number
|
||||||
|
registry_name:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
resource_tracker:
|
||||||
|
type: object
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
version: 1.0.0
|
||||||
@@ -18,7 +18,7 @@ cameracontroller_device:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
config:
|
config:
|
||||||
type: object
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
@@ -42,8 +42,7 @@ cameracontroller_device:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: stop参数
|
title: stop参数
|
||||||
@@ -51,7 +50,7 @@ cameracontroller_device:
|
|||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.cameraSII.cameraUSB:CameraController
|
module: unilabos.devices.cameraSII.cameraUSB:CameraController
|
||||||
status_types:
|
status_types:
|
||||||
status: Dict[str, Any]
|
status: dict
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: Uni-Lab-OS 摄像头驱动(Linux USB 摄像头版,无 PTZ)
|
description: Uni-Lab-OS 摄像头驱动(Linux USB 摄像头版,无 PTZ)
|
||||||
@@ -104,4 +103,5 @@ cameracontroller_device:
|
|||||||
required:
|
required:
|
||||||
- status
|
- status
|
||||||
type: object
|
type: object
|
||||||
|
registry_type: device
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -141,26 +141,30 @@ hplc.agilent:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -171,6 +175,7 @@ hplc.agilent:
|
|||||||
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
||||||
status_types:
|
status_types:
|
||||||
could_run: bool
|
could_run: bool
|
||||||
|
data_file: String
|
||||||
device_status: str
|
device_status: str
|
||||||
driver_init_ok: bool
|
driver_init_ok: bool
|
||||||
finish_status: str
|
finish_status: str
|
||||||
@@ -194,6 +199,10 @@ hplc.agilent:
|
|||||||
properties:
|
properties:
|
||||||
could_run:
|
could_run:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
data_file:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
device_status:
|
device_status:
|
||||||
type: string
|
type: string
|
||||||
driver_init_ok:
|
driver_init_ok:
|
||||||
@@ -207,13 +216,14 @@ hplc.agilent:
|
|||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- could_run
|
|
||||||
- device_status
|
|
||||||
- driver_init_ok
|
|
||||||
- finish_status
|
|
||||||
- is_running
|
|
||||||
- status_text
|
- status_text
|
||||||
|
- device_status
|
||||||
|
- could_run
|
||||||
|
- driver_init_ok
|
||||||
|
- is_running
|
||||||
- success
|
- success
|
||||||
|
- finish_status
|
||||||
|
- data_file
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
hplc.agilent-zhida:
|
hplc.agilent-zhida:
|
||||||
@@ -226,25 +236,26 @@ hplc.agilent-zhida:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -304,18 +315,21 @@ hplc.agilent-zhida:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -327,35 +341,35 @@ hplc.agilent-zhida:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
string: string
|
string: string
|
||||||
text: text
|
|
||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -393,7 +407,7 @@ hplc.agilent-zhida:
|
|||||||
status:
|
status:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- methods
|
|
||||||
- status
|
- status
|
||||||
|
- methods
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -120,41 +120,42 @@ raman.home_made:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
raman_cmd:
|
raman_cmd:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ separator.chinwe:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: connect参数
|
title: connect参数
|
||||||
@@ -66,145 +65,135 @@ separator.chinwe:
|
|||||||
required:
|
required:
|
||||||
- command_dict
|
- command_dict
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: execute_command_from_outer参数
|
title: execute_command_from_outer参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
motor_rotate_quarter:
|
motor_rotate_quarter:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
direction: 顺时针
|
direction: 顺时针
|
||||||
motor_id: 4
|
motor_id: 4
|
||||||
speed: 60
|
speed: 60
|
||||||
goal_default:
|
|
||||||
direction: 顺时针
|
|
||||||
motor_id: null
|
|
||||||
speed: 60
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 电机旋转 1/4 圈
|
description: 电机旋转 1/4 圈
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
direction:
|
direction:
|
||||||
default: 顺时针
|
default: 顺时针
|
||||||
description: 旋转方向
|
description: 旋转方向
|
||||||
|
enum:
|
||||||
|
- 顺时针
|
||||||
|
- 逆时针
|
||||||
type: string
|
type: string
|
||||||
motor_id:
|
motor_id:
|
||||||
|
default: '4'
|
||||||
description: 选择电机 (4:搅拌, 5:旋钮)
|
description: 选择电机 (4:搅拌, 5:旋钮)
|
||||||
type: integer
|
enum:
|
||||||
|
- '4'
|
||||||
|
- '5'
|
||||||
|
type: string
|
||||||
speed:
|
speed:
|
||||||
default: 60
|
default: 60
|
||||||
description: 速度 (RPM)
|
description: 速度 (RPM)
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
- motor_id
|
- motor_id
|
||||||
type: object
|
- speed
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: motor_rotate_quarter参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
motor_run_continuous:
|
motor_run_continuous:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
direction: 顺时针
|
direction: 顺时针
|
||||||
motor_id: 4
|
motor_id: 4
|
||||||
speed: 60
|
speed: 60
|
||||||
goal_default:
|
|
||||||
direction: 顺时针
|
|
||||||
motor_id: null
|
|
||||||
speed: null
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 电机一直旋转 (速度模式)
|
description: 电机一直旋转 (速度模式)
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
direction:
|
direction:
|
||||||
default: 顺时针
|
default: 顺时针
|
||||||
description: 旋转方向
|
description: 旋转方向
|
||||||
|
enum:
|
||||||
|
- 顺时针
|
||||||
|
- 逆时针
|
||||||
type: string
|
type: string
|
||||||
motor_id:
|
motor_id:
|
||||||
|
default: '4'
|
||||||
description: 选择电机 (4:搅拌, 5:旋钮)
|
description: 选择电机 (4:搅拌, 5:旋钮)
|
||||||
type: integer
|
enum:
|
||||||
|
- '4'
|
||||||
|
- '5'
|
||||||
|
type: string
|
||||||
speed:
|
speed:
|
||||||
|
default: 60
|
||||||
description: 速度 (RPM)
|
description: 速度 (RPM)
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
- motor_id
|
- motor_id
|
||||||
- speed
|
- speed
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: motor_run_continuous参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
motor_stop:
|
motor_stop:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
motor_id: 4
|
motor_id: 4
|
||||||
goal_default:
|
|
||||||
motor_id: null
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 停止指定步进电机
|
description: 停止指定步进电机
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
motor_id:
|
motor_id:
|
||||||
|
default: '4'
|
||||||
description: 选择电机
|
description: 选择电机
|
||||||
|
enum:
|
||||||
|
- '4'
|
||||||
|
- '5'
|
||||||
title: '注: 4=搅拌, 5=旋钮'
|
title: '注: 4=搅拌, 5=旋钮'
|
||||||
type: integer
|
type: string
|
||||||
required:
|
required:
|
||||||
- motor_id
|
- motor_id
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: motor_stop参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
pump_aspirate:
|
pump_aspirate:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
pump_id: 1
|
pump_id: 1
|
||||||
valve_port: 1
|
valve_port: 1
|
||||||
volume: 1000
|
volume: 1000
|
||||||
goal_default:
|
|
||||||
pump_id: null
|
|
||||||
valve_port: null
|
|
||||||
volume: null
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 注射泵吸液
|
description: 注射泵吸液
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
pump_id:
|
pump_id:
|
||||||
|
default: '1'
|
||||||
description: 选择泵
|
description: 选择泵
|
||||||
type: integer
|
enum:
|
||||||
|
- '1'
|
||||||
|
- '2'
|
||||||
|
- '3'
|
||||||
|
type: string
|
||||||
valve_port:
|
valve_port:
|
||||||
|
default: '1'
|
||||||
description: 阀门端口
|
description: 阀门端口
|
||||||
type: integer
|
enum:
|
||||||
|
- '1'
|
||||||
|
- '2'
|
||||||
|
- '3'
|
||||||
|
- '4'
|
||||||
|
- '5'
|
||||||
|
- '6'
|
||||||
|
- '7'
|
||||||
|
- '8'
|
||||||
|
type: string
|
||||||
volume:
|
volume:
|
||||||
|
default: 1000
|
||||||
description: 吸液步数
|
description: 吸液步数
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
@@ -212,38 +201,41 @@ separator.chinwe:
|
|||||||
- volume
|
- volume
|
||||||
- valve_port
|
- valve_port
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: pump_aspirate参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
pump_dispense:
|
pump_dispense:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
pump_id: 1
|
pump_id: 1
|
||||||
valve_port: 1
|
valve_port: 1
|
||||||
volume: 1000
|
volume: 1000
|
||||||
goal_default:
|
|
||||||
pump_id: null
|
|
||||||
valve_port: null
|
|
||||||
volume: null
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 注射泵排液
|
description: 注射泵排液
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
pump_id:
|
pump_id:
|
||||||
|
default: '1'
|
||||||
description: 选择泵
|
description: 选择泵
|
||||||
type: integer
|
enum:
|
||||||
|
- '1'
|
||||||
|
- '2'
|
||||||
|
- '3'
|
||||||
|
type: string
|
||||||
valve_port:
|
valve_port:
|
||||||
|
default: '1'
|
||||||
description: 阀门端口
|
description: 阀门端口
|
||||||
type: integer
|
enum:
|
||||||
|
- '1'
|
||||||
|
- '2'
|
||||||
|
- '3'
|
||||||
|
- '4'
|
||||||
|
- '5'
|
||||||
|
- '6'
|
||||||
|
- '7'
|
||||||
|
- '8'
|
||||||
|
type: string
|
||||||
volume:
|
volume:
|
||||||
|
default: 1000
|
||||||
description: 排液步数
|
description: 排液步数
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
@@ -251,152 +243,121 @@ separator.chinwe:
|
|||||||
- volume
|
- volume
|
||||||
- valve_port
|
- valve_port
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: pump_dispense参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
pump_initialize:
|
pump_initialize:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
drain_port: 0
|
drain_port: 0
|
||||||
output_port: 0
|
output_port: 0
|
||||||
pump_id: 1
|
pump_id: 1
|
||||||
speed: 10
|
speed: 10
|
||||||
goal_default:
|
|
||||||
drain_port: 0
|
|
||||||
output_port: 0
|
|
||||||
pump_id: null
|
|
||||||
speed: 10
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 初始化指定注射泵
|
description: 初始化指定注射泵
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
drain_port:
|
drain_port:
|
||||||
default: 0
|
default: 0
|
||||||
description: 排液口索引
|
description: 排液口索引
|
||||||
type: string
|
type: integer
|
||||||
output_port:
|
output_port:
|
||||||
default: 0
|
default: 0
|
||||||
description: 输出口索引
|
description: 输出口索引
|
||||||
type: string
|
|
||||||
pump_id:
|
|
||||||
description: 选择泵
|
|
||||||
title: '注: 1号泵, 2号泵, 3号泵'
|
|
||||||
type: integer
|
type: integer
|
||||||
|
pump_id:
|
||||||
|
default: '1'
|
||||||
|
description: 选择泵
|
||||||
|
enum:
|
||||||
|
- '1'
|
||||||
|
- '2'
|
||||||
|
- '3'
|
||||||
|
title: '注: 1号泵, 2号泵, 3号泵'
|
||||||
|
type: string
|
||||||
speed:
|
speed:
|
||||||
default: 10
|
default: 10
|
||||||
description: 运动速度
|
description: 运动速度
|
||||||
type: string
|
type: integer
|
||||||
required:
|
required:
|
||||||
- pump_id
|
- pump_id
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: pump_initialize参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
pump_valve:
|
pump_valve:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
port: 1
|
port: 1
|
||||||
pump_id: 1
|
pump_id: 1
|
||||||
goal_default:
|
|
||||||
port: null
|
|
||||||
pump_id: null
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 切换指定泵的阀门端口
|
description: 切换指定泵的阀门端口
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
port:
|
port:
|
||||||
|
default: '1'
|
||||||
description: 阀门端口号 (1-8)
|
description: 阀门端口号 (1-8)
|
||||||
type: integer
|
enum:
|
||||||
|
- '1'
|
||||||
|
- '2'
|
||||||
|
- '3'
|
||||||
|
- '4'
|
||||||
|
- '5'
|
||||||
|
- '6'
|
||||||
|
- '7'
|
||||||
|
- '8'
|
||||||
|
type: string
|
||||||
pump_id:
|
pump_id:
|
||||||
|
default: '1'
|
||||||
description: 选择泵
|
description: 选择泵
|
||||||
type: integer
|
enum:
|
||||||
|
- '1'
|
||||||
|
- '2'
|
||||||
|
- '3'
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- pump_id
|
- pump_id
|
||||||
- port
|
- port
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: pump_valve参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
wait_sensor_level:
|
wait_sensor_level:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
target_state: 有液
|
target_state: 有液
|
||||||
timeout: 30
|
timeout: 30
|
||||||
goal_default:
|
|
||||||
target_state: 有液
|
|
||||||
timeout: 30
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 等待传感器液位条件
|
description: 等待传感器液位条件
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
target_state:
|
target_state:
|
||||||
default: 有液
|
default: 有液
|
||||||
description: 目标液位状态
|
description: 目标液位状态
|
||||||
|
enum:
|
||||||
|
- 有液
|
||||||
|
- 无液
|
||||||
type: string
|
type: string
|
||||||
timeout:
|
timeout:
|
||||||
default: 30
|
default: 30
|
||||||
description: 超时时间 (秒)
|
description: 超时时间 (秒)
|
||||||
type: integer
|
type: integer
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- target_state
|
||||||
title: wait_sensor_level参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
wait_time:
|
wait_time:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
duration: 10
|
duration: 10
|
||||||
goal_default:
|
|
||||||
duration: null
|
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
schema:
|
||||||
description: 等待指定时间
|
description: 等待指定时间
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
duration:
|
duration:
|
||||||
|
default: 10
|
||||||
description: 等待时间 (秒)
|
description: 等待时间 (秒)
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
- duration
|
- duration
|
||||||
type: object
|
type: object
|
||||||
result:
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: wait_time参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.separator.chinwe:ChinweDevice
|
module: unilabos.devices.separator.chinwe:ChinweDevice
|
||||||
status_types:
|
status_types:
|
||||||
@@ -445,8 +406,8 @@ separator.chinwe:
|
|||||||
sensor_rssi:
|
sensor_rssi:
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
- is_connected
|
|
||||||
- sensor_level
|
- sensor_level
|
||||||
- sensor_rssi
|
- sensor_rssi
|
||||||
|
- is_connected
|
||||||
type: object
|
type: object
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ coincellassemblyworkstation_device:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: fun_wuliao_test参数
|
title: fun_wuliao_test参数
|
||||||
@@ -110,8 +109,7 @@ coincellassemblyworkstation_device:
|
|||||||
- elec_num
|
- elec_num
|
||||||
- elec_use_num
|
- elec_use_num
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: func_allpack_cmd参数
|
title: func_allpack_cmd参数
|
||||||
@@ -222,8 +220,7 @@ coincellassemblyworkstation_device:
|
|||||||
- elec_num
|
- elec_num
|
||||||
- elec_use_num
|
- elec_use_num
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: func_allpack_cmd_simp参数
|
title: func_allpack_cmd_simp参数
|
||||||
@@ -312,8 +309,7 @@ coincellassemblyworkstation_device:
|
|||||||
type: boolean
|
type: boolean
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: func_pack_device_init_auto_start_combined参数
|
title: func_pack_device_init_auto_start_combined参数
|
||||||
@@ -355,8 +351,7 @@ coincellassemblyworkstation_device:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: func_pack_device_stop参数
|
title: func_pack_device_stop参数
|
||||||
@@ -381,8 +376,7 @@ coincellassemblyworkstation_device:
|
|||||||
type: string
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: func_pack_get_msg_cmd参数
|
title: func_pack_get_msg_cmd参数
|
||||||
@@ -436,8 +430,7 @@ coincellassemblyworkstation_device:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: func_pack_send_finished_cmd参数
|
title: func_pack_send_finished_cmd参数
|
||||||
@@ -474,8 +467,7 @@ coincellassemblyworkstation_device:
|
|||||||
- assembly_type
|
- assembly_type
|
||||||
- assembly_pressure
|
- assembly_pressure
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: func_pack_send_msg_cmd参数
|
title: func_pack_send_msg_cmd参数
|
||||||
@@ -619,8 +611,7 @@ coincellassemblyworkstation_device:
|
|||||||
- elec_num
|
- elec_num
|
||||||
- elec_use_num
|
- elec_use_num
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: func_sendbottle_allpack_multi参数
|
title: func_sendbottle_allpack_multi参数
|
||||||
@@ -672,6 +663,31 @@ coincellassemblyworkstation_device:
|
|||||||
title: modify_deck_name参数
|
title: modify_deck_name参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-qiming_coin_cell_code:
|
auto-qiming_coin_cell_code:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -719,8 +735,7 @@ coincellassemblyworkstation_device:
|
|||||||
required:
|
required:
|
||||||
- fujipian_panshu
|
- fujipian_panshu
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: qiming_coin_cell_code参数
|
title: qiming_coin_cell_code参数
|
||||||
@@ -811,24 +826,25 @@ coincellassemblyworkstation_device:
|
|||||||
sys_status:
|
sys_status:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- sys_status
|
||||||
|
- sys_mode
|
||||||
|
- request_rec_msg_status
|
||||||
|
- request_send_msg_status
|
||||||
- data_assembly_coin_cell_num
|
- data_assembly_coin_cell_num
|
||||||
- data_assembly_pressure
|
|
||||||
- data_assembly_time
|
- data_assembly_time
|
||||||
|
- data_open_circuit_voltage
|
||||||
- data_axis_x_pos
|
- data_axis_x_pos
|
||||||
- data_axis_y_pos
|
- data_axis_y_pos
|
||||||
- data_axis_z_pos
|
- data_axis_z_pos
|
||||||
- data_coin_cell_code
|
|
||||||
- data_coin_num
|
|
||||||
- data_electrolyte_code
|
|
||||||
- data_electrolyte_volume
|
|
||||||
- data_glove_box_o2_content
|
|
||||||
- data_glove_box_pressure
|
|
||||||
- data_glove_box_water_content
|
|
||||||
- data_open_circuit_voltage
|
|
||||||
- data_pole_weight
|
- data_pole_weight
|
||||||
- request_rec_msg_status
|
- data_assembly_pressure
|
||||||
- request_send_msg_status
|
- data_electrolyte_volume
|
||||||
- sys_mode
|
- data_coin_num
|
||||||
- sys_status
|
- data_coin_cell_code
|
||||||
|
- data_electrolyte_code
|
||||||
|
- data_glove_box_pressure
|
||||||
|
- data_glove_box_o2_content
|
||||||
|
- data_glove_box_water_content
|
||||||
type: object
|
type: object
|
||||||
|
registry_type: device
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -50,25 +50,26 @@ gas_source.mock:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -81,25 +82,26 @@ gas_source.mock:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -114,31 +116,32 @@ gas_source.mock:
|
|||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -229,25 +232,26 @@ vacuum_pump.mock:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -260,25 +264,26 @@ vacuum_pump.mock:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -293,31 +298,32 @@ vacuum_pump.mock:
|
|||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ hotel.thermo_orbitor_rs2_hotel:
|
|||||||
action_value_mappings: {}
|
action_value_mappings: {}
|
||||||
module: unilabos.devices.resource_container.container:HotelContainer
|
module: unilabos.devices.resource_container.container:HotelContainer
|
||||||
status_types:
|
status_types:
|
||||||
rotation: ''
|
rotation: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: Thermo Orbitor RS2 Hotel容器设备,用于实验室样品的存储和管理。该设备通过HotelContainer类实现容器的旋转控制和状态监控,主要用于存储实验样品、试剂瓶或其他实验器具,支持旋转功能以便于样品的自动化存取。适用于需要有序存储和快速访问大量样品的实验室自动化场景。
|
description: Thermo Orbitor RS2 Hotel容器设备,用于实验室样品的存储和管理。该设备通过HotelContainer类实现容器的旋转控制和状态监控,主要用于存储实验样品、试剂瓶或其他实验器具,支持旋转功能以便于样品的自动化存取。适用于需要有序存储和快速访问大量样品的实验室自动化场景。
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ xyz_stepper_controller:
|
|||||||
required:
|
required:
|
||||||
- degrees
|
- degrees
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: integer
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: degrees_to_steps参数
|
title: degrees_to_steps参数
|
||||||
@@ -48,8 +47,7 @@ xyz_stepper_controller:
|
|||||||
required:
|
required:
|
||||||
- axis
|
- axis
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: emergency_stop参数
|
title: emergency_stop参数
|
||||||
@@ -74,10 +72,7 @@ xyz_stepper_controller:
|
|||||||
type: boolean
|
type: boolean
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
additionalProperties:
|
|
||||||
type: boolean
|
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: enable_all_axes参数
|
title: enable_all_axes参数
|
||||||
@@ -106,8 +101,7 @@ xyz_stepper_controller:
|
|||||||
required:
|
required:
|
||||||
- axis
|
- axis
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: enable_motor参数
|
title: enable_motor参数
|
||||||
@@ -128,10 +122,7 @@ xyz_stepper_controller:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
additionalProperties:
|
|
||||||
type: boolean
|
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: home_all_axes参数
|
title: home_all_axes参数
|
||||||
@@ -156,8 +147,7 @@ xyz_stepper_controller:
|
|||||||
required:
|
required:
|
||||||
- axis
|
- axis
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: home_axis参数
|
title: home_axis参数
|
||||||
@@ -198,8 +188,7 @@ xyz_stepper_controller:
|
|||||||
- axis
|
- axis
|
||||||
- position
|
- position
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: move_to_position参数
|
title: move_to_position参数
|
||||||
@@ -240,8 +229,7 @@ xyz_stepper_controller:
|
|||||||
- axis
|
- axis
|
||||||
- degrees
|
- degrees
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: move_to_position_degrees参数
|
title: move_to_position_degrees参数
|
||||||
@@ -282,8 +270,7 @@ xyz_stepper_controller:
|
|||||||
- axis
|
- axis
|
||||||
- revolutions
|
- revolutions
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: move_to_position_revolutions参数
|
title: move_to_position_revolutions参数
|
||||||
@@ -314,17 +301,14 @@ xyz_stepper_controller:
|
|||||||
default: 5000
|
default: 5000
|
||||||
type: integer
|
type: integer
|
||||||
x:
|
x:
|
||||||
type: integer
|
type: string
|
||||||
y:
|
y:
|
||||||
type: integer
|
type: string
|
||||||
z:
|
z:
|
||||||
type: integer
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
additionalProperties:
|
|
||||||
type: boolean
|
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: move_xyz参数
|
title: move_xyz参数
|
||||||
@@ -355,17 +339,14 @@ xyz_stepper_controller:
|
|||||||
default: 5000
|
default: 5000
|
||||||
type: integer
|
type: integer
|
||||||
x_deg:
|
x_deg:
|
||||||
type: number
|
type: string
|
||||||
y_deg:
|
y_deg:
|
||||||
type: number
|
type: string
|
||||||
z_deg:
|
z_deg:
|
||||||
type: number
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
additionalProperties:
|
|
||||||
type: boolean
|
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: move_xyz_degrees参数
|
title: move_xyz_degrees参数
|
||||||
@@ -396,17 +377,14 @@ xyz_stepper_controller:
|
|||||||
default: 5000
|
default: 5000
|
||||||
type: integer
|
type: integer
|
||||||
x_rev:
|
x_rev:
|
||||||
type: number
|
type: string
|
||||||
y_rev:
|
y_rev:
|
||||||
type: number
|
type: string
|
||||||
z_rev:
|
z_rev:
|
||||||
type: number
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
additionalProperties:
|
|
||||||
type: boolean
|
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: move_xyz_revolutions参数
|
title: move_xyz_revolutions参数
|
||||||
@@ -431,8 +409,7 @@ xyz_stepper_controller:
|
|||||||
required:
|
required:
|
||||||
- revolutions
|
- revolutions
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: integer
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: revolutions_to_steps参数
|
title: revolutions_to_steps参数
|
||||||
@@ -465,8 +442,7 @@ xyz_stepper_controller:
|
|||||||
- axis
|
- axis
|
||||||
- speed
|
- speed
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: set_speed_mode参数
|
title: set_speed_mode参数
|
||||||
@@ -491,8 +467,7 @@ xyz_stepper_controller:
|
|||||||
required:
|
required:
|
||||||
- steps
|
- steps
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: number
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: steps_to_degrees参数
|
title: steps_to_degrees参数
|
||||||
@@ -517,8 +492,7 @@ xyz_stepper_controller:
|
|||||||
required:
|
required:
|
||||||
- steps
|
- steps
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: number
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: steps_to_revolutions参数
|
title: steps_to_revolutions参数
|
||||||
@@ -539,10 +513,7 @@ xyz_stepper_controller:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
additionalProperties:
|
|
||||||
type: boolean
|
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: stop_all_axes参数
|
title: stop_all_axes参数
|
||||||
@@ -571,8 +542,7 @@ xyz_stepper_controller:
|
|||||||
required:
|
required:
|
||||||
- axis
|
- axis
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: wait_for_completion参数
|
title: wait_for_completion参数
|
||||||
@@ -580,7 +550,8 @@ xyz_stepper_controller:
|
|||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.liquid_handling.laiyu.drivers.xyz_stepper_driver:XYZStepperController
|
module: unilabos.devices.liquid_handling.laiyu.drivers.xyz_stepper_driver:XYZStepperController
|
||||||
status_types:
|
status_types:
|
||||||
all_positions: Dict[MotorAxis, MotorPosition]
|
all_positions: dict
|
||||||
|
motor_status: unilabos.devices.liquid_handling.laiyu.drivers.xyz_stepper_driver:MotorPosition
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: 新XYZ控制器
|
description: 新XYZ控制器
|
||||||
@@ -603,10 +574,12 @@ xyz_stepper_controller:
|
|||||||
data:
|
data:
|
||||||
properties:
|
properties:
|
||||||
all_positions:
|
all_positions:
|
||||||
additionalProperties:
|
|
||||||
type: object
|
type: object
|
||||||
|
motor_status:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
- motor_status
|
||||||
- all_positions
|
- all_positions
|
||||||
type: object
|
type: object
|
||||||
|
registry_type: device
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,31 @@ neware_battery_test_system:
|
|||||||
- battery_test
|
- battery_test
|
||||||
class:
|
class:
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-print_status_summary:
|
auto-print_status_summary:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -41,8 +66,7 @@ neware_battery_test_system:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: boolean
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: test_connection参数
|
title: test_connection参数
|
||||||
@@ -53,8 +77,9 @@ neware_battery_test_system:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: 调试方法:显示所有资源的实际名称
|
description: 调试方法:显示所有资源的实际名称
|
||||||
properties:
|
properties:
|
||||||
@@ -64,10 +89,19 @@ neware_battery_test_system:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 资源调试信息
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
description: 是否成功
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: debug_resource_names参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
export_status_json:
|
export_status_json:
|
||||||
@@ -77,8 +111,9 @@ neware_battery_test_system:
|
|||||||
goal_default:
|
goal_default:
|
||||||
filepath: bts_status.json
|
filepath: bts_status.json
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: 导出当前状态数据到JSON文件
|
description: 导出当前状态数据到JSON文件
|
||||||
properties:
|
properties:
|
||||||
@@ -92,10 +127,19 @@ neware_battery_test_system:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 导出操作结果信息
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
description: 导出是否成功
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: export_status_json参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
get_device_summary:
|
get_device_summary:
|
||||||
@@ -137,8 +181,10 @@ neware_battery_test_system:
|
|||||||
goal_default:
|
goal_default:
|
||||||
plate_num: null
|
plate_num: null
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
plate_data: plate_data
|
||||||
|
return_info: return_info
|
||||||
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: 获取指定盘或所有盘的状态信息
|
description: 获取指定盘或所有盘的状态信息
|
||||||
properties:
|
properties:
|
||||||
@@ -147,14 +193,29 @@ neware_battery_test_system:
|
|||||||
properties:
|
properties:
|
||||||
plate_num:
|
plate_num:
|
||||||
description: 盘号 (1 或 2),如果为null则返回所有盘的状态
|
description: 盘号 (1 或 2),如果为null则返回所有盘的状态
|
||||||
|
maximum: 2
|
||||||
|
minimum: 1
|
||||||
type: integer
|
type: integer
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
plate_data:
|
||||||
|
description: 盘状态数据(单盘或所有盘)
|
||||||
|
type: object
|
||||||
|
return_info:
|
||||||
|
description: 操作结果信息
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
description: 查询是否成功
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
- plate_data
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: get_plate_status参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
print_status_summary_action:
|
print_status_summary_action:
|
||||||
@@ -162,8 +223,9 @@ neware_battery_test_system:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: 打印通道状态摘要信息到控制台
|
description: 打印通道状态摘要信息到控制台
|
||||||
properties:
|
properties:
|
||||||
@@ -173,21 +235,28 @@ neware_battery_test_system:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 打印操作结果信息
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
description: 打印是否成功
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: print_status_summary_action参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
query_plate_action:
|
query_plate_action:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
plate_id: plate_id
|
string: plate_id
|
||||||
string: string
|
|
||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
return_info: return_info
|
||||||
success: success
|
success: success
|
||||||
@@ -195,23 +264,27 @@ neware_battery_test_system:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -225,11 +298,13 @@ neware_battery_test_system:
|
|||||||
csv_path: string
|
csv_path: string
|
||||||
output_dir: string
|
output_dir: string
|
||||||
goal_default:
|
goal_default:
|
||||||
csv_path: null
|
csv_path: ''
|
||||||
output_dir: .
|
output_dir: .
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
|
submitted_count: submitted_count
|
||||||
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: 从CSV文件批量提交Neware测试任务
|
description: 从CSV文件批量提交Neware测试任务
|
||||||
properties:
|
properties:
|
||||||
@@ -240,17 +315,31 @@ neware_battery_test_system:
|
|||||||
description: 输入CSV文件的绝对路径
|
description: 输入CSV文件的绝对路径
|
||||||
type: string
|
type: string
|
||||||
output_dir:
|
output_dir:
|
||||||
default: .
|
|
||||||
description: 输出目录(用于存储XML和备份文件),默认当前目录
|
description: 输出目录(用于存储XML和备份文件),默认当前目录
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- csv_path
|
- csv_path
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 执行结果详细信息
|
||||||
|
type: string
|
||||||
|
submitted_count:
|
||||||
|
description: 成功提交的任务数量
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
description: 是否成功
|
||||||
|
type: boolean
|
||||||
|
total_count:
|
||||||
|
description: CSV文件中的总行数
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: submit_from_csv参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
test_connection_action:
|
test_connection_action:
|
||||||
@@ -258,8 +347,9 @@ neware_battery_test_system:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: 测试与电池测试系统的TCP连接
|
description: 测试与电池测试系统的TCP连接
|
||||||
properties:
|
properties:
|
||||||
@@ -269,10 +359,19 @@ neware_battery_test_system:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 连接测试结果信息
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
description: 连接测试是否成功
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: test_connection_action参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
upload_backup_to_oss:
|
upload_backup_to_oss:
|
||||||
@@ -293,8 +392,12 @@ neware_battery_test_system:
|
|||||||
handler_key: uploaded_files
|
handler_key: uploaded_files
|
||||||
io_type: sink
|
io_type: sink
|
||||||
label: Uploaded Files (with standard flow info)
|
label: Uploaded Files (with standard flow info)
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
failed_files: failed_files
|
||||||
|
return_info: return_info
|
||||||
|
success: success
|
||||||
|
total_count: total_count
|
||||||
|
uploaded_count: uploaded_count
|
||||||
schema:
|
schema:
|
||||||
description: 上传备份文件到阿里云OSS
|
description: 上传备份文件到阿里云OSS
|
||||||
properties:
|
properties:
|
||||||
@@ -314,17 +417,65 @@ neware_battery_test_system:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
failed_files:
|
||||||
|
description: 上传失败的文件名列表
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
return_info:
|
||||||
|
description: 上传操作结果信息
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
description: 上传是否成功
|
||||||
|
type: boolean
|
||||||
|
total_count:
|
||||||
|
description: 总文件数
|
||||||
|
type: integer
|
||||||
|
uploaded_count:
|
||||||
|
description: 成功上传的文件数
|
||||||
|
type: integer
|
||||||
|
uploaded_files:
|
||||||
|
description: 成功上传的文件详情列表
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
Battery_Code:
|
||||||
|
description: 电池编码
|
||||||
|
type: string
|
||||||
|
Electrolyte_Code:
|
||||||
|
description: 电解液编码
|
||||||
|
type: string
|
||||||
|
filename:
|
||||||
|
description: 文件名
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: OSS下载链接
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- filename
|
||||||
|
- url
|
||||||
|
- Battery_Code
|
||||||
|
- Electrolyte_Code
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
- uploaded_count
|
||||||
|
- total_count
|
||||||
|
- failed_files
|
||||||
|
- uploaded_files
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: upload_backup_to_oss参数
|
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
|
module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
|
||||||
status_types:
|
status_types:
|
||||||
channel_status: Dict[int, Dict]
|
channel_status: dict
|
||||||
connection_info: Dict[str, str]
|
connection_info: dict
|
||||||
device_summary: dict
|
device_summary: dict
|
||||||
|
plate_status: dict
|
||||||
status: str
|
status: str
|
||||||
total_channels: int
|
total_channels: int
|
||||||
type: python
|
type: python
|
||||||
@@ -366,24 +517,23 @@ neware_battery_test_system:
|
|||||||
data:
|
data:
|
||||||
properties:
|
properties:
|
||||||
channel_status:
|
channel_status:
|
||||||
additionalProperties:
|
|
||||||
type: object
|
|
||||||
type: object
|
type: object
|
||||||
connection_info:
|
connection_info:
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
type: object
|
||||||
device_summary:
|
device_summary:
|
||||||
type: object
|
type: object
|
||||||
|
plate_status:
|
||||||
|
type: object
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
total_channels:
|
total_channels:
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
|
- status
|
||||||
- channel_status
|
- channel_status
|
||||||
- connection_info
|
- connection_info
|
||||||
- device_summary
|
|
||||||
- status
|
|
||||||
- total_channels
|
- total_channels
|
||||||
|
- plate_status
|
||||||
|
- device_summary
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ opcua_example:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.device_comms.opcua_client.client:OpcUaClient
|
module: unilabos.device_comms.opcua_client.client:OpcUaClient
|
||||||
status_types: {}
|
status_types:
|
||||||
|
node_value: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: null
|
description: null
|
||||||
@@ -166,7 +167,10 @@ opcua_example:
|
|||||||
- url
|
- url
|
||||||
type: object
|
type: object
|
||||||
data:
|
data:
|
||||||
properties: {}
|
properties:
|
||||||
required: []
|
node_value:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- node_value
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ opsky_ATR30007:
|
|||||||
type: string
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: run_once参数
|
title: run_once参数
|
||||||
|
|||||||
@@ -100,41 +100,42 @@ rotavap.one:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
set_timer:
|
set_timer:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -249,13 +250,9 @@ separator.homemade:
|
|||||||
feedback:
|
feedback:
|
||||||
status: status
|
status: status
|
||||||
goal:
|
goal:
|
||||||
event: event
|
|
||||||
settling_time: settling_time
|
settling_time: settling_time
|
||||||
stir_speed: stir_speed
|
stir_speed: stir_speed
|
||||||
stir_time: stir_time
|
stir_time: stir_time,
|
||||||
time: time
|
|
||||||
time_spec: time_spec
|
|
||||||
vessel: vessel
|
|
||||||
goal_default:
|
goal_default:
|
||||||
event: ''
|
event: ''
|
||||||
settling_time: ''
|
settling_time: ''
|
||||||
@@ -284,42 +281,34 @@ separator.homemade:
|
|||||||
sample_id: ''
|
sample_id: ''
|
||||||
type: ''
|
type: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
message: message
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: Stir_Feedback
|
title: Stir_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
event:
|
event:
|
||||||
type: string
|
type: string
|
||||||
settling_time:
|
settling_time:
|
||||||
type: string
|
type: string
|
||||||
stir_speed:
|
stir_speed:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
stir_time:
|
stir_time:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
time:
|
time:
|
||||||
type: string
|
type: string
|
||||||
time_spec:
|
time_spec:
|
||||||
type: string
|
type: string
|
||||||
vessel:
|
vessel:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
@@ -338,26 +327,16 @@ separator.homemade:
|
|||||||
parent:
|
parent:
|
||||||
type: string
|
type: string
|
||||||
pose:
|
pose:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
orientation:
|
orientation:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
w:
|
w:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
x:
|
x:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
y:
|
y:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
z:
|
z:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- x
|
- x
|
||||||
@@ -367,19 +346,12 @@ separator.homemade:
|
|||||||
title: orientation
|
title: orientation
|
||||||
type: object
|
type: object
|
||||||
position:
|
position:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
x:
|
x:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
y:
|
y:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
z:
|
z:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- x
|
- x
|
||||||
@@ -409,10 +381,17 @@ separator.homemade:
|
|||||||
- data
|
- data
|
||||||
title: vessel
|
title: vessel
|
||||||
type: object
|
type: object
|
||||||
|
required:
|
||||||
|
- vessel
|
||||||
|
- time
|
||||||
|
- event
|
||||||
|
- time_spec
|
||||||
|
- stir_time
|
||||||
|
- stir_speed
|
||||||
|
- settling_time
|
||||||
title: Stir_Goal
|
title: Stir_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
@@ -420,6 +399,10 @@ separator.homemade:
|
|||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
- message
|
||||||
|
- return_info
|
||||||
title: Stir_Result
|
title: Stir_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -435,34 +418,36 @@ separator.homemade:
|
|||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -28,6 +28,31 @@ post_process_station:
|
|||||||
title: load_config参数
|
title: load_config参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-print_cache_stats:
|
auto-print_cache_stats:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -79,41 +104,42 @@ post_process_station:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
disconnect:
|
disconnect:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: {}
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -123,41 +149,42 @@ post_process_station:
|
|||||||
type: SendCmd
|
type: SendCmd
|
||||||
read_node:
|
read_node:
|
||||||
feedback:
|
feedback:
|
||||||
status: status
|
result: result
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: node_name
|
||||||
node_name: node_name
|
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -256,19 +283,17 @@ post_process_station:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: PostProcessTriggerClean_Feedback
|
title: PostProcessTriggerClean_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
acetone_inner_wall_cleaning_count:
|
acetone_inner_wall_cleaning_count:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
acetone_inner_wall_cleaning_injection:
|
acetone_inner_wall_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
acetone_inner_wall_cleaning_waste_time:
|
acetone_inner_wall_cleaning_waste_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -279,8 +304,6 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
acetone_outer_wall_cleaning_injection:
|
acetone_outer_wall_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
acetone_outer_wall_cleaning_wait_time:
|
acetone_outer_wall_cleaning_wait_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -299,8 +322,6 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
acetone_stirrer_cleaning_injection:
|
acetone_stirrer_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
acetone_stirrer_cleaning_wait_time:
|
acetone_stirrer_cleaning_wait_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -327,8 +348,6 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
nmp_inner_wall_cleaning_injection:
|
nmp_inner_wall_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
nmp_inner_wall_cleaning_waste_time:
|
nmp_inner_wall_cleaning_waste_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -339,8 +358,6 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
nmp_outer_wall_cleaning_injection:
|
nmp_outer_wall_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
nmp_outer_wall_cleaning_wait_time:
|
nmp_outer_wall_cleaning_wait_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -359,8 +376,6 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
nmp_stirrer_cleaning_injection:
|
nmp_stirrer_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
nmp_stirrer_cleaning_wait_time:
|
nmp_stirrer_cleaning_wait_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -379,8 +394,6 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
water_inner_wall_cleaning_injection:
|
water_inner_wall_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
water_inner_wall_cleaning_waste_time:
|
water_inner_wall_cleaning_waste_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -391,8 +404,6 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
water_outer_wall_cleaning_injection:
|
water_outer_wall_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
water_outer_wall_cleaning_wait_time:
|
water_outer_wall_cleaning_wait_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -411,8 +422,6 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
water_stirrer_cleaning_injection:
|
water_stirrer_cleaning_injection:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
water_stirrer_cleaning_wait_time:
|
water_stirrer_cleaning_wait_time:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -422,13 +431,55 @@ post_process_station:
|
|||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
|
required:
|
||||||
|
- nmp_outer_wall_cleaning_injection
|
||||||
|
- nmp_outer_wall_cleaning_count
|
||||||
|
- nmp_outer_wall_cleaning_wait_time
|
||||||
|
- nmp_outer_wall_cleaning_waste_time
|
||||||
|
- nmp_inner_wall_cleaning_injection
|
||||||
|
- nmp_inner_wall_cleaning_count
|
||||||
|
- nmp_pump_cleaning_suction_count
|
||||||
|
- nmp_inner_wall_cleaning_waste_time
|
||||||
|
- nmp_stirrer_cleaning_injection
|
||||||
|
- nmp_stirrer_cleaning_count
|
||||||
|
- nmp_stirrer_cleaning_wait_time
|
||||||
|
- nmp_stirrer_cleaning_waste_time
|
||||||
|
- water_outer_wall_cleaning_injection
|
||||||
|
- water_outer_wall_cleaning_count
|
||||||
|
- water_outer_wall_cleaning_wait_time
|
||||||
|
- water_outer_wall_cleaning_waste_time
|
||||||
|
- water_inner_wall_cleaning_injection
|
||||||
|
- water_inner_wall_cleaning_count
|
||||||
|
- water_pump_cleaning_suction_count
|
||||||
|
- water_inner_wall_cleaning_waste_time
|
||||||
|
- water_stirrer_cleaning_injection
|
||||||
|
- water_stirrer_cleaning_count
|
||||||
|
- water_stirrer_cleaning_wait_time
|
||||||
|
- water_stirrer_cleaning_waste_time
|
||||||
|
- acetone_outer_wall_cleaning_injection
|
||||||
|
- acetone_outer_wall_cleaning_count
|
||||||
|
- acetone_outer_wall_cleaning_wait_time
|
||||||
|
- acetone_outer_wall_cleaning_waste_time
|
||||||
|
- acetone_inner_wall_cleaning_injection
|
||||||
|
- acetone_inner_wall_cleaning_count
|
||||||
|
- acetone_pump_cleaning_suction_count
|
||||||
|
- acetone_inner_wall_cleaning_waste_time
|
||||||
|
- acetone_stirrer_cleaning_injection
|
||||||
|
- acetone_stirrer_cleaning_count
|
||||||
|
- acetone_stirrer_cleaning_wait_time
|
||||||
|
- acetone_stirrer_cleaning_waste_time
|
||||||
|
- pipe_blowing_time
|
||||||
|
- injection_pump_forward_empty_suction_count
|
||||||
|
- injection_pump_reverse_empty_suction_count
|
||||||
|
- filtration_liquid_selection
|
||||||
title: PostProcessTriggerClean_Goal
|
title: PostProcessTriggerClean_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: PostProcessTriggerClean_Result
|
title: PostProcessTriggerClean_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -451,11 +502,11 @@ post_process_station:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: PostProcessGrab_Feedback
|
title: PostProcessGrab_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
raw_tank_number:
|
raw_tank_number:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -465,13 +516,17 @@ post_process_station:
|
|||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
|
required:
|
||||||
|
- reaction_tank_number
|
||||||
|
- raw_tank_number
|
||||||
title: PostProcessGrab_Goal
|
title: PostProcessGrab_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: PostProcessGrab_Result
|
title: PostProcessGrab_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -518,15 +573,13 @@ post_process_station:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: PostProcessTriggerPostPro_Feedback
|
title: PostProcessTriggerPostPro_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
atomization_fast_speed:
|
atomization_fast_speed:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
atomization_pressure_kpa:
|
atomization_pressure_kpa:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -541,12 +594,8 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
first_wash_water_amount:
|
first_wash_water_amount:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
initial_water_amount:
|
initial_water_amount:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
injection_pump_push_speed:
|
injection_pump_push_speed:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -573,20 +622,32 @@ post_process_station:
|
|||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
second_wash_water_amount:
|
second_wash_water_amount:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
wash_slow_speed:
|
wash_slow_speed:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
|
required:
|
||||||
|
- atomization_fast_speed
|
||||||
|
- wash_slow_speed
|
||||||
|
- injection_pump_suction_speed
|
||||||
|
- injection_pump_push_speed
|
||||||
|
- raw_liquid_suction_count
|
||||||
|
- first_wash_water_amount
|
||||||
|
- second_wash_water_amount
|
||||||
|
- first_powder_mixing_tim
|
||||||
|
- second_powder_mixing_time
|
||||||
|
- first_powder_wash_count
|
||||||
|
- second_powder_wash_count
|
||||||
|
- initial_water_amount
|
||||||
|
- pre_filtration_mixing_time
|
||||||
|
- atomization_pressure_kpa
|
||||||
title: PostProcessTriggerPostPro_Goal
|
title: PostProcessTriggerPostPro_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: PostProcessTriggerPostPro_Result
|
title: PostProcessTriggerPostPro_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -608,26 +669,30 @@ post_process_station:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -637,7 +702,8 @@ post_process_station:
|
|||||||
type: SendCmd
|
type: SendCmd
|
||||||
module: unilabos.devices.workstation.post_process.post_process:OpcUaClient
|
module: unilabos.devices.workstation.post_process.post_process:OpcUaClient
|
||||||
status_types:
|
status_types:
|
||||||
cache_stats: Dict[str, Any]
|
cache_stats: dict
|
||||||
|
node_value: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: 后处理站
|
description: 后处理站
|
||||||
@@ -652,9 +718,7 @@ post_process_station:
|
|||||||
config_path:
|
config_path:
|
||||||
type: string
|
type: string
|
||||||
deck:
|
deck:
|
||||||
anyOf:
|
type: string
|
||||||
- type: object
|
|
||||||
- type: object
|
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
subscription_interval:
|
subscription_interval:
|
||||||
@@ -674,7 +738,10 @@ post_process_station:
|
|||||||
properties:
|
properties:
|
||||||
cache_stats:
|
cache_stats:
|
||||||
type: object
|
type: object
|
||||||
|
node_value:
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- node_value
|
||||||
- cache_stats
|
- cache_stats
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -136,36 +136,36 @@ solenoid_valve:
|
|||||||
set_valve_position:
|
set_valve_position:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
position: position
|
string: position
|
||||||
string: string
|
|
||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -278,25 +278,26 @@ solenoid_valve.mock:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -309,25 +310,26 @@ solenoid_valve.mock:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -420,27 +422,6 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-list:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: list的参数schema
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: list参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-pull_plunger:
|
auto-pull_plunger:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -714,10 +695,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
position:
|
position:
|
||||||
anyOf:
|
type: string
|
||||||
- type: integer
|
|
||||||
- type: string
|
|
||||||
- type: number
|
|
||||||
required:
|
required:
|
||||||
- position
|
- position
|
||||||
type: object
|
type: object
|
||||||
@@ -742,9 +720,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
velocity:
|
velocity:
|
||||||
anyOf:
|
type: string
|
||||||
- type: integer
|
|
||||||
- type: string
|
|
||||||
required:
|
required:
|
||||||
- velocity
|
- velocity
|
||||||
type: object
|
type: object
|
||||||
@@ -804,13 +780,13 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
|||||||
status_types:
|
status_types:
|
||||||
max_velocity: float
|
max_velocity: float
|
||||||
mode: int
|
mode: int
|
||||||
plunger_position: ''
|
plunger_position: String
|
||||||
position: float
|
position: float
|
||||||
status: str
|
status: str
|
||||||
valve_position: str
|
valve_position: str
|
||||||
velocity_end: ''
|
velocity_end: String
|
||||||
velocity_grade: ''
|
velocity_grade: String
|
||||||
velocity_init: ''
|
velocity_init: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
|
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
|
||||||
@@ -909,15 +885,15 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
|||||||
velocity_init:
|
velocity_init:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- max_velocity
|
|
||||||
- mode
|
|
||||||
- plunger_position
|
|
||||||
- position
|
|
||||||
- status
|
- status
|
||||||
- valve_position
|
- mode
|
||||||
- velocity_end
|
- max_velocity
|
||||||
- velocity_grade
|
- velocity_grade
|
||||||
- velocity_init
|
- velocity_init
|
||||||
|
- velocity_end
|
||||||
|
- valve_position
|
||||||
|
- position
|
||||||
|
- plunger_position
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
syringe_pump_with_valve.runze.SY03B-T08:
|
syringe_pump_with_valve.runze.SY03B-T08:
|
||||||
@@ -967,27 +943,6 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-list:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: list的参数schema
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: list参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-pull_plunger:
|
auto-pull_plunger:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -1261,10 +1216,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
position:
|
position:
|
||||||
anyOf:
|
type: string
|
||||||
- type: integer
|
|
||||||
- type: string
|
|
||||||
- type: number
|
|
||||||
required:
|
required:
|
||||||
- position
|
- position
|
||||||
type: object
|
type: object
|
||||||
@@ -1289,9 +1241,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
velocity:
|
velocity:
|
||||||
anyOf:
|
type: string
|
||||||
- type: integer
|
|
||||||
- type: string
|
|
||||||
required:
|
required:
|
||||||
- velocity
|
- velocity
|
||||||
type: object
|
type: object
|
||||||
@@ -1351,13 +1301,13 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
|||||||
status_types:
|
status_types:
|
||||||
max_velocity: float
|
max_velocity: float
|
||||||
mode: int
|
mode: int
|
||||||
plunger_position: ''
|
plunger_position: String
|
||||||
position: float
|
position: float
|
||||||
status: str
|
status: str
|
||||||
valve_position: str
|
valve_position: str
|
||||||
velocity_end: ''
|
velocity_end: String
|
||||||
velocity_grade: ''
|
velocity_grade: String
|
||||||
velocity_init: ''
|
velocity_init: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
|
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
|
||||||
@@ -1472,14 +1422,14 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
|||||||
velocity_init:
|
velocity_init:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- max_velocity
|
|
||||||
- mode
|
|
||||||
- plunger_position
|
|
||||||
- position
|
|
||||||
- status
|
- status
|
||||||
- valve_position
|
- mode
|
||||||
- velocity_end
|
- max_velocity
|
||||||
- velocity_grade
|
- velocity_grade
|
||||||
- velocity_init
|
- velocity_init
|
||||||
|
- velocity_end
|
||||||
|
- valve_position
|
||||||
|
- position
|
||||||
|
- plunger_position
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ reaction_station.bioyond:
|
|||||||
start_point: start_point
|
start_point: start_point
|
||||||
start_step_key: start_step_key
|
start_step_key: start_step_key
|
||||||
goal_default:
|
goal_default:
|
||||||
duration: null
|
duration: 0
|
||||||
end_point: 0
|
end_point: 0
|
||||||
end_step_key: ''
|
end_step_key: ''
|
||||||
start_point: 0
|
start_point: 0
|
||||||
start_step_key: ''
|
start_step_key: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 添加时间约束 - 在两个工作流之间添加时间约束
|
description: 添加时间约束 - 在两个工作流之间添加时间约束
|
||||||
@@ -31,19 +30,23 @@ reaction_station.bioyond:
|
|||||||
description: 时间(秒)
|
description: 时间(秒)
|
||||||
type: integer
|
type: integer
|
||||||
end_point:
|
end_point:
|
||||||
default: 0
|
default: Start
|
||||||
description: 终点计时点 (Start=开始前, End=结束后)
|
description: 终点计时点 (Start=开始前, End=结束后)
|
||||||
type: integer
|
enum:
|
||||||
|
- Start
|
||||||
|
- End
|
||||||
|
type: string
|
||||||
end_step_key:
|
end_step_key:
|
||||||
default: ''
|
|
||||||
description: 终点步骤Key (可选, 默认为空则自动选择)
|
description: 终点步骤Key (可选, 默认为空则自动选择)
|
||||||
type: string
|
type: string
|
||||||
start_point:
|
start_point:
|
||||||
default: 0
|
default: Start
|
||||||
description: 起点计时点 (Start=开始前, End=结束后)
|
description: 起点计时点 (Start=开始前, End=结束后)
|
||||||
type: integer
|
enum:
|
||||||
|
- Start
|
||||||
|
- End
|
||||||
|
type: string
|
||||||
start_step_key:
|
start_step_key:
|
||||||
default: ''
|
|
||||||
description: 起点步骤Key (例如 "feeding", "liquid", 可选, 默认为空则自动选择)
|
description: 起点步骤Key (例如 "feeding", "liquid", 可选, 默认为空则自动选择)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
@@ -95,8 +98,7 @@ reaction_station.bioyond:
|
|||||||
required:
|
required:
|
||||||
- json_str
|
- json_str
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: create_order参数
|
title: create_order参数
|
||||||
@@ -123,8 +125,7 @@ reaction_station.bioyond:
|
|||||||
required:
|
required:
|
||||||
- workflow_ids
|
- workflow_ids
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: hard_delete_merged_workflows参数
|
title: hard_delete_merged_workflows参数
|
||||||
@@ -149,8 +150,7 @@ reaction_station.bioyond:
|
|||||||
required:
|
required:
|
||||||
- json_str
|
- json_str
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: merge_workflow_with_parameters参数
|
title: merge_workflow_with_parameters参数
|
||||||
@@ -175,8 +175,7 @@ reaction_station.bioyond:
|
|||||||
required:
|
required:
|
||||||
- report_request
|
- report_request
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: process_temperature_cutoff_report参数
|
title: process_temperature_cutoff_report参数
|
||||||
@@ -201,12 +200,7 @@ reaction_station.bioyond:
|
|||||||
required:
|
required:
|
||||||
- web_workflow_json
|
- web_workflow_json
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
items:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: process_web_workflows参数
|
title: process_web_workflows参数
|
||||||
@@ -235,8 +229,7 @@ reaction_station.bioyond:
|
|||||||
- reactor_id
|
- reactor_id
|
||||||
- temperature
|
- temperature
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: string
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: set_reactor_temperature参数
|
title: set_reactor_temperature参数
|
||||||
@@ -261,8 +254,7 @@ reaction_station.bioyond:
|
|||||||
required:
|
required:
|
||||||
- preintake_id
|
- preintake_id
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: skip_titration_steps参数
|
title: skip_titration_steps参数
|
||||||
@@ -283,8 +275,7 @@ reaction_station.bioyond:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: sync_workflow_sequence_from_bioyond参数
|
title: sync_workflow_sequence_from_bioyond参数
|
||||||
@@ -316,8 +307,7 @@ reaction_station.bioyond:
|
|||||||
type: integer
|
type: integer
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: wait_for_multiple_orders_and_get_reports参数
|
title: wait_for_multiple_orders_and_get_reports参数
|
||||||
@@ -369,8 +359,7 @@ reaction_station.bioyond:
|
|||||||
required:
|
required:
|
||||||
- workflow_id
|
- workflow_id
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: workflow_step_query参数
|
title: workflow_step_query参数
|
||||||
@@ -381,8 +370,9 @@ reaction_station.bioyond:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
code: code
|
||||||
|
message: message
|
||||||
schema:
|
schema:
|
||||||
description: 清空服务端所有非核心工作流 (保留核心流程)
|
description: 清空服务端所有非核心工作流 (保留核心流程)
|
||||||
properties:
|
properties:
|
||||||
@@ -392,6 +382,13 @@ reaction_station.bioyond:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
description: 操作结果代码(1表示成功)
|
||||||
|
type: integer
|
||||||
|
message:
|
||||||
|
description: 结果描述
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
@@ -408,14 +405,13 @@ reaction_station.bioyond:
|
|||||||
torque_variation: torque_variation
|
torque_variation: torque_variation
|
||||||
volume: volume
|
volume: volume
|
||||||
goal_default:
|
goal_default:
|
||||||
assign_material_name: null
|
assign_material_name: ''
|
||||||
temperature: 25.0
|
temperature: ''
|
||||||
time: '90'
|
time: ''
|
||||||
titration_type: '1'
|
titration_type: ''
|
||||||
torque_variation: 2
|
torque_variation: ''
|
||||||
volume: null
|
volume: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 滴回去
|
description: 滴回去
|
||||||
@@ -427,27 +423,33 @@ reaction_station.bioyond:
|
|||||||
description: 物料名称(不能为空)
|
description: 物料名称(不能为空)
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
default: 25.0
|
|
||||||
description: 温度设定(°C)
|
description: 温度设定(°C)
|
||||||
type: number
|
type: string
|
||||||
time:
|
time:
|
||||||
default: '90'
|
|
||||||
description: 观察时间(分钟)
|
description: 观察时间(分钟)
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
default: '1'
|
|
||||||
description: 是否滴定(NO=否, YES=是)
|
description: 是否滴定(NO=否, YES=是)
|
||||||
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
default: 2
|
|
||||||
description: 是否观察 (NO=否, YES=是)
|
description: 是否观察 (NO=否, YES=是)
|
||||||
type: integer
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
|
type: string
|
||||||
volume:
|
volume:
|
||||||
description: 分液公式(mL)
|
description: 分液公式(mL)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- assign_material_name
|
|
||||||
- volume
|
- volume
|
||||||
|
- assign_material_name
|
||||||
|
- time
|
||||||
|
- torque_variation
|
||||||
|
- titration_type
|
||||||
|
- temperature
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
@@ -460,7 +462,7 @@ reaction_station.bioyond:
|
|||||||
goal:
|
goal:
|
||||||
batch_reports_result: batch_reports_result
|
batch_reports_result: batch_reports_result
|
||||||
goal_default:
|
goal_default:
|
||||||
batch_reports_result: null
|
batch_reports_result: ''
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: batch_reports_result
|
- data_key: batch_reports_result
|
||||||
@@ -476,8 +478,8 @@ reaction_station.bioyond:
|
|||||||
handler_key: ACTUALS_EXTRACTED
|
handler_key: ACTUALS_EXTRACTED
|
||||||
io_type: sink
|
io_type: sink
|
||||||
label: Extracted Actuals
|
label: Extracted Actuals
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
schema:
|
schema:
|
||||||
description: 从批量任务完成报告中提取每个订单的实际加料量,输出extracted列表。
|
description: 从批量任务完成报告中提取每个订单的实际加料量,输出extracted列表。
|
||||||
properties:
|
properties:
|
||||||
@@ -491,6 +493,13 @@ reaction_station.bioyond:
|
|||||||
- batch_reports_result
|
- batch_reports_result
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: JSON字符串,包含actuals数组,每项含order_code, order_id, actualTargetWeigh,
|
||||||
|
actualVolume
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: extract_actuals_from_batch_reports结果
|
title: extract_actuals_from_batch_reports结果
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -508,14 +517,13 @@ reaction_station.bioyond:
|
|||||||
torque_variation: torque_variation
|
torque_variation: torque_variation
|
||||||
volume: volume
|
volume: volume
|
||||||
goal_default:
|
goal_default:
|
||||||
assign_material_name: BAPP
|
assign_material_name: ''
|
||||||
temperature: 25.0
|
temperature: ''
|
||||||
time: '0'
|
time: ''
|
||||||
titration_type: '1'
|
titration_type: ''
|
||||||
torque_variation: 1
|
torque_variation: ''
|
||||||
volume: '350'
|
volume: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 液体进料烧杯
|
description: 液体进料烧杯
|
||||||
@@ -524,30 +532,36 @@ reaction_station.bioyond:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
assign_material_name:
|
assign_material_name:
|
||||||
default: BAPP
|
|
||||||
description: 物料名称
|
description: 物料名称
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
default: 25.0
|
|
||||||
description: 温度设定(°C)
|
description: 温度设定(°C)
|
||||||
type: number
|
type: string
|
||||||
time:
|
time:
|
||||||
default: '0'
|
|
||||||
description: 观察时间(分钟)
|
description: 观察时间(分钟)
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
default: '1'
|
|
||||||
description: 是否滴定(NO=否, YES=是)
|
description: 是否滴定(NO=否, YES=是)
|
||||||
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
default: 1
|
|
||||||
description: 是否观察 (NO=否, YES=是)
|
description: 是否观察 (NO=否, YES=是)
|
||||||
type: integer
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
|
type: string
|
||||||
volume:
|
volume:
|
||||||
default: '350'
|
|
||||||
description: 分液公式(mL)
|
description: 分液公式(mL)
|
||||||
type: string
|
type: string
|
||||||
required: []
|
required:
|
||||||
|
- volume
|
||||||
|
- assign_material_name
|
||||||
|
- time
|
||||||
|
- torque_variation
|
||||||
|
- titration_type
|
||||||
|
- temperature
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
@@ -566,13 +580,13 @@ reaction_station.bioyond:
|
|||||||
torque_variation: torque_variation
|
torque_variation: torque_variation
|
||||||
volume: volume
|
volume: volume
|
||||||
goal_default:
|
goal_default:
|
||||||
assign_material_name: null
|
assign_material_name: ''
|
||||||
solvents: null
|
solvents: ''
|
||||||
temperature: 25.0
|
temperature: '25.00'
|
||||||
time: '360'
|
time: '360'
|
||||||
titration_type: '1'
|
titration_type: '1'
|
||||||
torque_variation: 2
|
torque_variation: '2'
|
||||||
volume: null
|
volume: ''
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: solvents
|
- data_key: solvents
|
||||||
@@ -581,7 +595,6 @@ reaction_station.bioyond:
|
|||||||
handler_key: solvents
|
handler_key: solvents
|
||||||
io_type: source
|
io_type: source
|
||||||
label: Solvents Data From Calculation Node
|
label: Solvents Data From Calculation Node
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 液体投料-溶剂。可以直接提供volume(mL),或通过solvents对象自动从additional_solvent(mL)计算volume。
|
description: 液体投料-溶剂。可以直接提供volume(mL),或通过solvents对象自动从additional_solvent(mL)计算volume。
|
||||||
@@ -596,21 +609,27 @@ reaction_station.bioyond:
|
|||||||
description: '溶剂信息对象(可选),包含: additional_solvent(溶剂体积mL), total_liquid_volume(总液体体积mL)。如果提供,将自动计算volume'
|
description: '溶剂信息对象(可选),包含: additional_solvent(溶剂体积mL), total_liquid_volume(总液体体积mL)。如果提供,将自动计算volume'
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
default: 25.0
|
default: '25.00'
|
||||||
description: 温度设定(°C),默认25.00
|
description: 温度设定(°C),默认25.00
|
||||||
type: number
|
type: string
|
||||||
time:
|
time:
|
||||||
default: '360'
|
default: '360'
|
||||||
description: 观察时间(分钟),默认360
|
description: 观察时间(分钟),默认360
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
default: '1'
|
default: 'NO'
|
||||||
description: 是否滴定(NO=否, YES=是),默认NO
|
description: 是否滴定(NO=否, YES=是),默认NO
|
||||||
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
default: 2
|
default: 'YES'
|
||||||
description: 是否观察 (NO=否, YES=是),默认YES
|
description: 是否观察 (NO=否, YES=是),默认YES
|
||||||
type: integer
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
|
type: string
|
||||||
volume:
|
volume:
|
||||||
description: 分液量(mL)。可直接提供,或通过solvents参数自动计算
|
description: 分液量(mL)。可直接提供,或通过solvents参数自动计算
|
||||||
type: string
|
type: string
|
||||||
@@ -636,15 +655,15 @@ reaction_station.bioyond:
|
|||||||
volume_formula: volume_formula
|
volume_formula: volume_formula
|
||||||
x_value: x_value
|
x_value: x_value
|
||||||
goal_default:
|
goal_default:
|
||||||
assign_material_name: null
|
assign_material_name: ''
|
||||||
extracted_actuals: null
|
extracted_actuals: ''
|
||||||
feeding_order_data: null
|
feeding_order_data: ''
|
||||||
temperature: 25.0
|
temperature: '25.00'
|
||||||
time: '90'
|
time: '90'
|
||||||
titration_type: '2'
|
titration_type: '2'
|
||||||
torque_variation: 2
|
torque_variation: '2'
|
||||||
volume_formula: null
|
volume_formula: ''
|
||||||
x_value: null
|
x_value: ''
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: extracted_actuals
|
- data_key: extracted_actuals
|
||||||
@@ -659,7 +678,6 @@ reaction_station.bioyond:
|
|||||||
handler_key: feeding_order
|
handler_key: feeding_order
|
||||||
io_type: source
|
io_type: source
|
||||||
label: Feeding Order Data From Calculation Node
|
label: Feeding Order Data From Calculation Node
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 液体进料(滴定)。支持两种模式:1)直接提供volume_formula;2)自动计算-提供x_value+feeding_order_data+extracted_actuals,系统自动生成公式"1000*(m二酐-x)*V二酐滴定/m二酐滴定"
|
description: 液体进料(滴定)。支持两种模式:1)直接提供volume_formula;2)自动计算-提供x_value+feeding_order_data+extracted_actuals,系统自动生成公式"1000*(m二酐-x)*V二酐滴定/m二酐滴定"
|
||||||
@@ -678,21 +696,27 @@ reaction_station.bioyond:
|
|||||||
{"feeding_order": [{"type": "main_anhydride", "amount": 1.915}]}'
|
{"feeding_order": [{"type": "main_anhydride", "amount": 1.915}]}'
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
default: 25.0
|
default: '25.00'
|
||||||
description: 温度设定(°C),默认25.00
|
description: 温度设定(°C),默认25.00
|
||||||
type: number
|
type: string
|
||||||
time:
|
time:
|
||||||
default: '90'
|
default: '90'
|
||||||
description: 观察时间(分钟),默认90
|
description: 观察时间(分钟),默认90
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
default: '2'
|
default: 'YES'
|
||||||
description: 是否滴定(NO=否, YES=是),默认YES
|
description: 是否滴定(NO=否, YES=是),默认YES
|
||||||
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
default: 2
|
default: 'YES'
|
||||||
description: 是否观察 (NO=否, YES=是),默认YES
|
description: 是否观察 (NO=否, YES=是),默认YES
|
||||||
type: integer
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
|
type: string
|
||||||
volume_formula:
|
volume_formula:
|
||||||
description: 分液公式(mL)。可直接提供固定公式,或留空由系统根据x_value、feeding_order_data、extracted_actuals自动生成
|
description: 分液公式(mL)。可直接提供固定公式,或留空由系统根据x_value、feeding_order_data、extracted_actuals自动生成
|
||||||
type: string
|
type: string
|
||||||
@@ -718,14 +742,13 @@ reaction_station.bioyond:
|
|||||||
torque_variation: torque_variation
|
torque_variation: torque_variation
|
||||||
volume_formula: volume_formula
|
volume_formula: volume_formula
|
||||||
goal_default:
|
goal_default:
|
||||||
assign_material_name: null
|
assign_material_name: ''
|
||||||
temperature: 25.0
|
temperature: ''
|
||||||
time: '0'
|
time: ''
|
||||||
titration_type: '1'
|
titration_type: ''
|
||||||
torque_variation: 1
|
torque_variation: ''
|
||||||
volume_formula: null
|
volume_formula: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 液体进料小瓶(非滴定)
|
description: 液体进料小瓶(非滴定)
|
||||||
@@ -737,27 +760,33 @@ reaction_station.bioyond:
|
|||||||
description: 物料名称
|
description: 物料名称
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
default: 25.0
|
|
||||||
description: 温度设定(°C)
|
description: 温度设定(°C)
|
||||||
type: number
|
type: string
|
||||||
time:
|
time:
|
||||||
default: '0'
|
|
||||||
description: 观察时间(分钟)
|
description: 观察时间(分钟)
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
default: '1'
|
|
||||||
description: 是否滴定(NO=否, YES=是)
|
description: 是否滴定(NO=否, YES=是)
|
||||||
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
default: 1
|
|
||||||
description: 是否观察 (NO=否, YES=是)
|
description: 是否观察 (NO=否, YES=是)
|
||||||
type: integer
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
|
type: string
|
||||||
volume_formula:
|
volume_formula:
|
||||||
description: 分液公式(mL)
|
description: 分液公式(mL)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- volume_formula
|
- volume_formula
|
||||||
- assign_material_name
|
- assign_material_name
|
||||||
|
- time
|
||||||
|
- torque_variation
|
||||||
|
- titration_type
|
||||||
|
- temperature
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
@@ -771,10 +800,9 @@ reaction_station.bioyond:
|
|||||||
task_name: task_name
|
task_name: task_name
|
||||||
workflow_name: workflow_name
|
workflow_name: workflow_name
|
||||||
goal_default:
|
goal_default:
|
||||||
task_name: null
|
task_name: ''
|
||||||
workflow_name: null
|
workflow_name: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 处理并执行工作流
|
description: 处理并执行工作流
|
||||||
@@ -792,8 +820,7 @@ reaction_station.bioyond:
|
|||||||
- workflow_name
|
- workflow_name
|
||||||
- task_name
|
- task_name
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: process_and_execute_workflow参数
|
title: process_and_execute_workflow参数
|
||||||
@@ -806,11 +833,10 @@ reaction_station.bioyond:
|
|||||||
cutoff: cutoff
|
cutoff: cutoff
|
||||||
temperature: temperature
|
temperature: temperature
|
||||||
goal_default:
|
goal_default:
|
||||||
assign_material_name: null
|
assign_material_name: ''
|
||||||
cutoff: '900000'
|
cutoff: ''
|
||||||
temperature: -10.0
|
temperature: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 反应器放入 - 将反应器放入工作站,配置物料名称、粘度上限和温度参数
|
description: 反应器放入 - 将反应器放入工作站,配置物料名称、粘度上限和温度参数
|
||||||
@@ -822,14 +848,14 @@ reaction_station.bioyond:
|
|||||||
description: 物料名称
|
description: 物料名称
|
||||||
type: string
|
type: string
|
||||||
cutoff:
|
cutoff:
|
||||||
default: '900000'
|
|
||||||
description: 粘度上限
|
description: 粘度上限
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
default: -10.0
|
|
||||||
description: 温度设定(°C)
|
description: 温度设定(°C)
|
||||||
type: number
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- cutoff
|
||||||
|
- temperature
|
||||||
- assign_material_name
|
- assign_material_name
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
@@ -843,7 +869,6 @@ reaction_station.bioyond:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 反应器取出 - 从工作站中取出反应器,无需参数的简单操作
|
description: 反应器取出 - 从工作站中取出反应器,无需参数的简单操作
|
||||||
@@ -853,7 +878,15 @@ reaction_station.bioyond:
|
|||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result:
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
description: 操作结果代码(1表示成功,0表示失败)
|
||||||
|
type: integer
|
||||||
|
return_info:
|
||||||
|
description: 操作结果详细信息
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: reactor_taken_out参数
|
title: reactor_taken_out参数
|
||||||
@@ -864,8 +897,8 @@ reaction_station.bioyond:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result:
|
||||||
result: {}
|
return_info: return_info
|
||||||
schema:
|
schema:
|
||||||
description: 启动调度器 - 启动Bioyond工作站的任务调度器,开始执行队列中的任务
|
description: 启动调度器 - 启动Bioyond工作站的任务调度器,开始执行队列中的任务
|
||||||
properties:
|
properties:
|
||||||
@@ -875,6 +908,12 @@ reaction_station.bioyond:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 调度器启动结果,成功返回1,失败返回0
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: scheduler_start结果
|
title: scheduler_start结果
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -891,13 +930,12 @@ reaction_station.bioyond:
|
|||||||
time: time
|
time: time
|
||||||
torque_variation: torque_variation
|
torque_variation: torque_variation
|
||||||
goal_default:
|
goal_default:
|
||||||
assign_material_name: null
|
assign_material_name: ''
|
||||||
material_id: null
|
material_id: ''
|
||||||
temperature: 25.0
|
temperature: ''
|
||||||
time: '0'
|
time: ''
|
||||||
torque_variation: 1
|
torque_variation: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 固体进料小瓶 - 通过小瓶向反应器中添加固体物料,支持多种粉末类型(盐、面粉、BTDA)
|
description: 固体进料小瓶 - 通过小瓶向反应器中添加固体物料,支持多种粉末类型(盐、面粉、BTDA)
|
||||||
@@ -910,21 +948,29 @@ reaction_station.bioyond:
|
|||||||
type: string
|
type: string
|
||||||
material_id:
|
material_id:
|
||||||
description: 粉末类型ID,Salt=盐(21分钟),Flour=面粉(27分钟),BTDA=BTDA(38分钟)
|
description: 粉末类型ID,Salt=盐(21分钟),Flour=面粉(27分钟),BTDA=BTDA(38分钟)
|
||||||
|
enum:
|
||||||
|
- Salt
|
||||||
|
- Flour
|
||||||
|
- BTDA
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
default: 25.0
|
|
||||||
description: 温度设定(°C)
|
description: 温度设定(°C)
|
||||||
type: number
|
type: string
|
||||||
time:
|
time:
|
||||||
default: '0'
|
|
||||||
description: 观察时间(分钟)
|
description: 观察时间(分钟)
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
default: 1
|
|
||||||
description: 是否观察 (NO=否, YES=是)
|
description: 是否观察 (NO=否, YES=是)
|
||||||
type: integer
|
enum:
|
||||||
|
- 'NO'
|
||||||
|
- 'YES'
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- assign_material_name
|
||||||
- material_id
|
- material_id
|
||||||
|
- time
|
||||||
|
- torque_variation
|
||||||
|
- temperature
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -37,41 +37,42 @@ agv.SEER:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
send_nav_task:
|
send_nav_task:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -122,6 +122,31 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
|||||||
title: moveit_task参数
|
title: moveit_task参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: post_init的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-resource_manager:
|
auto-resource_manager:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -173,41 +198,41 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
pick_and_place:
|
pick_and_place:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -216,41 +241,41 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
|||||||
type: object
|
type: object
|
||||||
type: SendCmd
|
type: SendCmd
|
||||||
set_position:
|
set_position:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -259,41 +284,41 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
|||||||
type: object
|
type: object
|
||||||
type: SendCmd
|
type: SendCmd
|
||||||
set_status:
|
set_status:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -430,41 +455,42 @@ robotic_arm.UR:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
move_pos_task:
|
move_pos_task:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -506,8 +532,8 @@ robotic_arm.UR:
|
|||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- arm_pose
|
- arm_pose
|
||||||
- arm_status
|
|
||||||
- gripper_pose
|
- gripper_pose
|
||||||
|
- arm_status
|
||||||
- gripper_status
|
- gripper_status
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
@@ -700,41 +726,41 @@ robotic_arm.elite:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
modbus_task_cmd:
|
modbus_task_cmd:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -744,8 +770,8 @@ robotic_arm.elite:
|
|||||||
type: SendCmd
|
type: SendCmd
|
||||||
module: unilabos.devices.arm.elite_robot:EliteRobot
|
module: unilabos.devices.arm.elite_robot:EliteRobot
|
||||||
status_types:
|
status_types:
|
||||||
actual_joint_positions: ''
|
actual_joint_positions: String
|
||||||
arm_pose: list[float]
|
arm_pose: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: Elite robot arm
|
description: Elite robot arm
|
||||||
@@ -771,8 +797,8 @@ robotic_arm.elite:
|
|||||||
type: number
|
type: number
|
||||||
type: array
|
type: array
|
||||||
required:
|
required:
|
||||||
- actual_joint_positions
|
|
||||||
- arm_pose
|
- arm_pose
|
||||||
|
- actual_joint_positions
|
||||||
type: object
|
type: object
|
||||||
model:
|
model:
|
||||||
mesh: elite_robot
|
mesh: elite_robot
|
||||||
|
|||||||
@@ -114,12 +114,11 @@ gripper.misumi_rz:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
type: object
|
type: string
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: modbus_crc参数
|
title: modbus_crc参数
|
||||||
@@ -399,26 +398,30 @@ gripper.misumi_rz:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -501,82 +504,71 @@ gripper.mock:
|
|||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
push_to:
|
push_to:
|
||||||
feedback:
|
feedback:
|
||||||
effort: effort
|
effort: torque
|
||||||
position: position
|
position: position
|
||||||
reached_goal: reached_goal
|
|
||||||
stalled: stalled
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command.max_effort: torque
|
||||||
position: position
|
command.position: position
|
||||||
torque: torque
|
|
||||||
velocity: velocity
|
|
||||||
goal_default:
|
goal_default:
|
||||||
command:
|
command:
|
||||||
max_effort: 0.0
|
max_effort: 0.0
|
||||||
position: 0.0
|
position: 0.0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
effort: effort
|
effort: torque
|
||||||
position: position
|
position: position
|
||||||
reached_goal: reached_goal
|
|
||||||
stalled: stalled
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
effort:
|
effort:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
position:
|
position:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
reached_goal:
|
reached_goal:
|
||||||
type: boolean
|
type: boolean
|
||||||
stalled:
|
stalled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- effort
|
||||||
|
- stalled
|
||||||
|
- reached_goal
|
||||||
title: GripperCommand_Feedback
|
title: GripperCommand_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
max_effort:
|
max_effort:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
position:
|
position:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- position
|
- position
|
||||||
- max_effort
|
- max_effort
|
||||||
title: command
|
title: command
|
||||||
type: object
|
type: object
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: GripperCommand_Goal
|
title: GripperCommand_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
effort:
|
effort:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
position:
|
position:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
reached_goal:
|
reached_goal:
|
||||||
type: boolean
|
type: boolean
|
||||||
stalled:
|
stalled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- effort
|
||||||
|
- stalled
|
||||||
|
- reached_goal
|
||||||
title: GripperCommand_Result
|
title: GripperCommand_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -612,8 +604,8 @@ gripper.mock:
|
|||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- position
|
- position
|
||||||
- status
|
|
||||||
- torque
|
|
||||||
- velocity
|
- velocity
|
||||||
|
- torque
|
||||||
|
- status
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -24,27 +24,6 @@ linear_motion.grbl:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-list:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: list的参数schema
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: list参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-set_position:
|
auto-set_position:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -114,39 +93,44 @@ linear_motion.grbl:
|
|||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
move_through_points:
|
move_through_points:
|
||||||
feedback:
|
feedback:
|
||||||
current_pose: current_pose
|
current_pose.pose.position: position
|
||||||
distance_remaining: distance_remaining
|
estimated_time_remaining.sec: time_remaining
|
||||||
estimated_time_remaining: estimated_time_remaining
|
navigation_time.sec: time_spent
|
||||||
navigation_time: navigation_time
|
number_of_poses_remaining: pose_number_remaining
|
||||||
number_of_poses_remaining: number_of_poses_remaining
|
|
||||||
number_of_recoveries: number_of_recoveries
|
|
||||||
goal:
|
goal:
|
||||||
behavior_tree: behavior_tree
|
poses[].pose.position: positions[]
|
||||||
poses: poses
|
|
||||||
positions: positions
|
|
||||||
goal_default:
|
goal_default:
|
||||||
behavior_tree: ''
|
behavior_tree: ''
|
||||||
poses: []
|
poses:
|
||||||
|
- header:
|
||||||
|
frame_id: ''
|
||||||
|
stamp:
|
||||||
|
nanosec: 0
|
||||||
|
sec: 0
|
||||||
|
pose:
|
||||||
|
orientation:
|
||||||
|
w: 1.0
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
position:
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
result: result
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
current_pose:
|
current_pose:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
header:
|
header:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
frame_id:
|
frame_id:
|
||||||
type: string
|
type: string
|
||||||
stamp:
|
stamp:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
nanosec:
|
nanosec:
|
||||||
maximum: 4294967295
|
maximum: 4294967295
|
||||||
@@ -167,26 +151,16 @@ linear_motion.grbl:
|
|||||||
title: header
|
title: header
|
||||||
type: object
|
type: object
|
||||||
pose:
|
pose:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
orientation:
|
orientation:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
w:
|
w:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
x:
|
x:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
y:
|
y:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
z:
|
z:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- x
|
- x
|
||||||
@@ -196,19 +170,12 @@ linear_motion.grbl:
|
|||||||
title: orientation
|
title: orientation
|
||||||
type: object
|
type: object
|
||||||
position:
|
position:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
x:
|
x:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
y:
|
y:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
z:
|
z:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- x
|
- x
|
||||||
@@ -227,11 +194,8 @@ linear_motion.grbl:
|
|||||||
title: current_pose
|
title: current_pose
|
||||||
type: object
|
type: object
|
||||||
distance_remaining:
|
distance_remaining:
|
||||||
maximum: 3.4028235e+38
|
|
||||||
minimum: -3.4028235e+38
|
|
||||||
type: number
|
type: number
|
||||||
estimated_time_remaining:
|
estimated_time_remaining:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
nanosec:
|
nanosec:
|
||||||
maximum: 4294967295
|
maximum: 4294967295
|
||||||
@@ -247,7 +211,6 @@ linear_motion.grbl:
|
|||||||
title: estimated_time_remaining
|
title: estimated_time_remaining
|
||||||
type: object
|
type: object
|
||||||
navigation_time:
|
navigation_time:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
nanosec:
|
nanosec:
|
||||||
maximum: 4294967295
|
maximum: 4294967295
|
||||||
@@ -270,10 +233,16 @@ linear_motion.grbl:
|
|||||||
maximum: 32767
|
maximum: 32767
|
||||||
minimum: -32768
|
minimum: -32768
|
||||||
type: integer
|
type: integer
|
||||||
|
required:
|
||||||
|
- current_pose
|
||||||
|
- navigation_time
|
||||||
|
- estimated_time_remaining
|
||||||
|
- number_of_recoveries
|
||||||
|
- distance_remaining
|
||||||
|
- number_of_poses_remaining
|
||||||
title: NavigateThroughPoses_Feedback
|
title: NavigateThroughPoses_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
behavior_tree:
|
behavior_tree:
|
||||||
type: string
|
type: string
|
||||||
@@ -287,8 +256,12 @@ linear_motion.grbl:
|
|||||||
stamp:
|
stamp:
|
||||||
properties:
|
properties:
|
||||||
nanosec:
|
nanosec:
|
||||||
|
maximum: 4294967295
|
||||||
|
minimum: 0
|
||||||
type: integer
|
type: integer
|
||||||
sec:
|
sec:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
- sec
|
- sec
|
||||||
@@ -341,17 +314,23 @@ linear_motion.grbl:
|
|||||||
required:
|
required:
|
||||||
- header
|
- header
|
||||||
- pose
|
- pose
|
||||||
|
title: poses
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
required:
|
||||||
|
- poses
|
||||||
|
- behavior_tree
|
||||||
title: NavigateThroughPoses_Goal
|
title: NavigateThroughPoses_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
result:
|
result:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: result
|
title: result
|
||||||
type: object
|
type: object
|
||||||
|
required:
|
||||||
|
- result
|
||||||
title: NavigateThroughPoses_Result
|
title: NavigateThroughPoses_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -361,15 +340,9 @@ linear_motion.grbl:
|
|||||||
type: NavigateThroughPoses
|
type: NavigateThroughPoses
|
||||||
set_spindle_speed:
|
set_spindle_speed:
|
||||||
feedback:
|
feedback:
|
||||||
error: error
|
position: spindle_speed
|
||||||
header: header
|
|
||||||
position: position
|
|
||||||
velocity: velocity
|
|
||||||
goal:
|
goal:
|
||||||
max_velocity: max_velocity
|
position: spindle_speed
|
||||||
min_duration: min_duration
|
|
||||||
position: position
|
|
||||||
spindle_speed: spindle_speed
|
|
||||||
goal_default:
|
goal_default:
|
||||||
max_velocity: 0.0
|
max_velocity: 0.0
|
||||||
min_duration:
|
min_duration:
|
||||||
@@ -377,25 +350,19 @@ linear_motion.grbl:
|
|||||||
sec: 0
|
sec: 0
|
||||||
position: 0.0
|
position: 0.0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
error:
|
error:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
header:
|
header:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
frame_id:
|
frame_id:
|
||||||
type: string
|
type: string
|
||||||
stamp:
|
stamp:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
nanosec:
|
nanosec:
|
||||||
maximum: 4294967295
|
maximum: 4294967295
|
||||||
@@ -416,24 +383,21 @@ linear_motion.grbl:
|
|||||||
title: header
|
title: header
|
||||||
type: object
|
type: object
|
||||||
position:
|
position:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
velocity:
|
velocity:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
|
required:
|
||||||
|
- header
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
- error
|
||||||
title: SingleJointPosition_Feedback
|
title: SingleJointPosition_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
max_velocity:
|
max_velocity:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
min_duration:
|
min_duration:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
nanosec:
|
nanosec:
|
||||||
maximum: 4294967295
|
maximum: 4294967295
|
||||||
@@ -449,13 +413,16 @@ linear_motion.grbl:
|
|||||||
title: min_duration
|
title: min_duration
|
||||||
type: object
|
type: object
|
||||||
position:
|
position:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- min_duration
|
||||||
|
- max_velocity
|
||||||
title: SingleJointPosition_Goal
|
title: SingleJointPosition_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: SingleJointPosition_Result
|
title: SingleJointPosition_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -465,7 +432,7 @@ linear_motion.grbl:
|
|||||||
type: SingleJointPosition
|
type: SingleJointPosition
|
||||||
module: unilabos.devices.cnc.grbl_sync:GrblCNC
|
module: unilabos.devices.cnc.grbl_sync:GrblCNC
|
||||||
status_types:
|
status_types:
|
||||||
position: Point3D
|
position: unilabos.messages:Point3D
|
||||||
spindle_speed: float
|
spindle_speed: float
|
||||||
status: str
|
status: str
|
||||||
type: python
|
type: python
|
||||||
@@ -504,9 +471,9 @@ linear_motion.grbl:
|
|||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- status
|
||||||
- position
|
- position
|
||||||
- spindle_speed
|
- spindle_speed
|
||||||
- status
|
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
linear_motion.toyo_xyz.sim:
|
linear_motion.toyo_xyz.sim:
|
||||||
@@ -633,6 +600,31 @@ linear_motion.toyo_xyz.sim:
|
|||||||
title: moveit_task参数
|
title: moveit_task参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: post_init的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-resource_manager:
|
auto-resource_manager:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -684,41 +676,41 @@ linear_motion.toyo_xyz.sim:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
pick_and_place:
|
pick_and_place:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -727,41 +719,41 @@ linear_motion.toyo_xyz.sim:
|
|||||||
type: object
|
type: object
|
||||||
type: SendCmd
|
type: SendCmd
|
||||||
set_position:
|
set_position:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -770,41 +762,41 @@ linear_motion.toyo_xyz.sim:
|
|||||||
type: object
|
type: object
|
||||||
type: SendCmd
|
type: SendCmd
|
||||||
set_status:
|
set_status:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -947,26 +939,30 @@ motor.iCL42:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -1004,8 +1000,8 @@ motor.iCL42:
|
|||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- is_executing_run
|
|
||||||
- motor_position
|
- motor_position
|
||||||
|
- is_executing_run
|
||||||
- success
|
- success
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -14,24 +14,19 @@ solid_dispenser.laiyu:
|
|||||||
powder_tube_number: 0
|
powder_tube_number: 0
|
||||||
target_tube_position: ''
|
target_tube_position: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
actual_mass_mg: actual_mass_mg
|
actual_mass_mg: actual_mass_mg
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: SolidDispenseAddPowderTube_Feedback
|
title: SolidDispenseAddPowderTube_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
compound_mass:
|
compound_mass:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
powder_tube_number:
|
powder_tube_number:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
@@ -39,19 +34,24 @@ solid_dispenser.laiyu:
|
|||||||
type: integer
|
type: integer
|
||||||
target_tube_position:
|
target_tube_position:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- powder_tube_number
|
||||||
|
- target_tube_position
|
||||||
|
- compound_mass
|
||||||
title: SolidDispenseAddPowderTube_Goal
|
title: SolidDispenseAddPowderTube_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
actual_mass_mg:
|
actual_mass_mg:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- actual_mass_mg
|
||||||
|
- success
|
||||||
title: SolidDispenseAddPowderTube_Result
|
title: SolidDispenseAddPowderTube_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -74,12 +74,11 @@ solid_dispenser.laiyu:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
type: object
|
type: string
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: calculate_crc参数
|
title: calculate_crc参数
|
||||||
@@ -100,12 +99,11 @@ solid_dispenser.laiyu:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: object
|
type: string
|
||||||
required:
|
required:
|
||||||
- command
|
- command
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: send_command参数
|
title: send_command参数
|
||||||
@@ -114,37 +112,36 @@ solid_dispenser.laiyu:
|
|||||||
discharge:
|
discharge:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
float_in: float_in
|
float_input: float_input
|
||||||
goal_default:
|
goal_default:
|
||||||
float_in: 0.0
|
float_in: 0.0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: FloatSingleInput_Feedback
|
title: FloatSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
float_in:
|
float_in:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
|
required:
|
||||||
|
- float_in
|
||||||
title: FloatSingleInput_Goal
|
title: FloatSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: FloatSingleInput_Result
|
title: FloatSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -159,31 +156,32 @@ solid_dispenser.laiyu:
|
|||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -202,41 +200,38 @@ solid_dispenser.laiyu:
|
|||||||
y: 0.0
|
y: 0.0
|
||||||
z: 0.0
|
z: 0.0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: Point3DSeparateInput_Feedback
|
title: Point3DSeparateInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
x:
|
x:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
y:
|
y:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
z:
|
z:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
title: Point3DSeparateInput_Goal
|
title: Point3DSeparateInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: Point3DSeparateInput_Result
|
title: Point3DSeparateInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -251,33 +246,34 @@ solid_dispenser.laiyu:
|
|||||||
goal_default:
|
goal_default:
|
||||||
int_input: 0
|
int_input: 0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: IntSingleInput_Feedback
|
title: IntSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
int_input:
|
int_input:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
|
required:
|
||||||
|
- int_input
|
||||||
title: IntSingleInput_Goal
|
title: IntSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: IntSingleInput_Result
|
title: IntSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -292,33 +288,34 @@ solid_dispenser.laiyu:
|
|||||||
goal_default:
|
goal_default:
|
||||||
int_input: 0
|
int_input: 0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: IntSingleInput_Feedback
|
title: IntSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
int_input:
|
int_input:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
|
required:
|
||||||
|
- int_input
|
||||||
title: IntSingleInput_Goal
|
title: IntSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: IntSingleInput_Result
|
title: IntSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -331,25 +328,26 @@ solid_dispenser.laiyu:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ chiller:
|
|||||||
- register_address
|
- register_address
|
||||||
- value
|
- value
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: build_modbus_frame参数
|
title: build_modbus_frame参数
|
||||||
@@ -64,8 +63,7 @@ chiller:
|
|||||||
required:
|
required:
|
||||||
- temperature
|
- temperature
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: integer
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: convert_temperature_to_modbus_value参数
|
title: convert_temperature_to_modbus_value参数
|
||||||
@@ -86,12 +84,11 @@ chiller:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
type: object
|
type: string
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: modbus_crc参数
|
title: modbus_crc参数
|
||||||
@@ -119,41 +116,42 @@ chiller:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
set_temperature:
|
set_temperature:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -268,15 +266,9 @@ heaterstirrer.dalong:
|
|||||||
feedback:
|
feedback:
|
||||||
status: status
|
status: status
|
||||||
goal:
|
goal:
|
||||||
pressure: pressure
|
|
||||||
purpose: purpose
|
purpose: purpose
|
||||||
reflux_solvent: reflux_solvent
|
|
||||||
stir: stir
|
|
||||||
stir_speed: stir_speed
|
|
||||||
temp: temp
|
temp: temp
|
||||||
temp_spec: temp_spec
|
|
||||||
time: time
|
time: time
|
||||||
time_spec: time_spec
|
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
goal_default:
|
goal_default:
|
||||||
pressure: ''
|
pressure: ''
|
||||||
@@ -309,23 +301,20 @@ heaterstirrer.dalong:
|
|||||||
sample_id: ''
|
sample_id: ''
|
||||||
type: ''
|
type: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
message: message
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: HeatChill_Feedback
|
title: HeatChill_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
pressure:
|
pressure:
|
||||||
type: string
|
type: string
|
||||||
@@ -336,12 +325,8 @@ heaterstirrer.dalong:
|
|||||||
stir:
|
stir:
|
||||||
type: boolean
|
type: boolean
|
||||||
stir_speed:
|
stir_speed:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
temp:
|
temp:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
temp_spec:
|
temp_spec:
|
||||||
type: string
|
type: string
|
||||||
@@ -350,7 +335,6 @@ heaterstirrer.dalong:
|
|||||||
time_spec:
|
time_spec:
|
||||||
type: string
|
type: string
|
||||||
vessel:
|
vessel:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
@@ -369,26 +353,16 @@ heaterstirrer.dalong:
|
|||||||
parent:
|
parent:
|
||||||
type: string
|
type: string
|
||||||
pose:
|
pose:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
orientation:
|
orientation:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
w:
|
w:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
x:
|
x:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
y:
|
y:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
z:
|
z:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- x
|
- x
|
||||||
@@ -398,19 +372,12 @@ heaterstirrer.dalong:
|
|||||||
title: orientation
|
title: orientation
|
||||||
type: object
|
type: object
|
||||||
position:
|
position:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
x:
|
x:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
y:
|
y:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
z:
|
z:
|
||||||
maximum: 1.7976931348623157e+308
|
|
||||||
minimum: -1.7976931348623157e+308
|
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- x
|
- x
|
||||||
@@ -440,10 +407,20 @@ heaterstirrer.dalong:
|
|||||||
- data
|
- data
|
||||||
title: vessel
|
title: vessel
|
||||||
type: object
|
type: object
|
||||||
|
required:
|
||||||
|
- vessel
|
||||||
|
- temp
|
||||||
|
- time
|
||||||
|
- temp_spec
|
||||||
|
- time_spec
|
||||||
|
- pressure
|
||||||
|
- reflux_solvent
|
||||||
|
- stir
|
||||||
|
- stir_speed
|
||||||
|
- purpose
|
||||||
title: HeatChill_Goal
|
title: HeatChill_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
@@ -451,6 +428,10 @@ heaterstirrer.dalong:
|
|||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
- message
|
||||||
|
- return_info
|
||||||
title: HeatChill_Result
|
title: HeatChill_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -459,42 +440,42 @@ heaterstirrer.dalong:
|
|||||||
type: object
|
type: object
|
||||||
type: HeatChill
|
type: HeatChill
|
||||||
set_temp_target:
|
set_temp_target:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: temp
|
||||||
temp: temp
|
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -503,42 +484,42 @@ heaterstirrer.dalong:
|
|||||||
type: object
|
type: object
|
||||||
type: SendCmd
|
type: SendCmd
|
||||||
set_temp_warning:
|
set_temp_warning:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: temp
|
||||||
temp: temp
|
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -588,8 +569,8 @@ heaterstirrer.dalong:
|
|||||||
- status
|
- status
|
||||||
- stir_speed
|
- stir_speed
|
||||||
- temp
|
- temp
|
||||||
- temp_target
|
|
||||||
- temp_warning
|
- temp_warning
|
||||||
|
- temp_target
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
tempsensor:
|
tempsensor:
|
||||||
@@ -710,41 +691,42 @@ tempsensor:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
set_warning:
|
set_warning:
|
||||||
feedback:
|
feedback: {}
|
||||||
status: status
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
goal_default:
|
goal_default:
|
||||||
command: ''
|
command: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result:
|
result:
|
||||||
return_info: return_info
|
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
title: SendCmd_Feedback
|
title: SendCmd_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
title: SendCmd_Goal
|
title: SendCmd_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SendCmd_Result
|
title: SendCmd_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,31 @@ xrd_d7mate:
|
|||||||
title: connect参数
|
title: connect参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-start_from_string:
|
auto-start_from_string:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -60,14 +85,11 @@ xrd_d7mate:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
params:
|
params:
|
||||||
anyOf:
|
type: string
|
||||||
- type: string
|
|
||||||
- type: object
|
|
||||||
required:
|
required:
|
||||||
- params
|
- params
|
||||||
type: object
|
type: object
|
||||||
result:
|
result: {}
|
||||||
type: object
|
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: start_from_string参数
|
title: start_from_string参数
|
||||||
@@ -83,18 +105,21 @@ xrd_d7mate:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -105,38 +130,38 @@ xrd_d7mate:
|
|||||||
get_sample_down:
|
get_sample_down:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
int_input: int_input
|
sample_station: 1
|
||||||
sample_station: sample_station
|
|
||||||
goal_default:
|
goal_default:
|
||||||
int_input: 0
|
int_input: 0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: IntSingleInput_Feedback
|
title: IntSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
int_input:
|
int_input:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
type: integer
|
type: integer
|
||||||
|
required:
|
||||||
|
- int_input
|
||||||
title: IntSingleInput_Goal
|
title: IntSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: IntSingleInput_Result
|
title: IntSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -154,18 +179,21 @@ xrd_d7mate:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -183,18 +211,21 @@ xrd_d7mate:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -207,25 +238,26 @@ xrd_d7mate:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -242,35 +274,42 @@ xrd_d7mate:
|
|||||||
sample_id: ''
|
sample_id: ''
|
||||||
start_theta: 10.0
|
start_theta: 10.0
|
||||||
goal_default:
|
goal_default:
|
||||||
end_theta: null
|
end_theta: 80.0
|
||||||
exp_time: null
|
exp_time: 0.5
|
||||||
increment: null
|
increment: 0.02
|
||||||
sample_id: null
|
sample_id: Sample001
|
||||||
start_theta: null
|
start_theta: 10.0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 送样完成后,发送样品信息和采集参数
|
description: 送样完成后,发送样品信息和采集参数
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
title: SampleReadyInput_Feedback
|
title: SampleReadyInput_Feedback
|
||||||
|
type: object
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
end_theta:
|
end_theta:
|
||||||
description: 结束角度(≥5.5°,且必须大于start_theta)
|
description: 结束角度(≥5.5°,且必须大于start_theta)
|
||||||
|
minimum: 5.5
|
||||||
type: number
|
type: number
|
||||||
exp_time:
|
exp_time:
|
||||||
description: 曝光时间(0.1-5.0秒)
|
description: 曝光时间(0.1-5.0秒)
|
||||||
|
maximum: 5.0
|
||||||
|
minimum: 0.1
|
||||||
type: number
|
type: number
|
||||||
increment:
|
increment:
|
||||||
description: 角度增量(≥0.005)
|
description: 角度增量(≥0.005)
|
||||||
|
minimum: 0.005
|
||||||
type: number
|
type: number
|
||||||
sample_id:
|
sample_id:
|
||||||
description: 样品标识符
|
description: 样品标识符
|
||||||
type: string
|
type: string
|
||||||
start_theta:
|
start_theta:
|
||||||
description: 起始角度(≥5°)
|
description: 起始角度(≥5°)
|
||||||
|
minimum: 5.0
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- sample_id
|
- sample_id
|
||||||
@@ -281,11 +320,19 @@ xrd_d7mate:
|
|||||||
title: SampleReadyInput_Goal
|
title: SampleReadyInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: SampleReadyInput_Result
|
title: SampleReadyInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: send_sample_ready参数
|
title: SampleReadyInput
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
set_power_off:
|
set_power_off:
|
||||||
@@ -293,25 +340,26 @@ xrd_d7mate:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -324,25 +372,26 @@ xrd_d7mate:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -356,16 +405,18 @@ xrd_d7mate:
|
|||||||
current: 30.0
|
current: 30.0
|
||||||
voltage: 40.0
|
voltage: 40.0
|
||||||
goal_default:
|
goal_default:
|
||||||
current: null
|
current: 30.0
|
||||||
voltage: null
|
voltage: 40.0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 设置高压电源电压和电流
|
description: 设置高压电源电压和电流
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
title: VoltageCurrentInput_Feedback
|
title: VoltageCurrentInput_Feedback
|
||||||
|
type: object
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
current:
|
current:
|
||||||
@@ -380,11 +431,19 @@ xrd_d7mate:
|
|||||||
title: VoltageCurrentInput_Goal
|
title: VoltageCurrentInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: VoltageCurrentInput_Result
|
title: VoltageCurrentInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: set_voltage_current参数
|
title: VoltageCurrentInput
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
start:
|
start:
|
||||||
@@ -394,12 +453,11 @@ xrd_d7mate:
|
|||||||
end_theta: 80.0
|
end_theta: 80.0
|
||||||
exp_time: 0.1
|
exp_time: 0.1
|
||||||
increment: 0.05
|
increment: 0.05
|
||||||
sample_id: ''
|
sample_id: 样品名称
|
||||||
start_theta: 10.0
|
start_theta: 10.0
|
||||||
string: ''
|
string: ''
|
||||||
wait_minutes: 3.0
|
wait_minutes: 3.0
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 启动自动模式→上样→等待→样品准备→监控→检测下样位→执行下样流程。
|
description: 启动自动模式→上样→等待→样品准备→监控→检测下样位→执行下样流程。
|
||||||
@@ -408,42 +466,54 @@ xrd_d7mate:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
end_theta:
|
end_theta:
|
||||||
default: 80.0
|
|
||||||
description: 结束角度(≥5.5°,且必须大于start_theta)
|
description: 结束角度(≥5.5°,且必须大于start_theta)
|
||||||
type: number
|
minimum: 5.5
|
||||||
|
type: string
|
||||||
exp_time:
|
exp_time:
|
||||||
default: 0.1
|
|
||||||
description: 曝光时间(0.1-5.0秒)
|
description: 曝光时间(0.1-5.0秒)
|
||||||
type: number
|
maximum: 5.0
|
||||||
|
minimum: 0.1
|
||||||
|
type: string
|
||||||
increment:
|
increment:
|
||||||
default: 0.05
|
|
||||||
description: 角度增量(≥0.005)
|
description: 角度增量(≥0.005)
|
||||||
type: number
|
minimum: 0.005
|
||||||
|
type: string
|
||||||
sample_id:
|
sample_id:
|
||||||
default: ''
|
|
||||||
description: 样品标识符
|
description: 样品标识符
|
||||||
type: string
|
type: string
|
||||||
start_theta:
|
start_theta:
|
||||||
default: 10.0
|
|
||||||
description: 起始角度(≥5°)
|
description: 起始角度(≥5°)
|
||||||
type: number
|
minimum: 5.0
|
||||||
|
type: string
|
||||||
string:
|
string:
|
||||||
default: ''
|
|
||||||
description: 字符串格式的参数输入,如果提供则优先解析使用
|
description: 字符串格式的参数输入,如果提供则优先解析使用
|
||||||
type: string
|
type: string
|
||||||
wait_minutes:
|
wait_minutes:
|
||||||
default: 3.0
|
|
||||||
description: 允许上样后等待分钟数
|
description: 允许上样后等待分钟数
|
||||||
|
minimum: 0.0
|
||||||
type: number
|
type: number
|
||||||
required: []
|
required:
|
||||||
|
- sample_id
|
||||||
|
- start_theta
|
||||||
|
- end_theta
|
||||||
|
- increment
|
||||||
|
- exp_time
|
||||||
title: StartWorkflow_Goal
|
title: StartWorkflow_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StartWorkflow_Result
|
title: StartWorkflow_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: start参数
|
title: StartWorkflow
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
start_auto_mode:
|
start_auto_mode:
|
||||||
@@ -451,15 +521,17 @@ xrd_d7mate:
|
|||||||
goal:
|
goal:
|
||||||
status: true
|
status: true
|
||||||
goal_default:
|
goal_default:
|
||||||
status: null
|
status: true
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 启动或停止自动模式
|
description: 启动或停止自动模式
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
title: BoolSingleInput_Feedback
|
title: BoolSingleInput_Feedback
|
||||||
|
type: object
|
||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
@@ -470,16 +542,25 @@ xrd_d7mate:
|
|||||||
title: BoolSingleInput_Goal
|
title: BoolSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: BoolSingleInput_Result
|
title: BoolSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: start_auto_mode参数
|
title: BoolSingleInput
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.xrd_d7mate.xrd_d7mate:XRDClient
|
module: unilabos.devices.xrd_d7mate.xrd_d7mate:XRDClient
|
||||||
status_types:
|
status_types:
|
||||||
current_acquire_data: dict
|
current_acquire_data: dict
|
||||||
|
sample_down: dict
|
||||||
sample_request: dict
|
sample_request: dict
|
||||||
sample_status: dict
|
sample_status: dict
|
||||||
type: python
|
type: python
|
||||||
@@ -505,13 +586,16 @@ xrd_d7mate:
|
|||||||
properties:
|
properties:
|
||||||
current_acquire_data:
|
current_acquire_data:
|
||||||
type: object
|
type: object
|
||||||
|
sample_down:
|
||||||
|
type: object
|
||||||
sample_request:
|
sample_request:
|
||||||
type: object
|
type: object
|
||||||
sample_status:
|
sample_status:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- current_acquire_data
|
|
||||||
- sample_request
|
- sample_request
|
||||||
|
- current_acquire_data
|
||||||
- sample_status
|
- sample_status
|
||||||
|
- sample_down
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -8,25 +8,26 @@ zhida_gcms:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -76,6 +77,31 @@ zhida_gcms:
|
|||||||
title: connect参数
|
title: connect参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
get_methods:
|
get_methods:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -86,18 +112,21 @@ zhida_gcms:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -115,18 +144,21 @@ zhida_gcms:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -144,18 +176,21 @@ zhida_gcms:
|
|||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -168,25 +203,26 @@ zhida_gcms:
|
|||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Feedback
|
title: EmptyIn_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: EmptyIn_Goal
|
title: EmptyIn_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
title: EmptyIn_Result
|
title: EmptyIn_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -198,35 +234,35 @@ zhida_gcms:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
string: string
|
string: string
|
||||||
text: text
|
|
||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -237,36 +273,36 @@ zhida_gcms:
|
|||||||
start_with_csv_file:
|
start_with_csv_file:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
csv_file_path: csv_file_path
|
|
||||||
string: string
|
string: string
|
||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
result: {}
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
feedback:
|
feedback:
|
||||||
additionalProperties: true
|
properties: {}
|
||||||
|
required: []
|
||||||
title: StrSingleInput_Feedback
|
title: StrSingleInput_Feedback
|
||||||
type: object
|
type: object
|
||||||
goal:
|
goal:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
string:
|
string:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
title: StrSingleInput_Goal
|
title: StrSingleInput_Goal
|
||||||
type: object
|
type: object
|
||||||
result:
|
result:
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
properties:
|
||||||
return_info:
|
return_info:
|
||||||
type: string
|
type: string
|
||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
title: StrSingleInput_Result
|
title: StrSingleInput_Result
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -307,8 +343,8 @@ zhida_gcms:
|
|||||||
version:
|
version:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- methods
|
|
||||||
- status
|
- status
|
||||||
|
- methods
|
||||||
- version
|
- version
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ YB_20ml_fenyeping:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_5ml_fenyeping:
|
YB_5ml_fenyeping:
|
||||||
category:
|
category:
|
||||||
@@ -21,6 +22,7 @@ YB_5ml_fenyeping:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_jia_yang_tou_da:
|
YB_jia_yang_tou_da:
|
||||||
category:
|
category:
|
||||||
@@ -33,6 +35,7 @@ YB_jia_yang_tou_da:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_pei_ye_da_Bottle:
|
YB_pei_ye_da_Bottle:
|
||||||
category:
|
category:
|
||||||
@@ -45,6 +48,7 @@ YB_pei_ye_da_Bottle:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_pei_ye_xiao_Bottle:
|
YB_pei_ye_xiao_Bottle:
|
||||||
category:
|
category:
|
||||||
@@ -57,6 +61,7 @@ YB_pei_ye_xiao_Bottle:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_qiang_tou:
|
YB_qiang_tou:
|
||||||
category:
|
category:
|
||||||
@@ -69,6 +74,7 @@ YB_qiang_tou:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_ye_Bottle:
|
YB_ye_Bottle:
|
||||||
category:
|
category:
|
||||||
@@ -82,4 +88,5 @@ YB_ye_Bottle:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ YB_100ml_yeti:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_20ml_fenyepingban:
|
YB_20ml_fenyepingban:
|
||||||
category:
|
category:
|
||||||
@@ -21,6 +22,7 @@ YB_20ml_fenyepingban:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_5ml_fenyepingban:
|
YB_5ml_fenyepingban:
|
||||||
category:
|
category:
|
||||||
@@ -33,6 +35,7 @@ YB_5ml_fenyepingban:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_6StockCarrier:
|
YB_6StockCarrier:
|
||||||
category:
|
category:
|
||||||
@@ -45,6 +48,7 @@ YB_6StockCarrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_6VialCarrier:
|
YB_6VialCarrier:
|
||||||
category:
|
category:
|
||||||
@@ -57,6 +61,7 @@ YB_6VialCarrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_gao_nian_ye_Bottle:
|
YB_gao_nian_ye_Bottle:
|
||||||
category:
|
category:
|
||||||
@@ -69,6 +74,7 @@ YB_gao_nian_ye_Bottle:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_gaonianye:
|
YB_gaonianye:
|
||||||
category:
|
category:
|
||||||
@@ -81,6 +87,7 @@ YB_gaonianye:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_jia_yang_tou_da_Carrier:
|
YB_jia_yang_tou_da_Carrier:
|
||||||
category:
|
category:
|
||||||
@@ -93,6 +100,7 @@ YB_jia_yang_tou_da_Carrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_peiyepingdaban:
|
YB_peiyepingdaban:
|
||||||
category:
|
category:
|
||||||
@@ -105,6 +113,7 @@ YB_peiyepingdaban:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_peiyepingxiaoban:
|
YB_peiyepingxiaoban:
|
||||||
category:
|
category:
|
||||||
@@ -117,6 +126,7 @@ YB_peiyepingxiaoban:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_qiang_tou_he:
|
YB_qiang_tou_he:
|
||||||
category:
|
category:
|
||||||
@@ -129,6 +139,7 @@ YB_qiang_tou_he:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_shi_pei_qi_kuai:
|
YB_shi_pei_qi_kuai:
|
||||||
category:
|
category:
|
||||||
@@ -141,6 +152,7 @@ YB_shi_pei_qi_kuai:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_ye:
|
YB_ye:
|
||||||
category:
|
category:
|
||||||
@@ -153,6 +165,7 @@ YB_ye:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_ye_100ml_Bottle:
|
YB_ye_100ml_Bottle:
|
||||||
category:
|
category:
|
||||||
@@ -165,4 +178,5 @@ YB_ye_100ml_Bottle:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ BIOYOND_PolymerStation_1BottleCarrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
BIOYOND_PolymerStation_1FlaskCarrier:
|
BIOYOND_PolymerStation_1FlaskCarrier:
|
||||||
category:
|
category:
|
||||||
@@ -19,6 +20,7 @@ BIOYOND_PolymerStation_1FlaskCarrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
BIOYOND_PolymerStation_6StockCarrier:
|
BIOYOND_PolymerStation_6StockCarrier:
|
||||||
category:
|
category:
|
||||||
@@ -30,6 +32,7 @@ BIOYOND_PolymerStation_6StockCarrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
BIOYOND_PolymerStation_8StockCarrier:
|
BIOYOND_PolymerStation_8StockCarrier:
|
||||||
category:
|
category:
|
||||||
@@ -41,4 +44,5 @@ BIOYOND_PolymerStation_8StockCarrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ BIOYOND_PolymerPreparationStation_Deck:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: 配液站.webp
|
icon: 配液站.webp
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
BIOYOND_PolymerReactionStation_Deck:
|
BIOYOND_PolymerReactionStation_Deck:
|
||||||
category:
|
category:
|
||||||
@@ -19,6 +20,7 @@ BIOYOND_PolymerReactionStation_Deck:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: 反应站.webp
|
icon: 反应站.webp
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
BIOYOND_YB_Deck:
|
BIOYOND_YB_Deck:
|
||||||
category:
|
category:
|
||||||
@@ -30,6 +32,7 @@ BIOYOND_YB_Deck:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: 配液站.webp
|
icon: 配液站.webp
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
CoincellDeck:
|
CoincellDeck:
|
||||||
category:
|
category:
|
||||||
@@ -41,4 +44,5 @@ CoincellDeck:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: koudian.webp
|
icon: koudian.webp
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -1,3 +1,24 @@
|
|||||||
|
disposal:
|
||||||
|
category:
|
||||||
|
- disposal
|
||||||
|
- waste
|
||||||
|
- resource_container
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.disposal:Disposal
|
||||||
|
type: unilabos
|
||||||
|
description: 废料处理位置,用于处理实验废料
|
||||||
|
handles:
|
||||||
|
- data_key: disposal_access
|
||||||
|
data_source: handle
|
||||||
|
data_type: fluid
|
||||||
|
handler_key: access
|
||||||
|
io_type: target
|
||||||
|
label: access
|
||||||
|
side: NORTH
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
hplc_plate:
|
hplc_plate:
|
||||||
category:
|
category:
|
||||||
- resource_container
|
- resource_container
|
||||||
@@ -19,6 +40,56 @@ hplc_plate:
|
|||||||
- 3.1416
|
- 3.1416
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/hplc_plate/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/hplc_plate/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
maintenance:
|
||||||
|
category:
|
||||||
|
- maintenance
|
||||||
|
- position
|
||||||
|
- resource_container
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.maintenance:Maintenance
|
||||||
|
type: unilabos
|
||||||
|
description: 维护位置,用于设备维护和校准
|
||||||
|
handles:
|
||||||
|
- data_key: maintenance_access
|
||||||
|
data_source: handle
|
||||||
|
data_type: mechanical
|
||||||
|
handler_key: access
|
||||||
|
io_type: target
|
||||||
|
label: access
|
||||||
|
side: NORTH
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
plate:
|
||||||
|
category:
|
||||||
|
- plate
|
||||||
|
- labware
|
||||||
|
- resource_container
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.plate:Plate
|
||||||
|
type: unilabos
|
||||||
|
description: 实验板,用于放置样品和试剂
|
||||||
|
handles:
|
||||||
|
- data_key: plate_access
|
||||||
|
data_source: handle
|
||||||
|
data_type: mechanical
|
||||||
|
handler_key: access
|
||||||
|
io_type: target
|
||||||
|
label: access
|
||||||
|
side: NORTH
|
||||||
|
- data_key: sample_wells
|
||||||
|
data_source: handle
|
||||||
|
data_type: fluid
|
||||||
|
handler_key: wells
|
||||||
|
io_type: target
|
||||||
|
label: wells
|
||||||
|
side: CENTER
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
plate_96:
|
plate_96:
|
||||||
category:
|
category:
|
||||||
@@ -41,6 +112,7 @@ plate_96:
|
|||||||
- 0
|
- 0
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
plate_96_high:
|
plate_96_high:
|
||||||
category:
|
category:
|
||||||
@@ -63,6 +135,35 @@ plate_96_high:
|
|||||||
- 1.5708
|
- 1.5708
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96_high/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96_high/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
tip_rack:
|
||||||
|
category:
|
||||||
|
- tip_rack
|
||||||
|
- labware
|
||||||
|
- resource_container
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.tip_rack:TipRack
|
||||||
|
type: unilabos
|
||||||
|
description: 枪头架资源,用于存放和管理移液器枪头
|
||||||
|
handles:
|
||||||
|
- data_key: tip_access
|
||||||
|
data_source: handle
|
||||||
|
data_type: mechanical
|
||||||
|
handler_key: access
|
||||||
|
io_type: target
|
||||||
|
label: access
|
||||||
|
side: NORTH
|
||||||
|
- data_key: tip_pickup
|
||||||
|
data_source: handle
|
||||||
|
data_type: mechanical
|
||||||
|
handler_key: pickup
|
||||||
|
io_type: target
|
||||||
|
label: pickup
|
||||||
|
side: SOUTH
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
tiprack_96_high:
|
tiprack_96_high:
|
||||||
category:
|
category:
|
||||||
@@ -94,6 +195,7 @@ tiprack_96_high:
|
|||||||
- 1.5708
|
- 1.5708
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_96_high/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_96_high/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
tiprack_box:
|
tiprack_box:
|
||||||
category:
|
category:
|
||||||
@@ -125,4 +227,5 @@ tiprack_box:
|
|||||||
- 0
|
- 0
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_box/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_box/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ bottle_container:
|
|||||||
- 0
|
- 0
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/bottle_container/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/bottle_container/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
tube_container:
|
tube_container:
|
||||||
category:
|
category:
|
||||||
@@ -61,4 +62,5 @@ tube_container:
|
|||||||
- 0
|
- 0
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tube_container/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tube_container/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ TransformXYZDeck:
|
|||||||
mesh: liquid_transform_xyz
|
mesh: liquid_transform_xyz
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/liquid_transform_xyz/macro_device.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/liquid_transform_xyz/macro_device.xacro
|
||||||
type: device
|
type: device
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ OTDeck:
|
|||||||
mesh: opentrons_liquid_handler
|
mesh: opentrons_liquid_handler
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/opentrons_liquid_handler/macro_device.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/opentrons_liquid_handler/macro_device.xacro
|
||||||
type: device
|
type: device
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
hplc_station:
|
hplc_station:
|
||||||
category:
|
category:
|
||||||
@@ -27,4 +28,5 @@ hplc_station:
|
|||||||
mesh: hplc_station
|
mesh: hplc_station
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/hplc_station/macro_device.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/hplc_station/macro_device.xacro
|
||||||
type: device
|
type: device
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
6884
unilabos/registry/resources/opentrons/lab.yaml
Normal file
6884
unilabos/registry/resources/opentrons/lab.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,4 +8,5 @@ Opentrons_96_adapter_Vb:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ appliedbiosystemsmicroamp_384_wellplate_40ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
biorad_384_wellplate_50ul:
|
biorad_384_wellplate_50ul:
|
||||||
category:
|
category:
|
||||||
@@ -19,6 +20,7 @@ biorad_384_wellplate_50ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
biorad_96_wellplate_200ul_pcr:
|
biorad_96_wellplate_200ul_pcr:
|
||||||
category:
|
category:
|
||||||
@@ -30,6 +32,7 @@ biorad_96_wellplate_200ul_pcr:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
corning_12_wellplate_6point9ml_flat:
|
corning_12_wellplate_6point9ml_flat:
|
||||||
category:
|
category:
|
||||||
@@ -41,6 +44,7 @@ corning_12_wellplate_6point9ml_flat:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
corning_24_wellplate_3point4ml_flat:
|
corning_24_wellplate_3point4ml_flat:
|
||||||
category:
|
category:
|
||||||
@@ -52,6 +56,7 @@ corning_24_wellplate_3point4ml_flat:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
corning_384_wellplate_112ul_flat:
|
corning_384_wellplate_112ul_flat:
|
||||||
category:
|
category:
|
||||||
@@ -63,6 +68,7 @@ corning_384_wellplate_112ul_flat:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
corning_48_wellplate_1point6ml_flat:
|
corning_48_wellplate_1point6ml_flat:
|
||||||
category:
|
category:
|
||||||
@@ -74,6 +80,7 @@ corning_48_wellplate_1point6ml_flat:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
corning_6_wellplate_16point8ml_flat:
|
corning_6_wellplate_16point8ml_flat:
|
||||||
category:
|
category:
|
||||||
@@ -85,6 +92,7 @@ corning_6_wellplate_16point8ml_flat:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
corning_96_wellplate_360ul_flat:
|
corning_96_wellplate_360ul_flat:
|
||||||
category:
|
category:
|
||||||
@@ -96,6 +104,7 @@ corning_96_wellplate_360ul_flat:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
nest_96_wellplate_100ul_pcr_full_skirt:
|
nest_96_wellplate_100ul_pcr_full_skirt:
|
||||||
category:
|
category:
|
||||||
@@ -127,6 +136,7 @@ nest_96_wellplate_100ul_pcr_full_skirt:
|
|||||||
- 1.5708
|
- 1.5708
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
nest_96_wellplate_200ul_flat:
|
nest_96_wellplate_200ul_flat:
|
||||||
category:
|
category:
|
||||||
@@ -138,6 +148,7 @@ nest_96_wellplate_200ul_flat:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
nest_96_wellplate_2ml_deep:
|
nest_96_wellplate_2ml_deep:
|
||||||
category:
|
category:
|
||||||
@@ -160,6 +171,7 @@ nest_96_wellplate_2ml_deep:
|
|||||||
- 1.5708
|
- 1.5708
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
thermoscientificnunc_96_wellplate_1300ul:
|
thermoscientificnunc_96_wellplate_1300ul:
|
||||||
category:
|
category:
|
||||||
@@ -171,6 +183,7 @@ thermoscientificnunc_96_wellplate_1300ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
thermoscientificnunc_96_wellplate_2000ul:
|
thermoscientificnunc_96_wellplate_2000ul:
|
||||||
category:
|
category:
|
||||||
@@ -182,6 +195,7 @@ thermoscientificnunc_96_wellplate_2000ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
usascientific_96_wellplate_2point4ml_deep:
|
usascientific_96_wellplate_2point4ml_deep:
|
||||||
category:
|
category:
|
||||||
@@ -193,4 +207,5 @@ usascientific_96_wellplate_2point4ml_deep:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ agilent_1_reservoir_290ml:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
axygen_1_reservoir_90ml:
|
axygen_1_reservoir_90ml:
|
||||||
category:
|
category:
|
||||||
@@ -19,6 +20,7 @@ axygen_1_reservoir_90ml:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
nest_12_reservoir_15ml:
|
nest_12_reservoir_15ml:
|
||||||
category:
|
category:
|
||||||
@@ -30,6 +32,7 @@ nest_12_reservoir_15ml:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
nest_1_reservoir_195ml:
|
nest_1_reservoir_195ml:
|
||||||
category:
|
category:
|
||||||
@@ -41,6 +44,7 @@ nest_1_reservoir_195ml:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
nest_1_reservoir_290ml:
|
nest_1_reservoir_290ml:
|
||||||
category:
|
category:
|
||||||
@@ -52,6 +56,7 @@ nest_1_reservoir_290ml:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
usascientific_12_reservoir_22ml:
|
usascientific_12_reservoir_22ml:
|
||||||
category:
|
category:
|
||||||
@@ -63,4 +68,5 @@ usascientific_12_reservoir_22ml:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ eppendorf_96_tiprack_1000ul_eptips:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
eppendorf_96_tiprack_10ul_eptips:
|
eppendorf_96_tiprack_10ul_eptips:
|
||||||
category:
|
category:
|
||||||
@@ -19,6 +20,7 @@ eppendorf_96_tiprack_10ul_eptips:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
geb_96_tiprack_1000ul:
|
geb_96_tiprack_1000ul:
|
||||||
category:
|
category:
|
||||||
@@ -30,6 +32,7 @@ geb_96_tiprack_1000ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
geb_96_tiprack_10ul:
|
geb_96_tiprack_10ul:
|
||||||
category:
|
category:
|
||||||
@@ -41,6 +44,7 @@ geb_96_tiprack_10ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_filtertiprack_1000ul:
|
opentrons_96_filtertiprack_1000ul:
|
||||||
category:
|
category:
|
||||||
@@ -71,6 +75,7 @@ opentrons_96_filtertiprack_1000ul:
|
|||||||
- 1.5708
|
- 1.5708
|
||||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
||||||
type: resource
|
type: resource
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_filtertiprack_10ul:
|
opentrons_96_filtertiprack_10ul:
|
||||||
category:
|
category:
|
||||||
@@ -82,6 +87,7 @@ opentrons_96_filtertiprack_10ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_filtertiprack_200ul:
|
opentrons_96_filtertiprack_200ul:
|
||||||
category:
|
category:
|
||||||
@@ -93,6 +99,7 @@ opentrons_96_filtertiprack_200ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_filtertiprack_20ul:
|
opentrons_96_filtertiprack_20ul:
|
||||||
category:
|
category:
|
||||||
@@ -104,6 +111,7 @@ opentrons_96_filtertiprack_20ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_tiprack_1000ul:
|
opentrons_96_tiprack_1000ul:
|
||||||
category:
|
category:
|
||||||
@@ -115,6 +123,7 @@ opentrons_96_tiprack_1000ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_tiprack_10ul:
|
opentrons_96_tiprack_10ul:
|
||||||
category:
|
category:
|
||||||
@@ -126,6 +135,7 @@ opentrons_96_tiprack_10ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_tiprack_20ul:
|
opentrons_96_tiprack_20ul:
|
||||||
category:
|
category:
|
||||||
@@ -137,6 +147,7 @@ opentrons_96_tiprack_20ul:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_tiprack_300ul:
|
opentrons_96_tiprack_300ul:
|
||||||
category:
|
category:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic:
|
opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic:
|
||||||
category:
|
category:
|
||||||
@@ -19,6 +20,7 @@ opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_10_tuberack_nest_4x50ml_6x15ml_conical:
|
opentrons_10_tuberack_nest_4x50ml_6x15ml_conical:
|
||||||
category:
|
category:
|
||||||
@@ -30,6 +32,7 @@ opentrons_10_tuberack_nest_4x50ml_6x15ml_conical:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_15_tuberack_falcon_15ml_conical:
|
opentrons_15_tuberack_falcon_15ml_conical:
|
||||||
category:
|
category:
|
||||||
@@ -41,6 +44,7 @@ opentrons_15_tuberack_falcon_15ml_conical:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_15_tuberack_nest_15ml_conical:
|
opentrons_15_tuberack_nest_15ml_conical:
|
||||||
category:
|
category:
|
||||||
@@ -52,6 +56,7 @@ opentrons_15_tuberack_nest_15ml_conical:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_aluminumblock_generic_2ml_screwcap:
|
opentrons_24_aluminumblock_generic_2ml_screwcap:
|
||||||
category:
|
category:
|
||||||
@@ -63,6 +68,7 @@ opentrons_24_aluminumblock_generic_2ml_screwcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_aluminumblock_nest_1point5ml_snapcap:
|
opentrons_24_aluminumblock_nest_1point5ml_snapcap:
|
||||||
category:
|
category:
|
||||||
@@ -74,6 +80,7 @@ opentrons_24_aluminumblock_nest_1point5ml_snapcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_eppendorf_1point5ml_safelock_snapcap:
|
opentrons_24_tuberack_eppendorf_1point5ml_safelock_snapcap:
|
||||||
category:
|
category:
|
||||||
@@ -85,6 +92,7 @@ opentrons_24_tuberack_eppendorf_1point5ml_safelock_snapcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap:
|
opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap:
|
||||||
category:
|
category:
|
||||||
@@ -96,6 +104,7 @@ opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic:
|
opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic:
|
||||||
category:
|
category:
|
||||||
@@ -107,6 +116,7 @@ opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_generic_0point75ml_snapcap_acrylic:
|
opentrons_24_tuberack_generic_0point75ml_snapcap_acrylic:
|
||||||
category:
|
category:
|
||||||
@@ -118,6 +128,7 @@ opentrons_24_tuberack_generic_0point75ml_snapcap_acrylic:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_generic_2ml_screwcap:
|
opentrons_24_tuberack_generic_2ml_screwcap:
|
||||||
category:
|
category:
|
||||||
@@ -129,6 +140,7 @@ opentrons_24_tuberack_generic_2ml_screwcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_nest_0point5ml_screwcap:
|
opentrons_24_tuberack_nest_0point5ml_screwcap:
|
||||||
category:
|
category:
|
||||||
@@ -140,6 +152,7 @@ opentrons_24_tuberack_nest_0point5ml_screwcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_nest_1point5ml_screwcap:
|
opentrons_24_tuberack_nest_1point5ml_screwcap:
|
||||||
category:
|
category:
|
||||||
@@ -151,6 +164,7 @@ opentrons_24_tuberack_nest_1point5ml_screwcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_nest_1point5ml_snapcap:
|
opentrons_24_tuberack_nest_1point5ml_snapcap:
|
||||||
category:
|
category:
|
||||||
@@ -162,6 +176,7 @@ opentrons_24_tuberack_nest_1point5ml_snapcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_nest_2ml_screwcap:
|
opentrons_24_tuberack_nest_2ml_screwcap:
|
||||||
category:
|
category:
|
||||||
@@ -173,6 +188,7 @@ opentrons_24_tuberack_nest_2ml_screwcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_24_tuberack_nest_2ml_snapcap:
|
opentrons_24_tuberack_nest_2ml_snapcap:
|
||||||
category:
|
category:
|
||||||
@@ -184,6 +200,7 @@ opentrons_24_tuberack_nest_2ml_snapcap:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_6_tuberack_falcon_50ml_conical:
|
opentrons_6_tuberack_falcon_50ml_conical:
|
||||||
category:
|
category:
|
||||||
@@ -195,6 +212,7 @@ opentrons_6_tuberack_falcon_50ml_conical:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_6_tuberack_nest_50ml_conical:
|
opentrons_6_tuberack_nest_50ml_conical:
|
||||||
category:
|
category:
|
||||||
@@ -206,6 +224,7 @@ opentrons_6_tuberack_nest_50ml_conical:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
opentrons_96_well_aluminum_block:
|
opentrons_96_well_aluminum_block:
|
||||||
category:
|
category:
|
||||||
@@ -217,4 +236,5 @@ opentrons_96_well_aluminum_block:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -29,4 +29,5 @@ container:
|
|||||||
side: WEST
|
side: WEST
|
||||||
icon: Flask.webp
|
icon: Flask.webp
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ POST_PROCESS_Raw_1BottleCarrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
POST_PROCESS_Reaction_1BottleCarrier:
|
POST_PROCESS_Reaction_1BottleCarrier:
|
||||||
category:
|
category:
|
||||||
@@ -19,4 +20,5 @@ POST_PROCESS_Reaction_1BottleCarrier:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ post_process_deck:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ PRCXI_30mm_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_Adapter:
|
PRCXI_Adapter:
|
||||||
category:
|
category:
|
||||||
@@ -21,6 +22,7 @@ PRCXI_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_Deep10_Adapter:
|
PRCXI_Deep10_Adapter:
|
||||||
category:
|
category:
|
||||||
@@ -33,6 +35,7 @@ PRCXI_Deep10_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_Deep300_Adapter:
|
PRCXI_Deep300_Adapter:
|
||||||
category:
|
category:
|
||||||
@@ -45,6 +48,7 @@ PRCXI_Deep300_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_PCR_Adapter:
|
PRCXI_PCR_Adapter:
|
||||||
category:
|
category:
|
||||||
@@ -57,6 +61,7 @@ PRCXI_PCR_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_Reservoir_Adapter:
|
PRCXI_Reservoir_Adapter:
|
||||||
category:
|
category:
|
||||||
@@ -69,6 +74,7 @@ PRCXI_Reservoir_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_Tip10_Adapter:
|
PRCXI_Tip10_Adapter:
|
||||||
category:
|
category:
|
||||||
@@ -81,6 +87,7 @@ PRCXI_Tip10_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_Tip1250_Adapter:
|
PRCXI_Tip1250_Adapter:
|
||||||
category:
|
category:
|
||||||
@@ -93,6 +100,7 @@ PRCXI_Tip1250_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_Tip300_Adapter:
|
PRCXI_Tip300_Adapter:
|
||||||
category:
|
category:
|
||||||
@@ -105,4 +113,5 @@ PRCXI_Tip300_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ PRCXI_48_DeepWell:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_96_DeepWell:
|
PRCXI_96_DeepWell:
|
||||||
category:
|
category:
|
||||||
@@ -21,6 +22,7 @@ PRCXI_96_DeepWell:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_AGenBio_4_troughplate:
|
PRCXI_AGenBio_4_troughplate:
|
||||||
category:
|
category:
|
||||||
@@ -33,6 +35,7 @@ PRCXI_AGenBio_4_troughplate:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_BioER_96_wellplate:
|
PRCXI_BioER_96_wellplate:
|
||||||
category:
|
category:
|
||||||
@@ -45,6 +48,7 @@ PRCXI_BioER_96_wellplate:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_BioRad_384_wellplate:
|
PRCXI_BioRad_384_wellplate:
|
||||||
category:
|
category:
|
||||||
@@ -57,6 +61,7 @@ PRCXI_BioRad_384_wellplate:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_CellTreat_96_wellplate:
|
PRCXI_CellTreat_96_wellplate:
|
||||||
category:
|
category:
|
||||||
@@ -69,6 +74,7 @@ PRCXI_CellTreat_96_wellplate:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_PCR_Plate_200uL_nonskirted:
|
PRCXI_PCR_Plate_200uL_nonskirted:
|
||||||
category:
|
category:
|
||||||
@@ -81,6 +87,7 @@ PRCXI_PCR_Plate_200uL_nonskirted:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_PCR_Plate_200uL_semiskirted:
|
PRCXI_PCR_Plate_200uL_semiskirted:
|
||||||
category:
|
category:
|
||||||
@@ -93,6 +100,7 @@ PRCXI_PCR_Plate_200uL_semiskirted:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_PCR_Plate_200uL_skirted:
|
PRCXI_PCR_Plate_200uL_skirted:
|
||||||
category:
|
category:
|
||||||
@@ -105,6 +113,7 @@ PRCXI_PCR_Plate_200uL_skirted:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_nest_12_troughplate:
|
PRCXI_nest_12_troughplate:
|
||||||
category:
|
category:
|
||||||
@@ -117,6 +126,7 @@ PRCXI_nest_12_troughplate:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_nest_1_troughplate:
|
PRCXI_nest_1_troughplate:
|
||||||
category:
|
category:
|
||||||
@@ -129,4 +139,5 @@ PRCXI_nest_1_troughplate:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ PRCXI_1000uL_Tips:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_10uL_Tips:
|
PRCXI_10uL_Tips:
|
||||||
category:
|
category:
|
||||||
@@ -21,6 +22,7 @@ PRCXI_10uL_Tips:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_10ul_eTips:
|
PRCXI_10ul_eTips:
|
||||||
category:
|
category:
|
||||||
@@ -33,6 +35,7 @@ PRCXI_10ul_eTips:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_1250uL_Tips:
|
PRCXI_1250uL_Tips:
|
||||||
category:
|
category:
|
||||||
@@ -45,6 +48,7 @@ PRCXI_1250uL_Tips:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_200uL_Tips:
|
PRCXI_200uL_Tips:
|
||||||
category:
|
category:
|
||||||
@@ -57,6 +61,7 @@ PRCXI_200uL_Tips:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
PRCXI_300ul_Tips:
|
PRCXI_300ul_Tips:
|
||||||
category:
|
category:
|
||||||
@@ -69,4 +74,5 @@ PRCXI_300ul_Tips:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ PRCXI_trash:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ PRCXI_EP_Adapter:
|
|||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -1,714 +0,0 @@
|
|||||||
"""
|
|
||||||
注册表工具函数
|
|
||||||
|
|
||||||
从 registry.py 中提取的纯工具函数,包括:
|
|
||||||
- docstring 解析
|
|
||||||
- 类型字符串 → JSON Schema 转换
|
|
||||||
- AST 类型节点解析
|
|
||||||
- TypedDict / Slot / Handle 等辅助检测
|
|
||||||
"""
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import typing
|
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
||||||
|
|
||||||
from msgcenterpy.instances.typed_dict_instance import TypedDictMessageInstance
|
|
||||||
|
|
||||||
from unilabos.utils.cls_creator import import_class
|
|
||||||
from unilabos.registry.decorators import Side, DataSource, normalize_enum_value
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 异常
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class ROSMsgNotFound(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Docstring 解析 (Google-style)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
_SECTION_RE = re.compile(r"^(\w[\w\s]*):\s*$")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_docstring(docstring: Optional[str]) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
解析 Google-style docstring,提取描述和参数说明。
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
{"description": "短描述", "params": {"param1": "参数1描述", ...}}
|
|
||||||
"""
|
|
||||||
result: Dict[str, Any] = {"description": "", "params": {}}
|
|
||||||
if not docstring:
|
|
||||||
return result
|
|
||||||
|
|
||||||
lines = docstring.strip().splitlines()
|
|
||||||
if not lines:
|
|
||||||
return result
|
|
||||||
|
|
||||||
result["description"] = lines[0].strip()
|
|
||||||
|
|
||||||
in_args = False
|
|
||||||
current_param: Optional[str] = None
|
|
||||||
current_desc_parts: list = []
|
|
||||||
|
|
||||||
for line in lines[1:]:
|
|
||||||
stripped = line.strip()
|
|
||||||
section_match = _SECTION_RE.match(stripped)
|
|
||||||
if section_match:
|
|
||||||
if current_param is not None:
|
|
||||||
result["params"][current_param] = "\n".join(current_desc_parts).strip()
|
|
||||||
current_param = None
|
|
||||||
current_desc_parts = []
|
|
||||||
section_name = section_match.group(1).lower()
|
|
||||||
in_args = section_name in ("args", "arguments", "parameters", "params")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_args:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if ":" in stripped and not stripped.startswith(" "):
|
|
||||||
if current_param is not None:
|
|
||||||
result["params"][current_param] = "\n".join(current_desc_parts).strip()
|
|
||||||
param_part, _, desc_part = stripped.partition(":")
|
|
||||||
param_name = param_part.strip().split("(")[0].strip()
|
|
||||||
current_param = param_name
|
|
||||||
current_desc_parts = [desc_part.strip()]
|
|
||||||
elif current_param is not None:
|
|
||||||
aline = line
|
|
||||||
if aline.startswith(" "):
|
|
||||||
aline = aline[4:]
|
|
||||||
elif aline.startswith("\t"):
|
|
||||||
aline = aline[1:]
|
|
||||||
current_desc_parts.append(aline.strip())
|
|
||||||
|
|
||||||
if current_param is not None:
|
|
||||||
result["params"][current_param] = "\n".join(current_desc_parts).strip()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 类型常量
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
SIMPLE_TYPE_MAP = {
|
|
||||||
"str": "string",
|
|
||||||
"string": "string",
|
|
||||||
"int": "integer",
|
|
||||||
"integer": "integer",
|
|
||||||
"float": "number",
|
|
||||||
"number": "number",
|
|
||||||
"bool": "boolean",
|
|
||||||
"boolean": "boolean",
|
|
||||||
"list": "array",
|
|
||||||
"array": "array",
|
|
||||||
"dict": "object",
|
|
||||||
"object": "object",
|
|
||||||
}
|
|
||||||
|
|
||||||
ARRAY_TYPES = {"list", "List", "tuple", "Tuple", "set", "Set", "Sequence", "Iterable"}
|
|
||||||
OBJECT_TYPES = {"dict", "Dict", "Mapping"}
|
|
||||||
WRAPPER_TYPES = {"Optional"}
|
|
||||||
SLOT_TYPES = {"ResourceSlot", "DeviceSlot"}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 简单类型映射
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def get_json_schema_type(type_str: str) -> str:
|
|
||||||
"""简单类型名 -> JSON Schema type"""
|
|
||||||
return SIMPLE_TYPE_MAP.get(type_str.lower(), "string")
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# AST 类型解析
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def parse_type_node(type_str: str):
|
|
||||||
"""将类型注解字符串解析为 AST 节点,失败返回 None。"""
|
|
||||||
import ast as _ast
|
|
||||||
|
|
||||||
try:
|
|
||||||
return _ast.parse(type_str.strip(), mode="eval").body
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _collect_bitor(node, out: list):
|
|
||||||
"""递归收集 X | Y | Z 的所有分支。"""
|
|
||||||
import ast as _ast
|
|
||||||
|
|
||||||
if isinstance(node, _ast.BinOp) and isinstance(node.op, _ast.BitOr):
|
|
||||||
_collect_bitor(node.left, out)
|
|
||||||
_collect_bitor(node.right, out)
|
|
||||||
else:
|
|
||||||
out.append(node)
|
|
||||||
|
|
||||||
|
|
||||||
def type_node_to_schema(
|
|
||||||
node,
|
|
||||||
import_map: Optional[Dict[str, str]] = None,
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""将 AST 类型注解节点递归转换为 JSON Schema dict。
|
|
||||||
|
|
||||||
当提供 import_map 时,对于未知类名会尝试通过 import_map 解析模块路径,
|
|
||||||
然后 import 真实类型对象来生成 schema (支持 TypedDict 等)。
|
|
||||||
|
|
||||||
映射规则:
|
|
||||||
- Optional[X] → X 的 schema (剥掉 Optional)
|
|
||||||
- Union[X, Y] → {"anyOf": [X_schema, Y_schema]}
|
|
||||||
- List[X] / Tuple[X] / Set[X] → {"type": "array", "items": X_schema}
|
|
||||||
- Dict[K, V] → {"type": "object", "additionalProperties": V_schema}
|
|
||||||
- Literal["a", "b"] → {"type": "string", "enum": ["a", "b"]}
|
|
||||||
- TypedDict (via import_map) → {"type": "object", "properties": {...}}
|
|
||||||
- 基本类型 str/int/... → {"type": "string"/"integer"/...}
|
|
||||||
"""
|
|
||||||
import ast as _ast
|
|
||||||
|
|
||||||
# --- Name 节点: str / int / dict / ResourceSlot / 自定义类 ---
|
|
||||||
if isinstance(node, _ast.Name):
|
|
||||||
name = node.id
|
|
||||||
if name in SLOT_TYPES:
|
|
||||||
return {"$slot": name}
|
|
||||||
json_type = SIMPLE_TYPE_MAP.get(name.lower())
|
|
||||||
if json_type:
|
|
||||||
return {"type": json_type}
|
|
||||||
# 尝试通过 import_map 解析并 import 真实类型
|
|
||||||
if import_map and name in import_map:
|
|
||||||
type_obj = resolve_type_object(import_map[name])
|
|
||||||
if type_obj is not None:
|
|
||||||
return type_to_schema(type_obj)
|
|
||||||
# 未知类名 → 无法转 schema 的自定义类型默认当 object
|
|
||||||
return {"type": "object"}
|
|
||||||
|
|
||||||
if isinstance(node, _ast.Constant):
|
|
||||||
if isinstance(node.value, str):
|
|
||||||
return {"type": SIMPLE_TYPE_MAP.get(node.value.lower(), "string")}
|
|
||||||
return {"type": "string"}
|
|
||||||
|
|
||||||
# --- Subscript 节点: List[X], Dict[K,V], Optional[X], Literal[...] 等 ---
|
|
||||||
if isinstance(node, _ast.Subscript):
|
|
||||||
base_name = node.value.id if isinstance(node.value, _ast.Name) else ""
|
|
||||||
|
|
||||||
# Optional[X] → 剥掉
|
|
||||||
if base_name in WRAPPER_TYPES:
|
|
||||||
return type_node_to_schema(node.slice, import_map)
|
|
||||||
|
|
||||||
# Union[X, None] → 剥掉 None; Union[X, Y] → anyOf
|
|
||||||
if base_name == "Union":
|
|
||||||
elts = node.slice.elts if isinstance(node.slice, _ast.Tuple) else [node.slice]
|
|
||||||
non_none = [
|
|
||||||
e
|
|
||||||
for e in elts
|
|
||||||
if not (isinstance(e, _ast.Constant) and e.value is None)
|
|
||||||
and not (isinstance(e, _ast.Name) and e.id == "None")
|
|
||||||
]
|
|
||||||
if len(non_none) == 1:
|
|
||||||
return type_node_to_schema(non_none[0], import_map)
|
|
||||||
if len(non_none) > 1:
|
|
||||||
return {"anyOf": [type_node_to_schema(e, import_map) for e in non_none]}
|
|
||||||
return {"type": "string"}
|
|
||||||
|
|
||||||
# Literal["a", "b", 1] → enum
|
|
||||||
if base_name == "Literal":
|
|
||||||
elts = node.slice.elts if isinstance(node.slice, _ast.Tuple) else [node.slice]
|
|
||||||
values = []
|
|
||||||
for e in elts:
|
|
||||||
if isinstance(e, _ast.Constant):
|
|
||||||
values.append(e.value)
|
|
||||||
elif isinstance(e, _ast.Name):
|
|
||||||
values.append(e.id)
|
|
||||||
if values:
|
|
||||||
return {"type": "string", "enum": values}
|
|
||||||
return {"type": "string"}
|
|
||||||
|
|
||||||
# List / Tuple / Set → array
|
|
||||||
if base_name in ARRAY_TYPES:
|
|
||||||
if isinstance(node.slice, _ast.Tuple) and node.slice.elts:
|
|
||||||
inner_node = node.slice.elts[0]
|
|
||||||
else:
|
|
||||||
inner_node = node.slice
|
|
||||||
return {"type": "array", "items": type_node_to_schema(inner_node, import_map)}
|
|
||||||
|
|
||||||
# Dict → object
|
|
||||||
if base_name in OBJECT_TYPES:
|
|
||||||
schema: Dict[str, Any] = {"type": "object"}
|
|
||||||
if isinstance(node.slice, _ast.Tuple) and len(node.slice.elts) >= 2:
|
|
||||||
val_node = node.slice.elts[1]
|
|
||||||
# Dict[str, Any] → 不加 additionalProperties (Any 等同于无约束)
|
|
||||||
is_any = (isinstance(val_node, _ast.Name) and val_node.id == "Any") or (
|
|
||||||
isinstance(val_node, _ast.Constant) and val_node.value is None
|
|
||||||
)
|
|
||||||
if not is_any:
|
|
||||||
val_schema = type_node_to_schema(val_node, import_map)
|
|
||||||
schema["additionalProperties"] = val_schema
|
|
||||||
return schema
|
|
||||||
|
|
||||||
# --- BinOp: X | Y (Python 3.10+) → 当 Union 处理 ---
|
|
||||||
if isinstance(node, _ast.BinOp) and isinstance(node.op, _ast.BitOr):
|
|
||||||
parts: list = []
|
|
||||||
_collect_bitor(node, parts)
|
|
||||||
non_none = [
|
|
||||||
p
|
|
||||||
for p in parts
|
|
||||||
if not (isinstance(p, _ast.Constant) and p.value is None)
|
|
||||||
and not (isinstance(p, _ast.Name) and p.id == "None")
|
|
||||||
]
|
|
||||||
if len(non_none) == 1:
|
|
||||||
return type_node_to_schema(non_none[0], import_map)
|
|
||||||
if len(non_none) > 1:
|
|
||||||
return {"anyOf": [type_node_to_schema(p, import_map) for p in non_none]}
|
|
||||||
return {"type": "string"}
|
|
||||||
|
|
||||||
return {"type": "string"}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 真实类型对象解析 (import-based)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_type_object(type_ref: str) -> Optional[Any]:
|
|
||||||
"""通过 'module.path:ClassName' 格式的引用 import 并返回真实类型对象。
|
|
||||||
|
|
||||||
对于 typing 内置名 (str, int, List 等) 直接返回 None (由 AST 路径处理)。
|
|
||||||
import 失败时静默返回 None。
|
|
||||||
"""
|
|
||||||
if ":" not in type_ref:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return import_class(type_ref)
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def is_typed_dict_class(obj: Any) -> bool:
|
|
||||||
"""检查对象是否是 TypedDict 类。"""
|
|
||||||
if obj is None:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
from typing_extensions import is_typeddict
|
|
||||||
|
|
||||||
return is_typeddict(obj)
|
|
||||||
except ImportError:
|
|
||||||
if isinstance(obj, type):
|
|
||||||
return hasattr(obj, "__required_keys__") and hasattr(obj, "__optional_keys__")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def type_to_schema(tp: Any) -> Dict[str, Any]:
|
|
||||||
"""将真实 typing 对象递归转换为 JSON Schema dict。
|
|
||||||
|
|
||||||
支持:
|
|
||||||
- 基本类型: str, int, float, bool → {"type": "string"/"integer"/...}
|
|
||||||
- typing 泛型: List[X], Dict[K,V], Optional[X], Union[X,Y], Literal[...]
|
|
||||||
- TypedDict → {"type": "object", "properties": {...}, "required": [...]}
|
|
||||||
- 自定义类 (ResourceSlot 等) → {"$slot": "..."} 或 {"type": "string"}
|
|
||||||
"""
|
|
||||||
origin = getattr(tp, "__origin__", None)
|
|
||||||
args = getattr(tp, "__args__", None)
|
|
||||||
|
|
||||||
# --- None / NoneType ---
|
|
||||||
if tp is type(None):
|
|
||||||
return {"type": "null"}
|
|
||||||
|
|
||||||
# --- 基本类型 ---
|
|
||||||
if tp is str:
|
|
||||||
return {"type": "string"}
|
|
||||||
if tp is int:
|
|
||||||
return {"type": "integer"}
|
|
||||||
if tp is float:
|
|
||||||
return {"type": "number"}
|
|
||||||
if tp is bool:
|
|
||||||
return {"type": "boolean"}
|
|
||||||
|
|
||||||
# --- TypedDict ---
|
|
||||||
if is_typed_dict_class(tp):
|
|
||||||
try:
|
|
||||||
return TypedDictMessageInstance.get_json_schema_from_typed_dict(tp)
|
|
||||||
except Exception:
|
|
||||||
return {"type": "object"}
|
|
||||||
|
|
||||||
# --- Literal ---
|
|
||||||
if origin is typing.Literal:
|
|
||||||
values = list(args) if args else []
|
|
||||||
return {"type": "string", "enum": values}
|
|
||||||
|
|
||||||
# --- Optional / Union ---
|
|
||||||
if origin is typing.Union:
|
|
||||||
non_none = [a for a in (args or ()) if a is not type(None)]
|
|
||||||
if len(non_none) == 1:
|
|
||||||
return type_to_schema(non_none[0])
|
|
||||||
if len(non_none) > 1:
|
|
||||||
return {"anyOf": [type_to_schema(a) for a in non_none]}
|
|
||||||
return {"type": "string"}
|
|
||||||
|
|
||||||
# --- List / Sequence / Set / Tuple / Iterable ---
|
|
||||||
if origin in (list, tuple, set, frozenset) or (
|
|
||||||
origin is not None
|
|
||||||
and getattr(origin, "__name__", "") in ("Sequence", "Iterable", "Iterator", "MutableSequence")
|
|
||||||
):
|
|
||||||
if args:
|
|
||||||
return {"type": "array", "items": type_to_schema(args[0])}
|
|
||||||
return {"type": "array"}
|
|
||||||
|
|
||||||
# --- Dict / Mapping ---
|
|
||||||
if origin in (dict,) or (origin is not None and getattr(origin, "__name__", "") in ("Mapping", "MutableMapping")):
|
|
||||||
schema: Dict[str, Any] = {"type": "object"}
|
|
||||||
if args and len(args) >= 2:
|
|
||||||
schema["additionalProperties"] = type_to_schema(args[1])
|
|
||||||
return schema
|
|
||||||
|
|
||||||
# --- Slot 类型 ---
|
|
||||||
if isinstance(tp, type):
|
|
||||||
name = tp.__name__
|
|
||||||
if name in SLOT_TYPES:
|
|
||||||
return {"$slot": name}
|
|
||||||
|
|
||||||
# --- 其他未知类型 fallback ---
|
|
||||||
if isinstance(tp, type):
|
|
||||||
return {"type": "object"}
|
|
||||||
return {"type": "string"}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Slot / Placeholder 检测
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def detect_slot_type(ptype) -> Tuple[Optional[str], bool]:
|
|
||||||
"""检测参数类型是否为 ResourceSlot / DeviceSlot。
|
|
||||||
|
|
||||||
兼容多种格式:
|
|
||||||
- runtime: "unilabos.registry.placeholder_type:ResourceSlot"
|
|
||||||
- runtime tuple: ("list", "unilabos.registry.placeholder_type:ResourceSlot")
|
|
||||||
- AST 裸名: "ResourceSlot", "List[ResourceSlot]", "Optional[ResourceSlot]"
|
|
||||||
|
|
||||||
Returns: (slot_name | None, is_list)
|
|
||||||
"""
|
|
||||||
ptype_str = str(ptype)
|
|
||||||
|
|
||||||
# 快速路径: 字符串里根本没有 Slot
|
|
||||||
if "ResourceSlot" not in ptype_str and "DeviceSlot" not in ptype_str:
|
|
||||||
return (None, False)
|
|
||||||
|
|
||||||
# runtime 格式: 完整模块路径
|
|
||||||
if isinstance(ptype, str):
|
|
||||||
if ptype.endswith(":ResourceSlot") or ptype == "ResourceSlot":
|
|
||||||
return ("ResourceSlot", False)
|
|
||||||
if ptype.endswith(":DeviceSlot") or ptype == "DeviceSlot":
|
|
||||||
return ("DeviceSlot", False)
|
|
||||||
# AST 复杂格式: List[ResourceSlot], Optional[ResourceSlot] 等
|
|
||||||
if "[" in ptype:
|
|
||||||
node = parse_type_node(ptype)
|
|
||||||
if node is not None:
|
|
||||||
schema = type_node_to_schema(node)
|
|
||||||
# 直接是 slot
|
|
||||||
if "$slot" in schema:
|
|
||||||
return (schema["$slot"], False)
|
|
||||||
# array 包裹 slot: {"type": "array", "items": {"$slot": "..."}}
|
|
||||||
items = schema.get("items", {})
|
|
||||||
if isinstance(items, dict) and "$slot" in items:
|
|
||||||
return (items["$slot"], True)
|
|
||||||
return (None, False)
|
|
||||||
|
|
||||||
# runtime tuple 格式
|
|
||||||
if isinstance(ptype, tuple) and len(ptype) == 2:
|
|
||||||
inner_str = str(ptype[1])
|
|
||||||
if "ResourceSlot" in inner_str:
|
|
||||||
return ("ResourceSlot", True)
|
|
||||||
if "DeviceSlot" in inner_str:
|
|
||||||
return ("DeviceSlot", True)
|
|
||||||
|
|
||||||
return (None, False)
|
|
||||||
|
|
||||||
|
|
||||||
def detect_placeholder_keys(params: list) -> Dict[str, str]:
|
|
||||||
"""Detect parameters that reference ResourceSlot or DeviceSlot."""
|
|
||||||
result: Dict[str, str] = {}
|
|
||||||
for p in params:
|
|
||||||
ptype = p.get("type", "")
|
|
||||||
if "ResourceSlot" in str(ptype):
|
|
||||||
result[p["name"]] = "unilabos_resources"
|
|
||||||
elif "DeviceSlot" in str(ptype):
|
|
||||||
result[p["name"]] = "unilabos_devices"
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Handle 规范化
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_ast_handles(handles_raw: Any) -> List[Dict[str, Any]]:
|
|
||||||
"""Convert AST-parsed handle structures to the standard registry format."""
|
|
||||||
if not handles_raw:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# handle_type → io_type 映射 (AST 内部类名 → YAML 标准字段值)
|
|
||||||
_HANDLE_TYPE_TO_IO_TYPE = {
|
|
||||||
"input": "target",
|
|
||||||
"output": "source",
|
|
||||||
"action_input": "action_target",
|
|
||||||
"action_output": "action_source",
|
|
||||||
}
|
|
||||||
|
|
||||||
result: List[Dict[str, Any]] = []
|
|
||||||
for h in handles_raw:
|
|
||||||
if isinstance(h, dict):
|
|
||||||
call = h.get("_call", "")
|
|
||||||
if "InputHandle" in call:
|
|
||||||
handle_type = "input"
|
|
||||||
elif "OutputHandle" in call:
|
|
||||||
handle_type = "output"
|
|
||||||
elif "ActionInputHandle" in call:
|
|
||||||
handle_type = "action_input"
|
|
||||||
elif "ActionOutputHandle" in call:
|
|
||||||
handle_type = "action_output"
|
|
||||||
else:
|
|
||||||
handle_type = h.get("handle_type", "unknown")
|
|
||||||
|
|
||||||
io_type = _HANDLE_TYPE_TO_IO_TYPE.get(handle_type, handle_type)
|
|
||||||
|
|
||||||
entry: Dict[str, Any] = {
|
|
||||||
"handler_key": h.get("key", ""),
|
|
||||||
"data_type": h.get("data_type", ""),
|
|
||||||
"io_type": io_type,
|
|
||||||
}
|
|
||||||
side = h.get("side")
|
|
||||||
if side:
|
|
||||||
entry["side"] = normalize_enum_value(side, Side) or side
|
|
||||||
label = h.get("label")
|
|
||||||
if label:
|
|
||||||
entry["label"] = label
|
|
||||||
data_key = h.get("data_key")
|
|
||||||
if data_key:
|
|
||||||
entry["data_key"] = data_key
|
|
||||||
data_source = h.get("data_source")
|
|
||||||
if data_source:
|
|
||||||
entry["data_source"] = normalize_enum_value(data_source, DataSource) or data_source
|
|
||||||
description = h.get("description")
|
|
||||||
if description:
|
|
||||||
entry["description"] = description
|
|
||||||
|
|
||||||
result.append(entry)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_ast_action_handles(handles_raw: Any) -> Dict[str, Any]:
|
|
||||||
"""Convert AST-parsed action handle list to {"input": [...], "output": [...]}.
|
|
||||||
|
|
||||||
Mirrors the runtime behavior of decorators._action_handles_to_dict:
|
|
||||||
- ActionInputHandle => grouped under "input"
|
|
||||||
- ActionOutputHandle => grouped under "output"
|
|
||||||
Field mapping: key -> handler_key (matches Pydantic serialization_alias).
|
|
||||||
"""
|
|
||||||
if not handles_raw or not isinstance(handles_raw, list):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
input_list: List[Dict[str, Any]] = []
|
|
||||||
output_list: List[Dict[str, Any]] = []
|
|
||||||
|
|
||||||
for h in handles_raw:
|
|
||||||
if not isinstance(h, dict):
|
|
||||||
continue
|
|
||||||
call = h.get("_call", "")
|
|
||||||
is_input = "ActionInputHandle" in call or "InputHandle" in call
|
|
||||||
is_output = "ActionOutputHandle" in call or "OutputHandle" in call
|
|
||||||
|
|
||||||
entry: Dict[str, Any] = {
|
|
||||||
"handler_key": h.get("key", ""),
|
|
||||||
"data_type": h.get("data_type", ""),
|
|
||||||
"label": h.get("label", ""),
|
|
||||||
}
|
|
||||||
_FIELD_ENUM_MAP = {"side": Side, "data_source": DataSource}
|
|
||||||
for opt_key in ("side", "data_key", "data_source", "description", "io_type"):
|
|
||||||
val = h.get(opt_key)
|
|
||||||
if val is not None:
|
|
||||||
if opt_key in _FIELD_ENUM_MAP:
|
|
||||||
val = normalize_enum_value(val, _FIELD_ENUM_MAP[opt_key]) or val
|
|
||||||
entry[opt_key] = val
|
|
||||||
|
|
||||||
# io_type: only add when explicitly set; do not default output to "sink" (YAML convention omits it)
|
|
||||||
if "io_type" not in entry and is_input:
|
|
||||||
entry["io_type"] = "source"
|
|
||||||
|
|
||||||
if is_input:
|
|
||||||
input_list.append(entry)
|
|
||||||
elif is_output:
|
|
||||||
output_list.append(entry)
|
|
||||||
|
|
||||||
result: Dict[str, Any] = {}
|
|
||||||
if input_list:
|
|
||||||
result["input"] = input_list
|
|
||||||
# Always include output (empty list when no outputs) to match YAML
|
|
||||||
result["output"] = output_list
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Schema 辅助
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_action_schema(
|
|
||||||
goal_schema: Dict[str, Any],
|
|
||||||
action_name: str,
|
|
||||||
description: str = "",
|
|
||||||
result_schema: Optional[Dict[str, Any]] = None,
|
|
||||||
feedback_schema: Optional[Dict[str, Any]] = None,
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
将 goal 参数 schema 包装为标准的 action schema 格式:
|
|
||||||
{ "properties": { "goal": ..., "feedback": ..., "result": ... }, ... }
|
|
||||||
"""
|
|
||||||
# 去掉 auto- 前缀用于 title/description,与 YAML 路径保持一致
|
|
||||||
display_name = action_name.removeprefix("auto-")
|
|
||||||
return {
|
|
||||||
"title": f"{display_name}参数",
|
|
||||||
"description": description or f"{display_name}的参数schema",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"goal": goal_schema,
|
|
||||||
"feedback": feedback_schema or {},
|
|
||||||
"result": result_schema or {},
|
|
||||||
},
|
|
||||||
"required": ["goal"],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def preserve_field_descriptions(new_schema: Dict[str, Any], prev_schema: Dict[str, Any]):
|
|
||||||
"""递归保留之前 schema 中各字段的 description / title。
|
|
||||||
|
|
||||||
覆盖顶层以及嵌套 properties(如 goal.properties.xxx.description)。
|
|
||||||
"""
|
|
||||||
if not prev_schema or not new_schema:
|
|
||||||
return
|
|
||||||
prev_props = prev_schema.get("properties", {})
|
|
||||||
new_props = new_schema.get("properties", {})
|
|
||||||
for field_name, prev_field in prev_props.items():
|
|
||||||
if field_name not in new_props:
|
|
||||||
continue
|
|
||||||
new_field = new_props[field_name]
|
|
||||||
if not isinstance(prev_field, dict) or not isinstance(new_field, dict):
|
|
||||||
continue
|
|
||||||
if "title" in prev_field:
|
|
||||||
new_field.setdefault("title", prev_field["title"])
|
|
||||||
if "description" in prev_field:
|
|
||||||
new_field.setdefault("description", prev_field["description"])
|
|
||||||
if "properties" in prev_field and "properties" in new_field:
|
|
||||||
preserve_field_descriptions(new_field, prev_field)
|
|
||||||
|
|
||||||
|
|
||||||
def strip_ros_descriptions(schema: Any):
|
|
||||||
"""递归清除 ROS schema 中自动生成的无意义 description(含 rosidl_parser 内存地址)。"""
|
|
||||||
if isinstance(schema, dict):
|
|
||||||
desc = schema.get("description", "")
|
|
||||||
if isinstance(desc, str) and "rosidl_parser" in desc:
|
|
||||||
del schema["description"]
|
|
||||||
for v in schema.values():
|
|
||||||
strip_ros_descriptions(v)
|
|
||||||
elif isinstance(schema, list):
|
|
||||||
for item in schema:
|
|
||||||
strip_ros_descriptions(item)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# 深度对比
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def _short(val, limit=120):
|
|
||||||
"""截断过长的值用于日志显示。"""
|
|
||||||
s = repr(val)
|
|
||||||
return s if len(s) <= limit else s[:limit] + "..."
|
|
||||||
|
|
||||||
|
|
||||||
def deep_diff(old, new, path="", max_depth=10) -> list:
|
|
||||||
"""递归对比两个对象,返回所有差异的描述列表。"""
|
|
||||||
diffs = []
|
|
||||||
if max_depth <= 0:
|
|
||||||
if old != new:
|
|
||||||
diffs.append(f"{path}: (达到最大深度) OLD≠NEW")
|
|
||||||
return diffs
|
|
||||||
|
|
||||||
if type(old) != type(new):
|
|
||||||
diffs.append(f"{path}: 类型不同 OLD={type(old).__name__}({_short(old)}) NEW={type(new).__name__}({_short(new)})")
|
|
||||||
return diffs
|
|
||||||
|
|
||||||
if isinstance(old, dict):
|
|
||||||
old_keys = set(old.keys())
|
|
||||||
new_keys = set(new.keys())
|
|
||||||
for k in sorted(new_keys - old_keys):
|
|
||||||
diffs.append(f"{path}.{k}: 新增字段 (AST有, YAML无) = {_short(new[k])}")
|
|
||||||
for k in sorted(old_keys - new_keys):
|
|
||||||
diffs.append(f"{path}.{k}: 缺失字段 (YAML有, AST无) = {_short(old[k])}")
|
|
||||||
for k in sorted(old_keys & new_keys):
|
|
||||||
diffs.extend(deep_diff(old[k], new[k], f"{path}.{k}", max_depth - 1))
|
|
||||||
elif isinstance(old, (list, tuple)):
|
|
||||||
if len(old) != len(new):
|
|
||||||
diffs.append(f"{path}: 列表长度不同 OLD={len(old)} NEW={len(new)}")
|
|
||||||
for i in range(min(len(old), len(new))):
|
|
||||||
diffs.extend(deep_diff(old[i], new[i], f"{path}[{i}]", max_depth - 1))
|
|
||||||
if len(new) > len(old):
|
|
||||||
for i in range(len(old), len(new)):
|
|
||||||
diffs.append(f"{path}[{i}]: 新增元素 = {_short(new[i])}")
|
|
||||||
elif len(old) > len(new):
|
|
||||||
for i in range(len(new), len(old)):
|
|
||||||
diffs.append(f"{path}[{i}]: 缺失元素 = {_short(old[i])}")
|
|
||||||
else:
|
|
||||||
if old != new:
|
|
||||||
diffs.append(f"{path}: OLD={_short(old)} NEW={_short(new)}")
|
|
||||||
return diffs
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# MRO 方法参数解析
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_method_params_via_import(module_str: str, method_name: str) -> Dict[str, str]:
|
|
||||||
"""当 AST 方法参数为空 (如 *args, **kwargs) 时, import class 并通过 MRO 获取真实方法参数.
|
|
||||||
|
|
||||||
返回 identity mapping {param_name: param_name}.
|
|
||||||
"""
|
|
||||||
if not module_str or ":" not in module_str:
|
|
||||||
return {}
|
|
||||||
try:
|
|
||||||
cls = import_class(module_str)
|
|
||||||
except Exception as e:
|
|
||||||
_logger.debug(f"[AST] resolve_method_params_via_import: import_class('{module_str}') failed: {e}")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
for base_cls in cls.__mro__:
|
|
||||||
if method_name not in base_cls.__dict__:
|
|
||||||
continue
|
|
||||||
method = base_cls.__dict__[method_name]
|
|
||||||
actual = getattr(method, "__wrapped__", method)
|
|
||||||
if isinstance(actual, (staticmethod, classmethod)):
|
|
||||||
actual = actual.__func__
|
|
||||||
if not callable(actual):
|
|
||||||
continue
|
|
||||||
sig = inspect.signature(actual, follow_wrapped=True)
|
|
||||||
params = [
|
|
||||||
p.name for p in sig.parameters.values()
|
|
||||||
if p.name not in ("self", "cls")
|
|
||||||
and p.kind not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
|
|
||||||
]
|
|
||||||
if params:
|
|
||||||
return {p: p for p in params}
|
|
||||||
except Exception as e:
|
|
||||||
_logger.debug(f"[AST] resolve_method_params_via_import: MRO walk for '{method_name}' failed: {e}")
|
|
||||||
return {}
|
|
||||||
@@ -12,11 +12,9 @@ class RegularContainer(Container):
|
|||||||
kwargs["size_y"] = 0
|
kwargs["size_y"] = 0
|
||||||
if "size_z" not in kwargs:
|
if "size_z" not in kwargs:
|
||||||
kwargs["size_z"] = 0
|
kwargs["size_z"] = 0
|
||||||
if "category" not in kwargs:
|
|
||||||
kwargs["category"] = "container"
|
|
||||||
|
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, category="container", **kwargs)
|
||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]):
|
def load_state(self, state: Dict[str, Any]):
|
||||||
super().load_state(state)
|
super().load_state(state)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ def canonicalize_nodes_data(
|
|||||||
if sample_id:
|
if sample_id:
|
||||||
logger.error(f"{node}的sample_id参数已弃用,sample_id: {sample_id}")
|
logger.error(f"{node}的sample_id参数已弃用,sample_id: {sample_id}")
|
||||||
for k in list(node.keys()):
|
for k in list(node.keys()):
|
||||||
if k not in ["id", "uuid", "name", "description", "schema", "model", "icon", "parent_uuid", "parent", "type", "class", "position", "config", "data", "children", "pose", "extra", "machine_name"]:
|
if k not in ["id", "uuid", "name", "description", "schema", "model", "icon", "parent_uuid", "parent", "type", "class", "position", "config", "data", "children", "pose", "extra"]:
|
||||||
v = node.pop(k)
|
v = node.pop(k)
|
||||||
node["config"][k] = v
|
node["config"][k] = v
|
||||||
if outer_host_node_id is not None:
|
if outer_host_node_id is not None:
|
||||||
@@ -288,15 +288,6 @@ def read_node_link_json(
|
|||||||
physical_setup_graph = nx.node_link_graph(graph_data, edges="links", multigraph=False)
|
physical_setup_graph = nx.node_link_graph(graph_data, edges="links", multigraph=False)
|
||||||
handle_communications(physical_setup_graph)
|
handle_communications(physical_setup_graph)
|
||||||
|
|
||||||
# Stamp machine_name on device trees only (resources are cloud-managed)
|
|
||||||
local_machine = BasicConfig.machine_name or "本地"
|
|
||||||
for tree in resource_tree_set.trees:
|
|
||||||
if tree.root_node.res_content.type != "device":
|
|
||||||
continue
|
|
||||||
for node in tree.get_all_nodes():
|
|
||||||
if not node.res_content.machine_name:
|
|
||||||
node.res_content.machine_name = local_machine
|
|
||||||
|
|
||||||
return physical_setup_graph, resource_tree_set, standardized_links
|
return physical_setup_graph, resource_tree_set, standardized_links
|
||||||
|
|
||||||
|
|
||||||
@@ -381,15 +372,6 @@ def read_graphml(graphml_file: str) -> tuple[nx.Graph, ResourceTreeSet, List[Dic
|
|||||||
physical_setup_graph = nx.node_link_graph(graph_data, link="links", multigraph=False)
|
physical_setup_graph = nx.node_link_graph(graph_data, link="links", multigraph=False)
|
||||||
handle_communications(physical_setup_graph)
|
handle_communications(physical_setup_graph)
|
||||||
|
|
||||||
# Stamp machine_name on device trees only (resources are cloud-managed)
|
|
||||||
local_machine = BasicConfig.machine_name or "本地"
|
|
||||||
for tree in resource_tree_set.trees:
|
|
||||||
if tree.root_node.res_content.type != "device":
|
|
||||||
continue
|
|
||||||
for node in tree.get_all_nodes():
|
|
||||||
if not node.res_content.machine_name:
|
|
||||||
node.res_content.machine_name = local_machine
|
|
||||||
|
|
||||||
return physical_setup_graph, resource_tree_set, standardized_links
|
return physical_setup_graph, resource_tree_set, standardized_links
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3681
unilabos/resources/lab_resources.py
Normal file
3681
unilabos/resources/lab_resources.py
Normal file
File diff suppressed because it is too large
Load Diff
1
unilabos/resources/opentrons_custom_labware_defs.json
Normal file
1
unilabos/resources/opentrons_custom_labware_defs.json
Normal file
File diff suppressed because one or more lines are too long
@@ -75,6 +75,14 @@ class ResourceDictPositionObject(BaseModel):
|
|||||||
z: float = Field(description="Z coordinate", default=0.0)
|
z: float = Field(description="Z coordinate", default=0.0)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceDictPoseExtraObjectType(BaseModel):
|
||||||
|
z_index: int
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceDictPoseExtraObject(BaseModel):
|
||||||
|
z_index: Optional[int] = Field(alias="zIndex", default=None)
|
||||||
|
|
||||||
|
|
||||||
class ResourceDictPositionType(TypedDict):
|
class ResourceDictPositionType(TypedDict):
|
||||||
size: ResourceDictPositionSizeType
|
size: ResourceDictPositionSizeType
|
||||||
scale: ResourceDictPositionScaleType
|
scale: ResourceDictPositionScaleType
|
||||||
@@ -101,7 +109,7 @@ class ResourceDictPosition(BaseModel):
|
|||||||
cross_section_type: Literal["rectangle", "circle", "rounded_rectangle"] = Field(
|
cross_section_type: Literal["rectangle", "circle", "rounded_rectangle"] = Field(
|
||||||
description="Cross section type", default="rectangle"
|
description="Cross section type", default="rectangle"
|
||||||
)
|
)
|
||||||
extra: Optional[Dict[str, Any]] = Field(description="Extra data", default=None)
|
extra: Optional[ResourceDictPoseExtraObject] = Field(description="Extra data", default=None)
|
||||||
|
|
||||||
|
|
||||||
class ResourceDictType(TypedDict):
|
class ResourceDictType(TypedDict):
|
||||||
@@ -120,7 +128,6 @@ class ResourceDictType(TypedDict):
|
|||||||
config: Dict[str, Any]
|
config: Dict[str, Any]
|
||||||
data: Dict[str, Any]
|
data: Dict[str, Any]
|
||||||
extra: Dict[str, Any]
|
extra: Dict[str, Any]
|
||||||
machine_name: str
|
|
||||||
|
|
||||||
|
|
||||||
# 统一的资源字典模型,parent 自动序列化为 parent_uuid,children 不序列化
|
# 统一的资源字典模型,parent 自动序列化为 parent_uuid,children 不序列化
|
||||||
@@ -142,7 +149,6 @@ class ResourceDict(BaseModel):
|
|||||||
config: Dict[str, Any] = Field(description="Resource configuration")
|
config: Dict[str, Any] = Field(description="Resource configuration")
|
||||||
data: Dict[str, Any] = Field(description="Resource data, eg: container liquid data")
|
data: Dict[str, Any] = Field(description="Resource data, eg: container liquid data")
|
||||||
extra: Dict[str, Any] = Field(description="Extra data, eg: slot index")
|
extra: Dict[str, Any] = Field(description="Extra data, eg: slot index")
|
||||||
machine_name: str = Field(description="Machine this resource belongs to", default="")
|
|
||||||
|
|
||||||
@field_serializer("parent_uuid")
|
@field_serializer("parent_uuid")
|
||||||
def _serialize_parent(self, parent_uuid: Optional["ResourceDict"]):
|
def _serialize_parent(self, parent_uuid: Optional["ResourceDict"]):
|
||||||
@@ -198,30 +204,22 @@ class ResourceDictInstance(object):
|
|||||||
self.typ = "dict"
|
self.typ = "dict"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_resource_instance_from_dict(cls, content: ResourceDictType) -> "ResourceDictInstance":
|
def get_resource_instance_from_dict(cls, content: Dict[str, Any]) -> "ResourceDictInstance":
|
||||||
"""从字典创建资源实例"""
|
"""从字典创建资源实例"""
|
||||||
if "id" not in content:
|
if "id" not in content:
|
||||||
content["id"] = content["name"]
|
content["id"] = content["name"]
|
||||||
if "uuid" not in content:
|
if "uuid" not in content:
|
||||||
content["uuid"] = str(uuid.uuid4())
|
content["uuid"] = str(uuid.uuid4())
|
||||||
if "description" in content and content["description"] is None:
|
if "description" in content and content["description"] is None:
|
||||||
# noinspection PyTypedDict
|
|
||||||
del content["description"]
|
del content["description"]
|
||||||
if "model" in content and content["model"] is None:
|
if "model" in content and content["model"] is None:
|
||||||
# noinspection PyTypedDict
|
|
||||||
del content["model"]
|
del content["model"]
|
||||||
# noinspection PyTypedDict
|
|
||||||
if "schema" in content and content["schema"] is None:
|
if "schema" in content and content["schema"] is None:
|
||||||
# noinspection PyTypedDict
|
|
||||||
del content["schema"]
|
del content["schema"]
|
||||||
# noinspection PyTypedDict
|
|
||||||
if "x" in content.get("position", {}):
|
if "x" in content.get("position", {}):
|
||||||
# 说明是老版本的position格式,转换成新的
|
# 说明是老版本的position格式,转换成新的
|
||||||
# noinspection PyTypedDict
|
|
||||||
content["position"] = {"position": content["position"]}
|
content["position"] = {"position": content["position"]}
|
||||||
# noinspection PyTypedDict
|
|
||||||
if not content.get("class"):
|
if not content.get("class"):
|
||||||
# noinspection PyTypedDict
|
|
||||||
content["class"] = ""
|
content["class"] = ""
|
||||||
if not content.get("config"): # todo: 后续从后端保证字段非空
|
if not content.get("config"): # todo: 后续从后端保证字段非空
|
||||||
content["config"] = {}
|
content["config"] = {}
|
||||||
@@ -232,18 +230,16 @@ class ResourceDictInstance(object):
|
|||||||
if "position" in content:
|
if "position" in content:
|
||||||
pose = content.get("pose", {})
|
pose = content.get("pose", {})
|
||||||
if "position" not in pose:
|
if "position" not in pose:
|
||||||
# noinspection PyTypedDict
|
|
||||||
if "position" in content["position"]:
|
if "position" in content["position"]:
|
||||||
# noinspection PyTypedDict
|
|
||||||
pose["position"] = content["position"]["position"]
|
pose["position"] = content["position"]["position"]
|
||||||
else:
|
else:
|
||||||
pose["position"] = ResourceDictPositionObjectType(x=0, y=0, z=0)
|
pose["position"] = {"x": 0, "y": 0, "z": 0}
|
||||||
if "size" not in pose:
|
if "size" not in pose:
|
||||||
pose["size"] = ResourceDictPositionSizeType(
|
pose["size"] = {
|
||||||
width= content["config"].get("size_x", 0),
|
"width": content["config"].get("size_x", 0),
|
||||||
height= content["config"].get("size_y", 0),
|
"height": content["config"].get("size_y", 0),
|
||||||
depth= content["config"].get("size_z", 0),
|
"depth": content["config"].get("size_z", 0),
|
||||||
)
|
}
|
||||||
content["pose"] = pose
|
content["pose"] = pose
|
||||||
try:
|
try:
|
||||||
res_dict = ResourceDict.model_validate(content)
|
res_dict = ResourceDict.model_validate(content)
|
||||||
@@ -411,7 +407,7 @@ class ResourceTreeSet(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_plr_resources(cls, resources: List["PLRResource"], known_newly_created=False, old_size=False) -> "ResourceTreeSet":
|
def from_plr_resources(cls, resources: List["PLRResource"], known_newly_created=False) -> "ResourceTreeSet":
|
||||||
"""
|
"""
|
||||||
从plr资源创建ResourceTreeSet
|
从plr资源创建ResourceTreeSet
|
||||||
"""
|
"""
|
||||||
@@ -434,20 +430,13 @@ class ResourceTreeSet(object):
|
|||||||
"resource_group": "resource_group",
|
"resource_group": "resource_group",
|
||||||
"trash": "trash",
|
"trash": "trash",
|
||||||
"plate_adapter": "plate_adapter",
|
"plate_adapter": "plate_adapter",
|
||||||
"consumable": "consumable",
|
|
||||||
"tool": "tool",
|
|
||||||
"condenser": "condenser",
|
|
||||||
"crucible": "crucible",
|
|
||||||
"reagent_bottle": "reagent_bottle",
|
|
||||||
"flask": "flask",
|
|
||||||
"beaker": "beaker",
|
|
||||||
}
|
}
|
||||||
if source in replace_info:
|
if source in replace_info:
|
||||||
return replace_info[source]
|
return replace_info[source]
|
||||||
elif source is None:
|
elif source is None:
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
logger.trace(f"转换pylabrobot的时候,出现未知类型 {source}")
|
print("转换pylabrobot的时候,出现未知类型", source)
|
||||||
return source
|
return source
|
||||||
|
|
||||||
def build_uuid_mapping(res: "PLRResource", uuid_list: list, parent_uuid: Optional[str] = None):
|
def build_uuid_mapping(res: "PLRResource", uuid_list: list, parent_uuid: Optional[str] = None):
|
||||||
@@ -502,7 +491,7 @@ class ResourceTreeSet(object):
|
|||||||
k: v
|
k: v
|
||||||
for k, v in d.items()
|
for k, v in d.items()
|
||||||
if k
|
if k
|
||||||
not in ([
|
not in [
|
||||||
"name",
|
"name",
|
||||||
"children",
|
"children",
|
||||||
"parent_name",
|
"parent_name",
|
||||||
@@ -513,15 +502,7 @@ class ResourceTreeSet(object):
|
|||||||
"size_z",
|
"size_z",
|
||||||
"cross_section_type",
|
"cross_section_type",
|
||||||
"bottom_type",
|
"bottom_type",
|
||||||
] if not old_size else [
|
]
|
||||||
"name",
|
|
||||||
"children",
|
|
||||||
"parent_name",
|
|
||||||
"location",
|
|
||||||
"rotation",
|
|
||||||
"cross_section_type",
|
|
||||||
"bottom_type",
|
|
||||||
])
|
|
||||||
},
|
},
|
||||||
"data": states[d["name"]],
|
"data": states[d["name"]],
|
||||||
"extra": extra,
|
"extra": extra,
|
||||||
@@ -553,10 +534,17 @@ class ResourceTreeSet(object):
|
|||||||
trees.append(tree_instance)
|
trees.append(tree_instance)
|
||||||
return cls(trees)
|
return cls(trees)
|
||||||
|
|
||||||
def to_plr_resources(self, skip_devices=True) -> List["PLRResource"]:
|
def to_plr_resources(
|
||||||
|
self, skip_devices: bool = True, requested_uuids: Optional[List[str]] = None
|
||||||
|
) -> List["PLRResource"]:
|
||||||
"""
|
"""
|
||||||
将 ResourceTreeSet 转换为 PLR 资源列表
|
将 ResourceTreeSet 转换为 PLR 资源列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
skip_devices: 是否跳过 device 类型节点
|
||||||
|
requested_uuids: 若指定,则按此 UUID 顺序返回对应资源(用于批量查询时一一对应),
|
||||||
|
否则返回各树的根节点列表
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[PLRResource]: PLR 资源实例列表
|
List[PLRResource]: PLR 资源实例列表
|
||||||
"""
|
"""
|
||||||
@@ -612,6 +600,71 @@ class ResourceTreeSet(object):
|
|||||||
d["model"] = res.config.get("model", None)
|
d["model"] = res.config.get("model", None)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
# deserialize 会单独处理的元数据 key,不传给构造函数
|
||||||
|
_META_KEYS = {"type", "parent_name", "location", "children", "rotation", "barcode"}
|
||||||
|
# deserialize 自定义逻辑使用的 key(如 TipSpot 用 prototype_tip 构建 make_tip),需保留
|
||||||
|
_DESERIALIZE_PRESERVED_KEYS = {"prototype_tip"}
|
||||||
|
|
||||||
|
def remove_incompatible_params(plr_d: dict) -> None:
|
||||||
|
"""递归移除 PLR 类不接受的参数,避免 deserialize 报错。
|
||||||
|
- 移除构造函数不接受的参数(如 compute_height_from_volume、ordering、category)
|
||||||
|
- 对 TubeRack:将 ordering 转为 ordered_items
|
||||||
|
- 保留 deserialize 自定义逻辑需要的 key(如 prototype_tip)
|
||||||
|
"""
|
||||||
|
if "type" in plr_d:
|
||||||
|
sub_cls = find_subclass(plr_d["type"], PLRResource)
|
||||||
|
if sub_cls is not None:
|
||||||
|
spec = inspect.signature(sub_cls)
|
||||||
|
valid_params = set(spec.parameters.keys())
|
||||||
|
# TubeRack 特殊处理:先转换 ordering,再参与后续过滤
|
||||||
|
if "ordering" not in valid_params and "ordering" in plr_d:
|
||||||
|
ordering = plr_d.pop("ordering", None)
|
||||||
|
if sub_cls.__name__ == "TubeRack":
|
||||||
|
plr_d["ordered_items"] = (
|
||||||
|
_ordering_to_ordered_items(plr_d, ordering)
|
||||||
|
if ordering
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
# 移除构造函数不接受的参数(保留 META 和 deserialize 自定义逻辑需要的 key)
|
||||||
|
for key in list(plr_d.keys()):
|
||||||
|
if (
|
||||||
|
key not in _META_KEYS
|
||||||
|
and key not in _DESERIALIZE_PRESERVED_KEYS
|
||||||
|
and key not in valid_params
|
||||||
|
):
|
||||||
|
plr_d.pop(key, None)
|
||||||
|
for child in plr_d.get("children", []):
|
||||||
|
remove_incompatible_params(child)
|
||||||
|
|
||||||
|
def _ordering_to_ordered_items(plr_d: dict, ordering: dict) -> dict:
|
||||||
|
"""将 ordering 转为 ordered_items,从 children 构建 Tube 对象"""
|
||||||
|
from pylabrobot.resources import Tube, Coordinate
|
||||||
|
from pylabrobot.serializer import deserialize as plr_deserialize
|
||||||
|
|
||||||
|
children = plr_d.get("children", [])
|
||||||
|
ordered_items = {}
|
||||||
|
for idx, (ident, child_name) in enumerate(ordering.items()):
|
||||||
|
child_data = children[idx] if idx < len(children) else None
|
||||||
|
if child_data is None:
|
||||||
|
continue
|
||||||
|
loc_data = child_data.get("location")
|
||||||
|
loc = (
|
||||||
|
plr_deserialize(loc_data)
|
||||||
|
if loc_data
|
||||||
|
else Coordinate(0, 0, 0)
|
||||||
|
)
|
||||||
|
tube = Tube(
|
||||||
|
name=child_data.get("name", child_name or ident),
|
||||||
|
size_x=child_data.get("size_x", 10),
|
||||||
|
size_y=child_data.get("size_y", 10),
|
||||||
|
size_z=child_data.get("size_z", 50),
|
||||||
|
max_volume=child_data.get("max_volume", 1000),
|
||||||
|
)
|
||||||
|
tube.location = loc
|
||||||
|
ordered_items[ident] = tube
|
||||||
|
plr_d["children"] = [] # 已并入 ordered_items,避免重复反序列化
|
||||||
|
return ordered_items
|
||||||
|
|
||||||
plr_resources = []
|
plr_resources = []
|
||||||
tracker = DeviceNodeResourceTracker()
|
tracker = DeviceNodeResourceTracker()
|
||||||
|
|
||||||
@@ -631,9 +684,7 @@ class ResourceTreeSet(object):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类。原始信息:{tree.root_node.res_content}"
|
f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类。原始信息:{tree.root_node.res_content}"
|
||||||
)
|
)
|
||||||
spec = inspect.signature(sub_cls)
|
remove_incompatible_params(plr_dict)
|
||||||
if "category" not in spec.parameters:
|
|
||||||
plr_dict.pop("category", None)
|
|
||||||
plr_resource = sub_cls.deserialize(plr_dict, allow_marshal=True)
|
plr_resource = sub_cls.deserialize(plr_dict, allow_marshal=True)
|
||||||
from pylabrobot.resources import Coordinate
|
from pylabrobot.resources import Coordinate
|
||||||
from pylabrobot.serializer import deserialize
|
from pylabrobot.serializer import deserialize
|
||||||
@@ -647,12 +698,47 @@ class ResourceTreeSet(object):
|
|||||||
plr_resources.append(plr_resource)
|
plr_resources.append(plr_resource)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"转换 PLR 资源失败: {e} {str(plr_dict)[:1000]}")
|
logger.error(f"转换 PLR 资源失败: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
logger.error(f"堆栈: {traceback.format_exc()}")
|
logger.error(f"堆栈: {traceback.format_exc()}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
if requested_uuids:
|
||||||
|
# 按请求的 UUID 顺序返回对应资源(从整棵树中按 uuid 提取)
|
||||||
|
# 优先使用 tracker.uuid_to_resources;若映射缺失,再递归遍历 PLR 树兜底搜索。
|
||||||
|
def _find_plr_by_uuid(roots: List["PLRResource"], uid: str) -> Optional["PLRResource"]:
|
||||||
|
stack = list(roots)
|
||||||
|
while stack:
|
||||||
|
node = stack.pop()
|
||||||
|
node_uid = getattr(node, "unilabos_uuid", None)
|
||||||
|
if node_uid == uid:
|
||||||
|
return node
|
||||||
|
children = getattr(node, "children", None) or []
|
||||||
|
stack.extend(children)
|
||||||
|
return None
|
||||||
|
|
||||||
|
result = []
|
||||||
|
missing_uuids = []
|
||||||
|
for uid in requested_uuids:
|
||||||
|
found = tracker.uuid_to_resources.get(uid)
|
||||||
|
if found is None:
|
||||||
|
found = _find_plr_by_uuid(plr_resources, uid)
|
||||||
|
if found is not None:
|
||||||
|
# 回填缓存,后续相同 uuid 可直接命中
|
||||||
|
tracker.uuid_to_resources[uid] = found
|
||||||
|
if found is None:
|
||||||
|
missing_uuids.append(uid)
|
||||||
|
else:
|
||||||
|
result.append(found)
|
||||||
|
|
||||||
|
if missing_uuids:
|
||||||
|
raise ValueError(
|
||||||
|
f"请求的 UUID 未在资源树中找到: {missing_uuids}。"
|
||||||
|
f"可用 UUID 数量: {len(tracker.uuid_to_resources)},"
|
||||||
|
f"资源树数量: {len(self.trees)}"
|
||||||
|
)
|
||||||
|
return result
|
||||||
return plr_resources
|
return plr_resources
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -820,7 +906,6 @@ class ResourceTreeSet(object):
|
|||||||
if remote_root_type == "device":
|
if remote_root_type == "device":
|
||||||
# 情况1: 一级是 device
|
# 情况1: 一级是 device
|
||||||
if remote_root_id not in local_device_map:
|
if remote_root_id not in local_device_map:
|
||||||
if remote_root_id != "host_node":
|
|
||||||
logger.warning(f"Device '{remote_root_id}' 在本地不存在,跳过该 device 下的物料同步")
|
logger.warning(f"Device '{remote_root_id}' 在本地不存在,跳过该 device 下的物料同步")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -868,27 +953,14 @@ class ResourceTreeSet(object):
|
|||||||
f"从远端同步了 {added_count} 个物料子树"
|
f"从远端同步了 {added_count} 个物料子树"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 二级物料已存在,比较三级子节点是否缺失
|
# 情况2: 二级是物料(不是 device)
|
||||||
local_material = local_children_map[remote_child_name]
|
if remote_child_name not in local_children_map:
|
||||||
local_material_children_map = {child.res_content.name: child for child in
|
# 引入整个子树
|
||||||
local_material.children}
|
remote_child.res_content.parent = local_device.res_content
|
||||||
added_count = 0
|
local_device.children.append(remote_child)
|
||||||
for remote_sub in remote_child.children:
|
logger.info(f"Device '{remote_root_id}': 从远端同步物料子树 '{remote_child_name}'")
|
||||||
remote_sub_name = remote_sub.res_content.name
|
|
||||||
if remote_sub_name not in local_material_children_map:
|
|
||||||
remote_sub.res_content.parent = local_material.res_content
|
|
||||||
local_material.children.append(remote_sub)
|
|
||||||
added_count += 1
|
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(f"物料 '{remote_root_id}/{remote_child_name}' 已存在,跳过")
|
||||||
f"物料 '{remote_root_id}/{remote_child_name}/{remote_sub_name}' "
|
|
||||||
f"已存在,跳过"
|
|
||||||
)
|
|
||||||
if added_count > 0:
|
|
||||||
logger.info(
|
|
||||||
f"物料 '{remote_root_id}/{remote_child_name}': "
|
|
||||||
f"从远端同步了 {added_count} 个子物料"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# 情况1: 一级节点是物料(不是 device)
|
# 情况1: 一级节点是物料(不是 device)
|
||||||
# 检查是否已存在
|
# 检查是否已存在
|
||||||
@@ -911,7 +983,7 @@ class ResourceTreeSet(object):
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def dump(self, old_position=False) -> List[List[Dict[str, Any]]]:
|
def dump(self) -> List[List[Dict[str, Any]]]:
|
||||||
"""
|
"""
|
||||||
将 ResourceTreeSet 序列化为嵌套列表格式
|
将 ResourceTreeSet 序列化为嵌套列表格式
|
||||||
|
|
||||||
@@ -927,10 +999,6 @@ class ResourceTreeSet(object):
|
|||||||
# 获取树的所有节点并序列化
|
# 获取树的所有节点并序列化
|
||||||
tree_nodes = [node.res_content.model_dump(by_alias=True) for node in tree.get_all_nodes()]
|
tree_nodes = [node.res_content.model_dump(by_alias=True) for node in tree.get_all_nodes()]
|
||||||
result.append(tree_nodes)
|
result.append(tree_nodes)
|
||||||
if old_position:
|
|
||||||
for r in result:
|
|
||||||
for rr in r:
|
|
||||||
rr["position"] = rr["pose"]["position"]
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from io import StringIO
|
|||||||
from typing import Iterable, Any, Dict, Type, TypeVar, Union
|
from typing import Iterable, Any, Dict, Type, TypeVar, Union
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from msgcenterpy.instances.ros2_instance import ROS2MessageInstance
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from dataclasses import asdict, is_dataclass
|
from dataclasses import asdict, is_dataclass
|
||||||
|
|
||||||
@@ -717,19 +716,6 @@ def ros_field_type_to_json_schema(
|
|||||||
# return {'type': 'object', 'description': f'未知类型: {field_type}'}
|
# return {'type': 'object', 'description': f'未知类型: {field_type}'}
|
||||||
|
|
||||||
|
|
||||||
def _strip_rosidl_descriptions(schema: Any) -> None:
|
|
||||||
"""递归清除 rosidl_parser 自动生成的无意义 description(含内存地址)。"""
|
|
||||||
if isinstance(schema, dict):
|
|
||||||
desc = schema.get("description", "")
|
|
||||||
if isinstance(desc, str) and "rosidl_parser" in desc:
|
|
||||||
del schema["description"]
|
|
||||||
for v in schema.values():
|
|
||||||
_strip_rosidl_descriptions(v)
|
|
||||||
elif isinstance(schema, list):
|
|
||||||
for item in schema:
|
|
||||||
_strip_rosidl_descriptions(item)
|
|
||||||
|
|
||||||
|
|
||||||
def ros_message_to_json_schema(msg_class: Any, field_name: str) -> Dict[str, Any]:
|
def ros_message_to_json_schema(msg_class: Any, field_name: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
将 ROS 消息类转换为 JSON Schema
|
将 ROS 消息类转换为 JSON Schema
|
||||||
@@ -741,10 +727,46 @@ def ros_message_to_json_schema(msg_class: Any, field_name: str) -> Dict[str, Any
|
|||||||
Returns:
|
Returns:
|
||||||
对应的 JSON Schema 定义
|
对应的 JSON Schema 定义
|
||||||
"""
|
"""
|
||||||
schema = ROS2MessageInstance(msg_class()).get_json_schema()
|
schema = {"type": "object", "properties": {}, "required": []}
|
||||||
|
|
||||||
|
# 优先使用字段名作为标题,否则使用类名
|
||||||
schema["title"] = field_name
|
schema["title"] = field_name
|
||||||
schema.pop("description", None)
|
|
||||||
_strip_rosidl_descriptions(schema)
|
# 获取消息的字段和字段类型
|
||||||
|
try:
|
||||||
|
for ind, slot_info in enumerate(msg_class._fields_and_field_types.items()):
|
||||||
|
slot_name, slot_type = slot_info
|
||||||
|
type_info = msg_class.SLOT_TYPES[ind]
|
||||||
|
field_schema = ros_field_type_to_json_schema(type_info, slot_name)
|
||||||
|
schema["properties"][slot_name] = field_schema
|
||||||
|
schema["required"].append(slot_name)
|
||||||
|
# if hasattr(msg_class, 'get_fields_and_field_types'):
|
||||||
|
# fields_and_types = msg_class.get_fields_and_field_types()
|
||||||
|
#
|
||||||
|
# for field_name, field_type in fields_and_types.items():
|
||||||
|
# # 将 ROS 字段类型转换为 JSON Schema
|
||||||
|
# field_schema = ros_field_type_to_json_schema(field_type)
|
||||||
|
#
|
||||||
|
# schema['properties'][field_name] = field_schema
|
||||||
|
# schema['required'].append(field_name)
|
||||||
|
# elif hasattr(msg_class, '__slots__') and hasattr(msg_class, '_fields_and_field_types'):
|
||||||
|
# # 直接从实例属性获取
|
||||||
|
# for field_name in msg_class.__slots__:
|
||||||
|
# # 移除前导下划线(如果有)
|
||||||
|
# clean_name = field_name[1:] if field_name.startswith('_') else field_name
|
||||||
|
#
|
||||||
|
# # 从 _fields_and_field_types 获取类型
|
||||||
|
# if clean_name in msg_class._fields_and_field_types:
|
||||||
|
# field_type = msg_class._fields_and_field_types[clean_name]
|
||||||
|
# field_schema = ros_field_type_to_json_schema(field_type)
|
||||||
|
#
|
||||||
|
# schema['properties'][clean_name] = field_schema
|
||||||
|
# schema['required'].append(clean_name)
|
||||||
|
except Exception as e:
|
||||||
|
# 如果获取字段类型失败,添加错误信息
|
||||||
|
schema["description"] = f"解析消息字段时出错: {str(e)}"
|
||||||
|
logger.error(f"解析 {msg_class.__name__} 消息字段失败: {str(e)}")
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|
||||||
@@ -791,8 +813,6 @@ def ros_action_to_json_schema(
|
|||||||
"required": ["goal"],
|
"required": ["goal"],
|
||||||
}
|
}
|
||||||
|
|
||||||
_strip_rosidl_descriptions(schema)
|
|
||||||
|
|
||||||
# 保留之前 schema 中 goal/feedback/result 下一级字段的 description
|
# 保留之前 schema 中 goal/feedback/result 下一级字段的 description
|
||||||
if previous_schema:
|
if previous_schema:
|
||||||
_preserve_field_descriptions(schema, previous_schema)
|
_preserve_field_descriptions(schema, previous_schema)
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ from unilabos_msgs.action import SendCmd
|
|||||||
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
|
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
|
||||||
|
|
||||||
from unilabos.config.config import BasicConfig
|
from unilabos.config.config import BasicConfig
|
||||||
from unilabos.registry.decorators import get_topic_config
|
from unilabos.utils.decorator import get_topic_config, get_all_subscriptions
|
||||||
from unilabos.utils.decorator import get_all_subscriptions
|
|
||||||
|
|
||||||
from unilabos.resources.container import RegularContainer
|
from unilabos.resources.container import RegularContainer
|
||||||
from unilabos.resources.graphio import (
|
from unilabos.resources.graphio import (
|
||||||
@@ -58,7 +57,6 @@ from unilabos_msgs.msg import Resource # type: ignore
|
|||||||
|
|
||||||
from unilabos.resources.resource_tracker import (
|
from unilabos.resources.resource_tracker import (
|
||||||
DeviceNodeResourceTracker,
|
DeviceNodeResourceTracker,
|
||||||
ResourceDictType,
|
|
||||||
ResourceTreeSet,
|
ResourceTreeSet,
|
||||||
ResourceTreeInstance,
|
ResourceTreeInstance,
|
||||||
ResourceDictInstance,
|
ResourceDictInstance,
|
||||||
@@ -196,9 +194,9 @@ class PropertyPublisher:
|
|||||||
self._value = None
|
self._value = None
|
||||||
try:
|
try:
|
||||||
self.publisher_ = node.create_publisher(msg_type, f"{name}", qos)
|
self.publisher_ = node.create_publisher(msg_type, f"{name}", qos)
|
||||||
except Exception as e:
|
except AttributeError as ex:
|
||||||
self.node.lab_logger().error(
|
self.node.lab_logger().error(
|
||||||
f"StatusError, DeviceId: {self.node.device_id} 创建发布者 {name} 失败,可能由于注册表有误,类型: {msg_type},错误: {e}"
|
f"创建发布者 {name} 失败,可能由于注册表有误,类型: {msg_type},错误: {ex}\n{traceback.format_exc()}"
|
||||||
)
|
)
|
||||||
self.timer = node.create_timer(self.timer_period, self.publish_property)
|
self.timer = node.create_timer(self.timer_period, self.publish_property)
|
||||||
self.__loop = ROS2DeviceNode.get_asyncio_loop()
|
self.__loop = ROS2DeviceNode.get_asyncio_loop()
|
||||||
@@ -571,11 +569,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
future.add_done_callback(done_cb)
|
future.add_done_callback(done_cb)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.lab_logger().error("Host请求添加物料时,本环境并不存在pylabrobot")
|
self.lab_logger().error("Host请求添加物料时,本环境并不存在pylabrobot")
|
||||||
res.response = get_result_info_str(traceback.format_exc(), False, {})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.lab_logger().error("Host请求添加物料时出错")
|
self.lab_logger().error("Host请求添加物料时出错")
|
||||||
self.lab_logger().error(traceback.format_exc())
|
self.lab_logger().error(traceback.format_exc())
|
||||||
res.response = get_result_info_str(traceback.format_exc(), False, {})
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
@@ -598,12 +594,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
self.s2c_resource_tree, # type: ignore
|
self.s2c_resource_tree, # type: ignore
|
||||||
callback_group=self.callback_group,
|
callback_group=self.callback_group,
|
||||||
),
|
),
|
||||||
"s2c_device_manage": self.create_service(
|
|
||||||
SerialCommand,
|
|
||||||
f"/srv{self.namespace}/s2c_device_manage",
|
|
||||||
self.s2c_device_manage, # type: ignore
|
|
||||||
callback_group=self.callback_group,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 向全局在线设备注册表添加设备信息
|
# 向全局在线设备注册表添加设备信息
|
||||||
@@ -1072,48 +1062,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
async def s2c_device_manage(self, req: SerialCommand_Request, res: SerialCommand_Response):
|
|
||||||
"""Handle add/remove device requests from HostNode via SerialCommand."""
|
|
||||||
try:
|
|
||||||
cmd = json.loads(req.command)
|
|
||||||
action = cmd.get("action", "")
|
|
||||||
data = cmd.get("data", {})
|
|
||||||
device_id = data.get("device_id", "")
|
|
||||||
|
|
||||||
if not device_id:
|
|
||||||
res.response = json.dumps({"success": False, "error": "device_id required"})
|
|
||||||
return res
|
|
||||||
|
|
||||||
if action == "add":
|
|
||||||
result = self.create_device(device_id, data)
|
|
||||||
elif action == "remove":
|
|
||||||
result = self.destroy_device(device_id)
|
|
||||||
else:
|
|
||||||
result = {"success": False, "error": f"Unknown action: {action}"}
|
|
||||||
|
|
||||||
res.response = json.dumps(result, ensure_ascii=False)
|
|
||||||
|
|
||||||
except NotImplementedError as e:
|
|
||||||
self.lab_logger().warning(f"[DeviceManage] {e}")
|
|
||||||
res.response = json.dumps({"success": False, "error": str(e)})
|
|
||||||
except Exception as e:
|
|
||||||
self.lab_logger().error(f"[DeviceManage] Error: {e}")
|
|
||||||
res.response = json.dumps({"success": False, "error": str(e)})
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
def create_device(self, device_id: str, config: "ResourceDictType") -> dict:
|
|
||||||
"""Create a sub-device dynamically. Override in HostNode / WorkstationNode."""
|
|
||||||
raise NotImplementedError(
|
|
||||||
f"{self.__class__.__name__} does not support dynamic device creation"
|
|
||||||
)
|
|
||||||
|
|
||||||
def destroy_device(self, device_id: str) -> dict:
|
|
||||||
"""Destroy a sub-device dynamically. Override in HostNode / WorkstationNode."""
|
|
||||||
raise NotImplementedError(
|
|
||||||
f"{self.__class__.__name__} does not support dynamic device removal"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def transfer_resource_to_another(
|
async def transfer_resource_to_another(
|
||||||
self,
|
self,
|
||||||
plr_resources: List["ResourcePLR"],
|
plr_resources: List["ResourcePLR"],
|
||||||
@@ -1256,40 +1204,22 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
return self._lab_logger
|
return self._lab_logger
|
||||||
|
|
||||||
def create_ros_publisher(self, attr_name, msg_type, initial_period=5.0):
|
def create_ros_publisher(self, attr_name, msg_type, initial_period=5.0):
|
||||||
"""创建ROS发布者,仅当方法/属性有 @topic_config 装饰器时才创建。"""
|
"""创建ROS发布者"""
|
||||||
# 检测 @topic_config 装饰器配置
|
# 检测装饰器配置(支持 get_{attr_name} 方法和 @property)
|
||||||
topic_config = {}
|
topic_config = {}
|
||||||
driver_class = type(self.driver_instance)
|
|
||||||
|
|
||||||
# 区分 @property 和普通方法两种情况
|
# 优先检测 get_{attr_name} 方法
|
||||||
is_prop = hasattr(driver_class, attr_name) and isinstance(
|
if hasattr(self.driver_instance, f"get_{attr_name}"):
|
||||||
getattr(driver_class, attr_name), property
|
getter_method = getattr(self.driver_instance, f"get_{attr_name}")
|
||||||
)
|
topic_config = get_topic_config(getter_method)
|
||||||
|
|
||||||
if is_prop:
|
# 如果没有配置,检测 @property 装饰的属性
|
||||||
# @property: 检测 fget 上的 @topic_config
|
|
||||||
class_attr = getattr(driver_class, attr_name)
|
|
||||||
if class_attr.fget is not None:
|
|
||||||
topic_config = get_topic_config(class_attr.fget)
|
|
||||||
else:
|
|
||||||
# 普通方法: 直接检测 attr_name 方法上的 @topic_config
|
|
||||||
if hasattr(self.driver_instance, attr_name):
|
|
||||||
method = getattr(self.driver_instance, attr_name)
|
|
||||||
if callable(method):
|
|
||||||
topic_config = get_topic_config(method)
|
|
||||||
|
|
||||||
# 没有 @topic_config 装饰器则跳过发布
|
|
||||||
if not topic_config:
|
if not topic_config:
|
||||||
return
|
driver_class = type(self.driver_instance)
|
||||||
|
if hasattr(driver_class, attr_name):
|
||||||
# 发布名称优先级: @topic_config(name=...) > get_ 前缀去除 > attr_name
|
class_attr = getattr(driver_class, attr_name)
|
||||||
cfg_name = topic_config.get("name")
|
if isinstance(class_attr, property) and class_attr.fget is not None:
|
||||||
if cfg_name:
|
topic_config = get_topic_config(class_attr.fget)
|
||||||
publish_name = cfg_name
|
|
||||||
elif attr_name.startswith("get_"):
|
|
||||||
publish_name = attr_name[4:]
|
|
||||||
else:
|
|
||||||
publish_name = attr_name
|
|
||||||
|
|
||||||
# 使用装饰器配置或默认值
|
# 使用装饰器配置或默认值
|
||||||
cfg_period = topic_config.get("period")
|
cfg_period = topic_config.get("period")
|
||||||
@@ -1302,10 +1232,10 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
# 获取属性值的方法
|
# 获取属性值的方法
|
||||||
def get_device_attr():
|
def get_device_attr():
|
||||||
try:
|
try:
|
||||||
if is_prop:
|
if hasattr(self.driver_instance, f"get_{attr_name}"):
|
||||||
return getattr(self.driver_instance, attr_name)
|
return getattr(self.driver_instance, f"get_{attr_name}")()
|
||||||
else:
|
else:
|
||||||
return getattr(self.driver_instance, attr_name)()
|
return getattr(self.driver_instance, attr_name)
|
||||||
except AttributeError as ex:
|
except AttributeError as ex:
|
||||||
if ex.args[0].startswith(f"AttributeError: '{self.driver_instance.__class__.__name__}' object"):
|
if ex.args[0].startswith(f"AttributeError: '{self.driver_instance.__class__.__name__}' object"):
|
||||||
self.lab_logger().error(
|
self.lab_logger().error(
|
||||||
@@ -1317,8 +1247,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
)
|
)
|
||||||
self.lab_logger().error(traceback.format_exc())
|
self.lab_logger().error(traceback.format_exc())
|
||||||
|
|
||||||
self._property_publishers[publish_name] = PropertyPublisher(
|
self._property_publishers[attr_name] = PropertyPublisher(
|
||||||
self, publish_name, get_device_attr, msg_type, period, print_publish, qos
|
self, attr_name, get_device_attr, msg_type, period, print_publish, qos
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_ros_action_server(self, action_name, action_value_mapping):
|
def create_ros_action_server(self, action_name, action_value_mapping):
|
||||||
@@ -1326,7 +1256,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
action_type = action_value_mapping["type"]
|
action_type = action_value_mapping["type"]
|
||||||
str_action_type = str(action_type)[8:-2]
|
str_action_type = str(action_type)[8:-2]
|
||||||
|
|
||||||
try:
|
|
||||||
self._action_servers[action_name] = ActionServer(
|
self._action_servers[action_name] = ActionServer(
|
||||||
self,
|
self,
|
||||||
action_type,
|
action_type,
|
||||||
@@ -1334,9 +1263,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
execute_callback=self._create_execute_callback(action_name, action_value_mapping),
|
execute_callback=self._create_execute_callback(action_name, action_value_mapping),
|
||||||
callback_group=self.callback_group,
|
callback_group=self.callback_group,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
self.lab_logger().error(f"创建ActionServer失败,Device: {self.device_id}, Action Name: {action_name}, Action Type: {action_type}, Error: {e}")
|
|
||||||
return
|
|
||||||
self.lab_logger().trace(f"发布动作: {action_name}, 类型: {str_action_type}")
|
self.lab_logger().trace(f"发布动作: {action_name}, 类型: {str_action_type}")
|
||||||
|
|
||||||
def _setup_decorated_subscribers(self):
|
def _setup_decorated_subscribers(self):
|
||||||
@@ -1884,8 +1811,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 处理单个 ResourceSlot
|
# 处理单个 ResourceSlot
|
||||||
_is_resource_slot = isinstance(arg_type, str) and arg_type.endswith(":ResourceSlot")
|
if arg_type == "unilabos.registry.placeholder_type:ResourceSlot":
|
||||||
if _is_resource_slot:
|
|
||||||
resource_data = function_args[arg_name]
|
resource_data = function_args[arg_name]
|
||||||
if isinstance(resource_data, dict) and "id" in resource_data:
|
if isinstance(resource_data, dict) and "id" in resource_data:
|
||||||
try:
|
try:
|
||||||
@@ -1899,7 +1825,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
|
|
||||||
# 处理 ResourceSlot 列表
|
# 处理 ResourceSlot 列表
|
||||||
elif isinstance(arg_type, tuple) and len(arg_type) == 2:
|
elif isinstance(arg_type, tuple) and len(arg_type) == 2:
|
||||||
if arg_type[0] == "list" and isinstance(arg_type[1], str) and arg_type[1].endswith(":ResourceSlot"):
|
resource_slot_type = "unilabos.registry.placeholder_type:ResourceSlot"
|
||||||
|
if arg_type[0] == "list" and arg_type[1] == resource_slot_type:
|
||||||
resource_list = function_args[arg_name]
|
resource_list = function_args[arg_name]
|
||||||
if isinstance(resource_list, list):
|
if isinstance(resource_list, list):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -4,14 +4,7 @@ import cv2
|
|||||||
from sensor_msgs.msg import Image
|
from sensor_msgs.msg import Image
|
||||||
from cv_bridge import CvBridge
|
from cv_bridge import CvBridge
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker
|
||||||
from unilabos.registry.decorators import device
|
|
||||||
|
|
||||||
|
|
||||||
@device(
|
|
||||||
id="camera",
|
|
||||||
category=["camera"],
|
|
||||||
description="""VideoPublisher摄像头设备节点,用于实时视频采集和流媒体发布。该设备通过OpenCV连接本地摄像头(如USB摄像头、内置摄像头等),定时采集视频帧并将其转换为ROS2的sensor_msgs/Image消息格式发布到视频话题。主要用于实验室自动化系统中的视觉监控、图像分析、实时观察等应用场景。支持可配置的摄像头索引、发布频率等参数。""",
|
|
||||||
)
|
|
||||||
class VideoPublisher(BaseROS2DeviceNode):
|
class VideoPublisher(BaseROS2DeviceNode):
|
||||||
def __init__(self, device_id='video_publisher', registry_name="", device_uuid='', camera_index=0, period: float = 0.1, resource_tracker: DeviceNodeResourceTracker = None):
|
def __init__(self, device_id='video_publisher', registry_name="", device_uuid='', camera_index=0, period: float = 0.1, resource_tracker: DeviceNodeResourceTracker = None):
|
||||||
# 初始化BaseROS2DeviceNode,使用自身作为driver_instance
|
# 初始化BaseROS2DeviceNode,使用自身作为driver_instance
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user