import asyncio
import collections
import contextlib
import json
import socket
import time
from typing import Any, List, Dict, Optional, TypedDict, Union, Sequence, Iterator, Literal

from pylabrobot.liquid_handling import (
    LiquidHandlerBackend,
    Pickup,
    SingleChannelAspiration,
    Drop,
    SingleChannelDispense,
    PickupTipRack,
    DropTipRack,
    MultiHeadAspirationPlate, ChatterBoxBackend, LiquidHandlerChatterboxBackend,
)
from pylabrobot.liquid_handling.standard import (
    MultiHeadAspirationContainer,
    MultiHeadDispenseContainer,
    MultiHeadDispensePlate,
    ResourcePickup,
    ResourceMove,
    ResourceDrop,
)
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash

from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract


class PRCXIError(RuntimeError):
    """Lilith 返回 Success=false 时抛出的业务异常"""


class Material(TypedDict):  # 和Plate同关系
    uuid: str
    Code: Optional[str]
    Name: Optional[str]
    SummaryName: Optional[str]
    PipetteHeight: Optional[int]
    materialEnum: Optional[int]


class WorkTablets(TypedDict):
    Number: int
    Code: str
    Material: Dict[str, Any]


class MatrixInfo(TypedDict):
    MatrixId: str
    MatrixName: str
    MatrixCount: int
    WorkTablets: list[WorkTablets]


class PRCXI9300Deck(Deck):
    """PRCXI 9300 的专用 Deck 类，继承自 Deck。

    该类定义了 PRCXI 9300 的工作台布局和槽位信息。
    """

    def __init__(self, name: str, size_x: float, size_y: float, size_z: float):
        super().__init__(name, size_x, size_y, size_z)
        self.slots = [None] * 6  # PRCXI 9300 有 6 个槽位


class PRCXI9300Container(Plate, TipRack):
    """PRCXI 9300 的专用 Deck 类，继承自 Deck。

    该类定义了 PRCXI 9300 的工作台布局和槽位信息。
    """

    def __init__(self, name: str, size_x: float, size_y: float, size_z: float, category: str, ordering: collections.OrderedDict, model: Optional[str] = None,):
        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)
        return data


class PRCXI9300Trash(Trash):
    """PRCXI 9300 的专用 Trash 类，继承自 Trash。

    该类定义了 PRCXI 9300 的工作台布局和槽位信息。
    """

    def __init__(self, name: str, size_x: float, size_y: float, size_z: float, category: str, **kwargs):
        if name != "trash":
            name = "trash"
            print("PRCXI9300Trash name must be 'trash', using 'trash' instead.")
        super().__init__(name, size_x, size_y, size_z, category=category, **kwargs)
        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)
        return data
    
