diff --git a/unilabos/devices/liquid_handling/liquid_handler_abstract.py b/unilabos/devices/liquid_handling/liquid_handler_abstract.py index a15d91f..6997360 100644 --- a/unilabos/devices/liquid_handling/liquid_handler_abstract.py +++ b/unilabos/devices/liquid_handling/liquid_handler_abstract.py @@ -1090,7 +1090,10 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware): pass if mix_times is not None: mix_times = int(mix_times) - + + # 设置tip racks + self.set_tiprack(tip_racks) + # 识别传输模式(mix_times 为 None 也应该能正常移液,只是不做 mix) num_sources = len(sources) num_targets = len(targets) @@ -1153,9 +1156,15 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware): """一对一传输模式:N sources -> N targets""" # 验证参数长度 if len(asp_vols) != len(targets): - raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.") + if len(asp_vols) == 1: + asp_vols = [asp_vols[0]] * len(targets) + else: + raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.") if len(dis_vols) != len(targets): - raise ValueError(f"Length of `dis_vols` {len(dis_vols)} must match `targets` {len(targets)}.") + if len(dis_vols) == 1: + dis_vols = [dis_vols[0]] * len(targets) + else: + raise ValueError(f"Length of `dis_vols` {len(dis_vols)} must match `targets` {len(targets)}.") if len(sources) != len(targets): raise ValueError(f"Length of `sources` {len(sources)} must match `targets` {len(targets)}.") @@ -1495,7 +1504,10 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware): """多对一传输模式:N sources -> 1 target(汇总/混合)""" # 验证和扩展体积参数 if len(asp_vols) != len(sources): - raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `sources` {len(sources)}.") + if len(asp_vols) == 1: + asp_vols = [asp_vols[0]] * len(sources) + else: + raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `sources` {len(sources)}.") # 支持两种模式: # 1. dis_vols 为单个值:所有源汇总,使用总吸液体积或指定分液体积 diff --git a/unilabos/devices/liquid_handling/prcxi/prcxi.py b/unilabos/devices/liquid_handling/prcxi/prcxi.py index d80efdd..be31410 100644 --- a/unilabos/devices/liquid_handling/prcxi/prcxi.py +++ b/unilabos/devices/liquid_handling/prcxi/prcxi.py @@ -1011,6 +1011,11 @@ class PRCXI9300Handler(LiquidHandlerAbstract): async def shaker_action(self, time: int, module_no: int, amplitude: int, is_wait: bool): return await self._unilabos_backend.shaker_action(time, module_no, amplitude, is_wait) + async def magnetic_action(self, time: int, module_no: int, height: int, is_wait: bool): + return await self._unilabos_backend.magnetic_action(time, module_no, height, is_wait) + + async def shaking_incubation_action(self, time: int, module_no: int, amplitude: int, is_wait: bool, temperature: int): + return await self._unilabos_backend.shaking_incubation_action(time, module_no, amplitude, is_wait, temperature) async def heater_action(self, temperature: float, time: int): return await self._unilabos_backend.heater_action(temperature, time) async def move_plate( @@ -1116,6 +1121,26 @@ class PRCXI9300Backend(LiquidHandlerBackend): self.steps_todo_list.append(step) return step + async def shaking_incubation_action(self, time: int, module_no: int, amplitude: int, is_wait: bool, temperature: int): + step = self.api_client.shaking_incubation_action( + time=time, + module_no=module_no, + amplitude=amplitude, + is_wait=is_wait, + temperature=temperature, + ) + self.steps_todo_list.append(step) + return step + + async def magnetic_action(self, time: int, module_no: int, height: int, is_wait: bool): + step = self.api_client.magnetic_action( + time=time, + module_no=module_no, + height=height, + is_wait=is_wait, + ) + self.steps_todo_list.append(step) + return step async def pick_up_resource(self, pickup: ResourcePickup, **backend_kwargs): @@ -1985,6 +2010,27 @@ class PRCXI9300Api: "AssistFun4": is_wait, } + def shaking_incubation_action(self, time: int, module_no: int, amplitude: int, is_wait: bool, temperature: int): + return { + "StepAxis": "Left", + "Function": "Shaking_Incubation", + "AssistFun1": time, + "AssistFun2": module_no, + "AssistFun3": amplitude, + "AssistFun4": is_wait, + "AssistFun5": temperature, + } + + def magnetic_action(self, time: int, module_no: int, height: int, is_wait: bool): + return { + "StepAxis": "Left", + "Function": "Magnetic", + "AssistFun1": time, + "AssistFun2": module_no, + "AssistFun3": height, + "AssistFun4": is_wait, + } + class DefaultLayout: def __init__(self, product_name: str = "PRCXI9300"): diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index 1f8733d..d6da2d3 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -4019,8 +4019,7 @@ liquid_handler: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -4094,29 +4093,29 @@ liquid_handler: - 0 handles: input: - - data_key: liquid + - data_key: sources data_source: handle data_type: resource handler_key: sources label: sources - - data_key: liquid - data_source: executor + - data_key: targets + data_source: handle data_type: resource handler_key: targets label: targets - - data_key: liquid - data_source: executor + - data_key: tip_racks + data_source: handle data_type: resource - handler_key: tip_rack - label: tip_rack + handler_key: tip_racks + label: tip_racks output: - - data_key: liquid + - data_key: sources data_source: handle data_type: resource handler_key: sources_out label: sources - - data_key: liquid - data_source: executor + - data_key: targets + data_source: handle data_type: resource handler_key: targets_out label: targets @@ -4176,11 +4175,9 @@ liquid_handler: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -4767,13 +4764,13 @@ liquid_handler.biomek: targets: '' handles: input: - - data_key: liquid + - data_key: sources data_source: handle data_type: resource handler_key: sources label: sources output: - - data_key: liquid + - data_key: targets data_source: handle data_type: resource handler_key: targets @@ -4926,29 +4923,29 @@ liquid_handler.biomek: volume: 0.0 handles: input: - - data_key: liquid + - data_key: sources data_source: handle data_type: resource handler_key: sources label: sources - - data_key: liquid - data_source: executor + - data_key: targets + data_source: handle data_type: resource handler_key: targets label: targets - - data_key: liquid - data_source: executor + - data_key: tip_racks + data_source: handle data_type: resource - handler_key: tip_rack - label: tip_rack + handler_key: tip_racks + label: tip_racks output: - - data_key: liquid + - data_key: sources data_source: handle data_type: resource handler_key: sources_out label: sources - - data_key: liquid - data_source: executor + - data_key: targets + data_source: handle data_type: resource handler_key: targets_out label: targets @@ -5043,8 +5040,7 @@ liquid_handler.biomek: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -5118,19 +5114,32 @@ liquid_handler.biomek: - 0 handles: input: - - data_key: liquid + - data_key: sources data_source: handle data_type: resource - handler_key: liquid-input - io_type: target - label: Liquid Input - output: - - data_key: liquid - data_source: executor + handler_key: sources + label: sources + - data_key: targets + data_source: handle data_type: resource - handler_key: liquid-output - io_type: source - label: Liquid Output + handler_key: targets + label: targets + - data_key: tip_racks + data_source: handle + data_type: resource + handler_key: tip_racks + label: tip_racks + output: + - data_key: sources + data_source: handle + data_type: resource + handler_key: sources_out + label: sources + - data_key: targets + data_source: handle + data_type: resource + handler_key: targets_out + label: targets placeholder_keys: sources: unilabos_resources targets: unilabos_resources @@ -5187,11 +5196,9 @@ liquid_handler.biomek: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -7610,6 +7617,43 @@ liquid_handler.prcxi: title: iter_tips参数 type: object type: UniLabJsonCommand + auto-magnetic_action: + feedback: {} + goal: {} + goal_default: + height: null + is_wait: null + module_no: null + time: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + height: + type: integer + is_wait: + type: boolean + module_no: + type: integer + time: + type: integer + required: + - time + - module_no + - height + - is_wait + type: object + result: {} + required: + - goal + title: magnetic_action参数 + type: object + type: UniLabJsonCommandAsync auto-move_to: feedback: {} goal: {} @@ -7643,6 +7687,31 @@ liquid_handler.prcxi: title: move_to参数 type: object type: UniLabJsonCommandAsync + auto-plr_pos_to_prcxi: + feedback: {} + goal: {} + goal_default: + resource: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + resource: + type: object + required: + - resource + type: object + result: {} + required: + - goal + title: plr_pos_to_prcxi参数 + type: object + type: UniLabJsonCommand auto-post_init: feedback: {} goal: {} @@ -7763,6 +7832,47 @@ liquid_handler.prcxi: title: shaker_action参数 type: object type: UniLabJsonCommandAsync + auto-shaking_incubation_action: + feedback: {} + goal: {} + goal_default: + amplitude: null + is_wait: null + module_no: null + temperature: null + time: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + amplitude: + type: integer + is_wait: + type: boolean + module_no: + type: integer + temperature: + type: integer + time: + type: integer + required: + - time + - module_no + - amplitude + - is_wait + - temperature + type: object + result: {} + required: + - goal + title: shaking_incubation_action参数 + type: object + type: UniLabJsonCommandAsync auto-touch_tip: feedback: {} goal: {} @@ -8497,7 +8607,19 @@ liquid_handler.prcxi: z: 0.0 sample_id: '' type: '' - handles: {} + handles: + input: + - data_key: plate + data_source: handle + data_type: resource + handler_key: plate + label: plate + output: + - data_key: plate + data_source: handle + data_type: resource + handler_key: plate + label: plate placeholder_keys: plate: unilabos_resources to: unilabos_resources @@ -9677,8 +9799,7 @@ liquid_handler.prcxi: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -9755,26 +9876,26 @@ liquid_handler.prcxi: - data_key: sources data_source: handle data_type: resource - handler_key: sources_identifier - label: 待移动液体 + handler_key: sources + label: sources - data_key: targets data_source: handle data_type: resource - handler_key: targets_identifier - label: 转移目标 - - data_key: tip_rack + handler_key: targets + label: targets + - data_key: tip_racks data_source: handle data_type: resource - handler_key: tip_rack_identifier - label: 墙头盒 + handler_key: tip_racks + label: tip_racks output: - - data_key: liquid + - data_key: sources data_source: handle data_type: resource handler_key: sources_out label: sources - - data_key: liquid - data_source: executor + - data_key: targets + data_source: handle data_type: resource handler_key: targets_out label: targets @@ -9834,11 +9955,9 @@ liquid_handler.prcxi: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -10160,6 +10279,12 @@ liquid_handler.prcxi: type: string deck: type: object + deck_y: + default: 400 + type: string + deck_z: + default: 300 + type: string host: type: string is_9320: @@ -10170,17 +10295,44 @@ liquid_handler.prcxi: type: string port: type: integer + rail_interval: + default: 0 + type: string + rail_nums: + default: 4 + type: string + rail_width: + default: 27.5 + type: string setup: default: true type: string simulator: default: false type: string + start_rail: + default: 2 + type: string step_mode: default: false type: string timeout: type: number + x_increase: + default: -0.003636 + type: string + x_offset: + default: -0.8 + type: string + xy_coupling: + default: -0.0045 + type: string + y_increase: + default: -0.003636 + type: string + y_offset: + default: -37.98 + type: string required: - deck - host diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index 4550407..0780a1c 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -1,3 +1,4 @@ +from ast import Try import inspect import io import json @@ -1341,12 +1342,17 @@ class BaseROS2DeviceNode(Node, Generic[T]): uuids = [item[1] for item in uuid_indices] resource_tree = await self.get_resource(uuids) plr_resources = resource_tree.to_plr_resources() - for i, (idx, _, resource_data) in enumerate(uuid_indices): - plr_resource = plr_resources[i] - if "sample_id" in resource_data: - plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"] - queried_resources[idx] = plr_resource - + # 通过uuid查找对应的plr_resource + tracker = self.resource_tracker + for idx, uuid, resource_data in uuid_indices: + try: + plr_resource = tracker.loop_find_with_uuid(plr_resources, uuid) + if "sample_id" in resource_data: + plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"] + queried_resources[idx] = plr_resource + except Exception as e: + self.lab_logger().error(f"资源查询失败: {e}\n{traceback.format_exc()}") + continue self.lab_logger().debug(f"资源查询结果: 共 {len(queried_resources)} 个资源") # 通过资源跟踪器获取本地实例