diff --git a/test/resources/YB_materials_info.json b/test/resources/YB_materials_info.json new file mode 100644 index 0000000..d5bc381 --- /dev/null +++ b/test/resources/YB_materials_info.json @@ -0,0 +1,52 @@ +[ + { + "id": "3a1d377b-299d-d0f2-ced9-48257f60dfad", + "typeName": "加样头(大)", + "code": "0005-00145", + "barCode": "", + "name": "LiDFOB", + "quantity": 9999.0, + "lockQuantity": 0.0, + "unit": "个", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a19da56-1379-ff7c-1745-07e200b44ce2", + "whid": "3a19da56-1378-613b-29f2-871e1a287aa5", + "whName": "粉末加样头堆栈", + "code": "0005-0001", + "x": 1, + "y": 1, + "z": 1, + "quantity": 0 + } + ], + "detail": [] + }, + { + "id": "3a1d377b-6a81-6a7e-147c-f89f6463656d", + "typeName": "液", + "code": "0006-00141", + "barCode": "", + "name": "EMC", + "quantity": 99999.0, + "lockQuantity": 0.0, + "unit": "g", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a1baa20-a7b1-c665-8b9c-d8099d07d2f6", + "whid": "3a1baa20-a7b0-5c19-8844-5de8924d4e78", + "whName": "4号手套箱内部堆栈", + "code": "0015-0001", + "x": 1, + "y": 1, + "z": 1, + "quantity": 0 + } + ], + "detail": [] + } +] \ No newline at end of file diff --git a/test/resources/test_resourcetreeset.py b/test/resources/test_resourcetreeset.py index ff5cfd0..b7602ed 100644 --- a/test/resources/test_resourcetreeset.py +++ b/test/resources/test_resourcetreeset.py @@ -1,3 +1,4 @@ +from ast import If import pytest import json import os @@ -13,13 +14,8 @@ lab_registry.setup() type_mapping = { - "烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("YB_1BottleCarrier", ""), - "样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), + "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), } @@ -57,12 +53,20 @@ def bioyond_materials_liquidhandling_2() -> list[dict]: "bioyond_materials_reaction", "bioyond_materials_liquidhandling_1", ]) -def test_resourcetreeset_from_plr(materials_fixture, request) -> list[dict]: - materials = request.getfixturevalue(materials_fixture) +def test_resourcetreeset_from_plr() -> list[dict]: + # 直接加载 bioyond_materials_reaction.json 文件 + current_dir = os.path.dirname(os.path.abspath(__file__)) + json_path = os.path.join(current_dir, "YB_materials_info.json") + with open(json_path, "r", encoding="utf-8") as f: + materials = json.load(f) deck = BIOYOND_PolymerReactionStation_Deck("test_deck") output = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=deck) - print(deck.summary()) + print(output) + # print(deck.summary()) r = ResourceTreeSet.from_plr_resources([deck]) print(r.dump()) # json.dump(deck.serialize(), open("test.json", "w", encoding="utf-8"), indent=4) + +if __name__ == "__main__": + test_resourcetreeset_from_plr() diff --git a/unilabos/devices/electrolysis_water_platfrom/electrolysis_water_platfrom.py b/unilabos/devices/electrolysis_water_platfrom/electrolysis_water_platfrom.py new file mode 100644 index 0000000..cfcfdee --- /dev/null +++ b/unilabos/devices/electrolysis_water_platfrom/electrolysis_water_platfrom.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +import serial +import time +import csv +import threading +import os +from collections import deque +from typing import Dict, Any, Optional +from pylabrobot.resources import Deck + +from unilabos.devices.workstation.workstation_base import WorkstationBase + + +class ElectrolysisWaterPlatform(WorkstationBase): + """ + 电解水平台工作站 + 基于 WorkstationBase 的电解水实验平台,支持串口通信和数据采集 + """ + + def __init__( + self, + deck: Deck, + port: str = "COM10", + baudrate: int = 115200, + csv_path: Optional[str] = None, + timeout: float = 0.2, + **kwargs + ): + super().__init__(deck, **kwargs) + + # ========== 配置 ========== + self.port = port + self.baudrate = baudrate + # 如果没有指定路径,默认保存在代码文件所在目录 + if csv_path is None: + current_dir = os.path.dirname(os.path.abspath(__file__)) + self.csv_path = os.path.join(current_dir, "stm32_data.csv") + else: + self.csv_path = csv_path + self.ser_timeout = timeout + self.chunk_read = 128 + + # 串口对象 + self.ser: Optional[serial.Serial] = None + self.stop_flag = False + + # 线程对象 + self.rx_thread: Optional[threading.Thread] = None + self.tx_thread: Optional[threading.Thread] = None + + # ==== 接收(下位机->上位机):固定 1+13+1 = 15 字节 ==== + self.RX_HEAD = 0x3E + self.RX_TAIL = 0x3E + self.RX_FRAME_LEN = 1 + 13 + 1 # 15 + + # ==== 发送(上位机->下位机):固定 1+9+1 = 11 字节 ==== + self.TX_HEAD = 0x3E + self.TX_TAIL = 0xE3 # 协议图中标注 E3 作为帧尾 + self.TX_FRAME_LEN = 1 + 9 + 1 # 11 + + def open_serial(self, port: Optional[str] = None, baudrate: Optional[int] = None, timeout: Optional[float] = None) -> Optional[serial.Serial]: + """打开串口""" + port = port or self.port + baudrate = baudrate or self.baudrate + timeout = timeout or self.ser_timeout + try: + ser = serial.Serial(port, baudrate, timeout=timeout) + print(f"[OK] 串口 {port} 已打开,波特率 {baudrate}") + ser.reset_input_buffer() + ser.reset_output_buffer() + self.ser = ser + return ser + except serial.SerialException as e: + print(f"[ERR] 无法打开串口 {port}: {e}") + return None + + def close_serial(self): + """关闭串口""" + if self.ser and self.ser.is_open: + self.ser.close() + print("[INFO] 串口已关闭") + + @staticmethod + def u16_be(h: int, l: int) -> int: + """将两个字节组合成16位无符号整数(大端序)""" + return ((h & 0xFF) << 8) | (l & 0xFF) + + @staticmethod + def split_u16_be(val: int) -> tuple: + """返回 (高字节, 低字节),输入会夹到 0..65535""" + v = int(max(0, min(65535, int(val)))) + return (v >> 8) & 0xFF, v & 0xFF + + # ================== 接收:固定15字节 ================== + def parse_rx_payload(self, dat13: bytes) -> Optional[Dict[str, Any]]: + """解析 13 字节数据区(下位机发送到上位机)""" + if len(dat13) != 13: + return None + current_mA = self.u16_be(dat13[0], dat13[1]) + voltage_mV = self.u16_be(dat13[2], dat13[3]) + temperature_raw = self.u16_be(dat13[4], dat13[5]) + tds_ppm = self.u16_be(dat13[6], dat13[7]) + gas_sccm = self.u16_be(dat13[8], dat13[9]) + liquid_mL = self.u16_be(dat13[10], dat13[11]) + ph_raw = dat13[12] & 0xFF + + return { + "Current_mA": current_mA, + "Voltage_mV": voltage_mV, + "Temperature_C": round(temperature_raw / 100.0, 2), + "TDS_ppm": tds_ppm, + "GasFlow_sccm": gas_sccm, + "LiquidFlow_mL": liquid_mL, + "pH": round(ph_raw / 10.0, 2) + } + + def try_parse_rx_frame(self, frame15: bytes) -> Optional[Dict[str, Any]]: + """尝试解析接收帧""" + if len(frame15) != self.RX_FRAME_LEN: + return None + if frame15[0] != self.RX_HEAD or frame15[-1] != self.RX_TAIL: + return None + return self.parse_rx_payload(frame15[1:-1]) + + def rx_thread_fn(self): + """接收线程函数""" + headers = ["Timestamp", "Current_mA", "Voltage_mV", + "Temperature_C", "TDS_ppm", "GasFlow_sccm", "LiquidFlow_mL", "pH"] + + new_file = not os.path.exists(self.csv_path) + f = open(self.csv_path, mode='a', newline='', encoding='utf-8') + writer = csv.writer(f) + if new_file: + writer.writerow(headers) + f.flush() + + buf = deque(maxlen=8192) + print(f"[RX] 开始接收(帧长 {self.RX_FRAME_LEN} 字节);写入:{self.csv_path}") + + try: + while not self.stop_flag and self.ser and self.ser.is_open: + chunk = self.ser.read(self.chunk_read) + if chunk: + buf.extend(chunk) + while True: + # 找帧头 + try: + start = next(i for i, b in enumerate(buf) if b == self.RX_HEAD) + except StopIteration: + buf.clear() + break + if start > 0: + for _ in range(start): + buf.popleft() + if len(buf) < self.RX_FRAME_LEN: + break + candidate = bytes([buf[i] for i in range(self.RX_FRAME_LEN)]) + if candidate[-1] == self.RX_TAIL: + parsed = self.try_parse_rx_frame(candidate) + for _ in range(self.RX_FRAME_LEN): + buf.popleft() + if parsed: + ts = time.strftime("%Y-%m-%d %H:%M:%S") + row = [ts, + parsed["Current_mA"], parsed["Voltage_mV"], + parsed["Temperature_C"], parsed["TDS_ppm"], + parsed["GasFlow_sccm"], parsed["LiquidFlow_mL"], + parsed["pH"]] + writer.writerow(row) + f.flush() + # 若不想打印可注释下一行 + # print(f"[{ts}] I={parsed['Current_mA']} mA, V={parsed['Voltage_mV']} mV, " + # f"T={parsed['Temperature_C']} °C, TDS={parsed['TDS_ppm']}, " + # f"Gas={parsed['GasFlow_sccm']} sccm, Liq={parsed['LiquidFlow_mL']} mL, pH={parsed['pH']}") + else: + # 头不变,尾不对,丢1字节继续对齐 + buf.popleft() + else: + time.sleep(0.01) + finally: + f.close() + print("[RX] 接收线程退出,CSV 已关闭") + + # ================== 发送:固定11字节 ================== + def build_tx_frame(self, mode: int, current_ma: int, voltage_mv: int, temp_c: float, ki: float, pump_percent: float) -> bytes: + """ + 发送帧:HEAD + [mode, I_hi, I_lo, V_hi, V_lo, T_hi, T_lo, Ki_byte, Pump_byte] + TAIL + - mode: 0=恒压, 1=恒流 + - current_ma: mA (0..65535) + - voltage_mv: mV (0..65535) + - temp_c: ℃,将 *100 后拆分为高/低字节 + - ki: 0.0..20.0 -> byte = round(ki * 10) 夹到 0..200 + - pump_percent: 0..100 -> byte = round(pump * 2) 夹到 0..200 + """ + mode_b = 1 if int(mode) == 1 else 0 + + i_hi, i_lo = self.split_u16_be(current_ma) + v_hi, v_lo = self.split_u16_be(voltage_mv) + + t100 = int(round(float(temp_c) * 100.0)) + t_hi, t_lo = self.split_u16_be(t100) + + ki_b = int(max(0, min(200, round(float(ki) * 10)))) + pump_b = int(max(0, min(200, round(float(pump_percent) * 2)))) + + return bytes(( + self.TX_HEAD, + mode_b, + i_hi, i_lo, + v_hi, v_lo, + t_hi, t_lo, + ki_b, + pump_b, + self.TX_TAIL + )) + + def tx_thread_fn(self): + """ + 发送线程函数 + 用户输入 6 个用逗号分隔的数值: + mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent + 例如: 0,1000,500,0,0,50 + """ + print("\n输入 6 个值(用英文逗号分隔),顺序为:") + print("mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent") + print("示例恒压:0,500,1000,25,0,100 (stop 结束)\n") + print("示例恒流:1,1000,500,25,0,100 (stop 结束)\n") + print("示例恒流:1,2000,500,25,0,100 (stop 结束)\n") + # 1,2000,500,25,0,100 + + while not self.stop_flag and self.ser and self.ser.is_open: + try: + line = input(">>> ").strip() + except EOFError: + self.stop_flag = True + break + + if not line: + continue + if line.lower() == "stop": + self.stop_flag = True + print("[SYS] 停止程序") + break + + try: + parts = [p.strip() for p in line.split(",")] + if len(parts) != 6: + raise ValueError("需要 6 个逗号分隔的数值") + mode = int(parts[0]) + i_ma = int(float(parts[1])) + v_mv = int(float(parts[2])) + t_c = float(parts[3]) + ki = float(parts[4]) + pump = float(parts[5]) + + frame = self.build_tx_frame(mode, i_ma, v_mv, t_c, ki, pump) + self.ser.write(frame) + print("[TX]", " ".join(f"{b:02X}" for b in frame)) + except Exception as e: + print("[TX] 输入/打包失败:", e) + print("格式:mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent") + continue + + def start(self): + """启动电解水平台""" + self.ser = self.open_serial() + if self.ser: + try: + self.rx_thread = threading.Thread(target=self.rx_thread_fn, daemon=True) + self.tx_thread = threading.Thread(target=self.tx_thread_fn, daemon=True) + self.rx_thread.start() + self.tx_thread.start() + print("[INFO] 电解水平台已启动") + self.tx_thread.join() # 等待用户输入线程结束(输入 stop) + finally: + self.close_serial() + + def stop(self): + """停止电解水平台""" + self.stop_flag = True + if self.rx_thread and self.rx_thread.is_alive(): + self.rx_thread.join(timeout=2.0) + if self.tx_thread and self.tx_thread.is_alive(): + self.tx_thread.join(timeout=2.0) + self.close_serial() + print("[INFO] 电解水平台已停止") + + +# ================== 主入口 ================== +if __name__ == "__main__": + # 创建一个简单的 Deck 用于测试 + from pylabrobot.resources import Deck + + deck = Deck() + platform = ElectrolysisWaterPlatform(deck) + platform.start() diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 7888039..ff29574 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -10,7 +10,6 @@ from datetime import datetime, timedelta import re import threading import json - from urllib3 import response from unilabos.devices.workstation.workstation_base import WorkstationBase from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer @@ -19,6 +18,8 @@ from unilabos.devices.workstation.bioyond_studio.config import ( ) from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService from unilabos.utils.log import logger +from unilabos.registry.registry import lab_registry + def _iso_local_now_ms() -> str: # 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z @@ -967,15 +968,16 @@ class BioyondCellWorkstation(BioyondWorkstation): if __name__ == "__main__": + lab_registry.setup() ws = BioyondCellWorkstation() - logger.info(ws.scheduler_stop()) + # logger.info(ws.scheduler_stop()) + # logger.info(ws.scheduler_start()) - - results = ws.create_materials(SOLID_LIQUID_MAPPINGS) - for r in results: - logger.info(r) + # results = ws.create_materials(SOLID_LIQUID_MAPPINGS) + # for r in results: + # logger.info(r) # 从CSV文件读取物料列表并批量创建入库 - result = ws.create_and_inbound_materials() + # result = ws.create_and_inbound_materials() # 继续后续流程 # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx index a89f1bc..e6addeb 100644 Binary files a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx and b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx differ diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 4e2fddd..f184a28 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -5,13 +5,10 @@ import os # ==================== API 基础配置 ==================== - - -# ==================== 完整的 Bioyond 配置 ==================== # BioyondCellWorkstation 默认配置(包含所有必需参数) API_CONFIG = { # API 连接配置 - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"), + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.10.169:44388"), "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), @@ -19,11 +16,9 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.91"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), - "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.22"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) - # 调试模式 - "debug_mode": False, + "debug_mode": False,# 调试模式 } # 库位映射配置 @@ -139,29 +134,10 @@ WAREHOUSE_MAPPING = { # 物料类型配置 MATERIAL_TYPE_MAPPINGS = { - "烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("YB_1BottleCarrier", ""), - "样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), - "20ml分液瓶": ("YB_20ml_Dispensing_Vial", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), - "100ml液体": ("YB_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"), - "液": ("YB_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), - "高粘液": ("YB_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"), - "加样头(大)": ("YB_Large_Dispense_Head", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), - "5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), - "5ml分液瓶": ("YB_5ml_Dispensing_Vial", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), - "20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), - "配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), - "配液瓶(小)": ("YB_Small_Solution_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), - "配液瓶(大)板": ("YB_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), - "配液瓶(大)": ("YB_Large_Solution_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), - "加样头(大)板": ("YB_6x_LargeDispenseHeadCarrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), - "适配器块": ("YB_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), - "枪头盒": ("YB_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), - "枪头": ("YB_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"), + + "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), + # YB信息 } SOLID_LIQUID_MAPPINGS = { diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index 795b740..3e3e0b3 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -63,7 +63,7 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): logger.error("Bioyond API客户端未初始化") return False - bioyond_data = self.bioyond_api_client.stock_material('{"typeMode": 2, "includeDetail": true}') + bioyond_data = self.bioyond_api_client.stock_material('{"typeMode": 1, "includeDetail": true}') if not bioyond_data: logger.warning("从Bioyond获取的物料数据为空") return False @@ -74,6 +74,7 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): type_mapping=self.workstation.bioyond_config["material_type_mappings"], deck=self.workstation.deck ) + print("unilab_resources:",unilab_resources) logger.info(f"从Bioyond同步了 {len(unilab_resources)} 个资源") return True diff --git a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml new file mode 100644 index 0000000..83320c5 --- /dev/null +++ b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml @@ -0,0 +1,25 @@ +YB_jia_yang_tou_da_1X1_carrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_1X1_carrier + type: pylabrobot + description: YB_jia_yang_tou_da_1X1_carrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_1BottleCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier + type: pylabrobot + description: YB_1BottleCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/bottle_carriers.yaml b/unilabos/registry/resources/bioyond/bottle_carriers.yaml deleted file mode 100644 index 0e40c9c..0000000 --- a/unilabos/registry/resources/bioyond/bottle_carriers.yaml +++ /dev/null @@ -1,132 +0,0 @@ -1BottleCarrier: - category: - - bottle_carriers - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_1BottleCarrier - type: pylabrobot - description: 1BottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -1FlaskCarrier: - category: - - bottle_carriers - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_1FlaskCarrier - type: pylabrobot - description: 1FlaskCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6StockCarrier: - category: - - bottle_carriers - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_6StockCarrier - type: pylabrobot - description: 6StockCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6VialCarrier: - category: - - bottle_carriers - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_6VialCarrier - type: pylabrobot - description: 6VialCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6x5ml_DispensingVialCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_6x5ml_DispensingVialCarrier - type: pylabrobot - description: 6x5ml_DispensingVialCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6x20ml_DispensingVialCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_6x20ml_DispensingVialCarrier - type: pylabrobot - description: 6x20ml_DispensingVialCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6x_SmallSolutionBottleCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_6x_SmallSolutionBottleCarrier - type: pylabrobot - description: 6x_SmallSolutionBottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -4x_LargeSolutionBottleCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_4x_LargeSolutionBottleCarrier - type: pylabrobot - description: 4x_LargeSolutionBottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6x_LargeDispenseHeadCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_6x_LargeDispenseHeadCarrier - type: pylabrobot - description: 6x_LargeDispenseHeadCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -AdapterBlock: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_AdapterBlock - type: pylabrobot - description: AdapterBlock - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -TipBox: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_TipBox - type: pylabrobot - description: TipBox - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/bottles.yaml b/unilabos/registry/resources/bioyond/bottles.yaml deleted file mode 100644 index af6718a..0000000 --- a/unilabos/registry/resources/bioyond/bottles.yaml +++ /dev/null @@ -1,281 +0,0 @@ -Liquid_Vial: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Reagent_Bottle: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Solid_Stock: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solid_Stock - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Solid_Vial: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solid_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Solution_Beaker: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -100ml_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -High_Viscosity_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Large_Dispense_Head: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -5ml_Dispensing_Vial: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -20ml_Dispensing_Vial: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Small_Solution_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Large_Solution_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Pipette_Tip: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 - -YB_Liquid_Vial: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Reagent_Bottle: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Solid_Stock: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solid_Stock - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Solid_Vial: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solid_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Solution_Beaker: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_100ml_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_High_Viscosity_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Large_Dispense_Head: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_5ml_Dispensing_Vial: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_20ml_Dispensing_Vial: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Small_Solution_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Large_Solution_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Pipette_Tip: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 diff --git a/unilabos/resources/bioyond/bottle_carriers.py b/unilabos/resources/bioyond/YB_bottle_carriers.py similarity index 89% rename from unilabos/resources/bioyond/bottle_carriers.py rename to unilabos/resources/bioyond/YB_bottle_carriers.py index 76e3a93..a4a3601 100644 --- a/unilabos/resources/bioyond/bottle_carriers.py +++ b/unilabos/resources/bioyond/YB_bottle_carriers.py @@ -1,18 +1,9 @@ from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d from unilabos.resources.itemized_carrier import Bottle, BottleCarrier -from unilabos.resources.bioyond.bottles import ( - YB_Solid_Stock, - YB_Solid_Vial, - YB_Liquid_Vial, - YB_Solution_Beaker, - YB_Reagent_Bottle, - YB_5ml_Dispensing_Vial, - YB_20ml_Dispensing_Vial, - YB_Small_Solution_Bottle, - YB_Large_Solution_Bottle, - YB_Large_Dispense_Head, - YB_Pipette_Tip +from unilabos.resources.bioyond.YB_bottles import ( + YB_jia_yang_tou_da, + YB_ye_Bottle ) # 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial @@ -207,10 +198,9 @@ def YB_6VialCarrier(name: str) -> BottleCarrier: carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}") return carrier - +"""1瓶载架 - 单个中央位置""" def YB_1BottleCarrier(name: str) -> BottleCarrier: - """1瓶载架 - 单个中央位置""" - + # 载架尺寸 (mm) carrier_size_x = 127.8 carrier_size_y = 85.5 @@ -241,49 +231,13 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = YB_Reagent_Bottle(f"{name}_flask_1") - return carrier - - -def YB_1FlaskCarrier(name: str) -> BottleCarrier: - """1瓶载架 - 单个中央位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 20.0 - - # 烧杯尺寸 - beaker_diameter = 70.0 - - # 计算中央位置 - center_x = (carrier_size_x - beaker_diameter) / 2 - center_y = (carrier_size_y - beaker_diameter) / 2 - center_z = 5.0 - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=[Coordinate(center_x, center_y, center_z)], - resource_size_x=beaker_diameter, - resource_size_y=beaker_diameter, - name_prefix=name, - ), - model="1FlaskCarrier", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - carrier[0] = YB_Reagent_Bottle(f"{name}_bottle_1") + carrier[0] = YB_ye_Bottle(f"{name}_flask_1") return carrier +"""5ml分液瓶板 - 4x2布局,8个位置""" def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: - """5ml分液瓶板 - 4x2布局,8个位置""" + # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -331,9 +285,9 @@ def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: carrier[i] = YB_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") return carrier - +"""20ml分液瓶板 - 4x2布局,8个位置""" def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: - """20ml分液瓶板 - 4x2布局,8个位置""" + # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -381,9 +335,9 @@ def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: carrier[i] = YB_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") return carrier - +"""配液瓶(小)板 - 4x2布局,8个位置""" def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: - """配液瓶(小)板 - 4x2布局,8个位置""" + # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -481,10 +435,9 @@ def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: carrier[i] = YB_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}") return carrier - -def YB_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: - """加样头(大)板 - 1x1布局,1个位置""" - +"""加样头(大)板 - 1x1布局,1个位置""" +def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: + # 载架尺寸 (mm) carrier_size_x = 127.8 carrier_size_y = 85.5 @@ -526,7 +479,7 @@ def YB_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = YB_Large_Dispense_Head(f"{name}_head_1") + carrier[0] = YB_jia_yang_tou_da(f"{name}_head_1") return carrier diff --git a/unilabos/resources/bioyond/YB_bottles.py b/unilabos/resources/bioyond/YB_bottles.py new file mode 100644 index 0000000..f38dc36 --- /dev/null +++ b/unilabos/resources/bioyond/YB_bottles.py @@ -0,0 +1,38 @@ +from unilabos.resources.itemized_carrier import Bottle, BottleCarrier +# 工厂函数 + +"""加样头(大)""" +def YB_jia_yang_tou_da( + name: str, + diameter: float = 20.0, + height: float = 100.0, + max_volume: float = 30000.0, # 30mL + barcode: str = None, +) -> Bottle: + """创建粉末瓶""" + return Bottle( + name=name, + diameter=diameter,# 未知 + height=height, + max_volume=max_volume, + barcode=barcode, + model="Solid_Stock", + ) + +"""液1x1""" +def YB_ye_Bottle( + name: str, + diameter: float = 40.0, + height: float = 70.0, + max_volume: float = 50000.0, # 50mL + barcode: str = None, +) -> Bottle: + """创建液体瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="Liquid_Bottle", + ) diff --git a/unilabos/resources/bioyond/YB_warehouses.py b/unilabos/resources/bioyond/YB_warehouses.py new file mode 100644 index 0000000..c546759 --- /dev/null +++ b/unilabos/resources/bioyond/YB_warehouses.py @@ -0,0 +1,160 @@ +from unilabos.resources.warehouse import WareHouse, warehouse_factory + + +def bioyond_warehouse_1x4x4(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=1, + num_items_y=4, + num_items_z=4, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) + + +def bioyond_warehouse_1x4x2(name: str) -> WareHouse: + """创建BioYond 4x1x2仓库""" + return warehouse_factory( + name=name, + num_items_x=1, + num_items_y=4, + num_items_z=2, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + removed_positions=None + ) + # 定义benyond的堆栈 +def bioyond_warehouse_1x2x2(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=2, + num_items_y=2, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="YB_warehouse", + ) +def bioyond_warehouse_10x1x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=10, + num_items_y=1, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_1x3x3(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=1, + num_items_y=3, + num_items_z=3, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_2x1x3(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=2, + num_items_y=1, + num_items_z=3, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) + +def bioyond_warehouse_3x3x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=3, + num_items_y=3, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_5x1x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=5, + num_items_y=1, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=3, + num_items_y=3, + num_items_z=1, + dx=12.0, + dy=12.0, + dz=12.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) + +def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: + """创建BioYond开关盖加液模块台面""" + return warehouse_factory( + name=name, + num_items_x=2, + num_items_y=5, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + removed_positions=None + ) \ No newline at end of file diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index bca92c9..92fcf1e 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -636,6 +636,8 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st plr_material: ResourcePLR = initialize_resource( {"name": material["name"], "class": className}, resource_type=ResourcePLR ) + print("plr_material:",plr_material) + print("code:",material.get("code", "")) plr_material.code = material.get("code", "") and material.get("barCode", "") or "" plr_material.unilabos_uuid = str(uuid.uuid4())