class PRCXI9300Handler(LiquidHandlerAbstract):
    support_touch_tip = False

    @property
    def reset_ok(self) -> bool:
        """检查设备是否已重置成功。"""
        if self._unilabos_backend.debug:
            return True
        return self._unilabos_backend.is_reset_ok

    def __init__(self, deck: Deck, host: str, port: int, timeout: float, channel_num=8, axis="Left", setup=True, debug=False, simulator=False, matrix_id=""):
        tablets_info = []
        count = 0
        for child in deck.children:
            if "Material" in child._unilabos_state:
                count += 1
                tablets_info.append(
                    WorkTablets(Number=count, Code=f"T{count}", Material=child._unilabos_state["Material"])
                )
        self._unilabos_backend = PRCXI9300Backend(tablets_info, host, port, timeout, channel_num, axis, setup, debug, matrix_id)
        super().__init__(backend=self._unilabos_backend, deck=deck, simulator=simulator, channel_num=channel_num)

    def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]):
        return super().set_liquid(wells, liquid_names, volumes)

    async def create_protocol(
        self,
        protocol_name: str = "",
        protocol_description: str = "",
        protocol_version: str = "",
        protocol_author: str = "",
        protocol_date: str = "",
        protocol_type: str = "",
        none_keys: List[str] = [],
    ):
        self._unilabos_backend.create_protocol(protocol_name)

    async def run_protocol(self):
        return self._unilabos_backend.run_protocol()

    async def remove_liquid(
        self,
        vols: List[float],
        sources: Sequence[Container],
        waste_liquid: Optional[Container] = None,
        *,
        use_channels: Optional[List[int]] = None,
        flow_rates: Optional[List[Optional[float]]] = None,
        offsets: Optional[List[Coordinate]] = None,
        liquid_height: Optional[List[Optional[float]]] = None,
        blow_out_air_volume: Optional[List[Optional[float]]] = None,
        spread: Optional[Literal["wide", "tight", "custom"]] = "wide",
        delays: Optional[List[int]] = None,
        is_96_well: Optional[bool] = False,
        top: Optional[List[float]] = None,
        none_keys: List[str] = [],
    ):
        return await super().remove_liquid(
            vols,
            sources,
            waste_liquid,
            use_channels=use_channels,
            flow_rates=flow_rates,
            offsets=offsets,
            liquid_height=liquid_height,
            blow_out_air_volume=blow_out_air_volume,
            spread=spread,
            delays=delays,
            is_96_well=is_96_well,
            top=top,
            none_keys=none_keys,
        )

    async def add_liquid(
        self,
        asp_vols: Union[List[float], float],
        dis_vols: Union[List[float], float],
        reagent_sources: Sequence[Container],
        targets: Sequence[Container],
        *,
        use_channels: Optional[List[int]] = None,
        flow_rates: Optional[List[Optional[float]]] = None,
        offsets: Optional[List[Coordinate]] = None,
        liquid_height: Optional[List[Optional[float]]] = None,
        blow_out_air_volume: Optional[List[Optional[float]]] = None,
        spread: Optional[Literal["wide", "tight", "custom"]] = "wide",
        is_96_well: bool = False,
        delays: Optional[List[int]] = None,
        mix_time: Optional[int] = None,
        mix_vol: Optional[int] = None,
        mix_rate: Optional[int] = None,
        mix_liquid_height: Optional[float] = None,
        none_keys: List[str] = [],
    ):
        return await super().add_liquid(
            asp_vols,
            dis_vols,
            reagent_sources,
            targets,
            use_channels=use_channels,
            flow_rates=flow_rates,
            offsets=offsets,
            liquid_height=liquid_height,
            blow_out_air_volume=blow_out_air_volume,
            spread=spread,
            is_96_well=is_96_well,
            delays=delays,
            mix_time=mix_time,
            mix_vol=mix_vol,
            mix_rate=mix_rate,
            mix_liquid_height=mix_liquid_height,
            none_keys=none_keys,
        )

    async def transfer_liquid(
        self,
        sources: Sequence[Container],
        targets: Sequence[Container],
        tip_racks: Sequence[TipRack],
        *,
        use_channels: Optional[List[int]] = None,
        asp_vols: Union[List[float], float],
        dis_vols: Union[List[float], float],
        asp_flow_rates: Optional[List[Optional[float]]] = None,
        dis_flow_rates: Optional[List[Optional[float]]] = None,
        offsets: Optional[List[Coordinate]] = None,
        touch_tip: bool = False,
        liquid_height: Optional[List[Optional[float]]] = None,
        blow_out_air_volume: Optional[List[Optional[float]]] = None,
        spread: Literal["wide", "tight", "custom"] = "wide",
        is_96_well: bool = False,
        mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
        mix_times: Optional[List[int]] = None,
        mix_vol: Optional[int] = None,
        mix_rate: Optional[int] = None,
        mix_liquid_height: Optional[float] = None,
        delays: Optional[List[int]] = None,
        none_keys: List[str] = [],
    ):
        return await super().transfer_liquid(
            sources,
            targets,
            tip_racks,
            use_channels=use_channels,
            asp_vols=asp_vols,
            dis_vols=dis_vols,
            asp_flow_rates=asp_flow_rates,
            dis_flow_rates=dis_flow_rates,
            offsets=offsets,
            touch_tip=touch_tip,
            liquid_height=liquid_height,
            blow_out_air_volume=blow_out_air_volume,
            spread=spread,
            is_96_well=is_96_well,
            mix_stage=mix_stage,
            mix_times=mix_times,
            mix_vol=mix_vol,
            mix_rate=mix_rate,
            mix_liquid_height=mix_liquid_height,
            delays=delays,
            none_keys=none_keys,
        )

    async def custom_delay(self, seconds=0, msg=None):
        return await super().custom_delay(seconds, msg)

    async def touch_tip(self, targets: Sequence[Container]):
        return await super().touch_tip(targets)

    async def mix(
        self,
        targets: Sequence[Container],
        mix_time: int = None,
        mix_vol: Optional[int] = None,
        height_to_bottom: Optional[float] = None,
        offsets: Optional[Coordinate] = None,
        mix_rate: Optional[float] = None,
        none_keys: List[str] = [],
    ):
        return await self._unilabos_backend.mix(targets, mix_time, mix_vol, height_to_bottom, offsets, mix_rate, none_keys)

    def iter_tips(self, tip_racks: Sequence[TipRack]) -> Iterator[Resource]:
        return super().iter_tips(tip_racks)

    async def pick_up_tips(self, tip_spots: List[TipSpot], use_channels: Optional[List[int]] = None,
                           offsets: Optional[List[Coordinate]] = None, **backend_kwargs):
        return await super().pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs)

    async def aspirate(self, resources: Sequence[Container], vols: List[float],
                       use_channels: Optional[List[int]] = None, flow_rates: Optional[List[Optional[float]]] = None,
                       offsets: Optional[List[Coordinate]] = None,
                       liquid_height: Optional[List[Optional[float]]] = None,
                       blow_out_air_volume: Optional[List[Optional[float]]] = None,
                       spread: Literal["wide", "tight", "custom"] = "wide", **backend_kwargs):

        return await super().aspirate(resources, vols, use_channels, flow_rates, offsets, liquid_height,
                                      blow_out_air_volume, spread, **backend_kwargs)

    async def drop_tips(self, tip_spots: Sequence[Union[TipSpot, Trash]], use_channels: Optional[List[int]] = None,
                        offsets: Optional[List[Coordinate]] = None, allow_nonzero_volume: bool = False,
                        **backend_kwargs):
        return await super().drop_tips(tip_spots, use_channels, offsets, allow_nonzero_volume, **backend_kwargs)

    async def dispense(self, resources: Sequence[Container], vols: List[float],
                       use_channels: Optional[List[int]] = None, flow_rates: Optional[List[Optional[float]]] = None,
                       offsets: Optional[List[Coordinate]] = None,
                       liquid_height: Optional[List[Optional[float]]] = None,
                       blow_out_air_volume: Optional[List[Optional[float]]] = None,
                       spread: Literal["wide", "tight", "custom"] = "wide", **backend_kwargs):
        return await super().dispense(resources, vols, use_channels, flow_rates, offsets, liquid_height,
                                      blow_out_air_volume, spread, **backend_kwargs)

    async def discard_tips(self, use_channels: Optional[List[int]] = None, allow_nonzero_volume: bool = True,
                           offsets: Optional[List[Coordinate]] = None, **backend_kwargs):
        return await super().discard_tips(use_channels, allow_nonzero_volume, offsets, **backend_kwargs)

    def set_tiprack(self, tip_racks: Sequence[TipRack]):
        super().set_tiprack(tip_racks)

    async def move_to(self, well: Well, dis_to_top: float = 0, channel: int = 0):
        return await super().move_to(well, dis_to_top, channel)


