Merge branch 'dev' into prcix9320

This commit is contained in:
q434343
2026-05-14 17:54:24 +08:00
parent 6288e37464
commit 940abc3664
66 changed files with 2471 additions and 730 deletions

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 = 1 # 缓存格式版本号,格式变更时递增
_CACHE_VERSION = 2 # 缓存格式版本号,格式变更时递增
# 合法的装饰器来源模块
_REGISTRY_DECORATOR_MODULE = "unilabos.registry.decorators"
@@ -258,8 +258,6 @@ def scan_directory(
}
# ---------------------------------------------------------------------------
# File-level parsing
# ---------------------------------------------------------------------------
@@ -361,6 +359,7 @@ 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,
}
@@ -497,7 +496,6 @@ def _collect_imports(tree: ast.Module, module_path: str = "") -> Dict[str, str]:
return import_map
# ---------------------------------------------------------------------------
# Decorator finding & argument extraction
# ---------------------------------------------------------------------------
@@ -768,6 +766,7 @@ 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)
}
@@ -780,6 +779,7 @@ 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 ---
@@ -825,6 +825,7 @@ def _extract_class_body(
action_args.setdefault("placeholder_keys", {})
action_args.setdefault("always_free", False)
action_args.setdefault("is_protocol", False)
action_args.setdefault("feedback_interval", 1.0)
action_args.setdefault("description", "")
action_args.setdefault("auto_prefix", False)
action_args.setdefault("parent", False)

View File

@@ -343,6 +343,7 @@ def action(
auto_prefix: bool = False,
parent: bool = False,
node_type: Optional["NodeType"] = None,
feedback_interval: Optional[float] = None,
):
"""
动作方法装饰器
@@ -378,9 +379,16 @@ def action(
"""
def decorator(func: F) -> F:
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
import asyncio as _asyncio
if _asyncio.iscoroutinefunction(func):
@wraps(func)
async def wrapper(*args, **kwargs):
return await func(*args, **kwargs)
else:
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
# action_type 为哨兵值 => 用户没传, 视为 None (UniLabJsonCommand)
resolved_type = None if action_type is _ACTION_TYPE_UNSET else action_type
@@ -399,6 +407,8 @@ def action(
"auto_prefix": auto_prefix,
"parent": parent,
}
if feedback_interval is not None:
meta["feedback_interval"] = feedback_interval
if node_type is not None:
meta["node_type"] = node_type.value if isinstance(node_type, NodeType) else str(node_type)
wrapper._action_registry_meta = meta # type: ignore[attr-defined]

View File

@@ -51,14 +51,18 @@ 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
@@ -85,11 +89,14 @@ 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
@@ -151,6 +158,13 @@ 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,14 +491,17 @@ 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
@@ -527,12 +530,16 @@ 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
@@ -661,15 +668,20 @@ 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
@@ -1956,19 +1968,19 @@ bioyond_cell:
properties:
source_wh_id:
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
description: 来源仓库ID
description: 来源仓库 Id (默认为3号仓库)
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
@@ -2061,9 +2073,11 @@ bioyond_cell:
goal:
properties:
order_code:
description: 任务编号
type: string
timeout:
default: 36000
description: 超时时间(秒)
type: integer
required:
- order_code
@@ -2092,12 +2106,15 @@ 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
@@ -2154,10 +2171,15 @@ 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,8 +47,10 @@ bioyond_dispensing_station:
goal:
properties:
report_request:
description: WorkstationReportRequest 对象,包含任务完成信息
type: string
used_materials:
description: 物料使用记录列表
type: string
required:
- report_request
@@ -102,6 +104,7 @@ bioyond_dispensing_station:
goal:
properties:
material_name:
description: 物料名称
type: string
required:
- material_name
@@ -611,10 +614,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
@@ -694,10 +697,13 @@ 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: 负极片矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
description: 负极片矩阵点位
type: integer
fujipian_panshu:
default: 0
@@ -204,7 +204,7 @@ coincellassemblyworkstation_device:
type: integer
gemo_juzhendianwei:
default: 0
description: 隔膜矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
description: 隔膜矩阵点位
type: integer
gemopanshu:
default: 0
@@ -216,7 +216,7 @@ coincellassemblyworkstation_device:
type: boolean
qiangtou_juzhendianwei:
default: 0
description: 枪头盒矩阵点位。盘位置从1开始计数有效范围1-32, 64-96 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
description: 枪头盒矩阵点位
type: integer
required:
- elec_num
@@ -308,7 +308,13 @@ coincellassemblyworkstation_device:
properties:
material_search_enable:
default: false
description: 是否启用物料搜寻功能。设备初始化后会弹出物料搜寻确认弹窗,此参数控制自动点击"是"(启用)或"否"(不启用)。默认为false(不启用物料搜寻)
description: '是否启用物料搜寻功能。
设备初始化后会弹出物料搜寻确认弹窗,
此参数控制自动点击''是''(启用)或''否''(不启用)。
默认为False不启用物料搜寻。'
type: boolean
required: []
type: object
@@ -547,15 +553,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
@@ -563,29 +569,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: 电解液瓶数如果在workflow中已通过handles连接上游(create_orders的bottle_count输出),则此参数会自动从上游获取,无需手动填写;如果单独使用此函数(没有上游连接),则必须手动填写电解液瓶数
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
@@ -593,7 +599,7 @@ coincellassemblyworkstation_device:
type: string
fujipian_juzhendianwei:
default: 0
description: 负极片矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
description: 负极片矩阵点位
type: integer
fujipian_panshu:
default: 0
@@ -601,7 +607,7 @@ coincellassemblyworkstation_device:
type: integer
gemo_juzhendianwei:
default: 0
description: 隔膜矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
description: 隔膜矩阵点位
type: integer
gemopanshu:
default: 0
@@ -613,7 +619,7 @@ coincellassemblyworkstation_device:
type: boolean
qiangtou_juzhendianwei:
default: 0
description: 枪头盒矩阵点位。盘位置从1开始计数有效范围1-32, 64-96 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
description: 枪头盒矩阵点位
type: integer
required:
- elec_num

View File

@@ -31,6 +31,6 @@ hotel.thermo_orbitor_rs2_hotel:
type: object
model:
mesh: thermo_orbitor_rs2_hotel
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/thermo_orbitor_rs2_hotel/macro_device.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/thermo_orbitor_rs2_hotel/macro_device.xacro
type: device
version: 1.0.0

View File

@@ -18,6 +18,7 @@ xyz_stepper_controller:
goal:
properties:
degrees:
description: 角度值
type: number
required:
- degrees
@@ -44,6 +45,7 @@ xyz_stepper_controller:
goal:
properties:
axis:
description: 电机轴
type: object
required:
- axis
@@ -71,6 +73,7 @@ xyz_stepper_controller:
properties:
enable:
default: true
description: True为使能False为失能
type: boolean
required: []
type: object
@@ -99,9 +102,11 @@ xyz_stepper_controller:
goal:
properties:
axis:
description: 电机轴
type: object
enable:
default: true
description: True为使能False为失能
type: boolean
required:
- axis
@@ -152,6 +157,7 @@ xyz_stepper_controller:
goal:
properties:
axis:
description: 电机轴
type: object
required:
- axis
@@ -183,16 +189,21 @@ 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
@@ -225,16 +236,21 @@ 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
@@ -267,16 +283,21 @@ 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
@@ -309,15 +330,20 @@ 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
@@ -350,15 +376,20 @@ 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
@@ -391,15 +422,20 @@ 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
@@ -427,6 +463,7 @@ xyz_stepper_controller:
goal:
properties:
revolutions:
description: 圈数
type: number
required:
- revolutions
@@ -456,10 +493,13 @@ xyz_stepper_controller:
properties:
acceleration:
default: 1000
description: 加速度(rpm/s)
type: integer
axis:
description: 电机轴
type: object
speed:
description: 运行速度(rpm),正值正转,负值反转
type: integer
required:
- axis
@@ -487,6 +527,7 @@ xyz_stepper_controller:
goal:
properties:
steps:
description: 步数
type: integer
required:
- steps
@@ -513,6 +554,7 @@ xyz_stepper_controller:
goal:
properties:
steps:
description: 步数
type: integer
required:
- steps
@@ -564,9 +606,11 @@ xyz_stepper_controller:
goal:
properties:
axis:
description: 电机轴
type: object
timeout:
default: 30.0
description: 超时时间(秒)
type: number
required:
- axis
@@ -591,11 +635,14 @@ 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,9 +510,11 @@ liquid_handler:
goal:
properties:
msg:
description: information to be printed
type: string
seconds:
default: 0
description: seconds to wait
type: string
required: []
type: object
@@ -2963,15 +2965,22 @@ 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
@@ -4829,11 +4838,13 @@ 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
@@ -4883,14 +4894,17 @@ 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
@@ -4901,6 +4915,7 @@ liquid_handler.biomek:
type: object
type: array
slot_on_deck:
description: 甲板上的槽位
type: integer
required:
- resource_tracker
@@ -5036,20 +5051,27 @@ 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: 输出JSON文件路径
description: 输出文件路径
type: string
required: []
type: object
@@ -146,7 +146,7 @@ neware_battery_test_system:
goal:
properties:
plate_num:
description: 盘号 (1 或 2),如果为null则返回所有盘的状态
description: 盘号 (1 或 2),如果为None则返回所有盘的状态
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 或 Battery_*.nda
description: 文件通配符模式,默认 "*" 上传所有文件(例如 "*.csv" 仅上传 CSV 文件)
type: string
oss_prefix:
description: OSS对象路径前缀默认使用self.oss_prefix
description: OSS 对象前缀默认使用类初始化时的配置
type: string
required: []
type: object
@@ -336,19 +336,25 @@ 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
@@ -360,6 +366,7 @@ neware_battery_test_system:
default: 20
type: number
timeout:
description: 通信超时时间(秒)
type: integer
required: []
type: object

View File

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

@@ -257,6 +257,8 @@ 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
@@ -300,6 +302,7 @@ 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
@@ -345,7 +348,7 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
type: object
model:
mesh: arm_slider
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/arm_slider/macro_device.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/arm_slider/macro_device.xacro
type: device
version: 1.0.0
robotic_arm.UR:

View File

@@ -768,6 +768,8 @@ 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
@@ -811,6 +813,7 @@ 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,6 +2179,7 @@ virtual_multiway_valve:
goal:
properties:
port_number:
description: 端口号 (1-8)
type: integer
required:
- port_number
@@ -2225,6 +2226,7 @@ virtual_multiway_valve:
goal:
properties:
port_number:
description: 目标端口号 (1-8)
type: integer
required:
- port_number
@@ -2261,6 +2263,7 @@ virtual_multiway_valve:
additionalProperties: false
properties:
command:
description: 目标位置 (0-8) 或位置字符串
type: string
title: SendCmd_Goal
type: object
@@ -2304,6 +2307,7 @@ virtual_multiway_valve:
additionalProperties: false
properties:
command:
description: 目标位置 (0-8) 或位置字符串
type: string
title: SendCmd_Goal
type: object
@@ -3960,6 +3964,14 @@ virtual_separator:
io_type: source
label: bottom_phase_out
side: SOUTH
- data_key: top_outlet
data_source: executor
data_type: fluid
description: 上相(轻相)液体输出口
handler_key: topphaseout
io_type: source
label: top_phase_out
side: NORTH
- data_key: mechanical_port
data_source: handle
data_type: mechanical
@@ -4207,6 +4219,7 @@ virtual_solenoid_valve:
additionalProperties: false
properties:
string:
description: '"ON"/"OFF" 或 "OPEN"/"CLOSED"'
type: string
title: StrSingleInput_Goal
type: object
@@ -4250,6 +4263,7 @@ virtual_solenoid_valve:
additionalProperties: false
properties:
command:
description: '"OPEN"/"CLOSED" 或其他控制命令'
type: string
title: SendCmd_Goal
type: object
@@ -4410,16 +4424,20 @@ 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
@@ -4431,6 +4449,7 @@ virtual_solid_dispenser:
type: string
vessel:
additionalProperties: false
description: 目标容器
properties:
category:
type: string
@@ -5560,8 +5579,10 @@ virtual_transfer_pump:
goal:
properties:
velocity:
description: 拉取速度 (ml/s)
type: number
volume:
description: 要拉取的体积 (ml)
type: number
required:
- volume
@@ -5588,8 +5609,10 @@ virtual_transfer_pump:
goal:
properties:
velocity:
description: 推出速度 (ml/s)
type: number
volume:
description: 要推出的体积 (ml)
type: number
required:
- volume
@@ -5685,10 +5708,12 @@ 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
@@ -5837,8 +5862,10 @@ 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: 允许上样后等待分钟数
description: 允许上样后、发送样品准备完成前的等待分钟数(默认 3 分钟)
type: number
required: []
title: StartWorkflow_Goal
@@ -492,12 +492,15 @@ 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,6 +217,7 @@ zhida_gcms:
additionalProperties: false
properties:
string:
description: Base64编码的CSV数据ROS2参数名
type: string
title: StrSingleInput_Goal
type: object
@@ -257,6 +258,7 @@ zhida_gcms:
additionalProperties: false
properties:
string:
description: CSV文件路径ROS2参数名
type: string
title: StrSingleInput_Goal
type: object
@@ -289,12 +291,15 @@ 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

@@ -238,6 +238,7 @@ class Registry:
"class_name": "unilabos_class",
},
"always_free": True,
"feedback_interval": 300.0,
},
"test_latency": test_latency_action,
"auto-test_resource": test_resource_action,
@@ -270,6 +271,7 @@ 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()
@@ -285,6 +287,10 @@ 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
@@ -560,13 +566,47 @@ 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",
@@ -597,12 +637,10 @@ 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]:
@@ -798,6 +836,7 @@ 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:
@@ -827,10 +866,15 @@ 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 = (action_args or {}).get("placeholder_keys") or detect_placeholder_keys(params)
# placeholder_keys: 先从参数类型自动检测,再用装饰器显式配置覆盖/补充
pk = detect_placeholder_keys(params)
pk.update((action_args or {}).get("placeholder_keys") or {})
# 从方法返回值类型生成 result schema
result_schema = None
@@ -845,13 +889,20 @@ 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, result_schema=result_schema),
"schema": wrap_action_schema(
goal_schema,
action_name,
description=(action_args or {}).get("description") or method_doc_info.get("description", ""),
result_schema=result_schema,
),
"goal_default": goal_default,
"handles": handles,
"placeholder_keys": pk,
}
if (action_args or {}).get("always_free") or method_info.get("always_free"):
entry["always_free"] = True
_fb_iv = (action_args or {}).get("feedback_interval", method_info.get("feedback_interval", 1.0))
entry["feedback_interval"] = _fb_iv
nt = normalize_enum_value((action_args or {}).get("node_type"), NodeType)
if nt:
entry["node_type"] = nt
@@ -882,7 +933,11 @@ 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", [])
@@ -975,20 +1030,34 @@ class Registry:
"schema": schema,
"goal_default": goal_default,
"handles": handles,
"placeholder_keys": action_args.get("placeholder_keys") or detect_placeholder_keys(method_params),
"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
_fb_iv = action_args.get("feedback_interval", method_info.get("feedback_interval", 1.0))
action_entry["feedback_interval"] = _fb_iv
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__", import_map=imap)
config_schema = self._generate_schema_from_ast_params(
init_params, "__init__", ast_meta.get("init_docstring"), import_map=imap
)
data_schema = self._generate_status_schema_from_ast(
ast_meta.get("status_properties", {}), imap
)
@@ -1036,7 +1105,6 @@ 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",
@@ -1066,12 +1134,10 @@ 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(
@@ -1801,7 +1867,7 @@ class Registry:
else:
action_key = f"auto-{k}"
goal_schema = self._generate_unilab_json_command_schema(
v["args"], import_map=enhanced_import_map
v["args"], docstring=v.get("docstring"), import_map=enhanced_import_map
)
ret_type = v.get("return_type", "")
result_schema = None
@@ -1810,7 +1876,13 @@ 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}", {})
new_schema = wrap_action_schema(goal_schema, action_key, result_schema=result_schema)
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,
)
old_schema = old_cfg.get("schema", {})
if old_schema:
preserve_field_descriptions(new_schema, old_schema)
@@ -1876,6 +1948,12 @@ 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,
@@ -1896,7 +1974,8 @@ class Registry:
device_config["init_param_schema"] = {}
init_schema = self._generate_unilab_json_command_schema(
enhanced_info["init_params"], "__init__",
enhanced_info["init_params"],
docstring=enhanced_info.get("init_docstring"),
import_map=enhanced_import_map,
)
device_config["init_param_schema"]["config"] = init_schema
@@ -1943,7 +2022,9 @@ 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", {})
@@ -2135,6 +2216,7 @@ class Registry:
"unilabos_device_id": {
"type": "string",
"default": "",
"title": "设备ID",
"description": "UniLabOS设备ID用于指定执行动作的具体设备实例",
},
**schema["properties"]["goal"]["properties"],
@@ -2206,7 +2288,14 @@ 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单例实例
"""
@@ -2220,7 +2309,12 @@ def build_registry(registry_paths=None, devices_dirs=None, upload_registry=False
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

@@ -17,7 +17,7 @@ hplc_plate:
- 0
- 0
- 3.1416
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/hplc_plate/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/hplc_plate/modal.xacro
type: resource
version: 1.0.0
plate_96:
@@ -39,7 +39,7 @@ plate_96:
- 0
- 0
- 0
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96/modal.xacro
type: resource
version: 1.0.0
plate_96_high:
@@ -61,7 +61,7 @@ plate_96_high:
- 1.5708
- 0
- 1.5708
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96_high/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/plate_96_high/modal.xacro
type: resource
version: 1.0.0
tiprack_96_high:
@@ -76,7 +76,7 @@ tiprack_96_high:
init_param_schema: {}
model:
children_mesh: generic_labware_tube_10_75/meshes/0_base.stl
children_mesh_path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/generic_labware_tube_10_75/modal.xacro
children_mesh_path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/generic_labware_tube_10_75/modal.xacro
children_mesh_tf:
- 0.0018
- 0.0018
@@ -92,7 +92,7 @@ tiprack_96_high:
- 1.5708
- 0
- 1.5708
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_96_high/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_96_high/modal.xacro
type: resource
version: 1.0.0
tiprack_box:
@@ -107,7 +107,7 @@ tiprack_box:
init_param_schema: {}
model:
children_mesh: tip/meshes/tip.stl
children_mesh_path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tip/modal.xacro
children_mesh_path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tip/modal.xacro
children_mesh_tf:
- 0.0045
- 0.0045
@@ -123,6 +123,6 @@ tiprack_box:
- 0
- 0
- 0
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_box/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_box/modal.xacro
type: resource
version: 1.0.0

View File

@@ -11,7 +11,7 @@ bottle_container:
init_param_schema: {}
model:
children_mesh: bottle/meshes/bottle.stl
children_mesh_path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/bottle/modal.xacro
children_mesh_path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/bottle/modal.xacro
children_mesh_tf:
- 0.04
- 0.04
@@ -27,7 +27,7 @@ bottle_container:
- 0
- 0
- 0
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/bottle_container/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/bottle_container/modal.xacro
type: resource
version: 1.0.0
tube_container:
@@ -43,7 +43,7 @@ tube_container:
init_param_schema: {}
model:
children_mesh: tube/meshes/tube.stl
children_mesh_path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tube/modal.xacro
children_mesh_path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tube/modal.xacro
children_mesh_tf:
- 0.017
- 0.017
@@ -59,6 +59,6 @@ tube_container:
- 0
- 0
- 0
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tube_container/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tube_container/modal.xacro
type: resource
version: 1.0.0

View File

@@ -10,6 +10,6 @@ TransformXYZDeck:
init_param_schema: {}
model:
mesh: liquid_transform_xyz
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/liquid_transform_xyz/macro_device.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/liquid_transform_xyz/macro_device.xacro
type: device
version: 1.0.0

View File

@@ -10,7 +10,7 @@ OTDeck:
init_param_schema: {}
model:
mesh: opentrons_liquid_handler
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/opentrons_liquid_handler/macro_device.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/opentrons_liquid_handler/macro_device.xacro
type: device
version: 1.0.0
hplc_station:
@@ -25,6 +25,6 @@ hplc_station:
init_param_schema: {}
model:
mesh: hplc_station
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/hplc_station/macro_device.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/hplc_station/macro_device.xacro
type: device
version: 1.0.0

View File

@@ -109,7 +109,7 @@ nest_96_wellplate_100ul_pcr_full_skirt:
init_param_schema: {}
model:
children_mesh: generic_labware_tube_10_75/meshes/0_base.stl
children_mesh_path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/generic_labware_tube_10_75/modal.xacro
children_mesh_path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/generic_labware_tube_10_75/modal.xacro
children_mesh_tf:
- 0.0018
- 0.0018
@@ -125,7 +125,7 @@ nest_96_wellplate_100ul_pcr_full_skirt:
- -1.5708
- 0
- 1.5708
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
type: resource
version: 1.0.0
nest_96_wellplate_200ul_flat:
@@ -158,7 +158,7 @@ nest_96_wellplate_2ml_deep:
- -1.5708
- 0
- 1.5708
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
type: resource
version: 1.0.0
thermoscientificnunc_96_wellplate_1300ul:

View File

@@ -69,7 +69,7 @@ opentrons_96_filtertiprack_1000ul:
- -1.5708
- 0
- 1.5708
path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
path: https://leap-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tecan_nested_tip_rack/modal.xacro
type: resource
version: 1.0.0
opentrons_96_filtertiprack_10ul:

View File

@@ -36,16 +36,40 @@ 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]:
"""
解析 Google-style docstring提取描述和参数说明。
解析 docstring提取描述和参数说明。
支持:
- Google-style ``Args:`` / ``Parameters:`` 小节
- 直接参数行 ``field: desc``
- 带显示名参数行 ``field[Display Name]: desc``
Returns:
{"description": "短描述", "params": {"param1": "参数1描述", ...}}
{
"description": "短描述",
"params": {"param1": "参数1描述", ...},
"param_display_names": {"param1": "显示名", ...},
}
"""
result: Dict[str, Any] = {"description": "", "params": {}}
result: Dict[str, Any] = {"description": "", "params": {}, "param_display_names": {}}
if not docstring:
return result
@@ -53,33 +77,53 @@ 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 = []
for line in lines[1:]:
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:]:
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:
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")
flush_current_param()
current_section = section_match.group(1).lower()
in_args = current_section in ("args", "arguments", "parameters", "params")
continue
if not in_args:
parse_as_param = in_args or current_section is None
if not parse_as_param:
continue
if ":" in stripped and not stripped.startswith(" "):
if current_param is not None:
result["params"][current_param] = "\n".join(current_desc_parts).strip()
if ":" in stripped:
flush_current_param()
param_part, _, desc_part = stripped.partition(":")
param_name = param_part.strip().split("(")[0].strip()
param_name, display_name = _parse_docstring_param_header(param_part)
current_param = param_name
current_display_name = display_name
current_desc_parts = [desc_part.strip()]
elif current_param is not None:
aline = line
@@ -89,8 +133,7 @@ def parse_docstring(docstring: Optional[str]) -> Dict[str, Any]:
aline = aline[1:]
current_desc_parts.append(aline.strip())
if current_param is not None:
result["params"][current_param] = "\n".join(current_desc_parts).strip()
flush_current_param()
return result