mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-24 05:48:20 +00:00
Compare commits
9 Commits
0f6264503a
...
feat/3d_bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48e13a7b4d | ||
|
|
d776550a4b | ||
|
|
3d8123849a | ||
|
|
d2f204c5b0 | ||
|
|
d8922884b1 | ||
|
|
427afe83d4 | ||
|
|
23c2e3b2f7 | ||
|
|
59c26265e9 | ||
|
|
4c2adea55a |
@@ -3,7 +3,7 @@
|
||||
|
||||
package:
|
||||
name: unilabos
|
||||
version: 0.10.18
|
||||
version: 0.10.19
|
||||
|
||||
source:
|
||||
path: ../../unilabos
|
||||
@@ -54,7 +54,7 @@ requirements:
|
||||
- pymodbus
|
||||
- matplotlib
|
||||
- pylibftdi
|
||||
- uni-lab::unilabos-env ==0.10.18
|
||||
- uni-lab::unilabos-env ==0.10.19
|
||||
|
||||
about:
|
||||
repository: https://github.com/deepmodeling/Uni-Lab-OS
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
package:
|
||||
name: unilabos-env
|
||||
version: 0.10.18
|
||||
version: 0.10.19
|
||||
|
||||
build:
|
||||
noarch: generic
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
package:
|
||||
name: unilabos-full
|
||||
version: 0.10.18
|
||||
version: 0.10.19
|
||||
|
||||
build:
|
||||
noarch: generic
|
||||
@@ -11,7 +11,7 @@ build:
|
||||
requirements:
|
||||
run:
|
||||
# Base unilabos package (includes unilabos-env)
|
||||
- uni-lab::unilabos ==0.10.18
|
||||
- uni-lab::unilabos ==0.10.19
|
||||
# Documentation tools
|
||||
- sphinx
|
||||
- sphinx_rtd_theme
|
||||
|
||||
160
.cursor/skills/add-device/SKILL.md
Normal file
160
.cursor/skills/add-device/SKILL.md
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
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>/` 目录下
|
||||
351
.cursor/skills/add-resource/SKILL.md
Normal file
351
.cursor/skills/add-resource/SKILL.md
Normal file
@@ -0,0 +1,351 @@
|
||||
---
|
||||
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` |
|
||||
292
.cursor/skills/add-resource/reference.md
Normal file
292
.cursor/skills/add-resource/reference.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# 资源高级参考
|
||||
|
||||
本文件是 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` |
|
||||
328
.cursor/skills/create-device-skill/SKILL.md
Normal file
328
.cursor/skills/create-device-skill/SKILL.md
Normal file
@@ -0,0 +1,328 @@
|
||||
---
|
||||
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
|
||||
└── ...
|
||||
```
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/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()
|
||||
69
.cursor/skills/create-device-skill/scripts/gen_auth.py
Normal file
69
.cursor/skills/create-device-skill/scripts/gen_auth.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/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()
|
||||
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: ros-humble-unilabos-msgs
|
||||
version: 0.10.18
|
||||
version: 0.10.19
|
||||
source:
|
||||
path: ../../unilabos_msgs
|
||||
target_directory: src
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: unilabos
|
||||
version: "0.10.18"
|
||||
version: "0.10.19"
|
||||
|
||||
source:
|
||||
path: ../..
|
||||
|
||||
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
||||
|
||||
setup(
|
||||
name=package_name,
|
||||
version='0.10.18',
|
||||
version='0.10.19',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=['setuptools'],
|
||||
|
||||
939
tests/ros/test_joint_state_bridge.py
Normal file
939
tests/ros/test_joint_state_bridge.py
Normal file
@@ -0,0 +1,939 @@
|
||||
"""
|
||||
P1 关节数据 & 资源跟随桥接测试 — 全面覆盖 HostNode 关节回调 + resource_pose 回调的边缘 case。
|
||||
|
||||
不依赖 ROS2 运行时,通过 mock 模拟 msg 和 bridge。
|
||||
|
||||
测试分组:
|
||||
E1: JointRepublisher JSON 输出格式 (已修复 str→json.dumps)
|
||||
E2: 关节状态回调 — 从 /joint_states (JointState msg) 直接读取 name/position
|
||||
E3: 资源跟随 (resource_pose) — 夹爪抓取/释放/多资源
|
||||
E4: 联合流程 — 关节 + 资源一并通过 bridge 发送
|
||||
E5: Bridge 调用验证
|
||||
E6: 同类型设备多实例 — 重复关节名场景
|
||||
E7: 吞吐优化 — 死区过滤、抑频、增量 resource_poses
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
from types import SimpleNamespace
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
# ==================== 辅助: 模拟 JointState msg ====================
|
||||
|
||||
|
||||
def _make_joint_state_msg(names: list, positions: list, velocities=None, efforts=None):
|
||||
"""构造模拟的 sensor_msgs/JointState 消息(不依赖 ROS2)"""
|
||||
msg = SimpleNamespace()
|
||||
msg.name = names
|
||||
msg.position = positions
|
||||
msg.velocity = velocities or [0.0] * len(names)
|
||||
msg.effort = efforts or [0.0] * len(names)
|
||||
return msg
|
||||
|
||||
|
||||
def _make_string_msg(data: str):
|
||||
"""构造模拟的 std_msgs/String 消息"""
|
||||
msg = SimpleNamespace()
|
||||
msg.data = data
|
||||
return msg
|
||||
|
||||
|
||||
# ==================== 辅助: 提取 HostNode 核心逻辑用于隔离测试 ====================
|
||||
|
||||
|
||||
class JointBridgeSimulator:
|
||||
"""
|
||||
模拟 HostNode 的关节桥接核心逻辑(提取自 host_node.py),
|
||||
不依赖 ROS2 Node、subscription 等基础设施。
|
||||
|
||||
包含吞吐优化逻辑:
|
||||
- 死区过滤 (dead band): 关节变化 < 阈值时不发送
|
||||
- 抑频 (throttle): 限制最大发送频率
|
||||
- 增量 resource_poses: 仅在变化时附带
|
||||
"""
|
||||
|
||||
JOINT_DEAD_BAND: float = 1e-4
|
||||
JOINT_MIN_INTERVAL: float = 0.05 # 秒
|
||||
|
||||
def __init__(self, device_uuid_map: Dict[str, str],
|
||||
dead_band: Optional[float] = None,
|
||||
min_interval: Optional[float] = None):
|
||||
self.device_uuid_map = device_uuid_map
|
||||
self._device_ids_sorted = sorted(device_uuid_map.keys(), key=len, reverse=True)
|
||||
self._resource_poses: Dict[str, str] = {}
|
||||
self._resource_poses_dirty: bool = False
|
||||
self._last_joint_values: Dict[str, float] = {}
|
||||
self._last_send_time: float = -float("inf") # 确保首条消息总是通过
|
||||
# 允许测试覆盖优化参数
|
||||
if dead_band is not None:
|
||||
self.JOINT_DEAD_BAND = dead_band
|
||||
if min_interval is not None:
|
||||
self.JOINT_MIN_INTERVAL = min_interval
|
||||
|
||||
def resource_pose_callback(self, msg) -> None:
|
||||
"""模拟 HostNode._resource_pose_callback(含变化检测)"""
|
||||
try:
|
||||
data = json.loads(msg.data)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return
|
||||
if not isinstance(data, dict) or not data:
|
||||
return
|
||||
has_change = False
|
||||
for k, v in data.items():
|
||||
if self._resource_poses.get(k) != v:
|
||||
has_change = True
|
||||
break
|
||||
if has_change:
|
||||
self._resource_poses.update(data)
|
||||
self._resource_poses_dirty = True
|
||||
|
||||
def joint_state_callback(self, msg, now: Optional[float] = None) -> dict:
|
||||
"""
|
||||
模拟 HostNode._joint_state_callback 核心逻辑(含优化)。
|
||||
now 参数允许测试控制时间。
|
||||
返回 {device_id: {"node_uuid": ..., "joint_states": {...}, "resource_poses": {...}}}。
|
||||
返回 {} 表示被优化过滤。
|
||||
"""
|
||||
names = list(msg.name)
|
||||
positions = list(msg.position)
|
||||
if not names or len(names) != len(positions):
|
||||
return {}
|
||||
|
||||
if now is None:
|
||||
now = time.time()
|
||||
resource_dirty = self._resource_poses_dirty
|
||||
|
||||
# 抑频检查
|
||||
if not resource_dirty and (now - self._last_send_time) < self.JOINT_MIN_INTERVAL:
|
||||
return {}
|
||||
|
||||
# 死区过滤
|
||||
has_significant_change = False
|
||||
for name, pos in zip(names, positions):
|
||||
last_val = self._last_joint_values.get(name)
|
||||
if last_val is None or abs(float(pos) - last_val) >= self.JOINT_DEAD_BAND:
|
||||
has_significant_change = True
|
||||
break
|
||||
|
||||
if not has_significant_change and not resource_dirty:
|
||||
return {}
|
||||
|
||||
# 更新状态
|
||||
for name, pos in zip(names, positions):
|
||||
self._last_joint_values[name] = float(pos)
|
||||
self._last_send_time = now
|
||||
|
||||
# 按设备 ID 分组关节数据
|
||||
device_joints: Dict[str, Dict[str, float]] = {}
|
||||
for name, pos in zip(names, positions):
|
||||
matched_device = None
|
||||
for device_id in self._device_ids_sorted:
|
||||
if name.startswith(device_id + "_"):
|
||||
matched_device = device_id
|
||||
break
|
||||
if matched_device:
|
||||
if matched_device not in device_joints:
|
||||
device_joints[matched_device] = {}
|
||||
device_joints[matched_device][name] = float(pos)
|
||||
elif len(self.device_uuid_map) == 1:
|
||||
fallback_id = self._device_ids_sorted[0]
|
||||
if fallback_id not in device_joints:
|
||||
device_joints[fallback_id] = {}
|
||||
device_joints[fallback_id][name] = float(pos)
|
||||
|
||||
# 构建设备级 resource_poses(仅 dirty 时附带)
|
||||
device_resource_poses: Dict[str, Dict[str, str]] = {}
|
||||
if resource_dirty:
|
||||
for resource_id, link_name in self._resource_poses.items():
|
||||
matched_device = None
|
||||
for device_id in self._device_ids_sorted:
|
||||
if link_name.startswith(device_id + "_"):
|
||||
matched_device = device_id
|
||||
break
|
||||
if matched_device:
|
||||
if matched_device not in device_resource_poses:
|
||||
device_resource_poses[matched_device] = {}
|
||||
device_resource_poses[matched_device][resource_id] = link_name
|
||||
elif len(self.device_uuid_map) == 1:
|
||||
fallback_id = self._device_ids_sorted[0]
|
||||
if fallback_id not in device_resource_poses:
|
||||
device_resource_poses[fallback_id] = {}
|
||||
device_resource_poses[fallback_id][resource_id] = link_name
|
||||
self._resource_poses_dirty = False
|
||||
|
||||
result = {}
|
||||
for device_id, joint_states in device_joints.items():
|
||||
node_uuid = self.device_uuid_map.get(device_id)
|
||||
if not node_uuid:
|
||||
continue
|
||||
result[device_id] = {
|
||||
"node_uuid": node_uuid,
|
||||
"joint_states": joint_states,
|
||||
"resource_poses": device_resource_poses.get(device_id, {}),
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
# 功能测试中禁用优化(dead_band=0, min_interval=0),确保逻辑正确性
|
||||
def _make_sim(device_uuid_map: Dict[str, str]) -> JointBridgeSimulator:
|
||||
"""创建禁用吞吐优化的模拟器(用于功能正确性测试)"""
|
||||
return JointBridgeSimulator(device_uuid_map, dead_band=0.0, min_interval=0.0)
|
||||
|
||||
|
||||
# ==================== E1: JointRepublisher JSON 输出 ====================
|
||||
|
||||
|
||||
class TestJointRepublisherFormat:
|
||||
"""验证 JointRepublisher 输出标准 JSON(双引号)而非 Python repr(单引号)"""
|
||||
|
||||
def test_output_is_valid_json(self):
|
||||
"""str() 产生单引号,json.dumps() 产生双引号"""
|
||||
joint_dict = {
|
||||
"name": ["joint1", "joint2"],
|
||||
"position": [0.1, 0.2],
|
||||
"velocity": [0.0, 0.0],
|
||||
"effort": [0.0, 0.0],
|
||||
}
|
||||
result = json.dumps(joint_dict)
|
||||
parsed = json.loads(result)
|
||||
assert parsed["name"] == ["joint1", "joint2"]
|
||||
assert parsed["position"] == [0.1, 0.2]
|
||||
assert "'" not in result
|
||||
|
||||
def test_str_produces_invalid_json(self):
|
||||
"""对比: str() 不是合法 JSON"""
|
||||
joint_dict = {"name": ["joint1"], "position": [0.1]}
|
||||
result = str(joint_dict)
|
||||
with pytest.raises(json.JSONDecodeError):
|
||||
json.loads(result)
|
||||
|
||||
|
||||
# ==================== E2: 关节状态回调(JointState msg 直接读取)====================
|
||||
|
||||
|
||||
class TestJointStateCallback:
|
||||
"""测试从 JointState msg 直接读取 name/position 的分组逻辑"""
|
||||
|
||||
def test_single_device_simple(self):
|
||||
"""单设备,关节名有设备前缀"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
msg = _make_joint_state_msg(
|
||||
["panda_joint1", "panda_joint2"], [0.5, 1.0]
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert "panda" in result
|
||||
assert result["panda"]["joint_states"]["panda_joint1"] == 0.5
|
||||
assert result["panda"]["joint_states"]["panda_joint2"] == 1.0
|
||||
|
||||
def test_single_device_no_prefix_fallback(self):
|
||||
"""单设备,关节名无设备前缀 → 应归入唯一设备"""
|
||||
sim = _make_sim({"robot1": "uuid-r1"})
|
||||
msg = _make_joint_state_msg(["joint_a", "joint_b"], [1.0, 2.0])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert "robot1" in result
|
||||
assert result["robot1"]["joint_states"]["joint_a"] == 1.0
|
||||
assert result["robot1"]["joint_states"]["joint_b"] == 2.0
|
||||
|
||||
def test_multi_device_distinct_prefixes(self):
|
||||
"""多设备,不同前缀,正确分组"""
|
||||
sim = _make_sim({"arm1": "uuid-arm1", "arm2": "uuid-arm2"})
|
||||
msg = _make_joint_state_msg(
|
||||
["arm1_j1", "arm1_j2", "arm2_j1", "arm2_j2"],
|
||||
[0.1, 0.2, 0.3, 0.4],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["arm1"]["joint_states"]["arm1_j1"] == 0.1
|
||||
assert result["arm1"]["joint_states"]["arm1_j2"] == 0.2
|
||||
assert result["arm2"]["joint_states"]["arm2_j1"] == 0.3
|
||||
assert result["arm2"]["joint_states"]["arm2_j2"] == 0.4
|
||||
|
||||
def test_ambiguous_prefix_longest_wins(self):
|
||||
"""前缀歧义: arm 和 arm_left — 最长前缀优先"""
|
||||
sim = _make_sim({"arm": "uuid-arm", "arm_left": "uuid-arm-left"})
|
||||
msg = _make_joint_state_msg(
|
||||
["arm_j1", "arm_left_j1", "arm_left_j2"],
|
||||
[0.1, 0.2, 0.3],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["arm"]["joint_states"]["arm_j1"] == 0.1
|
||||
assert result["arm_left"]["joint_states"]["arm_left_j1"] == 0.2
|
||||
assert result["arm_left"]["joint_states"]["arm_left_j2"] == 0.3
|
||||
|
||||
def test_multi_device_unmatched_joints_dropped(self):
|
||||
"""多设备时,无法匹配前缀的关节应被丢弃(不 fallback)"""
|
||||
sim = _make_sim({"arm1": "uuid-arm1", "arm2": "uuid-arm2"})
|
||||
msg = _make_joint_state_msg(
|
||||
["arm1_j1", "unknown_j1"],
|
||||
[0.1, 0.9],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["arm1"]["joint_states"]["arm1_j1"] == 0.1
|
||||
for device_id, data in result.items():
|
||||
assert "unknown_j1" not in data["joint_states"]
|
||||
|
||||
def test_empty_names(self):
|
||||
"""空 name 列表"""
|
||||
sim = _make_sim({"dev": "uuid-dev"})
|
||||
msg = _make_joint_state_msg([], [])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result == {}
|
||||
|
||||
def test_mismatched_lengths(self):
|
||||
"""name 和 position 长度不一致"""
|
||||
sim = _make_sim({"dev": "uuid-dev"})
|
||||
msg = _make_joint_state_msg(["j1", "j2"], [0.1])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result == {}
|
||||
|
||||
def test_no_devices(self):
|
||||
"""无设备 UUID 映射"""
|
||||
sim = _make_sim({})
|
||||
msg = _make_joint_state_msg(["j1"], [0.1])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result == {}
|
||||
|
||||
def test_numeric_prefix_device_ids(self):
|
||||
"""数字化设备 ID (如 deck1, deck12) — deck12_slot1 不应匹配 deck1"""
|
||||
sim = _make_sim({"deck1": "uuid-d1", "deck12": "uuid-d12"})
|
||||
msg = _make_joint_state_msg(
|
||||
["deck1_slot1", "deck12_slot1"],
|
||||
[1.0, 2.0],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["deck1"]["joint_states"]["deck1_slot1"] == 1.0
|
||||
assert result["deck12"]["joint_states"]["deck12_slot1"] == 2.0
|
||||
|
||||
def test_position_float_conversion(self):
|
||||
"""position 值应强制转为 float(即使输入为 int)"""
|
||||
sim = _make_sim({"arm": "uuid-arm"})
|
||||
msg = _make_joint_state_msg(["arm_j1"], [1])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["arm"]["joint_states"]["arm_j1"] == 1.0
|
||||
assert isinstance(result["arm"]["joint_states"]["arm_j1"], float)
|
||||
|
||||
def test_node_uuid_in_result(self):
|
||||
"""结果中应携带正确的 node_uuid"""
|
||||
sim = _make_sim({"panda": "uuid-panda-123"})
|
||||
msg = _make_joint_state_msg(["panda_j1"], [0.5])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["panda"]["node_uuid"] == "uuid-panda-123"
|
||||
|
||||
def test_device_with_no_uuid_skipped(self):
|
||||
"""device_uuid_map 中存在映射但值为空 → 跳过"""
|
||||
sim = _make_sim({"arm": ""})
|
||||
msg = _make_joint_state_msg(["arm_j1"], [0.5])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result == {}
|
||||
|
||||
def test_many_joints_single_device(self):
|
||||
"""单设备大量关节(如 7-DOF arm)"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
names = [f"panda_joint{i}" for i in range(1, 8)]
|
||||
positions = [float(i) * 0.1 for i in range(1, 8)]
|
||||
msg = _make_joint_state_msg(names, positions)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert len(result["panda"]["joint_states"]) == 7
|
||||
assert result["panda"]["joint_states"]["panda_joint7"] == pytest.approx(0.7)
|
||||
|
||||
def test_duplicate_joint_names_last_wins(self):
|
||||
"""同类型设备多个实例时,如果关节名完全重复(bug 场景),后出现的值覆盖前者"""
|
||||
sim = _make_sim({"dev": "uuid-dev"})
|
||||
msg = _make_joint_state_msg(["dev_j1", "dev_j1"], [1.0, 2.0])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["dev"]["joint_states"]["dev_j1"] == 2.0
|
||||
|
||||
def test_negative_positions(self):
|
||||
"""关节角度为负数"""
|
||||
sim = _make_sim({"arm": "uuid-arm"})
|
||||
msg = _make_joint_state_msg(["arm_j1", "arm_j2"], [-1.57, -3.14])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["arm"]["joint_states"]["arm_j1"] == pytest.approx(-1.57)
|
||||
assert result["arm"]["joint_states"]["arm_j2"] == pytest.approx(-3.14)
|
||||
|
||||
|
||||
# ==================== E3: 资源跟随 (resource_pose) ====================
|
||||
|
||||
|
||||
class TestResourcePoseCallback:
|
||||
"""测试 resource_pose 回调 — 夹爪抓取/释放/多资源"""
|
||||
|
||||
def test_single_resource_attach(self):
|
||||
"""单个资源挂载到夹爪 link"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
msg = _make_string_msg(json.dumps({"plate_1": "panda_gripper_link"}))
|
||||
sim.resource_pose_callback(msg)
|
||||
assert sim._resource_poses == {"plate_1": "panda_gripper_link"}
|
||||
assert sim._resource_poses_dirty is True
|
||||
|
||||
def test_multiple_resource_attach(self):
|
||||
"""多个资源同时挂载到不同 link"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
msg = _make_string_msg(json.dumps({
|
||||
"plate_1": "panda_gripper_link",
|
||||
"tip_rack": "panda_deck_link",
|
||||
}))
|
||||
sim.resource_pose_callback(msg)
|
||||
assert sim._resource_poses["plate_1"] == "panda_gripper_link"
|
||||
assert sim._resource_poses["tip_rack"] == "panda_deck_link"
|
||||
|
||||
def test_incremental_update(self):
|
||||
"""增量更新:新消息合并到已有状态"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "panda_deck_link"})))
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_2": "panda_gripper_link"})))
|
||||
assert len(sim._resource_poses) == 2
|
||||
assert sim._resource_poses["plate_1"] == "panda_deck_link"
|
||||
assert sim._resource_poses["plate_2"] == "panda_gripper_link"
|
||||
|
||||
def test_resource_reattach(self):
|
||||
"""资源从 deck 移动到 gripper(抓取操作)"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "panda_deck_link"})))
|
||||
assert sim._resource_poses["plate_1"] == "panda_deck_link"
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "panda_gripper_link"})))
|
||||
assert sim._resource_poses["plate_1"] == "panda_gripper_link"
|
||||
|
||||
def test_resource_release_back_to_world(self):
|
||||
"""释放资源回到 world"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "panda_gripper_link"})))
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "world"})))
|
||||
assert sim._resource_poses["plate_1"] == "world"
|
||||
|
||||
def test_empty_dict_heartbeat_no_dirty(self):
|
||||
"""空 dict(心跳包)不标记 dirty"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "panda_link"})))
|
||||
sim._resource_poses_dirty = False # 重置
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({})))
|
||||
assert sim._resource_poses_dirty is False # 空 dict 不应标记 dirty
|
||||
|
||||
def test_same_value_no_dirty(self):
|
||||
"""重复发送相同值不应标记 dirty"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "panda_link"})))
|
||||
sim._resource_poses_dirty = False
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "panda_link"})))
|
||||
assert sim._resource_poses_dirty is False
|
||||
|
||||
def test_invalid_json_ignored(self):
|
||||
"""非法 JSON 消息不影响状态"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate_1": "panda_link"})))
|
||||
sim.resource_pose_callback(_make_string_msg("not valid json {{{"))
|
||||
assert sim._resource_poses["plate_1"] == "panda_link"
|
||||
|
||||
def test_non_dict_json_ignored(self):
|
||||
"""JSON 但不是 dict(如 list)应被忽略"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps(["not", "a", "dict"])))
|
||||
assert sim._resource_poses == {}
|
||||
|
||||
def test_python_repr_ignored(self):
|
||||
"""Python repr 格式(单引号)应被忽略"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg("{'plate_1': 'panda_link'}"))
|
||||
assert sim._resource_poses == {}
|
||||
|
||||
def test_multi_device_resource_attach(self):
|
||||
"""多设备场景:不同设备的 link 挂载不同资源"""
|
||||
sim = _make_sim({"arm1": "uuid-arm1", "arm2": "uuid-arm2"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_A": "arm1_gripper_link",
|
||||
"plate_B": "arm2_gripper_link",
|
||||
})))
|
||||
assert sim._resource_poses["plate_A"] == "arm1_gripper_link"
|
||||
assert sim._resource_poses["plate_B"] == "arm2_gripper_link"
|
||||
|
||||
|
||||
# ==================== E4: 联合流程 — 关节 + 资源一并通过 bridge ====================
|
||||
|
||||
|
||||
class TestJointWithResourcePoses:
|
||||
"""测试关节状态回调时,resource_poses 被正确按设备分组并包含在结果中"""
|
||||
|
||||
def test_single_device_joint_with_resource(self):
|
||||
"""单设备:关节更新时携带已挂载的资源"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_1": "panda_gripper_link",
|
||||
})))
|
||||
msg = _make_joint_state_msg(["panda_j1", "panda_j2"], [0.5, 1.0])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["panda"]["resource_poses"] == {"plate_1": "panda_gripper_link"}
|
||||
|
||||
def test_single_device_no_resource(self):
|
||||
"""单设备:无资源挂载时 resource_poses 为空 dict"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
msg = _make_joint_state_msg(["panda_j1"], [0.5])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["panda"]["resource_poses"] == {}
|
||||
|
||||
def test_multi_device_resource_routing(self):
|
||||
"""多设备:资源按 link 前缀路由到正确设备"""
|
||||
sim = _make_sim({"arm1": "uuid-arm1", "arm2": "uuid-arm2"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_A": "arm1_gripper_link",
|
||||
"plate_B": "arm2_gripper_link",
|
||||
"tube_1": "arm1_tool_link",
|
||||
})))
|
||||
msg = _make_joint_state_msg(
|
||||
["arm1_j1", "arm2_j1"],
|
||||
[0.1, 0.2],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["arm1"]["resource_poses"] == {
|
||||
"plate_A": "arm1_gripper_link",
|
||||
"tube_1": "arm1_tool_link",
|
||||
}
|
||||
assert result["arm2"]["resource_poses"] == {"plate_B": "arm2_gripper_link"}
|
||||
|
||||
def test_resource_on_world_frame_not_routed(self):
|
||||
"""资源挂在 world frame(已释放)— 多设备时无法匹配任何设备前缀"""
|
||||
sim = _make_sim({"arm1": "uuid-arm1", "arm2": "uuid-arm2"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_A": "world",
|
||||
})))
|
||||
msg = _make_joint_state_msg(["arm1_j1"], [0.1])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["arm1"]["resource_poses"] == {}
|
||||
|
||||
def test_resource_world_frame_single_device_fallback(self):
|
||||
"""单设备时 world frame 的资源走 fallback"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_A": "world",
|
||||
})))
|
||||
msg = _make_joint_state_msg(["panda_j1"], [0.1])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["panda"]["resource_poses"] == {"plate_A": "world"}
|
||||
|
||||
def test_grab_and_move_sequence(self):
|
||||
"""完整夹取序列: 资源在 deck → gripper 抓取 → arm 移动 → 放下"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
|
||||
# 初始: plate 在 deck
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_1": "panda_deck_third_link",
|
||||
})))
|
||||
|
||||
msg = _make_joint_state_msg(
|
||||
["panda_j1", "panda_j2", "panda_j3"],
|
||||
[0.0, -0.5, 1.0],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["panda"]["resource_poses"]["plate_1"] == "panda_deck_third_link"
|
||||
|
||||
# 抓取: plate 从 deck → gripper
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_1": "panda_gripper_link",
|
||||
})))
|
||||
|
||||
msg = _make_joint_state_msg(
|
||||
["panda_j1", "panda_j2", "panda_j3"],
|
||||
[1.57, 0.0, -0.5],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["panda"]["resource_poses"]["plate_1"] == "panda_gripper_link"
|
||||
assert result["panda"]["joint_states"]["panda_j1"] == pytest.approx(1.57)
|
||||
|
||||
# 放下: plate 从 gripper → 目标 deck
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_1": "panda_deck_first_link",
|
||||
})))
|
||||
|
||||
msg = _make_joint_state_msg(
|
||||
["panda_j1", "panda_j2", "panda_j3"],
|
||||
[0.0, 0.0, 0.0],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["panda"]["resource_poses"]["plate_1"] == "panda_deck_first_link"
|
||||
|
||||
def test_simultaneous_grab_multiple_resources(self):
|
||||
"""同时持有多个资源(如双夹爪)"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_1": "panda_left_gripper",
|
||||
"plate_2": "panda_right_gripper",
|
||||
"tip_rack": "panda_deck_link",
|
||||
})))
|
||||
msg = _make_joint_state_msg(["panda_j1"], [0.5])
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert len(result["panda"]["resource_poses"]) == 3
|
||||
|
||||
def test_resource_with_ambiguous_link_prefix(self):
|
||||
"""link 前缀歧义: arm_left_gripper 应匹配 arm_left 而非 arm"""
|
||||
sim = _make_sim({"arm": "uuid-arm", "arm_left": "uuid-arm-left"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_A": "arm_gripper_link",
|
||||
"plate_B": "arm_left_gripper_link",
|
||||
})))
|
||||
msg = _make_joint_state_msg(
|
||||
["arm_j1", "arm_left_j1"],
|
||||
[0.1, 0.2],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["arm"]["resource_poses"] == {"plate_A": "arm_gripper_link"}
|
||||
assert result["arm_left"]["resource_poses"] == {"plate_B": "arm_left_gripper_link"}
|
||||
|
||||
|
||||
# ==================== E5: Bridge 调用验证 ====================
|
||||
|
||||
|
||||
class TestBridgeCalls:
|
||||
"""验证完整桥接流: callback → bridge.publish_joint_state 调用"""
|
||||
|
||||
def test_bridge_called_per_device(self):
|
||||
"""每个设备调用一次 publish_joint_state"""
|
||||
device_uuid_map = {"arm1": "uuid-111", "arm2": "uuid-222"}
|
||||
sim = _make_sim(device_uuid_map)
|
||||
bridge = MagicMock()
|
||||
bridge.publish_joint_state = MagicMock()
|
||||
|
||||
msg = _make_joint_state_msg(
|
||||
["arm1_j1", "arm2_j1"],
|
||||
[1.0, 2.0],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
|
||||
for device_id, data in result.items():
|
||||
bridge.publish_joint_state(
|
||||
data["node_uuid"], data["joint_states"], data["resource_poses"]
|
||||
)
|
||||
|
||||
assert bridge.publish_joint_state.call_count == 2
|
||||
call_uuids = {c[0][0] for c in bridge.publish_joint_state.call_args_list}
|
||||
assert call_uuids == {"uuid-111", "uuid-222"}
|
||||
|
||||
def test_bridge_called_with_resource_poses(self):
|
||||
"""bridge 调用时携带 resource_poses"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_1": "panda_gripper_link",
|
||||
})))
|
||||
|
||||
bridge = MagicMock()
|
||||
msg = _make_joint_state_msg(["panda_j1"], [0.5])
|
||||
result = sim.joint_state_callback(msg)
|
||||
|
||||
for device_id, data in result.items():
|
||||
bridge.publish_joint_state(
|
||||
data["node_uuid"], data["joint_states"], data["resource_poses"]
|
||||
)
|
||||
|
||||
bridge.publish_joint_state.assert_called_once_with(
|
||||
"uuid-panda",
|
||||
{"panda_j1": 0.5},
|
||||
{"plate_1": "panda_gripper_link"},
|
||||
)
|
||||
|
||||
def test_bridge_no_call_for_empty_joints(self):
|
||||
"""无关节数据时不调用 bridge"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
bridge = MagicMock()
|
||||
|
||||
msg = _make_joint_state_msg([], [])
|
||||
result = sim.joint_state_callback(msg)
|
||||
|
||||
for device_id, data in result.items():
|
||||
bridge.publish_joint_state(
|
||||
data["node_uuid"], data["joint_states"], data["resource_poses"]
|
||||
)
|
||||
|
||||
bridge.publish_joint_state.assert_not_called()
|
||||
|
||||
def test_bridge_resource_poses_empty_when_no_resources(self):
|
||||
"""无资源挂载时,resource_poses 参数为空 dict"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
bridge = MagicMock()
|
||||
|
||||
msg = _make_joint_state_msg(["panda_j1"], [0.5])
|
||||
result = sim.joint_state_callback(msg)
|
||||
|
||||
for device_id, data in result.items():
|
||||
bridge.publish_joint_state(
|
||||
data["node_uuid"], data["joint_states"], data["resource_poses"]
|
||||
)
|
||||
|
||||
bridge.publish_joint_state.assert_called_once_with(
|
||||
"uuid-panda",
|
||||
{"panda_j1": 0.5},
|
||||
{},
|
||||
)
|
||||
|
||||
def test_multi_bridge_all_called(self):
|
||||
"""多个 bridge 都应被调用"""
|
||||
sim = _make_sim({"arm": "uuid-arm"})
|
||||
bridges = [MagicMock(), MagicMock()]
|
||||
|
||||
msg = _make_joint_state_msg(["arm_j1"], [0.5])
|
||||
result = sim.joint_state_callback(msg)
|
||||
|
||||
for device_id, data in result.items():
|
||||
for bridge in bridges:
|
||||
bridge.publish_joint_state(
|
||||
data["node_uuid"], data["joint_states"], data["resource_poses"]
|
||||
)
|
||||
|
||||
for bridge in bridges:
|
||||
bridge.publish_joint_state.assert_called_once()
|
||||
|
||||
|
||||
# ==================== E6: 同类型设备多个实例 — 重复关节名场景 ====================
|
||||
|
||||
|
||||
class TestDuplicateDeviceTypes:
|
||||
"""
|
||||
多个同类型设备(如 2 个 OT-2 移液器),关节名格式为 {device_id}_{joint_name}。
|
||||
设备 ID 不同(如 ot2_left, ot2_right),但底层关节名相同(如 pipette_j1)。
|
||||
"""
|
||||
|
||||
def test_same_type_different_id(self):
|
||||
"""同类型设备不同 ID"""
|
||||
sim = _make_sim({
|
||||
"ot2_left": "uuid-ot2-left",
|
||||
"ot2_right": "uuid-ot2-right",
|
||||
})
|
||||
msg = _make_joint_state_msg(
|
||||
["ot2_left_pipette_j1", "ot2_left_pipette_j2",
|
||||
"ot2_right_pipette_j1", "ot2_right_pipette_j2"],
|
||||
[0.1, 0.2, 0.3, 0.4],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["ot2_left"]["joint_states"]["ot2_left_pipette_j1"] == 0.1
|
||||
assert result["ot2_left"]["joint_states"]["ot2_left_pipette_j2"] == 0.2
|
||||
assert result["ot2_right"]["joint_states"]["ot2_right_pipette_j1"] == 0.3
|
||||
assert result["ot2_right"]["joint_states"]["ot2_right_pipette_j2"] == 0.4
|
||||
|
||||
def test_same_type_with_resources_routed_correctly(self):
|
||||
"""同类型设备各自抓取资源,按 link 前缀正确路由"""
|
||||
sim = _make_sim({
|
||||
"ot2_left": "uuid-ot2-left",
|
||||
"ot2_right": "uuid-ot2-right",
|
||||
})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({
|
||||
"plate_A": "ot2_left_gripper",
|
||||
"plate_B": "ot2_right_gripper",
|
||||
})))
|
||||
msg = _make_joint_state_msg(
|
||||
["ot2_left_j1", "ot2_right_j1"],
|
||||
[0.5, 0.6],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["ot2_left"]["resource_poses"] == {"plate_A": "ot2_left_gripper"}
|
||||
assert result["ot2_right"]["resource_poses"] == {"plate_B": "ot2_right_gripper"}
|
||||
|
||||
def test_numbered_devices_no_confusion(self):
|
||||
"""编号设备: robot1 不应匹配 robot10 的关节"""
|
||||
sim = _make_sim({
|
||||
"robot1": "uuid-r1",
|
||||
"robot10": "uuid-r10",
|
||||
})
|
||||
msg = _make_joint_state_msg(
|
||||
["robot1_j1", "robot10_j1"],
|
||||
[1.0, 10.0],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert result["robot1"]["joint_states"]["robot1_j1"] == 1.0
|
||||
assert result["robot10"]["joint_states"]["robot10_j1"] == 10.0
|
||||
|
||||
def test_three_same_type_devices(self):
|
||||
"""三个同类型设备"""
|
||||
sim = _make_sim({
|
||||
"pump_a": "uuid-pa",
|
||||
"pump_b": "uuid-pb",
|
||||
"pump_c": "uuid-pc",
|
||||
})
|
||||
msg = _make_joint_state_msg(
|
||||
["pump_a_flow", "pump_b_flow", "pump_c_flow",
|
||||
"pump_a_pressure", "pump_b_pressure"],
|
||||
[1.0, 2.0, 3.0, 0.1, 0.2],
|
||||
)
|
||||
result = sim.joint_state_callback(msg)
|
||||
assert len(result["pump_a"]["joint_states"]) == 2
|
||||
assert len(result["pump_b"]["joint_states"]) == 2
|
||||
assert len(result["pump_c"]["joint_states"]) == 1
|
||||
|
||||
|
||||
# ==================== E7: 吞吐优化测试 ====================
|
||||
|
||||
|
||||
class TestThroughputOptimizations:
|
||||
"""测试死区过滤、抑频、增量 resource_poses 等优化行为"""
|
||||
|
||||
# --- 死区过滤 (Dead Band) ---
|
||||
|
||||
def test_dead_band_filters_tiny_change(self):
|
||||
"""关节变化小于死区阈值 → 被过滤"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.01, min_interval=0.0)
|
||||
msg1 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
result1 = sim.joint_state_callback(msg1, now=0.0)
|
||||
assert "arm" in result1
|
||||
|
||||
# 微小变化 (0.001 < 0.01 死区)
|
||||
msg2 = _make_joint_state_msg(["arm_j1"], [1.001])
|
||||
result2 = sim.joint_state_callback(msg2, now=1.0)
|
||||
assert result2 == {}
|
||||
|
||||
def test_dead_band_passes_significant_change(self):
|
||||
"""关节变化大于死区阈值 → 通过"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.01, min_interval=0.0)
|
||||
msg1 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
sim.joint_state_callback(msg1, now=0.0)
|
||||
|
||||
msg2 = _make_joint_state_msg(["arm_j1"], [1.05])
|
||||
result2 = sim.joint_state_callback(msg2, now=1.0)
|
||||
assert "arm" in result2
|
||||
assert result2["arm"]["joint_states"]["arm_j1"] == pytest.approx(1.05)
|
||||
|
||||
def test_dead_band_first_message_always_passes(self):
|
||||
"""首次消息总是通过(无历史值)"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=1000.0, min_interval=0.0)
|
||||
msg = _make_joint_state_msg(["arm_j1"], [0.001])
|
||||
result = sim.joint_state_callback(msg, now=0.0)
|
||||
assert "arm" in result
|
||||
|
||||
def test_dead_band_any_joint_change_triggers(self):
|
||||
"""多关节中只要有一个超过死区就全部发送"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.01, min_interval=0.0)
|
||||
msg1 = _make_joint_state_msg(["arm_j1", "arm_j2"], [1.0, 2.0])
|
||||
sim.joint_state_callback(msg1, now=0.0)
|
||||
|
||||
# j1 微变化,j2 大变化
|
||||
msg2 = _make_joint_state_msg(["arm_j1", "arm_j2"], [1.001, 2.5])
|
||||
result2 = sim.joint_state_callback(msg2, now=1.0)
|
||||
assert "arm" in result2
|
||||
# 两个关节的值都应包含在结果中
|
||||
assert result2["arm"]["joint_states"]["arm_j1"] == pytest.approx(1.001)
|
||||
assert result2["arm"]["joint_states"]["arm_j2"] == pytest.approx(2.5)
|
||||
|
||||
# --- 抑频 (Throttle) ---
|
||||
|
||||
def test_throttle_filters_rapid_messages(self):
|
||||
"""发送间隔内的消息被过滤"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.0, min_interval=0.1)
|
||||
msg1 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
result1 = sim.joint_state_callback(msg1, now=0.0)
|
||||
assert "arm" in result1
|
||||
|
||||
# 0.05s < 0.1s 间隔
|
||||
msg2 = _make_joint_state_msg(["arm_j1"], [2.0])
|
||||
result2 = sim.joint_state_callback(msg2, now=0.05)
|
||||
assert result2 == {}
|
||||
|
||||
def test_throttle_passes_after_interval(self):
|
||||
"""超过发送间隔后消息通过"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.0, min_interval=0.1)
|
||||
msg1 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
sim.joint_state_callback(msg1, now=0.0)
|
||||
|
||||
msg2 = _make_joint_state_msg(["arm_j1"], [2.0])
|
||||
result2 = sim.joint_state_callback(msg2, now=0.15)
|
||||
assert "arm" in result2
|
||||
|
||||
def test_throttle_bypassed_by_resource_change(self):
|
||||
"""resource_pose 变化时忽略抑频限制"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.0, min_interval=1.0)
|
||||
msg1 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
sim.joint_state_callback(msg1, now=0.0)
|
||||
|
||||
# 资源变化 → 强制发送
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate": "arm_gripper"})))
|
||||
msg2 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
result2 = sim.joint_state_callback(msg2, now=0.01) # 远小于 1.0 间隔
|
||||
assert "arm" in result2
|
||||
assert result2["arm"]["resource_poses"] == {"plate": "arm_gripper"}
|
||||
|
||||
# --- 增量 resource_poses ---
|
||||
|
||||
def test_resource_poses_only_sent_when_dirty(self):
|
||||
"""resource_poses 仅在 dirty 时附带,否则为空"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate": "panda_gripper"})))
|
||||
|
||||
# 第一次发送:dirty → 携带 resource_poses
|
||||
msg1 = _make_joint_state_msg(["panda_j1"], [0.5])
|
||||
result1 = sim.joint_state_callback(msg1)
|
||||
assert result1["panda"]["resource_poses"] == {"plate": "panda_gripper"}
|
||||
|
||||
# dirty 已清除
|
||||
assert sim._resource_poses_dirty is False
|
||||
|
||||
# 第二次发送:not dirty → resource_poses 为空
|
||||
msg2 = _make_joint_state_msg(["panda_j1"], [1.0])
|
||||
result2 = sim.joint_state_callback(msg2)
|
||||
assert result2["panda"]["resource_poses"] == {}
|
||||
|
||||
def test_resource_change_resets_dirty_after_send(self):
|
||||
"""dirty 在发送后被重置,再次 resource_pose 变化后重新标记"""
|
||||
sim = _make_sim({"panda": "uuid-panda"})
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate": "panda_deck"})))
|
||||
|
||||
msg = _make_joint_state_msg(["panda_j1"], [0.5])
|
||||
sim.joint_state_callback(msg)
|
||||
assert sim._resource_poses_dirty is False
|
||||
|
||||
# 再次资源变化
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate": "panda_gripper"})))
|
||||
assert sim._resource_poses_dirty is True
|
||||
|
||||
msg2 = _make_joint_state_msg(["panda_j1"], [1.0])
|
||||
result2 = sim.joint_state_callback(msg2)
|
||||
assert result2["panda"]["resource_poses"] == {"plate": "panda_gripper"}
|
||||
|
||||
# --- 组合场景 ---
|
||||
|
||||
def test_dead_band_bypassed_by_resource_dirty(self):
|
||||
"""关节无变化但 resource_pose 有变化 → 仍然发送"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.01, min_interval=0.0)
|
||||
msg1 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
sim.joint_state_callback(msg1, now=0.0)
|
||||
|
||||
sim.resource_pose_callback(_make_string_msg(json.dumps({"plate": "arm_gripper"})))
|
||||
# 关节值完全不变
|
||||
msg2 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
result2 = sim.joint_state_callback(msg2, now=1.0)
|
||||
assert "arm" in result2
|
||||
assert result2["arm"]["resource_poses"] == {"plate": "arm_gripper"}
|
||||
|
||||
def test_high_frequency_stream_only_significant_pass(self):
|
||||
"""模拟高频流: 只有显著变化的消息通过"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.01, min_interval=0.0)
|
||||
t = 0.0
|
||||
passed_count = 0
|
||||
|
||||
# 100 条消息,每条微小递增 0.001
|
||||
for i in range(100):
|
||||
t += 0.1
|
||||
val = 1.0 + i * 0.001
|
||||
msg = _make_joint_state_msg(["arm_j1"], [val])
|
||||
result = sim.joint_state_callback(msg, now=t)
|
||||
if result:
|
||||
passed_count += 1
|
||||
|
||||
# 首次总通过 + 每 10 条左右(累计 0.01 变化)通过一次
|
||||
assert passed_count < 20 # 远少于 100
|
||||
assert passed_count >= 5 # 但不应为 0
|
||||
|
||||
def test_throttle_and_dead_band_combined(self):
|
||||
"""同时受抑频和死区影响"""
|
||||
sim = JointBridgeSimulator({"arm": "uuid-arm"}, dead_band=0.01, min_interval=0.5)
|
||||
|
||||
# 首条通过
|
||||
msg1 = _make_joint_state_msg(["arm_j1"], [1.0])
|
||||
assert sim.joint_state_callback(msg1, now=0.0) != {}
|
||||
|
||||
# 时间不够 + 变化不够 → 过滤
|
||||
msg2 = _make_joint_state_msg(["arm_j1"], [1.001])
|
||||
assert sim.joint_state_callback(msg2, now=0.1) == {}
|
||||
|
||||
# 时间够但变化不够 → 过滤
|
||||
msg3 = _make_joint_state_msg(["arm_j1"], [1.002])
|
||||
assert sim.joint_state_callback(msg3, now=1.0) == {}
|
||||
|
||||
# 时间够且变化够 → 通过
|
||||
msg4 = _make_joint_state_msg(["arm_j1"], [1.05])
|
||||
assert sim.joint_state_callback(msg4, now=1.5) != {}
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.10.18"
|
||||
__version__ = "0.10.19"
|
||||
|
||||
@@ -50,6 +50,17 @@ class BaseCommunicationClient(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
def publish_joint_state(self, node_uuid: str, joint_states: dict, resource_poses: dict = None) -> None:
|
||||
"""
|
||||
发布高频关节状态数据(push_joint_state action,不写 DB)
|
||||
|
||||
Args:
|
||||
node_uuid: 设备节点的云端 UUID
|
||||
joint_states: 关节名 → 角度/位置 的映射
|
||||
resource_poses: 物料附着映射(可选)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def publish_job_status(
|
||||
self, feedback_data: dict, job_id: str, status: str, return_info: Optional[dict] = None
|
||||
|
||||
@@ -247,6 +247,12 @@ def parse_args():
|
||||
default=False,
|
||||
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(
|
||||
"--no_update_feedback",
|
||||
action="store_true",
|
||||
@@ -258,6 +264,12 @@ def parse_args():
|
||||
default=False,
|
||||
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",
|
||||
@@ -336,11 +348,18 @@ def main():
|
||||
check_mode = args_dict.get("check_mode", False)
|
||||
|
||||
if not skip_env_check:
|
||||
from unilabos.utils.environment_check import check_environment
|
||||
from unilabos.utils.environment_check import check_environment, check_device_package_requirements
|
||||
|
||||
if not check_environment(auto_install=True):
|
||||
print_status("环境检查失败,程序退出", "error")
|
||||
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:
|
||||
print_status("跳过环境依赖检查", "warning")
|
||||
|
||||
@@ -471,19 +490,7 @@ def main():
|
||||
BasicConfig.vis_2d_enable = args_dict["2d_vis"]
|
||||
BasicConfig.check_mode = check_mode
|
||||
|
||||
from unilabos.resources.graphio import (
|
||||
read_node_link_json,
|
||||
read_graphml,
|
||||
dict_from_graph,
|
||||
)
|
||||
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.web import http_client
|
||||
from unilabos.app.web import start_server
|
||||
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
|
||||
|
||||
# 显示启动横幅
|
||||
print_unilab_banner(args_dict)
|
||||
@@ -491,11 +498,15 @@ def main():
|
||||
# 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: 注册表验证完成后直接退出
|
||||
@@ -505,6 +516,20 @@ def main():
|
||||
print_status(f"Check mode: 注册表验证完成 ({device_count} 设备, {resource_count} 资源),退出", "info")
|
||||
os._exit(0)
|
||||
|
||||
# 以下导入依赖 ROS2 环境,check_mode 已退出不需要
|
||||
from unilabos.resources.graphio import (
|
||||
read_node_link_json,
|
||||
read_graphml,
|
||||
dict_from_graph,
|
||||
modify_to_backend_format,
|
||||
)
|
||||
from unilabos.app.communication import get_communication_client
|
||||
from unilabos.app.backend import start_backend
|
||||
from unilabos.app.web import http_client
|
||||
from unilabos.app.web import start_server
|
||||
from unilabos.app.register import register_devices_and_resources
|
||||
from unilabos.resources.resource_tracker import ResourceTreeSet, ResourceDict
|
||||
|
||||
# Step 1: 上传全部注册表到服务端,同步保存到 unilabos_data
|
||||
if BasicConfig.upload_registry:
|
||||
if BasicConfig.ak and BasicConfig.sk:
|
||||
@@ -602,6 +627,10 @@ def main():
|
||||
resource_tree_set.merge_remote_resources(remote_tree_set)
|
||||
print_status("远端物料同步完成", "info")
|
||||
|
||||
# 第二次设备包依赖检查:云端物料同步后,community 包可能引入新的 requirements
|
||||
# TODO: 当 community device package 功能上线后,在这里调用
|
||||
# install_requirements_txt(community_pkg_path / "requirements.txt", label="community.xxx")
|
||||
|
||||
# 使用 ResourceTreeSet 代替 list
|
||||
args_dict["resources_config"] = resource_tree_set
|
||||
args_dict["devices_config"] = resource_tree_set
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
import json
|
||||
import time
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
from unilabos.utils.log import logger
|
||||
from unilabos.utils.type_check import TypeEncoder
|
||||
|
||||
try:
|
||||
import orjson
|
||||
|
||||
def _normalize_device(info: dict) -> dict:
|
||||
"""Serialize via orjson to strip non-JSON types (type objects etc.)."""
|
||||
return orjson.loads(orjson.dumps(info, default=str))
|
||||
except ImportError:
|
||||
def _normalize_device(info: dict) -> dict:
|
||||
return json.loads(json.dumps(info, ensure_ascii=False, cls=TypeEncoder))
|
||||
from unilabos.utils.tools import normalize_json as _normalize_device
|
||||
|
||||
|
||||
def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]:
|
||||
|
||||
@@ -8,24 +8,7 @@ import json
|
||||
import os
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
try:
|
||||
import orjson as _json_fast
|
||||
|
||||
def _fast_dumps(obj, **kwargs) -> bytes:
|
||||
return _json_fast.dumps(obj, option=_json_fast.OPT_NON_STR_KEYS, default=str)
|
||||
|
||||
def _fast_dumps_pretty(obj, **kwargs) -> bytes:
|
||||
return _json_fast.dumps(
|
||||
obj, option=_json_fast.OPT_NON_STR_KEYS | _json_fast.OPT_INDENT_2, default=str,
|
||||
)
|
||||
except ImportError:
|
||||
_json_fast = None # type: ignore[assignment]
|
||||
|
||||
def _fast_dumps(obj, **kwargs) -> bytes:
|
||||
return json.dumps(obj, ensure_ascii=False, default=str).encode("utf-8")
|
||||
|
||||
def _fast_dumps_pretty(obj, **kwargs) -> bytes:
|
||||
return json.dumps(obj, indent=2, ensure_ascii=False, default=str).encode("utf-8")
|
||||
from unilabos.utils.tools import fast_dumps as _fast_dumps, fast_dumps_pretty as _fast_dumps_pretty
|
||||
|
||||
import requests
|
||||
from unilabos.resources.resource_tracker import ResourceTreeSet
|
||||
|
||||
@@ -1434,6 +1434,21 @@ class WebSocketClient(BaseCommunicationClient):
|
||||
self.message_processor.send_message(message)
|
||||
# logger.trace(f"[WebSocketClient] Device status published: {device_id}.{property_name}")
|
||||
|
||||
def publish_joint_state(self, node_uuid: str, joint_states: dict, resource_poses: dict = None) -> None:
|
||||
"""发布高频关节状态(push_joint_state,不写 DB)"""
|
||||
if self.is_disabled or not self.is_connected():
|
||||
return
|
||||
|
||||
message = {
|
||||
"action": "push_joint_state",
|
||||
"data": {
|
||||
"node_uuid": node_uuid,
|
||||
"joint_states": joint_states or {},
|
||||
"resource_poses": resource_poses or {},
|
||||
},
|
||||
}
|
||||
self.message_processor.send_message(message)
|
||||
|
||||
def publish_job_status(
|
||||
self, feedback_data: dict, item: QueueItem, status: str, return_info: Optional[dict] = None
|
||||
) -> None:
|
||||
|
||||
88
unilabos/devices/virtual/virtual_sample_demo.py
Normal file
88
unilabos/devices/virtual/virtual_sample_demo.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""虚拟样品演示设备 — 用于前端 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, "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, "samples": samples}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 状态属性
|
||||
# ------------------------------------------------------------------
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Idle")
|
||||
@@ -139,6 +139,7 @@ def scan_directory(
|
||||
executor: ThreadPoolExecutor = None,
|
||||
exclude_files: Optional[set] = None,
|
||||
cache: Optional[Dict[str, Any]] = None,
|
||||
include_files: Optional[List[Union[str, Path]]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Recursively scan .py files under *root_dir* for @device and @resource
|
||||
@@ -164,6 +165,7 @@ def scan_directory(
|
||||
exclude_files: 要排除的文件名集合 (如 {"lab_resources.py"})
|
||||
cache: Mutable cache dict (``load_scan_cache()`` result). Hits are read
|
||||
from here; misses are written back so the caller can persist later.
|
||||
include_files: 指定扫描的文件列表,提供时跳过目录递归收集,直接扫描这些文件。
|
||||
"""
|
||||
if executor is None:
|
||||
raise ValueError("executor is required and must not be None")
|
||||
@@ -175,7 +177,10 @@ def scan_directory(
|
||||
python_path = Path(python_path).resolve()
|
||||
|
||||
# --- Collect files (depth/count limited) ---
|
||||
py_files = _collect_py_files(root_dir, max_depth=max_depth, max_files=max_files, exclude_files=exclude_files)
|
||||
if include_files is not None:
|
||||
py_files = [Path(f).resolve() for f in include_files if Path(f).resolve().exists()]
|
||||
else:
|
||||
py_files = _collect_py_files(root_dir, max_depth=max_depth, max_files=max_files, exclude_files=exclude_files)
|
||||
|
||||
cache_files: Dict[str, Any] = cache.get("files", {}) if cache else {}
|
||||
|
||||
@@ -839,24 +844,39 @@ def _extract_class_body(
|
||||
if _has_decorator(item, "not_action") and _is_registry_decorator("not_action", import_map):
|
||||
continue
|
||||
|
||||
# --- get_ 前缀且无额外参数(仅 self)→ status property ---
|
||||
if method_name.startswith("get_"):
|
||||
real_args = [a for a in item.args.args if a.arg != "self"]
|
||||
if len(real_args) == 0:
|
||||
prop_name = method_name[4:]
|
||||
return_type = _get_annotation_str(item.returns, import_map)
|
||||
if prop_name not in result["status_properties"]:
|
||||
result["status_properties"][prop_name] = {
|
||||
"name": prop_name,
|
||||
"return_type": return_type,
|
||||
"is_property": False,
|
||||
"topic_config": None,
|
||||
}
|
||||
continue
|
||||
|
||||
# --- Public method without @action => auto-action ---
|
||||
# Skip lifecycle / dunder methods that should never be auto-actions
|
||||
if method_name in ("post_init", "__str__", "__repr__"):
|
||||
continue
|
||||
|
||||
# 'close' and 'cleanup' could be actions in some drivers -- keep them
|
||||
|
||||
method_params = _extract_method_params(item, import_map)
|
||||
return_type = _get_annotation_str(item.returns, import_map)
|
||||
is_async = isinstance(item, ast.AsyncFunctionDef)
|
||||
method_doc = ast.get_docstring(item)
|
||||
|
||||
result["auto_methods"][method_name] = {
|
||||
auto_entry: dict = {
|
||||
"params": method_params,
|
||||
"return_type": return_type,
|
||||
"is_async": is_async,
|
||||
"docstring": method_doc,
|
||||
}
|
||||
if _has_decorator(item, "always_free") and _is_registry_decorator("always_free", import_map):
|
||||
auto_entry["always_free"] = True
|
||||
result["auto_methods"][method_name] = auto_entry
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -13,21 +13,18 @@ Qone_nmr:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -71,31 +68,6 @@ Qone_nmr:
|
||||
title: monitor_folder_for_new_content参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -138,21 +110,18 @@ Qone_nmr:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -167,32 +136,31 @@ Qone_nmr:
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -22,7 +22,8 @@ bioyond_cell:
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: auto_batch_outbound_from_xlsx参数
|
||||
@@ -490,7 +491,9 @@ bioyond_cell:
|
||||
goal:
|
||||
properties:
|
||||
material_names:
|
||||
type: string
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type_id:
|
||||
default: 3a190ca0-b2f6-9aeb-8067-547e72c11469
|
||||
type: string
|
||||
@@ -499,7 +502,8 @@ bioyond_cell:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: create_and_inbound_materials参数
|
||||
@@ -535,7 +539,8 @@ bioyond_cell:
|
||||
- type_id
|
||||
- warehouse_name
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: create_material参数
|
||||
@@ -556,11 +561,16 @@ bioyond_cell:
|
||||
goal:
|
||||
properties:
|
||||
mappings:
|
||||
additionalProperties:
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- mappings
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- goal
|
||||
title: create_materials参数
|
||||
@@ -592,7 +602,8 @@ bioyond_cell:
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: create_orders参数
|
||||
@@ -624,7 +635,8 @@ bioyond_cell:
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: create_orders_v2参数
|
||||
@@ -665,7 +677,8 @@ bioyond_cell:
|
||||
- bottle_type
|
||||
- location_code
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: create_sample参数
|
||||
@@ -718,7 +731,8 @@ bioyond_cell:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: order_list_v2参数
|
||||
@@ -821,7 +835,8 @@ bioyond_cell:
|
||||
required:
|
||||
- material_obj
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: report_material_change参数
|
||||
@@ -875,7 +890,8 @@ bioyond_cell:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_continue参数
|
||||
@@ -896,7 +912,8 @@ bioyond_cell:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_reset参数
|
||||
@@ -917,7 +934,8 @@ bioyond_cell:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_start参数
|
||||
@@ -1362,7 +1380,8 @@ bioyond_cell:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_start_and_auto_feeding参数
|
||||
@@ -1807,7 +1826,8 @@ bioyond_cell:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_start_and_auto_feeding_v2参数
|
||||
@@ -1828,7 +1848,8 @@ bioyond_cell:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_stop参数
|
||||
@@ -1850,12 +1871,15 @@ bioyond_cell:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- items
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: storage_batch_inbound参数
|
||||
@@ -1884,7 +1908,8 @@ bioyond_cell:
|
||||
- material_id
|
||||
- location_id
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: storage_inbound参数
|
||||
@@ -1905,7 +1930,8 @@ bioyond_cell:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: transfer_1_to_2参数
|
||||
@@ -1946,7 +1972,8 @@ bioyond_cell:
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: transfer_3_to_2参数
|
||||
@@ -1983,7 +2010,8 @@ bioyond_cell:
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: transfer_3_to_2_to_1参数
|
||||
@@ -2007,10 +2035,11 @@ bioyond_cell:
|
||||
ip:
|
||||
type: string
|
||||
port:
|
||||
type: string
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: update_push_ip参数
|
||||
@@ -2039,7 +2068,8 @@ bioyond_cell:
|
||||
required:
|
||||
- order_code
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_order_finish参数
|
||||
@@ -2072,7 +2102,8 @@ bioyond_cell:
|
||||
required:
|
||||
- order_code
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_order_finish_polling参数
|
||||
@@ -2104,7 +2135,8 @@ bioyond_cell:
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_transfer_task参数
|
||||
@@ -2112,8 +2144,7 @@ bioyond_cell:
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
|
||||
status_types:
|
||||
device_id: String
|
||||
material_info: dict
|
||||
device_id: ''
|
||||
type: python
|
||||
config_info: []
|
||||
description: ''
|
||||
@@ -2134,11 +2165,7 @@ bioyond_cell:
|
||||
properties:
|
||||
device_id:
|
||||
type: string
|
||||
material_info:
|
||||
type: object
|
||||
required:
|
||||
- device_id
|
||||
- material_info
|
||||
type: object
|
||||
registry_type: device
|
||||
version: 1.0.0
|
||||
|
||||
@@ -24,7 +24,8 @@ bioyond_dispensing_station:
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: brief_step_parameters参数
|
||||
@@ -53,7 +54,8 @@ bioyond_dispensing_station:
|
||||
- report_request
|
||||
- used_materials
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: process_order_finish_report参数
|
||||
@@ -78,7 +80,8 @@ bioyond_dispensing_station:
|
||||
required:
|
||||
- order_id
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: project_order_report参数
|
||||
@@ -128,7 +131,8 @@ bioyond_dispensing_station:
|
||||
required:
|
||||
- workflow_id
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: workflow_sample_locations参数
|
||||
@@ -144,12 +148,12 @@ bioyond_dispensing_station:
|
||||
temperature: temperature
|
||||
titration: titration
|
||||
goal_default:
|
||||
delay_time: '600'
|
||||
hold_m_name: ''
|
||||
delay_time: null
|
||||
hold_m_name: null
|
||||
liquid_material_name: NMP
|
||||
speed: '400'
|
||||
temperature: '40'
|
||||
titration: ''
|
||||
speed: null
|
||||
temperature: null
|
||||
titration: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: titration
|
||||
@@ -165,20 +169,16 @@ bioyond_dispensing_station:
|
||||
handler_key: BATCH_CREATE_RESULT
|
||||
io_type: sink
|
||||
label: Complete Batch Create Result JSON (contains order_codes and order_ids)
|
||||
result:
|
||||
return_info: return_info
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 批量创建90%10%小瓶投料任务。从计算节点接收titration数据,包含物料名称、主称固体质量、滴定固体质量和滴定溶剂体积。返回的return_info中包含order_codes和order_ids列表。
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: BatchCreate9010VialFeedingTasks_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
default: '600'
|
||||
description: 延迟时间(秒),默认600
|
||||
type: string
|
||||
hold_m_name:
|
||||
@@ -189,11 +189,9 @@ bioyond_dispensing_station:
|
||||
description: 10%物料的液体物料名称,默认为"NMP"
|
||||
type: string
|
||||
speed:
|
||||
default: '400'
|
||||
description: 搅拌速度,默认400
|
||||
type: string
|
||||
temperature:
|
||||
default: '40'
|
||||
description: 温度(℃),默认40
|
||||
type: string
|
||||
titration:
|
||||
@@ -202,21 +200,14 @@ bioyond_dispensing_station:
|
||||
type: string
|
||||
required:
|
||||
- titration
|
||||
- hold_m_name
|
||||
title: BatchCreate9010VialFeedingTasks_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 批量任务创建结果汇总JSON字符串,包含total(总数)、success(成功数)、failed(失败数)、order_codes(任务编码数组)、order_ids(任务ID数组)、details(每个任务的详细信息)
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: BatchCreate9010VialFeedingTasks_Result
|
||||
type: object
|
||||
type: string
|
||||
required:
|
||||
- goal
|
||||
title: BatchCreate9010VialFeedingTasks
|
||||
title: batch_create_90_10_vial_feeding_tasks参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
batch_create_diamine_solution_tasks:
|
||||
@@ -228,11 +219,11 @@ bioyond_dispensing_station:
|
||||
speed: speed
|
||||
temperature: temperature
|
||||
goal_default:
|
||||
delay_time: '600'
|
||||
delay_time: null
|
||||
liquid_material_name: NMP
|
||||
solutions: ''
|
||||
speed: '400'
|
||||
temperature: '20'
|
||||
solutions: null
|
||||
speed: null
|
||||
temperature: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: solutions
|
||||
@@ -248,20 +239,16 @@ bioyond_dispensing_station:
|
||||
handler_key: BATCH_CREATE_RESULT
|
||||
io_type: sink
|
||||
label: Complete Batch Create Result JSON (contains order_codes and order_ids)
|
||||
result:
|
||||
return_info: return_info
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 批量创建二胺溶液配置任务。自动为多个二胺样品创建溶液配置任务,每个任务包含固体物料称量、溶剂添加、搅拌混合等步骤。返回的return_info中包含order_codes和order_ids列表。
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: BatchCreateDiamineSolutionTasks_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
default: '600'
|
||||
description: 溶液配置完成后的延迟时间(秒),用于充分混合和溶解,默认600秒
|
||||
type: string
|
||||
liquid_material_name:
|
||||
@@ -275,11 +262,9 @@ bioyond_dispensing_station:
|
||||
4.5, "solvent_volume": 18}]'
|
||||
type: string
|
||||
speed:
|
||||
default: '400'
|
||||
description: 搅拌速度(rpm),用于混合溶液,默认400转/分钟
|
||||
type: string
|
||||
temperature:
|
||||
default: '20'
|
||||
description: 配置温度(℃),溶液配置过程的目标温度,默认20℃(室温)
|
||||
type: string
|
||||
required:
|
||||
@@ -287,17 +272,11 @@ bioyond_dispensing_station:
|
||||
title: BatchCreateDiamineSolutionTasks_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 批量任务创建结果汇总JSON字符串,包含total(总数)、success(成功数)、failed(失败数)、order_codes(任务编码数组)、order_ids(任务ID数组)、details(每个任务的详细信息)
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: BatchCreateDiamineSolutionTasks_Result
|
||||
type: object
|
||||
type: string
|
||||
required:
|
||||
- goal
|
||||
title: BatchCreateDiamineSolutionTasks
|
||||
title: batch_create_diamine_solution_tasks参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
compute_experiment_design:
|
||||
@@ -309,7 +288,7 @@ bioyond_dispensing_station:
|
||||
wt_percent: wt_percent
|
||||
goal_default:
|
||||
m_tot: '70'
|
||||
ratio: ''
|
||||
ratio: null
|
||||
titration_percent: '0.03'
|
||||
wt_percent: '0.25'
|
||||
handles:
|
||||
@@ -338,12 +317,8 @@ bioyond_dispensing_station:
|
||||
handler_key: feeding_order
|
||||
io_type: sink
|
||||
label: Feeding Order Data From Calculation Node
|
||||
result:
|
||||
feeding_order: feeding_order
|
||||
return_info: return_info
|
||||
solutions: solutions
|
||||
solvents: solvents
|
||||
titration: titration
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 计算实验设计,输出solutions/titration/solvents/feeding_order用于后续节点。
|
||||
properties:
|
||||
@@ -356,7 +331,7 @@ bioyond_dispensing_station:
|
||||
type: string
|
||||
ratio:
|
||||
description: 组分摩尔比的对象,保持输入顺序,如{"MDA":1,"BTDA":1}
|
||||
type: string
|
||||
type: object
|
||||
titration_percent:
|
||||
default: '0.03'
|
||||
description: 滴定比例(10%部分)
|
||||
@@ -371,14 +346,23 @@ bioyond_dispensing_station:
|
||||
result:
|
||||
properties:
|
||||
feeding_order:
|
||||
items: {}
|
||||
title: Feeding Order
|
||||
type: array
|
||||
return_info:
|
||||
title: Return Info
|
||||
type: string
|
||||
solutions:
|
||||
items: {}
|
||||
title: Solutions
|
||||
type: array
|
||||
solvents:
|
||||
additionalProperties: true
|
||||
title: Solvents
|
||||
type: object
|
||||
titration:
|
||||
additionalProperties: true
|
||||
title: Titration
|
||||
type: object
|
||||
required:
|
||||
- solutions
|
||||
@@ -386,11 +370,11 @@ bioyond_dispensing_station:
|
||||
- solvents
|
||||
- feeding_order
|
||||
- return_info
|
||||
title: ComputeExperimentDesign_Result
|
||||
title: ComputeExperimentDesignReturn
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: ComputeExperimentDesign
|
||||
title: compute_experiment_design参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
create_90_10_vial_feeding_task:
|
||||
@@ -444,17 +428,18 @@ bioyond_dispensing_station:
|
||||
speed: ''
|
||||
temperature: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: DispenStationVialFeed_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
@@ -502,38 +487,13 @@ bioyond_dispensing_station:
|
||||
type: string
|
||||
temperature:
|
||||
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
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: DispenStationVialFeed_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -564,17 +524,18 @@ bioyond_dispensing_station:
|
||||
temperature: ''
|
||||
volume: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: DispenStationSolnPrep_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
@@ -594,24 +555,13 @@ bioyond_dispensing_station:
|
||||
type: string
|
||||
volume:
|
||||
type: string
|
||||
required:
|
||||
- order_name
|
||||
- material_name
|
||||
- target_weigh
|
||||
- volume
|
||||
- liquid_material_name
|
||||
- speed
|
||||
- temperature
|
||||
- delay_time
|
||||
- hold_m_name
|
||||
title: DispenStationSolnPrep_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: DispenStationSolnPrep_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -624,8 +574,8 @@ bioyond_dispensing_station:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动调度器 - 启动Bioyond配液站的任务调度器,开始执行队列中的任务
|
||||
properties:
|
||||
@@ -635,12 +585,6 @@ bioyond_dispensing_station:
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 调度器启动结果,成功返回1,失败返回0
|
||||
type: integer
|
||||
required:
|
||||
- return_info
|
||||
title: scheduler_start结果
|
||||
type: object
|
||||
required:
|
||||
@@ -654,8 +598,8 @@ bioyond_dispensing_station:
|
||||
target_device_id: target_device_id
|
||||
transfer_groups: transfer_groups
|
||||
goal_default:
|
||||
target_device_id: ''
|
||||
transfer_groups: ''
|
||||
target_device_id: null
|
||||
transfer_groups: null
|
||||
handles: {}
|
||||
placeholder_keys:
|
||||
target_device_id: unilabos_devices
|
||||
@@ -671,32 +615,13 @@ bioyond_dispensing_station:
|
||||
type: string
|
||||
transfer_groups:
|
||||
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
|
||||
required:
|
||||
- target_device_id
|
||||
- transfer_groups
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: transfer_materials_to_reaction_station参数
|
||||
@@ -709,9 +634,9 @@ bioyond_dispensing_station:
|
||||
check_interval: check_interval
|
||||
timeout: timeout
|
||||
goal_default:
|
||||
batch_create_result: ''
|
||||
check_interval: '10'
|
||||
timeout: '7200'
|
||||
batch_create_result: null
|
||||
check_interval: 10
|
||||
timeout: 7200
|
||||
handles:
|
||||
input:
|
||||
- data_key: batch_create_result
|
||||
@@ -727,47 +652,35 @@ bioyond_dispensing_station:
|
||||
handler_key: batch_reports_result
|
||||
io_type: sink
|
||||
label: Batch Order Completion Reports
|
||||
result:
|
||||
return_info: return_info
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 同时等待多个任务完成并获取所有实验报告。从上游batch_create任务接收包含order_codes和order_ids的结果对象,并行监控所有任务状态并返回每个任务的报告。
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: WaitForMultipleOrdersAndGetReports_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
batch_create_result:
|
||||
description: 批量创建任务的返回结果对象,包含order_codes和order_ids数组。从上游batch_create节点通过handle传递
|
||||
type: string
|
||||
check_interval:
|
||||
default: '10'
|
||||
default: 10
|
||||
description: 检查任务状态的时间间隔(秒),默认每10秒检查一次所有待完成任务
|
||||
type: string
|
||||
type: integer
|
||||
timeout:
|
||||
default: '7200'
|
||||
default: 7200
|
||||
description: 等待超时时间(秒),默认7200秒(2小时)。超过此时间未完成的任务将标记为timeout
|
||||
type: string
|
||||
required:
|
||||
- batch_create_result
|
||||
type: integer
|
||||
required: []
|
||||
title: WaitForMultipleOrdersAndGetReports_Goal
|
||||
type: object
|
||||
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
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: WaitForMultipleOrdersAndGetReports
|
||||
title: wait_for_multiple_orders_and_get_reports参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.bioyond_studio.dispensing_station.dispensing_station:BioyondDispensingStation
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
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:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
type: object
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
@@ -42,7 +42,8 @@ cameracontroller_device:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: stop参数
|
||||
@@ -50,7 +51,7 @@ cameracontroller_device:
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.cameraSII.cameraUSB:CameraController
|
||||
status_types:
|
||||
status: dict
|
||||
status: Dict[str, Any]
|
||||
type: python
|
||||
config_info: []
|
||||
description: Uni-Lab-OS 摄像头驱动(Linux USB 摄像头版,无 PTZ)
|
||||
@@ -103,5 +104,4 @@ cameracontroller_device:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
registry_type: device
|
||||
version: 1.0.0
|
||||
|
||||
@@ -141,30 +141,26 @@ hplc.agilent:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -175,7 +171,6 @@ hplc.agilent:
|
||||
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
||||
status_types:
|
||||
could_run: bool
|
||||
data_file: String
|
||||
device_status: str
|
||||
driver_init_ok: bool
|
||||
finish_status: str
|
||||
@@ -199,10 +194,6 @@ hplc.agilent:
|
||||
properties:
|
||||
could_run:
|
||||
type: boolean
|
||||
data_file:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
device_status:
|
||||
type: string
|
||||
driver_init_ok:
|
||||
@@ -216,14 +207,13 @@ hplc.agilent:
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- status_text
|
||||
- device_status
|
||||
- could_run
|
||||
- device_status
|
||||
- driver_init_ok
|
||||
- is_running
|
||||
- success
|
||||
- finish_status
|
||||
- data_file
|
||||
- is_running
|
||||
- status_text
|
||||
- success
|
||||
type: object
|
||||
version: 1.0.0
|
||||
hplc.agilent-zhida:
|
||||
@@ -236,26 +226,25 @@ hplc.agilent-zhida:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -315,21 +304,18 @@ hplc.agilent-zhida:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -341,35 +327,35 @@ hplc.agilent-zhida:
|
||||
feedback: {}
|
||||
goal:
|
||||
string: string
|
||||
text: text
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -407,7 +393,7 @@ hplc.agilent-zhida:
|
||||
status:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- methods
|
||||
- status
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -120,42 +120,41 @@ raman.home_made:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
raman_cmd:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -19,7 +19,8 @@ separator.chinwe:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: connect参数
|
||||
@@ -65,135 +66,145 @@ separator.chinwe:
|
||||
required:
|
||||
- command_dict
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: execute_command_from_outer参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
motor_rotate_quarter:
|
||||
feedback: {}
|
||||
goal:
|
||||
direction: 顺时针
|
||||
motor_id: 4
|
||||
speed: 60
|
||||
goal_default:
|
||||
direction: 顺时针
|
||||
motor_id: null
|
||||
speed: 60
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 电机旋转 1/4 圈
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
direction:
|
||||
default: 顺时针
|
||||
description: 旋转方向
|
||||
enum:
|
||||
- 顺时针
|
||||
- 逆时针
|
||||
type: string
|
||||
motor_id:
|
||||
default: '4'
|
||||
description: 选择电机 (4:搅拌, 5:旋钮)
|
||||
enum:
|
||||
- '4'
|
||||
- '5'
|
||||
type: string
|
||||
type: integer
|
||||
speed:
|
||||
default: 60
|
||||
description: 速度 (RPM)
|
||||
type: integer
|
||||
required:
|
||||
- motor_id
|
||||
- speed
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: motor_rotate_quarter参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
motor_run_continuous:
|
||||
feedback: {}
|
||||
goal:
|
||||
direction: 顺时针
|
||||
motor_id: 4
|
||||
speed: 60
|
||||
goal_default:
|
||||
direction: 顺时针
|
||||
motor_id: null
|
||||
speed: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 电机一直旋转 (速度模式)
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
direction:
|
||||
default: 顺时针
|
||||
description: 旋转方向
|
||||
enum:
|
||||
- 顺时针
|
||||
- 逆时针
|
||||
type: string
|
||||
motor_id:
|
||||
default: '4'
|
||||
description: 选择电机 (4:搅拌, 5:旋钮)
|
||||
enum:
|
||||
- '4'
|
||||
- '5'
|
||||
type: string
|
||||
type: integer
|
||||
speed:
|
||||
default: 60
|
||||
description: 速度 (RPM)
|
||||
type: integer
|
||||
required:
|
||||
- motor_id
|
||||
- speed
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: motor_run_continuous参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
motor_stop:
|
||||
feedback: {}
|
||||
goal:
|
||||
motor_id: 4
|
||||
goal_default:
|
||||
motor_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 停止指定步进电机
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
motor_id:
|
||||
default: '4'
|
||||
description: 选择电机
|
||||
enum:
|
||||
- '4'
|
||||
- '5'
|
||||
title: '注: 4=搅拌, 5=旋钮'
|
||||
type: string
|
||||
type: integer
|
||||
required:
|
||||
- motor_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: motor_stop参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pump_aspirate:
|
||||
feedback: {}
|
||||
goal:
|
||||
pump_id: 1
|
||||
valve_port: 1
|
||||
volume: 1000
|
||||
goal_default:
|
||||
pump_id: null
|
||||
valve_port: null
|
||||
volume: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 注射泵吸液
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
pump_id:
|
||||
default: '1'
|
||||
description: 选择泵
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
type: string
|
||||
type: integer
|
||||
valve_port:
|
||||
default: '1'
|
||||
description: 阀门端口
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
- '4'
|
||||
- '5'
|
||||
- '6'
|
||||
- '7'
|
||||
- '8'
|
||||
type: string
|
||||
type: integer
|
||||
volume:
|
||||
default: 1000
|
||||
description: 吸液步数
|
||||
type: integer
|
||||
required:
|
||||
@@ -201,41 +212,38 @@ separator.chinwe:
|
||||
- volume
|
||||
- valve_port
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: pump_aspirate参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pump_dispense:
|
||||
feedback: {}
|
||||
goal:
|
||||
pump_id: 1
|
||||
valve_port: 1
|
||||
volume: 1000
|
||||
goal_default:
|
||||
pump_id: null
|
||||
valve_port: null
|
||||
volume: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 注射泵排液
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
pump_id:
|
||||
default: '1'
|
||||
description: 选择泵
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
type: string
|
||||
type: integer
|
||||
valve_port:
|
||||
default: '1'
|
||||
description: 阀门端口
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
- '4'
|
||||
- '5'
|
||||
- '6'
|
||||
- '7'
|
||||
- '8'
|
||||
type: string
|
||||
type: integer
|
||||
volume:
|
||||
default: 1000
|
||||
description: 排液步数
|
||||
type: integer
|
||||
required:
|
||||
@@ -243,121 +251,152 @@ separator.chinwe:
|
||||
- volume
|
||||
- valve_port
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: pump_dispense参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pump_initialize:
|
||||
feedback: {}
|
||||
goal:
|
||||
drain_port: 0
|
||||
output_port: 0
|
||||
pump_id: 1
|
||||
speed: 10
|
||||
goal_default:
|
||||
drain_port: 0
|
||||
output_port: 0
|
||||
pump_id: null
|
||||
speed: 10
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 初始化指定注射泵
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
drain_port:
|
||||
default: 0
|
||||
description: 排液口索引
|
||||
type: integer
|
||||
type: string
|
||||
output_port:
|
||||
default: 0
|
||||
description: 输出口索引
|
||||
type: integer
|
||||
pump_id:
|
||||
default: '1'
|
||||
description: 选择泵
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
title: '注: 1号泵, 2号泵, 3号泵'
|
||||
type: string
|
||||
pump_id:
|
||||
description: 选择泵
|
||||
title: '注: 1号泵, 2号泵, 3号泵'
|
||||
type: integer
|
||||
speed:
|
||||
default: 10
|
||||
description: 运动速度
|
||||
type: integer
|
||||
type: string
|
||||
required:
|
||||
- pump_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: pump_initialize参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pump_valve:
|
||||
feedback: {}
|
||||
goal:
|
||||
port: 1
|
||||
pump_id: 1
|
||||
goal_default:
|
||||
port: null
|
||||
pump_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 切换指定泵的阀门端口
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
port:
|
||||
default: '1'
|
||||
description: 阀门端口号 (1-8)
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
- '4'
|
||||
- '5'
|
||||
- '6'
|
||||
- '7'
|
||||
- '8'
|
||||
type: string
|
||||
type: integer
|
||||
pump_id:
|
||||
default: '1'
|
||||
description: 选择泵
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
type: string
|
||||
type: integer
|
||||
required:
|
||||
- pump_id
|
||||
- port
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: pump_valve参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
wait_sensor_level:
|
||||
feedback: {}
|
||||
goal:
|
||||
target_state: 有液
|
||||
timeout: 30
|
||||
goal_default:
|
||||
target_state: 有液
|
||||
timeout: 30
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 等待传感器液位条件
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
target_state:
|
||||
default: 有液
|
||||
description: 目标液位状态
|
||||
enum:
|
||||
- 有液
|
||||
- 无液
|
||||
type: string
|
||||
timeout:
|
||||
default: 30
|
||||
description: 超时时间 (秒)
|
||||
type: integer
|
||||
required:
|
||||
- target_state
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: wait_sensor_level参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
wait_time:
|
||||
feedback: {}
|
||||
goal:
|
||||
duration: 10
|
||||
goal_default:
|
||||
duration: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 等待指定时间
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
duration:
|
||||
default: 10
|
||||
description: 等待时间 (秒)
|
||||
type: integer
|
||||
required:
|
||||
- duration
|
||||
type: object
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: wait_time参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.separator.chinwe:ChinweDevice
|
||||
status_types:
|
||||
@@ -406,8 +445,8 @@ separator.chinwe:
|
||||
sensor_rssi:
|
||||
type: integer
|
||||
required:
|
||||
- is_connected
|
||||
- sensor_level
|
||||
- sensor_rssi
|
||||
- is_connected
|
||||
type: object
|
||||
version: 2.1.0
|
||||
|
||||
@@ -64,7 +64,8 @@ coincellassemblyworkstation_device:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: fun_wuliao_test参数
|
||||
@@ -109,7 +110,8 @@ coincellassemblyworkstation_device:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: func_allpack_cmd参数
|
||||
@@ -220,7 +222,8 @@ coincellassemblyworkstation_device:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: func_allpack_cmd_simp参数
|
||||
@@ -309,7 +312,8 @@ coincellassemblyworkstation_device:
|
||||
type: boolean
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_device_init_auto_start_combined参数
|
||||
@@ -351,7 +355,8 @@ coincellassemblyworkstation_device:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_device_stop参数
|
||||
@@ -376,7 +381,8 @@ coincellassemblyworkstation_device:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_get_msg_cmd参数
|
||||
@@ -430,7 +436,8 @@ coincellassemblyworkstation_device:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_send_finished_cmd参数
|
||||
@@ -467,7 +474,8 @@ coincellassemblyworkstation_device:
|
||||
- assembly_type
|
||||
- assembly_pressure
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_send_msg_cmd参数
|
||||
@@ -611,7 +619,8 @@ coincellassemblyworkstation_device:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: func_sendbottle_allpack_multi参数
|
||||
@@ -663,31 +672,6 @@ coincellassemblyworkstation_device:
|
||||
title: modify_deck_name参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -735,7 +719,8 @@ coincellassemblyworkstation_device:
|
||||
required:
|
||||
- fujipian_panshu
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: qiming_coin_cell_code参数
|
||||
@@ -826,25 +811,24 @@ coincellassemblyworkstation_device:
|
||||
sys_status:
|
||||
type: string
|
||||
required:
|
||||
- sys_status
|
||||
- sys_mode
|
||||
- request_rec_msg_status
|
||||
- request_send_msg_status
|
||||
- data_assembly_coin_cell_num
|
||||
- data_assembly_pressure
|
||||
- data_assembly_time
|
||||
- data_open_circuit_voltage
|
||||
- data_axis_x_pos
|
||||
- data_axis_y_pos
|
||||
- data_axis_z_pos
|
||||
- data_pole_weight
|
||||
- data_assembly_pressure
|
||||
- data_electrolyte_volume
|
||||
- data_coin_num
|
||||
- data_coin_cell_code
|
||||
- data_coin_num
|
||||
- data_electrolyte_code
|
||||
- data_glove_box_pressure
|
||||
- data_electrolyte_volume
|
||||
- data_glove_box_o2_content
|
||||
- data_glove_box_pressure
|
||||
- data_glove_box_water_content
|
||||
- data_open_circuit_voltage
|
||||
- data_pole_weight
|
||||
- request_rec_msg_status
|
||||
- request_send_msg_status
|
||||
- sys_mode
|
||||
- sys_status
|
||||
type: object
|
||||
registry_type: device
|
||||
version: 1.0.0
|
||||
|
||||
@@ -50,26 +50,25 @@ gas_source.mock:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -82,26 +81,25 @@ gas_source.mock:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -116,32 +114,31 @@ gas_source.mock:
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -232,26 +229,25 @@ vacuum_pump.mock:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -264,26 +260,25 @@ vacuum_pump.mock:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -298,32 +293,31 @@ vacuum_pump.mock:
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -5,7 +5,7 @@ hotel.thermo_orbitor_rs2_hotel:
|
||||
action_value_mappings: {}
|
||||
module: unilabos.devices.resource_container.container:HotelContainer
|
||||
status_types:
|
||||
rotation: String
|
||||
rotation: ''
|
||||
type: python
|
||||
config_info: []
|
||||
description: Thermo Orbitor RS2 Hotel容器设备,用于实验室样品的存储和管理。该设备通过HotelContainer类实现容器的旋转控制和状态监控,主要用于存储实验样品、试剂瓶或其他实验器具,支持旋转功能以便于样品的自动化存取。适用于需要有序存储和快速访问大量样品的实验室自动化场景。
|
||||
|
||||
@@ -22,7 +22,8 @@ xyz_stepper_controller:
|
||||
required:
|
||||
- degrees
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: integer
|
||||
required:
|
||||
- goal
|
||||
title: degrees_to_steps参数
|
||||
@@ -47,7 +48,8 @@ xyz_stepper_controller:
|
||||
required:
|
||||
- axis
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: emergency_stop参数
|
||||
@@ -72,7 +74,10 @@ xyz_stepper_controller:
|
||||
type: boolean
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: enable_all_axes参数
|
||||
@@ -101,7 +106,8 @@ xyz_stepper_controller:
|
||||
required:
|
||||
- axis
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: enable_motor参数
|
||||
@@ -122,7 +128,10 @@ xyz_stepper_controller:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: home_all_axes参数
|
||||
@@ -147,7 +156,8 @@ xyz_stepper_controller:
|
||||
required:
|
||||
- axis
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: home_axis参数
|
||||
@@ -188,7 +198,8 @@ xyz_stepper_controller:
|
||||
- axis
|
||||
- position
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: move_to_position参数
|
||||
@@ -229,7 +240,8 @@ xyz_stepper_controller:
|
||||
- axis
|
||||
- degrees
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: move_to_position_degrees参数
|
||||
@@ -270,7 +282,8 @@ xyz_stepper_controller:
|
||||
- axis
|
||||
- revolutions
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: move_to_position_revolutions参数
|
||||
@@ -301,14 +314,17 @@ xyz_stepper_controller:
|
||||
default: 5000
|
||||
type: integer
|
||||
x:
|
||||
type: string
|
||||
type: integer
|
||||
y:
|
||||
type: string
|
||||
type: integer
|
||||
z:
|
||||
type: string
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: move_xyz参数
|
||||
@@ -339,14 +355,17 @@ xyz_stepper_controller:
|
||||
default: 5000
|
||||
type: integer
|
||||
x_deg:
|
||||
type: string
|
||||
type: number
|
||||
y_deg:
|
||||
type: string
|
||||
type: number
|
||||
z_deg:
|
||||
type: string
|
||||
type: number
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: move_xyz_degrees参数
|
||||
@@ -377,14 +396,17 @@ xyz_stepper_controller:
|
||||
default: 5000
|
||||
type: integer
|
||||
x_rev:
|
||||
type: string
|
||||
type: number
|
||||
y_rev:
|
||||
type: string
|
||||
type: number
|
||||
z_rev:
|
||||
type: string
|
||||
type: number
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: move_xyz_revolutions参数
|
||||
@@ -409,7 +431,8 @@ xyz_stepper_controller:
|
||||
required:
|
||||
- revolutions
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: integer
|
||||
required:
|
||||
- goal
|
||||
title: revolutions_to_steps参数
|
||||
@@ -442,7 +465,8 @@ xyz_stepper_controller:
|
||||
- axis
|
||||
- speed
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: set_speed_mode参数
|
||||
@@ -467,7 +491,8 @@ xyz_stepper_controller:
|
||||
required:
|
||||
- steps
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: number
|
||||
required:
|
||||
- goal
|
||||
title: steps_to_degrees参数
|
||||
@@ -492,7 +517,8 @@ xyz_stepper_controller:
|
||||
required:
|
||||
- steps
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: number
|
||||
required:
|
||||
- goal
|
||||
title: steps_to_revolutions参数
|
||||
@@ -513,7 +539,10 @@ xyz_stepper_controller:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: stop_all_axes参数
|
||||
@@ -542,7 +571,8 @@ xyz_stepper_controller:
|
||||
required:
|
||||
- axis
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_completion参数
|
||||
@@ -550,8 +580,7 @@ xyz_stepper_controller:
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.liquid_handling.laiyu.drivers.xyz_stepper_driver:XYZStepperController
|
||||
status_types:
|
||||
all_positions: dict
|
||||
motor_status: unilabos.devices.liquid_handling.laiyu.drivers.xyz_stepper_driver:MotorPosition
|
||||
all_positions: Dict[MotorAxis, MotorPosition]
|
||||
type: python
|
||||
config_info: []
|
||||
description: 新XYZ控制器
|
||||
@@ -574,12 +603,10 @@ xyz_stepper_controller:
|
||||
data:
|
||||
properties:
|
||||
all_positions:
|
||||
type: object
|
||||
motor_status:
|
||||
additionalProperties:
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- motor_status
|
||||
- all_positions
|
||||
type: object
|
||||
registry_type: device
|
||||
version: 1.0.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,31 +5,6 @@ neware_battery_test_system:
|
||||
- battery_test
|
||||
class:
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -66,7 +41,8 @@ neware_battery_test_system:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: boolean
|
||||
required:
|
||||
- goal
|
||||
title: test_connection参数
|
||||
@@ -77,9 +53,8 @@ neware_battery_test_system:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 调试方法:显示所有资源的实际名称
|
||||
properties:
|
||||
@@ -89,19 +64,10 @@ neware_battery_test_system:
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 资源调试信息
|
||||
type: string
|
||||
success:
|
||||
description: 是否成功
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: debug_resource_names参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
export_status_json:
|
||||
@@ -111,9 +77,8 @@ neware_battery_test_system:
|
||||
goal_default:
|
||||
filepath: bts_status.json
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 导出当前状态数据到JSON文件
|
||||
properties:
|
||||
@@ -127,19 +92,10 @@ neware_battery_test_system:
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 导出操作结果信息
|
||||
type: string
|
||||
success:
|
||||
description: 导出是否成功
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: export_status_json参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
get_device_summary:
|
||||
@@ -181,10 +137,8 @@ neware_battery_test_system:
|
||||
goal_default:
|
||||
plate_num: null
|
||||
handles: {}
|
||||
result:
|
||||
plate_data: plate_data
|
||||
return_info: return_info
|
||||
success: success
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 获取指定盘或所有盘的状态信息
|
||||
properties:
|
||||
@@ -193,29 +147,14 @@ neware_battery_test_system:
|
||||
properties:
|
||||
plate_num:
|
||||
description: 盘号 (1 或 2),如果为null则返回所有盘的状态
|
||||
maximum: 2
|
||||
minimum: 1
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
plate_data:
|
||||
description: 盘状态数据(单盘或所有盘)
|
||||
type: object
|
||||
return_info:
|
||||
description: 操作结果信息
|
||||
type: string
|
||||
success:
|
||||
description: 查询是否成功
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
- plate_data
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: get_plate_status参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
print_status_summary_action:
|
||||
@@ -223,9 +162,8 @@ neware_battery_test_system:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 打印通道状态摘要信息到控制台
|
||||
properties:
|
||||
@@ -235,28 +173,21 @@ neware_battery_test_system:
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 打印操作结果信息
|
||||
type: string
|
||||
success:
|
||||
description: 打印是否成功
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: print_status_summary_action参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
query_plate_action:
|
||||
feedback: {}
|
||||
goal:
|
||||
string: plate_id
|
||||
plate_id: plate_id
|
||||
string: string
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
@@ -264,27 +195,23 @@ neware_battery_test_system:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -298,13 +225,11 @@ neware_battery_test_system:
|
||||
csv_path: string
|
||||
output_dir: string
|
||||
goal_default:
|
||||
csv_path: ''
|
||||
csv_path: null
|
||||
output_dir: .
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
submitted_count: submitted_count
|
||||
success: success
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 从CSV文件批量提交Neware测试任务
|
||||
properties:
|
||||
@@ -315,31 +240,17 @@ neware_battery_test_system:
|
||||
description: 输入CSV文件的绝对路径
|
||||
type: string
|
||||
output_dir:
|
||||
default: .
|
||||
description: 输出目录(用于存储XML和备份文件),默认当前目录
|
||||
type: string
|
||||
required:
|
||||
- csv_path
|
||||
type: object
|
||||
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
|
||||
required:
|
||||
- goal
|
||||
title: submit_from_csv参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
test_connection_action:
|
||||
@@ -347,9 +258,8 @@ neware_battery_test_system:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 测试与电池测试系统的TCP连接
|
||||
properties:
|
||||
@@ -359,19 +269,10 @@ neware_battery_test_system:
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 连接测试结果信息
|
||||
type: string
|
||||
success:
|
||||
description: 连接测试是否成功
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: test_connection_action参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
upload_backup_to_oss:
|
||||
@@ -392,12 +293,8 @@ neware_battery_test_system:
|
||||
handler_key: uploaded_files
|
||||
io_type: sink
|
||||
label: Uploaded Files (with standard flow info)
|
||||
result:
|
||||
failed_files: failed_files
|
||||
return_info: return_info
|
||||
success: success
|
||||
total_count: total_count
|
||||
uploaded_count: uploaded_count
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 上传备份文件到阿里云OSS
|
||||
properties:
|
||||
@@ -417,65 +314,17 @@ neware_battery_test_system:
|
||||
required: []
|
||||
type: object
|
||||
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
|
||||
required:
|
||||
- goal
|
||||
title: upload_backup_to_oss参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
|
||||
status_types:
|
||||
channel_status: dict
|
||||
connection_info: dict
|
||||
channel_status: Dict[int, Dict]
|
||||
connection_info: Dict[str, str]
|
||||
device_summary: dict
|
||||
plate_status: dict
|
||||
status: str
|
||||
total_channels: int
|
||||
type: python
|
||||
@@ -517,23 +366,24 @@ neware_battery_test_system:
|
||||
data:
|
||||
properties:
|
||||
channel_status:
|
||||
additionalProperties:
|
||||
type: object
|
||||
type: object
|
||||
connection_info:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
device_summary:
|
||||
type: object
|
||||
plate_status:
|
||||
type: object
|
||||
status:
|
||||
type: string
|
||||
total_channels:
|
||||
type: integer
|
||||
required:
|
||||
- status
|
||||
- channel_status
|
||||
- connection_info
|
||||
- total_channels
|
||||
- plate_status
|
||||
- device_summary
|
||||
- status
|
||||
- total_channels
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -142,8 +142,7 @@ opcua_example:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.device_comms.opcua_client.client:OpcUaClient
|
||||
status_types:
|
||||
node_value: String
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: null
|
||||
@@ -167,10 +166,7 @@ opcua_example:
|
||||
- url
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
node_value:
|
||||
type: string
|
||||
required:
|
||||
- node_value
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -80,7 +80,8 @@ opsky_ATR30007:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: run_once参数
|
||||
|
||||
@@ -100,42 +100,41 @@ rotavap.one:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
set_timer:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -250,9 +249,13 @@ separator.homemade:
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
event: event
|
||||
settling_time: settling_time
|
||||
stir_speed: stir_speed
|
||||
stir_time: stir_time,
|
||||
stir_time: stir_time
|
||||
time: time
|
||||
time_spec: time_spec
|
||||
vessel: vessel
|
||||
goal_default:
|
||||
event: ''
|
||||
settling_time: ''
|
||||
@@ -281,34 +284,42 @@ separator.homemade:
|
||||
sample_id: ''
|
||||
type: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
message: message
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: Stir_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
event:
|
||||
type: string
|
||||
settling_time:
|
||||
type: string
|
||||
stir_speed:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
stir_time:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
time:
|
||||
type: string
|
||||
time_spec:
|
||||
type: string
|
||||
vessel:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
@@ -327,16 +338,26 @@ separator.homemade:
|
||||
parent:
|
||||
type: string
|
||||
pose:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
orientation:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
w:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
x:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
y:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
z:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
@@ -346,12 +367,19 @@ separator.homemade:
|
||||
title: orientation
|
||||
type: object
|
||||
position:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
x:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
y:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
z:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
@@ -381,17 +409,10 @@ separator.homemade:
|
||||
- data
|
||||
title: vessel
|
||||
type: object
|
||||
required:
|
||||
- vessel
|
||||
- time
|
||||
- event
|
||||
- time_spec
|
||||
- stir_time
|
||||
- stir_speed
|
||||
- settling_time
|
||||
title: Stir_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
@@ -399,10 +420,6 @@ separator.homemade:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- success
|
||||
- message
|
||||
- return_info
|
||||
title: Stir_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -418,36 +435,34 @@ separator.homemade:
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -28,31 +28,6 @@ post_process_station:
|
||||
title: load_config参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -104,42 +79,41 @@ post_process_station:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
disconnect:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: {}
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -149,42 +123,41 @@ post_process_station:
|
||||
type: SendCmd
|
||||
read_node:
|
||||
feedback:
|
||||
result: result
|
||||
status: status
|
||||
goal:
|
||||
command: node_name
|
||||
command: command
|
||||
node_name: node_name
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -283,17 +256,19 @@ post_process_station:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: PostProcessTriggerClean_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
acetone_inner_wall_cleaning_count:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_inner_wall_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
acetone_inner_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
@@ -304,6 +279,8 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_outer_wall_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
acetone_outer_wall_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
@@ -322,6 +299,8 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
acetone_stirrer_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
acetone_stirrer_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
@@ -348,6 +327,8 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_inner_wall_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
nmp_inner_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
@@ -358,6 +339,8 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_outer_wall_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
nmp_outer_wall_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
@@ -376,6 +359,8 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
nmp_stirrer_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
nmp_stirrer_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
@@ -394,6 +379,8 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_inner_wall_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
water_inner_wall_cleaning_waste_time:
|
||||
maximum: 2147483647
|
||||
@@ -404,6 +391,8 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_outer_wall_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
water_outer_wall_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
@@ -422,6 +411,8 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
water_stirrer_cleaning_injection:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
water_stirrer_cleaning_wait_time:
|
||||
maximum: 2147483647
|
||||
@@ -431,55 +422,13 @@ post_process_station:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
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
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: PostProcessTriggerClean_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -502,11 +451,11 @@ post_process_station:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: PostProcessGrab_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
raw_tank_number:
|
||||
maximum: 2147483647
|
||||
@@ -516,17 +465,13 @@ post_process_station:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
required:
|
||||
- reaction_tank_number
|
||||
- raw_tank_number
|
||||
title: PostProcessGrab_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: PostProcessGrab_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -573,13 +518,15 @@ post_process_station:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: PostProcessTriggerPostPro_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
atomization_fast_speed:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
atomization_pressure_kpa:
|
||||
maximum: 2147483647
|
||||
@@ -594,8 +541,12 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
first_wash_water_amount:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
initial_water_amount:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
injection_pump_push_speed:
|
||||
maximum: 2147483647
|
||||
@@ -622,32 +573,20 @@ post_process_station:
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
second_wash_water_amount:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
wash_slow_speed:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
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
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: PostProcessTriggerPostPro_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -669,30 +608,26 @@ post_process_station:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -702,8 +637,7 @@ post_process_station:
|
||||
type: SendCmd
|
||||
module: unilabos.devices.workstation.post_process.post_process:OpcUaClient
|
||||
status_types:
|
||||
cache_stats: dict
|
||||
node_value: String
|
||||
cache_stats: Dict[str, Any]
|
||||
type: python
|
||||
config_info: []
|
||||
description: 后处理站
|
||||
@@ -718,7 +652,9 @@ post_process_station:
|
||||
config_path:
|
||||
type: string
|
||||
deck:
|
||||
type: string
|
||||
anyOf:
|
||||
- type: object
|
||||
- type: object
|
||||
password:
|
||||
type: string
|
||||
subscription_interval:
|
||||
@@ -738,10 +674,7 @@ post_process_station:
|
||||
properties:
|
||||
cache_stats:
|
||||
type: object
|
||||
node_value:
|
||||
type: string
|
||||
required:
|
||||
- node_value
|
||||
- cache_stats
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -136,36 +136,36 @@ solenoid_valve:
|
||||
set_valve_position:
|
||||
feedback: {}
|
||||
goal:
|
||||
string: position
|
||||
position: position
|
||||
string: string
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -278,26 +278,25 @@ solenoid_valve.mock:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -310,26 +309,25 @@ solenoid_valve.mock:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -422,6 +420,27 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
title: initialize参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -695,7 +714,10 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
goal:
|
||||
properties:
|
||||
position:
|
||||
type: string
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
- type: number
|
||||
required:
|
||||
- position
|
||||
type: object
|
||||
@@ -720,7 +742,9 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
goal:
|
||||
properties:
|
||||
velocity:
|
||||
type: string
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
required:
|
||||
- velocity
|
||||
type: object
|
||||
@@ -780,13 +804,13 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
status_types:
|
||||
max_velocity: float
|
||||
mode: int
|
||||
plunger_position: String
|
||||
plunger_position: ''
|
||||
position: float
|
||||
status: str
|
||||
valve_position: str
|
||||
velocity_end: String
|
||||
velocity_grade: String
|
||||
velocity_init: String
|
||||
velocity_end: ''
|
||||
velocity_grade: ''
|
||||
velocity_init: ''
|
||||
type: python
|
||||
config_info: []
|
||||
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
|
||||
@@ -885,15 +909,15 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
velocity_init:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- mode
|
||||
- max_velocity
|
||||
- mode
|
||||
- plunger_position
|
||||
- position
|
||||
- status
|
||||
- valve_position
|
||||
- velocity_end
|
||||
- velocity_grade
|
||||
- velocity_init
|
||||
- velocity_end
|
||||
- valve_position
|
||||
- position
|
||||
- plunger_position
|
||||
type: object
|
||||
version: 1.0.0
|
||||
syringe_pump_with_valve.runze.SY03B-T08:
|
||||
@@ -943,6 +967,27 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
title: initialize参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -1216,7 +1261,10 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
goal:
|
||||
properties:
|
||||
position:
|
||||
type: string
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
- type: number
|
||||
required:
|
||||
- position
|
||||
type: object
|
||||
@@ -1241,7 +1289,9 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
goal:
|
||||
properties:
|
||||
velocity:
|
||||
type: string
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
required:
|
||||
- velocity
|
||||
type: object
|
||||
@@ -1301,13 +1351,13 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
status_types:
|
||||
max_velocity: float
|
||||
mode: int
|
||||
plunger_position: String
|
||||
plunger_position: ''
|
||||
position: float
|
||||
status: str
|
||||
valve_position: str
|
||||
velocity_end: String
|
||||
velocity_grade: String
|
||||
velocity_init: String
|
||||
velocity_end: ''
|
||||
velocity_grade: ''
|
||||
velocity_init: ''
|
||||
type: python
|
||||
config_info: []
|
||||
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
|
||||
@@ -1422,14 +1472,14 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
velocity_init:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- mode
|
||||
- max_velocity
|
||||
- mode
|
||||
- plunger_position
|
||||
- position
|
||||
- status
|
||||
- valve_position
|
||||
- velocity_end
|
||||
- velocity_grade
|
||||
- velocity_init
|
||||
- velocity_end
|
||||
- valve_position
|
||||
- position
|
||||
- plunger_position
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -13,12 +13,13 @@ reaction_station.bioyond:
|
||||
start_point: start_point
|
||||
start_step_key: start_step_key
|
||||
goal_default:
|
||||
duration: 0
|
||||
duration: null
|
||||
end_point: 0
|
||||
end_step_key: ''
|
||||
start_point: 0
|
||||
start_step_key: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 添加时间约束 - 在两个工作流之间添加时间约束
|
||||
@@ -30,23 +31,19 @@ reaction_station.bioyond:
|
||||
description: 时间(秒)
|
||||
type: integer
|
||||
end_point:
|
||||
default: Start
|
||||
default: 0
|
||||
description: 终点计时点 (Start=开始前, End=结束后)
|
||||
enum:
|
||||
- Start
|
||||
- End
|
||||
type: string
|
||||
type: integer
|
||||
end_step_key:
|
||||
default: ''
|
||||
description: 终点步骤Key (可选, 默认为空则自动选择)
|
||||
type: string
|
||||
start_point:
|
||||
default: Start
|
||||
default: 0
|
||||
description: 起点计时点 (Start=开始前, End=结束后)
|
||||
enum:
|
||||
- Start
|
||||
- End
|
||||
type: string
|
||||
type: integer
|
||||
start_step_key:
|
||||
default: ''
|
||||
description: 起点步骤Key (例如 "feeding", "liquid", 可选, 默认为空则自动选择)
|
||||
type: string
|
||||
required:
|
||||
@@ -98,7 +95,8 @@ reaction_station.bioyond:
|
||||
required:
|
||||
- json_str
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: create_order参数
|
||||
@@ -125,7 +123,8 @@ reaction_station.bioyond:
|
||||
required:
|
||||
- workflow_ids
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: hard_delete_merged_workflows参数
|
||||
@@ -150,7 +149,8 @@ reaction_station.bioyond:
|
||||
required:
|
||||
- json_str
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: merge_workflow_with_parameters参数
|
||||
@@ -175,7 +175,8 @@ reaction_station.bioyond:
|
||||
required:
|
||||
- report_request
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: process_temperature_cutoff_report参数
|
||||
@@ -200,7 +201,12 @@ reaction_station.bioyond:
|
||||
required:
|
||||
- web_workflow_json
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
items:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- goal
|
||||
title: process_web_workflows参数
|
||||
@@ -229,7 +235,8 @@ reaction_station.bioyond:
|
||||
- reactor_id
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: string
|
||||
required:
|
||||
- goal
|
||||
title: set_reactor_temperature参数
|
||||
@@ -254,7 +261,8 @@ reaction_station.bioyond:
|
||||
required:
|
||||
- preintake_id
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: skip_titration_steps参数
|
||||
@@ -275,7 +283,8 @@ reaction_station.bioyond:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: sync_workflow_sequence_from_bioyond参数
|
||||
@@ -307,7 +316,8 @@ reaction_station.bioyond:
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_multiple_orders_and_get_reports参数
|
||||
@@ -359,7 +369,8 @@ reaction_station.bioyond:
|
||||
required:
|
||||
- workflow_id
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: workflow_step_query参数
|
||||
@@ -370,9 +381,8 @@ reaction_station.bioyond:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result:
|
||||
code: code
|
||||
message: message
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 清空服务端所有非核心工作流 (保留核心流程)
|
||||
properties:
|
||||
@@ -382,13 +392,6 @@ reaction_station.bioyond:
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
code:
|
||||
description: 操作结果代码(1表示成功)
|
||||
type: integer
|
||||
message:
|
||||
description: 结果描述
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
@@ -405,13 +408,14 @@ reaction_station.bioyond:
|
||||
torque_variation: torque_variation
|
||||
volume: volume
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
titration_type: ''
|
||||
torque_variation: ''
|
||||
volume: ''
|
||||
assign_material_name: null
|
||||
temperature: 25.0
|
||||
time: '90'
|
||||
titration_type: '1'
|
||||
torque_variation: 2
|
||||
volume: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 滴回去
|
||||
@@ -423,33 +427,27 @@ reaction_station.bioyond:
|
||||
description: 物料名称(不能为空)
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
type: number
|
||||
time:
|
||||
default: '90'
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
titration_type:
|
||||
default: '1'
|
||||
description: 是否滴定(NO=否, YES=是)
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 2
|
||||
description: 是否观察 (NO=否, YES=是)
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
type: integer
|
||||
volume:
|
||||
description: 分液公式(mL)
|
||||
type: string
|
||||
required:
|
||||
- volume
|
||||
- assign_material_name
|
||||
- time
|
||||
- torque_variation
|
||||
- titration_type
|
||||
- temperature
|
||||
- volume
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
@@ -462,7 +460,7 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
batch_reports_result: batch_reports_result
|
||||
goal_default:
|
||||
batch_reports_result: ''
|
||||
batch_reports_result: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: batch_reports_result
|
||||
@@ -478,8 +476,8 @@ reaction_station.bioyond:
|
||||
handler_key: ACTUALS_EXTRACTED
|
||||
io_type: sink
|
||||
label: Extracted Actuals
|
||||
result:
|
||||
return_info: return_info
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 从批量任务完成报告中提取每个订单的实际加料量,输出extracted列表。
|
||||
properties:
|
||||
@@ -493,13 +491,6 @@ reaction_station.bioyond:
|
||||
- batch_reports_result
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: JSON字符串,包含actuals数组,每项含order_code, order_id, actualTargetWeigh,
|
||||
actualVolume
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: extract_actuals_from_batch_reports结果
|
||||
type: object
|
||||
required:
|
||||
@@ -517,13 +508,14 @@ reaction_station.bioyond:
|
||||
torque_variation: torque_variation
|
||||
volume: volume
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
titration_type: ''
|
||||
torque_variation: ''
|
||||
volume: ''
|
||||
assign_material_name: BAPP
|
||||
temperature: 25.0
|
||||
time: '0'
|
||||
titration_type: '1'
|
||||
torque_variation: 1
|
||||
volume: '350'
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 液体进料烧杯
|
||||
@@ -532,36 +524,30 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
default: BAPP
|
||||
description: 物料名称
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
type: number
|
||||
time:
|
||||
default: '0'
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
titration_type:
|
||||
default: '1'
|
||||
description: 是否滴定(NO=否, YES=是)
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 1
|
||||
description: 是否观察 (NO=否, YES=是)
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
type: integer
|
||||
volume:
|
||||
default: '350'
|
||||
description: 分液公式(mL)
|
||||
type: string
|
||||
required:
|
||||
- volume
|
||||
- assign_material_name
|
||||
- time
|
||||
- torque_variation
|
||||
- titration_type
|
||||
- temperature
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
@@ -580,13 +566,13 @@ reaction_station.bioyond:
|
||||
torque_variation: torque_variation
|
||||
volume: volume
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
solvents: ''
|
||||
temperature: '25.00'
|
||||
assign_material_name: null
|
||||
solvents: null
|
||||
temperature: 25.0
|
||||
time: '360'
|
||||
titration_type: '1'
|
||||
torque_variation: '2'
|
||||
volume: ''
|
||||
torque_variation: 2
|
||||
volume: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: solvents
|
||||
@@ -595,6 +581,7 @@ reaction_station.bioyond:
|
||||
handler_key: solvents
|
||||
io_type: source
|
||||
label: Solvents Data From Calculation Node
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 液体投料-溶剂。可以直接提供volume(mL),或通过solvents对象自动从additional_solvent(mL)计算volume。
|
||||
@@ -609,27 +596,21 @@ reaction_station.bioyond:
|
||||
description: '溶剂信息对象(可选),包含: additional_solvent(溶剂体积mL), total_liquid_volume(总液体体积mL)。如果提供,将自动计算volume'
|
||||
type: string
|
||||
temperature:
|
||||
default: '25.00'
|
||||
default: 25.0
|
||||
description: 温度设定(°C),默认25.00
|
||||
type: string
|
||||
type: number
|
||||
time:
|
||||
default: '360'
|
||||
description: 观察时间(分钟),默认360
|
||||
type: string
|
||||
titration_type:
|
||||
default: 'NO'
|
||||
default: '1'
|
||||
description: 是否滴定(NO=否, YES=是),默认NO
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 'YES'
|
||||
default: 2
|
||||
description: 是否观察 (NO=否, YES=是),默认YES
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
type: integer
|
||||
volume:
|
||||
description: 分液量(mL)。可直接提供,或通过solvents参数自动计算
|
||||
type: string
|
||||
@@ -655,15 +636,15 @@ reaction_station.bioyond:
|
||||
volume_formula: volume_formula
|
||||
x_value: x_value
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
extracted_actuals: ''
|
||||
feeding_order_data: ''
|
||||
temperature: '25.00'
|
||||
assign_material_name: null
|
||||
extracted_actuals: null
|
||||
feeding_order_data: null
|
||||
temperature: 25.0
|
||||
time: '90'
|
||||
titration_type: '2'
|
||||
torque_variation: '2'
|
||||
volume_formula: ''
|
||||
x_value: ''
|
||||
torque_variation: 2
|
||||
volume_formula: null
|
||||
x_value: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: extracted_actuals
|
||||
@@ -678,6 +659,7 @@ reaction_station.bioyond:
|
||||
handler_key: feeding_order
|
||||
io_type: source
|
||||
label: Feeding Order Data From Calculation Node
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 液体进料(滴定)。支持两种模式:1)直接提供volume_formula;2)自动计算-提供x_value+feeding_order_data+extracted_actuals,系统自动生成公式"1000*(m二酐-x)*V二酐滴定/m二酐滴定"
|
||||
@@ -696,27 +678,21 @@ reaction_station.bioyond:
|
||||
{"feeding_order": [{"type": "main_anhydride", "amount": 1.915}]}'
|
||||
type: string
|
||||
temperature:
|
||||
default: '25.00'
|
||||
default: 25.0
|
||||
description: 温度设定(°C),默认25.00
|
||||
type: string
|
||||
type: number
|
||||
time:
|
||||
default: '90'
|
||||
description: 观察时间(分钟),默认90
|
||||
type: string
|
||||
titration_type:
|
||||
default: 'YES'
|
||||
default: '2'
|
||||
description: 是否滴定(NO=否, YES=是),默认YES
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 'YES'
|
||||
default: 2
|
||||
description: 是否观察 (NO=否, YES=是),默认YES
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
type: integer
|
||||
volume_formula:
|
||||
description: 分液公式(mL)。可直接提供固定公式,或留空由系统根据x_value、feeding_order_data、extracted_actuals自动生成
|
||||
type: string
|
||||
@@ -742,13 +718,14 @@ reaction_station.bioyond:
|
||||
torque_variation: torque_variation
|
||||
volume_formula: volume_formula
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
titration_type: ''
|
||||
torque_variation: ''
|
||||
volume_formula: ''
|
||||
assign_material_name: null
|
||||
temperature: 25.0
|
||||
time: '0'
|
||||
titration_type: '1'
|
||||
torque_variation: 1
|
||||
volume_formula: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 液体进料小瓶(非滴定)
|
||||
@@ -760,33 +737,27 @@ reaction_station.bioyond:
|
||||
description: 物料名称
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
type: number
|
||||
time:
|
||||
default: '0'
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
titration_type:
|
||||
default: '1'
|
||||
description: 是否滴定(NO=否, YES=是)
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 1
|
||||
description: 是否观察 (NO=否, YES=是)
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
type: integer
|
||||
volume_formula:
|
||||
description: 分液公式(mL)
|
||||
type: string
|
||||
required:
|
||||
- volume_formula
|
||||
- assign_material_name
|
||||
- time
|
||||
- torque_variation
|
||||
- titration_type
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
@@ -800,9 +771,10 @@ reaction_station.bioyond:
|
||||
task_name: task_name
|
||||
workflow_name: workflow_name
|
||||
goal_default:
|
||||
task_name: ''
|
||||
workflow_name: ''
|
||||
task_name: null
|
||||
workflow_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 处理并执行工作流
|
||||
@@ -820,7 +792,8 @@ reaction_station.bioyond:
|
||||
- workflow_name
|
||||
- task_name
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: process_and_execute_workflow参数
|
||||
@@ -833,10 +806,11 @@ reaction_station.bioyond:
|
||||
cutoff: cutoff
|
||||
temperature: temperature
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
cutoff: ''
|
||||
temperature: ''
|
||||
assign_material_name: null
|
||||
cutoff: '900000'
|
||||
temperature: -10.0
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应器放入 - 将反应器放入工作站,配置物料名称、粘度上限和温度参数
|
||||
@@ -848,14 +822,14 @@ reaction_station.bioyond:
|
||||
description: 物料名称
|
||||
type: string
|
||||
cutoff:
|
||||
default: '900000'
|
||||
description: 粘度上限
|
||||
type: string
|
||||
temperature:
|
||||
default: -10.0
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
type: number
|
||||
required:
|
||||
- cutoff
|
||||
- temperature
|
||||
- assign_material_name
|
||||
type: object
|
||||
result: {}
|
||||
@@ -869,6 +843,7 @@ reaction_station.bioyond:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应器取出 - 从工作站中取出反应器,无需参数的简单操作
|
||||
@@ -878,15 +853,7 @@ reaction_station.bioyond:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
code:
|
||||
description: 操作结果代码(1表示成功,0表示失败)
|
||||
type: integer
|
||||
return_info:
|
||||
description: 操作结果详细信息
|
||||
type: string
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: reactor_taken_out参数
|
||||
@@ -897,8 +864,8 @@ reaction_station.bioyond:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动调度器 - 启动Bioyond工作站的任务调度器,开始执行队列中的任务
|
||||
properties:
|
||||
@@ -908,12 +875,6 @@ reaction_station.bioyond:
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 调度器启动结果,成功返回1,失败返回0
|
||||
type: integer
|
||||
required:
|
||||
- return_info
|
||||
title: scheduler_start结果
|
||||
type: object
|
||||
required:
|
||||
@@ -930,12 +891,13 @@ reaction_station.bioyond:
|
||||
time: time
|
||||
torque_variation: torque_variation
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
material_id: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
torque_variation: ''
|
||||
assign_material_name: null
|
||||
material_id: null
|
||||
temperature: 25.0
|
||||
time: '0'
|
||||
torque_variation: 1
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 固体进料小瓶 - 通过小瓶向反应器中添加固体物料,支持多种粉末类型(盐、面粉、BTDA)
|
||||
@@ -948,29 +910,21 @@ reaction_station.bioyond:
|
||||
type: string
|
||||
material_id:
|
||||
description: 粉末类型ID,Salt=盐(21分钟),Flour=面粉(27分钟),BTDA=BTDA(38分钟)
|
||||
enum:
|
||||
- Salt
|
||||
- Flour
|
||||
- BTDA
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
type: number
|
||||
time:
|
||||
default: '0'
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 1
|
||||
description: 是否观察 (NO=否, YES=是)
|
||||
enum:
|
||||
- 'NO'
|
||||
- 'YES'
|
||||
type: string
|
||||
type: integer
|
||||
required:
|
||||
- assign_material_name
|
||||
- material_id
|
||||
- time
|
||||
- torque_variation
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
|
||||
@@ -37,42 +37,41 @@ agv.SEER:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
send_nav_task:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -122,31 +122,6 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
||||
title: moveit_task参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -198,41 +173,41 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pick_and_place:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -241,41 +216,41 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
||||
type: object
|
||||
type: SendCmd
|
||||
set_position:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -284,41 +259,41 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
||||
type: object
|
||||
type: SendCmd
|
||||
set_status:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -455,42 +430,41 @@ robotic_arm.UR:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
move_pos_task:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -532,8 +506,8 @@ robotic_arm.UR:
|
||||
type: string
|
||||
required:
|
||||
- arm_pose
|
||||
- gripper_pose
|
||||
- arm_status
|
||||
- gripper_pose
|
||||
- gripper_status
|
||||
type: object
|
||||
version: 1.0.0
|
||||
@@ -726,41 +700,41 @@ robotic_arm.elite:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
modbus_task_cmd:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -770,8 +744,8 @@ robotic_arm.elite:
|
||||
type: SendCmd
|
||||
module: unilabos.devices.arm.elite_robot:EliteRobot
|
||||
status_types:
|
||||
actual_joint_positions: String
|
||||
arm_pose: String
|
||||
actual_joint_positions: ''
|
||||
arm_pose: list[float]
|
||||
type: python
|
||||
config_info: []
|
||||
description: Elite robot arm
|
||||
@@ -797,8 +771,8 @@ robotic_arm.elite:
|
||||
type: number
|
||||
type: array
|
||||
required:
|
||||
- arm_pose
|
||||
- actual_joint_positions
|
||||
- arm_pose
|
||||
type: object
|
||||
model:
|
||||
mesh: elite_robot
|
||||
|
||||
@@ -114,11 +114,12 @@ gripper.misumi_rz:
|
||||
goal:
|
||||
properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: modbus_crc参数
|
||||
@@ -398,30 +399,26 @@ gripper.misumi_rz:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -504,71 +501,82 @@ gripper.mock:
|
||||
type: UniLabJsonCommand
|
||||
push_to:
|
||||
feedback:
|
||||
effort: torque
|
||||
effort: effort
|
||||
position: position
|
||||
reached_goal: reached_goal
|
||||
stalled: stalled
|
||||
goal:
|
||||
command.max_effort: torque
|
||||
command.position: position
|
||||
command: command
|
||||
position: position
|
||||
torque: torque
|
||||
velocity: velocity
|
||||
goal_default:
|
||||
command:
|
||||
max_effort: 0.0
|
||||
position: 0.0
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
effort: torque
|
||||
effort: effort
|
||||
position: position
|
||||
reached_goal: reached_goal
|
||||
stalled: stalled
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
effort:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
position:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
reached_goal:
|
||||
type: boolean
|
||||
stalled:
|
||||
type: boolean
|
||||
required:
|
||||
- position
|
||||
- effort
|
||||
- stalled
|
||||
- reached_goal
|
||||
title: GripperCommand_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
max_effort:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
position:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- position
|
||||
- max_effort
|
||||
title: command
|
||||
type: object
|
||||
required:
|
||||
- command
|
||||
title: GripperCommand_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
effort:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
position:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
reached_goal:
|
||||
type: boolean
|
||||
stalled:
|
||||
type: boolean
|
||||
required:
|
||||
- position
|
||||
- effort
|
||||
- stalled
|
||||
- reached_goal
|
||||
title: GripperCommand_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -604,8 +612,8 @@ gripper.mock:
|
||||
type: number
|
||||
required:
|
||||
- position
|
||||
- velocity
|
||||
- torque
|
||||
- status
|
||||
- torque
|
||||
- velocity
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -24,6 +24,27 @@ linear_motion.grbl:
|
||||
title: initialize参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -93,44 +114,39 @@ linear_motion.grbl:
|
||||
type: UniLabJsonCommandAsync
|
||||
move_through_points:
|
||||
feedback:
|
||||
current_pose.pose.position: position
|
||||
estimated_time_remaining.sec: time_remaining
|
||||
navigation_time.sec: time_spent
|
||||
number_of_poses_remaining: pose_number_remaining
|
||||
current_pose: current_pose
|
||||
distance_remaining: distance_remaining
|
||||
estimated_time_remaining: estimated_time_remaining
|
||||
navigation_time: navigation_time
|
||||
number_of_poses_remaining: number_of_poses_remaining
|
||||
number_of_recoveries: number_of_recoveries
|
||||
goal:
|
||||
poses[].pose.position: positions[]
|
||||
behavior_tree: behavior_tree
|
||||
poses: poses
|
||||
positions: positions
|
||||
goal_default:
|
||||
behavior_tree: ''
|
||||
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
|
||||
poses: []
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
result: result
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
current_pose:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
header:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
frame_id:
|
||||
type: string
|
||||
stamp:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
nanosec:
|
||||
maximum: 4294967295
|
||||
@@ -151,16 +167,26 @@ linear_motion.grbl:
|
||||
title: header
|
||||
type: object
|
||||
pose:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
orientation:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
w:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
x:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
y:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
z:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
@@ -170,12 +196,19 @@ linear_motion.grbl:
|
||||
title: orientation
|
||||
type: object
|
||||
position:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
x:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
y:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
z:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
@@ -194,8 +227,11 @@ linear_motion.grbl:
|
||||
title: current_pose
|
||||
type: object
|
||||
distance_remaining:
|
||||
maximum: 3.4028235e+38
|
||||
minimum: -3.4028235e+38
|
||||
type: number
|
||||
estimated_time_remaining:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
nanosec:
|
||||
maximum: 4294967295
|
||||
@@ -211,6 +247,7 @@ linear_motion.grbl:
|
||||
title: estimated_time_remaining
|
||||
type: object
|
||||
navigation_time:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
nanosec:
|
||||
maximum: 4294967295
|
||||
@@ -233,16 +270,10 @@ linear_motion.grbl:
|
||||
maximum: 32767
|
||||
minimum: -32768
|
||||
type: integer
|
||||
required:
|
||||
- current_pose
|
||||
- navigation_time
|
||||
- estimated_time_remaining
|
||||
- number_of_recoveries
|
||||
- distance_remaining
|
||||
- number_of_poses_remaining
|
||||
title: NavigateThroughPoses_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
behavior_tree:
|
||||
type: string
|
||||
@@ -256,12 +287,8 @@ linear_motion.grbl:
|
||||
stamp:
|
||||
properties:
|
||||
nanosec:
|
||||
maximum: 4294967295
|
||||
minimum: 0
|
||||
type: integer
|
||||
sec:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
required:
|
||||
- sec
|
||||
@@ -314,23 +341,17 @@ linear_motion.grbl:
|
||||
required:
|
||||
- header
|
||||
- pose
|
||||
title: poses
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- poses
|
||||
- behavior_tree
|
||||
title: NavigateThroughPoses_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
result:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: result
|
||||
type: object
|
||||
required:
|
||||
- result
|
||||
title: NavigateThroughPoses_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -340,9 +361,15 @@ linear_motion.grbl:
|
||||
type: NavigateThroughPoses
|
||||
set_spindle_speed:
|
||||
feedback:
|
||||
position: spindle_speed
|
||||
error: error
|
||||
header: header
|
||||
position: position
|
||||
velocity: velocity
|
||||
goal:
|
||||
position: spindle_speed
|
||||
max_velocity: max_velocity
|
||||
min_duration: min_duration
|
||||
position: position
|
||||
spindle_speed: spindle_speed
|
||||
goal_default:
|
||||
max_velocity: 0.0
|
||||
min_duration:
|
||||
@@ -350,19 +377,25 @@ linear_motion.grbl:
|
||||
sec: 0
|
||||
position: 0.0
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
error:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
header:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
frame_id:
|
||||
type: string
|
||||
stamp:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
nanosec:
|
||||
maximum: 4294967295
|
||||
@@ -383,21 +416,24 @@ linear_motion.grbl:
|
||||
title: header
|
||||
type: object
|
||||
position:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
velocity:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- header
|
||||
- position
|
||||
- velocity
|
||||
- error
|
||||
title: SingleJointPosition_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
max_velocity:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
min_duration:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
nanosec:
|
||||
maximum: 4294967295
|
||||
@@ -413,16 +449,13 @@ linear_motion.grbl:
|
||||
title: min_duration
|
||||
type: object
|
||||
position:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- position
|
||||
- min_duration
|
||||
- max_velocity
|
||||
title: SingleJointPosition_Goal
|
||||
type: object
|
||||
result:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: SingleJointPosition_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -432,7 +465,7 @@ linear_motion.grbl:
|
||||
type: SingleJointPosition
|
||||
module: unilabos.devices.cnc.grbl_sync:GrblCNC
|
||||
status_types:
|
||||
position: unilabos.messages:Point3D
|
||||
position: Point3D
|
||||
spindle_speed: float
|
||||
status: str
|
||||
type: python
|
||||
@@ -471,9 +504,9 @@ linear_motion.grbl:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- position
|
||||
- spindle_speed
|
||||
- status
|
||||
type: object
|
||||
version: 1.0.0
|
||||
linear_motion.toyo_xyz.sim:
|
||||
@@ -600,31 +633,6 @@ linear_motion.toyo_xyz.sim:
|
||||
title: moveit_task参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -676,41 +684,41 @@ linear_motion.toyo_xyz.sim:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pick_and_place:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -719,41 +727,41 @@ linear_motion.toyo_xyz.sim:
|
||||
type: object
|
||||
type: SendCmd
|
||||
set_position:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -762,41 +770,41 @@ linear_motion.toyo_xyz.sim:
|
||||
type: object
|
||||
type: SendCmd
|
||||
set_status:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -939,30 +947,26 @@ motor.iCL42:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -1000,8 +1004,8 @@ motor.iCL42:
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- motor_position
|
||||
- is_executing_run
|
||||
- motor_position
|
||||
- success
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -14,19 +14,24 @@ solid_dispenser.laiyu:
|
||||
powder_tube_number: 0
|
||||
target_tube_position: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
actual_mass_mg: actual_mass_mg
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: SolidDispenseAddPowderTube_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
compound_mass:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
powder_tube_number:
|
||||
maximum: 2147483647
|
||||
@@ -34,24 +39,19 @@ solid_dispenser.laiyu:
|
||||
type: integer
|
||||
target_tube_position:
|
||||
type: string
|
||||
required:
|
||||
- powder_tube_number
|
||||
- target_tube_position
|
||||
- compound_mass
|
||||
title: SolidDispenseAddPowderTube_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
actual_mass_mg:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- actual_mass_mg
|
||||
- success
|
||||
title: SolidDispenseAddPowderTube_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -74,11 +74,12 @@ solid_dispenser.laiyu:
|
||||
goal:
|
||||
properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: calculate_crc参数
|
||||
@@ -99,11 +100,12 @@ solid_dispenser.laiyu:
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: send_command参数
|
||||
@@ -112,36 +114,37 @@ solid_dispenser.laiyu:
|
||||
discharge:
|
||||
feedback: {}
|
||||
goal:
|
||||
float_input: float_input
|
||||
float_in: float_in
|
||||
goal_default:
|
||||
float_in: 0.0
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: FloatSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
float_in:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- float_in
|
||||
title: FloatSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: FloatSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -156,32 +159,31 @@ solid_dispenser.laiyu:
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -200,38 +202,41 @@ solid_dispenser.laiyu:
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: Point3DSeparateInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
x:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
y:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
z:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
title: Point3DSeparateInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: Point3DSeparateInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -246,34 +251,33 @@ solid_dispenser.laiyu:
|
||||
goal_default:
|
||||
int_input: 0
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: IntSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
int_input:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
required:
|
||||
- int_input
|
||||
title: IntSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: IntSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -288,34 +292,33 @@ solid_dispenser.laiyu:
|
||||
goal_default:
|
||||
int_input: 0
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: IntSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
int_input:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
required:
|
||||
- int_input
|
||||
title: IntSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: IntSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -328,26 +331,25 @@ solid_dispenser.laiyu:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
|
||||
@@ -34,7 +34,8 @@ chiller:
|
||||
- register_address
|
||||
- value
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: build_modbus_frame参数
|
||||
@@ -63,7 +64,8 @@ chiller:
|
||||
required:
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: integer
|
||||
required:
|
||||
- goal
|
||||
title: convert_temperature_to_modbus_value参数
|
||||
@@ -84,11 +86,12 @@ chiller:
|
||||
goal:
|
||||
properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: modbus_crc参数
|
||||
@@ -116,42 +119,41 @@ chiller:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
set_temperature:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -266,9 +268,15 @@ heaterstirrer.dalong:
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
pressure: pressure
|
||||
purpose: purpose
|
||||
reflux_solvent: reflux_solvent
|
||||
stir: stir
|
||||
stir_speed: stir_speed
|
||||
temp: temp
|
||||
temp_spec: temp_spec
|
||||
time: time
|
||||
time_spec: time_spec
|
||||
vessel: vessel
|
||||
goal_default:
|
||||
pressure: ''
|
||||
@@ -301,20 +309,23 @@ heaterstirrer.dalong:
|
||||
sample_id: ''
|
||||
type: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
message: message
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: HeatChill_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
pressure:
|
||||
type: string
|
||||
@@ -325,8 +336,12 @@ heaterstirrer.dalong:
|
||||
stir:
|
||||
type: boolean
|
||||
stir_speed:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
temp:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
temp_spec:
|
||||
type: string
|
||||
@@ -335,6 +350,7 @@ heaterstirrer.dalong:
|
||||
time_spec:
|
||||
type: string
|
||||
vessel:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
@@ -353,16 +369,26 @@ heaterstirrer.dalong:
|
||||
parent:
|
||||
type: string
|
||||
pose:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
orientation:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
w:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
x:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
y:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
z:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
@@ -372,12 +398,19 @@ heaterstirrer.dalong:
|
||||
title: orientation
|
||||
type: object
|
||||
position:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
x:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
y:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
z:
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
@@ -407,20 +440,10 @@ heaterstirrer.dalong:
|
||||
- data
|
||||
title: vessel
|
||||
type: object
|
||||
required:
|
||||
- vessel
|
||||
- temp
|
||||
- time
|
||||
- temp_spec
|
||||
- time_spec
|
||||
- pressure
|
||||
- reflux_solvent
|
||||
- stir
|
||||
- stir_speed
|
||||
- purpose
|
||||
title: HeatChill_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
@@ -428,10 +451,6 @@ heaterstirrer.dalong:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- success
|
||||
- message
|
||||
- return_info
|
||||
title: HeatChill_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -440,42 +459,42 @@ heaterstirrer.dalong:
|
||||
type: object
|
||||
type: HeatChill
|
||||
set_temp_target:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: temp
|
||||
command: command
|
||||
temp: temp
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -484,42 +503,42 @@ heaterstirrer.dalong:
|
||||
type: object
|
||||
type: SendCmd
|
||||
set_temp_warning:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: temp
|
||||
command: command
|
||||
temp: temp
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -569,8 +588,8 @@ heaterstirrer.dalong:
|
||||
- status
|
||||
- stir_speed
|
||||
- temp
|
||||
- temp_warning
|
||||
- temp_target
|
||||
- temp_warning
|
||||
type: object
|
||||
version: 1.0.0
|
||||
tempsensor:
|
||||
@@ -691,42 +710,41 @@ tempsensor:
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
set_warning:
|
||||
feedback: {}
|
||||
feedback:
|
||||
status: status
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -45,31 +45,6 @@ xrd_d7mate:
|
||||
title: connect参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -85,11 +60,14 @@ xrd_d7mate:
|
||||
goal:
|
||||
properties:
|
||||
params:
|
||||
type: string
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: object
|
||||
required:
|
||||
- params
|
||||
type: object
|
||||
result: {}
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: start_from_string参数
|
||||
@@ -105,21 +83,18 @@ xrd_d7mate:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -130,38 +105,38 @@ xrd_d7mate:
|
||||
get_sample_down:
|
||||
feedback: {}
|
||||
goal:
|
||||
sample_station: 1
|
||||
int_input: int_input
|
||||
sample_station: sample_station
|
||||
goal_default:
|
||||
int_input: 0
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: IntSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
int_input:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
required:
|
||||
- int_input
|
||||
title: IntSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: IntSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -179,21 +154,18 @@ xrd_d7mate:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -211,21 +183,18 @@ xrd_d7mate:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -238,26 +207,25 @@ xrd_d7mate:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -274,42 +242,35 @@ xrd_d7mate:
|
||||
sample_id: ''
|
||||
start_theta: 10.0
|
||||
goal_default:
|
||||
end_theta: 80.0
|
||||
exp_time: 0.5
|
||||
increment: 0.02
|
||||
sample_id: Sample001
|
||||
start_theta: 10.0
|
||||
end_theta: null
|
||||
exp_time: null
|
||||
increment: null
|
||||
sample_id: null
|
||||
start_theta: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 送样完成后,发送样品信息和采集参数
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: SampleReadyInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
end_theta:
|
||||
description: 结束角度(≥5.5°,且必须大于start_theta)
|
||||
minimum: 5.5
|
||||
type: number
|
||||
exp_time:
|
||||
description: 曝光时间(0.1-5.0秒)
|
||||
maximum: 5.0
|
||||
minimum: 0.1
|
||||
type: number
|
||||
increment:
|
||||
description: 角度增量(≥0.005)
|
||||
minimum: 0.005
|
||||
type: number
|
||||
sample_id:
|
||||
description: 样品标识符
|
||||
type: string
|
||||
start_theta:
|
||||
description: 起始角度(≥5°)
|
||||
minimum: 5.0
|
||||
type: number
|
||||
required:
|
||||
- sample_id
|
||||
@@ -320,19 +281,11 @@ xrd_d7mate:
|
||||
title: SampleReadyInput_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SampleReadyInput_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SampleReadyInput
|
||||
title: send_sample_ready参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
set_power_off:
|
||||
@@ -340,26 +293,25 @@ xrd_d7mate:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -372,26 +324,25 @@ xrd_d7mate:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -405,18 +356,16 @@ xrd_d7mate:
|
||||
current: 30.0
|
||||
voltage: 40.0
|
||||
goal_default:
|
||||
current: 30.0
|
||||
voltage: 40.0
|
||||
current: null
|
||||
voltage: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 设置高压电源电压和电流
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: VoltageCurrentInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
current:
|
||||
@@ -431,19 +380,11 @@ xrd_d7mate:
|
||||
title: VoltageCurrentInput_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: VoltageCurrentInput_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: VoltageCurrentInput
|
||||
title: set_voltage_current参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
start:
|
||||
@@ -453,11 +394,12 @@ xrd_d7mate:
|
||||
end_theta: 80.0
|
||||
exp_time: 0.1
|
||||
increment: 0.05
|
||||
sample_id: 样品名称
|
||||
sample_id: ''
|
||||
start_theta: 10.0
|
||||
string: ''
|
||||
wait_minutes: 3.0
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动自动模式→上样→等待→样品准备→监控→检测下样位→执行下样流程。
|
||||
@@ -466,54 +408,42 @@ xrd_d7mate:
|
||||
goal:
|
||||
properties:
|
||||
end_theta:
|
||||
default: 80.0
|
||||
description: 结束角度(≥5.5°,且必须大于start_theta)
|
||||
minimum: 5.5
|
||||
type: string
|
||||
type: number
|
||||
exp_time:
|
||||
default: 0.1
|
||||
description: 曝光时间(0.1-5.0秒)
|
||||
maximum: 5.0
|
||||
minimum: 0.1
|
||||
type: string
|
||||
type: number
|
||||
increment:
|
||||
default: 0.05
|
||||
description: 角度增量(≥0.005)
|
||||
minimum: 0.005
|
||||
type: string
|
||||
type: number
|
||||
sample_id:
|
||||
default: ''
|
||||
description: 样品标识符
|
||||
type: string
|
||||
start_theta:
|
||||
default: 10.0
|
||||
description: 起始角度(≥5°)
|
||||
minimum: 5.0
|
||||
type: string
|
||||
type: number
|
||||
string:
|
||||
default: ''
|
||||
description: 字符串格式的参数输入,如果提供则优先解析使用
|
||||
type: string
|
||||
wait_minutes:
|
||||
default: 3.0
|
||||
description: 允许上样后等待分钟数
|
||||
minimum: 0.0
|
||||
type: number
|
||||
required:
|
||||
- sample_id
|
||||
- start_theta
|
||||
- end_theta
|
||||
- increment
|
||||
- exp_time
|
||||
required: []
|
||||
title: StartWorkflow_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StartWorkflow_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: StartWorkflow
|
||||
title: start参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
start_auto_mode:
|
||||
@@ -521,17 +451,15 @@ xrd_d7mate:
|
||||
goal:
|
||||
status: true
|
||||
goal_default:
|
||||
status: true
|
||||
status: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动或停止自动模式
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: BoolSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
status:
|
||||
@@ -542,25 +470,16 @@ xrd_d7mate:
|
||||
title: BoolSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: BoolSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: BoolSingleInput
|
||||
title: start_auto_mode参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.xrd_d7mate.xrd_d7mate:XRDClient
|
||||
status_types:
|
||||
current_acquire_data: dict
|
||||
sample_down: dict
|
||||
sample_request: dict
|
||||
sample_status: dict
|
||||
type: python
|
||||
@@ -586,16 +505,13 @@ xrd_d7mate:
|
||||
properties:
|
||||
current_acquire_data:
|
||||
type: object
|
||||
sample_down:
|
||||
type: object
|
||||
sample_request:
|
||||
type: object
|
||||
sample_status:
|
||||
type: object
|
||||
required:
|
||||
- sample_request
|
||||
- current_acquire_data
|
||||
- sample_request
|
||||
- sample_status
|
||||
- sample_down
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -8,26 +8,25 @@ zhida_gcms:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -77,31 +76,6 @@ zhida_gcms:
|
||||
title: connect参数
|
||||
type: object
|
||||
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:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -112,21 +86,18 @@ zhida_gcms:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -144,21 +115,18 @@ zhida_gcms:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -176,21 +144,18 @@ zhida_gcms:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -203,26 +168,25 @@ zhida_gcms:
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: EmptyIn_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -234,35 +198,35 @@ zhida_gcms:
|
||||
feedback: {}
|
||||
goal:
|
||||
string: string
|
||||
text: text
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -273,36 +237,36 @@ zhida_gcms:
|
||||
start_with_csv_file:
|
||||
feedback: {}
|
||||
goal:
|
||||
csv_file_path: csv_file_path
|
||||
string: string
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
additionalProperties: true
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: StrSingleInput_Result
|
||||
type: object
|
||||
required:
|
||||
@@ -343,8 +307,8 @@ zhida_gcms:
|
||||
version:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- methods
|
||||
- status
|
||||
- version
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@ YB_20ml_fenyeping:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_5ml_fenyeping:
|
||||
category:
|
||||
@@ -22,7 +21,6 @@ YB_5ml_fenyeping:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_jia_yang_tou_da:
|
||||
category:
|
||||
@@ -35,7 +33,6 @@ YB_jia_yang_tou_da:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_pei_ye_da_Bottle:
|
||||
category:
|
||||
@@ -48,7 +45,6 @@ YB_pei_ye_da_Bottle:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_pei_ye_xiao_Bottle:
|
||||
category:
|
||||
@@ -61,7 +57,6 @@ YB_pei_ye_xiao_Bottle:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_qiang_tou:
|
||||
category:
|
||||
@@ -74,7 +69,6 @@ YB_qiang_tou:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_ye_Bottle:
|
||||
category:
|
||||
@@ -88,5 +82,4 @@ YB_ye_Bottle:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -9,7 +9,6 @@ YB_100ml_yeti:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_20ml_fenyepingban:
|
||||
category:
|
||||
@@ -22,7 +21,6 @@ YB_20ml_fenyepingban:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_5ml_fenyepingban:
|
||||
category:
|
||||
@@ -35,7 +33,6 @@ YB_5ml_fenyepingban:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_6StockCarrier:
|
||||
category:
|
||||
@@ -48,7 +45,6 @@ YB_6StockCarrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_6VialCarrier:
|
||||
category:
|
||||
@@ -61,7 +57,6 @@ YB_6VialCarrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_gao_nian_ye_Bottle:
|
||||
category:
|
||||
@@ -74,7 +69,6 @@ YB_gao_nian_ye_Bottle:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_gaonianye:
|
||||
category:
|
||||
@@ -87,7 +81,6 @@ YB_gaonianye:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_jia_yang_tou_da_Carrier:
|
||||
category:
|
||||
@@ -100,7 +93,6 @@ YB_jia_yang_tou_da_Carrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_peiyepingdaban:
|
||||
category:
|
||||
@@ -113,7 +105,6 @@ YB_peiyepingdaban:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_peiyepingxiaoban:
|
||||
category:
|
||||
@@ -126,7 +117,6 @@ YB_peiyepingxiaoban:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_qiang_tou_he:
|
||||
category:
|
||||
@@ -139,7 +129,6 @@ YB_qiang_tou_he:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_shi_pei_qi_kuai:
|
||||
category:
|
||||
@@ -152,7 +141,6 @@ YB_shi_pei_qi_kuai:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_ye:
|
||||
category:
|
||||
@@ -165,7 +153,6 @@ YB_ye:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_ye_100ml_Bottle:
|
||||
category:
|
||||
@@ -178,5 +165,4 @@ YB_ye_100ml_Bottle:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -8,7 +8,6 @@ BIOYOND_PolymerStation_1BottleCarrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_1FlaskCarrier:
|
||||
category:
|
||||
@@ -20,7 +19,6 @@ BIOYOND_PolymerStation_1FlaskCarrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_6StockCarrier:
|
||||
category:
|
||||
@@ -32,7 +30,6 @@ BIOYOND_PolymerStation_6StockCarrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_8StockCarrier:
|
||||
category:
|
||||
@@ -44,5 +41,4 @@ BIOYOND_PolymerStation_8StockCarrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -8,7 +8,6 @@ BIOYOND_PolymerPreparationStation_Deck:
|
||||
handles: []
|
||||
icon: 配液站.webp
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerReactionStation_Deck:
|
||||
category:
|
||||
@@ -20,7 +19,6 @@ BIOYOND_PolymerReactionStation_Deck:
|
||||
handles: []
|
||||
icon: 反应站.webp
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
BIOYOND_YB_Deck:
|
||||
category:
|
||||
@@ -32,7 +30,6 @@ BIOYOND_YB_Deck:
|
||||
handles: []
|
||||
icon: 配液站.webp
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
CoincellDeck:
|
||||
category:
|
||||
@@ -44,5 +41,4 @@ CoincellDeck:
|
||||
handles: []
|
||||
icon: koudian.webp
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
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:
|
||||
category:
|
||||
- resource_container
|
||||
@@ -40,56 +19,6 @@ hplc_plate:
|
||||
- 3.1416
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/hplc_plate/modal.xacro
|
||||
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
|
||||
plate_96:
|
||||
category:
|
||||
@@ -112,7 +41,6 @@ plate_96:
|
||||
- 0
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96/modal.xacro
|
||||
type: resource
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
plate_96_high:
|
||||
category:
|
||||
@@ -135,35 +63,6 @@ plate_96_high:
|
||||
- 1.5708
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96_high/modal.xacro
|
||||
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
|
||||
tiprack_96_high:
|
||||
category:
|
||||
@@ -195,7 +94,6 @@ tiprack_96_high:
|
||||
- 1.5708
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_96_high/modal.xacro
|
||||
type: resource
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
tiprack_box:
|
||||
category:
|
||||
@@ -227,5 +125,4 @@ tiprack_box:
|
||||
- 0
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_box/modal.xacro
|
||||
type: resource
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -29,7 +29,6 @@ bottle_container:
|
||||
- 0
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/bottle_container/modal.xacro
|
||||
type: resource
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
tube_container:
|
||||
category:
|
||||
@@ -62,5 +61,4 @@ tube_container:
|
||||
- 0
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tube_container/modal.xacro
|
||||
type: resource
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -12,5 +12,4 @@ TransformXYZDeck:
|
||||
mesh: liquid_transform_xyz
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/liquid_transform_xyz/macro_device.xacro
|
||||
type: device
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -12,7 +12,6 @@ OTDeck:
|
||||
mesh: opentrons_liquid_handler
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/opentrons_liquid_handler/macro_device.xacro
|
||||
type: device
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
hplc_station:
|
||||
category:
|
||||
@@ -28,5 +27,4 @@ hplc_station:
|
||||
mesh: hplc_station
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/hplc_station/macro_device.xacro
|
||||
type: device
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -8,5 +8,4 @@ Opentrons_96_adapter_Vb:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -8,7 +8,6 @@ appliedbiosystemsmicroamp_384_wellplate_40ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
biorad_384_wellplate_50ul:
|
||||
category:
|
||||
@@ -20,7 +19,6 @@ biorad_384_wellplate_50ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
biorad_96_wellplate_200ul_pcr:
|
||||
category:
|
||||
@@ -32,7 +30,6 @@ biorad_96_wellplate_200ul_pcr:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
corning_12_wellplate_6point9ml_flat:
|
||||
category:
|
||||
@@ -44,7 +41,6 @@ corning_12_wellplate_6point9ml_flat:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
corning_24_wellplate_3point4ml_flat:
|
||||
category:
|
||||
@@ -56,7 +52,6 @@ corning_24_wellplate_3point4ml_flat:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
corning_384_wellplate_112ul_flat:
|
||||
category:
|
||||
@@ -68,7 +63,6 @@ corning_384_wellplate_112ul_flat:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
corning_48_wellplate_1point6ml_flat:
|
||||
category:
|
||||
@@ -80,7 +74,6 @@ corning_48_wellplate_1point6ml_flat:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
corning_6_wellplate_16point8ml_flat:
|
||||
category:
|
||||
@@ -92,7 +85,6 @@ corning_6_wellplate_16point8ml_flat:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
corning_96_wellplate_360ul_flat:
|
||||
category:
|
||||
@@ -104,7 +96,6 @@ corning_96_wellplate_360ul_flat:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
nest_96_wellplate_100ul_pcr_full_skirt:
|
||||
category:
|
||||
@@ -136,7 +127,6 @@ nest_96_wellplate_100ul_pcr_full_skirt:
|
||||
- 1.5708
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
||||
type: resource
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
nest_96_wellplate_200ul_flat:
|
||||
category:
|
||||
@@ -148,7 +138,6 @@ nest_96_wellplate_200ul_flat:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
nest_96_wellplate_2ml_deep:
|
||||
category:
|
||||
@@ -171,7 +160,6 @@ nest_96_wellplate_2ml_deep:
|
||||
- 1.5708
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
||||
type: resource
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
thermoscientificnunc_96_wellplate_1300ul:
|
||||
category:
|
||||
@@ -183,7 +171,6 @@ thermoscientificnunc_96_wellplate_1300ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
thermoscientificnunc_96_wellplate_2000ul:
|
||||
category:
|
||||
@@ -195,7 +182,6 @@ thermoscientificnunc_96_wellplate_2000ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
usascientific_96_wellplate_2point4ml_deep:
|
||||
category:
|
||||
@@ -207,5 +193,4 @@ usascientific_96_wellplate_2point4ml_deep:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -8,7 +8,6 @@ agilent_1_reservoir_290ml:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
axygen_1_reservoir_90ml:
|
||||
category:
|
||||
@@ -20,7 +19,6 @@ axygen_1_reservoir_90ml:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
nest_12_reservoir_15ml:
|
||||
category:
|
||||
@@ -32,7 +30,6 @@ nest_12_reservoir_15ml:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
nest_1_reservoir_195ml:
|
||||
category:
|
||||
@@ -44,7 +41,6 @@ nest_1_reservoir_195ml:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
nest_1_reservoir_290ml:
|
||||
category:
|
||||
@@ -56,7 +52,6 @@ nest_1_reservoir_290ml:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
usascientific_12_reservoir_22ml:
|
||||
category:
|
||||
@@ -68,5 +63,4 @@ usascientific_12_reservoir_22ml:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -8,7 +8,6 @@ eppendorf_96_tiprack_1000ul_eptips:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
eppendorf_96_tiprack_10ul_eptips:
|
||||
category:
|
||||
@@ -20,7 +19,6 @@ eppendorf_96_tiprack_10ul_eptips:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
geb_96_tiprack_1000ul:
|
||||
category:
|
||||
@@ -32,7 +30,6 @@ geb_96_tiprack_1000ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
geb_96_tiprack_10ul:
|
||||
category:
|
||||
@@ -44,7 +41,6 @@ geb_96_tiprack_10ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_filtertiprack_1000ul:
|
||||
category:
|
||||
@@ -75,7 +71,6 @@ opentrons_96_filtertiprack_1000ul:
|
||||
- 1.5708
|
||||
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
|
||||
type: resource
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_filtertiprack_10ul:
|
||||
category:
|
||||
@@ -87,7 +82,6 @@ opentrons_96_filtertiprack_10ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_filtertiprack_200ul:
|
||||
category:
|
||||
@@ -99,7 +93,6 @@ opentrons_96_filtertiprack_200ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_filtertiprack_20ul:
|
||||
category:
|
||||
@@ -111,7 +104,6 @@ opentrons_96_filtertiprack_20ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_tiprack_1000ul:
|
||||
category:
|
||||
@@ -123,7 +115,6 @@ opentrons_96_tiprack_1000ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_tiprack_10ul:
|
||||
category:
|
||||
@@ -135,7 +126,6 @@ opentrons_96_tiprack_10ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_tiprack_20ul:
|
||||
category:
|
||||
@@ -147,7 +137,6 @@ opentrons_96_tiprack_20ul:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_tiprack_300ul:
|
||||
category:
|
||||
|
||||
@@ -8,7 +8,6 @@ opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic:
|
||||
category:
|
||||
@@ -20,7 +19,6 @@ opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_10_tuberack_nest_4x50ml_6x15ml_conical:
|
||||
category:
|
||||
@@ -32,7 +30,6 @@ opentrons_10_tuberack_nest_4x50ml_6x15ml_conical:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_15_tuberack_falcon_15ml_conical:
|
||||
category:
|
||||
@@ -44,7 +41,6 @@ opentrons_15_tuberack_falcon_15ml_conical:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_15_tuberack_nest_15ml_conical:
|
||||
category:
|
||||
@@ -56,7 +52,6 @@ opentrons_15_tuberack_nest_15ml_conical:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_aluminumblock_generic_2ml_screwcap:
|
||||
category:
|
||||
@@ -68,7 +63,6 @@ opentrons_24_aluminumblock_generic_2ml_screwcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_aluminumblock_nest_1point5ml_snapcap:
|
||||
category:
|
||||
@@ -80,7 +74,6 @@ opentrons_24_aluminumblock_nest_1point5ml_snapcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_eppendorf_1point5ml_safelock_snapcap:
|
||||
category:
|
||||
@@ -92,7 +85,6 @@ opentrons_24_tuberack_eppendorf_1point5ml_safelock_snapcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap:
|
||||
category:
|
||||
@@ -104,7 +96,6 @@ opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic:
|
||||
category:
|
||||
@@ -116,7 +107,6 @@ opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_generic_0point75ml_snapcap_acrylic:
|
||||
category:
|
||||
@@ -128,7 +118,6 @@ opentrons_24_tuberack_generic_0point75ml_snapcap_acrylic:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_generic_2ml_screwcap:
|
||||
category:
|
||||
@@ -140,7 +129,6 @@ opentrons_24_tuberack_generic_2ml_screwcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_nest_0point5ml_screwcap:
|
||||
category:
|
||||
@@ -152,7 +140,6 @@ opentrons_24_tuberack_nest_0point5ml_screwcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_nest_1point5ml_screwcap:
|
||||
category:
|
||||
@@ -164,7 +151,6 @@ opentrons_24_tuberack_nest_1point5ml_screwcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_nest_1point5ml_snapcap:
|
||||
category:
|
||||
@@ -176,7 +162,6 @@ opentrons_24_tuberack_nest_1point5ml_snapcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_nest_2ml_screwcap:
|
||||
category:
|
||||
@@ -188,7 +173,6 @@ opentrons_24_tuberack_nest_2ml_screwcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_24_tuberack_nest_2ml_snapcap:
|
||||
category:
|
||||
@@ -200,7 +184,6 @@ opentrons_24_tuberack_nest_2ml_snapcap:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_6_tuberack_falcon_50ml_conical:
|
||||
category:
|
||||
@@ -212,7 +195,6 @@ opentrons_6_tuberack_falcon_50ml_conical:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_6_tuberack_nest_50ml_conical:
|
||||
category:
|
||||
@@ -224,7 +206,6 @@ opentrons_6_tuberack_nest_50ml_conical:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
opentrons_96_well_aluminum_block:
|
||||
category:
|
||||
@@ -236,5 +217,4 @@ opentrons_96_well_aluminum_block:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -29,5 +29,4 @@ container:
|
||||
side: WEST
|
||||
icon: Flask.webp
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -8,7 +8,6 @@ POST_PROCESS_Raw_1BottleCarrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
POST_PROCESS_Reaction_1BottleCarrier:
|
||||
category:
|
||||
@@ -20,5 +19,4 @@ POST_PROCESS_Reaction_1BottleCarrier:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -9,5 +9,4 @@ post_process_deck:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -9,7 +9,6 @@ PRCXI_30mm_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Adapter:
|
||||
category:
|
||||
@@ -22,7 +21,6 @@ PRCXI_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Deep10_Adapter:
|
||||
category:
|
||||
@@ -35,7 +33,6 @@ PRCXI_Deep10_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Deep300_Adapter:
|
||||
category:
|
||||
@@ -48,7 +45,6 @@ PRCXI_Deep300_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_PCR_Adapter:
|
||||
category:
|
||||
@@ -61,7 +57,6 @@ PRCXI_PCR_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Reservoir_Adapter:
|
||||
category:
|
||||
@@ -74,7 +69,6 @@ PRCXI_Reservoir_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Tip10_Adapter:
|
||||
category:
|
||||
@@ -87,7 +81,6 @@ PRCXI_Tip10_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Tip1250_Adapter:
|
||||
category:
|
||||
@@ -100,7 +93,6 @@ PRCXI_Tip1250_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_Tip300_Adapter:
|
||||
category:
|
||||
@@ -113,5 +105,4 @@ PRCXI_Tip300_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -9,7 +9,6 @@ PRCXI_48_DeepWell:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_96_DeepWell:
|
||||
category:
|
||||
@@ -22,7 +21,6 @@ PRCXI_96_DeepWell:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_AGenBio_4_troughplate:
|
||||
category:
|
||||
@@ -35,7 +33,6 @@ PRCXI_AGenBio_4_troughplate:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_BioER_96_wellplate:
|
||||
category:
|
||||
@@ -48,7 +45,6 @@ PRCXI_BioER_96_wellplate:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_BioRad_384_wellplate:
|
||||
category:
|
||||
@@ -61,7 +57,6 @@ PRCXI_BioRad_384_wellplate:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_CellTreat_96_wellplate:
|
||||
category:
|
||||
@@ -74,7 +69,6 @@ PRCXI_CellTreat_96_wellplate:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_PCR_Plate_200uL_nonskirted:
|
||||
category:
|
||||
@@ -87,7 +81,6 @@ PRCXI_PCR_Plate_200uL_nonskirted:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_PCR_Plate_200uL_semiskirted:
|
||||
category:
|
||||
@@ -100,7 +93,6 @@ PRCXI_PCR_Plate_200uL_semiskirted:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_PCR_Plate_200uL_skirted:
|
||||
category:
|
||||
@@ -113,7 +105,6 @@ PRCXI_PCR_Plate_200uL_skirted:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_nest_12_troughplate:
|
||||
category:
|
||||
@@ -126,7 +117,6 @@ PRCXI_nest_12_troughplate:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_nest_1_troughplate:
|
||||
category:
|
||||
@@ -139,5 +129,4 @@ PRCXI_nest_1_troughplate:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -9,7 +9,6 @@ PRCXI_1000uL_Tips:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_10uL_Tips:
|
||||
category:
|
||||
@@ -22,7 +21,6 @@ PRCXI_10uL_Tips:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_10ul_eTips:
|
||||
category:
|
||||
@@ -35,7 +33,6 @@ PRCXI_10ul_eTips:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_1250uL_Tips:
|
||||
category:
|
||||
@@ -48,7 +45,6 @@ PRCXI_1250uL_Tips:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_200uL_Tips:
|
||||
category:
|
||||
@@ -61,7 +57,6 @@ PRCXI_200uL_Tips:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
PRCXI_300ul_Tips:
|
||||
category:
|
||||
@@ -74,5 +69,4 @@ PRCXI_300ul_Tips:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -9,5 +9,4 @@ PRCXI_trash:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -9,5 +9,4 @@ PRCXI_EP_Adapter:
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -599,14 +599,39 @@ def wrap_action_schema(
|
||||
|
||||
|
||||
def preserve_field_descriptions(new_schema: Dict[str, Any], prev_schema: Dict[str, Any]):
|
||||
"""保留之前 schema 中的 field descriptions"""
|
||||
"""递归保留之前 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 in new_props and "title" in prev_field:
|
||||
new_props[field_name].setdefault("title", prev_field["title"])
|
||||
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)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -717,6 +717,19 @@ def ros_field_type_to_json_schema(
|
||||
# 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]:
|
||||
"""
|
||||
将 ROS 消息类转换为 JSON Schema
|
||||
@@ -730,7 +743,8 @@ def ros_message_to_json_schema(msg_class: Any, field_name: str) -> Dict[str, Any
|
||||
"""
|
||||
schema = ROS2MessageInstance(msg_class()).get_json_schema()
|
||||
schema["title"] = field_name
|
||||
schema.pop("description")
|
||||
schema.pop("description", None)
|
||||
_strip_rosidl_descriptions(schema)
|
||||
return schema
|
||||
|
||||
|
||||
@@ -777,6 +791,8 @@ def ros_action_to_json_schema(
|
||||
"required": ["goal"],
|
||||
}
|
||||
|
||||
_strip_rosidl_descriptions(schema)
|
||||
|
||||
# 保留之前 schema 中 goal/feedback/result 下一级字段的 description
|
||||
if previous_schema:
|
||||
_preserve_field_descriptions(schema, previous_schema)
|
||||
|
||||
@@ -1884,7 +1884,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
continue
|
||||
|
||||
# 处理单个 ResourceSlot
|
||||
if arg_type == "unilabos.registry.placeholder_type:ResourceSlot":
|
||||
_is_resource_slot = isinstance(arg_type, str) and arg_type.endswith(":ResourceSlot")
|
||||
if _is_resource_slot:
|
||||
resource_data = function_args[arg_name]
|
||||
if isinstance(resource_data, dict) and "id" in resource_data:
|
||||
try:
|
||||
@@ -1898,8 +1899,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
|
||||
# 处理 ResourceSlot 列表
|
||||
elif isinstance(arg_type, tuple) and len(arg_type) == 2:
|
||||
resource_slot_type = "unilabos.registry.placeholder_type:ResourceSlot"
|
||||
if arg_type[0] == "list" and arg_type[1] == resource_slot_type:
|
||||
if arg_type[0] == "list" and isinstance(arg_type[1], str) and arg_type[1].endswith(":ResourceSlot"):
|
||||
resource_list = function_args[arg_name]
|
||||
if isinstance(resource_list, list):
|
||||
try:
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Optional, Dict, Any, List, ClassVar, Set, Unio
|
||||
|
||||
from action_msgs.msg import GoalStatus
|
||||
from geometry_msgs.msg import Point
|
||||
from sensor_msgs.msg import JointState as JointStateMsg
|
||||
from rclpy.action import ActionClient, get_action_server_names_and_types_by_node
|
||||
from rclpy.service import Service
|
||||
from typing_extensions import TypedDict
|
||||
@@ -348,6 +349,10 @@ class HostNode(BaseROS2DeviceNode):
|
||||
else:
|
||||
self.lab_logger().warning(f"[Host Node] Device {device_id} already existed, skipping.")
|
||||
self.update_device_status_subscriptions()
|
||||
|
||||
# 订阅 joint_state_repub topic,桥接关节数据到云端
|
||||
self._init_joint_state_bridge()
|
||||
|
||||
# TODO: 需要验证 初始化所有控制器节点
|
||||
if controllers_config:
|
||||
update_rate = controllers_config["controller_manager"]["ros__parameters"]["update_rate"]
|
||||
@@ -782,6 +787,179 @@ class HostNode(BaseROS2DeviceNode):
|
||||
else:
|
||||
self.lab_logger().trace(f"Status updated: {device_id}.{property_name} = {msg.data}")
|
||||
|
||||
"""关节数据 & 资源跟随桥接"""
|
||||
|
||||
# 吞吐优化参数
|
||||
_JOINT_DEAD_BAND: float = 1e-4 # 关节角度变化小于此值视为无变化
|
||||
_JOINT_MIN_INTERVAL: float = 0.05 # 最小发送间隔 (秒),限制到 ~20Hz
|
||||
|
||||
def _init_joint_state_bridge(self):
|
||||
"""
|
||||
订阅 /joint_states (sensor_msgs/JointState) 和 resource_pose (String),
|
||||
构建 device_id → uuid 映射,并维护 resource_poses 状态。
|
||||
|
||||
吞吐优化:
|
||||
- 死区过滤 (dead band): 关节角度变化 < 阈值时不发送
|
||||
- 抑频 (throttle): 限制最大发送频率,避免 ROS2 1kHz 打满 WS
|
||||
- 增量 resource_poses: 仅在 resource_pose 实际变化时才附带发送
|
||||
"""
|
||||
# 构建 device_id → cloud_uuid 映射(从 devices_config 中获取)
|
||||
self._device_uuid_map: Dict[str, str] = {}
|
||||
for tree in self.devices_config.trees:
|
||||
node = tree.root_node
|
||||
if node.res_content.type == "device" and node.res_content.uuid:
|
||||
self._device_uuid_map[node.res_content.id] = node.res_content.uuid
|
||||
|
||||
# 按 device_id 长度降序排列,最长前缀优先匹配(避免 arm 抢先匹配 arm_left_j1)
|
||||
self._device_ids_sorted = sorted(self._device_uuid_map.keys(), key=len, reverse=True)
|
||||
|
||||
# 资源挂载状态:{resource_id: parent_link_name}
|
||||
self._resource_poses: Dict[str, str] = {}
|
||||
# resource_pose 变化标志,仅在真正变化时随关节数据发送
|
||||
self._resource_poses_dirty: bool = False
|
||||
|
||||
# 吞吐优化状态
|
||||
self._last_joint_values: Dict[str, float] = {} # 上次发送的关节值(全局)
|
||||
self._last_send_time: float = -float("inf") # 上次发送时间戳(初始为-inf确保首条通过)
|
||||
self._last_sent_resource_poses: Dict[str, str] = {} # 上次发送的 resource_poses 快照
|
||||
|
||||
if not self._device_uuid_map:
|
||||
self.lab_logger().debug("[Host Node] 无设备 UUID 映射,跳过关节桥接")
|
||||
return
|
||||
|
||||
# 直接订阅 /joint_states(sensor_msgs/JointState),无需经过 JointRepublisher
|
||||
self.create_subscription(
|
||||
JointStateMsg,
|
||||
"/joint_states",
|
||||
self._joint_state_callback,
|
||||
10,
|
||||
callback_group=self.callback_group,
|
||||
)
|
||||
|
||||
# 订阅 resource_pose(资源挂载变化,由 ResourceMeshManager 发布)
|
||||
from std_msgs.msg import String as StdString
|
||||
self.create_subscription(
|
||||
StdString,
|
||||
"resource_pose",
|
||||
self._resource_pose_callback,
|
||||
10,
|
||||
callback_group=self.callback_group,
|
||||
)
|
||||
|
||||
self.lab_logger().info(
|
||||
f"[Host Node] 已订阅 /joint_states 和 resource_pose,设备映射: {list(self._device_uuid_map.keys())}"
|
||||
)
|
||||
|
||||
def _resource_pose_callback(self, msg):
|
||||
"""
|
||||
接收 ResourceMeshManager 发布的资源挂载变更。
|
||||
|
||||
msg.data 格式: JSON dict,如 {"tip_rack_A1": "gripper_link", "plate_1": "deck_link"}
|
||||
空 dict {} 表示无变化(心跳包)。
|
||||
"""
|
||||
try:
|
||||
data = json.loads(msg.data)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
return
|
||||
if not isinstance(data, dict) or not data:
|
||||
return
|
||||
# 检测实际变化
|
||||
has_change = False
|
||||
for k, v in data.items():
|
||||
if self._resource_poses.get(k) != v:
|
||||
has_change = True
|
||||
break
|
||||
if has_change:
|
||||
self._resource_poses.update(data)
|
||||
self._resource_poses_dirty = True
|
||||
|
||||
def _joint_state_callback(self, msg: JointStateMsg):
|
||||
"""
|
||||
直接接收 /joint_states (sensor_msgs/JointState),按设备分组后通过 bridge 发送到云端。
|
||||
|
||||
吞吐优化:
|
||||
1. 抑频: 距上次发送 < _JOINT_MIN_INTERVAL 则跳过(除非有 resource_pose 变化)
|
||||
2. 死区: 所有关节角度变化 < _JOINT_DEAD_BAND 则跳过(除非有 resource_pose 变化)
|
||||
3. 增量 resource_poses: 仅在 dirty 时附带,否则发空 dict
|
||||
"""
|
||||
names = list(msg.name)
|
||||
positions = list(msg.position)
|
||||
if not names or len(names) != len(positions):
|
||||
return
|
||||
|
||||
now = time.time()
|
||||
resource_dirty = self._resource_poses_dirty
|
||||
|
||||
# 抑频检查:resource_pose 变化时强制发送
|
||||
if not resource_dirty and (now - self._last_send_time) < self._JOINT_MIN_INTERVAL:
|
||||
return
|
||||
|
||||
# 死区过滤:检测是否有关节值实质变化
|
||||
has_significant_change = False
|
||||
for name, pos in zip(names, positions):
|
||||
last_val = self._last_joint_values.get(name)
|
||||
if last_val is None or abs(float(pos) - last_val) >= self._JOINT_DEAD_BAND:
|
||||
has_significant_change = True
|
||||
break
|
||||
|
||||
# 无关节变化且无资源变化 → 跳过
|
||||
if not has_significant_change and not resource_dirty:
|
||||
return
|
||||
|
||||
# 更新上次发送的关节值
|
||||
for name, pos in zip(names, positions):
|
||||
self._last_joint_values[name] = float(pos)
|
||||
self._last_send_time = now
|
||||
|
||||
# 按设备 ID 分组关节数据(最长前缀优先匹配)
|
||||
device_joints: Dict[str, Dict[str, float]] = {}
|
||||
for name, pos in zip(names, positions):
|
||||
matched_device = None
|
||||
for device_id in self._device_ids_sorted:
|
||||
if name.startswith(device_id + "_"):
|
||||
matched_device = device_id
|
||||
break
|
||||
|
||||
if matched_device:
|
||||
if matched_device not in device_joints:
|
||||
device_joints[matched_device] = {}
|
||||
device_joints[matched_device][name] = float(pos)
|
||||
elif len(self._device_uuid_map) == 1:
|
||||
fallback_id = self._device_ids_sorted[0]
|
||||
if fallback_id not in device_joints:
|
||||
device_joints[fallback_id] = {}
|
||||
device_joints[fallback_id][name] = float(pos)
|
||||
|
||||
# 构建设备级 resource_poses(仅在 dirty 时附带实际数据)
|
||||
device_resource_poses: Dict[str, Dict[str, str]] = {}
|
||||
if resource_dirty:
|
||||
for resource_id, link_name in self._resource_poses.items():
|
||||
matched_device = None
|
||||
for device_id in self._device_ids_sorted:
|
||||
if link_name.startswith(device_id + "_"):
|
||||
matched_device = device_id
|
||||
break
|
||||
if matched_device:
|
||||
if matched_device not in device_resource_poses:
|
||||
device_resource_poses[matched_device] = {}
|
||||
device_resource_poses[matched_device][resource_id] = link_name
|
||||
elif len(self._device_uuid_map) == 1:
|
||||
fallback_id = self._device_ids_sorted[0]
|
||||
if fallback_id not in device_resource_poses:
|
||||
device_resource_poses[fallback_id] = {}
|
||||
device_resource_poses[fallback_id][resource_id] = link_name
|
||||
self._resource_poses_dirty = False
|
||||
|
||||
# 通过 bridge 发送 push_joint_state(含 resource_poses)
|
||||
for device_id, joint_states in device_joints.items():
|
||||
node_uuid = self._device_uuid_map.get(device_id)
|
||||
if not node_uuid:
|
||||
continue
|
||||
resource_poses = device_resource_poses.get(device_id, {})
|
||||
for bridge in self.bridges:
|
||||
if hasattr(bridge, "publish_joint_state"):
|
||||
bridge.publish_joint_state(node_uuid, joint_states, resource_poses)
|
||||
|
||||
def send_goal(
|
||||
self,
|
||||
item: "QueueItem",
|
||||
|
||||
@@ -41,7 +41,7 @@ class JointRepublisher(BaseROS2DeviceNode):
|
||||
json_dict["velocity"] = list(msg.velocity)
|
||||
json_dict["effort"] = list(msg.effort)
|
||||
|
||||
self.msg.data = str(json_dict)
|
||||
self.msg.data = json.dumps(json_dict)
|
||||
self.joint_repub.publish(self.msg)
|
||||
# print('-'*20)
|
||||
# print(self.msg.data)
|
||||
|
||||
@@ -6,20 +6,180 @@
|
||||
import argparse
|
||||
import importlib
|
||||
import locale
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from unilabos.utils.banner_print import print_status
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 底层安装工具
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _is_chinese_locale() -> bool:
|
||||
try:
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
return bool(lang and ("zh" in lang.lower() or "chinese" in lang.lower()))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
_USE_UV: Optional[bool] = None
|
||||
|
||||
|
||||
def _has_uv() -> bool:
|
||||
global _USE_UV
|
||||
if _USE_UV is None:
|
||||
_USE_UV = shutil.which("uv") is not None
|
||||
return _USE_UV
|
||||
|
||||
|
||||
def _install_packages(
|
||||
packages: List[str],
|
||||
upgrade: bool = False,
|
||||
label: str = "",
|
||||
) -> bool:
|
||||
"""
|
||||
安装/升级一组包。优先 uv pip install,回退 sys pip。
|
||||
逐个安装,任意一个失败不影响后续包。
|
||||
|
||||
Returns:
|
||||
True if all succeeded, False otherwise.
|
||||
"""
|
||||
if not packages:
|
||||
return True
|
||||
|
||||
is_chinese = _is_chinese_locale()
|
||||
use_uv = _has_uv()
|
||||
failed: List[str] = []
|
||||
|
||||
for pkg in packages:
|
||||
action_word = "升级" if upgrade else "安装"
|
||||
if label:
|
||||
print_status(f"[{label}] 正在{action_word} {pkg}...", "info")
|
||||
else:
|
||||
print_status(f"正在{action_word} {pkg}...", "info")
|
||||
|
||||
if use_uv:
|
||||
cmd = ["uv", "pip", "install"]
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
cmd.append(pkg)
|
||||
if is_chinese:
|
||||
cmd.extend(["--index-url", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||||
else:
|
||||
cmd = [sys.executable, "-m", "pip", "install"]
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
cmd.append(pkg)
|
||||
if is_chinese:
|
||||
cmd.extend(["-i", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||
if result.returncode == 0:
|
||||
installer = "uv" if use_uv else "pip"
|
||||
print_status(f"✓ {pkg} {action_word}成功 (via {installer})", "success")
|
||||
else:
|
||||
stderr_short = result.stderr.strip().split("\n")[-1] if result.stderr else "unknown error"
|
||||
print_status(f"× {pkg} {action_word}失败: {stderr_short}", "error")
|
||||
failed.append(pkg)
|
||||
except subprocess.TimeoutExpired:
|
||||
print_status(f"× {pkg} {action_word}超时 (300s)", "error")
|
||||
failed.append(pkg)
|
||||
except Exception as e:
|
||||
print_status(f"× {pkg} {action_word}异常: {e}", "error")
|
||||
failed.append(pkg)
|
||||
|
||||
if failed:
|
||||
print_status(f"有 {len(failed)} 个包操作失败: {', '.join(failed)}", "error")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# requirements.txt 安装(可多次调用)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def install_requirements_txt(req_path: str | Path, label: str = "") -> bool:
|
||||
"""
|
||||
读取一个 requirements.txt 文件,检查缺失的包并安装。
|
||||
|
||||
Args:
|
||||
req_path: requirements.txt 文件路径
|
||||
label: 日志前缀标签(如 "device_package_sim")
|
||||
|
||||
Returns:
|
||||
True if all ok, False if any install failed.
|
||||
"""
|
||||
req_path = Path(req_path)
|
||||
if not req_path.exists():
|
||||
return True
|
||||
|
||||
tag = label or req_path.parent.name
|
||||
print_status(f"[{tag}] 检查依赖: {req_path}", "info")
|
||||
|
||||
reqs: List[str] = []
|
||||
with open(req_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and not line.startswith("-"):
|
||||
reqs.append(line)
|
||||
|
||||
if not reqs:
|
||||
return True
|
||||
|
||||
missing: List[str] = []
|
||||
for req in reqs:
|
||||
pkg_import = req.split(">=")[0].split("==")[0].split("<")[0].split("[")[0].split(">")[0].strip()
|
||||
pkg_import = pkg_import.replace("-", "_")
|
||||
try:
|
||||
importlib.import_module(pkg_import)
|
||||
except ImportError:
|
||||
missing.append(req)
|
||||
|
||||
if not missing:
|
||||
print_status(f"[{tag}] ✓ 依赖检查通过 ({len(reqs)} 个包)", "success")
|
||||
return True
|
||||
|
||||
print_status(f"[{tag}] 缺失 {len(missing)} 个依赖: {', '.join(missing)}", "warning")
|
||||
return _install_packages(missing, label=tag)
|
||||
|
||||
|
||||
def check_device_package_requirements(devices_dirs: list[str]) -> bool:
|
||||
"""
|
||||
检查 --devices 指定的所有外部设备包目录中的 requirements.txt。
|
||||
对每个目录查找 requirements.txt(先在目录内找,再在父目录找)。
|
||||
"""
|
||||
if not devices_dirs:
|
||||
return True
|
||||
|
||||
all_ok = True
|
||||
for d in devices_dirs:
|
||||
d_path = Path(d).resolve()
|
||||
req_file = d_path / "requirements.txt"
|
||||
if not req_file.exists():
|
||||
req_file = d_path.parent / "requirements.txt"
|
||||
if not req_file.exists():
|
||||
continue
|
||||
if not install_requirements_txt(req_file, label=d_path.name):
|
||||
all_ok = False
|
||||
|
||||
return all_ok
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# UniLabOS 核心环境检查
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class EnvironmentChecker:
|
||||
"""环境检查器"""
|
||||
|
||||
def __init__(self):
|
||||
# 定义必需的包及其安装名称的映射
|
||||
self.required_packages = {
|
||||
# 包导入名 : pip安装名
|
||||
# "pymodbus.framer.FramerType": "pymodbus==3.9.2",
|
||||
"websockets": "websockets",
|
||||
"msgcenterpy": "msgcenterpy",
|
||||
"orjson": "orjson",
|
||||
@@ -28,33 +188,17 @@ class EnvironmentChecker:
|
||||
"crcmod": "crcmod-plus",
|
||||
}
|
||||
|
||||
# 特殊安装包(需要特殊处理的包)
|
||||
self.special_packages = {"pylabrobot": "git+https://github.com/Xuwznln/pylabrobot.git"}
|
||||
|
||||
# 包版本要求(包名: 最低版本)
|
||||
self.version_requirements = {
|
||||
"msgcenterpy": "0.1.7", # msgcenterpy 最低版本要求
|
||||
"msgcenterpy": "0.1.8",
|
||||
}
|
||||
|
||||
self.missing_packages = []
|
||||
self.failed_installs = []
|
||||
self.packages_need_upgrade = []
|
||||
|
||||
# 检测系统语言
|
||||
self.is_chinese = self._is_chinese_locale()
|
||||
|
||||
def _is_chinese_locale(self) -> bool:
|
||||
"""检测系统是否为中文环境"""
|
||||
try:
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
if lang and ("zh" in lang.lower() or "chinese" in lang.lower()):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
self.missing_packages: List[tuple] = []
|
||||
self.failed_installs: List[tuple] = []
|
||||
self.packages_need_upgrade: List[tuple] = []
|
||||
|
||||
def check_package_installed(self, package_name: str) -> bool:
|
||||
"""检查包是否已安装"""
|
||||
try:
|
||||
importlib.import_module(package_name)
|
||||
return True
|
||||
@@ -62,7 +206,6 @@ class EnvironmentChecker:
|
||||
return False
|
||||
|
||||
def get_package_version(self, package_name: str) -> str | None:
|
||||
"""获取已安装包的版本"""
|
||||
try:
|
||||
module = importlib.import_module(package_name)
|
||||
return getattr(module, "__version__", None)
|
||||
@@ -70,88 +213,32 @@ class EnvironmentChecker:
|
||||
return None
|
||||
|
||||
def compare_version(self, current: str, required: str) -> bool:
|
||||
"""
|
||||
比较版本号
|
||||
Returns:
|
||||
True: current >= required
|
||||
False: current < required
|
||||
"""
|
||||
try:
|
||||
current_parts = [int(x) for x in current.split(".")]
|
||||
required_parts = [int(x) for x in required.split(".")]
|
||||
|
||||
# 补齐长度
|
||||
max_len = max(len(current_parts), len(required_parts))
|
||||
current_parts.extend([0] * (max_len - len(current_parts)))
|
||||
required_parts.extend([0] * (max_len - len(required_parts)))
|
||||
|
||||
return current_parts >= required_parts
|
||||
except Exception:
|
||||
return True # 如果无法比较,假设版本满足要求
|
||||
|
||||
def install_package(self, package_name: str, pip_name: str, upgrade: bool = False) -> bool:
|
||||
"""安装包"""
|
||||
try:
|
||||
action = "升级" if upgrade else "安装"
|
||||
print_status(f"正在{action} {package_name} ({pip_name})...", "info")
|
||||
|
||||
# 构建安装命令
|
||||
cmd = [sys.executable, "-m", "pip", "install"]
|
||||
|
||||
# 如果是升级操作,添加 --upgrade 参数
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
|
||||
cmd.append(pip_name)
|
||||
|
||||
# 如果是中文环境,使用清华镜像源
|
||||
if self.is_chinese:
|
||||
cmd.extend(["-i", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||||
|
||||
# 执行安装
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) # 5分钟超时
|
||||
|
||||
if result.returncode == 0:
|
||||
print_status(f"✓ {package_name} {action}成功", "success")
|
||||
return True
|
||||
else:
|
||||
print_status(f"× {package_name} {action}失败: {result.stderr}", "error")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print_status(f"× {package_name} {action}超时", "error")
|
||||
return False
|
||||
except Exception as e:
|
||||
print_status(f"× {package_name} {action}异常: {str(e)}", "error")
|
||||
return False
|
||||
|
||||
def upgrade_package(self, package_name: str, pip_name: str) -> bool:
|
||||
"""升级包"""
|
||||
return self.install_package(package_name, pip_name, upgrade=True)
|
||||
return True
|
||||
|
||||
def check_all_packages(self) -> bool:
|
||||
"""检查所有必需的包"""
|
||||
print_status("开始检查环境依赖...", "info")
|
||||
|
||||
# 检查常规包
|
||||
for import_name, pip_name in self.required_packages.items():
|
||||
if not self.check_package_installed(import_name):
|
||||
self.missing_packages.append((import_name, pip_name))
|
||||
else:
|
||||
# 检查版本要求
|
||||
if import_name in self.version_requirements:
|
||||
current_version = self.get_package_version(import_name)
|
||||
required_version = self.version_requirements[import_name]
|
||||
elif import_name in self.version_requirements:
|
||||
current_version = self.get_package_version(import_name)
|
||||
required_version = self.version_requirements[import_name]
|
||||
if current_version and not self.compare_version(current_version, required_version):
|
||||
print_status(
|
||||
f"{import_name} 版本过低 (当前: {current_version}, 需要: >={required_version})",
|
||||
"warning",
|
||||
)
|
||||
self.packages_need_upgrade.append((import_name, pip_name))
|
||||
|
||||
if current_version:
|
||||
if not self.compare_version(current_version, required_version):
|
||||
print_status(
|
||||
f"{import_name} 版本过低 (当前: {current_version}, 需要: >={required_version})",
|
||||
"warning",
|
||||
)
|
||||
self.packages_need_upgrade.append((import_name, pip_name))
|
||||
|
||||
# 检查特殊包
|
||||
for package_name, install_url in self.special_packages.items():
|
||||
if not self.check_package_installed(package_name):
|
||||
self.missing_packages.append((package_name, install_url))
|
||||
@@ -170,7 +257,6 @@ class EnvironmentChecker:
|
||||
return False
|
||||
|
||||
def install_missing_packages(self, auto_install: bool = True) -> bool:
|
||||
"""安装缺失的包"""
|
||||
if not self.missing_packages and not self.packages_need_upgrade:
|
||||
return True
|
||||
|
||||
@@ -178,62 +264,36 @@ class EnvironmentChecker:
|
||||
if self.missing_packages:
|
||||
print_status("缺失以下包:", "warning")
|
||||
for import_name, pip_name in self.missing_packages:
|
||||
print_status(f" - {import_name} (pip install {pip_name})", "warning")
|
||||
print_status(f" - {import_name} ({pip_name})", "warning")
|
||||
if self.packages_need_upgrade:
|
||||
print_status("需要升级以下包:", "warning")
|
||||
for import_name, pip_name in self.packages_need_upgrade:
|
||||
print_status(f" - {import_name} (pip install --upgrade {pip_name})", "warning")
|
||||
print_status(f" - {import_name} ({pip_name})", "warning")
|
||||
return False
|
||||
|
||||
# 安装缺失的包
|
||||
if self.missing_packages:
|
||||
print_status(f"开始自动安装 {len(self.missing_packages)} 个缺失的包...", "info")
|
||||
pkgs = [pip_name for _, pip_name in self.missing_packages]
|
||||
if not _install_packages(pkgs, label="unilabos"):
|
||||
self.failed_installs.extend(self.missing_packages)
|
||||
|
||||
success_count = 0
|
||||
for import_name, pip_name in self.missing_packages:
|
||||
if self.install_package(import_name, pip_name):
|
||||
success_count += 1
|
||||
else:
|
||||
self.failed_installs.append((import_name, pip_name))
|
||||
|
||||
print_status(f"✓ 成功安装 {success_count}/{len(self.missing_packages)} 个包", "success")
|
||||
|
||||
# 升级需要更新的包
|
||||
if self.packages_need_upgrade:
|
||||
print_status(f"开始自动升级 {len(self.packages_need_upgrade)} 个包...", "info")
|
||||
pkgs = [pip_name for _, pip_name in self.packages_need_upgrade]
|
||||
if not _install_packages(pkgs, upgrade=True, label="unilabos"):
|
||||
self.failed_installs.extend(self.packages_need_upgrade)
|
||||
|
||||
upgrade_success_count = 0
|
||||
for import_name, pip_name in self.packages_need_upgrade:
|
||||
if self.upgrade_package(import_name, pip_name):
|
||||
upgrade_success_count += 1
|
||||
else:
|
||||
self.failed_installs.append((import_name, pip_name))
|
||||
|
||||
print_status(f"✓ 成功升级 {upgrade_success_count}/{len(self.packages_need_upgrade)} 个包", "success")
|
||||
|
||||
if self.failed_installs:
|
||||
print_status(f"有 {len(self.failed_installs)} 个包操作失败:", "error")
|
||||
for import_name, pip_name in self.failed_installs:
|
||||
print_status(f" - {import_name} ({pip_name})", "error")
|
||||
return False
|
||||
|
||||
return True
|
||||
return not self.failed_installs
|
||||
|
||||
def verify_installation(self) -> bool:
|
||||
"""验证安装结果"""
|
||||
if not self.missing_packages and not self.packages_need_upgrade:
|
||||
return True
|
||||
|
||||
print_status("验证安装结果...", "info")
|
||||
|
||||
failed_verification = []
|
||||
|
||||
# 验证新安装的包
|
||||
for import_name, pip_name in self.missing_packages:
|
||||
if not self.check_package_installed(import_name):
|
||||
failed_verification.append((import_name, pip_name))
|
||||
|
||||
# 验证升级的包
|
||||
for import_name, pip_name in self.packages_need_upgrade:
|
||||
if not self.check_package_installed(import_name):
|
||||
failed_verification.append((import_name, pip_name))
|
||||
@@ -270,17 +330,14 @@ def check_environment(auto_install: bool = True, show_details: bool = True) -> b
|
||||
"""
|
||||
checker = EnvironmentChecker()
|
||||
|
||||
# 检查包
|
||||
if checker.check_all_packages():
|
||||
return True
|
||||
|
||||
# 安装缺失的包
|
||||
if not checker.install_missing_packages(auto_install):
|
||||
if show_details:
|
||||
print_status("请手动安装缺失的包后重新启动程序", "error")
|
||||
return False
|
||||
|
||||
# 验证安装
|
||||
if not checker.verify_installation():
|
||||
if show_details:
|
||||
print_status("安装验证失败,请检查网络连接或手动安装", "error")
|
||||
@@ -290,14 +347,12 @@ def check_environment(auto_install: bool = True, show_details: bool = True) -> b
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 命令行参数解析
|
||||
parser = argparse.ArgumentParser(description="UniLabOS 环境依赖检查工具")
|
||||
parser.add_argument("--no-auto-install", action="store_true", help="仅检查环境,不自动安装缺失的包")
|
||||
parser.add_argument("--silent", action="store_true", help="静默模式,不显示详细信息")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 执行环境检查
|
||||
auto_install = not args.no_auto_install
|
||||
show_details = not args.silent
|
||||
|
||||
|
||||
@@ -21,15 +21,11 @@ __all__ = [
|
||||
"get_class",
|
||||
"get_module",
|
||||
"init_from_list",
|
||||
"get_class_info_static",
|
||||
"get_registry_class_info",
|
||||
"get_enhanced_class_info",
|
||||
]
|
||||
|
||||
from ast import Constant
|
||||
|
||||
from unilabos.resources.resource_tracker import PARAM_SAMPLE_UUIDS
|
||||
from unilabos.utils import logger
|
||||
from unilabos.registry.decorators import is_not_action, is_always_free
|
||||
|
||||
|
||||
class ImportManager:
|
||||
@@ -45,6 +41,7 @@ class ImportManager:
|
||||
self._modules: Dict[str, Any] = {}
|
||||
self._classes: Dict[str, Type] = {}
|
||||
self._functions: Dict[str, Callable] = {}
|
||||
self._search_miss: set = set()
|
||||
|
||||
if module_list:
|
||||
for module_path in module_list:
|
||||
@@ -159,193 +156,113 @@ class ImportManager:
|
||||
Returns:
|
||||
找到的类对象,如果未找到则返回None
|
||||
"""
|
||||
# 如果cls_name是builtins中的关键字,则返回对应类
|
||||
if class_name in builtins.__dict__:
|
||||
return builtins.__dict__[class_name]
|
||||
# 首先在已索引的类中查找
|
||||
if class_name in self._classes:
|
||||
return self._classes[class_name]
|
||||
|
||||
cache_key = class_name.lower() if search_lower else class_name
|
||||
if cache_key in self._search_miss:
|
||||
return None
|
||||
|
||||
if search_lower:
|
||||
classes = {name.lower(): obj for name, obj in self._classes.items()}
|
||||
if class_name in classes:
|
||||
return classes[class_name]
|
||||
|
||||
# 遍历所有已加载的模块进行搜索
|
||||
for module_path, module in self._modules.items():
|
||||
for name, obj in inspect.getmembers(module):
|
||||
if inspect.isclass(obj) and (
|
||||
(name.lower() == class_name.lower()) if search_lower else (name == class_name)
|
||||
):
|
||||
# 将找到的类添加到索引中
|
||||
self._classes[name] = obj
|
||||
self._classes[f"{module_path}:{name}"] = obj
|
||||
return obj
|
||||
|
||||
self._search_miss.add(cache_key)
|
||||
return None
|
||||
|
||||
def get_enhanced_class_info(self, module_path: str, use_dynamic: bool = True) -> Dict[str, Any]:
|
||||
"""
|
||||
获取增强的类信息,支持动态导入和静态分析
|
||||
def get_enhanced_class_info(self, module_path: str, **_kwargs) -> Dict[str, Any]:
|
||||
"""通过 AST 分析获取类的增强信息。
|
||||
|
||||
复用 ``ast_registry_scanner`` 的 ``_collect_imports`` / ``_extract_class_body``,
|
||||
与 AST 扫描注册表完全一致。
|
||||
|
||||
Args:
|
||||
module_path: 模块路径,格式为 "module.path" 或 "module.path:ClassName"
|
||||
use_dynamic: 是否优先使用动态导入
|
||||
module_path: 格式 ``"module.path:ClassName"``
|
||||
|
||||
Returns:
|
||||
包含详细类信息的字典
|
||||
``{"module_path", "ast_analysis_success", "import_map",
|
||||
"init_params", "status_methods", "action_methods"}``
|
||||
"""
|
||||
result = {
|
||||
from unilabos.registry.ast_registry_scanner import (
|
||||
_collect_imports,
|
||||
_extract_class_body,
|
||||
_filepath_to_module,
|
||||
)
|
||||
|
||||
result: Dict[str, Any] = {
|
||||
"module_path": module_path,
|
||||
"dynamic_import_success": False,
|
||||
"static_analysis_success": False,
|
||||
"init_params": {},
|
||||
"status_methods": {}, # get_ 开头和 @property 方法
|
||||
"action_methods": {}, # set_ 开头和其他非_开头方法
|
||||
}
|
||||
|
||||
# 尝试动态导入
|
||||
dynamic_info = None
|
||||
static_info = None
|
||||
if use_dynamic:
|
||||
try:
|
||||
dynamic_info = self._get_dynamic_class_info(module_path)
|
||||
result["dynamic_import_success"] = True
|
||||
logger.debug(f"[ImportManager] 动态导入类 {module_path} 成功")
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"[UniLab Registry] 在补充注册表时,动态导入类 "
|
||||
f"{module_path} 失败(将使用静态分析,"
|
||||
f"建议修复导入错误,以实现更好的注册表识别效果!): {e}"
|
||||
)
|
||||
use_dynamic = False
|
||||
if not use_dynamic:
|
||||
# 尝试静态分析
|
||||
try:
|
||||
static_info = self._get_static_class_info(module_path)
|
||||
result["static_analysis_success"] = True
|
||||
logger.debug(f"[ImportManager] 静态分析类 {module_path} 成功")
|
||||
except Exception as e:
|
||||
logger.warning(f"[ImportManager] 静态分析类 {module_path} 失败: {e}")
|
||||
|
||||
# 合并信息(优先使用动态导入的信息)
|
||||
if dynamic_info:
|
||||
result.update(dynamic_info)
|
||||
elif static_info:
|
||||
result.update(static_info)
|
||||
|
||||
return result
|
||||
|
||||
def _get_dynamic_class_info(self, class_path: str) -> Dict[str, Any]:
|
||||
"""使用inspect模块动态获取类信息"""
|
||||
cls = get_class(class_path)
|
||||
class_name = cls.__name__
|
||||
|
||||
result = {
|
||||
"class_name": class_name,
|
||||
"init_params": self._analyze_method_signature(cls.__init__)["args"],
|
||||
"ast_analysis_success": False,
|
||||
"import_map": {},
|
||||
"init_params": [],
|
||||
"status_methods": {},
|
||||
"action_methods": {},
|
||||
}
|
||||
# 分析类的所有成员
|
||||
for name, method in cls.__dict__.items():
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
|
||||
# 检查是否是property
|
||||
if isinstance(method, property):
|
||||
# @property 装饰的方法
|
||||
# noinspection PyTypeChecker
|
||||
return_type = self._get_return_type_from_method(method.fget) if method.fget else "Any"
|
||||
prop_info = {
|
||||
"name": name,
|
||||
"return_type": return_type,
|
||||
}
|
||||
result["status_methods"][name] = prop_info
|
||||
|
||||
# 检查是否有对应的setter
|
||||
if method.fset:
|
||||
setter_info = self._analyze_method_signature(method.fset)
|
||||
result["action_methods"][name] = setter_info
|
||||
|
||||
elif inspect.ismethod(method) or inspect.isfunction(method):
|
||||
if name.startswith("get_"):
|
||||
actual_name = name[4:] # 去掉get_前缀
|
||||
if actual_name in result["status_methods"]:
|
||||
continue
|
||||
# get_ 开头的方法归类为status
|
||||
method_info = self._analyze_method_signature(method)
|
||||
result["status_methods"][actual_name] = method_info
|
||||
elif not name.startswith("_"):
|
||||
# 检查是否被 @not_action 装饰器标记
|
||||
if is_not_action(method):
|
||||
continue
|
||||
# 其他非_开头的方法归类为action
|
||||
method_info = self._analyze_method_signature(method)
|
||||
# 检查是否被 @always_free 装饰器标记
|
||||
if is_always_free(method):
|
||||
method_info["always_free"] = True
|
||||
result["action_methods"][name] = method_info
|
||||
|
||||
return result
|
||||
|
||||
def _get_static_class_info(self, module_path: str) -> Dict[str, Any]:
|
||||
"""使用AST静态分析获取类信息"""
|
||||
module_name, class_name = module_path.rsplit(":", 1)
|
||||
# 将模块路径转换为文件路径
|
||||
file_path = self._module_path_to_file_path(module_name)
|
||||
if not file_path or not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f"找不到模块文件: {module_name} -> {file_path}")
|
||||
logger.warning(f"[ImportManager] 找不到模块文件: {module_name} -> {file_path}")
|
||||
return result
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
source_code = f.read()
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
tree = ast.parse(f.read(), filename=file_path)
|
||||
except Exception as e:
|
||||
logger.warning(f"[ImportManager] 解析文件 {file_path} 失败: {e}")
|
||||
return result
|
||||
|
||||
tree = ast.parse(source_code)
|
||||
# 推导 module dotted path → 构建 import_map
|
||||
python_path = Path(file_path)
|
||||
for sp in sorted(sys.path, key=len, reverse=True):
|
||||
try:
|
||||
Path(file_path).relative_to(sp)
|
||||
python_path = Path(sp)
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
module_dotted = _filepath_to_module(Path(file_path), python_path)
|
||||
import_map = _collect_imports(tree, module_dotted)
|
||||
result["import_map"] = import_map
|
||||
|
||||
# 查找目标类
|
||||
# 定位目标类 AST 节点
|
||||
target_class = None
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
if node.name == class_name:
|
||||
target_class = node
|
||||
break
|
||||
if isinstance(node, ast.ClassDef) and node.name == class_name:
|
||||
target_class = node
|
||||
break
|
||||
|
||||
if target_class is None:
|
||||
raise AttributeError(f"在文件 {file_path} 中找不到类 {class_name}")
|
||||
logger.warning(f"[ImportManager] 在文件 {file_path} 中找不到类 {class_name}")
|
||||
return result
|
||||
|
||||
result = {
|
||||
"class_name": class_name,
|
||||
"init_params": {},
|
||||
"status_methods": {},
|
||||
"action_methods": {},
|
||||
body = _extract_class_body(target_class, import_map)
|
||||
|
||||
# 映射到统一字段名(与 registry.py complete_registry 消费端一致)
|
||||
result["init_params"] = body.get("init_params", [])
|
||||
result["status_methods"] = body.get("status_properties", {})
|
||||
result["action_methods"] = {
|
||||
k: {
|
||||
"args": v.get("params", []),
|
||||
"return_type": v.get("return_type", ""),
|
||||
"is_async": v.get("is_async", False),
|
||||
"always_free": v.get("always_free", False),
|
||||
"docstring": v.get("docstring"),
|
||||
}
|
||||
for k, v in body.get("auto_methods", {}).items()
|
||||
}
|
||||
|
||||
# 分析类的方法
|
||||
for node in target_class.body:
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
method_info = self._analyze_method_node(node)
|
||||
method_name = node.name
|
||||
if method_name == "__init__":
|
||||
result["init_params"] = method_info["args"]
|
||||
elif method_name.startswith("_"):
|
||||
continue
|
||||
elif self._is_property_method(node):
|
||||
# @property 装饰的方法
|
||||
result["status_methods"][method_name] = method_info
|
||||
elif method_name.startswith("get_"):
|
||||
# get_ 开头的方法归类为status
|
||||
actual_name = method_name[4:] # 去掉get_前缀
|
||||
if actual_name not in result["status_methods"]:
|
||||
result["status_methods"][actual_name] = method_info
|
||||
else:
|
||||
# 检查是否被 @not_action 装饰器标记
|
||||
if self._is_not_action_method(node):
|
||||
continue
|
||||
# 其他非_开头的方法归类为action
|
||||
# 检查是否被 @always_free 装饰器标记
|
||||
if self._is_always_free_method(node):
|
||||
method_info["always_free"] = True
|
||||
result["action_methods"][method_name] = method_info
|
||||
result["ast_analysis_success"] = True
|
||||
return result
|
||||
|
||||
def _analyze_method_signature(self, method, skip_unilabos_params: bool = True) -> Dict[str, Any]:
|
||||
@@ -401,23 +318,26 @@ class ImportManager:
|
||||
"name": method.__name__,
|
||||
"args": args,
|
||||
"return_type": self._get_type_string(signature.return_annotation),
|
||||
"return_annotation": signature.return_annotation, # 保留原始类型注解,用于TypedDict等特殊处理
|
||||
"is_async": inspect.iscoroutinefunction(method),
|
||||
}
|
||||
|
||||
def _get_return_type_from_method(self, method) -> str:
|
||||
def _get_return_type_from_method(self, method) -> Union[str, Tuple[str, Any]]:
|
||||
"""从方法中获取返回类型"""
|
||||
signature = inspect.signature(method)
|
||||
return self._get_type_string(signature.return_annotation)
|
||||
|
||||
def _get_type_string(self, annotation) -> Union[str, Tuple[str, Any]]:
|
||||
"""将类型注解转换为Class Library中可搜索的类名"""
|
||||
"""将类型注解转换为类型字符串。
|
||||
|
||||
非内建类返回 ``module:ClassName`` 全路径(如
|
||||
``"unilabos.registry.placeholder_type:ResourceSlot"``),
|
||||
避免短名冲突;内建类型直接返回短名(如 ``"str"``、``"int"``)。
|
||||
"""
|
||||
if annotation == inspect.Parameter.empty:
|
||||
return "Any" # 如果没有注解,返回Any
|
||||
return "Any"
|
||||
if annotation is None:
|
||||
return "None" # 明确的None类型
|
||||
return "None"
|
||||
if hasattr(annotation, "__origin__"):
|
||||
# 处理typing模块的类型
|
||||
origin = annotation.__origin__
|
||||
if origin in (list, set, tuple):
|
||||
if hasattr(annotation, "__args__") and annotation.__args__:
|
||||
@@ -432,132 +352,26 @@ class ImportManager:
|
||||
return "dict"
|
||||
elif origin is Optional:
|
||||
return "Unknown"
|
||||
return f"Unknown"
|
||||
return "Unknown"
|
||||
annotation_str = str(annotation)
|
||||
# 处理typing模块的复杂类型
|
||||
if "typing." in annotation_str:
|
||||
# 简化typing类型显示
|
||||
return (
|
||||
annotation_str.replace("typing.", "")
|
||||
if getattr(annotation, "_name", None) is None
|
||||
else annotation._name.lower()
|
||||
)
|
||||
# 如果是类型对象
|
||||
if hasattr(annotation, "__name__"):
|
||||
# 如果是内置类型
|
||||
if annotation.__module__ == "builtins":
|
||||
return annotation.__name__
|
||||
else:
|
||||
# 如果是自定义类,返回完整路径
|
||||
return f"{annotation.__module__}:{annotation.__name__}"
|
||||
# 如果是typing模块的类型
|
||||
module = getattr(annotation, "__module__", None)
|
||||
if module and module != "builtins":
|
||||
return f"{module}:{annotation.__name__}"
|
||||
return annotation.__name__
|
||||
elif hasattr(annotation, "_name"):
|
||||
return annotation._name
|
||||
# 如果是字符串形式的类型注解
|
||||
elif isinstance(annotation, str):
|
||||
return annotation
|
||||
else:
|
||||
return annotation_str
|
||||
|
||||
def _is_property_method(self, node: ast.FunctionDef) -> bool:
|
||||
"""检查是否是@property装饰的方法"""
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Name) and decorator.id == "property":
|
||||
return True
|
||||
return False
|
||||
|
||||
def _is_setter_method(self, node: ast.FunctionDef) -> bool:
|
||||
"""检查是否是@xxx.setter装饰的方法"""
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Attribute) and decorator.attr == "setter":
|
||||
return True
|
||||
return False
|
||||
|
||||
def _is_not_action_method(self, node: ast.FunctionDef) -> bool:
|
||||
"""检查是否是@not_action装饰的方法"""
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Name) and decorator.id == "not_action":
|
||||
return True
|
||||
return False
|
||||
|
||||
def _is_always_free_method(self, node: ast.FunctionDef) -> bool:
|
||||
"""检查是否是@always_free装饰的方法,或 @action(always_free=True) 装饰的方法"""
|
||||
for decorator in node.decorator_list:
|
||||
# 检查 @action(always_free=True)
|
||||
if isinstance(decorator, ast.Call):
|
||||
func = decorator.func
|
||||
if isinstance(func, ast.Name) and func.id == "action":
|
||||
for keyword in decorator.keywords:
|
||||
if keyword.arg == "always_free":
|
||||
if isinstance(keyword.value, Constant) and keyword.value.value is True:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_property_name_from_setter(self, node: ast.FunctionDef) -> str:
|
||||
"""从setter装饰器中获取属性名"""
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Attribute) and decorator.attr == "setter":
|
||||
if isinstance(decorator.value, ast.Name):
|
||||
return decorator.value.id
|
||||
return node.name
|
||||
|
||||
def get_class_info_static(self, module_class_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
静态分析获取类的方法信息,不需要实际导入模块
|
||||
|
||||
Args:
|
||||
module_class_path: 格式为 "module.path:ClassName" 的字符串
|
||||
|
||||
Returns:
|
||||
包含类方法信息的字典
|
||||
"""
|
||||
try:
|
||||
if ":" not in module_class_path:
|
||||
raise ValueError("module_class_path必须是 'module.path:ClassName' 格式")
|
||||
|
||||
module_path, class_name = module_class_path.rsplit(":", 1)
|
||||
|
||||
# 将模块路径转换为文件路径
|
||||
file_path = self._module_path_to_file_path(module_path)
|
||||
if not file_path or not os.path.exists(file_path):
|
||||
logger.warning(f"找不到模块文件: {module_path} -> {file_path}")
|
||||
return {}
|
||||
|
||||
# 解析源码
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
source_code = f.read()
|
||||
|
||||
tree = ast.parse(source_code)
|
||||
|
||||
# 查找目标类
|
||||
class_node = None
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef) and node.name == class_name:
|
||||
class_node = node
|
||||
break
|
||||
|
||||
if not class_node:
|
||||
logger.warning(f"在模块 {module_path} 中找不到类 {class_name}")
|
||||
return {}
|
||||
|
||||
# 分析类的方法
|
||||
methods_info = {}
|
||||
for node in class_node.body:
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
method_info = self._analyze_method_node(node)
|
||||
methods_info[node.name] = method_info
|
||||
|
||||
return {
|
||||
"class_name": class_name,
|
||||
"module_path": module_path,
|
||||
"file_path": file_path,
|
||||
"methods": methods_info,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"静态分析类 {module_class_path} 时出错: {str(e)}")
|
||||
return {}
|
||||
|
||||
def _module_path_to_file_path(self, module_path: str) -> Optional[str]:
|
||||
for path in sys.path:
|
||||
potential_path = Path(path) / module_path.replace(".", "/")
|
||||
@@ -572,222 +386,6 @@ class ImportManager:
|
||||
|
||||
return None
|
||||
|
||||
def _analyze_method_node(self, node: ast.FunctionDef) -> Dict[str, Any]:
|
||||
"""分析方法节点,提取参数和返回类型信息"""
|
||||
method_info = {
|
||||
"name": node.name,
|
||||
"args": [],
|
||||
"return_type": None,
|
||||
"is_async": isinstance(node, ast.AsyncFunctionDef),
|
||||
}
|
||||
# 获取默认值列表
|
||||
defaults = node.args.defaults
|
||||
num_defaults = len(defaults)
|
||||
|
||||
# 计算必需参数数量
|
||||
total_args = len(node.args.args)
|
||||
num_required = total_args - num_defaults
|
||||
|
||||
# 提取参数信息
|
||||
for i, arg in enumerate(node.args.args):
|
||||
if arg.arg == "self":
|
||||
continue
|
||||
# 跳过 sample_uuids 参数(由系统自动注入)
|
||||
if arg.arg == PARAM_SAMPLE_UUIDS:
|
||||
continue
|
||||
arg_info = {
|
||||
"name": arg.arg,
|
||||
"type": None,
|
||||
"default": None,
|
||||
"required": i < num_required,
|
||||
}
|
||||
|
||||
# 提取类型注解
|
||||
if arg.annotation:
|
||||
arg_info["type"] = ast.unparse(arg.annotation) if hasattr(ast, "unparse") else str(arg.annotation)
|
||||
|
||||
# 提取默认值并推断类型
|
||||
if i >= num_required:
|
||||
default_index = i - num_required
|
||||
if default_index < len(defaults):
|
||||
default_value: Constant = defaults[default_index] # type: ignore
|
||||
assert isinstance(default_value, Constant), "暂不支持对非常量类型进行推断,可反馈开源仓库"
|
||||
arg_info["default"] = default_value.value
|
||||
# 如果没有类型注解,尝试从默认值推断类型
|
||||
if not arg_info["type"]:
|
||||
arg_info["type"] = self._get_type_string(type(arg_info["default"]))
|
||||
method_info["args"].append(arg_info)
|
||||
|
||||
# 提取返回类型
|
||||
if node.returns:
|
||||
method_info["return_type"] = ast.unparse(node.returns) if hasattr(ast, "unparse") else str(node.returns)
|
||||
|
||||
return method_info
|
||||
|
||||
def _infer_type_from_default(self, node: ast.AST) -> Optional[str]:
|
||||
"""从默认值推断参数类型"""
|
||||
if isinstance(node, ast.Constant):
|
||||
value = node.value
|
||||
if isinstance(value, bool):
|
||||
return "bool"
|
||||
elif isinstance(value, int):
|
||||
return "int"
|
||||
elif isinstance(value, float):
|
||||
return "float"
|
||||
elif isinstance(value, str):
|
||||
return "str"
|
||||
elif value is None:
|
||||
return "Optional[Any]"
|
||||
elif isinstance(node, ast.List):
|
||||
return "List"
|
||||
elif isinstance(node, ast.Dict):
|
||||
return "Dict"
|
||||
elif isinstance(node, ast.Tuple):
|
||||
return "Tuple"
|
||||
elif isinstance(node, ast.Set):
|
||||
return "Set"
|
||||
elif isinstance(node, ast.Name):
|
||||
# 常见的默认值模式
|
||||
if node.id in ["None"]:
|
||||
return "Optional[Any]"
|
||||
elif node.id in ["True", "False"]:
|
||||
return "bool"
|
||||
|
||||
return None
|
||||
|
||||
def _infer_types_from_docstring(self, method_info: Dict[str, Any]) -> None:
|
||||
"""从docstring中推断参数类型"""
|
||||
docstring = method_info.get("docstring", "")
|
||||
if not docstring:
|
||||
return
|
||||
|
||||
lines = docstring.split("\n")
|
||||
in_args_section = False
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
# 检测Args或Arguments段落
|
||||
if line.lower().startswith(("args:", "arguments:")):
|
||||
in_args_section = True
|
||||
continue
|
||||
elif line.startswith(("returns:", "return:", "yields:", "raises:")):
|
||||
in_args_section = False
|
||||
continue
|
||||
elif not line or not in_args_section:
|
||||
continue
|
||||
|
||||
# 解析参数行,格式通常是: param_name (type): description 或 param_name: description
|
||||
if ":" in line:
|
||||
parts = line.split(":", 1)
|
||||
param_part = parts[0].strip()
|
||||
|
||||
# 提取参数名和类型
|
||||
param_name = None
|
||||
param_type = None
|
||||
|
||||
if "(" in param_part and ")" in param_part:
|
||||
# 格式: param_name (type)
|
||||
param_name = param_part.split("(")[0].strip()
|
||||
type_part = param_part.split("(")[1].split(")")[0].strip()
|
||||
param_type = type_part
|
||||
else:
|
||||
# 格式: param_name
|
||||
param_name = param_part
|
||||
|
||||
# 更新对应参数的类型信息
|
||||
if param_name:
|
||||
for arg_info in method_info["args"]:
|
||||
if arg_info["name"] == param_name and not arg_info["type"]:
|
||||
if param_type:
|
||||
arg_info["inferred_type"] = param_type
|
||||
elif not arg_info["inferred_type"]:
|
||||
# 从描述中推断类型
|
||||
description = parts[1].strip().lower()
|
||||
if any(word in description for word in ["path", "file", "directory", "filename"]):
|
||||
arg_info["inferred_type"] = "str"
|
||||
elif any(
|
||||
word in description for word in ["port", "number", "count", "size", "length"]
|
||||
):
|
||||
arg_info["inferred_type"] = "int"
|
||||
elif any(
|
||||
word in description for word in ["rate", "ratio", "percentage", "temperature"]
|
||||
):
|
||||
arg_info["inferred_type"] = "float"
|
||||
elif any(word in description for word in ["flag", "enable", "disable", "option"]):
|
||||
arg_info["inferred_type"] = "bool"
|
||||
|
||||
def get_registry_class_info(self, module_class_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取适用于注册表的类信息,包含完整的类型推断
|
||||
|
||||
Args:
|
||||
module_class_path: 格式为 "module.path:ClassName" 的字符串
|
||||
|
||||
Returns:
|
||||
适用于注册表的类信息字典
|
||||
"""
|
||||
class_info = self.get_class_info_static(module_class_path)
|
||||
if not class_info:
|
||||
return {}
|
||||
|
||||
registry_info = {
|
||||
"class_name": class_info["class_name"],
|
||||
"module_path": class_info["module_path"],
|
||||
"file_path": class_info["file_path"],
|
||||
"methods": {},
|
||||
"properties": [],
|
||||
"init_params": {},
|
||||
"action_methods": {},
|
||||
}
|
||||
|
||||
for method_name, method_info in class_info["methods"].items():
|
||||
# 分类处理不同类型的方法
|
||||
if method_info["is_property"]:
|
||||
registry_info["properties"].append(
|
||||
{
|
||||
"name": method_name,
|
||||
"return_type": method_info.get("return_type"),
|
||||
"docstring": method_info.get("docstring"),
|
||||
}
|
||||
)
|
||||
elif method_name == "__init__":
|
||||
# 处理初始化参数
|
||||
init_params = {}
|
||||
for arg in method_info["args"]:
|
||||
if arg["name"] != "self":
|
||||
param_info = {
|
||||
"name": arg["name"],
|
||||
"type": arg.get("type") or arg.get("inferred_type"),
|
||||
"required": arg.get("is_required", True),
|
||||
"default": arg.get("default"),
|
||||
}
|
||||
init_params[arg["name"]] = param_info
|
||||
registry_info["init_params"] = init_params
|
||||
elif not method_name.startswith("_"):
|
||||
# 处理公共方法(可能的action方法)
|
||||
action_info = {
|
||||
"name": method_name,
|
||||
"params": {},
|
||||
"return_type": method_info.get("return_type"),
|
||||
"docstring": method_info.get("docstring"),
|
||||
"num_required": method_info.get("num_required", 0) - 1, # 减去self
|
||||
"num_defaults": method_info.get("num_defaults", 0),
|
||||
}
|
||||
|
||||
for arg in method_info["args"]:
|
||||
if arg["name"] != "self":
|
||||
param_info = {
|
||||
"name": arg["name"],
|
||||
"type": arg.get("type") or arg.get("inferred_type"),
|
||||
"required": arg.get("is_required", True),
|
||||
"default": arg.get("default"),
|
||||
}
|
||||
action_info["params"][arg["name"]] = param_info
|
||||
|
||||
registry_info["action_methods"][method_name] = action_info
|
||||
|
||||
return registry_info
|
||||
|
||||
|
||||
# 全局实例,便于直接使用
|
||||
@@ -815,16 +413,6 @@ def init_from_list(module_list: List[str]) -> None:
|
||||
default_manager = ImportManager(module_list)
|
||||
|
||||
|
||||
def get_class_info_static(module_class_path: str) -> Dict[str, Any]:
|
||||
"""静态分析获取类信息的便捷函数"""
|
||||
return default_manager.get_class_info_static(module_class_path)
|
||||
|
||||
|
||||
def get_registry_class_info(module_class_path: str) -> Dict[str, Any]:
|
||||
"""获取适用于注册表的类信息的便捷函数"""
|
||||
return default_manager.get_registry_class_info(module_class_path)
|
||||
|
||||
|
||||
def get_enhanced_class_info(module_path: str, use_dynamic: bool = True) -> Dict[str, Any]:
|
||||
def get_enhanced_class_info(module_path: str, **kwargs) -> Dict[str, Any]:
|
||||
"""获取增强的类信息的便捷函数"""
|
||||
return default_manager.get_enhanced_class_info(module_path, use_dynamic)
|
||||
return default_manager.get_enhanced_class_info(module_path, **kwargs)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
networkx
|
||||
typing_extensions
|
||||
websockets
|
||||
msgcenterpy>=0.1.7
|
||||
msgcenterpy>=0.1.8
|
||||
orjson>=3.11
|
||||
opentrons_shared_data
|
||||
pint
|
||||
|
||||
@@ -1,4 +1,39 @@
|
||||
import json
|
||||
|
||||
from unilabos.utils.type_check import TypeEncoder, json_default
|
||||
|
||||
try:
|
||||
import orjson
|
||||
|
||||
def fast_dumps(obj, **kwargs) -> bytes:
|
||||
"""JSON 序列化为 bytes,优先使用 orjson。"""
|
||||
return orjson.dumps(obj, option=orjson.OPT_NON_STR_KEYS, default=json_default)
|
||||
|
||||
def fast_dumps_pretty(obj, **kwargs) -> bytes:
|
||||
"""JSON 序列化为 bytes(带缩进),优先使用 orjson。"""
|
||||
return orjson.dumps(
|
||||
obj,
|
||||
option=orjson.OPT_NON_STR_KEYS | orjson.OPT_INDENT_2,
|
||||
default=json_default,
|
||||
)
|
||||
|
||||
def normalize_json(info: dict) -> dict:
|
||||
"""经 JSON 序列化/反序列化一轮来清理非标准类型。"""
|
||||
return orjson.loads(orjson.dumps(info, default=json_default))
|
||||
|
||||
except ImportError:
|
||||
|
||||
def fast_dumps(obj, **kwargs) -> bytes: # type: ignore[misc]
|
||||
return json.dumps(obj, ensure_ascii=False, cls=TypeEncoder).encode("utf-8")
|
||||
|
||||
def fast_dumps_pretty(obj, **kwargs) -> bytes: # type: ignore[misc]
|
||||
return json.dumps(obj, indent=2, ensure_ascii=False, cls=TypeEncoder).encode("utf-8")
|
||||
|
||||
def normalize_json(info: dict) -> dict: # type: ignore[misc]
|
||||
return json.loads(json.dumps(info, ensure_ascii=False, cls=TypeEncoder))
|
||||
|
||||
|
||||
# 辅助函数:将UUID数组转换为字符串
|
||||
def uuid_to_str(uuid_array) -> str:
|
||||
"""将UUID字节数组转换为十六进制字符串"""
|
||||
return "".join(format(byte, "02x") for byte in uuid_array)
|
||||
return "".join(format(byte, "02x") for byte in uuid_array)
|
||||
|
||||
@@ -15,14 +15,21 @@ def get_type_class(type_hint):
|
||||
return final_type
|
||||
|
||||
|
||||
def json_default(obj):
|
||||
"""将 type 对象序列化为类名,其余 fallback 到 str()。"""
|
||||
if isinstance(obj, type):
|
||||
return str(obj)[8:-2]
|
||||
return str(obj)
|
||||
|
||||
|
||||
class TypeEncoder(json.JSONEncoder):
|
||||
"""自定义JSON编码器处理特殊类型"""
|
||||
|
||||
def default(self, obj):
|
||||
# 优先处理类型对象
|
||||
if isinstance(obj, type):
|
||||
return str(obj)[8:-2]
|
||||
return super().default(obj)
|
||||
try:
|
||||
return json_default(obj)
|
||||
except Exception:
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
class NoAliasDumper(yaml.SafeDumper):
|
||||
@@ -43,13 +50,10 @@ class ResultInfoEncoder(json.JSONEncoder):
|
||||
"""专门用于处理任务执行结果信息的JSON编码器"""
|
||||
|
||||
def default(self, obj):
|
||||
# 优先处理类型对象
|
||||
if isinstance(obj, type):
|
||||
return str(obj)[8:-2]
|
||||
return json_default(obj)
|
||||
|
||||
# 对于无法序列化的对象,统一转换为字符串
|
||||
try:
|
||||
# 尝试调用 __dict__ 或者其他序列化方法
|
||||
if hasattr(obj, "__dict__"):
|
||||
return obj.__dict__
|
||||
elif hasattr(obj, "_asdict"): # namedtuple
|
||||
@@ -59,10 +63,8 @@ class ResultInfoEncoder(json.JSONEncoder):
|
||||
elif hasattr(obj, "dict"):
|
||||
return obj.dict()
|
||||
else:
|
||||
# 如果都不行,转换为字符串
|
||||
return str(obj)
|
||||
except Exception:
|
||||
# 如果转换失败,直接返回字符串表示
|
||||
return str(obj)
|
||||
|
||||
|
||||
@@ -80,7 +82,7 @@ def get_result_info_str(error: str, suc: bool, return_value=None) -> str:
|
||||
"""
|
||||
samples = None
|
||||
if isinstance(return_value, dict):
|
||||
if "samples" in return_value:
|
||||
if "samples" in return_value and type(return_value["samples"]) in [list, tuple] and type(return_value["samples"][0]) == dict:
|
||||
samples = return_value.pop("samples")
|
||||
result_info = {"error": error, "suc": suc, "return_value": return_value, "samples": samples}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="3">
|
||||
<name>unilabos_msgs</name>
|
||||
<version>0.10.18</version>
|
||||
<version>0.10.19</version>
|
||||
<description>ROS2 Messages package for unilabos devices</description>
|
||||
<maintainer email="changjh@pku.edu.cn">Junhan Chang</maintainer>
|
||||
<maintainer email="18435084+Xuwznln@users.noreply.github.com">Xuwznln</maintainer>
|
||||
|
||||
Reference in New Issue
Block a user