class PRCXI9300Backend(LiquidHandlerBackend):
    """PRCXI 9300 的后端实现，继承自 LiquidHandlerBackend。

    该类提供了与 PRCXI 9300 设备进行通信的基本方法，包括方案管理、自动化控制、运行状态查询等。
    """

    _num_channels = 8  # 默认通道数为 8
    _is_reset_ok = False

    @property
    def is_reset_ok(self) -> bool:
        self._is_reset_ok = self.api_client.get_reset_status()
        return self._is_reset_ok
    
    matrix_info: MatrixInfo
    protocol_name: str
    steps_todo_list = []

    def __init__(
        self,
        tablets_info: list[WorkTablets],
        host: str = "127.0.0.1",
        port: int = 9999,
        timeout: float = 10.0,
        channel_num: int=8,
        axis: str="Left",
        setup=True,
        debug=False,
        matrix_id="",
    ) -> None:
        super().__init__()
        self.tablets_info = tablets_info
        self.matrix_id = matrix_id
        self.api_client = PRCXI9300Api(host, port, timeout, axis, debug)
        self.host, self.port, self.timeout = host, port, timeout
        self._num_channels = channel_num
        self._execute_setup = setup
        self.debug = debug

    def create_protocol(self, protocol_name):
        self.protocol_name = protocol_name
        self.steps_todo_list = []

    def run_protocol(self):
        assert self.is_reset_ok, "PRCXI9300Backend is not reset successfully. Please call setup() first."
        run_time = time.time()
        self.matrix_info = MatrixInfo(
            MatrixId=f"{int(run_time)}",
            MatrixName=f"protocol_{run_time}",
            MatrixCount=len(self.tablets_info),
            WorkTablets=self.tablets_info,
        )
        #print(json.dumps(self.matrix_info, indent=2))
        if not len(self.matrix_id):
            res = self.api_client.add_WorkTablet_Matrix(self.matrix_info)
            assert res["Success"], f"Failed to create matrix: {res.get('Message', 'Unknown error')}"
            print(f"PRCXI9300Backend created matrix with ID: {self.matrix_info['MatrixId']}, result: {res}")
            solution_id = self.api_client.add_solution(
                f"protocol_{run_time}", self.matrix_info["MatrixId"], self.steps_todo_list
            )
        else:
            print(f"PRCXI9300Backend using predefined worktable {self.matrix_id}, skipping matrix creation.")
            solution_id = self.api_client.add_solution(
                f"protocol_{run_time}", self.matrix_id, self.steps_todo_list
            )
        print(f"PRCXI9300Backend created solution with ID: {solution_id}")
        self.api_client.load_solution(solution_id)
        print(json.dumps(self.steps_todo_list, indent=2))
        return self.api_client.start()

    @classmethod
    def check_channels(cls, use_channels: List[int]) -> List[int]:
        """检查通道是否符合要求，PRCXI9300Backend 只支持所有 8 个通道。"""
        if use_channels != [0, 1, 2, 3, 4, 5, 6, 7]:
            print("PRCXI9300Backend only supports all 8 channels, using default [0, 1, 2, 3, 4, 5, 6, 7].")
            return [0, 1, 2, 3, 4, 5, 6, 7]
        return use_channels

    async def setup(self):
        await super().setup()
        try:
            if self._execute_setup:
                self.api_client.call("IAutomation", "Reset")
                while not self.is_reset_ok:
                    print("Waiting for PRCXI9300 to reset...")
                    await asyncio.sleep(1)
                print("PRCXI9300 reset successfully.")
        except ConnectionRefusedError as e:
            raise RuntimeError(
                f"Failed to connect to PRCXI9300 API at {self.host}:{self.port}. "
                "Please ensure the PRCXI9300 service is running."
            ) from e

    async def stop(self):
        self.api_client.call("IAutomation", "Stop")

    async def pick_up_tips(self, ops: List[Pickup], use_channels: List[int] = None):
        """Pick up tips from the specified resource."""
        print('where?'*200)
        plate_indexes = []
        for op in ops:
            plate = op.resource.parent
            deck = plate.parent
            plate_index = deck.children.index(plate)
            print(f"Plate index: {plate_index}, Plate name: {plate.name}")
            print(f"Number of children in deck: {len(deck.children)}")

            plate_indexes.append(plate_index)

        if len(set(plate_indexes)) != 1:
            raise ValueError("All pickups must be from the same plate. Found different plates: " + str(plate_indexes)) 

        tip_columns = []
        for op in ops:
            tipspot = op.resource
            tipspot_index = tipspot.parent.children.index(tipspot)
            tip_columns.append(tipspot_index // 8)
        if len(set(tip_columns)) != 1:
            raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns))
        # print('111'*99)
        # print(plate_indexes[0])
        PlateNo = plate_indexes[0] + 1
        hole_col = tip_columns[0] + 1
        hole_row = 1
        if self._num_channels == 1:
            hole_row = tipspot_index % 8 + 1 

        step = self.api_client.Load(dosage=0, plate_no=PlateNo, is_whole_plate=False, hole_row=hole_row, hole_col=hole_col,
                                    blending_times=0, balance_height=0, plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
                                    hole_numbers="1,2,3,4,5,6,7,8")
        self.steps_todo_list.append(step)

    async def drop_tips(self, ops: List[Drop], use_channels: List[int] = None):
        """Pick up tips from the specified resource."""

        # 检查trash # 
        if ops[0].resource.name == "trash":

            PlateNo = ops[0].resource.parent.children.index(ops[0].resource) + 1

            step = self.api_client.UnLoad(
                dosage=0,
                plate_no=PlateNo,
                is_whole_plate=False,
                hole_row=1,
                hole_col=3,
                blending_times=0,
                balance_height=0,
                plate_or_hole=f"H{1}-8,T{PlateNo}",
                hole_numbers="1,2,3,4,5,6,7,8",
        )
            self.steps_todo_list.append(step)
            return
        #print(ops[0].resource.parent.children.index(ops[0].resource))

        
        plate_indexes = []
        for op in ops:
            plate = op.resource.parent
            deck = plate.parent
            plate_index = deck.children.index(plate)
            plate_indexes.append(plate_index)
        if len(set(plate_indexes)) != 1:
            raise ValueError("All drop_tips must be from the same plate. Found different plates: " + str(plate_indexes)) 

        tip_columns = []
        for op in ops:
            tipspot = op.resource
            tipspot_index = tipspot.parent.children.index(tipspot)
            tip_columns.append(tipspot_index // 8)
        if len(set(tip_columns)) != 1:
            raise ValueError("All drop_tips must be from the same tip column. Found different columns: " + str(tip_columns))
        
        PlateNo = plate_indexes[0] + 1
        hole_col = tip_columns[0] + 1

        if self.channel_num == 1:
            hole_row = tipspot_index % 8 + 1

        step = self.api_client.UnLoad(
            dosage=0,
            plate_no=PlateNo,
            is_whole_plate=False,
            hole_row=hole_row,
            hole_col=hole_col,
            blending_times=0,
            balance_height=0,
            plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
            hole_numbers="1,2,3,4,5,6,7,8",
        )
        self.steps_todo_list.append(step)

    async def mix(
            self,
            targets: Sequence[Container],
            mix_time: int = None,
            mix_vol: Optional[int] = None,
            height_to_bottom: Optional[float] = None,
            offsets: Optional[Coordinate] = None,
            mix_rate: Optional[float] = None,
            none_keys: List[str] = [],
    ):
        
        """Mix liquid in the specified resources."""
   
        plate_indexes = []
        for op in targets:
            deck = op.parent.parent
            plate = op.parent
            plate_index = deck.children.index(plate)
            plate_indexes.append(plate_index)

        if len(set(plate_indexes)) != 1:
            raise ValueError("All pickups must be from the same plate. Found different plates: " + str(plate_indexes)) 

        tip_columns = []
        for op in targets:
            tipspot_index = op.parent.children.index(op)
            tip_columns.append(tipspot_index // 8)

        if len(set(tip_columns)) != 1:
            raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns))
        
        PlateNo = plate_indexes[0] + 1
        hole_col = tip_columns[0] + 1
        hole_row = 1
        if self.num_channels == 1:
            hole_row = tipspot_index % 8 + 1 

        assert mix_time > 0
        step = self.api_client.Blending(
            dosage=mix_vol,
            plate_no=PlateNo,
            is_whole_plate=False,
            hole_row=hole_row,
            hole_col=hole_col,
            blending_times=mix_time,
            balance_height=0,
            plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
            hole_numbers="1,2,3,4,5,6,7,8",
        )
        self.steps_todo_list.append(step)

    async def aspirate(self, ops: List[SingleChannelAspiration], use_channels: List[int] = None):

        """Aspirate liquid from the specified resources."""

        plate_indexes = []
        for op in ops:
            plate = op.resource.parent
            deck = plate.parent
            plate_index = deck.children.index(plate)
            plate_indexes.append(plate_index)

        if len(set(plate_indexes)) != 1:
            raise ValueError("All pickups must be from the same plate. Found different plates: " + str(plate_indexes)) 

        tip_columns = []
        for op in ops:
            tipspot = op.resource
            tipspot_index = tipspot.parent.children.index(tipspot)
            tip_columns.append(tipspot_index // 8)

        if len(set(tip_columns)) != 1:
            raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns))
        
        volumes = [op.volume for op in ops]
        if len(set(volumes)) != 1:
            raise ValueError("All aspirate volumes must be the same. Found different volumes: " + str(volumes))
        
        PlateNo = plate_indexes[0] + 1
        hole_col = tip_columns[0] + 1
        hole_row = 1
        if self.num_channels == 1:
            hole_row = tipspot_index % 8 + 1 

        step = self.api_client.Imbibing(dosage=int(volumes[0]), plate_no=PlateNo, is_whole_plate=False, hole_row=hole_row,
                                        hole_col=hole_col, blending_times=0, balance_height=0,
                                        plate_or_hole=f"H{hole_col}-8,T{PlateNo}", hole_numbers="1,2,3,4,5,6,7,8")
        self.steps_todo_list.append(step)
        


    async def dispense(self, ops: List[SingleChannelDispense], use_channels: List[int] = None):
        
        """Dispense liquid into the specified resources."""

        plate_indexes = []
        for op in ops:
            plate = op.resource.parent
            deck = plate.parent
            plate_index = deck.children.index(plate)
            plate_indexes.append(plate_index)

        if len(set(plate_indexes)) != 1:
            raise ValueError("All dispense must be from the same plate. Found different plates: " + str(plate_indexes)) 

        tip_columns = []
        for op in ops:
            tipspot = op.resource
            tipspot_index = tipspot.parent.children.index(tipspot)
            tip_columns.append(tipspot_index // 8)

        if len(set(tip_columns)) != 1:
            raise ValueError("All dispense must be from the same tip column. Found different columns: " + str(tip_columns))
        
        volumes = [op.volume for op in ops]
        if len(set(volumes)) != 1:
            raise ValueError("All dispense volumes must be the same. Found different volumes: " + str(volumes))
        
        PlateNo = plate_indexes[0] + 1
        hole_col = tip_columns[0] + 1

        hole_row = 1
        if self.num_channels == 1:
            hole_row = tipspot_index % 8 + 1

        step = self.api_client.Tapping(
            dosage=int(volumes[0]),
            plate_no=PlateNo,
            is_whole_plate=False,
            hole_row=hole_row,
            hole_col=hole_col,
            blending_times=0,
            balance_height=0,
            plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
            hole_numbers="1,2,3,4,5,6,7,8",
        )
        self.steps_todo_list.append(step)

    async def pick_up_tips96(self, pickup: PickupTipRack):
        raise NotImplementedError("The PRCXI backend does not support the 96 head.")

    async def drop_tips96(self, drop: DropTipRack):
        raise NotImplementedError("The PRCXI backend does not support the 96 head.")

    async def aspirate96(self, aspiration: Union[MultiHeadAspirationPlate, MultiHeadAspirationContainer]):
        raise NotImplementedError("The Opentrons backend does not support the 96 head.")

    async def dispense96(self, dispense: Union[MultiHeadDispensePlate, MultiHeadDispenseContainer]):
        raise NotImplementedError("The Opentrons backend does not support the 96 head.")

    async def pick_up_resource(self, pickup: ResourcePickup):
        raise NotImplementedError("The Opentrons backend does not support the robotic arm.")

    async def move_picked_up_resource(self, move: ResourceMove):
        raise NotImplementedError("The Opentrons backend does not support the robotic arm.")

    async def drop_resource(self, drop: ResourceDrop):
        raise NotImplementedError("The Opentrons backend does not support the robotic arm.")

    def can_pick_up_tip(self, channel_idx: int, tip: Tip) -> bool:
        return True  # PRCXI9300Backend does not have tip compatibility issues

    def serialize(self) -> dict:
        raise NotImplementedError()

    @property
    def num_channels(self) -> int:
        return self._num_channels


class PRCXI9300Api:
    def __init__(self, host: str = "127.0.0.1", port: int = 9999, timeout: float = 10.0, axis="Left", debug: bool = False) -> None:
        self.host, self.port, self.timeout = host, port, timeout
        self.debug = debug
        self.axis = axis

    @staticmethod
    def _len_prefix(n: int) -> bytes:
        return bytes.fromhex(format(n, "016x"))

    def _raw_request(self, payload: str) -> str:
        if self.debug:
            return " "
        with contextlib.closing(socket.socket()) as sock:
            sock.settimeout(self.timeout)
            sock.connect((self.host, self.port))
            data = payload.encode()
            sock.sendall(self._len_prefix(len(data)) + data)

            chunks, first = [], True
            while True:
                chunk = sock.recv(4096)
                if not chunk:
                    break
                if first:
                    chunk, first = chunk[8:], False
                chunks.append(chunk)
            return b"".join(chunks).decode()

    # ---------------------------------------------------- 方案相关（ISolution）
    def list_solutions(self) -> List[Dict[str, Any]]:
        """GetSolutionList"""
        return self.call("ISolution", "GetSolutionList")

    def load_solution(self, solution_id: str) -> bool:
        """LoadSolution"""
        return self.call("ISolution", "LoadSolution", [solution_id])

    def add_solution(self, name: str, matrix_id: str, steps: List[Dict[str, Any]]) -> str:
        """AddSolution → 返回新方案 GUID"""
        return self.call("ISolution", "AddSolution", [name, matrix_id, steps])

    # ---------------------------------------------------- 自动化控制（IAutomation）
    def start(self) -> bool:
        return self.call("IAutomation", "Start")

    def call(self, service: str, method: str, params: Optional[list] = None) -> Any:
        payload = json.dumps(
            {"ServiceName": service, "MethodName": method, "Paramters": params or []}, separators=(",", ":")
        )
        resp = json.loads(self._raw_request(payload))
        if not resp.get("Success", False):
            raise PRCXIError(resp.get("Msg", "Unknown error"))
        data = resp.get("Data")
        try:
            return json.loads(data)
        except (TypeError, json.JSONDecodeError):
            return data

    def pause(self) -> bool:
        """Pause"""
        return self.call("IAutomation", "Pause")

    def resume(self) -> bool:
        """Resume"""
        return self.call("IAutomation", "Resume")

    def get_error_code(self) -> Optional[str]:
        """GetErrorCode"""
        return self.call("IAutomation", "GetErrorCode")

    def get_reset_status(self) -> bool:
        """GetErrorCode"""
        if self.debug:
            return True
        res = self.call("IAutomation", "GetResetStatus")
        return not res

    def clear_error_code(self) -> bool:
        """RemoveErrorCodet"""
        return self.call("IAutomation", "RemoveErrorCodet")

    # ---------------------------------------------------- 运行状态（IMachineState）
    def step_state_list(self) -> List[Dict[str, Any]]:
        """GetStepStateList"""
        return self.call("IMachineState", "GetStepStateList")

    def step_status(self, seq_num: int) -> Dict[str, Any]:
        """GetStepStatus"""
        return self.call("IMachineState", "GetStepStatus", [seq_num])

    def step_state(self, seq_num: int) -> Dict[str, Any]:
        """GetStepState"""
        return self.call("IMachineState", "GetStepState", [seq_num])

    def axis_location(self, axis_num: int = 1) -> Dict[str, Any]:
        """GetLocation"""
        return self.call("IMachineState", "GetLocation", [axis_num])

    # ---------------------------------------------------- 版位矩阵（IMatrix）
    def list_matrices(self) -> List[Dict[str, Any]]:
        """GetWorkTabletMatrices"""
        return self.call("IMatrix", "GetWorkTabletMatrices")

    def matrix_by_id(self, matrix_id: str) -> Dict[str, Any]:
        """GetWorkTabletMatrixById"""
        return self.call("IMatrix", "GetWorkTabletMatrixById", [matrix_id])

    def add_WorkTablet_Matrix(self, matrix: MatrixInfo):
        return self.call("IMatrix", "AddWorkTabletMatrix", [matrix])

    def Load(self, dosage: int, plate_no: int, is_whole_plate: bool, hole_row: int, hole_col: int, blending_times: int,
             balance_height: int, plate_or_hole: str, hole_numbers: str, assist_fun1: str = "", assist_fun2: str = "",
             assist_fun3: str = "", assist_fun4: str = "", assist_fun5: str = "",
             liquid_method: str = "NormalDispense") -> Dict[str, Any]:
        return {
            "StepAxis": self.axis,
            "Function": "Load",
            "DosageNum": dosage,
            "PlateNo": plate_no,
            "IsWholePlate": is_whole_plate,
            "HoleRow": hole_row,
            "HoleCol": hole_col,
            "BlendingTimes": blending_times,
            "BalanceHeight": balance_height,
            "PlateOrHoleNum": plate_or_hole,
            "AssistFun1": assist_fun1,
            "AssistFun2": assist_fun2,
            "AssistFun3": assist_fun3,
            "AssistFun4": assist_fun4,
            "AssistFun5": assist_fun5,
            "HoleNumbers": hole_numbers,
            "LiquidDispensingMethod": liquid_method,
        }

    def Imbibing(self, dosage: int, plate_no: int, is_whole_plate: bool, hole_row: int, hole_col: int,
                 blending_times: int, balance_height: int, plate_or_hole: str, hole_numbers: str, assist_fun1: str = "",
                 assist_fun2: str = "", assist_fun3: str = "", assist_fun4: str = "", assist_fun5: str = "",
                 liquid_method: str = "NormalDispense") -> Dict[str, Any]:
        return {
            "StepAxis": self.axis,
            "Function": "Imbibing",
            "DosageNum": dosage,
            "PlateNo": plate_no,
            "IsWholePlate": is_whole_plate,
            "HoleRow": hole_row,
            "HoleCol": hole_col,
            "BlendingTimes": blending_times,
            "BalanceHeight": balance_height,
            "PlateOrHoleNum": plate_or_hole,
            "AssistFun1": assist_fun1,
            "AssistFun2": assist_fun2,
            "AssistFun3": assist_fun3,
            "AssistFun4": assist_fun4,
            "AssistFun5": assist_fun5,
            "HoleNumbers": hole_numbers,
            "LiquidDispensingMethod": liquid_method,
        }

    def Tapping(
        self,
        dosage: int,
        plate_no: int,
        is_whole_plate: bool,
        hole_row: int,
        hole_col: int,
        blending_times: int,
        balance_height: int,
        plate_or_hole: str,
        hole_numbers: str,
        assist_fun1: str = "",
        assist_fun2: str = "",
        assist_fun3: str = "",
        assist_fun4: str = "",
        assist_fun5: str = "",
        liquid_method: str = "NormalDispense",
    ) -> Dict[str, Any]:
        return {
            "StepAxis": self.axis,
            "Function": "Tapping",
            "DosageNum": dosage,
            "PlateNo": plate_no,
            "IsWholePlate": is_whole_plate,
            "HoleRow": hole_row,
            "HoleCol": hole_col,
            "BlendingTimes": blending_times,
            "BalanceHeight": balance_height,
            "PlateOrHoleNum": plate_or_hole,
            "AssistFun1": assist_fun1,
            "AssistFun2": assist_fun2,
            "AssistFun3": assist_fun3,
            "AssistFun4": assist_fun4,
            "AssistFun5": assist_fun5,
            "HoleNumbers": hole_numbers,
            "LiquidDispensingMethod": liquid_method,
        }

    def Blending(
        self,
        dosage: int,
        plate_no: int,
        is_whole_plate: bool,
        hole_row: int,
        hole_col: int,
        blending_times: int,
        balance_height: int,
        plate_or_hole: str,
        hole_numbers: str,
        assist_fun1: str = "",
        assist_fun2: str = "",
        assist_fun3: str = "",
        assist_fun4: str = "",
        assist_fun5: str = "",
        liquid_method: str = "NormalDispense",
    ) -> Dict[str, Any]:
        return {
            "StepAxis": self.axis,
            "Function": "Blending",
            "DosageNum": dosage,
            "PlateNo": plate_no,
            "IsWholePlate": is_whole_plate,
            "HoleRow": hole_row,
            "HoleCol": hole_col,
            "BlendingTimes": blending_times,
            "BalanceHeight": balance_height,
            "PlateOrHoleNum": plate_or_hole,
            "AssistFun1": assist_fun1,
            "AssistFun2": assist_fun2,
            "AssistFun3": assist_fun3,
            "AssistFun4": assist_fun4,
            "AssistFun5": assist_fun5,
            "HoleNumbers": hole_numbers,
            "LiquidDispensingMethod": liquid_method,
        }

    def UnLoad(
        self,
        dosage: int,
        plate_no: int,
        is_whole_plate: bool,
        hole_row: int,
        hole_col: int,
        blending_times: int,
        balance_height: int,
        plate_or_hole: str,
        hole_numbers: str,
        assist_fun1: str = "",
        assist_fun2: str = "",
        assist_fun3: str = "",
        assist_fun4: str = "",
        assist_fun5: str = "",
        liquid_method: str = "NormalDispense",
    ) -> Dict[str, Any]:
        return {
            "StepAxis": self.axis,
            "Function": "UnLoad",
            "DosageNum": dosage,
            "PlateNo": plate_no,
            "IsWholePlate": is_whole_plate,
            "HoleRow": hole_row,
            "HoleCol": hole_col,
            "BlendingTimes": blending_times,
            "BalanceHeight": balance_height,
            "PlateOrHoleNum": plate_or_hole,
            "AssistFun1": assist_fun1,
            "AssistFun2": assist_fun2,
            "AssistFun3": assist_fun3,
            "AssistFun4": assist_fun4,
            "AssistFun5": assist_fun5,
            "HoleNumbers": hole_numbers,
            "LiquidDispensingMethod": liquid_method,
        }


if __name__ == "__main__":
    # Example usage
    # 1. 用导出的json，给每个T1 T2板子设定相应的物料，如果是孔板和枪头盒，要对应区分
    # 2. backend需要支持num channel为1的情况
    # 3. 设计一个单点动作流程，可以跑
    # 4.

    
    # deck = PRCXI9300Deck(name="PRCXI_Deck_9300", size_x=100, size_y=100, size_z=100)

    # from pylabrobot.resources.opentrons.tip_racks import opentrons_96_tiprack_300ul,opentrons_96_tiprack_10ul
    # from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep

    # def get_well_container(name: str) -> PRCXI9300Container:
    #     well_containers = corning_96_wellplate_360ul_flat(name).serialize()
    #     plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
    #                        ordering=collections.OrderedDict())
    #     plate_serialized = plate.serialize()
    #     plate_serialized["parent_name"] = deck.name
    #     well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
    #     new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
    #     return new_plate

    # def get_tip_rack(name: str) -> PRCXI9300Container:
    #     tip_racks = opentrons_96_tiprack_300ul("name").serialize()
    #     tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
    #                        ordering=collections.OrderedDict())
    #     tip_rack_serialized = tip_rack.serialize()
    #     tip_rack_serialized["parent_name"] = deck.name
    #     tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
    #     new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
    #     return new_tip_rack
    
    # plate1 = get_tip_rack("RackT1")
    # plate1.load_state({
    #     "Material": {
    #         "uuid": "076250742950465b9d6ea29a225dfb00",
    #         "Code": "ZX-001-300",
    #         "Name": "300μL Tip头"
    #     }
    # })

    # plate2 = get_well_container("PlateT2")
    # plate2.load_state({
    #     "Material": {
    #         "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
    #         "Code": "ZX-019-2.2",
    #         "Name": "96深孔板"
    #     }
    # })


    # plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash")
    # plate3.load_state({
    #     "Material": {
    #         "uuid": "730067cf07ae43849ddf4034299030e9"
    #     }
    # })

    # plate4 = get_well_container("PlateT4")
    # plate4.load_state({
    #     "Material": {
    #         "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
    #         "Code": "ZX-019-2.2",
    #         "Name": "96深孔板"
    #     }
    # })

    # plate5 = get_well_container("PlateT5")
    # plate5.load_state({
    #     "Material": {
    #         "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
    #         "Code": "ZX-019-2.2",
    #         "Name": "96深孔板"
    #     }
    # })
    # plate6 = get_well_container("PlateT6")

    # plate6.load_state({
    #     "Material": {
    #         "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
    #         "Code": "ZX-019-2.2",
    #         "Name": "96深孔板"
    #     }
    # })

    # deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
    # deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
    # deck.assign_child_resource(plate3, location=Coordinate(0, 0, 0))
    # deck.assign_child_resource(plate4, location=Coordinate(0, 0, 0))
    # deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
    # deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))

    # # print(plate2)
    # plate_2_liquids = [[('water', 500)]]*96
    # plate2.set_well_liquids(plate_2_liquids)

    # handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999, 
    #                            timeout=10.0, setup=False, debug=False, 
    #                            matrix_id="71593",
    #                            channel_num=8, axis="Left")  # Initialize the handler with the deck and host settings
    

    # handler.set_tiprack([plate1]) 

    # asyncio.run(handler.setup())  # Initialize the handler and setup the connection
    # from pylabrobot.resources import set_volume_tracking
    # # from pylabrobot.resources import set_tip_tracking
    # set_volume_tracking(enabled=True)
    # from unilabos.resources.graphio import *
    # A = tree_to_list([resource_plr_to_ulab(deck)])
    # with open("deck_9300_new.json", "w", encoding="utf-8") as f:
    #     json.dump(A, f, indent=4, ensure_ascii=False)
    # asyncio.run(handler.create_protocol(protocol_name="Test Protocol"))  # Initialize the backend and setup the connection

    # # asyncio.run(handler.pick_up_tips(plate1.children[:8],[0,1,2,3,4,5,6,7]))
    # # print(plate1.children[:8])
    # # asyncio.run(handler.aspirate(plate2.children[:8],[50]*8, [0,1,2,3,4,5,6,7]))
    # # print(plate2.children[:8])
    # # asyncio.run(handler.dispense(plate5.children[:8],[50]*8,[0,1,2,3,4,5,6,7]))
    # # print(plate5.children[:8])

    # # # # # asyncio.run(handler.drop_tips(tip_rack.children[8:16],[0,1,2,3,4,5,6,7]))
    # # asyncio.run(handler.discard_tips())

    # # asyncio.run(handler.mix(well_containers.children[:8
    # # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
    # # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2))  # Print matrix info
    # # asyncio.run(handler.add_liquid(
    # #     asp_vols=[100]*16,
    # #     dis_vols=[100]*16,
    # #     reagent_sources=plate2.children[:16],
    # #     targets=plate5.children[:16],
    # #     use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
    # #     flow_rates=[None] * 32,
    # #     offsets=[Coordinate(0, 0, 0)] * 32,
    # #     liquid_height=[None] * 16,
    # #     blow_out_air_volume=[None] * 16,
    # #     delays=None,
    # #     mix_time=3,
    # #     mix_vol=50,
    # #     spread="wide",
    # # ))
    # # asyncio.run(handler.run_protocol())  # Run the protocol
    # # asyncio.run(handler.remove_liquid(
    # #     vols=[100]*16,
    # #     sources=plate2.children[-16:],
    # #     waste_liquid=plate5.children[:16], # 这个有些奇怪，但是好像也只能这么写
    # #     use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
    # #     flow_rates=[None] * 32,
    # #     offsets=[Coordinate(0, 0, 0)] * 32,
    # #     liquid_height=[None] * 32,
    # #     blow_out_air_volume=[None] * 32,
    # #     spread="wide",
    # # ))

    # acid = [20]*8+[40]*8+[60]*8+[80]*8+[100]*8+[120]*8+[140]*8+[160]*8+[180]*8+[200]*8+[220]*8+[240]*8
    # alkaline = acid[::-1]  # Reverse the acid list for alkaline
    # asyncio.run(handler.transfer_liquid(
    #     asp_vols=acid,
    #     dis_vols=acid,
    #     tip_racks=[plate1],
    #     sources=plate2.children[:],
    #     targets=plate5.children[:],
    #     use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
    #     offsets=[Coordinate(0, 0, 0)] * 32,
    #     asp_flow_rates=[None] * 16,
    #     dis_flow_rates=[None] * 16,
    #     liquid_height=[None] * 32,
    #     blow_out_air_volume=[None] * 32,
    #     mix_times=3,
    #     mix_vol=50,
    #     spread="wide",
    # ))
    # asyncio.run(handler.run_protocol())  # Run the protocol
    # # # input("Running protocol...")
    # # # input("Press Enter to continue...")  # Wait for user input before proceeding
    # # # print("PRCXI9300Handler initialized with deck and host settings.")

    # # Example usage
    # # 1. 用导出的json，给每个T1 T2板子设定相应的物料，如果是孔板和枪头盒，要对应区分
    # # 2. 设计一个单点动作流程，可以跑
    # # 3.


    deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)

    from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
    from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep

    def get_well_container(name: str) -> PRCXI9300Container:
        well_containers = corning_96_wellplate_360ul_flat(name).serialize()
        plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
                           ordering=collections.OrderedDict())
        plate_serialized = plate.serialize()
        plate_serialized["parent_name"] = deck.name
        well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
        new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
        return new_plate

    def get_tip_rack(name: str) -> PRCXI9300Container:
        tip_racks = opentrons_96_tiprack_10ul("name").serialize()
        tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
                           ordering=collections.OrderedDict())
        tip_rack_serialized = tip_rack.serialize()
        tip_rack_serialized["parent_name"] = deck.name
        tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
        new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
        return new_tip_rack
    
    plate1 = get_well_container("HPLCPlateT1")
    plate1.load_state({
        "Material": {
            "uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
            "Code": "HPLC01",
            "Name":  "HPLC料盘"
        }
    })
    plate2 = get_well_container("PlateT2")
    plate2.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650",
        }
    })
    plate3 = get_well_container("PlateT3")
    plate3.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650",
        }
    })
    trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
    trash.load_state({
        "Material": {
            "uuid": "730067cf07ae43849ddf4034299030e9"
        }
    })
    plate5 = get_well_container("PlateT5")
    plate5.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650",
        }
    })
    plate6 = get_well_container("PlateT6")
    plate6.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650"
        }
    })
    plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
    plate7.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650"
        }
    })
    plate8 = get_tip_rack("RackT8")
    plate8.load_state({
        "Material": {
            "uuid": "068b3815e36b4a72a59bae017011b29f",
            "Code": "ZX-001-10+",
            "Name": "10μL加长 Tip头"
        }
    })
    plate9 = get_well_container("PlateT9")
    plate9.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650"
        }
    })
    plate10 = get_well_container("PlateT10")
    plate10.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650"
        }
    })
    plate11 = get_well_container("PlateT11")
    plate11.load_state({
        "Material": {
            "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
        }
    })
    plate12 = get_well_container("PlateT12")
    plate12.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650"
        }
    })
    plate13 = get_well_container("PlateT13")
    plate13.load_state({
        "Material": {
            "uuid": "04211a2dc93547fe9bf6121eac533650"
        }
    })

    # container_for_nothing = PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())

    deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing1", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing2", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
    deck.assign_child_resource(trash, location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing3", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing4", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
    deck.assign_child_resource(plate8, location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing5", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing6", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
    deck.assign_child_resource(plate11, location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
    deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing8", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))

    handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999, 
                               timeout=10.0, setup=False, debug=False, 
                               matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
                               channel_num=1, axis="Right")  # Initialize the handler with the deck and host settings
    
    handler.set_tiprack([plate8])  # Set the tip rack for the handler
    asyncio.run(handler.setup())  # Initialize the handler and setup the connection
    from pylabrobot.resources import set_volume_tracking
    # from pylabrobot.resources import set_tip_tracking
    set_volume_tracking(enabled=True)

    plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)])  # Set liquids for every 8 wells in plate8

    from unilabos.resources.graphio import *

    A = tree_to_list([resource_plr_to_ulab(deck)])
    # with open("deck.json", "w", encoding="utf-8") as f:
    #     json.dump(A, f, indent=4, ensure_ascii=False)

    print(plate11.get_well(0).tracker.get_used_volume())
    asyncio.run(handler.create_protocol(protocol_name="Test Protocol"))  # Initialize the backend and setup the connection

    # asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
    # print(plate8.children[8])
    # # asyncio.run(handler.run_protocol())
    # asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
    # print(plate11.children[0])
    # # asyncio.run(handler.run_protocol())
    # asyncio.run(handler.dispense([plate1.children[0]],[10],[0]))
    # print(plate1.children[0])
    # # asyncio.run(handler.run_protocol())
    # asyncio.run(handler.mix([plate1.children[0]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
    # print(plate1.children[0])
    # asyncio.run(handler.discard_tips())

    asyncio.run(handler.add_liquid(
    asp_vols=[10]*7,
    dis_vols=[10]*7,
    reagent_sources=plate11.children[:7],
    targets=plate1.children[2:9],
    use_channels=[0],
    flow_rates=[None] * 7,
    offsets=[Coordinate(0, 0, 0)] * 7,
    liquid_height=[None] * 7,
    blow_out_air_volume=[None] * 2,
    delays=None,
    mix_time=3,
    mix_vol=5,
    spread="custom",
))

    asyncio.run(handler.run_protocol())  # Run the protocol
