From a90613f3de1e9c7f549c9260a389261a9d7bd461 Mon Sep 17 00:00:00 2001 From: ZiWei <131428629+ZiWei09@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:43:59 +0800 Subject: [PATCH] refactor: Move config from module to instance initialization --- .../workstation/bioyond_studio/bioyond_rpc.py | 12 +- .../workstation/bioyond_studio/config.py | 142 -------- .../bioyond_studio/config.py.deprecated | 329 ++++++++++++++++++ .../dispensing_station.py | 96 ++++- .../reaction_staion/reaction_station.py | 103 +++++- .../workstation/bioyond_studio/station.py | 79 +++-- .../devices/bioyond_dispensing_station.yaml | 9 +- 7 files changed, 560 insertions(+), 210 deletions(-) delete mode 100644 unilabos/devices/workstation/bioyond_studio/config.py create mode 100644 unilabos/devices/workstation/bioyond_studio/config.py.deprecated rename unilabos/devices/workstation/bioyond_studio/{dispending_station => dispensing_station}/dispensing_station.py (95%) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py b/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py index 7d95365..c365be7 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py @@ -9,7 +9,7 @@ from datetime import datetime, timezone from unilabos.device_comms.rpc import BaseRequest from typing import Optional, List, Dict, Any import json -from unilabos.devices.workstation.bioyond_studio.config import LOCATION_MAPPING + class SimpleLogger: @@ -49,6 +49,14 @@ class BioyondV1RPC(BaseRequest): self.config = config self.api_key = config["api_key"] self.host = config["api_host"] + + # 初始化 location_mapping + # 直接从 warehouse_mapping 构建,确保数据源所谓的单一和结构化 + self.location_mapping = {} + warehouse_mapping = self.config.get("warehouse_mapping", {}) + for warehouse_name, warehouse_config in warehouse_mapping.items(): + if "site_uuids" in warehouse_config: + self.location_mapping.update(warehouse_config["site_uuids"]) self._logger = SimpleLogger() self.material_cache = {} self._load_material_cache() @@ -318,7 +326,7 @@ class BioyondV1RPC(BaseRequest): def material_outbound(self, material_id: str, location_name: str, quantity: int) -> dict: """指定库位出库物料(通过库位名称)""" - location_id = LOCATION_MAPPING.get(location_name, location_name) + location_id = self.location_mapping.get(location_name, location_name) params = { "materialId": material_id, diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py deleted file mode 100644 index e06c413..0000000 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ /dev/null @@ -1,142 +0,0 @@ -# config.py -""" -配置文件 - 包含所有配置信息和映射关系 -""" - -# API配置 -API_CONFIG = { - "api_key": "", - "api_host": "" -} - -# 工作流映射配置 -WORKFLOW_MAPPINGS = { - "reactor_taken_out": "", - "reactor_taken_in": "", - "Solid_feeding_vials": "", - "Liquid_feeding_vials(non-titration)": "", - "Liquid_feeding_solvents": "", - "Liquid_feeding(titration)": "", - "liquid_feeding_beaker": "", - "Drip_back": "", -} - -# 工作流名称到DisplaySectionName的映射 -WORKFLOW_TO_SECTION_MAP = { - 'reactor_taken_in': '反应器放入', - 'liquid_feeding_beaker': '液体投料-烧杯', - 'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)', - 'Liquid_feeding_solvents': '液体投料-溶剂', - 'Solid_feeding_vials': '固体投料-小瓶', - 'Liquid_feeding(titration)': '液体投料-滴定', - 'reactor_taken_out': '反应器取出' -} - -# 库位映射配置 -WAREHOUSE_MAPPING = { - "粉末堆栈": { - "uuid": "", - "site_uuids": { - # 样品板 - "A1": "3a14198e-6929-31f0-8a22-0f98f72260df", - "A2": "3a14198e-6929-4379-affa-9a2935c17f99", - "A3": "3a14198e-6929-56da-9a1c-7f5fbd4ae8af", - "A4": "3a14198e-6929-5e99-2b79-80720f7cfb54", - "B1": "3a14198e-6929-f525-9a1b-1857552b28ee", - "B2": "3a14198e-6929-bf98-0fd5-26e1d68bf62d", - "B3": "3a14198e-6929-2d86-a468-602175a2b5aa", - "B4": "3a14198e-6929-1a98-ae57-e97660c489ad", - # 分装板 - "C1": "3a14198e-6929-46fe-841e-03dd753f1e4a", - "C2": "3a14198e-6929-1bc9-a9bd-3b7ca66e7f95", - "C3": "3a14198e-6929-72ac-32ce-9b50245682b8", - "C4": "3a14198e-6929-3bd8-e6c7-4a9fd93be118", - "D1": "3a14198e-6929-8a0b-b686-6f4a2955c4e2", - "D2": "3a14198e-6929-dde1-fc78-34a84b71afdf", - "D3": "3a14198e-6929-a0ec-5f15-c0f9f339f963", - "D4": "3a14198e-6929-7ac8-915a-fea51cb2e884" - } - }, - "溶液堆栈": { - "uuid": "", - "site_uuids": { - "A1": "3a14198e-d724-e036-afdc-2ae39a7f3383", - "A2": "3a14198e-d724-afa4-fc82-0ac8a9016791", - "A3": "3a14198e-d724-ca48-bb9e-7e85751e55b6", - "A4": "3a14198e-d724-df6d-5e32-5483b3cab583", - "B1": "3a14198e-d724-d818-6d4f-5725191a24b5", - "B2": "3a14198e-d724-be8a-5e0b-012675e195c6", - "B3": "3a14198e-d724-cc1e-5c2c-228a130f40a8", - "B4": "3a14198e-d724-1e28-c885-574c3df468d0", - "C1": "3a14198e-d724-b5bb-adf3-4c5a0da6fb31", - "C2": "3a14198e-d724-ab4e-48cb-817c3c146707", - "C3": "3a14198e-d724-7f18-1853-39d0c62e1d33", - "C4": "3a14198e-d724-28a2-a760-baa896f46b66", - "D1": "3a14198e-d724-d378-d266-2508a224a19f", - "D2": "3a14198e-d724-f56e-468b-0110a8feb36a", - "D3": "3a14198e-d724-0cf1-dea9-a1f40fe7e13c", - "D4": "3a14198e-d724-0ddd-9654-f9352a421de9" - } - }, - "试剂堆栈": { - "uuid": "", - "site_uuids": { - "A1": "3a14198c-c2cf-8b40-af28-b467808f1c36", - "A2": "3a14198c-c2d0-f3e7-871a-e470d144296f", - "A3": "3a14198c-c2d0-dc7d-b8d0-e1d88cee3094", - "A4": "3a14198c-c2d0-2070-efc8-44e245f10c6f", - "B1": "3a14198c-c2d0-354f-39ad-642e1a72fcb8", - "B2": "3a14198c-c2d0-1559-105d-0ea30682cab4", - "B3": "3a14198c-c2d0-725e-523d-34c037ac2440", - "B4": "3a14198c-c2d0-efce-0939-69ca5a7dfd39" - } - } -} - -# 物料类型配置 -MATERIAL_TYPE_MAPPINGS = { - "烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""), - "样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), -} - -# 步骤参数配置(各工作流的步骤UUID) -WORKFLOW_STEP_IDS = { - "reactor_taken_in": { - "config": "" - }, - "liquid_feeding_beaker": { - "liquid": "", - "observe": "" - }, - "liquid_feeding_vials_non_titration": { - "liquid": "", - "observe": "" - }, - "liquid_feeding_solvents": { - "liquid": "", - "observe": "" - }, - "solid_feeding_vials": { - "feeding": "", - "observe": "" - }, - "liquid_feeding_titration": { - "liquid": "", - "observe": "" - }, - "drip_back": { - "liquid": "", - "observe": "" - } -} - -LOCATION_MAPPING = {} - -ACTION_NAMES = {} - -HTTP_SERVICE_CONFIG = {} \ No newline at end of file diff --git a/unilabos/devices/workstation/bioyond_studio/config.py.deprecated b/unilabos/devices/workstation/bioyond_studio/config.py.deprecated new file mode 100644 index 0000000..cccd087 --- /dev/null +++ b/unilabos/devices/workstation/bioyond_studio/config.py.deprecated @@ -0,0 +1,329 @@ +# config.py +""" +Bioyond工作站配置文件 +包含API配置、工作流映射、物料类型映射、仓库库位映射等所有配置信息 +""" + +from unilabos.resources.bioyond.decks import BIOYOND_PolymerReactionStation_Deck + +# ============================================================================ +# 基础配置 +# ============================================================================ + +# API配置 +API_CONFIG = { + "api_key": "DE9BDDA0", + "api_host": "http://192.168.1.200:44402" +} + +# HTTP 报送服务配置 +HTTP_SERVICE_CONFIG = { + "http_service_host": "127.0.0.1", # 监听地址 + "http_service_port": 8080, # 监听端口 +} + +# Deck配置 - 反应站工作台配置 +DECK_CONFIG = BIOYOND_PolymerReactionStation_Deck(setup=True) + +# ============================================================================ +# 工作流配置 +# ============================================================================ + +# 工作流ID映射 +WORKFLOW_MAPPINGS = { + "reactor_taken_out": "3a16081e-4788-ca37-eff4-ceed8d7019d1", + "reactor_taken_in": "3a160df6-76b3-0957-9eb0-cb496d5721c6", + "Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6", + "Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47", + "Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046", + "Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6", + "liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784", + "Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a", +} + +# 工作流名称到显示名称的映射 +WORKFLOW_TO_SECTION_MAP = { + 'reactor_taken_in': '反应器放入', + 'reactor_taken_out': '反应器取出', + 'Solid_feeding_vials': '固体投料-小瓶', + 'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)', + 'Liquid_feeding_solvents': '液体投料-溶剂', + 'Liquid_feeding(titration)': '液体投料-滴定', + 'liquid_feeding_beaker': '液体投料-烧杯', + 'Drip_back': '液体回滴' +} + +# 工作流步骤ID配置 +WORKFLOW_STEP_IDS = { + "reactor_taken_in": { + "config": "60a06f85-c5b3-29eb-180f-4f62dd7e2154" + }, + "liquid_feeding_beaker": { + "liquid": "6808cda7-fee7-4092-97f0-5f9c2ffa60e3", + "observe": "1753c0de-dffc-4ee6-8458-805a2e227362" + }, + "liquid_feeding_vials_non_titration": { + "liquid": "62ea6e95-3d5d-43db-bc1e-9a1802673861", + "observe": "3a167d99-6172-b67b-5f22-a7892197142e" + }, + "liquid_feeding_solvents": { + "liquid": "1fcea355-2545-462b-b727-350b69a313bf", + "observe": "0553dfb3-9ac5-4ace-8e00-2f11029919a8" + }, + "solid_feeding_vials": { + "feeding": "f7ae7448-4f20-4c1d-8096-df6fbadd787a", + "observe": "263c7ed5-7277-426b-bdff-d6fbf77bcc05" + }, + "liquid_feeding_titration": { + "liquid": "a00ec41b-e666-4422-9c20-bfcd3cd15c54", + "observe": "ac738ff6-4c58-4155-87b1-d6f65a2c9ab5" + }, + "drip_back": { + "liquid": "371be86a-ab77-4769-83e5-54580547c48a", + "observe": "ce024b9d-bd20-47b8-9f78-ca5ce7f44cf1" + } +} + +# 工作流动作名称配置 +ACTION_NAMES = { + "reactor_taken_in": { + "config": "通量-配置", + "stirring": "反应模块-开始搅拌" + }, + "solid_feeding_vials": { + "feeding": "粉末加样模块-投料", + "observe": "反应模块-观察搅拌结果" + }, + "liquid_feeding_vials_non_titration": { + "liquid": "稀释液瓶加液位-液体投料", + "observe": "反应模块-滴定结果观察" + }, + "liquid_feeding_solvents": { + "liquid": "试剂AB放置位-试剂吸液分液", + "observe": "反应模块-观察搅拌结果" + }, + "liquid_feeding_titration": { + "liquid": "稀释液瓶加液位-稀释液吸液分液", + "observe": "反应模块-滴定结果观察" + }, + "liquid_feeding_beaker": { + "liquid": "烧杯溶液放置位-烧杯吸液分液", + "observe": "反应模块-观察搅拌结果" + }, + "drip_back": { + "liquid": "试剂AB放置位-试剂吸液分液", + "observe": "反应模块-向下滴定结果观察" + } +} + +# ============================================================================ +# 仓库配置 +# ============================================================================ +# 说明: +# - 出库和入库操作都需要UUID +WAREHOUSE_MAPPING = { + # ========== 反应站仓库 ========== + + # 堆栈1左 - 反应站左侧堆栈 (4行×4列=16个库位, A01~D04) + "堆栈1左": { + "uuid": "3a14aa17-0d49-dce4-486e-4b5c85c8b366", + "site_uuids": { + "A01": "3a14aa17-0d49-11d7-a6e1-f236b3e5e5a3", + "A02": "3a14aa17-0d49-4bc5-8836-517b75473f5f", + "A03": "3a14aa17-0d49-c2bc-6222-5cee8d2d94f8", + "A04": "3a14aa17-0d49-3ce2-8e9a-008c38d116fb", + "B01": "3a14aa17-0d49-f49c-6b66-b27f185a3b32", + "B02": "3a14aa17-0d49-cf46-df85-a979c9c9920c", + "B03": "3a14aa17-0d49-7698-4a23-f7ffb7d48ba3", + "B04": "3a14aa17-0d49-1231-99be-d5870e6478e9", + "C01": "3a14aa17-0d49-be34-6fae-4aed9d48b70b", + "C02": "3a14aa17-0d49-11d7-0897-34921dcf6b7c", + "C03": "3a14aa17-0d49-9840-0bd5-9c63c1bb2c29", + "C04": "3a14aa17-0d49-8335-3bff-01da69ea4911", + "D01": "3a14aa17-0d49-2bea-c8e5-2b32094935d5", + "D02": "3a14aa17-0d49-cff4-e9e8-5f5f0bc1ef32", + "D03": "3a14aa17-0d49-4948-cb0a-78f30d1ca9b8", + "D04": "3a14aa17-0d49-fd2f-9dfb-a29b11e84099", + }, + }, + + # 堆栈1右 - 反应站右侧堆栈 (4行×4列=16个库位, A05~D08) + "堆栈1右": { + "uuid": "3a14aa17-0d49-dce4-486e-4b5c85c8b366", + "site_uuids": { + "A05": "3a14aa17-0d49-2c61-edc8-72a8ca7192dd", + "A06": "3a14aa17-0d49-60c8-2b00-40b17198f397", + "A07": "3a14aa17-0d49-ec5b-0b75-634dce8eed25", + "A08": "3a14aa17-0d49-3ec9-55b3-f3189c4ec53d", + "B05": "3a14aa17-0d49-6a4e-abcf-4c113eaaeaad", + "B06": "3a14aa17-0d49-e3f6-2dd6-28c2e8194fbe", + "B07": "3a14aa17-0d49-11a6-b861-ee895121bf52", + "B08": "3a14aa17-0d49-9c7d-1145-d554a6e482f0", + "C05": "3a14aa17-0d49-45c4-7a34-5105bc3e2368", + "C06": "3a14aa17-0d49-867e-39ab-31b3fe9014be", + "C07": "3a14aa17-0d49-ec56-c4b4-39fd9b2131e7", + "C08": "3a14aa17-0d49-1128-d7d9-ffb1231c98c0", + "D05": "3a14aa17-0d49-e843-f961-ea173326a14b", + "D06": "3a14aa17-0d49-4d26-a985-f188359c4f8b", + "D07": "3a14aa17-0d49-223a-b520-bc092bb42fe0", + "D08": "3a14aa17-0d49-4fa3-401a-6a444e1cca22", + }, + }, + + # 站内试剂存放堆栈 + "站内试剂存放堆栈": { + "uuid": "3a14aa3b-9fab-9d8e-d1a7-828f01f51f0c", + "site_uuids": { + "A01": "3a14aa3b-9fab-adac-7b9c-e1ee446b51d5", + "A02": "3a14aa3b-9fab-ca72-febc-b7c304476c78" + } + }, + + + # 测量小瓶仓库(测密度) + "测量小瓶仓库": { + "uuid": "3a15012f-705b-c0de-3f9e-950c205f9921", + "site_uuids": { + "A01": "3a15012f-705e-0524-3161-c523b5aebc97", + "A02": "3a15012f-705e-7cd1-32ab-ad4fd1ab75c8", + "A03": "3a15012f-705e-a5d6-edac-bdbfec236260", + "B01": "3a15012f-705e-e0ee-80e0-10a6b3fc500d", + "B02": "3a15012f-705e-e499-180d-de06d60d0b21", + "B03": "3a15012f-705e-eff6-63f1-09f742096b26" + } + }, + + # 站内Tip盒堆栈 - 用于存放枪头盒 (耗材) + "站内Tip盒堆栈": { + "uuid": "3a14aa3a-2d3c-b5c1-9ddf-7c4a957d459a", + "site_uuids": { + "A01": "3a14aa3a-2d3d-e700-411a-0ddf85e1f18a", + "A02": "3a14aa3a-2d3d-a7ce-099a-d5632fdafa24", + "A03": "3a14aa3a-2d3d-bdf6-a702-c60b38b08501", + "B01": "3a14aa3a-2d3d-d704-f076-2a8d5bc72cb8", + "B02": "3a14aa3a-2d3d-c350-2526-0778d173a5ac", + "B03": "3a14aa3a-2d3d-bc38-b356-f0de2e44e0c7" + } + }, + # ========== 配液站仓库 ========== + "粉末堆栈": { + "uuid": "3a14198e-6928-121f-7ca6-88ad3ae7e6a0", + "site_uuids": { + "A01": "3a14198e-6929-31f0-8a22-0f98f72260df", + "A02": "3a14198e-6929-4379-affa-9a2935c17f99", + "A03": "3a14198e-6929-56da-9a1c-7f5fbd4ae8af", + "A04": "3a14198e-6929-5e99-2b79-80720f7cfb54", + "B01": "3a14198e-6929-f525-9a1b-1857552b28ee", + "B02": "3a14198e-6929-bf98-0fd5-26e1d68bf62d", + "B03": "3a14198e-6929-2d86-a468-602175a2b5aa", + "B04": "3a14198e-6929-1a98-ae57-e97660c489ad", + "C01": "3a14198e-6929-46fe-841e-03dd753f1e4a", + "C02": "3a14198e-6929-72ac-32ce-9b50245682b8", + "C03": "3a14198e-6929-8a0b-b686-6f4a2955c4e2", + "C04": "3a14198e-6929-a0ec-5f15-c0f9f339f963", + "D01": "3a14198e-6929-1bc9-a9bd-3b7ca66e7f95", + "D02": "3a14198e-6929-3bd8-e6c7-4a9fd93be118", + "D03": "3a14198e-6929-dde1-fc78-34a84b71afdf", + "D04": "3a14198e-6929-7ac8-915a-fea51cb2e884" + } + }, + "溶液堆栈": { + "uuid": "3a14198e-d723-2c13-7d12-50143e190a23", + "site_uuids": { + "A01": "3a14198e-d724-e036-afdc-2ae39a7f3383", + "A02": "3a14198e-d724-d818-6d4f-5725191a24b5", + "A03": "3a14198e-d724-b5bb-adf3-4c5a0da6fb31", + "A04": "3a14198e-d724-d378-d266-2508a224a19f", + "B01": "3a14198e-d724-afa4-fc82-0ac8a9016791", + "B02": "3a14198e-d724-be8a-5e0b-012675e195c6", + "B03": "3a14198e-d724-ab4e-48cb-817c3c146707", + "B04": "3a14198e-d724-f56e-468b-0110a8feb36a", + "C01": "3a14198e-d724-ca48-bb9e-7e85751e55b6", + "C02": "3a14198e-d724-cc1e-5c2c-228a130f40a8", + "C03": "3a14198e-d724-7f18-1853-39d0c62e1d33", + "C04": "3a14198e-d724-0cf1-dea9-a1f40fe7e13c", + "D01": "3a14198e-d724-df6d-5e32-5483b3cab583", + "D02": "3a14198e-d724-1e28-c885-574c3df468d0", + "D03": "3a14198e-d724-28a2-a760-baa896f46b66", + "D04": "3a14198e-d724-0ddd-9654-f9352a421de9" + } + }, + "试剂堆栈": { + "uuid": "3a14198c-c2cc-0290-e086-44a428fba248", + "site_uuids": { + "A01": "3a14198c-c2cf-8b40-af28-b467808f1c36", # x=1, y=1, code=0001-0001 + "A02": "3a14198c-c2d0-dc7d-b8d0-e1d88cee3094", # x=1, y=2, code=0001-0002 + "A03": "3a14198c-c2d0-354f-39ad-642e1a72fcb8", # x=1, y=3, code=0001-0003 + "A04": "3a14198c-c2d0-725e-523d-34c037ac2440", # x=1, y=4, code=0001-0004 + "B01": "3a14198c-c2d0-f3e7-871a-e470d144296f", # x=2, y=1, code=0001-0005 + "B02": "3a14198c-c2d0-2070-efc8-44e245f10c6f", # x=2, y=2, code=0001-0006 + "B03": "3a14198c-c2d0-1559-105d-0ea30682cab4", # x=2, y=3, code=0001-0007 + "B04": "3a14198c-c2d0-efce-0939-69ca5a7dfd39" # x=2, y=4, code=0001-0008 + } + } +} + +# ============================================================================ +# 物料类型配置 +# ============================================================================ +# 说明: +# - 格式: PyLabRobot资源类型名称 → Bioyond系统typeId的UUID +# - 这个映射基于 resource.model 属性 (不是显示名称!) +# - UUID为空表示该类型暂未在Bioyond系统中定义 +MATERIAL_TYPE_MAPPINGS = { + # ================================================配液站资源============================================================ + # ==================================================样品=============================================================== + "BIOYOND_PolymerStation_1FlaskCarrier": ("烧杯", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), # 配液站-样品-烧杯 + "BIOYOND_PolymerStation_1BottleCarrier": ("试剂瓶", "3a14196b-8bcf-a460-4f74-23f21ca79e72"), # 配液站-样品-试剂瓶 + "BIOYOND_PolymerStation_6StockCarrier": ("分装板", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), # 配液站-样品-分装板 + "BIOYOND_PolymerStation_Liquid_Vial": ("10%分装小瓶", "3a14196c-76be-2279-4e22-7310d69aed68"), # 配液站-样品-分装板-第一排小瓶 + "BIOYOND_PolymerStation_Solid_Vial": ("90%分装小瓶", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), # 配液站-样品-分装板-第二排小瓶 + # ==================================================试剂=============================================================== + "BIOYOND_PolymerStation_8StockCarrier": ("样品板", "3a14196e-b7a0-a5da-1931-35f3000281e9"), # 配液站-试剂-样品板(8孔) + "BIOYOND_PolymerStation_Solid_Stock": ("样品瓶", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), # 配液站-试剂-样品板-样品瓶 + +} + +# ============================================================================ +# 动态生成的库位UUID映射(从WAREHOUSE_MAPPING中提取) +# ============================================================================ + +LOCATION_MAPPING = {} +for warehouse_name, warehouse_config in WAREHOUSE_MAPPING.items(): + if "site_uuids" in warehouse_config: + LOCATION_MAPPING.update(warehouse_config["site_uuids"]) + +# ============================================================================ +# 物料默认参数配置 +# ============================================================================ +# 说明: +# - 为特定物料名称自动添加默认参数(如密度、分子量、单位等) +# - 格式: 物料名称 → {参数字典} +# - 在创建或更新物料时,会自动合并这些参数到 Parameters 字段 +# - unit: 物料的计量单位(会用于 unit 字段) +# - density/densityUnit: 密度信息(会添加到 Parameters 中) + +MATERIAL_DEFAULT_PARAMETERS = { + # 溶剂类 + "NMP": { + "unit": "毫升", + "density": "1.03", + "densityUnit": "g/mL", + "description": "N-甲基吡咯烷酮 (N-Methyl-2-pyrrolidone)" + }, + # 可以继续添加其他物料... +} + +# ============================================================================ +# 物料类型默认参数配置 +# ============================================================================ +# 说明: +# - 为特定物料类型(UUID)自动添加默认参数 +# - 格式: Bioyond类型UUID → {参数字典} +# - 优先级低于按名称匹配的配置 +MATERIAL_TYPE_PARAMETERS = { + # 示例: + # "3a14196b-24f2-ca49-9081-0cab8021bf1a": { # 烧杯 + # "unit": "个" + # } +} diff --git a/unilabos/devices/workstation/bioyond_studio/dispending_station/dispensing_station.py b/unilabos/devices/workstation/bioyond_studio/dispensing_station/dispensing_station.py similarity index 95% rename from unilabos/devices/workstation/bioyond_studio/dispending_station/dispensing_station.py rename to unilabos/devices/workstation/bioyond_studio/dispensing_station/dispensing_station.py index 94b3a76..dc48487 100644 --- a/unilabos/devices/workstation/bioyond_studio/dispending_station/dispensing_station.py +++ b/unilabos/devices/workstation/bioyond_studio/dispensing_station/dispensing_station.py @@ -5,7 +5,7 @@ from typing import Optional, Dict, Any, List from typing_extensions import TypedDict import requests import pint -from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG + from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import BioyondException from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation @@ -26,13 +26,89 @@ class ComputeExperimentDesignReturn(TypedDict): class BioyondDispensingStation(BioyondWorkstation): def __init__( self, - config, - # 桌子 - deck, - *args, + config: dict = None, + deck=None, + protocol_type=None, **kwargs, - ): - super().__init__(config, deck, *args, **kwargs) + ): + """初始化配液站 + + Args: + config: 配置字典,应包含material_type_mappings等配置 + deck: Deck对象 + protocol_type: 协议类型(由ROS系统传递,此处忽略) + **kwargs: 其他可能的参数 + """ + if config is None: + config = {} + + # 将 kwargs 合并到 config 中 (处理扁平化配置如 api_key) + config.update(kwargs) + + if deck is None and config: + deck = config.get('deck') + + # 🔧 修复: 确保 Deck 上的 warehouses 具有正确的 UUID (必须在 super().__init__ 之前执行,因为父类会触发同步) + # 从配置中读取 warehouse_mapping,并应用到实际的 deck 资源上 + if config and "warehouse_mapping" in config and deck: + warehouse_mapping = config["warehouse_mapping"] + print(f"正在根据配置更新 Deck warehouse UUIDs... (共有 {len(warehouse_mapping)} 个配置)") + + user_deck = deck + # 初始化 warehouses 字典 + if not hasattr(user_deck, "warehouses") or user_deck.warehouses is None: + user_deck.warehouses = {} + + # 1. 尝试从 children 中查找匹配的资源 + for child in user_deck.children: + # 简单判断: 如果名字在 mapping 中,就认为是 warehouse + if child.name in warehouse_mapping: + user_deck.warehouses[child.name] = child + print(f" - 从子资源中找到 warehouse: {child.name}") + + # 2. 如果还是没找到,且 Deck 类有 setup 方法,尝试调用 setup (针对 Deck 对象正确但未初始化的情况) + if not user_deck.warehouses and hasattr(user_deck, "setup"): + print(" - 尝试调用 deck.setup() 初始化仓库...") + try: + user_deck.setup() + # setup 后重新检查 + if hasattr(user_deck, "warehouses") and user_deck.warehouses: + print(f" - setup() 成功,找到 {len(user_deck.warehouses)} 个仓库") + except Exception as e: + print(f" - 调用 setup() 失败: {e}") + + # 3. 如果仍然为空,可能需要手动创建 (仅针对特定已知的 Deck 类型进行补救,这里暂时只打印警告) + if not user_deck.warehouses: + print(" - ⚠️ 仍然无法找到任何 warehouse 资源!") + + for wh_name, wh_config in warehouse_mapping.items(): + target_uuid = wh_config.get("uuid") + + # 尝试在 deck.warehouses 中查找 + wh_resource = None + if hasattr(user_deck, "warehouses") and wh_name in user_deck.warehouses: + wh_resource = user_deck.warehouses[wh_name] + + # 如果没找到,尝试在所有子资源中查找 + if not wh_resource: + wh_resource = user_deck.get_resource(wh_name) + + if wh_resource: + if target_uuid: + current_uuid = getattr(wh_resource, "uuid", None) + print(f"✅ 更新仓库 '{wh_name}' UUID: {current_uuid} -> {target_uuid}") + + # 动态添加 uuid 属性 + wh_resource.uuid = target_uuid + # 同时也确保 category 正确,避免 graphio 识别错误 + # wh_resource.category = "warehouse" + else: + print(f"⚠️ 仓库 '{wh_name}' 在配置中没有 UUID") + else: + print(f"❌ 在 Deck 中未找到配置的仓库: '{wh_name}'") + + super().__init__(bioyond_config=config, deck=deck) + # self.config = config # self.api_key = config["api_key"] # self.host = config["api_host"] @@ -90,7 +166,7 @@ class BioyondDispensingStation(BioyondWorkstation): dict: 服务端响应,失败时返回 {code:0,message,...} """ request_data = { - "apiKey": API_CONFIG["api_key"], + "apiKey": self.bioyond_config["api_key"], "requestTime": self.hardware_interface.get_current_time_iso8601(), "data": data } @@ -121,7 +197,7 @@ class BioyondDispensingStation(BioyondWorkstation): dict: 服务端响应,失败时返回 {code:0,message,...} """ request_data = { - "apiKey": API_CONFIG["api_key"], + "apiKey": self.bioyond_config["api_key"], "requestTime": self.hardware_interface.get_current_time_iso8601(), "data": data } @@ -1682,7 +1758,7 @@ class BioyondDispensingStation(BioyondWorkstation): f"开始执行批量物料转移: {len(transfer_groups)}组任务 -> {target_device_id}" ) - from .config import WAREHOUSE_MAPPING + warehouse_mapping = self.bioyond_config.get("warehouse_mapping", {}) results = [] successful_count = 0 failed_count = 0 diff --git a/unilabos/devices/workstation/bioyond_studio/reaction_staion/reaction_station.py b/unilabos/devices/workstation/bioyond_studio/reaction_staion/reaction_station.py index 6f1e926..be5560c 100644 --- a/unilabos/devices/workstation/bioyond_studio/reaction_staion/reaction_station.py +++ b/unilabos/devices/workstation/bioyond_studio/reaction_staion/reaction_station.py @@ -9,12 +9,8 @@ from datetime import datetime from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import MachineState from unilabos.ros.msgs.message_converter import convert_to_ros_msg, Float64, String -from unilabos.devices.workstation.bioyond_studio.config import ( - WORKFLOW_STEP_IDS, - WORKFLOW_TO_SECTION_MAP, - ACTION_NAMES -) -from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG + + class BioyondReactor: @@ -63,9 +59,72 @@ class BioyondReactionStation(BioyondWorkstation): protocol_type: 协议类型(由ROS系统传递,此处忽略) **kwargs: 其他可能的参数 """ + if config is None: + config = {} + + # 将 kwargs 合并到 config 中 (处理扁平化配置如 api_key) + config.update(kwargs) + if deck is None and config: deck = config.get('deck') + # 🔧 修复: 确保 Deck 上的 warehouses 具有正确的 UUID (必须在 super().__init__ 之前执行,因为父类会触发同步) + # 从配置中读取 warehouse_mapping,并应用到实际的 deck 资源上 + if config and "warehouse_mapping" in config and deck: + warehouse_mapping = config["warehouse_mapping"] + print(f"正在根据配置更新 Deck warehouse UUIDs... (共有 {len(warehouse_mapping)} 个配置)") + + user_deck = deck + # 初始化 warehouses 字典 + if not hasattr(user_deck, "warehouses") or user_deck.warehouses is None: + user_deck.warehouses = {} + + # 1. 尝试从 children 中查找匹配的资源 + for child in user_deck.children: + # 简单判断: 如果名字在 mapping 中,就认为是 warehouse + if child.name in warehouse_mapping: + user_deck.warehouses[child.name] = child + print(f" - 从子资源中找到 warehouse: {child.name}") + + # 2. 如果还是没找到,且 Deck 类有 setup 方法,尝试调用 setup (针对 Deck 对象正确但未初始化的情况) + if not user_deck.warehouses and hasattr(user_deck, "setup"): + print(" - 尝试调用 deck.setup() 初始化仓库...") + try: + user_deck.setup() + # setup 后重新检查 + if hasattr(user_deck, "warehouses") and user_deck.warehouses: + print(f" - setup() 成功,找到 {len(user_deck.warehouses)} 个仓库") + except Exception as e: + print(f" - 调用 setup() 失败: {e}") + + # 3. 如果仍然为空,可能需要手动创建 (仅针对特定已知的 Deck 类型进行补救,这里暂时只打印警告) + if not user_deck.warehouses: + print(" - ⚠️ 仍然无法找到任何 warehouse 资源!") + + for wh_name, wh_config in warehouse_mapping.items(): + target_uuid = wh_config.get("uuid") + + # 尝试在 deck.warehouses 中查找 + wh_resource = None + if hasattr(user_deck, "warehouses") and wh_name in user_deck.warehouses: + wh_resource = user_deck.warehouses[wh_name] + + # 如果没找到,尝试在所有子资源中查找 + if not wh_resource: + wh_resource = user_deck.get_resource(wh_name) + + if wh_resource: + if target_uuid: + current_uuid = getattr(wh_resource, "uuid", None) + print(f"✅ 更新仓库 '{wh_name}' UUID: {current_uuid} -> {target_uuid}") + wh_resource.uuid = target_uuid + else: + print(f"⚠️ 仓库 '{wh_name}' 在配置中没有 UUID") + else: + print(f"❌ 在 Deck 中未找到配置的仓库: '{wh_name}'") + + super().__init__(bioyond_config=config, deck=deck) + print(f"BioyondReactionStation初始化 - config包含workflow_mappings: {'workflow_mappings' in (config or {})}") if config and 'workflow_mappings' in config: print(f"workflow_mappings内容: {config['workflow_mappings']}") @@ -96,16 +155,19 @@ class BioyondReactionStation(BioyondWorkstation): # 动态获取工作流步骤ID self.workflow_step_ids = self._fetch_workflow_step_ids() + # 从配置中获取 action_names + self.action_names = self.bioyond_config.get("action_names", {}) + def _fetch_workflow_step_ids(self) -> Dict[str, Dict[str, str]]: """动态获取工作流步骤ID""" print("正在从LIMS获取最新工作流步骤ID...") - api_host = API_CONFIG.get("api_host") - api_key = API_CONFIG.get("api_key") + api_host = self.bioyond_config.get("api_host") + api_key = self.bioyond_config.get("api_key") if not api_host or not api_key: - print("API配置缺失,使用默认配置") - return WORKFLOW_STEP_IDS + print("API配置缺失,无法动态获取工作流步骤ID") + return {} def call_api(endpoint, data=None): url = f"{api_host}{endpoint}" @@ -124,8 +186,8 @@ class BioyondReactionStation(BioyondWorkstation): # 1. 获取工作流列表 resp = call_api("/api/lims/workflow/work-flow-list", {"type": 2, "includeDetail": True}) if not resp: - print("无法获取工作流列表,使用默认配置") - return WORKFLOW_STEP_IDS + print("无法获取工作流列表") + return {} workflows = resp.get("data", []) if isinstance(workflows, dict): @@ -135,13 +197,16 @@ class BioyondReactionStation(BioyondWorkstation): workflows = workflows["items"] if not workflows: - print("工作流列表为空,使用默认配置") - return WORKFLOW_STEP_IDS + print("工作流列表为空") + return {} new_ids = {} + #从配置中获取workflow_to_section_map + workflow_to_section_map = self.bioyond_config.get("workflow_to_section_map", {}) + # 2. 遍历映射表 - for internal_name, section_name in WORKFLOW_TO_SECTION_MAP.items(): + for internal_name, section_name in workflow_to_section_map.items(): # 查找对应的工作流对象 wf_obj = next((w for w in workflows if w.get("name") == section_name), None) if not wf_obj: @@ -1511,7 +1576,7 @@ class BioyondReactionStation(BioyondWorkstation): dict: 服务端响应,失败时返回 {code:0,message,...} """ request_data = { - "apiKey": API_CONFIG["api_key"], + "apiKey": self.bioyond_config["api_key"], "requestTime": self.hardware_interface.get_current_time_iso8601(), "data": data } @@ -1551,7 +1616,7 @@ class BioyondReactionStation(BioyondWorkstation): dict: 服务端响应,失败时返回 {code:0,message,...} """ request_data = { - "apiKey": API_CONFIG["api_key"], + "apiKey": self.bioyond_config["api_key"], "requestTime": self.hardware_interface.get_current_time_iso8601(), "data": data } @@ -1746,7 +1811,7 @@ class BioyondReactionStation(BioyondWorkstation): print(f"🕒 工作流名称已添加时间戳: {original_name} -> {data['name']}") request_data = { - "apiKey": API_CONFIG["api_key"], + "apiKey": self.bioyond_config["api_key"], "requestTime": self.hardware_interface.get_current_time_iso8601(), "data": data } @@ -1863,7 +1928,7 @@ class BioyondReactionStation(BioyondWorkstation): tcm_bs_list = [] if self.pending_time_constraints: print(f"\n🔗 处理时间约束 ({len(self.pending_time_constraints)} 个)...") - from unilabos.devices.workstation.bioyond_studio.config import WORKFLOW_STEP_IDS + # 建立索引到名称的映射 workflow_names_by_index = [w["name"] for w in workflows_result] diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index 5025f79..6f14390 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -24,9 +24,7 @@ from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode from unilabos.ros.msgs.message_converter import convert_to_ros_msg, Float64, String from pylabrobot.resources.resource import Resource as ResourcePLR -from unilabos.devices.workstation.bioyond_studio.config import ( - API_CONFIG, WORKFLOW_MAPPINGS, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, HTTP_SERVICE_CONFIG -) + from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService @@ -259,9 +257,8 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): else: logger.info(f"[同步→Bioyond] ➕ 物料不存在于 Bioyond,将创建新物料并入库") - # 第1步:获取仓库配置 - from .config import WAREHOUSE_MAPPING - warehouse_mapping = WAREHOUSE_MAPPING + # 第1步:从配置中获取仓库配置 + warehouse_mapping = self.bioyond_config.get("warehouse_mapping", {}) # 确定目标仓库名称 parent_name = None @@ -323,12 +320,13 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): # 第2步:转换为 Bioyond 格式 logger.info(f"[同步→Bioyond] 🔄 转换物料为 Bioyond 格式...") - # 导入物料默认参数配置 - from .config import MATERIAL_DEFAULT_PARAMETERS, MATERIAL_TYPE_PARAMETERS + # 从配置中获取物料默认参数 + material_default_params = self.workstation.bioyond_config.get("material_default_parameters", {}) + material_type_params = self.workstation.bioyond_config.get("material_type_parameters", {}) # 合并参数配置:物料名称参数 + typeId参数(转换为 type: 格式) - merged_params = MATERIAL_DEFAULT_PARAMETERS.copy() - for type_id, params in MATERIAL_TYPE_PARAMETERS.items(): + merged_params = material_default_params.copy() + for type_id, params in material_type_params.items(): merged_params[f"type:{type_id}"] = params bioyond_material = resource_plr_to_bioyond( @@ -558,11 +556,13 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): return material_bioyond_id # 转换为 Bioyond 格式 - from .config import MATERIAL_DEFAULT_PARAMETERS, MATERIAL_TYPE_PARAMETERS + # 从配置中获取物料默认参数 + material_default_params = self.workstation.bioyond_config.get("material_default_parameters", {}) + material_type_params = self.workstation.bioyond_config.get("material_type_parameters", {}) # 合并参数配置:物料名称参数 + typeId参数(转换为 type: 格式) - merged_params = MATERIAL_DEFAULT_PARAMETERS.copy() - for type_id, params in MATERIAL_TYPE_PARAMETERS.items(): + merged_params = material_default_params.copy() + for type_id, params in material_type_params.items(): merged_params[f"type:{type_id}"] = params bioyond_material = resource_plr_to_bioyond( @@ -623,8 +623,7 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): logger.info(f"[物料入库] 目标库位: {update_site}") # 获取仓库配置和目标库位 UUID - from .config import WAREHOUSE_MAPPING - warehouse_mapping = WAREHOUSE_MAPPING + warehouse_mapping = self.workstation.bioyond_config.get("warehouse_mapping", {}) parent_name = None target_location_uuid = None @@ -738,10 +737,28 @@ class BioyondWorkstation(WorkstationBase): raise ValueError("Deck 配置不能为空,请在配置文件中添加正确的 deck 配置") # 初始化 warehouses 属性 - self.deck.warehouses = {} - for resource in self.deck.children: - if isinstance(resource, WareHouse): - self.deck.warehouses[resource.name] = resource + if not hasattr(self.deck, "warehouses") or self.deck.warehouses is None: + self.deck.warehouses = {} + + # 仅当 warehouses 为空时尝试重新扫描(避免覆盖子类的修复) + if not self.deck.warehouses: + for resource in self.deck.children: + # 兼容性增强: 只要是仓库类别或者是 WareHouse 实例均可 + is_warehouse = isinstance(resource, WareHouse) or getattr(resource, "category", "") == "warehouse" + + # 如果配置中有定义,也可以认定为 warehouse + if not is_warehouse and "warehouse_mapping" in bioyond_config: + if resource.name in bioyond_config["warehouse_mapping"]: + is_warehouse = True + + if is_warehouse: + self.deck.warehouses[resource.name] = resource + # 确保 category 被正确设置,方便后续使用 + if getattr(resource, "category", "") != "warehouse": + try: + resource.category = "warehouse" + except: + pass # 创建通信模块 self._create_communication_module(bioyond_config) @@ -760,10 +777,11 @@ class BioyondWorkstation(WorkstationBase): self._set_workflow_mappings(bioyond_config["workflow_mappings"]) # 准备 HTTP 报送接收服务配置(延迟到 post_init 启动) - # 从 bioyond_config 中获取,如果没有则使用 HTTP_SERVICE_CONFIG 的默认值 + # 从 bioyond_config 中的 http_service_config 获取 + http_service_cfg = bioyond_config.get("http_service_config", {}) self._http_service_config = { - "host": bioyond_config.get("http_service_host", HTTP_SERVICE_CONFIG["http_service_host"]), - "port": bioyond_config.get("http_service_port", HTTP_SERVICE_CONFIG["http_service_port"]) + "host": http_service_cfg.get("http_service_host", "127.0.0.1"), + "port": http_service_cfg.get("http_service_port", 8080) } self.http_service = None # 将在 post_init 启动 self.connection_monitor = None # 将在 post_init 启动 @@ -831,19 +849,14 @@ class BioyondWorkstation(WorkstationBase): def _create_communication_module(self, config: Optional[Dict[str, Any]] = None) -> None: """创建Bioyond通信模块""" - # 创建默认配置 - default_config = { - **API_CONFIG, - "workflow_mappings": WORKFLOW_MAPPINGS, - "material_type_mappings": MATERIAL_TYPE_MAPPINGS, - "warehouse_mapping": WAREHOUSE_MAPPING - } - - # 如果传入了 config,合并配置(config 中的值会覆盖默认值) + # 直接使用传入的配置,不再使用默认值 + # 所有配置必须从 JSON 文件中提供 if config: - self.bioyond_config = {**default_config, **config} + self.bioyond_config = config else: - self.bioyond_config = default_config + # 如果没有配置,使用空字典(会导致后续错误,但这是预期的) + self.bioyond_config = {} + print("警告: 未提供 bioyond_config,请确保在 JSON 配置文件中提供完整配置") self.hardware_interface = BioyondV1RPC(self.bioyond_config) diff --git a/unilabos/registry/devices/bioyond_dispensing_station.yaml b/unilabos/registry/devices/bioyond_dispensing_station.yaml index daf6d2b..97b55cc 100644 --- a/unilabos/registry/devices/bioyond_dispensing_station.yaml +++ b/unilabos/registry/devices/bioyond_dispensing_station.yaml @@ -652,15 +652,16 @@ bioyond_dispensing_station: config: properties: config: - type: string + type: object deck: type: string - required: - - config - - deck + protocol_type: + type: string + required: [] type: object data: properties: {} required: [] type: object + model: {} version: 1.0.0