Compare commits

..

2 Commits

Author SHA1 Message Date
Junhan Chang
662c063f50 fix pumps and liquid_handler handle 2025-08-07 20:59:57 +08:00
Xuwznln
01cbbba0b3 feat: workstation example 2025-08-07 15:26:17 +08:00
8 changed files with 821 additions and 25 deletions

View File

@@ -67,7 +67,7 @@ class PRCXI9300Deck(Deck):
class PRCXI9300Container(Plate, TipRack):
"""PRCXI 9300 的专用 Deck 类,继承自 Deck。
"""PRCXI 9300 的专用 Container 类,继承自 Plate和TipRack。
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
"""

View File

@@ -3997,7 +3997,34 @@ liquid_handler:
touch_tip: false
use_channels:
- 0
handles: []
handles:
input:
- data_key: liquid
data_source: handle
data_type: resource
handler_key: sources
label: sources
- data_key: liquid
data_source: executor
data_type: resource
handler_key: targets
label: targets
- data_key: liquid
data_source: executor
data_type: resource
handler_key: tip_rack
label: tip_rack
output:
- data_key: liquid
data_source: handle
data_type: resource
handler_key: sources_out
label: sources
- data_key: liquid
data_source: executor
data_type: resource
handler_key: targets_out
label: targets
placeholder_keys:
sources: unilabos_resources
targets: unilabos_resources
@@ -4363,21 +4390,7 @@ liquid_handler:
type: python
config_info: []
description: Liquid handler device controlled by pylabrobot
handles:
input:
- data_key: liquid
data_source: handle
data_type: resource
handler_key: liquid-input
io_type: target
label: Liquid Input
output:
- data_key: liquid
data_source: executor
data_type: resource
handler_key: liquid-output
io_type: source
label: Liquid Output
handles: []
icon: icon_yiyezhan.webp
init_param_schema:
config:

View File

@@ -366,7 +366,7 @@ solenoid_valve.mock:
- valve_position
type: object
version: 1.0.0
syringe_pump_with_valve.runze:
syringe_pump_with_valve.runze.SY03B-T08:
category:
- pump_and_valve
class:
@@ -764,8 +764,582 @@ syringe_pump_with_valve.runze:
type: python
config_info: []
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
handles: []
icon: ''
handles:
- data_key: fluid_port_1
data_source: executor
data_type: fluid
description: 八通阀门端口1
handler_key: '1'
io_type: source
label: '1'
side: NORTH
- data_key: fluid_port_2
data_source: executor
data_type: fluid
description: 八通阀门端口2
handler_key: '2'
io_type: source
label: '2'
side: EAST
- data_key: fluid_port_3
data_source: executor
data_type: fluid
description: 八通阀门端口3
handler_key: '3'
io_type: source
label: '3'
side: EAST
- data_key: fluid_port_4
data_source: executor
data_type: fluid
description: 八通阀门端口4
handler_key: '4'
io_type: source
label: '4'
side: SOUTH
- data_key: fluid_port_5
data_source: executor
data_type: fluid
description: 八通阀门端口5
handler_key: '5'
io_type: source
label: '5'
side: SOUTH
- data_key: fluid_port_6
data_source: executor
data_type: fluid
description: 八通阀门端口6
handler_key: '6'
io_type: source
label: '6'
side: WEST
- data_key: fluid_port_7
data_source: executor
data_type: fluid
description: 八通阀门端口7
handler_key: '7'
io_type: source
label: '7'
side: WEST
- data_key: fluid_port_8
data_source: executor
data_type: fluid
description: 八通阀门端口8-特殊输入
handler_key: '8'
io_type: target
label: '8'
side: WEST
- data_key: fluid_port_8
data_source: executor
data_type: fluid
description: 八通阀门端口8
handler_key: '8'
io_type: source
label: '8'
side: NORTH
init_param_schema:
config:
properties:
address:
default: '1'
type: string
max_volume:
default: 25.0
type: number
mode:
type: string
port:
type: string
required:
- port
type: object
data:
properties:
max_velocity:
type: number
mode:
type: integer
plunger_position:
type: string
position:
type: number
status:
type: string
valve_position:
type: string
velocity_end:
type: string
velocity_grade:
type: string
velocity_init:
type: string
required:
- status
- mode
- max_velocity
- velocity_grade
- velocity_init
- velocity_end
- valve_position
- position
- plunger_position
type: object
version: 1.0.0
syringe_pump_with_valve.runze.SY03B-T06:
category:
- pump_and_valve
class:
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-initialize:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: initialize的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: initialize参数
type: object
type: UniLabJsonCommand
auto-pull_plunger:
feedback: {}
goal: {}
goal_default:
volume: null
handles: []
result: {}
schema:
description: pull_plunger的参数schema
properties:
feedback: {}
goal:
properties:
volume:
type: number
required:
- volume
type: object
result: {}
required:
- goal
title: pull_plunger参数
type: object
type: UniLabJsonCommand
auto-push_plunger:
feedback: {}
goal: {}
goal_default:
volume: null
handles: []
result: {}
schema:
description: push_plunger的参数schema
properties:
feedback: {}
goal:
properties:
volume:
type: number
required:
- volume
type: object
result: {}
required:
- goal
title: push_plunger参数
type: object
type: UniLabJsonCommand
auto-query_aux_input_status_1:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_aux_input_status_1的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_aux_input_status_1参数
type: object
type: UniLabJsonCommand
auto-query_aux_input_status_2:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_aux_input_status_2的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_aux_input_status_2参数
type: object
type: UniLabJsonCommand
auto-query_backlash_position:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_backlash_position的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_backlash_position参数
type: object
type: UniLabJsonCommand
auto-query_command_buffer_status:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_command_buffer_status的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_command_buffer_status参数
type: object
type: UniLabJsonCommand
auto-query_software_version:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_software_version的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_software_version参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
full_command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
full_command:
type: string
required:
- full_command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
auto-set_baudrate:
feedback: {}
goal: {}
goal_default:
baudrate: null
handles: []
result: {}
schema:
description: set_baudrate的参数schema
properties:
feedback: {}
goal:
properties:
baudrate:
type: string
required:
- baudrate
type: object
result: {}
required:
- goal
title: set_baudrate参数
type: object
type: UniLabJsonCommand
auto-set_max_velocity:
feedback: {}
goal: {}
goal_default:
velocity: null
handles: []
result: {}
schema:
description: set_max_velocity的参数schema
properties:
feedback: {}
goal:
properties:
velocity:
type: number
required:
- velocity
type: object
result: {}
required:
- goal
title: set_max_velocity参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
max_velocity: null
position: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties:
max_velocity:
type: number
position:
type: number
required:
- position
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_valve_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_valve_position的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: string
required:
- position
type: object
result: {}
required:
- goal
title: set_valve_position参数
type: object
type: UniLabJsonCommand
auto-set_velocity_grade:
feedback: {}
goal: {}
goal_default:
velocity: null
handles: []
result: {}
schema:
description: set_velocity_grade的参数schema
properties:
feedback: {}
goal:
properties:
velocity:
type: string
required:
- velocity
type: object
result: {}
required:
- goal
title: set_velocity_grade参数
type: object
type: UniLabJsonCommand
auto-stop_operation:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: stop_operation的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop_operation参数
type: object
type: UniLabJsonCommand
auto-wait_error:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_error的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_error参数
type: object
type: UniLabJsonCommand
hardware_interface:
name: hardware_interface
read: send_command
write: send_command
module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
status_types:
max_velocity: float
mode: int
plunger_position: String
position: float
status: str
valve_position: str
velocity_end: String
velocity_grade: String
velocity_init: String
type: python
config_info: []
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
handles:
- data_key: fluid_port_1
data_source: executor
data_type: fluid
description: 八通阀门端口1
handler_key: '1'
io_type: source
label: '1'
side: NORTH
- data_key: fluid_port_2
data_source: executor
data_type: fluid
description: 八通阀门端口2
handler_key: '2'
io_type: source
label: '2'
side: EAST
- data_key: fluid_port_3
data_source: executor
data_type: fluid
description: 八通阀门端口3
handler_key: '3'
io_type: source
label: '3'
side: SOUTH
- data_key: fluid_port_4
data_source: executor
data_type: fluid
description: 八通阀门端口4
handler_key: '4'
io_type: source
label: '4'
side: SOUTH
- data_key: fluid_port_5
data_source: executor
data_type: fluid
description: 八通阀门端口5
handler_key: '5'
io_type: source
label: '5'
side: EAST
- data_key: fluid_port_6
data_source: executor
data_type: fluid
description: 八通阀门端口6
handler_key: '6'
io_type: source
label: '6'
side: NORTH
- data_key: fluid_port_6
data_source: executor
data_type: fluid
description: 六通阀门端口6-特殊输入
handler_key: '6'
io_type: target
label: '6-in'
side: WEST
init_param_schema:
config:
properties:

View File

@@ -6136,3 +6136,109 @@ workstation:
required: []
type: object
version: 1.0.0
workstation.example:
category:
- work_station
class:
action_value_mappings:
auto-append_resource:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: append_resource参数
type: object
type: UniLabJsonCommand
auto-create_resource:
feedback: {}
goal: {}
goal_default:
bind_location: null
bind_parent_id: null
liquid_input_slot: null
liquid_type: null
liquid_volume: null
resource_tracker: null
resources: null
slot_on_deck: null
handles: []
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
bind_location:
type: object
bind_parent_id:
type: string
liquid_input_slot:
type: array
liquid_type:
type: array
liquid_volume:
type: array
resource_tracker:
type: string
resources:
type: array
slot_on_deck:
type: integer
required:
- resource_tracker
- resources
- bind_parent_id
- bind_location
- liquid_input_slot
- liquid_type
- liquid_volume
- slot_on_deck
type: object
result: {}
required:
- goal
title: create_resource参数
type: object
type: UniLabJsonCommand
module: unilabos.ros.nodes.presets.workstation:WorkStationExample
status_types: {}
type: ros2
config_info: []
description: ''
handles: []
icon: ''
init_param_schema:
config:
properties:
children:
type: object
device_id:
type: string
protocol_type:
type: string
resource_tracker:
type: string
required:
- device_id
- children
- protocol_type
- resource_tracker
type: object
data:
properties: {}
required: []
type: object
version: 1.0.0

View File

@@ -0,0 +1,12 @@
get_workstation_plate_resource:
category:
- workstation
class:
module: unilabos.ros.nodes.presets.workstation:get_workstation_plate_resource
type: pylabrobot
description: workstation example resource
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0

View File

@@ -0,0 +1,9 @@
def register():
# noinspection PyUnresolvedReferences
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Deck
# noinspection PyUnresolvedReferences
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container
# noinspection PyUnresolvedReferences
from unilabos.ros.nodes.presets.workstation import WorkStationContainer

View File

@@ -31,6 +31,7 @@ from unilabos.resources.graphio import (
resource_plr_to_ulab,
tree_to_list,
)
from unilabos.resources.plr_additional_res_reg import register
from unilabos.ros.msgs.message_converter import (
convert_to_ros_msg,
convert_from_ros_msg,
@@ -941,17 +942,14 @@ class ROS2DeviceNode:
if use_pylabrobot_creator:
# 先对pylabrobot的子资源进行加载不然subclass无法认出
# 在下方对于加载Deck等Resource要手动import
# noinspection PyUnresolvedReferences
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Deck
# noinspection PyUnresolvedReferences
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container
register()
self._driver_creator = PyLabRobotCreator(
driver_class, children=children, resource_tracker=self.resource_tracker
)
else:
from unilabos.ros.nodes.presets.protocol_node import ROS2ProtocolNode
if self._driver_class is ROS2ProtocolNode:
if issubclass(self._driver_class, ROS2ProtocolNode): # 是ProtocolNode的子节点就要调用ProtocolNodeCreator
self._driver_creator = ProtocolNodeCreator(driver_class, children=children, resource_tracker=self.resource_tracker)
else:
self._driver_creator = DeviceClassCreator(driver_class, children=children, resource_tracker=self.resource_tracker)

View File

@@ -0,0 +1,84 @@
import collections
from typing import Union, Dict, Any, Optional
from unilabos_msgs.msg import Resource
from pylabrobot.resources import Resource as PLRResource, Plate, TipRack, Coordinate
from unilabos.ros.nodes.presets.protocol_node import ROS2ProtocolNode
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker
class WorkStationContainer(Plate, TipRack):
"""
WorkStation 专用 Container 类,继承自 Plate和TipRack
注意这个物料必须通过plr_additional_res_reg.py注册到edge才能正常序列化
"""
def __init__(self, name: str, size_x: float, size_y: float, size_z: float, category: str, ordering: collections.OrderedDict, model: Optional[str] = None,):
"""
这里的初始化入参要和plr的保持一致
"""
super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model)
self._unilabos_state = {} # 必须有此行,自己的类描述的是物料的
def load_state(self, state: Dict[str, Any]) -> None:
"""从给定的状态加载工作台信息。"""
super().load_state(state)
self._unilabos_state = state
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
data = super().serialize_state()
data.update(self._unilabos_state) # Container自身的信息云端物料将保存这一data本地也通过这里的data进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data
def get_workstation_plate_resource(name: str) -> PLRResource: # 要给定一个返回plr的方法
"""
用于获取一些模板,例如返回一个带有特定信息/子物料的 Plate这里需要到注册表注册例如unilabos/registry/resources/organic/workstation.yaml
可以直接运行该函数或者利用注册表补全机制,来检查是否资源出错
:param name: 资源名称
:return: Resource对象
"""
plate = WorkStationContainer(name, size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
tip_rack = WorkStationContainer("tip_rack_inside_plate", size_x=50, size_y=50, size_z=10, category="tip_rack", ordering=collections.OrderedDict())
plate.assign_child_resource(tip_rack, Coordinate.zero())
return plate
class WorkStationExample(ROS2ProtocolNode):
def __init__(self,
# 你可以在这里增加任意的参数对应启动json填写相应的参数内容
device_id: str,
children: dict,
protocol_type: Union[str, list[str]],
resource_tracker: DeviceNodeResourceTracker
):
super().__init__(device_id, children, protocol_type, resource_tracker)
def create_resource(
self,
resource_tracker: DeviceNodeResourceTracker,
resources: list[Resource],
bind_parent_id: str,
bind_location: dict[str, float],
liquid_input_slot: list[int],
liquid_type: list[str],
liquid_volume: list[int],
slot_on_deck: int,
) -> Dict[str, Any]:
return { # edge侧返回给前端的创建物料的结果。云端调用初始化瓶子等。执行该函数时物料已经上报给云端一般不需要继承使用
}
def transfer_bottle(self, tip_rack: PLRResource, base_plate: PLRResource): # 使用自定义物料的举例
"""
将tip_rack assign给base_plate两个入参都得是PLRResourceunilabos会代替当前物料操作自动刷新他们的父子关系等状态
"""
pass
def trigger_resource_update(self, from_plate: PLRResource, to_base_plate: PLRResource):
"""
有些时候物料发生了子设备的迁移一般对该设备的最大一级的物料进行操作例如要将A物料搬移到B物料上他们不共同拥有一个物料
该步骤操作结束后会主动刷新from_plate的父物料与to_base_plate的父物料如没有则刷新自身
"""
to_base_plate.assign_child_resource(from_plate, Coordinate.zero())
pass