# #     asyncio.run(handler.transfer_liquid(
# #     asp_vols=[10]*2,
# #     dis_vols=[10]*2,
# #     sources=plate11.children[:2],
# #     targets=plate11.children[-2:],
# #     use_channels=[0],
# #     offsets=[Coordinate(0, 0, 0)] * 4,
# #     liquid_height=[None] * 2,
# #     blow_out_air_volume=[None] * 2,
# #     delays=None,
# #     mix_times=3,
# #     mix_vol=5,
# #     spread="wide",
# #     tip_racks=[plate8]
# # ))

# #     asyncio.run(handler.remove_liquid(
# #     vols=[10]*2,
# #     sources=plate11.children[:2],
# #     waste_liquid=plate11.children[43],
# #     use_channels=[0],
# #     offsets=[Coordinate(0, 0, 0)] * 4,
# #     liquid_height=[None] * 2,
# #     blow_out_air_volume=[None] * 2,
# #     delays=None,
# #     spread="wide"
# # ))
#     asyncio.run(handler.run_protocol())

#     # asyncio.run(handler.discard_tips())
#     # asyncio.run(handler.mix(well_containers.children[:8
#     # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
#     #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2))  # Print matrix info


#     # asyncio.run(handler.remove_liquid(
#     #     vols=[100]*16,
#     #     sources=well_containers.children[-16:],
#     #     waste_liquid=well_containers.children[:16], # 这个有些奇怪，但是好像也只能这么写
#     #     use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
#     #     flow_rates=[None] * 32,
#     #     offsets=[Coordinate(0, 0, 0)] * 32,
#     #     liquid_height=[None] * 32,
#     #     blow_out_air_volume=[None] * 32,
#     #     spread="wide",
#     # ))
#     # asyncio.run(handler.transfer_liquid(
#     #     asp_vols=[100]*16,
#     #     dis_vols=[100]*16,
#     #     tip_racks=[tip_rack],
#     #     sources=well_containers.children[-16:],
#     #     targets=well_containers.children[:16],
#     #     use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
#     #     offsets=[Coordinate(0, 0, 0)] * 32,
#     #     asp_flow_rates=[None] * 16,
#     #     dis_flow_rates=[None] * 16,
#     #     liquid_height=[None] * 32,
#     #     blow_out_air_volume=[None] * 32,
#     #     mix_times=3,
#     #     mix_vol=50,
#     #     spread="wide",
#     # ))
    print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2))  # Print matrix info
#     # input("pick_up_tips add step")
#     #asyncio.run(handler.run_protocol())  # Run the protocol
#     # input("Running protocol...")
#     # input("Press Enter to continue...")  # Wait for user input before proceeding
#     # print("PRCXI9300Handler initialized with deck and host settings.")

