Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
839fb6e92d ci(deps): bump conda-incubator/setup-miniconda from 3 to 4
Bumps [conda-incubator/setup-miniconda](https://github.com/conda-incubator/setup-miniconda) from 3 to 4.
- [Release notes](https://github.com/conda-incubator/setup-miniconda/releases)
- [Changelog](https://github.com/conda-incubator/setup-miniconda/blob/main/CHANGELOG.md)
- [Commits](https://github.com/conda-incubator/setup-miniconda/compare/v3...v4)

---
updated-dependencies:
- dependency-name: conda-incubator/setup-miniconda
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 06:30:59 +00:00
26 changed files with 252 additions and 869 deletions

View File

@@ -71,22 +71,6 @@ from unilabos.registry.decorators import action
- `_` 开头的方法 → 不扫描
- `@not_action` 标记的方法 → 排除
### 参数文档 → JSON Schema 元数据
`__init__` 和 action 方法 docstring 的 `Args:` 小节里,使用以下格式生成入参 schema 的显示信息:
```python
"""
Args:
param[显示名称]: 参数说明,会写入 JSON Schema 的 description。
"""
```
- `param[显示名称]` 的显示名称会写入 goal property 的 `title`
- `:` 后面的说明会写入 goal property 的 `description`
- 如果只写 `param: 参数说明``title` 会兜底为字段名,`description` 使用参数说明。
- 如果没有写参数文档,生成器也会兜底补齐 `title=<字段名>``description=""`,但新设备应优先写清楚显示名和说明。
### @topic_config — 状态属性配置
```python
@@ -121,27 +105,13 @@ import logging
from typing import Any, Dict, Optional
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
from unilabos.registry.decorators import action, device, not_action, topic_config
from unilabos.registry.decorators import device, action, topic_config, not_action
@device(
id="my_device",
category=["my_category"],
description="设备描述",
display_name="设备显示名",
)
@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):
"""
初始化设备。
Args:
device_id[设备ID]: 设备实例 ID默认使用 my_device。
config[设备配置]: 设备启动配置。
"""
self.device_id = device_id or "my_device"
self.config = config or {}
self.logger = logging.getLogger(f"MyDevice.{self.device_id}")
@@ -163,13 +133,7 @@ class MyDevice:
@action(description="执行操作")
def my_action(self, param: float = 0.0, name: str = "") -> Dict[str, Any]:
"""
带 @action 装饰器 → 注册为 'my_action' 动作。
Args:
param[操作数值]: 操作使用的数值参数。
name[操作名称]: 操作名称或备注。
"""
"""带 @action 装饰器 → 注册为 'my_action' 动作"""
return {"success": True}
def get_info(self) -> Dict[str, Any]:

View File

@@ -25,7 +25,7 @@ jobs:
fetch-depth: 0
- name: Setup Miniforge
uses: conda-incubator/setup-miniconda@v3
uses: conda-incubator/setup-miniconda@v4
with:
miniforge-version: latest
use-mamba: true

View File

@@ -70,7 +70,7 @@ jobs:
- name: Setup Miniforge (with mamba)
if: steps.should_build.outputs.should_build == 'true'
uses: conda-incubator/setup-miniconda@v3
uses: conda-incubator/setup-miniconda@v4
with:
miniforge-version: latest
use-mamba: true

View File

@@ -51,7 +51,7 @@ jobs:
fetch-depth: 0
- name: Setup Miniforge (with mamba)
uses: conda-incubator/setup-miniconda@v3
uses: conda-incubator/setup-miniconda@v4
with:
miniforge-version: latest
use-mamba: true

View File

@@ -98,7 +98,7 @@ jobs:
- name: Setup Miniconda
if: steps.should_build.outputs.should_build == 'true'
uses: conda-incubator/setup-miniconda@v3
uses: conda-incubator/setup-miniconda@v4
with:
miniconda-version: 'latest'
channels: conda-forge,robostack-staging,defaults

View File

@@ -98,7 +98,7 @@ jobs:
- name: Setup Miniconda
if: steps.should_build.outputs.should_build == 'true'
uses: conda-incubator/setup-miniconda@v3
uses: conda-incubator/setup-miniconda@v4
with:
miniconda-version: 'latest'
channels: conda-forge,robostack-staging,uni-lab,defaults

View File

@@ -14,30 +14,20 @@ Virtual Workbench Device - 模拟工作台设备
import logging
import time
from typing import Dict, Any, Optional, List
from dataclasses import dataclass
from enum import Enum
from threading import Lock, RLock
from typing import Any, Dict, List, Optional, cast
from typing_extensions import TypedDict
from unilabos.registry.decorators import (
ActionInputHandle,
ActionOutputHandle,
DataSource,
NodeType,
action,
device,
not_action,
topic_config,
device, action, ActionInputHandle, ActionOutputHandle, DataSource, topic_config, not_action, NodeType
)
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, ROS2DeviceNode
from unilabos.resources.resource_tracker import (
SampleUUIDsType,
LabSample,
ResourceTreeSet,
)
from unilabos.resources.resource_tracker import SampleUUIDsType, LabSample, ResourceTreeSet
# ============ TypedDict 返回类型定义 ============
@@ -122,7 +112,6 @@ class HeatingStation:
@device(
id="virtual_workbench",
display_name="虚拟工作台",
category=["virtual_device"],
description="Virtual Workbench with 1 robotic arm and 3 heating stations for concurrent material processing",
)
@@ -148,19 +137,7 @@ class VirtualWorkbench:
HEATING_TIME: float = 60.0 # 加热时间(秒)
NUM_HEATING_STATIONS: int = 3 # 加热台数量
def __init__(
self,
device_id: Optional[str] = None,
config: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""
初始化虚拟工作台。
Args:
device_id[设备ID]: 工作台设备实例 ID默认使用 virtual_workbench。
config[设备配置]: 可包含 arm_operation_time、heating_time、num_heating_stations。
"""
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")
@@ -174,13 +151,9 @@ class VirtualWorkbench:
self.data: Dict[str, Any] = {}
# 从config中获取可配置参数
self.ARM_OPERATION_TIME = float(
self.config.get("arm_operation_time", self.ARM_OPERATION_TIME)
)
self.ARM_OPERATION_TIME = float(self.config.get("arm_operation_time", self.ARM_OPERATION_TIME))
self.HEATING_TIME = float(self.config.get("heating_time", self.HEATING_TIME))
self.NUM_HEATING_STATIONS = int(
self.config.get("num_heating_stations", self.NUM_HEATING_STATIONS)
)
self.NUM_HEATING_STATIONS = int(self.config.get("num_heating_stations", self.NUM_HEATING_STATIONS))
# 机械臂状态和锁
self._arm_lock = Lock()
@@ -189,8 +162,7 @@ class VirtualWorkbench:
# 加热台状态
self._heating_stations: Dict[int, HeatingStation] = {
i: HeatingStation(station_id=i)
for i in range(1, self.NUM_HEATING_STATIONS + 1)
i: HeatingStation(station_id=i) for i in range(1, self.NUM_HEATING_STATIONS + 1)
}
self._stations_lock = RLock()
@@ -320,113 +292,45 @@ class VirtualWorkbench:
self.logger.info(f"机械臂已释放 (完成: {task})")
@action(
always_free=True,
node_type=NodeType.MANUAL_CONFIRM,
placeholder_keys={"assignee_user_ids": "unilabos_manual_confirm"},
goal_default={"timeout_seconds": 3600, "assignee_user_ids": []},
feedback_interval=300,
always_free=True, node_type=NodeType.MANUAL_CONFIRM, placeholder_keys={
"assignee_user_ids": "unilabos_manual_confirm"
}, goal_default={
"timeout_seconds": 3600,
"assignee_user_ids": []
}, feedback_interval=300,
handles=[
ActionInputHandle(
key="target_device",
data_type="device_id",
label="目标设备",
data_key="target_device",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="resource",
data_type="resource",
label="待转移资源",
data_key="resource",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="mount_resource",
data_type="resource",
label="目标孔位",
data_key="mount_resource",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="collector_mass",
data_type="collector_mass",
label="极流体质量",
data_key="collector_mass",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="active_material",
data_type="active_material",
label="活性物质含量",
data_key="active_material",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="capacity",
data_type="capacity",
label="克容量",
data_key="capacity",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="battery_system",
data_type="battery_system",
label="电池体系",
data_key="battery_system",
data_source=DataSource.HANDLE,
),
ActionInputHandle(key="target_device", data_type="device_id",
label="目标设备", data_key="target_device", data_source=DataSource.HANDLE),
ActionInputHandle(key="resource", data_type="resource",
label="待转移资源", data_key="resource", data_source=DataSource.HANDLE),
ActionInputHandle(key="mount_resource", data_type="resource",
label="目标孔位", data_key="mount_resource", data_source=DataSource.HANDLE),
ActionInputHandle(key="collector_mass", data_type="collector_mass",
label="极流体质量", data_key="collector_mass", data_source=DataSource.HANDLE),
ActionInputHandle(key="active_material", data_type="active_material",
label="活性物质含量", data_key="active_material", data_source=DataSource.HANDLE),
ActionInputHandle(key="capacity", data_type="capacity",
label="克容量", data_key="capacity", data_source=DataSource.HANDLE),
ActionInputHandle(key="battery_system", data_type="battery_system",
label="电池体系", data_key="battery_system", data_source=DataSource.HANDLE),
# transfer使用
ActionOutputHandle(
key="target_device",
data_type="device_id",
label="目标设备",
data_key="target_device",
data_source=DataSource.EXECUTOR,
),
ActionOutputHandle(
key="resource",
data_type="resource",
label="待转移资源",
data_key="resource.@flatten",
data_source=DataSource.EXECUTOR,
),
ActionOutputHandle(
key="mount_resource",
data_type="resource",
label="目标孔位",
data_key="mount_resource.@flatten",
data_source=DataSource.EXECUTOR,
),
ActionOutputHandle(key="target_device", data_type="device_id",
label="目标设备", data_key="target_device", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="resource", data_type="resource",
label="待转移资源", data_key="resource.@flatten", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="mount_resource", data_type="resource",
label="目标孔位", data_key="mount_resource.@flatten", data_source=DataSource.EXECUTOR),
# test使用
ActionOutputHandle(
key="collector_mass",
data_type="collector_mass",
label="极流体质量",
data_key="collector_mass",
data_source=DataSource.EXECUTOR,
),
ActionOutputHandle(
key="active_material",
data_type="active_material",
label="活性物质含量",
data_key="active_material",
data_source=DataSource.EXECUTOR,
),
ActionOutputHandle(
key="capacity",
data_type="capacity",
label="克容量",
data_key="capacity",
data_source=DataSource.EXECUTOR,
),
ActionOutputHandle(
key="battery_system",
data_type="battery_system",
label="电池体系",
data_key="battery_system",
data_source=DataSource.EXECUTOR,
),
],
ActionOutputHandle(key="collector_mass", data_type="collector_mass",
label="极流体质量", data_key="collector_mass", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="active_material", data_type="active_material",
label="活性物质含量", data_key="active_material", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="capacity", data_type="capacity",
label="克容量", data_key="capacity", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="battery_system", data_type="battery_system",
label="电池体系", data_key="battery_system", data_source=DataSource.EXECUTOR),
]
)
def manual_confirm(
self,
@@ -439,156 +343,67 @@ class VirtualWorkbench:
battery_system: List[str],
timeout_seconds: int,
assignee_user_ids: list[str],
**kwargs,
**kwargs
) -> dict:
"""
人工确认资源转移和扣电测试参数。
Args:
resource[待转移资源]: 需要人工确认的资源列表。
target_device[目标设备]: 资源要转移到的目标设备 ID。
mount_resource[目标孔位]: 资源要挂载到的目标孔位列表。
collector_mass[极流体质量]: 每个样品对应的极流体质量。
active_material[活性物质含量]: 每个样品对应的活性物质含量。
capacity[克容量]: 每个样品对应的克容量,单位 mAh/g。
battery_system[电池体系]: 每个样品对应的电池体系名称。
timeout_seconds[超时时间]: 人工确认超时时间,单位秒。
assignee_user_ids[确认人]: 指定处理人工确认任务的用户 ID 列表。
Note:
修改的结果无效,是只读的。
timeout_seconds: 超时时间默认3600秒
collector_mass: 极流体质量
active_material: 活性物质含量
capacity: 克容量mAh/g
battery_system: 电池体系
修改的结果无效,是只读的
"""
resource_tree = ResourceTreeSet.from_plr_resources(cast(Any, resource)).dump()
mount_resource_tree = ResourceTreeSet.from_plr_resources(cast(Any, mount_resource)).dump()
resource = ResourceTreeSet.from_plr_resources(resource).dump()
mount_resource = ResourceTreeSet.from_plr_resources(mount_resource).dump()
kwargs.update(locals())
kwargs.pop("kwargs")
kwargs.pop("self")
kwargs["resource"] = resource_tree
kwargs["mount_resource"] = mount_resource_tree
kwargs.pop("resource_tree")
kwargs.pop("mount_resource_tree")
return kwargs
@action(
description="转移物料",
handles=[
ActionInputHandle(
key="target_device",
data_type="device_id",
label="目标设备",
data_key="target_device",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="resource",
data_type="resource",
label="待转移资源",
data_key="resource",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="mount_resource",
data_type="resource",
label="目标孔位",
data_key="mount_resource",
data_source=DataSource.HANDLE,
),
],
ActionInputHandle(key="target_device", data_type="device_id",
label="目标设备", data_key="target_device", data_source=DataSource.HANDLE),
ActionInputHandle(key="resource", data_type="resource",
label="待转移资源", data_key="resource", data_source=DataSource.HANDLE),
ActionInputHandle(key="mount_resource", data_type="resource",
label="目标孔位", data_key="mount_resource", data_source=DataSource.HANDLE),
]
)
async def transfer(
self,
resource: List[ResourceSlot],
target_device: DeviceSlot,
mount_resource: List[ResourceSlot],
):
"""
转移资源到目标设备。
Args:
resource[待转移资源]: 待转移的资源列表。
target_device[目标设备]: 接收资源的目标设备 ID。
mount_resource[目标孔位]: 目标设备上的挂载孔位列表。
"""
future = ROS2DeviceNode.run_async_func(
self._ros_node.transfer_resource_to_another,
True,
async def transfer(self, resource: List[ResourceSlot], target_device: DeviceSlot, mount_resource: List[ResourceSlot]):
future = ROS2DeviceNode.run_async_func(self._ros_node.transfer_resource_to_another, True,
**{
"plr_resources": resource,
"target_device_id": target_device,
"target_resources": mount_resource,
"sites": [None] * len(mount_resource),
},
)
})
result = await future
return result
@action(
description="扣电测试启动",
handles=[
ActionInputHandle(
key="resource",
data_type="resource",
label="待转移资源",
data_key="resource",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="mount_resource",
data_type="resource",
label="目标孔位",
data_key="mount_resource",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="collector_mass",
data_type="collector_mass",
label="极流体质量",
data_key="collector_mass",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="active_material",
data_type="active_material",
label="活性物质含量",
data_key="active_material",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="capacity",
data_type="capacity",
label="克容量",
data_key="capacity",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="battery_system",
data_type="battery_system",
label="电池体系",
data_key="battery_system",
data_source=DataSource.HANDLE,
),
],
ActionInputHandle(key="resource", data_type="resource",
label="待转移资源", data_key="resource", data_source=DataSource.HANDLE),
ActionInputHandle(key="mount_resource", data_type="resource",
label="目标孔位", data_key="mount_resource", data_source=DataSource.HANDLE),
ActionInputHandle(key="collector_mass", data_type="collector_mass",
label="极流体质量", data_key="collector_mass", data_source=DataSource.HANDLE),
ActionInputHandle(key="active_material", data_type="active_material",
label="活性物质含量", data_key="active_material", data_source=DataSource.HANDLE),
ActionInputHandle(key="capacity", data_type="capacity",
label="克容量", data_key="capacity", data_source=DataSource.HANDLE),
ActionInputHandle(key="battery_system", data_type="battery_system",
label="电池体系", data_key="battery_system", data_source=DataSource.HANDLE),
]
)
async def test(
self,
resource: List[ResourceSlot],
mount_resource: List[ResourceSlot],
collector_mass: List[float],
active_material: List[float],
capacity: List[float],
battery_system: list[str],
self, resource: List[ResourceSlot], mount_resource: List[ResourceSlot], collector_mass: List[float], active_material: List[float], capacity: List[float], battery_system: list[str]
):
"""
启动扣电测试。
Args:
resource[待测试资源]: 需要进行扣电测试的资源列表。
mount_resource[测试孔位]: 扣电测试使用的目标孔位列表。
collector_mass[极流体质量]: 每个样品对应的极流体质量。
active_material[活性物质含量]: 每个样品对应的活性物质含量。
capacity[克容量]: 每个样品对应的克容量,单位 mAh/g。
battery_system[电池体系]: 每个样品对应的电池体系名称。
"""
print(resource)
print(mount_resource)
print(collector_mass)
@@ -600,11 +415,16 @@ class VirtualWorkbench:
auto_prefix=True,
description="批量准备物料 - 虚拟起始节点, 生成A1-A5物料, 输出5个handle供后续节点使用",
handles=[
ActionOutputHandle(key="channel_1", data_type="workbench_material", label="实验1", data_key="material_1", data_source=DataSource.EXECUTOR), # noqa: E501
ActionOutputHandle(key="channel_2", data_type="workbench_material", label="实验2", data_key="material_2", data_source=DataSource.EXECUTOR), # noqa: E501
ActionOutputHandle(key="channel_3", data_type="workbench_material", label="实验3", data_key="material_3", data_source=DataSource.EXECUTOR), # noqa: E501
ActionOutputHandle(key="channel_4", data_type="workbench_material", label="实验4", data_key="material_4", data_source=DataSource.EXECUTOR), # noqa: E501
ActionOutputHandle(key="channel_5", data_type="workbench_material", label="实验5", data_key="material_5", data_source=DataSource.EXECUTOR), # noqa: E501
ActionOutputHandle(key="channel_1", data_type="workbench_material",
label="实验1", data_key="material_1", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="channel_2", data_type="workbench_material",
label="实验2", data_key="material_2", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="channel_3", data_type="workbench_material",
label="实验3", data_key="material_3", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="channel_4", data_type="workbench_material",
label="实验4", data_key="material_4", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="channel_5", data_type="workbench_material",
label="实验5", data_key="material_5", data_source=DataSource.EXECUTOR),
],
)
def prepare_materials(
@@ -617,9 +437,6 @@ class VirtualWorkbench:
作为工作流的起始节点, 生成指定数量的物料编号供后续节点使用。
输出5个handle (material_1 ~ material_5), 分别对应实验1~5。
Args:
count[物料数量]: 要生成的物料数量,默认生成 5 个。
"""
materials = [i for i in range(1, count + 1)]
@@ -640,11 +457,7 @@ class VirtualWorkbench:
LabSample(
sample_uuid=sample_uuid,
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
),
extra={"material_uuid": content} if isinstance(content, str) else (content.serialize() if content else {}),
)
for sample_uuid, content in sample_uuids.items()
],
@@ -654,27 +467,12 @@ class VirtualWorkbench:
auto_prefix=True,
description="将物料从An位置移动到空闲加热台, 返回分配的加热台ID",
handles=[
ActionInputHandle(
key="material_input",
data_type="workbench_material",
label="物料编号",
data_key="material_number",
data_source=DataSource.HANDLE,
),
ActionOutputHandle(
key="heating_station_output",
data_type="workbench_station",
label="加热台ID",
data_key="station_id",
data_source=DataSource.EXECUTOR,
),
ActionOutputHandle(
key="material_number_output",
data_type="workbench_material",
label="物料编号",
data_key="material_number",
data_source=DataSource.EXECUTOR,
),
ActionInputHandle(key="material_input", data_type="workbench_material",
label="物料编号", data_key="material_number", data_source=DataSource.HANDLE),
ActionOutputHandle(key="heating_station_output", data_type="workbench_station",
label="加热台ID", data_key="station_id", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="material_number_output", data_type="workbench_material",
label="物料编号", data_key="material_number", data_source=DataSource.EXECUTOR),
],
)
def move_to_heating_station(
@@ -686,9 +484,6 @@ class VirtualWorkbench:
将物料从An位置移动到加热台
多线程并发调用时, 会竞争机械臂使用权, 并自动查找空闲加热台
Args:
material_number[物料编号]: 要移动的物料编号,对应 A1、A2 等起始位置。
"""
material_id = f"A{material_number}"
task_desc = f"移动{material_id}到加热台"
@@ -751,8 +546,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -775,8 +569,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -788,34 +581,14 @@ class VirtualWorkbench:
always_free=True,
description="启动指定加热台的加热程序",
handles=[
ActionInputHandle(
key="station_id_input",
data_type="workbench_station",
label="加热台ID",
data_key="station_id",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="material_number_input",
data_type="workbench_material",
label="物料编号",
data_key="material_number",
data_source=DataSource.HANDLE,
),
ActionOutputHandle(
key="heating_done_station",
data_type="workbench_station",
label="加热完成-加热台ID",
data_key="station_id",
data_source=DataSource.EXECUTOR,
),
ActionOutputHandle(
key="heating_done_material",
data_type="workbench_material",
label="加热完成-物料编号",
data_key="material_number",
data_source=DataSource.EXECUTOR,
),
ActionInputHandle(key="station_id_input", data_type="workbench_station",
label="加热台ID", data_key="station_id", data_source=DataSource.HANDLE),
ActionInputHandle(key="material_number_input", data_type="workbench_material",
label="物料编号", data_key="material_number", data_source=DataSource.HANDLE),
ActionOutputHandle(key="heating_done_station", data_type="workbench_station",
label="加热完成-加热台ID", data_key="station_id", data_source=DataSource.EXECUTOR),
ActionOutputHandle(key="heating_done_material", data_type="workbench_material",
label="加热完成-物料编号", data_key="material_number", data_source=DataSource.EXECUTOR),
],
)
def start_heating(
@@ -826,10 +599,6 @@ class VirtualWorkbench:
) -> StartHeatingResult:
"""
启动指定加热台的加热程序
Args:
station_id[加热台ID]: 要启动加热的加热台编号。
material_number[物料编号]: 当前加热台上的物料编号。
"""
self.logger.info(f"[加热台{station_id}] 开始加热")
@@ -846,8 +615,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -870,8 +638,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -891,8 +658,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -932,9 +698,7 @@ class VirtualWorkbench:
self._update_data_status(f"加热台{station_id}加热中: {progress:.1f}%")
if time.time() - last_countdown_log >= 5.0:
self.logger.info(
f"[加热台{station_id}] {material_id} 剩余 {remaining:.1f}s"
)
self.logger.info(f"[加热台{station_id}] {material_id} 剩余 {remaining:.1f}s")
last_countdown_log = time.time()
if elapsed >= self.HEATING_TIME:
@@ -951,9 +715,7 @@ class VirtualWorkbench:
self._active_tasks[material_id]["status"] = "heating_completed"
self._update_data_status(f"加热台{station_id}加热完成")
self.logger.info(
f"[加热台{station_id}] {material_id}加热完成 (用时{self.HEATING_TIME}s)"
)
self.logger.info(f"[加热台{station_id}] {material_id}加热完成 (用时{self.HEATING_TIME}s)")
return {
"success": True,
@@ -967,8 +729,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -979,20 +740,10 @@ class VirtualWorkbench:
auto_prefix=True,
description="将物料从加热台移动到输出位置Cn",
handles=[
ActionInputHandle(
key="output_station_input",
data_type="workbench_station",
label="加热台ID",
data_key="station_id",
data_source=DataSource.HANDLE,
),
ActionInputHandle(
key="output_material_input",
data_type="workbench_material",
label="物料编号",
data_key="material_number",
data_source=DataSource.HANDLE,
),
ActionInputHandle(key="output_station_input", data_type="workbench_station",
label="加热台ID", data_key="station_id", data_source=DataSource.HANDLE),
ActionInputHandle(key="output_material_input", data_type="workbench_material",
label="物料编号", data_key="material_number", data_source=DataSource.HANDLE),
],
)
def move_to_output(
@@ -1003,10 +754,6 @@ class VirtualWorkbench:
) -> MoveToOutputResult:
"""
将物料从加热台移动到输出位置Cn
Args:
station_id[加热台ID]: 已完成加热的加热台编号。
material_number[物料编号]: 要移动到输出位置的物料编号,对应 Cn。
"""
output_number = material_number
@@ -1023,8 +770,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -1048,8 +794,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -1069,8 +814,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()
@@ -1152,8 +896,7 @@ class VirtualWorkbench:
oss_path="",
extra=(
{"material_uuid": content}
if isinstance(content, str)
else (content.serialize() if content else {})
if isinstance(content, str) else (content.serialize() if content else {})
),
)
for sample_uuid, content in sample_uuids.items()

View File

@@ -32,7 +32,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
MAX_SCAN_DEPTH = 10 # 最大目录递归深度
MAX_SCAN_FILES = 1000 # 最大扫描文件数量
_CACHE_VERSION = 2 # 缓存格式版本号,格式变更时递增
_CACHE_VERSION = 1 # 缓存格式版本号,格式变更时递增
# 合法的装饰器来源模块
_REGISTRY_DECORATOR_MODULE = "unilabos.registry.decorators"
@@ -258,6 +258,8 @@ def scan_directory(
}
# ---------------------------------------------------------------------------
# File-level parsing
# ---------------------------------------------------------------------------
@@ -359,7 +361,6 @@ def _parse_file(
"actions": class_body.get("actions", {}),
"status_properties": class_body.get("status_properties", {}),
"init_params": class_body.get("init_params", []),
"init_docstring": class_body.get("init_docstring"),
"auto_methods": class_body.get("auto_methods", {}),
"import_map": import_map,
}
@@ -496,6 +497,7 @@ def _collect_imports(tree: ast.Module, module_path: str = "") -> Dict[str, str]:
return import_map
# ---------------------------------------------------------------------------
# Decorator finding & argument extraction
# ---------------------------------------------------------------------------
@@ -766,7 +768,6 @@ def _extract_class_body(
"actions": {}, # method_name -> action_info
"status_properties": {}, # prop_name -> status_info
"init_params": [], # [{"name": ..., "type": ..., "default": ...}, ...]
"init_docstring": None,
"auto_methods": {}, # method_name -> method_info (no @action decorator)
}
@@ -779,7 +780,6 @@ def _extract_class_body(
# --- __init__ ---
if method_name == "__init__":
result["init_params"] = _extract_method_params(item, import_map)
result["init_docstring"] = ast.get_docstring(item)
continue
# --- Skip private/dunder ---

View File

@@ -51,18 +51,14 @@ Qone_nmr:
properties:
check_interval:
default: 60
description: 检查间隔时间默认60秒
type: string
expected_count:
default: 1
description: 期望生成的.nmr文件数量默认1个
type: string
monitor_dir:
description: 要监督的目录路径如果未指定则使用self.monitor_directory
type: string
stability_checks:
default: 3
description: 文件大小稳定性检查次数默认3次
type: string
required: []
type: object
@@ -89,14 +85,11 @@ Qone_nmr:
goal:
properties:
output_dir:
description: 输出目录如果未指定使用self.output_directory
type: string
string_list:
description: 字符串列表
type: string
txt_encoding:
default: utf-8
description: 文件编码
type: string
required:
- string_list
@@ -158,13 +151,6 @@ Qone_nmr:
additionalProperties: false
properties:
string:
description: '包含多个字符串的输入数据,支持两种格式:
1. 逗号分隔:如 "A 1 B 2 C 3, X 10 Y 20 Z 30"
2. 换行分隔:如 "A 1 B 2 C 3
X 10 Y 20 Z 30"'
type: string
title: StrSingleInput_Goal
type: object

View File

@@ -491,17 +491,14 @@ bioyond_cell:
goal:
properties:
material_names:
description: 物料名称列表;默认使用 [LiPF6, LiDFOB, DTD, LiFSI, LiPO2F2]
items:
type: string
type: array
type_id:
default: 3a190ca0-b2f6-9aeb-8067-547e72c11469
description: 物料类型ID
type: string
warehouse_name:
default: 粉末加样头堆栈
description: 目标仓库名(用于取位置信息)
type: string
required: []
type: object
@@ -530,16 +527,12 @@ bioyond_cell:
goal:
properties:
location_name_or_id:
description: 具体库位名称(如 A01或库位 UUID由用户指定。
type: string
material_name:
description: 物料名称(会优先匹配配置模板)。
type: string
type_id:
description: 物料类型 ID若为空则尝试从配置推断
type: string
warehouse_name:
description: 需要入库的仓库名称;若为空则仅创建不入库。
type: string
required:
- material_name
@@ -668,20 +661,15 @@ bioyond_cell:
goal:
properties:
board_type:
description: 板类型,如 "5ml分液瓶板"、"配液瓶(小)板"
type: string
bottle_type:
description: 瓶类型,如 "5ml分液瓶"、"配液瓶(小)"
type: string
location_code:
description: 库位编号,例如 "A01"
type: string
name:
description: 物料名称
type: string
warehouse_name:
default: 手动堆栈
description: 仓库名称,默认为 "手动堆栈",支持 "自动堆栈-左"、"自动堆栈-右" 等
type: string
required:
- name
@@ -1968,19 +1956,19 @@ bioyond_cell:
properties:
source_wh_id:
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
description: 来源仓库 Id (默认为3号仓库)
description: 来源仓库ID
type: string
source_x:
default: 1
description: 来源位置 X 坐标
description: 来源位置X坐标
type: integer
source_y:
default: 1
description: 来源位置 Y 坐标
description: 来源位置Y坐标
type: integer
source_z:
default: 1
description: 来源位置 Z 坐标
description: 来源位置Z坐标
type: integer
required: []
type: object
@@ -2073,11 +2061,9 @@ bioyond_cell:
goal:
properties:
order_code:
description: 任务编号
type: string
timeout:
default: 36000
description: 超时时间(秒)
type: integer
required:
- order_code
@@ -2106,15 +2092,12 @@ bioyond_cell:
goal:
properties:
order_code:
description: 任务编号
type: string
poll_interval:
default: 0.5
description: 轮询间隔(秒),默认 0.5 秒
type: number
timeout:
default: 36000
description: 超时时间(秒)
type: integer
required:
- order_code
@@ -2171,15 +2154,10 @@ bioyond_cell:
config:
properties:
bioyond_config:
description: '从 JSON 文件加载的 bioyond 配置字典
包含 api_host, api_key, HTTP_host, HTTP_port 等配置'
type: object
deck:
description: Deck 配置(可选,会从 JSON 中自动处理)
type: string
protocol_type:
description: 协议类型(可选)
type: string
required: []
type: object

View File

@@ -47,10 +47,8 @@ bioyond_dispensing_station:
goal:
properties:
report_request:
description: WorkstationReportRequest 对象,包含任务完成信息
type: string
used_materials:
description: 物料使用记录列表
type: string
required:
- report_request
@@ -104,7 +102,6 @@ bioyond_dispensing_station:
goal:
properties:
material_name:
description: 物料名称
type: string
required:
- material_name
@@ -614,10 +611,10 @@ bioyond_dispensing_station:
goal:
properties:
target_device_id:
description: 目标反应站设备ID(所有转移组使用同一个设备)
description: 目标反应站设备ID(从设备列表中选择,所有转移组使用同一个目标设备
type: string
transfer_groups:
description: '转移任务组列表,每组包含:'
description: 转移任务组列表每组包含物料名称、目标堆栈和目标库位,可以添加多组
type: array
required:
- target_device_id
@@ -697,13 +694,10 @@ bioyond_dispensing_station:
config:
properties:
config:
description: 配置字典,应包含material_type_mappings等配置
type: object
deck:
description: Deck对象
type: string
protocol_type:
description: 协议类型(由ROS系统传递,此处忽略)
type: string
required: []
type: object

View File

@@ -150,15 +150,15 @@ coincellassemblyworkstation_device:
properties:
assembly_pressure:
default: 4200
description: 电池压制力 (N)
description: 电池压制力(N)
type: integer
assembly_type:
default: 7
description: 组装类型 (7=不用铝箔垫, 8=使用铝箔垫)
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
type: integer
battery_clean_ignore:
default: false
description: 是否忽略电池清洁
description: 是否忽略电池清洁步骤
type: boolean
battery_pressure_mode:
default: true
@@ -166,29 +166,29 @@ coincellassemblyworkstation_device:
type: boolean
dual_drop_first_volume:
default: 25
description: 二次滴液第一次排液体积 (μL)
description: 二次滴液第一次排液体积(μL)
type: integer
dual_drop_mode:
default: false
description: 电解液添加模式 (False=单次滴液, True=二次滴液)
description: 电解液添加模式(false=单次滴液, true=二次滴液)
type: boolean
dual_drop_start_timing:
default: false
description: 二次滴液开始滴液时机 (False=正极片前, True=正极片后)
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
type: boolean
dual_drop_suction_timing:
default: false
description: 二次滴液吸液时机 (False=正常吸液, True=先吸液)
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
type: boolean
elec_num:
description: 电解液瓶数
type: string
elec_use_num:
description: 每瓶电解液组装电池数
description: 每瓶电解液组装电池数
type: string
elec_vol:
default: 50
description: 电解液吸液量 (μL)
description: 电解液吸液量(μL)
type: integer
file_path:
default: /Users/sml/work
@@ -196,7 +196,7 @@ coincellassemblyworkstation_device:
type: string
fujipian_juzhendianwei:
default: 0
description: 负极片矩阵点位
description: 负极片矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
fujipian_panshu:
default: 0
@@ -204,7 +204,7 @@ coincellassemblyworkstation_device:
type: integer
gemo_juzhendianwei:
default: 0
description: 隔膜矩阵点位
description: 隔膜矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
gemopanshu:
default: 0
@@ -216,7 +216,7 @@ coincellassemblyworkstation_device:
type: boolean
qiangtou_juzhendianwei:
default: 0
description: 枪头盒矩阵点位
description: 枪头盒矩阵点位。盘位置从1开始计数有效范围1-32, 64-96 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
required:
- elec_num
@@ -308,13 +308,7 @@ coincellassemblyworkstation_device:
properties:
material_search_enable:
default: false
description: '是否启用物料搜寻功能。
设备初始化后会弹出物料搜寻确认弹窗,
此参数控制自动点击''是''(启用)或''否''(不启用)。
默认为False不启用物料搜寻。'
description: 是否启用物料搜寻功能。设备初始化后会弹出物料搜寻确认弹窗,此参数控制自动点击"是"(启用)或"否"(不启用)。默认为false(不启用物料搜寻)
type: boolean
required: []
type: object
@@ -553,15 +547,15 @@ coincellassemblyworkstation_device:
properties:
assembly_pressure:
default: 4200
description: 电池压制力 (N)
description: 电池压制力(N)
type: integer
assembly_type:
default: 7
description: 组装类型 (7=不用铝箔垫, 8=使用铝箔垫)
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
type: integer
battery_clean_ignore:
default: false
description: 是否忽略电池清洁
description: 是否忽略电池清洁步骤
type: boolean
battery_pressure_mode:
default: true
@@ -569,29 +563,29 @@ coincellassemblyworkstation_device:
type: boolean
dual_drop_first_volume:
default: 25
description: 二次滴液第一次排液体积 (μL)
description: 二次滴液第一次排液体积(μL)
type: integer
dual_drop_mode:
default: false
description: 电解液添加模式 (False=单次滴液, True=二次滴液)
description: 电解液添加模式(false=单次滴液, true=二次滴液)
type: boolean
dual_drop_start_timing:
default: false
description: 二次滴液开始滴液时机 (False=正极片前, True=正极片后)
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
type: boolean
dual_drop_suction_timing:
default: false
description: 二次滴液吸液时机 (False=正常吸液, True=先吸液)
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
type: boolean
elec_num:
description: 电解液瓶数
description: 电解液瓶数如果在workflow中已通过handles连接上游(create_orders的bottle_count输出),则此参数会自动从上游获取,无需手动填写;如果单独使用此函数(没有上游连接),则必须手动填写电解液瓶数
type: string
elec_use_num:
description: 每瓶电解液组装电池数
description: 每瓶电解液组装电池数
type: string
elec_vol:
default: 50
description: 电解液吸液量 (μL)
description: 电解液吸液量(μL)
type: integer
file_path:
default: /Users/sml/work
@@ -599,7 +593,7 @@ coincellassemblyworkstation_device:
type: string
fujipian_juzhendianwei:
default: 0
description: 负极片矩阵点位
description: 负极片矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
fujipian_panshu:
default: 0
@@ -607,7 +601,7 @@ coincellassemblyworkstation_device:
type: integer
gemo_juzhendianwei:
default: 0
description: 隔膜矩阵点位
description: 隔膜矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
gemopanshu:
default: 0
@@ -619,7 +613,7 @@ coincellassemblyworkstation_device:
type: boolean
qiangtou_juzhendianwei:
default: 0
description: 枪头盒矩阵点位
description: 枪头盒矩阵点位。盘位置从1开始计数有效范围1-32, 64-96 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
required:
- elec_num

View File

@@ -18,7 +18,6 @@ xyz_stepper_controller:
goal:
properties:
degrees:
description: 角度值
type: number
required:
- degrees
@@ -45,7 +44,6 @@ xyz_stepper_controller:
goal:
properties:
axis:
description: 电机轴
type: object
required:
- axis
@@ -73,7 +71,6 @@ xyz_stepper_controller:
properties:
enable:
default: true
description: True为使能False为失能
type: boolean
required: []
type: object
@@ -102,11 +99,9 @@ xyz_stepper_controller:
goal:
properties:
axis:
description: 电机轴
type: object
enable:
default: true
description: True为使能False为失能
type: boolean
required:
- axis
@@ -157,7 +152,6 @@ xyz_stepper_controller:
goal:
properties:
axis:
description: 电机轴
type: object
required:
- axis
@@ -189,21 +183,16 @@ xyz_stepper_controller:
properties:
acceleration:
default: 1000
description: 加速度(rpm/s)
type: integer
axis:
description: 电机轴
type: object
position:
description: 目标位置(步数)
type: integer
precision:
default: 100
description: 到位精度
type: integer
speed:
default: 5000
description: 运行速度(rpm)
type: integer
required:
- axis
@@ -236,21 +225,16 @@ xyz_stepper_controller:
properties:
acceleration:
default: 1000
description: 加速度
type: integer
axis:
description: 电机轴
type: object
degrees:
description: 目标角度(度)
type: number
precision:
default: 100
description: 精度
type: integer
speed:
default: 5000
description: 移动速度
type: integer
required:
- axis
@@ -283,21 +267,16 @@ xyz_stepper_controller:
properties:
acceleration:
default: 1000
description: 加速度
type: integer
axis:
description: 电机轴
type: object
precision:
default: 100
description: 精度
type: integer
revolutions:
description: 目标圈数
type: number
speed:
default: 5000
description: 移动速度
type: integer
required:
- axis
@@ -330,20 +309,15 @@ xyz_stepper_controller:
properties:
acceleration:
default: 1000
description: 加速度
type: integer
speed:
default: 5000
description: 运行速度
type: integer
x:
description: X轴目标位置
type: integer
y:
description: Y轴目标位置
type: integer
z:
description: Z轴目标位置
type: integer
required: []
type: object
@@ -376,20 +350,15 @@ xyz_stepper_controller:
properties:
acceleration:
default: 1000
description: 加速度
type: integer
speed:
default: 5000
description: 移动速度
type: integer
x_deg:
description: X轴目标角度
type: number
y_deg:
description: Y轴目标角度
type: number
z_deg:
description: Z轴目标角度
type: number
required: []
type: object
@@ -422,20 +391,15 @@ xyz_stepper_controller:
properties:
acceleration:
default: 1000
description: 加速度
type: integer
speed:
default: 5000
description: 移动速度
type: integer
x_rev:
description: X轴目标圈数
type: number
y_rev:
description: Y轴目标圈数
type: number
z_rev:
description: Z轴目标圈数
type: number
required: []
type: object
@@ -463,7 +427,6 @@ xyz_stepper_controller:
goal:
properties:
revolutions:
description: 圈数
type: number
required:
- revolutions
@@ -493,13 +456,10 @@ xyz_stepper_controller:
properties:
acceleration:
default: 1000
description: 加速度(rpm/s)
type: integer
axis:
description: 电机轴
type: object
speed:
description: 运行速度(rpm),正值正转,负值反转
type: integer
required:
- axis
@@ -527,7 +487,6 @@ xyz_stepper_controller:
goal:
properties:
steps:
description: 步数
type: integer
required:
- steps
@@ -554,7 +513,6 @@ xyz_stepper_controller:
goal:
properties:
steps:
description: 步数
type: integer
required:
- steps
@@ -606,11 +564,9 @@ xyz_stepper_controller:
goal:
properties:
axis:
description: 电机轴
type: object
timeout:
default: 30.0
description: 超时时间(秒)
type: number
required:
- axis
@@ -635,14 +591,11 @@ xyz_stepper_controller:
properties:
baudrate:
default: 115200
description: 波特率
type: integer
port:
description: 串口端口名
type: string
timeout:
default: 1.0
description: 通信超时时间
type: number
required:
- port

View File

@@ -510,11 +510,9 @@ liquid_handler:
goal:
properties:
msg:
description: information to be printed
type: string
seconds:
default: 0
description: seconds to wait
type: string
required: []
type: object
@@ -2965,22 +2963,15 @@ liquid_handler:
additionalProperties: false
properties:
channel:
description: int
maximum: 2147483647
minimum: -2147483648
type: integer
dis_to_top:
description: 'float
Height in mm to move to relative to the well top.'
maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308
type: number
well:
additionalProperties: false
description: 'Well
The target well.'
properties:
category:
type: string
@@ -4838,13 +4829,11 @@ liquid_handler:
config:
properties:
backend:
description: Backend to use.
type: object
channel_num:
default: 8
type: integer
deck:
description: Deck to use.
type: object
simulator:
default: false
@@ -4894,17 +4883,14 @@ liquid_handler.biomek:
bind_parent_id:
type: string
liquid_input_slot:
description: 液体输入槽列表
items:
type: integer
type: array
liquid_type:
description: 液体类型列表
items:
type: string
type: array
liquid_volume:
description: 液体体积列表
items:
type: integer
type: array
@@ -4915,7 +4901,6 @@ liquid_handler.biomek:
type: object
type: array
slot_on_deck:
description: 甲板上的槽位
type: integer
required:
- resource_tracker
@@ -5051,27 +5036,20 @@ liquid_handler.biomek:
additionalProperties: false
properties:
none_keys:
description: 需要设置为None的键列表
items:
type: string
type: array
protocol_author:
description: 协议作者
type: string
protocol_date:
description: 协议日期
type: string
protocol_description:
description: 协议描述
type: string
protocol_name:
description: 协议名称
type: string
protocol_type:
description: 协议类型
type: string
protocol_version:
description: 协议版本
type: string
title: LiquidHandlerProtocolCreation_Goal
type: object

View File

@@ -87,7 +87,7 @@ neware_battery_test_system:
properties:
filepath:
default: bts_status.json
description: 输出文件路径
description: 输出JSON文件路径
type: string
required: []
type: object
@@ -146,7 +146,7 @@ neware_battery_test_system:
goal:
properties:
plate_num:
description: 盘号 (1 或 2),如果为None则返回所有盘的状态
description: 盘号 (1 或 2),如果为null则返回所有盘的状态
type: integer
required: []
type: object
@@ -237,11 +237,11 @@ neware_battery_test_system:
goal:
properties:
csv_path:
description: 输入CSV文件路径
description: 输入CSV文件的绝对路径
type: string
output_dir:
default: .
description: 输出目录用于存储XML文件和备份,默认当前目录
description: 输出目录用于存储XML和备份文件),默认当前目录
type: string
required:
- csv_path
@@ -302,14 +302,14 @@ neware_battery_test_system:
goal:
properties:
backup_dir:
description: 备份目录路径默认使用最近一次 submit_from_csvbackup_dir
description: 备份目录路径默认使用最近一次submit_from_csvbackup_dir
type: string
file_pattern:
default: '*'
description: 文件通配符模式,默认 "*" 上传所有文件(例如 "*.csv" 仅上传 CSV 文件)
description: 文件通配符模式,例如 *.csv 或 Battery_*.nda
type: string
oss_prefix:
description: OSS 对象前缀默认使用类初始化时的配置
description: OSS对象路径前缀默认使用self.oss_prefix
type: string
required: []
type: object
@@ -336,25 +336,19 @@ neware_battery_test_system:
config:
properties:
devtype:
description: 设备类型标识
type: string
ip:
description: TCP服务器IP地址
type: string
machine_id:
default: 1
description: 机器ID
type: integer
oss_prefix:
default: neware_backup
description: OSS对象路径前缀默认"neware_backup"
type: string
oss_upload_enabled:
default: false
description: 是否启用OSS上传功能默认False
type: boolean
port:
description: TCP端口
type: integer
size_x:
default: 50
@@ -366,7 +360,6 @@ neware_battery_test_system:
default: 20
type: number
timeout:
description: 通信超时时间(秒)
type: integer
required: []
type: object

View File

@@ -207,12 +207,8 @@ separator.homemade:
goal:
properties:
condition:
description: The condition to be monitored, either 'delta' or 'time'.
type: string
value:
description: 'The threshold value for the condition.
`delta > 0.05`, `time > 60`'
type: string
required:
- condition
@@ -309,17 +305,12 @@ separator.homemade:
event:
type: string
settling_time:
description: The duration for which to settle after stirring, in
seconds. Defaults to 10.
type: string
stir_speed:
description: The speed of stirring, in RPM. Defaults to 300.
maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308
type: number
stir_time:
description: The duration for which to stir, in seconds. Defaults
to 10.
maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308
type: number

View File

@@ -456,7 +456,6 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal:
properties:
volume:
description: 'absolute position of the plunger, unit: mL'
type: number
required:
- volume
@@ -482,7 +481,6 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal:
properties:
volume:
description: 'absolute position of the plunger, unit: mL'
type: number
required:
- volume
@@ -689,10 +687,8 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal:
properties:
max_velocity:
description: 'maximum velocity of the plunger, unit: ml/s'
type: number
position:
description: 'absolute position of the plunger, unit: ml'
type: number
required:
- position
@@ -1007,7 +1003,6 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal:
properties:
volume:
description: 'absolute position of the plunger, unit: mL'
type: number
required:
- volume
@@ -1033,7 +1028,6 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal:
properties:
volume:
description: 'absolute position of the plunger, unit: mL'
type: number
required:
- volume
@@ -1240,10 +1234,8 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal:
properties:
max_velocity:
description: 'maximum velocity of the plunger, unit: ml/s'
type: number
position:
description: 'absolute position of the plunger, unit: ml'
type: number
required:
- position

View File

@@ -32,7 +32,7 @@ reaction_station.bioyond:
type: integer
end_point:
default: 0
description: 终点计时点 (Start=0, End=1)
description: 终点计时点 (Start=开始前, End=结束后)
type: integer
end_step_key:
default: ''
@@ -40,11 +40,11 @@ reaction_station.bioyond:
type: string
start_point:
default: 0
description: 起点计时点 (Start=0, End=1)
description: 起点计时点 (Start=开始前, End=结束后)
type: integer
start_step_key:
default: ''
description: 起点步骤Key (可选, 默认为空则自动选择)
description: 起点步骤Key (例如 "feeding", "liquid", 可选, 默认为空则自动选择)
type: string
required:
- duration
@@ -91,7 +91,6 @@ reaction_station.bioyond:
goal:
properties:
json_str:
description: 订单参数的JSON字符串
type: string
required:
- json_str
@@ -118,7 +117,6 @@ reaction_station.bioyond:
goal:
properties:
workflow_ids:
description: 要删除的工作流ID数组
items:
type: string
type: array
@@ -147,7 +145,6 @@ reaction_station.bioyond:
goal:
properties:
json_str:
description: 'JSON格式的字符串,包含:'
type: string
required:
- json_str
@@ -200,7 +197,6 @@ reaction_station.bioyond:
goal:
properties:
web_workflow_json:
description: JSON 格式的网页工作流列表
type: string
required:
- web_workflow_json
@@ -232,10 +228,8 @@ reaction_station.bioyond:
goal:
properties:
reactor_id:
description: 反应器编号 (1-5)
type: integer
temperature:
description: 目标温度 (°C)
type: number
required:
- reactor_id
@@ -263,7 +257,6 @@ reaction_station.bioyond:
goal:
properties:
preintake_id:
description: 通量ID
type: string
required:
- preintake_id
@@ -345,7 +338,6 @@ reaction_station.bioyond:
goal:
properties:
value:
description: 工作流 ID 列表
items:
type: string
type: array
@@ -373,7 +365,6 @@ reaction_station.bioyond:
goal:
properties:
workflow_id:
description: 工作流ID
type: string
required:
- workflow_id
@@ -433,11 +424,11 @@ reaction_station.bioyond:
goal:
properties:
assign_material_name:
description: 物料名称(液体种类)
description: 物料名称(不能为空)
type: string
temperature:
default: 25.0
description: 温度(C)
description: 温度设定(°C)
type: number
time:
default: '90'
@@ -445,14 +436,14 @@ reaction_station.bioyond:
type: string
titration_type:
default: '1'
description: 是否滴定(NO=1, YES=2)
description: 是否滴定(NO=, YES=)
type: string
torque_variation:
default: 2
description: 是否观察(NO=1, YES=2)
description: 是否观察 (NO=, YES=)
type: integer
volume:
description: 分液量(μL)
description: 分液公式(mL)
type: string
required:
- assign_material_name
@@ -534,11 +525,11 @@ reaction_station.bioyond:
properties:
assign_material_name:
default: BAPP
description: 物料名称(试剂瓶位)
description: 物料名称
type: string
temperature:
default: 25.0
description: 温度设定(C)
description: 温度设定(°C)
type: number
time:
default: '0'
@@ -546,15 +537,15 @@ reaction_station.bioyond:
type: string
titration_type:
default: '1'
description: 是否滴定(NO=1, YES=2)
description: 是否滴定(NO=, YES=)
type: string
torque_variation:
default: 1
description: 是否观察(int类型, 1=否, 2=是)
description: 是否观察 (NO=否, YES=是)
type: integer
volume:
default: '350'
description: 分液质量(g)
description: 分液公式(mL)
type: string
required: []
type: object
@@ -602,28 +593,26 @@ reaction_station.bioyond:
description: 物料名称
type: string
solvents:
description: '溶剂信息的字典或JSON字符串(可选),格式如下:
{'
description: '溶剂信息对象(可选),包含: additional_solvent(溶剂体积mL), total_liquid_volume(总液体体积mL)。如果提供,将自动计算volume'
type: string
temperature:
default: 25.0
description: 温度设定(C)
description: 温度设定(°C),默认25.00
type: number
time:
default: '360'
description: 观察时间(分钟)
description: 观察时间(分钟),默认360
type: string
titration_type:
default: '1'
description: 是否滴定(NO=1, YES=2)
description: 是否滴定(NO=, YES=是),默认NO
type: string
torque_variation:
default: 2
description: 是否观察(NO=1, YES=2)
description: 是否观察 (NO=, YES=是),默认YES
type: integer
volume:
description: 分液量(μL),直接指定体积(可选,如果提供solvents自动计算)
description: 分液量(mL)。可直接提供,或通过solvents参数自动计算
type: string
required:
- assign_material_name
@@ -682,32 +671,33 @@ reaction_station.bioyond:
description: 物料名称
type: string
extracted_actuals:
description: 从报告提取的实际加料量JSON字符串,包含actualTargetWeigh和actualVolume
description: 从报告提取的实际加料量JSON字符串,包含actualTargetWeigh(m二酐滴定)和actualVolume(V二酐滴定)
type: string
feeding_order_data:
description: feeding_order JSON字符串或对象,用于获取m二酐值
description: 'feeding_order JSON对象,用于获取m二酐值(type为main_anhydride的amount)。示例:
{"feeding_order": [{"type": "main_anhydride", "amount": 1.915}]}'
type: string
temperature:
default: 25.0
description: 温度(C)
description: 温度设定(°C),默认25.00
type: number
time:
default: '90'
description: 观察时间(分钟)
description: 观察时间(分钟),默认90
type: string
titration_type:
default: '2'
description: 是否滴定(NO=1, YES=2),默认2
description: 是否滴定(NO=, YES=),默认YES
type: string
torque_variation:
default: 2
description: 是否观察(NO=1, YES=2)
description: 是否观察 (NO=, YES=是),默认YES
type: integer
volume_formula:
description: 分液公式(μL),如果提供则直接使用,否则自动计算
description: 分液公式(mL)。可直接提供固定公式,或留空由系统根据x_value、feeding_order_data、extracted_actuals自动生成
type: string
x_value:
description: 手工输入的x值,格式如 "1-2-3"
description: 公式中的x值,手工输入,格式为"{{1-2-3}}"(包含双花括号)。用于自动公式计算
type: string
required:
- assign_material_name
@@ -748,7 +738,7 @@ reaction_station.bioyond:
type: string
temperature:
default: 25.0
description: 温度(C)
description: 温度设定(°C)
type: number
time:
default: '0'
@@ -756,14 +746,14 @@ reaction_station.bioyond:
type: string
titration_type:
default: '1'
description: 是否滴定(NO=1, YES=2)
description: 是否滴定(NO=, YES=)
type: string
torque_variation:
default: 1
description: 是否观察(NO=1, YES=2)
description: 是否观察 (NO=, YES=)
type: integer
volume_formula:
description: 分液公式(μL)
description: 分液公式(mL)
type: string
required:
- volume_formula
@@ -796,7 +786,7 @@ reaction_station.bioyond:
description: 任务名称
type: string
workflow_name:
description: 合并后的工作流名称
description: 工作流名称
type: string
required:
- workflow_name
@@ -829,15 +819,15 @@ reaction_station.bioyond:
goal:
properties:
assign_material_name:
description: 物料名称(不能为空)
description: 物料名称
type: string
cutoff:
default: '900000'
description: 粘度上限(需为有效数字字符串,默认 "900000")
description: 粘度上限
type: string
temperature:
default: -10.0
description: 温度设定(C,范围:-50.00 至 100.00)
description: 温度设定(°C)
type: number
required:
- assign_material_name
@@ -919,11 +909,11 @@ reaction_station.bioyond:
description: 物料名称(用于获取试剂瓶位ID)
type: string
material_id:
description: 粉末类型ID, Salt=1, Flour=2, BTDA=3
description: 粉末类型IDSalt=21分钟Flour=面粉27分钟BTDA=BTDA38分钟
type: string
temperature:
default: 25.0
description: 温度设定(C)
description: 温度设定(°C)
type: number
time:
default: '0'
@@ -931,7 +921,7 @@ reaction_station.bioyond:
type: string
torque_variation:
default: 1
description: 是否观察(NO=1, YES=2)
description: 是否观察 (NO=, YES=)
type: integer
required:
- material_id
@@ -955,13 +945,10 @@ reaction_station.bioyond:
config:
properties:
config:
description: 配置字典,应包含workflow_mappings等配置
type: object
deck:
description: Deck对象
type: string
protocol_type:
description: 协议类型(由ROS系统传递,此处忽略)
type: string
required: []
type: object

View File

@@ -198,8 +198,6 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
additionalProperties: false
properties:
command:
description: A JSON-formatted string that includes option, target,
speed, lift_height, mt_height
type: string
title: SendCmd_Goal
type: object
@@ -243,8 +241,6 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
additionalProperties: false
properties:
command:
description: A JSON-formatted string that includes quaternion, speed,
position
type: string
title: SendCmd_Goal
type: object
@@ -288,7 +284,6 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
additionalProperties: false
properties:
command:
description: A JSON-formatted string that includes speed
type: string
title: SendCmd_Goal
type: object

View File

@@ -709,8 +709,6 @@ linear_motion.toyo_xyz.sim:
additionalProperties: false
properties:
command:
description: A JSON-formatted string that includes option, target,
speed, lift_height, mt_height
type: string
title: SendCmd_Goal
type: object
@@ -754,8 +752,6 @@ linear_motion.toyo_xyz.sim:
additionalProperties: false
properties:
command:
description: A JSON-formatted string that includes quaternion, speed,
position
type: string
title: SendCmd_Goal
type: object
@@ -799,7 +795,6 @@ linear_motion.toyo_xyz.sim:
additionalProperties: false
properties:
command:
description: A JSON-formatted string that includes speed
type: string
title: SendCmd_Goal
type: object

View File

@@ -2179,7 +2179,6 @@ virtual_multiway_valve:
goal:
properties:
port_number:
description: 端口号 (1-8)
type: integer
required:
- port_number
@@ -2226,7 +2225,6 @@ virtual_multiway_valve:
goal:
properties:
port_number:
description: 目标端口号 (1-8)
type: integer
required:
- port_number
@@ -2263,7 +2261,6 @@ virtual_multiway_valve:
additionalProperties: false
properties:
command:
description: 目标位置 (0-8) 或位置字符串
type: string
title: SendCmd_Goal
type: object
@@ -2307,7 +2304,6 @@ virtual_multiway_valve:
additionalProperties: false
properties:
command:
description: 目标位置 (0-8) 或位置字符串
type: string
title: SendCmd_Goal
type: object
@@ -4219,7 +4215,6 @@ virtual_solenoid_valve:
additionalProperties: false
properties:
string:
description: '"ON"/"OFF" 或 "OPEN"/"CLOSED"'
type: string
title: StrSingleInput_Goal
type: object
@@ -4263,7 +4258,6 @@ virtual_solenoid_valve:
additionalProperties: false
properties:
command:
description: '"OPEN"/"CLOSED" 或其他控制命令'
type: string
title: SendCmd_Goal
type: object
@@ -4424,20 +4418,16 @@ virtual_solid_dispenser:
event:
type: string
mass:
description: 质量字符串 (如 "2.9 g")
type: string
mol:
description: 摩尔数字符串 (如 "0.12 mol")
type: string
purpose:
description: 添加目的
type: string
rate_spec:
type: string
ratio:
type: string
reagent:
description: 试剂名称
type: string
stir:
type: boolean
@@ -4449,7 +4439,6 @@ virtual_solid_dispenser:
type: string
vessel:
additionalProperties: false
description: 目标容器
properties:
category:
type: string
@@ -5579,10 +5568,8 @@ virtual_transfer_pump:
goal:
properties:
velocity:
description: 拉取速度 (ml/s)
type: number
volume:
description: 要拉取的体积 (ml)
type: number
required:
- volume
@@ -5609,10 +5596,8 @@ virtual_transfer_pump:
goal:
properties:
velocity:
description: 推出速度 (ml/s)
type: number
volume:
description: 要推出的体积 (ml)
type: number
required:
- volume
@@ -5708,12 +5693,10 @@ virtual_transfer_pump:
additionalProperties: false
properties:
max_velocity:
description: 移动速度 (ml/s)
maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308
type: number
position:
description: 目标位置 (ml)
maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308
type: number
@@ -5862,10 +5845,8 @@ virtual_transfer_pump:
config:
properties:
config:
description: 配置字典包含max_volume, port等参数
type: object
device_id:
description: 设备ID
type: string
required: []
type: object

View File

@@ -409,11 +409,11 @@ xrd_d7mate:
properties:
end_theta:
default: 80.0
description: 结束角度≥5.5°,且必须大于 start_theta
description: 结束角度≥5.5°且必须大于start_theta
type: number
exp_time:
default: 0.1
description: 曝光时间0.1-5.0 秒)
description: 曝光时间0.1-5.0秒)
type: number
increment:
default: 0.05
@@ -421,7 +421,7 @@ xrd_d7mate:
type: number
sample_id:
default: ''
description: 样品名称
description: 样品标识符
type: string
start_theta:
default: 10.0
@@ -433,7 +433,7 @@ xrd_d7mate:
type: string
wait_minutes:
default: 3.0
description: 允许上样后、发送样品准备完成前的等待分钟数(默认 3 分钟)
description: 允许上样后等待分钟数
type: number
required: []
title: StartWorkflow_Goal
@@ -492,15 +492,12 @@ xrd_d7mate:
properties:
host:
default: 127.0.0.1
description: 设备IP地址
type: string
port:
default: 6001
description: 通信端口默认6001
type: string
timeout:
default: 10.0
description: 超时时间,单位秒
type: string
required: []
type: object

View File

@@ -217,7 +217,6 @@ zhida_gcms:
additionalProperties: false
properties:
string:
description: Base64编码的CSV数据ROS2参数名
type: string
title: StrSingleInput_Goal
type: object
@@ -258,7 +257,6 @@ zhida_gcms:
additionalProperties: false
properties:
string:
description: CSV文件路径ROS2参数名
type: string
title: StrSingleInput_Goal
type: object
@@ -291,15 +289,12 @@ zhida_gcms:
properties:
host:
default: 192.168.3.184
description: 设备IP地址本地部署时可使用'127.0.0.1'
type: string
port:
default: 5792
description: 通信端口默认5792
type: string
timeout:
default: 10.0
description: 超时时间,单位秒
type: string
required: []
type: object

View File

@@ -271,7 +271,6 @@ class Registry:
registry_cache.pkl 一个文件中,删除即可完全重置。
"""
import time as _time
from unilabos.registry.ast_registry_scanner import _CACHE_VERSION as AST_SCAN_CACHE_VERSION
from unilabos.registry.ast_registry_scanner import scan_directory
scan_t0 = _time.perf_counter()
@@ -287,10 +286,6 @@ class Registry:
# ---- 统一缓存:一个 pkl 包含所有数据 ----
unified_cache = self._load_config_cache()
ast_cache = unified_cache.setdefault("_ast_scan", {"files": {}})
if ast_cache.get("version") != AST_SCAN_CACHE_VERSION:
ast_cache = {"version": AST_SCAN_CACHE_VERSION, "files": {}}
unified_cache["_ast_scan"] = ast_cache
unified_cache.pop("_build_results", None)
# 默认:扫描 unilabos 包所在的父目录
pkg_root = Path(__file__).resolve().parent.parent # .../unilabos
@@ -566,47 +561,13 @@ class Registry:
return prop_schema
@staticmethod
def _apply_docstring_param_metadata(
schema: Dict[str, Any],
doc_info: Dict[str, Any],
field_to_param: Optional[Dict[str, str]] = None,
apply_defaults: bool = False,
) -> None:
"""Apply parsed docstring display names and descriptions to schema properties."""
if not schema or not doc_info:
return
props = schema.get("properties", {})
if not isinstance(props, dict):
return
param_descs = doc_info.get("params", {}) or {}
param_display_names = doc_info.get("param_display_names", {}) or {}
for field_name, prop_schema in props.items():
if not isinstance(prop_schema, dict):
continue
param_name = field_to_param.get(field_name, field_name) if field_to_param else field_name
if not isinstance(param_name, str):
continue
param_name = param_name.removesuffix("[]")
if param_name in param_display_names:
prop_schema["title"] = param_display_names[param_name]
elif apply_defaults and not prop_schema.get("title"):
prop_schema["title"] = field_name
if param_name in param_descs:
prop_schema["description"] = param_descs[param_name]
elif apply_defaults and "description" not in prop_schema:
prop_schema["description"] = ""
def _generate_unilab_json_command_schema(
self, method_args: list, docstring: Optional[str] = None,
import_map: Optional[Dict[str, str]] = None,
apply_doc_defaults: bool = False,
) -> Dict[str, Any]:
"""根据方法参数和 docstring 生成 UniLabJsonCommand schema"""
doc_info = parse_docstring(docstring)
param_descs = doc_info.get("params", {})
schema = {
"type": "object",
@@ -637,10 +598,12 @@ class Registry:
param_name, param_type, param_default, import_map=import_map
)
if param_name in param_descs:
schema["properties"][param_name]["description"] = param_descs[param_name]
if param_required:
schema["required"].append(param_name)
self._apply_docstring_param_metadata(schema, doc_info, apply_defaults=apply_doc_defaults)
return schema
def _generate_status_types_schema(self, status_methods: Dict[str, Any]) -> Dict[str, Any]:
@@ -836,7 +799,6 @@ class Registry:
type_str = "UniLabJsonCommandAsync" if is_async else "UniLabJsonCommand"
params = method_info.get("params", [])
method_doc = method_info.get("docstring")
method_doc_info = parse_docstring(method_doc)
goal_schema = self._generate_schema_from_ast_params(params, method_name, method_doc, imap)
if action_args is not None:
@@ -866,11 +828,7 @@ class Registry:
# action handles: 从 @action(handles=[...]) 提取并转换为标准格式
raw_handles = (action_args or {}).get("handles")
handles = (
normalize_ast_action_handles(raw_handles)
if isinstance(raw_handles, list)
else (raw_handles or {})
)
handles = normalize_ast_action_handles(raw_handles) if isinstance(raw_handles, list) else (raw_handles or {})
# placeholder_keys: 先从参数类型自动检测,再用装饰器显式配置覆盖/补充
pk = detect_placeholder_keys(params)
@@ -889,12 +847,7 @@ class Registry:
"goal": goal,
"feedback": (action_args or {}).get("feedback") or {},
"result": (action_args or {}).get("result") or {},
"schema": wrap_action_schema(
goal_schema,
action_name,
description=(action_args or {}).get("description") or method_doc_info.get("description", ""),
result_schema=result_schema,
),
"schema": wrap_action_schema(goal_schema, action_name, result_schema=result_schema),
"goal_default": goal_default,
"handles": handles,
"placeholder_keys": pk,
@@ -933,11 +886,7 @@ class Registry:
action_name = f"auto-{action_name}"
raw_handles = action_args.get("handles")
handles = (
normalize_ast_action_handles(raw_handles)
if isinstance(raw_handles, list)
else (raw_handles or {})
)
handles = normalize_ast_action_handles(raw_handles) if isinstance(raw_handles, list) else (raw_handles or {})
method_params = method_info.get("params", [])
@@ -1030,10 +979,7 @@ class Registry:
"schema": schema,
"goal_default": goal_default,
"handles": handles,
"placeholder_keys": {
**detect_placeholder_keys(method_params),
**(action_args.get("placeholder_keys") or {}),
},
"placeholder_keys": {**detect_placeholder_keys(method_params), **(action_args.get("placeholder_keys") or {})},
}
if action_args.get("always_free") or method_info.get("always_free"):
action_entry["always_free"] = True
@@ -1042,22 +988,13 @@ class Registry:
nt = normalize_enum_value(action_args.get("node_type"), NodeType)
if nt:
action_entry["node_type"] = nt
goal_schema_for_docs = action_entry.get("schema", {}).get("properties", {}).get("goal", {})
self._apply_docstring_param_metadata(
goal_schema_for_docs,
parse_docstring(method_info.get("docstring")),
goal,
apply_defaults=True,
)
action_value_mappings[action_name] = action_entry
action_value_mappings = dict(sorted(action_value_mappings.items()))
# --- init_param_schema = { config: <init_params>, data: <status_types> } ---
init_params = ast_meta.get("init_params", [])
config_schema = self._generate_schema_from_ast_params(
init_params, "__init__", ast_meta.get("init_docstring"), import_map=imap
)
config_schema = self._generate_schema_from_ast_params(init_params, "__init__", import_map=imap)
data_schema = self._generate_status_schema_from_ast(
ast_meta.get("status_properties", {}), imap
)
@@ -1105,6 +1042,7 @@ class Registry:
) -> Dict[str, Any]:
"""Generate JSON Schema from AST-extracted parameter list."""
doc_info = parse_docstring(docstring)
param_descs = doc_info.get("params", {})
schema: Dict[str, Any] = {
"type": "object",
@@ -1134,10 +1072,12 @@ class Registry:
pname, ptype, pdefault, import_map
)
if pname in param_descs:
schema["properties"][pname]["description"] = param_descs[pname]
if prequired:
schema["required"].append(pname)
self._apply_docstring_param_metadata(schema, doc_info, apply_defaults=True)
return schema
def _generate_status_schema_from_ast(
@@ -1867,7 +1807,7 @@ class Registry:
else:
action_key = f"auto-{k}"
goal_schema = self._generate_unilab_json_command_schema(
v["args"], docstring=v.get("docstring"), import_map=enhanced_import_map
v["args"], import_map=enhanced_import_map
)
ret_type = v.get("return_type", "")
result_schema = None
@@ -1876,13 +1816,7 @@ class Registry:
"result", ret_type, None, import_map=enhanced_import_map
)
old_cfg = old_action_configs.get(action_key) or old_action_configs.get(f"auto-{k}", {})
doc_info = parse_docstring(v.get("docstring"))
new_schema = wrap_action_schema(
goal_schema,
action_key,
description=doc_info.get("description", ""),
result_schema=result_schema,
)
new_schema = wrap_action_schema(goal_schema, action_key, result_schema=result_schema)
old_schema = old_cfg.get("schema", {})
if old_schema:
preserve_field_descriptions(new_schema, old_schema)
@@ -1948,12 +1882,6 @@ class Registry:
merged_pk = dict(old_cfg.get("placeholder_keys", {}))
merged_pk.update(detect_placeholder_keys(v["args"]))
goal_schema_for_docs = (
entry_schema.get("properties", {}).get("goal", {})
if isinstance(entry_schema, dict)
else {}
)
self._apply_docstring_param_metadata(goal_schema_for_docs, doc_info, entry_goal)
entry = {
"type": entry_type,
@@ -1974,8 +1902,7 @@ class Registry:
device_config["init_param_schema"] = {}
init_schema = self._generate_unilab_json_command_schema(
enhanced_info["init_params"],
docstring=enhanced_info.get("init_docstring"),
enhanced_info["init_params"], "__init__",
import_map=enhanced_import_map,
)
device_config["init_param_schema"]["config"] = init_schema
@@ -2022,9 +1949,7 @@ class Registry:
action_str_type_mapping[action_type_str] = target_type
if target_type is not None:
try:
action_config["goal_default"] = ROS2MessageInstance(
target_type.Goal()
).get_python_dict()
action_config["goal_default"] = ROS2MessageInstance(target_type.Goal()).get_python_dict()
except Exception:
action_config["goal_default"] = {}
prev_schema = action_config.get("schema", {})
@@ -2216,7 +2141,6 @@ class Registry:
"unilabos_device_id": {
"type": "string",
"default": "",
"title": "设备ID",
"description": "UniLabOS设备ID用于指定执行动作的具体设备实例",
},
**schema["properties"]["goal"]["properties"],
@@ -2288,14 +2212,7 @@ class Registry:
lab_registry = Registry()
def build_registry(
registry_paths=None,
devices_dirs=None,
upload_registry=False,
check_mode=False,
complete_registry=False,
external_only=False,
):
def build_registry(registry_paths=None, devices_dirs=None, upload_registry=False, check_mode=False, complete_registry=False, external_only=False):
"""
构建或获取Registry单例实例
"""
@@ -2309,12 +2226,7 @@ def build_registry(
if path not in current_paths:
lab_registry.registry_paths.append(path)
lab_registry.setup(
devices_dirs=devices_dirs,
upload_registry=upload_registry,
complete_registry=complete_registry,
external_only=external_only,
)
lab_registry.setup(devices_dirs=devices_dirs, upload_registry=upload_registry, complete_registry=complete_registry, external_only=external_only)
# 将 AST 扫描的字符串类型替换为实际 ROS2 消息类(仅查找 ROS2 类型,不 import 设备模块)
lab_registry.resolve_all_types()

View File

@@ -36,40 +36,16 @@ class ROSMsgNotFound(Exception):
# ---------------------------------------------------------------------------
_SECTION_RE = re.compile(r"^(\w[\w\s]*):\s*$")
_PARAM_HEADER_RE = re.compile(
r"^\s*(?P<name>\w[\w]*)\s*(?:\[(?P<display_name>[^\]]+)\])?(?:\s*\([^)]*\))?\s*$"
)
def _parse_docstring_param_header(param_part: str) -> Tuple[str, Optional[str]]:
"""Parse ``name[display_name]`` or Google-style ``name (type)``."""
match = _PARAM_HEADER_RE.match(param_part.strip())
if not match:
return param_part.strip().split("(")[0].strip(), None
display_name = match.group("display_name")
if display_name is not None:
display_name = display_name.strip() or None
return match.group("name").strip(), display_name
def parse_docstring(docstring: Optional[str]) -> Dict[str, Any]:
"""
解析 docstring提取描述和参数说明。
支持:
- Google-style ``Args:`` / ``Parameters:`` 小节
- 直接参数行 ``field: desc``
- 带显示名参数行 ``field[Display Name]: desc``
解析 Google-style docstring提取描述和参数说明。
Returns:
{
"description": "短描述",
"params": {"param1": "参数1描述", ...},
"param_display_names": {"param1": "显示名", ...},
}
{"description": "短描述", "params": {"param1": "参数1描述", ...}}
"""
result: Dict[str, Any] = {"description": "", "params": {}, "param_display_names": {}}
result: Dict[str, Any] = {"description": "", "params": {}}
if not docstring:
return result
@@ -77,53 +53,33 @@ def parse_docstring(docstring: Optional[str]) -> Dict[str, Any]:
if not lines:
return result
result["description"] = lines[0].strip()
in_args = False
current_section: Optional[str] = None
current_param: Optional[str] = None
current_display_name: Optional[str] = None
current_desc_parts: list = []
def flush_current_param() -> None:
nonlocal current_param, current_display_name, current_desc_parts
if current_param is None:
return
result["params"][current_param] = "\n".join(current_desc_parts).strip()
if current_display_name:
result["param_display_names"][current_param] = current_display_name
current_param = None
current_display_name = None
current_desc_parts = []
first_line = lines[0].strip()
start_index = 0
if not _SECTION_RE.match(first_line) and ":" not in first_line:
result["description"] = first_line
start_index = 1
for line in lines[start_index:]:
for line in lines[1:]:
stripped = line.strip()
if not stripped:
if current_param is not None:
current_desc_parts.append("")
continue
section_match = _SECTION_RE.match(stripped)
if section_match:
flush_current_param()
current_section = section_match.group(1).lower()
in_args = current_section in ("args", "arguments", "parameters", "params")
if current_param is not None:
result["params"][current_param] = "\n".join(current_desc_parts).strip()
current_param = None
current_desc_parts = []
section_name = section_match.group(1).lower()
in_args = section_name in ("args", "arguments", "parameters", "params")
continue
parse_as_param = in_args or current_section is None
if not parse_as_param:
if not in_args:
continue
if ":" in stripped:
flush_current_param()
if ":" in stripped and not stripped.startswith(" "):
if current_param is not None:
result["params"][current_param] = "\n".join(current_desc_parts).strip()
param_part, _, desc_part = stripped.partition(":")
param_name, display_name = _parse_docstring_param_header(param_part)
param_name = param_part.strip().split("(")[0].strip()
current_param = param_name
current_display_name = display_name
current_desc_parts = [desc_part.strip()]
elif current_param is not None:
aline = line
@@ -133,7 +89,8 @@ def parse_docstring(docstring: Optional[str]) -> Dict[str, Any]:
aline = aline[1:]
current_desc_parts.append(aline.strip())
flush_current_param()
if current_param is not None:
result["params"][current_param] = "\n".join(current_desc_parts).strip()
return result

View File

@@ -206,7 +206,6 @@ class ImportManager:
"ast_analysis_success": False,
"import_map": {},
"init_params": [],
"init_docstring": None,
"status_methods": {},
"action_methods": {},
}
@@ -252,7 +251,6 @@ class ImportManager:
# 映射到统一字段名(与 registry.py complete_registry 消费端一致)
result["init_params"] = body.get("init_params", [])
result["init_docstring"] = body.get("init_docstring")
result["status_methods"] = body.get("status_properties", {})
result["action_methods"] = {
k: {