支持选择器注册表自动生成

支持转运物料
This commit is contained in:
Xuwznln
2025-10-11 00:57:22 +08:00
parent 67c01271b7
commit 609b6006e8
31 changed files with 4268 additions and 278 deletions

View File

@@ -6,7 +6,6 @@ import signal
import sys
import threading
import time
from copy import deepcopy
from typing import Dict, Any, List
import networkx as nx
@@ -262,8 +261,6 @@ def main():
read_node_link_json,
read_graphml,
dict_from_graph,
dict_to_nested_dict,
initialize_resources,
)
from unilabos.app.communication import get_communication_client
from unilabos.registry.registry import build_registry
@@ -286,9 +283,8 @@ def main():
graph: nx.Graph
resource_tree_set: ResourceTreeSet
resource_links: List[Dict[str, Any]]
request_startup_json = http_client.request_startup_json()
if args_dict["graph"] is None:
request_startup_json = http_client.request_startup_json()
if not request_startup_json:
print_status(
"未指定设备加载文件路径尝试从HTTP获取失败请检查网络或者使用-g参数指定设备加载文件路径", "error"
@@ -339,6 +335,13 @@ def main():
resource_edge_info.pop(edge_info - ind - 1)
continue
# 如果从远端获取了物料信息,则与本地物料进行同步
if request_startup_json and "nodes" in request_startup_json:
print_status("开始同步远端物料到本地...", "info")
remote_tree_set = ResourceTreeSet.from_raw_list(request_startup_json["nodes"])
resource_tree_set.merge_remote_resources(remote_tree_set)
print_status("远端物料同步完成", "info")
# 使用 ResourceTreeSet 代替 list
args_dict["resources_config"] = resource_tree_set
args_dict["devices_config"] = resource_tree_set

View File

@@ -80,7 +80,7 @@ class HTTPClient:
info(f"首次添加资源,当前远程地址: {self.remote_addr}")
response = requests.post(
f"{self.remote_addr}/edge/material",
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
json={"nodes": resources.dump()[0], "mount_uuid": mount_uuid},
headers={"Authorization": f"Lab {self.auth}"},
timeout=100,
)
@@ -102,6 +102,8 @@ class HTTPClient:
data = res["data"]
for i in data:
uuid_mapping[i["uuid"]] = i["cloud_uuid"]
if i["cloud_uuid"] == i["uuid"]:
print(i["cloud_uuid"], i["uuid"])
else:
logger.error(f"添加物料失败: {response.text}")
for u, n in old_uuids.items():
@@ -298,7 +300,7 @@ class HTTPClient:
Response: API响应对象
"""
response = requests.get(
f"{self.remote_addr}/lab/resource/graph_info/",
f"{self.remote_addr}/edge/material/download",
headers={"Authorization": f"Lab {self.auth}"},
timeout=(3, 30),
)

View File

@@ -0,0 +1,129 @@
# config.py
"""
配置文件 - 包含所有配置信息和映射关系
"""
# API配置
API_CONFIG = {
"api_key": "",
"api_host": ""
}
# 站点类型配置
STATION_TYPES = {
"REACTION": "reaction_station", # 仅反应站
"DISPENSING": "dispensing_station", # 仅配液站
"HYBRID": "hybrid_station" # 混合模式
}
# 默认站点配置
DEFAULT_STATION_CONFIG = {
"station_type": STATION_TYPES["REACTION"], # 默认反应站模式
"enable_reaction_station": True, # 是否启用反应站功能
"enable_dispensing_station": False, # 是否启用配液站功能
"station_name": "BioyondReactionStation", # 站点名称
"description": "Bioyond反应工作站" # 站点描述
}
# 工作流映射配置
WORKFLOW_MAPPINGS = {
"reactor_taken_out": "",
"reactor_taken_in": "",
"Solid_feeding_vials": "",
"Liquid_feeding_vials(non-titration)": "",
"Liquid_feeding_solvents": "",
"Liquid_feeding(titration)": "",
"liquid_feeding_beaker": "",
"Drip_back": "",
}
# 工作流名称到DisplaySectionName的映射
WORKFLOW_TO_SECTION_MAP = {
'reactor_taken_in': '反应器放入',
'liquid_feeding_beaker': '液体投料-烧杯',
'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)',
'Liquid_feeding_solvents': '液体投料-溶剂',
'Solid_feeding_vials': '固体投料-小瓶',
'Liquid_feeding(titration)': '液体投料-滴定',
'reactor_taken_out': '反应器取出'
}
# 库位映射配置
LOCATION_MAPPING = {
'A01': '',
'A02': '',
'A03': '',
'A04': '',
'A05': '',
'A06': '',
'A07': '',
'A08': '',
'B01': '',
'B02': '',
'B03': '',
'B04': '',
'B05': '',
'B06': '',
'B07': '',
'B08': '',
'C01': '',
'C02': '',
'C03': '',
'C04': '',
'C05': '',
'C06': '',
'C07': '',
'C08': '',
'D01': '',
'D02': '',
'D03': '',
'D04': '',
'D05': '',
'D06': '',
'D07': '',
'D08': '',
}
# 物料类型配置
MATERIAL_TYPE_IDS = {
"样品板": "",
"样品": "",
"烧杯": ""
}
MATERIAL_TYPE_MAPPINGS = {
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
"样品板": "BIOYOND_PolymerStation_6VialCarrier",
}
# 步骤参数配置各工作流的步骤UUID
WORKFLOW_STEP_IDS = {
"reactor_taken_in": {
"config": ""
},
"liquid_feeding_beaker": {
"liquid": "",
"observe": ""
},
"liquid_feeding_vials_non_titration": {
"liquid": "",
"observe": ""
},
"liquid_feeding_solvents": {
"liquid": "",
"observe": ""
},
"solid_feeding_vials": {
"feeding": "",
"observe": ""
},
"liquid_feeding_titration": {
"liquid": "",
"observe": ""
},
"drip_back": {
"liquid": "",
"observe": ""
}
}

View File

@@ -10,12 +10,14 @@ import json
from unilabos.devices.workstation.workstation_base import WorkstationBase, ResourceSynchronizer
from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import BioyondV1RPC
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
from unilabos.resources.warehouse import WareHouse
from unilabos.utils.log import logger
from unilabos.resources.graphio import resource_bioyond_to_plr
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
from pylabrobot.resources.resource import Resource as ResourcePLR
from unilabos.devices.workstation.bioyond_studio.config import (
API_CONFIG, WORKFLOW_MAPPINGS, MATERIAL_TYPE_MAPPINGS,
@@ -153,6 +155,13 @@ class BioyondWorkstation(WorkstationBase):
"resources": [self.deck]
})
def transfer_resource_to_another(self, resource_a: ResourceSlot, device_id: DeviceSlot, resource_b: ResourceSlot):
ROS2DeviceNode.run_async_func(self._ros_node.transfer_resource_to_another, True, **{
"plr_resources": [resource_a],
"target_device_id": device_id,
"target_resource_uuid": getattr(resource_b, "unilabos_uuid", None),
})
def _configure_station_type(self, station_config: Optional[Dict[str, Any]] = None) -> None:
"""配置站点类型和功能模块

View File

@@ -10,6 +10,7 @@ serial:
request: null
response: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: handle_serial_request的参数schema
@@ -36,6 +37,7 @@ serial:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: read_data的参数schema
@@ -57,6 +59,7 @@ serial:
goal_default:
command: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: send_command的参数schema

View File

@@ -8,6 +8,7 @@ camera.USB:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 用于安全地关闭摄像头设备释放摄像头资源停止视频采集和发布服务。调用此函数将清理OpenCV摄像头连接并销毁ROS2节点。
@@ -28,6 +29,7 @@ camera.USB:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 定时器回调函数的参数schema。此函数负责定期采集摄像头视频帧将OpenCV格式的图像转换为ROS Image消息格式并发布到指定的视频话题。默认以10Hz频率执行确保视频流的连续性和实时性。

View File

@@ -8,6 +8,7 @@ hplc.agilent:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 检查安捷伦HPLC设备状态的函数。用于监控设备的运行状态、连接状态、错误信息等关键指标。该函数定期查询设备状态确保系统稳定运行及时发现和报告设备异常。适用于自动化流程中的设备监控、故障诊断、系统维护等场景。
@@ -29,6 +30,7 @@ hplc.agilent:
goal_default:
file_path: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 从文本文件中提取分析数据的函数。用于解析安捷伦HPLC生成的结果文件提取峰面积、保留时间、浓度等关键分析数据。支持多种文件格式的自动识别和数据结构化处理为后续数据分析和报告生成提供标准化的数据格式。适用于批量数据处理、结果验证、质量控制等分析工作流程。
@@ -55,6 +57,7 @@ hplc.agilent:
resource: null
wf_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 启动安捷伦HPLC分析序列的函数。用于执行预定义的分析方法序列包括样品进样、色谱分离、检测等完整的分析流程。支持参数配置、资源分配、工作流程管理等功能实现全自动的样品分析。适用于批量样品处理、标准化分析、质量检测等需要连续自动分析的应用场景。
@@ -83,6 +86,7 @@ hplc.agilent:
goal_default:
device_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 尝试关闭HPLC子设备的函数。用于安全地关闭泵、检测器、进样器等各个子模块确保设备正常断开连接并保护硬件安全。该函数提供错误处理和状态确认机制避免强制关闭可能造成的设备损坏。适用于设备维护、系统重启、紧急停机等需要安全关闭设备的场景。
@@ -106,6 +110,7 @@ hplc.agilent:
goal_default:
device_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 尝试打开HPLC子设备的函数。用于初始化和连接泵、检测器、进样器等各个子模块建立设备通信并进行自检。该函数提供连接验证和错误恢复机制确保子设备正常启动并准备就绪。适用于设备初始化、系统启动、设备重连等需要建立设备连接的场景。
@@ -263,6 +268,7 @@ hplc.agilent-zhida:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: HPLC设备连接关闭函数。安全地断开与智达HPLC设备的TCP socket连接释放网络资源。该函数确保连接的正确关闭避免网络资源泄露。通常在设备使用完毕或系统关闭时调用。
@@ -283,6 +289,7 @@ hplc.agilent-zhida:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: HPLC设备连接建立函数。与智达HPLC设备建立TCP socket通信连接配置通信超时参数。该函数是设备使用前的必要步骤建立成功后可进行状态查询、方法获取、任务启动等操作。连接失败时会抛出异常。

View File

@@ -9,6 +9,7 @@ raman.home_made:
goal_default:
int_time: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 设置CCD检测器积分时间的函数。用于配置拉曼光谱仪的信号采集时间控制光谱数据的质量和信噪比。较长的积分时间可获得更高的信号强度和更好的光谱质量但会增加测量时间。该函数允许根据样品特性和测量要求动态调整检测参数优化测量效果。
@@ -33,6 +34,7 @@ raman.home_made:
goal_default:
output_voltage_laser: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 设置激光器输出功率的函数。用于控制拉曼光谱仪激光器的功率输出,调节激光强度以适应不同样品的测量需求。适当的激光功率能够获得良好的拉曼信号同时避免样品损伤。该函数支持精确的功率控制,确保测量结果的稳定性和重现性。
@@ -58,6 +60,7 @@ raman.home_made:
int_time: null
laser_power: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 执行无背景扣除的拉曼光谱测量函数。用于直接采集样品的拉曼光谱信号,不进行背景校正处理。该函数配置积分时间和激光功率参数,获取原始光谱数据用于后续的数据处理分析。适用于对光谱数据质量要求较高或需要自定义背景处理流程的测量场景。
@@ -88,6 +91,7 @@ raman.home_made:
laser_power: null
sample_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 执行多次平均的无背景拉曼光谱测量函数。通过多次测量取平均值来提高光谱数据的信噪比和测量精度,减少随机噪声影响。该函数支持自定义平均次数、积分时间、激光功率等参数,并可为样品指定名称便于数据管理。适用于对测量精度要求较高的定量分析和研究应用。

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ gas_source.mock:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_closed的参数schema
@@ -28,6 +29,7 @@ gas_source.mock:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_open的参数schema
@@ -188,6 +190,7 @@ vacuum_pump.mock:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_closed的参数schema
@@ -208,6 +211,7 @@ vacuum_pump.mock:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_open的参数schema

View File

@@ -564,6 +564,7 @@ liquid_handler:
protocol_type: null
protocol_version: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 创建实验协议函数。用于建立新的液体处理实验协议,定义协议名称、描述、版本、作者、日期等基本信息。该函数支持协议模板化管理,便于实验流程的标准化和重复性。适用于实验设计、方法开发、标准操作程序建立等需要协议管理的应用场景。
@@ -607,6 +608,7 @@ liquid_handler:
msg: null
seconds: 0
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 自定义延时函数。在实验流程中插入可配置的等待时间,用于满足特定的反应时间、孵育时间或设备稳定时间要求。支持自定义延时消息和秒数设置,提供流程控制和时间管理功能。适用于酶反应等待、温度平衡、样品孵育等需要时间控制的实验步骤。
@@ -633,6 +635,7 @@ liquid_handler:
goal_default:
tip_racks: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 吸头迭代函数。用于自动管理和切换吸头架中的吸头,实现批量实验中的吸头自动分配和追踪。该函数监控吸头使用状态,自动切换到下一个可用吸头位置,确保实验流程的连续性。适用于高通量实验、批量处理、自动化流水线等需要大量吸头管理的应用场景。
@@ -659,6 +662,7 @@ liquid_handler:
volumes: null
wells: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -689,6 +693,7 @@ liquid_handler:
goal_default:
tip_racks: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 吸头架设置函数。用于配置和初始化液体处理系统的吸头架信息,包括吸头架位置、类型、容量等参数。该函数建立吸头资源管理系统,为后续的吸头选择和使用提供基础配置。适用于系统初始化、吸头架更换、实验配置等需要吸头资源管理的操作场景。
@@ -713,6 +718,7 @@ liquid_handler:
goal_default:
targets: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 吸头碰触函数。控制移液器吸头轻触容器边缘或底部,用于去除吸头外壁附着的液滴,提高移液精度和减少污染。该函数支持多目标位置操作,可配置碰触参数和位置偏移。适用于精密移液、减少液体残留、防止交叉污染等需要提高移液质量的实验操作。
@@ -739,6 +745,7 @@ liquid_handler:
target_group_name: null
unit_volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -4495,6 +4502,7 @@ liquid_handler.biomek:
resources: null
slot_on_deck: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: create_resource的参数schema
@@ -4554,6 +4562,7 @@ liquid_handler.biomek:
parent: null
slot_on_deck: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: instrument_setup_biomek的参数schema
@@ -6042,6 +6051,7 @@ liquid_handler.prcxi:
protocol_type: ''
protocol_version: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: create_protocol的参数schema
@@ -6087,6 +6097,7 @@ liquid_handler.prcxi:
msg: null
seconds: 0
handles: {}
placeholder_keys: {}
result: {}
schema:
description: custom_delay的参数schema
@@ -6113,6 +6124,7 @@ liquid_handler.prcxi:
goal_default:
tip_racks: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: iter_tips的参数schema
@@ -6139,6 +6151,7 @@ liquid_handler.prcxi:
dis_to_top: 0
well: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: move_to的参数schema
@@ -6168,6 +6181,7 @@ liquid_handler.prcxi:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: run_protocol的参数schema
@@ -6183,12 +6197,50 @@ liquid_handler.prcxi:
title: run_protocol参数
type: object
type: UniLabJsonCommandAsync
auto-set_group:
feedback: {}
goal: {}
goal_default:
group_name: null
volumes: null
wells: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
group_name:
type: string
volumes:
items:
type: number
type: array
wells:
items:
type: object
type: array
required:
- group_name
- wells
- volumes
type: object
result: {}
required:
- goal
title: set_group参数
type: object
type: UniLabJsonCommand
auto-touch_tip:
feedback: {}
goal: {}
goal_default:
targets: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: touch_tip的参数schema
@@ -6207,6 +6259,39 @@ liquid_handler.prcxi:
title: touch_tip参数
type: object
type: UniLabJsonCommandAsync
auto-transfer_group:
feedback: {}
goal: {}
goal_default:
source_group_name: null
target_group_name: null
unit_volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
source_group_name:
type: string
target_group_name:
type: string
unit_volume:
type: number
required:
- source_group_name
- target_group_name
- unit_volume
type: object
result: {}
required:
- goal
title: transfer_group参数
type: object
type: UniLabJsonCommandAsync
discard_tips:
feedback: {}
goal:

View File

@@ -9,6 +9,7 @@ neware_battery_test_system:
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -32,6 +33,7 @@ neware_battery_test_system:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -52,6 +54,7 @@ neware_battery_test_system:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''

View File

@@ -9,6 +9,7 @@ rotavap.one:
goal_default:
cmd: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cmd_write的参数schema
@@ -32,6 +33,7 @@ rotavap.one:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: main_loop的参数schema
@@ -53,6 +55,7 @@ rotavap.one:
goal_default:
time: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_pump_time的参数schema
@@ -77,6 +80,7 @@ rotavap.one:
goal_default:
time: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_rotate_time的参数schema
@@ -172,6 +176,7 @@ separator.homemade:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: read_sensor_loop的参数schema
@@ -194,6 +199,7 @@ separator.homemade:
condition: null
value: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: valve_open的参数schema
@@ -221,6 +227,7 @@ separator.homemade:
goal_default:
data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: write的参数schema

View File

@@ -8,6 +8,7 @@ solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: close的参数schema
@@ -28,6 +29,7 @@ solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_closed的参数schema
@@ -48,6 +50,7 @@ solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_open的参数schema
@@ -68,6 +71,7 @@ solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -88,6 +92,7 @@ solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: read_data的参数schema
@@ -109,6 +114,7 @@ solenoid_valve:
goal_default:
command: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: send_command的参数schema
@@ -205,6 +211,7 @@ solenoid_valve.mock:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_closed的参数schema
@@ -225,6 +232,7 @@ solenoid_valve.mock:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_open的参数schema
@@ -246,6 +254,7 @@ solenoid_valve.mock:
goal_default:
position: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_valve_position的参数schema
@@ -376,6 +385,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: close的参数schema
@@ -396,6 +406,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -417,6 +428,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal_default:
volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: pull_plunger的参数schema
@@ -441,6 +453,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal_default:
volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: push_plunger的参数schema
@@ -464,6 +477,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_aux_input_status_1的参数schema
@@ -484,6 +498,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_aux_input_status_2的参数schema
@@ -504,6 +519,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_backlash_position的参数schema
@@ -524,6 +540,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_command_buffer_status的参数schema
@@ -544,6 +561,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_software_version的参数schema
@@ -565,6 +583,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal_default:
full_command: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: send_command的参数schema
@@ -589,6 +608,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal_default:
baudrate: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_baudrate的参数schema
@@ -613,6 +633,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal_default:
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_max_velocity的参数schema
@@ -638,6 +659,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
max_velocity: null
position: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_position的参数schema
@@ -664,6 +686,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal_default:
position: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_valve_position的参数schema
@@ -688,6 +711,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal_default:
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_velocity_grade的参数schema
@@ -711,6 +735,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: stop_operation的参数schema
@@ -731,6 +756,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: wait_error的参数schema
@@ -880,6 +906,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: close的参数schema
@@ -900,6 +927,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -921,6 +949,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal_default:
volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: pull_plunger的参数schema
@@ -945,6 +974,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal_default:
volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: push_plunger的参数schema
@@ -968,6 +998,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_aux_input_status_1的参数schema
@@ -988,6 +1019,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_aux_input_status_2的参数schema
@@ -1008,6 +1040,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_backlash_position的参数schema
@@ -1028,6 +1061,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_command_buffer_status的参数schema
@@ -1048,6 +1082,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: query_software_version的参数schema
@@ -1069,6 +1104,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal_default:
full_command: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: send_command的参数schema
@@ -1093,6 +1129,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal_default:
baudrate: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_baudrate的参数schema
@@ -1117,6 +1154,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal_default:
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_max_velocity的参数schema
@@ -1142,6 +1180,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
max_velocity: null
position: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_position的参数schema
@@ -1168,6 +1207,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal_default:
position: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_valve_position的参数schema
@@ -1192,6 +1232,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal_default:
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_velocity_grade的参数schema
@@ -1215,6 +1256,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: stop_operation的参数schema
@@ -1235,6 +1277,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: wait_error的参数schema

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ agv.SEER:
ex_data: ''
obj: receive_socket
handles: {}
placeholder_keys: {}
result: {}
schema:
description: AGV底层通信命令发送函数。通过TCP socket连接向AGV发送底层控制命令支持pose位置、status状态、nav导航等命令类型。用于获取AGV当前位置坐标、运行状态或发送导航指令。该函数封装了AGV的通信协议将命令转换为十六进制数据包并处理响应解析。

View File

@@ -8,6 +8,7 @@ robotic_arm.SCARA_with_slider.virtual:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: check_tf_update_actions的参数schema
@@ -33,6 +34,7 @@ robotic_arm.SCARA_with_slider.virtual:
retry: 10
speed: 1
handles: {}
placeholder_keys: {}
result: {}
schema:
description: moveit_joint_task的参数schema
@@ -78,6 +80,7 @@ robotic_arm.SCARA_with_slider.virtual:
speed: 1
target_link: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: moveit_task的参数schema
@@ -125,6 +128,7 @@ robotic_arm.SCARA_with_slider.virtual:
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: post_init的参数schema
@@ -150,6 +154,7 @@ robotic_arm.SCARA_with_slider.virtual:
parent_link: null
resource: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: resource_manager的参数schema
@@ -176,6 +181,7 @@ robotic_arm.SCARA_with_slider.virtual:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: wait_for_resource_action的参数schema
@@ -360,6 +366,7 @@ robotic_arm.UR:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 机械臂初始化函数。执行UR机械臂的完整初始化流程包括上电、释放制动器、解除保护停止状态等。该函数确保机械臂从安全停止状态恢复到可操作状态是机械臂使用前的必要步骤。初始化完成后机械臂将处于就绪状态可以接收后续的运动指令。
@@ -381,6 +388,7 @@ robotic_arm.UR:
goal_default:
data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 从JSON字符串加载位置数据函数。接收包含机械臂位置信息的JSON格式字符串解析并存储位置数据供后续运动任务使用。位置数据通常包含多个预定义的工作位置坐标用于实现精确的多点运动控制。适用于动态配置机械臂工作位置的场景。
@@ -405,6 +413,7 @@ robotic_arm.UR:
goal_default:
file: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 从文件加载位置数据函数。读取指定的JSON文件并加载其中的机械臂位置信息。该函数支持从外部配置文件中获取预设的工作位置便于位置数据的管理和重用。适用于需要从固定配置文件中读取复杂位置序列的应用场景。
@@ -428,6 +437,7 @@ robotic_arm.UR:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 重新加载位置数据函数。重新读取并解析之前设置的位置文件,更新内存中的位置数据。该函数用于在位置文件被修改后刷新机械臂的位置配置,无需重新初始化整个系统。适用于动态更新机械臂工作位置的场景。
@@ -536,6 +546,7 @@ robotic_arm.elite:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -556,6 +567,7 @@ robotic_arm.elite:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -579,6 +591,7 @@ robotic_arm.elite:
start_addr: null
unit_id: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -609,6 +622,7 @@ robotic_arm.elite:
goal_default:
job_id: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -635,6 +649,7 @@ robotic_arm.elite:
unit_id: null
value: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -665,6 +680,7 @@ robotic_arm.elite:
goal_default:
response: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -689,6 +705,7 @@ robotic_arm.elite:
goal_default:
command: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''

View File

@@ -8,6 +8,7 @@ gripper.misumi_rz:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: data_loop的参数schema
@@ -28,6 +29,7 @@ gripper.misumi_rz:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: data_reader的参数schema
@@ -51,6 +53,7 @@ gripper.misumi_rz:
pos: null
speed: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 夹爪抓取运动控制函数。控制夹爪的开合运动,支持位置、速度、力矩的精确设定。位置参数控制夹爪开合程度,速度参数控制运动快慢,力矩参数控制夹持强度。该函数提供安全的力控制,避免损坏被抓取物体,适用于各种形状和材质的物品抓取。
@@ -80,6 +83,7 @@ gripper.misumi_rz:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 夹爪初始化函数。执行Misumi RZ夹爪的完整初始化流程包括Modbus通信建立、电机参数配置、传感器校准等。该函数确保夹爪系统从安全状态恢复到可操作状态是夹爪使用前的必要步骤。初始化完成后夹爪将处于就绪状态可接收抓取和旋转指令。
@@ -101,6 +105,7 @@ gripper.misumi_rz:
goal_default:
data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: modbus_crc的参数schema
@@ -130,6 +135,7 @@ gripper.misumi_rz:
spin_pos: null
spin_v: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: move_and_rotate的参数schema
@@ -169,6 +175,7 @@ gripper.misumi_rz:
goal_default:
cmd: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 节点夹爪移动任务函数。接收逗号分隔的命令字符串,解析位置、速度、力矩参数并执行夹爪抓取动作。该函数等待运动完成并返回执行结果,提供同步的运动控制接口。适用于需要可靠完成确认的精密抓取操作。
@@ -193,6 +200,7 @@ gripper.misumi_rz:
goal_default:
cmd: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 节点旋转移动任务函数。接收逗号分隔的命令字符串,解析角度、速度、力矩参数并执行夹爪旋转动作。该函数等待旋转完成并返回执行结果,提供同步的旋转控制接口。适用于需要精确角度定位和完成确认的旋转操作。
@@ -219,6 +227,7 @@ gripper.misumi_rz:
data_len: null
id: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: read_address的参数schema
@@ -251,6 +260,7 @@ gripper.misumi_rz:
pos: null
speed: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 夹爪绝对位置旋转控制函数。控制夹爪主轴旋转到指定的绝对角度位置支持360度连续旋转。位置参数指定目标角度速度参数控制旋转速率力矩参数设定旋转阻力限制。该函数提供高精度的角度定位适用于需要精确方向控制的操作场景。
@@ -284,6 +294,7 @@ gripper.misumi_rz:
fun: null
id: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: send_cmd的参数schema
@@ -316,6 +327,7 @@ gripper.misumi_rz:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: wait_for_gripper的参数schema
@@ -336,6 +348,7 @@ gripper.misumi_rz:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: wait_for_gripper_init的参数schema
@@ -356,6 +369,7 @@ gripper.misumi_rz:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: wait_for_rotate的参数schema
@@ -462,6 +476,7 @@ gripper.mock:
Gripper1: {}
wf_name: gripper_run
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 模拟夹爪资源ID编辑函数。用于测试和演示资源管理功能模拟修改夹爪资源的标识信息。该函数接收工作流名称、参数和资源对象模拟真实的资源更新过程并返回修改后的资源信息。适用于系统测试和开发调试场景。

View File

@@ -8,6 +8,7 @@ linear_motion.grbl:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: CNC设备初始化函数。执行Grbl CNC的完整初始化流程包括归零操作、轴校准和状态复位。该函数将所有轴移动到原点位置(0,0,0),确保设备处于已知的参考状态。初始化完成后设备进入空闲状态,可接收后续的运动指令。
@@ -29,6 +30,7 @@ linear_motion.grbl:
goal_default:
position: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: CNC绝对位置设定函数。控制CNC设备移动到指定的三维坐标位置(x,y,z)。该函数支持安全限位检查,防止超出设备工作范围。移动过程中会监控设备状态,确保安全到达目标位置。适用于精确定位和轨迹控制操作。
@@ -52,6 +54,7 @@ linear_motion.grbl:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: CNC操作停止函数。立即停止当前正在执行的所有CNC运动包括轴移动和主轴旋转。该函数用于紧急停止或任务中断确保设备和工件的安全。停止后设备将保持当前位置等待新的指令。
@@ -72,6 +75,7 @@ linear_motion.grbl:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: wait_error的参数schema
@@ -482,6 +486,7 @@ linear_motion.toyo_xyz.sim:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: check_tf_update_actions的参数schema
@@ -507,6 +512,7 @@ linear_motion.toyo_xyz.sim:
retry: 10
speed: 1
handles: {}
placeholder_keys: {}
result: {}
schema:
description: moveit_joint_task的参数schema
@@ -552,6 +558,7 @@ linear_motion.toyo_xyz.sim:
speed: 1
target_link: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: moveit_task的参数schema
@@ -599,6 +606,7 @@ linear_motion.toyo_xyz.sim:
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: post_init的参数schema
@@ -624,6 +632,7 @@ linear_motion.toyo_xyz.sim:
parent_link: null
resource: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: resource_manager的参数schema
@@ -650,6 +659,7 @@ linear_motion.toyo_xyz.sim:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: wait_for_resource_action的参数schema
@@ -837,6 +847,7 @@ motor.iCL42:
position: null
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 步进电机执行运动函数。直接执行电机运动命令,包括位置设定、速度控制和路径规划。该函数处理底层的电机控制协议,消除警告信息,设置运动参数并启动电机运行。适用于需要直接控制电机运动的应用场景。
@@ -866,6 +877,7 @@ motor.iCL42:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: iCL42电机设备初始化函数。建立与iCL42步进电机驱动器的串口通信连接配置通信参数包括波特率、数据位、校验位等。该函数是电机使用前的必要步骤确保驱动器处于可控状态并准备接收运动指令。
@@ -889,6 +901,7 @@ motor.iCL42:
position: null
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 步进电机运动控制函数。根据指定的运动模式、目标位置和速度参数控制电机运动。支持多种运动模式和精确的位置控制,自动处理运动轨迹规划和执行。该函数提供异步执行和状态反馈,确保运动的准确性和可靠性。

View File

@@ -65,6 +65,7 @@ solid_dispenser.laiyu:
goal_default:
data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: Modbus CRC-16校验码计算函数。计算Modbus RTU通信协议所需的CRC-16校验码确保数据传输的完整性和可靠性。该函数实现标准的CRC-16算法用于构造完整的Modbus指令帧。
@@ -89,6 +90,7 @@ solid_dispenser.laiyu:
goal_default:
command: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: Modbus指令发送函数。构造完整的Modbus RTU指令帧包含CRC校验发送给分装设备并等待响应。该函数处理底层通信协议确保指令的正确传输和响应接收支持最长3分钟的响应等待时间。

View File

@@ -12,6 +12,7 @@ chiller:
register_address: null
value: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: build_modbus_frame的参数schema
@@ -46,6 +47,7 @@ chiller:
decimal_points: 1
temperature: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: convert_temperature_to_modbus_value的参数schema
@@ -73,6 +75,7 @@ chiller:
goal_default:
data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: modbus_crc的参数schema
@@ -96,6 +99,7 @@ chiller:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: stop的参数schema
@@ -188,6 +192,7 @@ heaterstirrer.dalong:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: close的参数schema
@@ -209,6 +214,7 @@ heaterstirrer.dalong:
goal_default:
speed: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_stir_speed的参数schema
@@ -234,6 +240,7 @@ heaterstirrer.dalong:
temp: null
type: warning
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_temp_inner的参数schema
@@ -580,6 +587,7 @@ tempsensor:
register_address: null
register_count: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: build_modbus_request的参数schema
@@ -613,6 +621,7 @@ tempsensor:
goal_default:
data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: calculate_crc的参数schema
@@ -637,6 +646,7 @@ tempsensor:
goal_default:
response: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: read_modbus_response的参数schema
@@ -661,6 +671,7 @@ tempsensor:
goal_default:
command: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: send_prototype_command的参数schema

View File

@@ -8,6 +8,7 @@ virtual_centrifuge:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -28,6 +29,7 @@ virtual_centrifuge:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -296,6 +298,7 @@ virtual_column:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -316,6 +319,7 @@ virtual_column:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -691,6 +695,7 @@ virtual_filter:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -711,6 +716,7 @@ virtual_filter:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -1089,6 +1095,7 @@ virtual_gas_source:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -1109,6 +1116,7 @@ virtual_gas_source:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -1129,6 +1137,7 @@ virtual_gas_source:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_closed的参数schema
@@ -1149,6 +1158,7 @@ virtual_gas_source:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_open的参数schema
@@ -1311,6 +1321,7 @@ virtual_heatchill:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -1331,6 +1342,7 @@ virtual_heatchill:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -1880,6 +1892,7 @@ virtual_multiway_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: close的参数schema
@@ -1901,6 +1914,7 @@ virtual_multiway_valve:
goal_default:
port_number: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_at_port的参数schema
@@ -1925,6 +1939,7 @@ virtual_multiway_valve:
goal_default:
position: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_at_position的参数schema
@@ -1948,6 +1963,7 @@ virtual_multiway_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_at_pump_position的参数schema
@@ -1968,6 +1984,7 @@ virtual_multiway_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -1988,6 +2005,7 @@ virtual_multiway_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: reset的参数schema
@@ -2009,6 +2027,7 @@ virtual_multiway_valve:
goal_default:
port_number: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_to_port的参数schema
@@ -2032,6 +2051,7 @@ virtual_multiway_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_to_pump_position的参数schema
@@ -2053,6 +2073,7 @@ virtual_multiway_valve:
goal_default:
port_number: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: switch_between_pump_and_port的参数schema
@@ -2300,6 +2321,7 @@ virtual_rotavap:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -2320,6 +2342,7 @@ virtual_rotavap:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -2630,6 +2653,7 @@ virtual_separator:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -2650,6 +2674,7 @@ virtual_separator:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -3517,6 +3542,7 @@ virtual_solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -3537,6 +3563,7 @@ virtual_solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -3557,6 +3584,7 @@ virtual_solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_closed的参数schema
@@ -3577,6 +3605,7 @@ virtual_solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: reset的参数schema
@@ -3597,6 +3626,7 @@ virtual_solenoid_valve:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: toggle的参数schema
@@ -4035,6 +4065,7 @@ virtual_solid_dispenser:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -4056,6 +4087,7 @@ virtual_solid_dispenser:
goal_default:
reagent_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -4079,6 +4111,7 @@ virtual_solid_dispenser:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -4100,6 +4133,7 @@ virtual_solid_dispenser:
goal_default:
mass_str: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -4124,6 +4158,7 @@ virtual_solid_dispenser:
goal_default:
mol_str: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
@@ -4206,6 +4241,7 @@ virtual_stirrer:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -4226,6 +4262,7 @@ virtual_stirrer:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -4777,6 +4814,7 @@ virtual_transfer_pump:
velocity: null
volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: aspirate的参数schema
@@ -4802,6 +4840,7 @@ virtual_transfer_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -4824,6 +4863,7 @@ virtual_transfer_pump:
velocity: null
volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: dispense的参数schema
@@ -4850,6 +4890,7 @@ virtual_transfer_pump:
goal_default:
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: empty_syringe的参数schema
@@ -4873,6 +4914,7 @@ virtual_transfer_pump:
goal_default:
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: fill_syringe的参数schema
@@ -4895,6 +4937,7 @@ virtual_transfer_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -4915,6 +4958,7 @@ virtual_transfer_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_empty的参数schema
@@ -4935,6 +4979,7 @@ virtual_transfer_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_full的参数schema
@@ -4957,6 +5002,7 @@ virtual_transfer_pump:
velocity: null
volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: pull_plunger的参数schema
@@ -4984,6 +5030,7 @@ virtual_transfer_pump:
velocity: null
volume: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: push_plunger的参数schema
@@ -5010,6 +5057,7 @@ virtual_transfer_pump:
goal_default:
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: set_max_velocity的参数schema
@@ -5033,6 +5081,7 @@ virtual_transfer_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: stop_operation的参数schema
@@ -5277,6 +5326,7 @@ virtual_vacuum_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: cleanup的参数schema
@@ -5297,6 +5347,7 @@ virtual_vacuum_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: initialize的参数schema
@@ -5317,6 +5368,7 @@ virtual_vacuum_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_closed的参数schema
@@ -5337,6 +5389,7 @@ virtual_vacuum_pump:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: is_open的参数schema

File diff suppressed because it is too large Load Diff

View File

@@ -40,6 +40,7 @@ zhida_gcms:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 安全关闭与智达 GCMS 设备的 TCP 连接,释放网络资源。
@@ -60,6 +61,7 @@ zhida_gcms:
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 与智达 GCMS 设备建立 TCP 连接,配置超时参数。
@@ -81,6 +83,7 @@ zhida_gcms:
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''

View File

@@ -0,0 +1,9 @@
from pylabrobot.resources import Resource
class ResourceSlot(Resource):
pass
class DeviceSlot(str):
pass

View File

@@ -7,11 +7,14 @@ import importlib
from pathlib import Path
from typing import Any, Dict, List, Union, Tuple
import msgcenterpy
import yaml
from unilabos_msgs.msg import Resource
from unilabos.config.config import BasicConfig
from unilabos.resources.graphio import resource_plr_to_ulab, tree_to_list
from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema, String
from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema, String, \
ros_message_to_json_schema
from unilabos.utils import logger
from unilabos.utils.decorator import singleton
from unilabos.utils.import_manager import get_enhanced_class_info, get_class
@@ -426,7 +429,10 @@ class Registry:
param_type = arg_info.get("type", "")
param_default = arg_info.get("default")
param_required = arg_info.get("required", True)
schema["properties"][param_name] = self._generate_schema_from_info(param_name, param_type, param_default)
if param_type == "unilabos.registry.placeholder_type:ResourceSlot":
schema["properties"][param_name] = ros_message_to_json_schema(Resource, param_name)
else:
schema["properties"][param_name] = self._generate_schema_from_info(param_name, param_type, param_default)
if param_required:
schema["required"].append(param_name)
@@ -536,13 +542,17 @@ class Registry:
"schema": self._generate_unilab_json_command_schema(v["args"], k),
"goal_default": {i["name"]: i["default"] for i in v["args"]},
"handles": [],
"placeholder_keys": {
i["name"]: "unilabos_resources" if i["type"] == "unilabos.registry.placeholder_type:ResourceSlot" else "unilabos_devices"
for i in v["args"]
if i.get("type", "") in ["unilabos.registry.placeholder_type:ResourceSlot", "unilabos.registry.placeholder_type:DeviceSlot"]
}
}
# 不生成已配置action的动作
for k, v in enhanced_info["action_methods"].items()
if k not in device_config["class"]["action_value_mappings"]
}
)
# 恢复原有的description信息auto开头的不修改
for action_name, description in old_descriptions.items():
if action_name in device_config["class"]["action_value_mappings"]: # 有一些会被删除

View File

@@ -1,15 +1,3 @@
BIOYOND_PolymerReactionStation_Deck:
category:
- deck
class:
module: unilabos.resources.bioyond.decks:BIOYOND_PolymerReactionStation_Deck
type: pylabrobot
description: BIOYOND PolymerReactionStation Deck
handles: []
icon: '反应站.webp'
init_param_schema: {}
registry_type: resource
version: 1.0.0
BIOYOND_PolymerPreparationStation_Deck:
category:
- deck
@@ -18,7 +6,19 @@ BIOYOND_PolymerPreparationStation_Deck:
type: pylabrobot
description: BIOYOND PolymerPreparationStation Deck
handles: []
icon: '配液站.webp'
icon: 配液站.webp
init_param_schema: {}
registry_type: resource
version: 1.0.0
BIOYOND_PolymerReactionStation_Deck:
category:
- deck
class:
module: unilabos.resources.bioyond.decks:BIOYOND_PolymerReactionStation_Deck
type: pylabrobot
description: BIOYOND PolymerReactionStation Deck
handles: []
icon: 反应站.webp
init_param_schema: {}
registry_type: resource
version: 1.0.0

View File

@@ -9,10 +9,7 @@ from unilabos_msgs.msg import Resource
from unilabos.resources.container import RegularContainer
from unilabos.ros.msgs.message_converter import convert_to_ros_msg
from unilabos.ros.nodes.resource_tracker import (
ResourceDictInstance,
ResourceTreeSet,
)
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet
from unilabos.utils.banner_print import print_status
try:
@@ -54,57 +51,24 @@ def canonicalize_nodes_data(
if child in id2idx:
nodes[id2idx[child]]["parent"] = parent
# 第三步:使用 ResourceInstanceDictFlatten 标准化每个节点
standardized_instances = []
known_nodes: Dict[str, ResourceDictInstance] = {} # {node_id: ResourceDictInstance}
uuid_to_instance: Dict[str, ResourceDictInstance] = {} # {uuid: ResourceDictInstance}
# 第三步:打印节点信息(用于调试)
for node in nodes:
try:
print_status(f"DeviceId: {node['id']}, Class: {node['class']}", "info")
# 使用标准化方法
resource_instance = ResourceDictInstance.get_resource_instance_from_dict(node)
known_nodes[node["id"]] = resource_instance
uuid_to_instance[resource_instance.res_content.uuid] = resource_instance
standardized_instances.append(resource_instance)
except Exception as e:
print_status(f"Failed to standardize node {node.get('id', 'unknown')}:\n{traceback.format_exc()}", "error")
continue
print_status(f"Failed to read node {node.get('id', 'unknown')}: {e}", "error")
# 第四步:建立 parentchildren 关系
for node in nodes:
node_id = node["id"]
if node_id not in known_nodes:
continue
# 第四步:使用 from_raw_list 创建 ResourceTreeSet自动处理标准化、parent-children关系
try:
resource_tree_set = ResourceTreeSet.from_raw_list(nodes)
except Exception as e:
print_status(f"Failed to create ResourceTreeSet:\n{traceback.format_exc()}", "error")
raise
current_instance = known_nodes[node_id]
# 优先使用 parent_uuid 进行匹配,如果不存在则使用 parent
parent_uuid = node.get("parent_uuid")
parent_id = node.get("parent")
parent_instance = None
# 优先用 parent_uuid 匹配
if parent_uuid and parent_uuid in uuid_to_instance:
parent_instance = uuid_to_instance[parent_uuid]
# 否则用 parent_id 匹配
elif parent_id and parent_id in known_nodes:
parent_instance = known_nodes[parent_id]
# 设置 parent 引用
if parent_instance:
current_instance.res_content.parent = parent_instance.res_content
# 将当前节点添加到父节点的 children 列表
parent_instance.children.append(current_instance)
# 第五步:创建 ResourceTreeSet
resource_tree_set = ResourceTreeSet.from_nested_list(standardized_instances)
return resource_tree_set
def canonicalize_links_ports(
links: List[Dict[str, Any]], resource_tree_set: ResourceTreeSet
) -> List[Dict[str, Any]]:
def canonicalize_links_ports(links: List[Dict[str, Any]], resource_tree_set: ResourceTreeSet) -> List[Dict[str, Any]]:
"""
标准化边/连接的端口信息
@@ -593,18 +557,24 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict =
plr_materials = []
for material in bioyond_materials:
className = type_mapping.get(material.get("typeName"), "RegularContainer") if type_mapping else "RegularContainer"
className = (
type_mapping.get(material.get("typeName"), "RegularContainer") if type_mapping else "RegularContainer"
)
plr_material: ResourcePLR = initialize_resource({"name": material["name"], "class": className}, resource_type=ResourcePLR)
plr_material: ResourcePLR = initialize_resource(
{"name": material["name"], "class": className}, resource_type=ResourcePLR
)
plr_material.code = material.get("code", "") and material.get("barCode", "") or ""
# 处理子物料detail
if material.get("detail") and len(material["detail"]) > 0:
child_ids = []
for detail in material["detail"]:
number = (detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y + \
(detail.get("x", 0) - 1) * plr_material.num_items_x + \
(detail.get("y", 0) - 1)
number = (
(detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y
+ (detail.get("x", 0) - 1) * plr_material.num_items_x
+ (detail.get("y", 0) - 1)
)
bottle = plr_material[number]
bottle.code = detail.get("code", "")
bottle.tracker.liquids = [
@@ -617,9 +587,11 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict =
for loc in material.get("locations", []):
if hasattr(deck, "warehouses") and loc.get("whName") in deck.warehouses:
warehouse = deck.warehouses[loc["whName"]]
idx = (loc.get("y", 0) - 1) * warehouse.num_items_x * warehouse.num_items_y + \
(loc.get("x", 0) - 1) * warehouse.num_items_x + \
(loc.get("z", 0) - 1)
idx = (
(loc.get("y", 0) - 1) * warehouse.num_items_x * warehouse.num_items_y
+ (loc.get("x", 0) - 1) * warehouse.num_items_x
+ (loc.get("z", 0) - 1)
)
if 0 <= idx < warehouse.capacity:
if warehouse[idx] is None or isinstance(warehouse[idx], ResourceHolder):
warehouse[idx] = plr_material

View File

@@ -345,111 +345,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
res.response = ""
return res
async def s2c_resource_tree(req: SerialCommand_Request, res: SerialCommand_Response):
"""
处理资源树更新请求
支持三种操作:
- add: 添加新资源到资源树
- update: 更新现有资源
- remove: 从资源树中移除资源
"""
try:
data = json.loads(req.command)
results = []
for i in data:
action = i.get("action") # remove, add, update
resources_uuid: List[str] = i.get("data") # 资源数据
self.lab_logger().info(
f"[Resource Tree Update] Processing {action} operation, "
f"resources count: {len(resources_uuid)}"
)
tree_set = None
if action in ["add", "update"]:
response: SerialCommand.Response = await self._resource_clients[
"c2s_update_resource_tree"
].call_async(
SerialCommand.Request(
command=json.dumps(
{"data": {"data": resources_uuid, "with_children": False}, "action": "get"}
)
)
) # type: ignore
raw_nodes = json.loads(response.response)
nodes = [ResourceDictInstance.get_resource_instance_from_dict(n) for n in raw_nodes]
uuids_to_nodes = {u["uuid"]: n for u, n in zip(raw_nodes, nodes)}
for u, n in zip(raw_nodes, nodes):
n.res_content.parent = uuids_to_nodes.get(u["parent_uuid"]).res_content if u["parent_uuid"] in uuids_to_nodes else None
print(n.res_content.parent)
tree_set = ResourceTreeSet.from_nested_list(nodes)
try:
if action == "add":
# 添加资源到资源跟踪器
plr_resources = tree_set.to_plr_resources()
for plr_resource, tree in zip(plr_resources, tree_set.trees):
self.resource_tracker.add_resource(plr_resource)
parent_uuid = tree.root_node.res_content.parent_uuid
if parent_uuid:
parent_resource: ResourcePLR = self.resource_tracker.uuid_to_resources.get(parent_uuid)
if parent_resource is None:
self.lab_logger().warning(f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在")
else:
try:
parent_resource.assign_child_resource(plr_resource, location=None)
except Exception as e:
self.lab_logger().warning(
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}")
func = getattr(self.driver_instance, "resource_tree_add", None)
if callable(func):
func(plr_resources)
results.append({"success": True, "action": "add"})
elif action == "update":
# 更新资源
plr_resources = tree_set.to_plr_resources()
for plr_resource, tree in zip(plr_resources, tree_set.trees):
states = plr_resource.serialize_all_state()
original_instance: ResourcePLR = self.resource_tracker.figure_resource({"uuid": tree.root_node.res_content.uuid}, try_mode=False)
original_instance.load_all_state(states)
self.lab_logger().info(f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] 及其子节点 {len(original_instance.get_all_children())}")
func = getattr(self.driver_instance, "resource_tree_update", None)
if callable(func):
func(tree_set)
results.append({"success": True, "action": "update"})
elif action == "remove":
# 移除资源
func = getattr(self.driver_instance, "resource_tree_remove", None)
if callable(func):
resources_instances: List[ResourcePLR] = [self.resource_tracker.uuid_to_resources[i] for
i in resources_uuid]
self.resource_tracker.add_resource()
[r.parent.unassign_child_resource(r) for r in resources_instances if r is not None]
func(resources_instances)
[r.parent.unassign_child_resource(r) for r in resources_instances if r is not None]
results.append({"success": True, "action": "remove"})
except Exception as e:
error_msg = f"Error processing {action} operation: {str(e)}"
self.lab_logger().error(f"[Resource Tree Update] {error_msg}")
self.lab_logger().error(traceback.format_exc())
results.append({"success": False, "action": action, "error": error_msg})
# 返回处理结果
result_json = {"results": results, "total": len(data)}
res.response = json.dumps(result_json, ensure_ascii=False)
self.lab_logger().info(f"[Resource Tree Update] Completed processing {len(data)} operations")
except json.JSONDecodeError as e:
error_msg = f"Invalid JSON format: {str(e)}"
self.lab_logger().error(f"[Resource Tree Update] {error_msg}")
res.response = json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
except Exception as e:
error_msg = f"Unexpected error: {str(e)}"
self.lab_logger().error(f"[Resource Tree Update] {error_msg}")
self.lab_logger().error(traceback.format_exc())
res.response = json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
return res
async def append_resource(req: SerialCommand_Request, res: SerialCommand_Response):
# 物料传输到对应的node节点
@@ -644,7 +539,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
"s2c_resource_tree": self.create_service(
SerialCommand,
f"/srv{self.namespace}/s2c_resource_tree",
s2c_resource_tree, # type: ignore
self.s2c_resource_tree, # type: ignore
callback_group=self.callback_group,
),
}
@@ -667,6 +562,159 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self.lab_logger().error(traceback.format_exc())
self.lab_logger().debug(f"资源更新结果: {response}")
async def s2c_resource_tree(self, req: SerialCommand_Request, res: SerialCommand_Response):
"""
处理资源树更新请求
支持三种操作:
- add: 添加新资源到资源树
- update: 更新现有资源
- remove: 从资源树中移除资源
"""
try:
data = json.loads(req.command)
results = []
for i in data:
action = i.get("action") # remove, add, update
resources_uuid: List[str] = i.get("data") # 资源数据
self.lab_logger().info(
f"[Resource Tree Update] Processing {action} operation, "
f"resources count: {len(resources_uuid)}"
)
tree_set = None
if action in ["add", "update"]:
response: SerialCommand.Response = await self._resource_clients[
"c2s_update_resource_tree"
].call_async(
SerialCommand.Request(
command=json.dumps(
{"data": {"data": resources_uuid, "with_children": False}, "action": "get"}
)
)
) # type: ignore
raw_nodes = json.loads(response.response)
tree_set = ResourceTreeSet.from_raw_list(raw_nodes)
try:
if action == "add":
# 添加资源到资源跟踪器
plr_resources = tree_set.to_plr_resources()
for plr_resource, tree in zip(plr_resources, tree_set.trees):
self.resource_tracker.add_resource(plr_resource)
parent_uuid = tree.root_node.res_content.parent_uuid
if parent_uuid:
parent_resource: ResourcePLR = self.resource_tracker.uuid_to_resources.get(parent_uuid)
if parent_resource is None:
self.lab_logger().warning(
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在")
else:
try:
parent_resource.assign_child_resource(plr_resource, location=None)
except Exception as e:
self.lab_logger().warning(
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}")
func = getattr(self.driver_instance, "resource_tree_add", None)
if callable(func):
func(plr_resources)
results.append({"success": True, "action": "add"})
elif action == "update":
# 更新资源
plr_resources = tree_set.to_plr_resources()
for plr_resource, tree in zip(plr_resources, tree_set.trees):
states = plr_resource.serialize_all_state()
original_instance: ResourcePLR = self.resource_tracker.figure_resource(
{"uuid": tree.root_node.res_content.uuid}, try_mode=False)
original_instance.load_all_state(states)
self.lab_logger().info(
f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] 及其子节点 {len(original_instance.get_all_children())}")
func = getattr(self.driver_instance, "resource_tree_update", None)
if callable(func):
func(plr_resources)
results.append({"success": True, "action": "update"})
elif action == "remove":
# 移除资源
plr_resources: List[ResourcePLR] = [self.resource_tracker.uuid_to_resources[i] for
i in resources_uuid]
func = getattr(self.driver_instance, "resource_tree_remove", None)
if callable(func):
func(plr_resources)
for plr_resource in plr_resources:
plr_resource.parent.unassign_child_resource(plr_resource)
self.resource_tracker.remove_resource(plr_resource)
results.append({"success": True, "action": "remove"})
except Exception as e:
error_msg = f"Error processing {action} operation: {str(e)}"
self.lab_logger().error(f"[Resource Tree Update] {error_msg}")
self.lab_logger().error(traceback.format_exc())
results.append({"success": False, "action": action, "error": error_msg})
# 返回处理结果
result_json = {"results": results, "total": len(data)}
res.response = json.dumps(result_json, ensure_ascii=False)
self.lab_logger().info(f"[Resource Tree Update] Completed processing {len(data)} operations")
except json.JSONDecodeError as e:
error_msg = f"Invalid JSON format: {str(e)}"
self.lab_logger().error(f"[Resource Tree Update] {error_msg}")
res.response = json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
except Exception as e:
error_msg = f"Unexpected error: {str(e)}"
self.lab_logger().error(f"[Resource Tree Update] {error_msg}")
self.lab_logger().error(traceback.format_exc())
res.response = json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
return res
async def transfer_resource_to_another(self, plr_resources: List["ResourcePLR"], target_device_id, target_resource_uuid: str):
# 准备工作
uids = []
for plr_resource in plr_resources:
uid = getattr(plr_resource, "unilabos_uuid", None)
if uid is None:
raise ValueError(f"物料{plr_resource}没有unilabos_uuid属性无法转运")
uids.append(uid)
srv_address = f"/srv{target_device_id}/s2c_resource_tree"
sclient = self.create_client(SerialCommand, srv_address)
# 等待服务可用(设置超时)
if not sclient.wait_for_service(timeout_sec=5.0):
self.lab_logger().error(f"[{self.device_id} Node-Resource] Service {srv_address} not available")
raise ValueError(f"[{self.device_id} Node-Resource] Service {srv_address} not available")
# 先从当前节点移除资源
await self.s2c_resource_tree(SerialCommand_Request(command=json.dumps([{
"action": "remove",
"data": uids # 只移除父节点
}], ensure_ascii=False)), SerialCommand_Response())
# 通知云端转运资源
tree_set = ResourceTreeSet.from_plr_resources(plr_resources)
for root_node in tree_set.root_nodes:
root_node.res_content.parent = None
root_node.res_content.parent_uuid = target_resource_uuid
r = SerialCommand.Request()
r.command = json.dumps({"data": {"data": tree_set.dump()}, "action": "update"}) # 和Update Resource一致
response: SerialCommand_Response = await self._resource_clients["c2s_update_resource_tree"].call_async(r) # type: ignore
self.lab_logger().info(f"资源云端转运到{target_device_id}结果: {response.response}")
# 创建请求
request = SerialCommand.Request()
request.command = json.dumps([{
"action": "add",
"data": tree_set.all_nodes_uuid # 只添加父节点,子节点会自动添加
}], ensure_ascii=False)
future = sclient.call_async(request)
timeout = 30.0
start_time = time.time()
while not future.done():
if time.time() - start_time > timeout:
self.lab_logger().error(f"[{self.device_id} Node-Resource] Timeout waiting for response from {target_device_id}")
return False
time.sleep(0.05)
self.lab_logger().info(f"资源本地增加到{target_device_id}结果: {response.response}")
return None
def register_device(self):
"""向注册表中注册设备信息"""
topics_info = self._property_publishers.copy()

View File

@@ -247,7 +247,6 @@ class HostNode(BaseROS2DeviceNode):
else:
resource_instance = self.resource_tracker.figure_resource({"name": node.res_content.name})
self._resource_tracker.loop_update_uuid(resource_instance, uuid_mapping)
except Exception as ex:
self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
self.lab_logger().error(traceback.format_exc())
@@ -1323,7 +1322,7 @@ class HostNode(BaseROS2DeviceNode):
if time.time() - start_time > timeout:
self.lab_logger().error(f"[Host Node-Resource] Timeout waiting for response from {device_id}")
return False
time.sleep(0.01)
time.sleep(0.05)
response = future.result()
self.lab_logger().info(

View File

@@ -454,6 +454,59 @@ class ResourceTreeSet(object):
return plr_resources
@classmethod
def from_raw_list(cls, raw_list: List[Dict[str, Any]]) -> "ResourceTreeSet":
"""
从原始字典列表创建 ResourceTreeSet自动建立 parent-children 关系
Args:
raw_list: 原始字典列表,每个字典代表一个资源节点
Returns:
ResourceTreeSet 实例
Raises:
ValueError: 当建立关系时发现不一致
"""
# 第一步:将字典列表转换为 ResourceDictInstance 列表
instances = [ResourceDictInstance.get_resource_instance_from_dict(node_dict) for node_dict in raw_list]
# 第二步:建立映射关系
uuid_to_instance: Dict[str, ResourceDictInstance] = {}
id_to_instance: Dict[str, ResourceDictInstance] = {}
for raw_node, instance in zip(raw_list, instances):
# 建立 uuid 映射
if instance.res_content.uuid:
uuid_to_instance[instance.res_content.uuid] = instance
# 建立 id 映射
if instance.res_content.id:
id_to_instance[instance.res_content.id] = instance
# 第三步:建立 parent-children 关系
for raw_node, instance in zip(raw_list, instances):
# 优先使用 parent_uuid 进行匹配,如果不存在则使用 parent (id)
parent_uuid = raw_node.get("parent_uuid")
parent_id = raw_node.get("parent")
parent_instance = None
# 优先用 parent_uuid 匹配
if parent_uuid and parent_uuid in uuid_to_instance:
parent_instance = uuid_to_instance[parent_uuid]
# 否则用 parent (id) 匹配
elif parent_id and parent_id in id_to_instance:
parent_instance = id_to_instance[parent_id]
# 设置 parent 引用并建立 children 关系
if parent_instance:
instance.res_content.parent = parent_instance.res_content
# 将当前节点添加到父节点的 children 列表(避免重复添加)
if instance not in parent_instance.children:
parent_instance.children.append(instance)
# 第四步:使用 from_nested_list 创建 ResourceTreeSet
return cls.from_nested_list(instances)
@classmethod
def from_nested_list(cls, nested_list: List[ResourceDictInstance]) -> "ResourceTreeSet":
"""
@@ -497,6 +550,16 @@ class ResourceTreeSet(object):
"""
return [node for tree in self.trees for node in tree.get_all_nodes()]
@property
def all_nodes_uuid(self) -> List[str]:
"""
获取所有树中的所有节点
Returns:
所有节点的资源实例列表
"""
return [node.res_content.uuid for tree in self.trees for node in tree.get_all_nodes()]
def find_by_uuid(self, target_uuid: str) -> Optional[ResourceDictInstance]:
"""
在所有树中通过uuid查找节点
@@ -513,6 +576,116 @@ class ResourceTreeSet(object):
return result
return None
def merge_remote_resources(self, remote_tree_set: "ResourceTreeSet") -> "ResourceTreeSet":
"""
将远端物料同步到本地物料中(以子树为单位)
同步规则:
1. 一级节点(根节点):如果不存在的物料,引入整个子树
2. 一级设备下的二级物料:如果不存在,引入整个子树
3. 二级设备下的三级物料:如果不存在,引入整个子树
如果存在则跳过并提示
Args:
remote_tree_set: 远端的资源树集合
Returns:
合并后的资源树集合self
"""
# 构建本地映射:一级 device id -> 根节点实例
local_device_map: Dict[str, ResourceDictInstance] = {}
for root_node in self.root_nodes:
if root_node.res_content.type == "device":
local_device_map[root_node.res_content.id] = root_node
# 记录需要添加的新根节点(不属于任何 device 的物料)
new_root_nodes: List[ResourceDictInstance] = []
# 遍历远端根节点
for remote_root in remote_tree_set.root_nodes:
remote_root_id = remote_root.res_content.id
remote_root_type = remote_root.res_content.type
if remote_root_type == "device":
# 情况1: 一级是 device
if remote_root_id not in local_device_map:
logger.warning(f"Device '{remote_root_id}' 在本地不存在,跳过该 device 下的物料同步")
continue
local_device = local_device_map[remote_root_id]
# 构建本地一级 device 下的子节点映射
local_children_map = {child.res_content.name: child for child in local_device.children}
# 遍历远端一级 device 的子节点
for remote_child in remote_root.children:
remote_child_name = remote_child.res_content.name
remote_child_type = remote_child.res_content.type
if remote_child_type == "device":
# 情况2: 二级是 device
if remote_child_name not in local_children_map:
logger.warning(f"Device '{remote_root_id}/{remote_child_name}' 在本地不存在,跳过")
continue
local_sub_device = local_children_map[remote_child_name]
# 构建本地二级 device 下的子节点映射
local_sub_children_map = {child.res_content.name: child for child in local_sub_device.children}
# 遍历远端二级 device 的子节点(三级物料)
added_count = 0
for remote_material in remote_child.children:
remote_material_name = remote_material.res_content.name
# 情况3: 三级物料
if remote_material_name not in local_sub_children_map:
# 引入整个子树
remote_material.res_content.parent = local_sub_device.res_content
local_sub_device.children.append(remote_material)
added_count += 1
else:
logger.info(
f"物料 '{remote_root_id}/{remote_child_name}/{remote_material_name}' "
f"已存在,跳过"
)
if added_count > 0:
logger.info(
f"Device '{remote_root_id}/{remote_child_name}': "
f"从远端同步了 {added_count} 个物料子树"
)
else:
# 情况2: 二级是物料(不是 device
if remote_child_name not in local_children_map:
# 引入整个子树
remote_child.res_content.parent = local_device.res_content
local_device.children.append(remote_child)
logger.info(f"Device '{remote_root_id}': 从远端同步物料子树 '{remote_child_name}'")
else:
logger.info(f"物料 '{remote_root_id}/{remote_child_name}' 已存在,跳过")
else:
# 情况1: 一级节点是物料(不是 device
# 检查是否已存在
existing = False
for local_root in self.root_nodes:
if local_root.res_content.name == remote_root.res_content.name:
existing = True
logger.info(f"根节点物料 '{remote_root.res_content.name}' 已存在,跳过")
break
if not existing:
# 引入整个子树
new_root_nodes.append(remote_root)
logger.info(f"添加远端独立物料根节点子树: '{remote_root_id}'")
# 将新的根节点添加到本地树集合
if new_root_nodes:
for new_root in new_root_nodes:
self.trees.append(ResourceTreeInstance(new_root))
return self
def dump(self) -> List[List[Dict[str, Any]]]:
"""
将 ResourceTreeSet 序列化为嵌套列表格式