Skip to content

Conversation

@ZiWei09
Copy link
Collaborator

@ZiWei09 ZiWei09 commented Oct 30, 2025

This pull request introduces significant improvements to the integration between the UniLabOS system and the Bioyond workstation, focusing on more robust material synchronization, enhanced error handling, and improved configuration mapping. The changes affect both the configuration files and the core synchronization logic, making the system more reliable and easier to debug.

Enhancements to Bioyond integration and material synchronization:

  • Material Synchronization Logic:
    The sync_from_external and sync_to_external methods in station.py now handle both sample and reagent types by querying Bioyond for both typeMode=1 and typeMode=2, combining results for more complete material data synchronization. Logging has been improved for better traceability. [1] [2]

  • Robust Material Inbound Workflow:
    The material inbound process now checks for location occupancy, verifies that the target location is available, and confirms successful storage by re-querying Bioyond. It also distinguishes between creating new materials and using existing ones, with detailed logging and error handling throughout the process.

Configuration and mapping improvements:

  • Material Type Mapping Updates:
    The configuration files for both dispensing and reaction stations have been updated to use more explicit and consistent material type mappings, switching from Chinese names to standardized Bioyond identifiers, and including UUIDs for each type. [1] [2]

  • Addition of New Experiment Configuration:
    A new experiment configuration file, ICCAS506.json, has been added, defining nodes and device/deck relationships for Bioyond dispensing and reaction stations, with detailed mappings and setup information.

Error handling and logging:

  • Improved Exception Management:
    Exception handling in synchronization methods has been refined to ensure errors are logged clearly, and stack traces are printed only when necessary, facilitating easier debugging and maintenance. [1] [2]

Summary by Sourcery

Add station-specific bottle and carrier definitions, revise warehouse layout handling, and enhance Bioyond synchronization with improved error handling, logging, and configuration mapping.

New Features:

  • Add dispensing and reaction station bottle and carrier factory functions (e.g., 8-stock, 6-vial, reagent and flask carriers, and tip boxes).
  • Introduce configurable row-major/column-major warehouse_factory layouts and a new reagent stack warehouse.
  • Implement backward-compatible wrappers for PolymerStation carriers.
  • Extend station synchronization to query both sample (typeMode=1) and reagent (typeMode=2) materials, check location occupancy, and re-verify inbound storage.
  • Add resource_tree_add and resource_tree_transfer methods to handle incremental and transfer synchronization to Bioyond.

Enhancements:

  • Replace print statements with logger calls and enrich logging across sync methods and graphio conversions.
  • Improve resource_bioyond_to_plr and resource_plr_to_bioyond mappings with reverse lookup, unique material naming, robust typeId validation, and fixed coordinate translations.
  • Update bottle_carriers.yaml and bottles.yaml registries to include new resources and maintain backward compatibility.

Documentation:

  • Add new experiment configuration file ICCAS506.json for Bioyond workstation integration.

…efinitions

- Removed traceback printing in error handling for Bioyond synchronization.
- Enhanced logging for existing Bioyond material ID usage during synchronization.
- Added new bottle carrier definitions for single flask and updated existing ones.
- Refactored dispensing station and reaction station bottle definitions for clarity and consistency.
- Improved resource mapping and error handling in graphio for Bioyond resource conversion.
- Introduced layout parameter in warehouse factory for better warehouse configuration.
@sourcery-ai
Copy link

sourcery-ai bot commented Oct 30, 2025

Reviewer's Guide

This PR deeply refactors the Bioyond integration by extending synchronization logic to handle both sample and reagent types with a robust inbound workflow and unified configuration merging; expands dispensing/reaction station resource definitions with new carriers and containers plus registry entries; adds flexible warehouse ordering (row/col-major) and new reagent stacks; enriches PLR↔Bioyond mapping with reverse lookups, unique naming, and detail inference; standardizes logging and error handling across modules; and includes a new ICCAS506 experiment configuration.

Sequence diagram for improved material synchronization between UniLabOS and Bioyond

sequenceDiagram
    participant UniLabOS
    participant BioyondAPI
    UniLabOS->>BioyondAPI: Query stock_material(typeMode=1)
    BioyondAPI-->>UniLabOS: Return sample type materials
    UniLabOS->>BioyondAPI: Query stock_material(typeMode=2)
    BioyondAPI-->>UniLabOS: Return reagent type materials
    UniLabOS->>UniLabOS: Combine results
    UniLabOS->>UniLabOS: Convert to PLR format
    UniLabOS->>UniLabOS: Log results and errors
Loading

Sequence diagram for robust material inbound workflow

sequenceDiagram
    participant UniLabOS
    participant BioyondAPI
    UniLabOS->>BioyondAPI: Check if material exists
    alt Material exists
        UniLabOS->>BioyondAPI: Move material to target location
    else Material does not exist
        UniLabOS->>BioyondAPI: Add new material
        BioyondAPI-->>UniLabOS: Return material ID
    end
    UniLabOS->>BioyondAPI: Check if target location is occupied
    alt Location occupied by same material
        UniLabOS->>UniLabOS: Skip inbound
    else Location occupied by other material
        UniLabOS->>UniLabOS: Abort inbound
    else Location available
        UniLabOS->>BioyondAPI: Inbound material to location
        BioyondAPI-->>UniLabOS: Confirm inbound
        UniLabOS->>BioyondAPI: Re-query to verify location
    end
Loading

ER diagram for material type mapping and registry updates

erDiagram
    MATERIAL_TYPE_MAPPINGS {
      model string
      display_name string
      uuid string
    }
    BOTTLE_CARRIERS {
      name string
      model string
      registry_type string
    }
    BOTTLES {
      name string
      model string
      registry_type string
    }
    MATERIAL_TYPE_MAPPINGS ||--o{ BOTTLE_CARRIERS : maps
    MATERIAL_TYPE_MAPPINGS ||--o{ BOTTLES : maps
    BOTTLE_CARRIERS ||--o{ BOTTLES : contains
Loading

Class diagram for new and updated bottle carriers and containers

classDiagram
    class BottleCarrier {
      +name: str
      +size_x: float
      +size_y: float
      +size_z: float
      +sites: dict
      +model: str
      +num_items_x: int
      +num_items_y: int
      +num_items_z: int
    }
    class Bottle {
      +name: str
      +diameter: float
      +height: float
      +max_volume: float
      +barcode: str
      +model: str
    }
    class BIOYOND_DispensingStation_8StockCarrier {
      +BottleCarrier
    }
    class BIOYOND_DispensingStation_6VialCarrier {
      +BottleCarrier
    }
    class BIOYOND_DispensingStation_1BottleCarrier {
      +BottleCarrier
    }
    class BIOYOND_DispensingStation_1FlaskCarrier {
      +BottleCarrier
    }
    class BIOYOND_ReactionStation_6StockCarrier {
      +BottleCarrier
    }
    class BIOYOND_ReactionStation_1BottleCarrier {
      +BottleCarrier
    }
    class BIOYOND_ReactionStation_1FlaskCarrier {
      +BottleCarrier
    }
    class BIOYOND_DispensingStation_Solid_Stock {
      +Bottle
    }
    class BIOYOND_DispensingStation_Solid_Vial {
      +Bottle
    }
    class BIOYOND_DispensingStation_Liquid_Vial {
      +Bottle
    }
    class BIOYOND_DispensingStation_Reagent_Bottle {
      +Bottle
    }
    class BIOYOND_ReactionStation_Reactor {
      +Bottle
    }
    class BIOYOND_ReactionStation_Solid_Vial {
      +Bottle
    }
    class BIOYOND_ReactionStation_Liquid_Vial {
      +Bottle
    }
    class BIOYOND_ReactionStation_Reagent_Bottle {
      +Bottle
    }
    class BIOYOND_ReactionStation_Flask {
      +Bottle
    }
    BIOYOND_DispensingStation_8StockCarrier --|> BottleCarrier
    BIOYOND_DispensingStation_6VialCarrier --|> BottleCarrier
    BIOYOND_DispensingStation_1BottleCarrier --|> BottleCarrier
    BIOYOND_DispensingStation_1FlaskCarrier --|> BottleCarrier
    BIOYOND_ReactionStation_6StockCarrier --|> BottleCarrier
    BIOYOND_ReactionStation_1BottleCarrier --|> BottleCarrier
    BIOYOND_ReactionStation_1FlaskCarrier --|> BottleCarrier
    BIOYOND_DispensingStation_Solid_Stock --|> Bottle
    BIOYOND_DispensingStation_Solid_Vial --|> Bottle
    BIOYOND_DispensingStation_Liquid_Vial --|> Bottle
    BIOYOND_DispensingStation_Reagent_Bottle --|> Bottle
    BIOYOND_ReactionStation_Reactor --|> Bottle
    BIOYOND_ReactionStation_Solid_Vial --|> Bottle
    BIOYOND_ReactionStation_Liquid_Vial --|> Bottle
    BIOYOND_ReactionStation_Reagent_Bottle --|> Bottle
    BIOYOND_ReactionStation_Flask --|> Bottle
Loading

Class diagram for WareHouse with ordering_layout

classDiagram
    class WareHouse {
      +name: str
      +size_x: float
      +size_y: float
      +size_z: float
      +num_items_x: int
      +num_items_y: int
      +num_items_z: int
      +sites: dict
      +ordering_layout: str
      +serialize()
    }
Loading

File-Level Changes

Change Details Files
Overhaul of sync_from_external and sync_to_external workflows
  • Query both typeMode=1 and typeMode=2 and merge results
  • Implement multi-step inbound in sync_to_external (ID reuse, occupancy check, re-query for verification)
  • Merge default and custom Bioyond configs; refine exception handling and logging
  • Iterate resources in resource_tree_add with ID check; hook resource_tree_transfer
unilabos/devices/workstation/bioyond_studio/station.py
unilabos/ros/nodes/base_device_node.py
Extended bottle carrier definitions for dispensing and reaction stations
  • Add DispensingStation_* and ReactionStation_* carrier factory functions with correct sizes and ordering
  • Rename and deprecate old PolymerStation_* wrappers for backward compatibility
  • Adjust model strings and carrier layouts (2×4, 2×3, 1×1)
unilabos/resources/bioyond/bottle_carriers.py
unilabos/registry/resources/bioyond/bottle_carriers.yaml
Added new container factories for dispensing/reaction stations
  • Implement Bottle and TipBox factories for DispensingStation and ReactionStation
  • Define reactor, vial, flask, tip box models with barcode support
  • Update registry entries for all new container types
unilabos/resources/bioyond/bottles.py
unilabos/registry/resources/bioyond/bottles.yaml
Flexible warehouse ordering and new reagent stacks
  • Introduce layout parameter and ordering_layout attribute (row-major vs col-major) in warehouse_factory and WareHouse
  • Add bioyond_warehouse_reagent_stack and update decks setup
  • Serialize ordering_layout and adjust coordinate calculations
unilabos/resources/warehouse.py
unilabos/resources/bioyond/warehouses.py
unilabos/resources/bioyond/decks.py
Enhanced PLR↔Bioyond resource mapping in graphio
  • Build reverse TypeName→(model,UUID) mapping and log entries
  • Assign unique suffixes to same-name materials
  • Infer detail types for sample plates and validate indices
  • Streamline resource_plr_to_bioyond typeId lookups with error reports
unilabos/resources/graphio.py
Unified logging and improved error handling
  • Replace print statements with logger.warning/debug
  • Remove raw traceback prints or wrap them under controlled logging
  • Add contextual log messages throughout sync and mapping code
unilabos/devices/workstation/bioyond_studio/station.py
unilabos/resources/graphio.py
Addition of new ICCAS506 experiment configuration
  • Create ICCAS506.json defining nodes and device/deck relationships
test/experiments/ICCAS506.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The sync_to_external method is very large and deeply nested—consider breaking it into smaller helper functions (e.g. for location resolution, inbound checks, creation vs. move) to improve readability and maintainability.
  • You’re querying stock_material twice in both sync methods; extract that logic (fetching typeMode 1 and 2 and merging results) into a shared helper to reduce duplication and ensure consistency.
  • Instead of manual traceback.print_exc() and inline imports in exception blocks, use logger.exception() (or logger.error with exc_info) to capture stack traces consistently through your logging framework.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The sync_to_external method is very large and deeply nested—consider breaking it into smaller helper functions (e.g. for location resolution, inbound checks, creation vs. move) to improve readability and maintainability.
- You’re querying stock_material twice in both sync methods; extract that logic (fetching typeMode 1 and 2 and merging results) into a shared helper to reduce duplication and ensure consistency.
- Instead of manual traceback.print_exc() and inline imports in exception blocks, use logger.exception() (or logger.error with exc_info) to capture stack traces consistently through your logging framework.

## Individual Comments

### Comment 1
<location> `unilabos/resources/warehouse.py:27-30` </location>
<code_context>
     model: Optional[str] = None,
-    col_offset: int = 0,  # 新增:列起始偏移量,用于生成A05-D08等命名
+    col_offset: int = 0,  # 列起始偏移量,用于生成A05-D08等命名
+    layout: str = "col-major",  # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
 ):
-    # 创建16个板架位 (4层 x 4位置)
</code_context>

<issue_to_address>
**suggestion:** Consider validating the 'layout' parameter to ensure only supported values are used.

Adding explicit validation for 'layout' will prevent unsupported values and reduce the risk of configuration errors.

```suggestion
    layout: str = "col-major",  # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
):
    # 验证 layout 参数
    if layout not in ("col-major", "row-major"):
        raise ValueError(f"Unsupported layout: {layout}. Supported values are 'col-major' and 'row-major'.")
    # 创建位置坐标
    locations = []
```
</issue_to_address>

### Comment 2
<location> `unilabos/resources/graphio.py:632-635` </location>
<code_context>
     """
     plr_materials = []

+    # 创建反向映射: {显示名称: (model, UUID)} -> 用于从 Bioyond typeName 查找 model
+    # 如果 type_mapping 的 key 已经是显示名称,则直接使用;否则创建反向映射
+    reverse_type_mapping = {}
+    for key, value in type_mapping.items():
+        # value 可能是 tuple 或 list: (显示名称, UUID) 或 [显示名称, UUID]
+        display_name = value[0] if isinstance(value, (tuple, list)) and len(value) >= 1 else None
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Reverse mapping logic assumes display names are unique, which may not always be true.

Currently, duplicate display names in type_mapping will cause only the first entry to be retained in the reverse mapping. Please add a check to detect duplicates and raise a warning or error if found.
</issue_to_address>

### Comment 3
<location> `unilabos/resources/graphio.py:875-877` </location>
<code_context>
+            # 单个瓶子(非载架)类型的资源
             bottle = resource[0] if resource.capacity > 0 else resource
+
+            # 根据 resource.model 从 type_mapping 获取正确的 typeId
+            type_info = type_mapping.get(resource.model)
+            if type_info:
+                type_id = type_info[1]
+            else:
</code_context>

<issue_to_address>
**issue (bug_risk):** Defaulting to a hardcoded typeId for unmapped models may lead to incorrect data.

Using a default typeId when a model is missing from type_mapping risks misclassification. It would be safer to raise an error or enforce explicit mappings for all resource types.
</issue_to_address>

### Comment 4
<location> `unilabos/devices/workstation/bioyond_studio/station.py:457-466` </location>
<code_context>
         """
-        self.resource_synchronizer.sync_to_external(resources)
+        logger.info(f"[resource_tree_add] 开始同步 {len(resources)} 个资源到 Bioyond 系统")
+        for resource in resources:
+            try:
+                # 🔍 检查资源是否已有 Bioyond ID (避免重复入库)
+                extra_info = getattr(resource, "unilabos_extra", {})
+                material_bioyond_id = extra_info.get("material_bioyond_id")
+
+                if material_bioyond_id:
+                    logger.info(f"⏭️ [resource_tree_add] 跳过资源 {resource.name}: 已有 Bioyond ID ({material_bioyond_id[:8]}...),可能由 transfer 已处理")
+                    continue
+
+                logger.info(f"[resource_tree_add] 同步资源: {resource}")
+                self.resource_synchronizer.sync_to_external(resource)
+            except Exception as e:
+                logger.error(f"[resource_tree_add] 同步资源失败 {resource}: {e}")
</code_context>

<issue_to_address>
**issue (bug_risk):** Iterating over resources and skipping those with Bioyond ID may miss updates for moved resources.

Please ensure that resources with existing Bioyond IDs are checked for location changes and updated in Bioyond if necessary.
</issue_to_address>

### Comment 5
<location> `unilabos/ros/nodes/base_device_node.py:611-619` </location>
<code_context>
                         f"物料{plr_resource}请求挂载到{parent_resource},额外参数:{additional_params}"
                     )
+
+                    # ⭐ assign 之前,需要从 resources 列表中移除
+                    # 因为资源将不再是顶级资源,而是成为 parent_resource 的子资源
+                    # 如果不移除,figure_resource 会找到两次:一次在 resources,一次在 parent 的 children
+                    resource_id = id(plr_resource)
+                    for i, r in enumerate(self.resource_tracker.resources):
+                        if id(r) == resource_id:
+                            self.resource_tracker.resources.pop(i)
+                            self.lab_logger().debug(f"从顶级资源列表中移除 {plr_resource.name}(即将成为 {parent_resource.name} 的子资源)")
+                            break
+
                     parent_resource.assign_child_resource(
</code_context>

<issue_to_address>
**suggestion:** Removing resources by id may not handle all references or edge cases.

Removing a resource from the top-level list may not update other references, such as those in trackers or caches, potentially causing inconsistencies. Please review all places where the resource might be referenced and ensure proper cleanup.

Suggested implementation:

```python
                    resource_id = id(plr_resource)
                    for i, r in enumerate(self.resource_tracker.resources):
                        if id(r) == resource_id:
                            self.resource_tracker.resources.pop(i)
                            self.lab_logger().debug(f"从顶级资源列表中移除 {plr_resource.name}(即将成为 {parent_resource.name} 的子资源)")
                            # 清理 resource_tracker 相关引用
                            if hasattr(self.resource_tracker, "resource_cache"):
                                # 假设 resource_cache 是以 id 为 key 的 dict
                                if resource_id in self.resource_tracker.resource_cache:
                                    del self.resource_tracker.resource_cache[resource_id]
                                    self.lab_logger().debug(f"从 resource_cache 中移除 {plr_resource.name}")
                            # 如果有其他 tracker/caches,按需清理
                            # TODO: 如有其他引用结构,请在此处补充清理逻辑
                            break

```

请检查 `resource_tracker` 是否还有其他引用资源的结构(如 set、list、dict 等),并在上述代码块中补充相应的清理逻辑。如果有自定义缓存或索引,请确保同步移除相关条目,以避免悬挂引用和数据不一致。
</issue_to_address>

### Comment 6
<location> `unilabos/devices/workstation/bioyond_studio/station.py:98` </location>
<code_context>
    def sync_to_external(self, resource: Any) -> bool:
        """将本地物料数据变更同步到Bioyond系统"""
        try:
            # ✅ 跳过仓库类型的资源 - 仓库是容器,不是物料
            resource_category = getattr(resource, "category", None)
            if resource_category == "warehouse":
                logger.debug(f"[同步→Bioyond] 跳过仓库类型资源: {resource.name} (仓库是容器,不需要同步为物料)")
                return True

            logger.info(f"[同步→Bioyond] 收到物料变更: {resource.name}")

            # 获取物料的 Bioyond ID
            extra_info = getattr(resource, "unilabos_extra", {})
            material_bioyond_id = extra_info.get("material_bioyond_id")

            # ⭐ 如果没有 Bioyond ID,尝试从 Bioyond 系统中按名称查询
            if not material_bioyond_id:
                logger.warning(f"[同步→Bioyond] 物料 {resource.name} 没有 Bioyond ID,尝试按名称查询...")
                try:
                    # 查询所有类型的物料:0=耗材, 1=样品, 2=试剂
                    import json
                    all_materials = []

                    for type_mode in [0, 1, 2]:
                        query_params = json.dumps({
                            "typeMode": type_mode,
                            "filter": "",   # 空字符串表示查询所有
                            "includeDetail": True
                        })
                        materials = self.bioyond_api_client.stock_material(query_params)
                        if materials:
                            all_materials.extend(materials)

                    logger.info(f"[同步→Bioyond] 查询到 {len(all_materials)} 个物料")

                    # 按名称匹配
                    for mat in all_materials:
                        if mat.get("name") == resource.name:
                            material_bioyond_id = mat.get("id")
                            mat_type = mat.get("typeName", "未知")
                            logger.info(f"✅ 找到物料 {resource.name} ({mat_type}) 的 Bioyond ID: {material_bioyond_id[:8]}...")
                            # 保存 ID 到资源对象
                            extra_info["material_bioyond_id"] = material_bioyond_id
                            setattr(resource, "unilabos_extra", extra_info)
                            break

                    if not material_bioyond_id:
                        logger.warning(f"⚠️ 在 Bioyond 系统中未找到名为 {resource.name} 的物料")
                        logger.info(f"[同步→Bioyond] 这是一个新物料,将创建并入库到 Bioyond 系统")
                        # 不返回,继续执行后续的创建+入库流程
                except Exception as e:
                    logger.error(f"查询 Bioyond 物料失败: {e}")
                    return False

            # 检查是否有位置更新请求
            update_site = extra_info.get("update_resource_site")

            if not update_site:
                logger.debug(f"[同步→Bioyond] 物料 {resource.name} 无位置更新请求,跳过同步")
                return True

            # ===== 物料移动/创建流程 =====
            logger.info(f"[同步→Bioyond] 📍 物料 {resource.name} 目标库位: {update_site}")

            if material_bioyond_id:
                logger.info(f"[同步→Bioyond] 🔄 物料已存在于 Bioyond (ID: {material_bioyond_id[:8]}...),执行移动操作")
            else:
                logger.info(f"[同步→Bioyond] ➕ 物料不存在于 Bioyond,将创建新物料并入库")

            # 第1步:获取仓库配置
            from .config import WAREHOUSE_MAPPING
            warehouse_mapping = WAREHOUSE_MAPPING

            # 确定目标仓库名称(优先使用 resource.parent.name)
            parent_name = None
            target_location_uuid = None

            # 如果资源有父节点,优先使用父节点名称
            if resource.parent is not None:
                parent_name = resource.parent.name
                logger.info(f"[同步→Bioyond] 从资源父节点获取仓库名称: {parent_name}")

                # 检查该仓库是否在配置中
                if parent_name in warehouse_mapping:
                    site_uuids = warehouse_mapping[parent_name].get("site_uuids", {})
                    if update_site in site_uuids:
                        target_location_uuid = site_uuids[update_site]
                        logger.info(f"[同步→Bioyond] 目标仓库: {parent_name}/{update_site}")
                        logger.info(f"[同步→Bioyond] 目标库位UUID: {target_location_uuid[:8]}...")
                    else:
                        logger.warning(f"⚠️ [同步→Bioyond] 仓库 {parent_name} 中没有库位 {update_site}")
                else:
                    logger.warning(f"⚠️ [同步→Bioyond] 仓库 {parent_name} 未在 WAREHOUSE_MAPPING 中配置")
                    parent_name = None

            # 如果没有找到,则遍历所有仓库查找
            if not parent_name or not target_location_uuid:
                logger.info(f"[同步→Bioyond] 从所有仓库中查找库位 {update_site}...")
                for warehouse_name, warehouse_info in warehouse_mapping.items():
                    site_uuids = warehouse_info.get("site_uuids", {})
                    if update_site in site_uuids:
                        parent_name = warehouse_name
                        target_location_uuid = site_uuids[update_site]
                        logger.info(f"[同步→Bioyond] 目标仓库: {parent_name}/{update_site}")
                        logger.info(f"[同步→Bioyond] 目标库位UUID: {target_location_uuid[:8]}...")
                        break

            if not parent_name or not target_location_uuid:
                logger.error(f"❌ [同步→Bioyond] 库位 {update_site} 没有在 WAREHOUSE_MAPPING 中配置")
                logger.debug(f"[同步→Bioyond] 可用仓库: {list(warehouse_mapping.keys())}")
                return False

            # 第2步:转换为 Bioyond 格式
            logger.info(f"[同步→Bioyond] 🔄 转换物料为 Bioyond 格式...")
            bioyond_material = resource_plr_to_bioyond(
                [resource],
                type_mapping=self.workstation.bioyond_config["material_type_mappings"],
                warehouse_mapping=self.workstation.bioyond_config["warehouse_mapping"]
            )[0]

            logger.debug(f"[同步→Bioyond] Bioyond 物料数据: {bioyond_material}")

            location_info = bioyond_material.pop("locations", None)
            logger.debug(f"[同步→Bioyond] 库位信息: {location_info}, 类型: {type(location_info)}")

            # 第3步:根据是否已有 Bioyond ID 决定创建还是使用现有物料
            if material_bioyond_id:
                # 物料已存在,直接使用现有 ID
                material_id = material_bioyond_id
                logger.info(f"✅ [同步→Bioyond] 使用已有物料 ID: {material_id[:8]}...")
            else:
                # 物料不存在,调用 API 创建新物料
                logger.info(f"[同步→Bioyond] 📤 调用 Bioyond API 添加物料...")
                material_id = self.bioyond_api_client.add_material(bioyond_material)

                if not material_id:
                    logger.error(f"❌ [同步→Bioyond] 添加物料失败,API 返回空")
                    return False

                logger.info(f"✅ [同步→Bioyond] 物料添加成功,Bioyond ID: {material_id[:8]}...")

                # 保存新创建的物料 ID 到资源对象
                extra_info["material_bioyond_id"] = material_id
                setattr(resource, "unilabos_extra", extra_info)

            # 第4步:物料入库前先检查目标库位是否被占用
            if location_info:
                logger.info(f"[同步→Bioyond] 📥 准备入库到库位 {update_site}...")

                # 处理不同的 location_info 数据结构
                if isinstance(location_info, list) and len(location_info) > 0:
                    location_id = location_info[0]["id"]
                elif isinstance(location_info, dict):
                    location_id = location_info["id"]
                else:
                    logger.warning(f"⚠️ [同步→Bioyond] 无效的库位信息格式: {location_info}")
                    location_id = None

                if location_id:
                    # 查询目标库位是否已有物料
                    logger.info(f"[同步→Bioyond] 🔍 检查库位 {update_site} (UUID: {location_id[:8]}...) 是否被占用...")

                    # 查询所有物料,检查是否有物料在目标库位
                    try:
                        all_materials_type1 = self.bioyond_api_client.stock_material('{"typeMode": 1, "includeDetail": true}')
                        all_materials_type2 = self.bioyond_api_client.stock_material('{"typeMode": 2, "includeDetail": true}')
                        all_materials = (all_materials_type1 or []) + (all_materials_type2 or [])

                        # 检查是否有物料已经在目标库位
                        location_occupied = False
                        occupying_material = None

                        for material in all_materials:
                            locations = material.get("locations", [])
                            for loc in locations:
                                if loc.get("id") == location_id:
                                    location_occupied = True
                                    occupying_material = material
                                    logger.warning(f"⚠️ [同步→Bioyond] 库位 {update_site} 已被占用!")
                                    logger.warning(f"   占用物料: {material.get('name')} (ID: {material.get('id', '')[:8]}...)")
                                    logger.warning(f"   占用位置: code={loc.get('code')}, x={loc.get('x')}, y={loc.get('y')}")
                                    break
                            if location_occupied:
                                break

                        if location_occupied:
                            # 如果是同一个物料(名称相同),说明已经入库过了,跳过
                            if occupying_material and occupying_material.get("name") == resource.name:
                                logger.info(f"✅ [同步→Bioyond] 物料 {resource.name} 已经在库位 {update_site},跳过重复入库")
                                return True
                            else:
                                logger.error(f"❌ [同步→Bioyond] 库位 {update_site} 已被其他物料占用,拒绝入库")
                                return False

                        logger.info(f"✅ [同步→Bioyond] 库位 {update_site} 可用,准备入库...")

                    except Exception as e:
                        logger.warning(f"⚠️ [同步→Bioyond] 检查库位状态时发生异常: {e},继续尝试入库...")

                    # 执行入库
                    logger.info(f"[同步→Bioyond] 📥 调用 Bioyond API 物料入库...")
                    response = self.bioyond_api_client.material_inbound(material_id, location_id)

                    # 注意:Bioyond API 成功时返回空字典 {},所以不能用 if not response 判断
                    # 只要没有抛出异常,就认为成功(response 是 dict 类型,即使是 {} 也不是 None)
                    if response is not None:
                        logger.info(f"✅ [同步→Bioyond] 物料 {resource.name} 成功入库到 {update_site}")

                        # 入库成功后,重新查询验证物料实际入库位置
                        logger.info(f"[同步→Bioyond] 🔍 验证物料实际入库位置...")
                        try:
                            all_materials_type1 = self.bioyond_api_client.stock_material('{"typeMode": 1, "includeDetail": true}')
                            all_materials_type2 = self.bioyond_api_client.stock_material('{"typeMode": 2, "includeDetail": true}')
                            all_materials = (all_materials_type1 or []) + (all_materials_type2 or [])

                            for material in all_materials:
                                if material.get("id") == material_id:
                                    locations = material.get("locations", [])
                                    if locations:
                                        actual_loc = locations[0]
                                        logger.info(f"📍 [同步→Bioyond] 物料实际位置: code={actual_loc.get('code')}, "
                                                  f"warehouse={actual_loc.get('whName')}, "
                                                  f"x={actual_loc.get('x')}, y={actual_loc.get('y')}")

                                        # 验证 UUID 是否匹配
                                        if actual_loc.get("id") != location_id:
                                            logger.error(f"❌ [同步→Bioyond] UUID 不匹配!")
                                            logger.error(f"   预期 UUID: {location_id}")
                                            logger.error(f"   实际 UUID: {actual_loc.get('id')}")
                                            logger.error(f"   这说明配置文件中的 UUID 映射有误,请检查 config.py 中的 WAREHOUSE_MAPPING")
                                    break
                        except Exception as e:
                            logger.warning(f"⚠️ [同步→Bioyond] 验证入库位置时发生异常: {e}")
                    else:
                        logger.error(f"❌ [同步→Bioyond] 物料入库失败")
                        return False
                else:
                    logger.warning(f"⚠️ [同步→Bioyond] 无法获取库位 ID,跳过入库操作")
            else:
                logger.warning(f"⚠️ [同步→Bioyond] 物料没有库位信息,跳过入库操作")
            return True

        except Exception as e:
            logger.error(f"❌ [同步→Bioyond] 同步物料 {resource.name} 时发生异常: {e}")
            import traceback
            traceback.print_exc()
            return False

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Use named expression to simplify assignment and conditional [×2] ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
- Replace f-string with no interpolated values with string ([`remove-redundant-fstring`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-redundant-fstring/))
- Swap positions of nested conditionals ([`swap-nested-ifs`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-nested-ifs/))
- Hoist nested repeated code outside conditional statements ([`hoist-similar-statement-from-if`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/hoist-similar-statement-from-if/))
- Low code quality found in BioyondResourceSynchronizer.sync\_to\_external - 4% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

<br/><details><summary>Explanation</summary>




The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

### Comment 7
<location> `unilabos/devices/workstation/bioyond_studio/station.py:461-463` </location>
<code_context>
    def resource_tree_add(self, resources: List[ResourcePLR]) -> None:
        """添加资源到资源树并更新ROS节点

        Args:
            resources (List[ResourcePLR]): 要添加的资源列表
        """
        logger.info(f"[resource_tree_add] 开始同步 {len(resources)} 个资源到 Bioyond 系统")
        for resource in resources:
            try:
                # 🔍 检查资源是否已有 Bioyond ID (避免重复入库)
                extra_info = getattr(resource, "unilabos_extra", {})
                material_bioyond_id = extra_info.get("material_bioyond_id")

                if material_bioyond_id:
                    logger.info(f"⏭️ [resource_tree_add] 跳过资源 {resource.name}: 已有 Bioyond ID ({material_bioyond_id[:8]}...),可能由 transfer 已处理")
                    continue

                logger.info(f"[resource_tree_add] 同步资源: {resource}")
                self.resource_synchronizer.sync_to_external(resource)
            except Exception as e:
                logger.error(f"[resource_tree_add] 同步资源失败 {resource}: {e}")
                import traceback
                traceback.print_exc()

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))

```suggestion
                if material_bioyond_id := extra_info.get("material_bioyond_id"):
```
</issue_to_address>

### Comment 8
<location> `unilabos/devices/workstation/bioyond_studio/station.py:491-493` </location>
<code_context>
    def resource_tree_transfer(self, old_parent: Optional[ResourcePLR], resource: ResourcePLR, new_parent: ResourcePLR) -> None:
        """处理资源在设备间迁移时的同步

        当资源从一个设备迁移到 BioyondWorkstation 时,需要同步到 Bioyond 系统

        Args:
            old_parent: 资源的原父节点(可能为 None)
            resource: 要迁移的资源
            new_parent: 资源的新父节点
        """
        logger.info(f"[resource_tree_transfer] 资源迁移: {resource.name}")
        logger.info(f"  旧父节点: {old_parent.name if old_parent else 'None'}")
        logger.info(f"  新父节点: {new_parent.name}")

        try:
            # 同步资源到 Bioyond 系统
            logger.info(f"[resource_tree_transfer] 开始同步资源 {resource.name} 到 Bioyond 系统")
            result = self.resource_synchronizer.sync_to_external(resource)

            if result:
                logger.info(f"✅ [resource_tree_transfer] 资源 {resource.name} 成功同步到 Bioyond 系统")
            else:
                logger.warning(f"⚠️ [resource_tree_transfer] 资源 {resource.name} 同步到 Bioyond 系统失败")

        except Exception as e:
            logger.error(f"❌ [resource_tree_transfer] 资源 {resource.name} 同步异常: {e}")
            import traceback
            traceback.print_exc()

</code_context>

<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))

```suggestion
            if result := self.resource_synchronizer.sync_to_external(resource):
```
</issue_to_address>

### Comment 9
<location> `unilabos/resources/graphio.py:588-590` </location>
<code_context>
def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, with_children=True):
    def replace_plr_type_to_ulab(source: str):
        replace_info = {
            "plate": "plate",
            "well": "well",
            "tip_spot": "tip_spot",
            "trash": "trash",
            "deck": "deck",
            "tip_rack": "tip_rack",
            "warehouse": "warehouse",
            "container": "container",
        }
        if source in replace_info:
            return replace_info[source]
        else:
            logger.warning(f"转换pylabrobot的时候,出现未知类型: {source}")
            return source

    def resource_plr_to_ulab_inner(d: dict, all_states: dict, child=True) -> dict:
        r = {
            "id": d["name"],
            "name": d["name"],
            "sample_id": None,
            "children": [resource_plr_to_ulab_inner(child, all_states) for child in d["children"]] if child else [],
            "parent": d["parent_name"] if d["parent_name"] else parent_name if parent_name else None,
            "type": replace_plr_type_to_ulab(d.get("category")),  # FIXME plr自带的type是python class name
            "class": d.get("class", ""),
            "position": (
                {"x": d["location"]["x"], "y": d["location"]["y"], "z": d["location"]["z"]}
                if d["location"]
                else {"x": 0, "y": 0, "z": 0}
            ),
            "config": {k: v for k, v in d.items() if k not in ["name", "children", "parent_name", "location"]},
            "data": all_states[d["name"]],
        }
        return r

    d = resource_plr.serialize()
    all_states = resource_plr.serialize_all_state()
    r = resource_plr_to_ulab_inner(d, all_states, with_children)

    return r

</code_context>

<issue_to_address>
**suggestion (code-quality):** Remove unnecessary else after guard condition ([`remove-unnecessary-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-else/))

```suggestion
        logger.warning(f"转换pylabrobot的时候,出现未知类型: {source}")
        return source
```
</issue_to_address>

### Comment 10
<location> `unilabos/resources/graphio.py:829` </location>
<code_context>
def resource_plr_to_bioyond(plr_resources: list[ResourcePLR], type_mapping: dict = {}, warehouse_mapping: dict = {}) -> list[dict]:
    bioyond_materials = []
    for resource in plr_resources:
        if isinstance(resource, BottleCarrier):
            # 获取 BottleCarrier 的类型映射
            type_info = type_mapping.get(resource.model)
            if not type_info:
                logger.error(f"❌ [PLR→Bioyond] BottleCarrier 资源 '{resource.name}' 的 model '{resource.model}' 不在 type_mapping 中")
                logger.debug(f"[PLR→Bioyond] 可用的 type_mapping 键: {list(type_mapping.keys())}")
                raise ValueError(f"资源 model '{resource.model}' 未在 MATERIAL_TYPE_MAPPINGS 中配置")

            material = {
                "typeId": type_info[1],
                "name": resource.name,
                "unit": "个",
                "quantity": 1,
                "details": [],
                "Parameters": "{}"
            }
            for bottle in resource.children:
                if isinstance(resource, ItemizedCarrier):
                    site = resource.get_child_identifier(bottle)
                else:
                    site = {"x": bottle.location.x - 1, "y": bottle.location.y - 1}

                # 获取子物料的类型映射
                bottle_type_info = type_mapping.get(bottle.model)
                if not bottle_type_info:
                    logger.error(f"❌ [PLR→Bioyond] 子物料 '{bottle.name}' 的 model '{bottle.model}' 不在 type_mapping 中")
                    raise ValueError(f"子物料 model '{bottle.model}' 未在 MATERIAL_TYPE_MAPPINGS 中配置")

                detail_item = {
                    "typeId": bottle_type_info[1],
                    "name": bottle.name,
                    "code": bottle.code if hasattr(bottle, "code") else "",
                    "quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
                    "x": site["x"] + 1,
                    "y": site["y"] + 1,
                    "molecular": 1,
                    "Parameters": json.dumps({"molecular": 1})
                }
                material["details"].append(detail_item)
        else:
            # 单个瓶子(非载架)类型的资源
            bottle = resource[0] if resource.capacity > 0 else resource

            # 根据 resource.model 从 type_mapping 获取正确的 typeId
            type_info = type_mapping.get(resource.model)
            if type_info:
                type_id = type_info[1]
            else:
                # 如果找不到映射,记录警告并使用默认值
                logger.warning(f"[PLR→Bioyond] 资源 {resource.name} 的 model '{resource.model}' 不在 type_mapping 中,使用默认烧杯类型")
                type_id = "3a14196b-24f2-ca49-9081-0cab8021bf1a"  # 默认使用烧杯类型

            material = {
                "typeId": type_id,
                "name": resource.name if hasattr(resource, "name") else "",
                "unit": "个",  # 修复:Bioyond API 要求 unit 字段不能为空
                "quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
                "Parameters": "{}"
            }

        if resource.parent is not None and isinstance(resource.parent, ItemizedCarrier):
            site_in_parent = resource.parent.get_child_identifier(resource)

            material["locations"] = [
                {
                    "id": warehouse_mapping[resource.parent.name]["site_uuids"][site_in_parent["identifier"]],
                    "whid": warehouse_mapping[resource.parent.name]["uuid"],
                    "whName": resource.parent.name,
                    "x": site_in_parent["x"] + 1,
                    "y": site_in_parent["y"] + 1,
                    "z": 1,
                    "quantity": 0
                }
            ]

        bioyond_materials.append(material)
    return bioyond_materials

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
- Replace mutable default arguments with None ([`default-mutable-arg`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/default-mutable-arg/))
- Low code quality found in resource\_plr\_to\_bioyond - 13% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

<br/><details><summary>Explanation</summary>

The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

### Comment 11
<location> `unilabos/resources/warehouse.py:69` </location>
<code_context>
def warehouse_factory(
    name: str,
    num_items_x: int = 1,
    num_items_y: int = 4,
    num_items_z: int = 4,
    dx: float = 137.0,
    dy: float = 96.0,
    dz: float = 120.0,
    item_dx: float = 10.0,
    item_dy: float = 10.0,
    item_dz: float = 10.0,
    removed_positions: Optional[List[int]] = None,
    empty: bool = False,
    category: str = "warehouse",
    model: Optional[str] = None,
    col_offset: int = 0,  # 列起始偏移量,用于生成A05-D08等命名
    layout: str = "col-major",  # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
):
    # 创建位置坐标
    locations = []

    for layer in range(num_items_z):  # 层
        for row in range(num_items_y):  # 行
            for col in range(num_items_x):  # 列
                # 计算位置
                x = dx + col * item_dx

                # 根据 layout 决定 y 坐标计算
                if layout == "row-major":
                    # 行优先:row=0(A行) 应该显示在上方,需要较小的 y 值
                    y = dy + row * item_dy
                else:
                    # 列优先:保持原逻辑(row=0 对应较大的 y)
                    y = dy + (num_items_y - row - 1) * item_dy

                z = dz + (num_items_z - layer - 1) * item_dz
                locations.append(Coordinate(x, y, z))
    if removed_positions:
        locations = [loc for i, loc in enumerate(locations) if i not in removed_positions]
    _sites = create_homogeneous_resources(
        klass=ResourceHolder,
        locations=locations,
        resource_size_x=127.0,
        resource_size_y=86.0,
        name_prefix=name,
    )
    len_x, len_y = (num_items_x, num_items_y) if num_items_z == 1 else (num_items_y, num_items_z) if num_items_x == 1 else (num_items_x, num_items_z)

    # 根据 layout 参数生成不同的排序方式
    # 注意:物理位置的 y 坐标是倒序的 (row=0 时 y 最大,对应前端显示的顶部)
    if layout == "row-major":
        # 行优先顺序: A01,A02,A03,A04, B01,B02,B03,B04
        # locations[0] 对应 row=0, y最大(前端顶部)→ 应该是 A01
        keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
    else:
        # 列优先顺序: A01,B01,C01,D01, A02,B02,C02,D02
        keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]

    sites = {i: site for i, site in zip(keys, _sites.values())}

    return WareHouse(
        name=name,
        size_x=dx + item_dx * num_items_x,
        size_y=dy + item_dy * num_items_y,
        size_z=dz + item_dz * num_items_z,
        num_items_x = num_items_x,
        num_items_y = num_items_y,
        num_items_z = num_items_z,
        ordering_layout=layout,  # 传递排序方式到 ordering_layout
        # ordered_items=ordered_items,
        # ordering=ordering,
        sites=sites,
        category=category,
        model=model,
    )

</code_context>

<issue_to_address>
**suggestion (code-quality):** We've found these issues:

- Replace identity comprehension with call to collection constructor ([`identity-comprehension`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/identity-comprehension/))
- Low code quality found in warehouse\_factory - 23% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

```suggestion
    sites = dict(zip(keys, _sites.values()))
```

<br/><details><summary>Explanation</summary>Convert list/set/tuple comprehensions that do not change the input elements into.

#### Before

```python
# List comprehensions
[item for item in coll]
[item for item in friends.names()]

# Dict comprehensions
{k: v for k, v in coll}
{k: v for k, v in coll.items()}  # Only if we know coll is a `dict`

# Unneeded call to `.items()`
dict(coll.items())  # Only if we know coll is a `dict`

# Set comprehensions
{item for item in coll}
```

#### After

```python
# List comprehensions
list(iter(coll))
list(iter(friends.names()))

# Dict comprehensions
dict(coll)
dict(coll)

# Unneeded call to `.items()`
dict(coll)

# Set comprehensions
set(coll)
```

All these comprehensions are just creating a copy of the original collection.
They can all be simplified by simply constructing a new collection directly. The
resulting code is easier to read and shows the intent more clearly.
The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +27 to 30
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
):
# 创建16个板架位 (4层 x 4位置)
# 创建位置坐标
locations = []
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider validating the 'layout' parameter to ensure only supported values are used.

Adding explicit validation for 'layout' will prevent unsupported values and reduce the risk of configuration errors.

Suggested change
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
):
# 创建16个板架位 (4层 x 4位置)
# 创建位置坐标
locations = []
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
):
# 验证 layout 参数
if layout not in ("col-major", "row-major"):
raise ValueError(f"Unsupported layout: {layout}. Supported values are 'col-major' and 'row-major'.")
# 创建位置坐标
locations = []

Comment on lines +632 to +635
# 创建反向映射: {显示名称: (model, UUID)} -> 用于从 Bioyond typeName 查找 model
# 如果 type_mapping 的 key 已经是显示名称,则直接使用;否则创建反向映射
reverse_type_mapping = {}
for key, value in type_mapping.items():
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Reverse mapping logic assumes display names are unique, which may not always be true.

Currently, duplicate display names in type_mapping will cause only the first entry to be retained in the reverse mapping. Please add a check to detect duplicates and raise a warning or error if found.

Comment on lines +875 to +877
# 根据 resource.model 从 type_mapping 获取正确的 typeId
type_info = type_mapping.get(resource.model)
if type_info:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Defaulting to a hardcoded typeId for unmapped models may lead to incorrect data.

Using a default typeId when a model is missing from type_mapping risks misclassification. It would be safer to raise an error or enforce explicit mappings for all resource types.

Comment on lines 588 to 590
else:
print("转换pylabrobot的时候,出现未知类型", source)
logger.warning(f"转换pylabrobot的时候,出现未知类型: {source}")
return source
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Remove unnecessary else after guard condition (remove-unnecessary-else)

Suggested change
else:
print("转换pylabrobot的时候,出现未知类型", source)
logger.warning(f"转换pylabrobot的时候,出现未知类型: {source}")
return source
logger.warning(f"转换pylabrobot的时候,出现未知类型: {source}")
return source

# 列优先顺序: A01,B01,C01,D01, A02,B02,C02,D02
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]

sites = {i: site for i, site in zip(keys, _sites.values())}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): We've found these issues:

Suggested change
sites = {i: site for i, site in zip(keys, _sites.values())}
sites = dict(zip(keys, _sites.values()))


ExplanationConvert list/set/tuple comprehensions that do not change the input elements into.

Before

# List comprehensions
[item for item in coll]
[item for item in friends.names()]

# Dict comprehensions
{k: v for k, v in coll}
{k: v for k, v in coll.items()}  # Only if we know coll is a `dict`

# Unneeded call to `.items()`
dict(coll.items())  # Only if we know coll is a `dict`

# Set comprehensions
{item for item in coll}

After

# List comprehensions
list(iter(coll))
list(iter(friends.names()))

# Dict comprehensions
dict(coll)
dict(coll)

# Unneeded call to `.items()`
dict(coll)

# Set comprehensions
set(coll)

All these comprehensions are just creating a copy of the original collection.
They can all be simplified by simply constructing a new collection directly. The
resulting code is easier to read and shows the intent more clearly.
The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

  • Reduce the function length by extracting pieces of functionality out into
    their own functions. This is the most important thing you can do - ideally a
    function should be less than 10 lines.
  • Reduce nesting, perhaps by introducing guard clauses to return early.
  • Ensure that variables are tightly scoped, so that code using related concepts
    sits together within the function rather than being scattered.

Xuwznln and others added 19 commits November 7, 2025 16:11
- 将DispensingStation和ReactionStation资源统一为PolymerStation命名
- 优化物料同步逻辑,支持耗材类型(typeMode=0)的查询
- 添加物料默认参数配置功能
- 调整仓库坐标布局
- 清理废弃资源定义
将DispensingStation和ReactionStation的物料类型映射统一更名为PolymerStation,保持命名一致性
将物料同步流程拆分为两个独立阶段:transfer阶段只创建物料,add阶段执行入库
简化状态检查接口,仅返回连接状态
将液体进料烧杯的体积单位从μL改为g以匹配实际使用场景
在返回结果中添加merged_workflow和order_params字段,提供更完整的工作流信息
在create_order方法返回结果中增加order_params字段,以便调用方获取完整的任务参数
原逻辑将主称固体平均分成3份作为90%物料,现改为直接使用main_portion
将多个单独的任务编码和ID字段合并为统一的return_info字段
更新相关描述以反映新的数据结构
- 在graphio.py中添加API必需字段
- 实现工作站HTTP服务启动和停止逻辑
- 添加任务完成状态跟踪字典和等待方法
- 重写任务完成报送处理方法记录状态
- 支持批量任务完成等待和报告获取
…ort功能

该功能已被wait_for_multiple_orders_and_get_reports替代,简化代码结构
处理状态查询时安全获取device_id,避免因属性不存在导致的异常
在物料入库API调用失败时,添加更详细的错误信息打印
同时修正station.py中对空响应和失败情况的判断逻辑
重构瓶架载体的分配逻辑,使用嵌套循环替代硬编码索引分配
添加更详细的坐标映射说明,明确PLR与Bioyond坐标的对应关系
当API返回成功但无data字段时,返回包含success标识的字典而非空字典
@Xuwznln Xuwznln merged commit 850eeae into deepmodeling:dev Nov 14, 2025
8 checks passed
Xuwznln added a commit that referenced this pull request Nov 14, 2025
* 更新Bioyond工作站配置,添加新的物料类型映射和载架定义,优化物料查询逻辑

* 添加Bioyond实验配置文件,定义物料类型映射和设备配置

* 更新bioyond_warehouse_reagent_stack方法,修正试剂堆栈尺寸和布局描述

* 更新Bioyond实验配置,修正物料类型映射,优化设备配置

* 更新Bioyond资源同步逻辑,优化物料入库流程,增强错误处理和日志记录

* 更新Bioyond资源,添加配液站和反应站专用载架,优化仓库工厂函数的排序方式

* 更新Bioyond资源,添加配液站和反应站相关载架,优化试剂瓶和样品瓶配置

* 更新Bioyond实验配置,修正试剂瓶载架ID,确保与设备匹配

* 更新Bioyond资源,移除反应站单烧杯载架,添加反应站单烧瓶载架分类

* Refactor Bioyond resource synchronization and update bottle carrier definitions

- Removed traceback printing in error handling for Bioyond synchronization.
- Enhanced logging for existing Bioyond material ID usage during synchronization.
- Added new bottle carrier definitions for single flask and updated existing ones.
- Refactored dispensing station and reaction station bottle definitions for clarity and consistency.
- Improved resource mapping and error handling in graphio for Bioyond resource conversion.
- Introduced layout parameter in warehouse factory for better warehouse configuration.

* 更新Bioyond仓库工厂,添加排序方式支持,优化坐标计算逻辑

* 更新Bioyond载架和甲板配置,调整样品板尺寸和仓库坐标

* 更新Bioyond资源同步,增强占用位置日志信息,修正坐标转换逻辑

* 更新Bioyond反应站和分配站配置,调整材料类型映射和ID,移除不必要的项

* support name change during materials change

* fix json dumps

* correct tip

* 优化调度器API路径,更新相关方法描述

* 更新 BIOYOND 载架相关文档,调整 API 以支持自带试剂瓶的载架类型,修复资源获取时的子物料处理逻辑

* 实现资源删除时的同步处理,优化出库操作逻辑

* 修复 ItemizedCarrier 中的可见性逻辑

* 保存 Bioyond 原始信息到 unilabos_extra,以便出库时查询

* 根据 resource.capacity 判断是试剂瓶(载架)还是多瓶载架,走不同的奔曜转换

* Fix bioyond bottle_carriers ordering

* 优化 Bioyond 物料同步逻辑,增强坐标解析和位置更新处理

* disable slave connect websocket

* correct remove_resource stats

* change uuid logger to trace level

* enable slave mode

* refactor(bioyond): 统一资源命名并优化物料同步逻辑

- 将DispensingStation和ReactionStation资源统一为PolymerStation命名
- 优化物料同步逻辑,支持耗材类型(typeMode=0)的查询
- 添加物料默认参数配置功能
- 调整仓库坐标布局
- 清理废弃资源定义

* feat(warehouses): 为仓库函数添加col_offset和layout参数

* refactor: 更新实验配置中的物料类型映射命名

将DispensingStation和ReactionStation的物料类型映射统一更名为PolymerStation,保持命名一致性

* fix: 更新实验配置中的载体名称从6VialCarrier到6StockCarrier

* feat(bioyond): 实现物料创建与入库分离逻辑

将物料同步流程拆分为两个独立阶段:transfer阶段只创建物料,add阶段执行入库
简化状态检查接口,仅返回连接状态

* fix(reaction_station): 修正液体进料烧杯体积单位并增强返回结果

将液体进料烧杯的体积单位从μL改为g以匹配实际使用场景
在返回结果中添加merged_workflow和order_params字段,提供更完整的工作流信息

* feat(dispensing_station): 在任务创建返回结果中添加order_params信息

在create_order方法返回结果中增加order_params字段,以便调用方获取完整的任务参数

* fix(dispensing_station): 修改90%物料分配逻辑从分成3份改为直接使用

原逻辑将主称固体平均分成3份作为90%物料,现改为直接使用main_portion

* feat(bioyond): 添加任务编码和任务ID的输出,支持批量任务创建后的状态监控

* refactor(registry): 简化设备配置中的任务结果处理逻辑

将多个单独的任务编码和ID字段合并为统一的return_info字段
更新相关描述以反映新的数据结构

* feat(工作站): 添加HTTP报送服务和任务完成状态跟踪

- 在graphio.py中添加API必需字段
- 实现工作站HTTP服务启动和停止逻辑
- 添加任务完成状态跟踪字典和等待方法
- 重写任务完成报送处理方法记录状态
- 支持批量任务完成等待和报告获取

* refactor(dispensing_station): 移除wait_for_order_completion_and_get_report功能

该功能已被wait_for_multiple_orders_and_get_reports替代,简化代码结构

* fix: 更新任务报告API错误

* fix(workstation_http_service): 修复状态查询中device_id获取逻辑

处理状态查询时安全获取device_id,避免因属性不存在导致的异常

* fix(bioyond_studio): 改进物料入库失败时的错误处理和日志记录

在物料入库API调用失败时,添加更详细的错误信息打印
同时修正station.py中对空响应和失败情况的判断逻辑

* refactor(bioyond): 优化瓶架载体的分配逻辑和注释说明

重构瓶架载体的分配逻辑,使用嵌套循环替代硬编码索引分配
添加更详细的坐标映射说明,明确PLR与Bioyond坐标的对应关系

* fix(bioyond_rpc): 修复物料入库成功时无data字段返回空的问题

当API返回成功但无data字段时,返回包含success标识的字典而非空字典

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@dp.tech>
@ZiWei09 ZiWei09 deleted the HR branch November 19, 2025 06:36
Xuwznln added a commit that referenced this pull request Dec 25, 2025
* 0.10.7 Update (#101)

* Cleanup registry to be easy-understanding (#76)

* delete deprecated mock devices

* rename categories

* combine chromatographic devices

* rename rviz simulation nodes

* organic virtual devices

* parse vessel_id

* run registry completion before merge

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>

* fix: workstation handlers and vessel_id parsing

* fix: working dir error when input config path
feat: report publish topic when error

* modify default discovery_interval to 15s

* feat: add trace log level

* feat: 添加ChinWe设备控制类,支持串口通信和电机控制功能 (#79)

* fix: drop_tips not using auto resource select

* fix: discard_tips error

* fix: discard_tips

* fix: prcxi_res

* add: prcxi res
fix: startup slow

* feat: workstation example

* fix pumps and liquid_handler handle

* feat: 优化protocol node节点运行日志

* fix all protocol_compilers and remove deprecated devices

* feat: 新增use_remote_resource参数

* fix and remove redundant info

* bugfixes on organic protocols

* fix filter protocol

* fix protocol node

* 临时兼容错误的driver写法

* fix: prcxi import error

* use call_async in all service to avoid deadlock

* fix: figure_resource

* Update recipe.yaml

* add workstation template and battery example

* feat: add sk & ak

* update workstation base

* Create workstation_architecture.md

* refactor: workstation_base 重构为仅含业务逻辑,通信和子设备管理交给 ProtocolNode

* refactor: ProtocolNode→WorkstationNode

* Add:msgs.action (#83)

* update: Workstation dev 将版本号从 0.10.3 更新为 0.10.4 (#84)

* Add:msgs.action

* update: 将版本号从 0.10.3 更新为 0.10.4

* simplify resource system

* uncompleted refactor

* example for use WorkstationBase

* feat: websocket

* feat: websocket test

* feat: workstation example

* feat: action status

* fix: station自己的方法注册错误

* fix: 还原protocol node处理方法

* fix: build

* fix: missing job_id key

* ws test version 1

* ws test version 2

* ws protocol

* 增加物料关系上传日志

* 增加物料关系上传日志

* 修正物料关系上传

* 修复工站的tracker实例追踪失效问题

* 增加handle检测,增加material edge关系上传

* 修复event loop错误

* 修复edge上报错误

* 修复async错误

* 更新schema的title字段

* 主机节点信息等支持自动刷新

* 注册表编辑器

* 修复status密集发送时,消息出错

* 增加addr参数

* fix: addr param

* fix: addr param

* 取消labid 和 强制config输入

* Add action definitions for LiquidHandlerSetGroup and LiquidHandlerTransferGroup

- Created LiquidHandlerSetGroup.action with fields for group name, wells, and volumes.
- Created LiquidHandlerTransferGroup.action with fields for source and target group names and unit volume.
- Both actions include response fields for return information and success status.

* Add LiquidHandlerSetGroup and LiquidHandlerTransferGroup actions to CMakeLists

* Add set_group and transfer_group methods to PRCXI9300Handler and update liquid_handler.yaml

* result_info改为字典类型

* 新增uat的地址替换

* runze multiple pump support

(cherry picked from commit 49354fc)

* remove runze multiple software obtainer

(cherry picked from commit 8bcc92a)

* support multiple backbone

(cherry picked from commit 4771ff2)

* Update runze pump format

* Correct runze multiple backbone

* Update runze_multiple_backbone

* Correct runze pump multiple receive method.

* Correct runze pump multiple receive method.

* 对于PRCXI9320的transfer_group,一对多和多对多

* 移除MQTT,更新launch文档,提供注册表示例文件,更新到0.10.5

* fix import error

* fix dupe upload registry

* refactor ws client

* add server timeout

* Fix: run-column with correct vessel id (#86)

* fix run_column

* Update run_column_protocol.py

(cherry picked from commit e5aa4d9)

* resource_update use resource_add

* 新增版位推荐功能

* 重新规定了版位推荐的入参

* update registry with nested obj

* fix protocol node log_message, added create_resource return value

* fix protocol node log_message, added create_resource return value

* try fix add protocol

* fix resource_add

* 修复移液站错误的aspirate注册表

* Feature/xprbalance-zhida (#80)

* feat(devices): add Zhida GC/MS pretreatment automation workstation

* feat(devices): add mettler_toledo xpr balance

* balance

* 重新补全zhida注册表

* PRCXI9320 json

* PRCXI9320 json

* PRCXI9320 json

* fix resource download

* remove class for resource

* bump version to 0.10.6

* 更新所有注册表

* 修复protocolnode的兼容性

* 修复protocolnode的兼容性

* Update install md

* Add Defaultlayout

* 更新物料接口

* fix dict to tree/nested-dict converter

* coin_cell_station draft

* refactor: rename "station_resource" to "deck"

* add standardized BIOYOND resources: bottle_carrier, bottle

* refactor and add BIOYOND resources tests

* add BIOYOND deck assignment and pass all tests

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92)

* feat: 新威电池测试系统驱动与注册文件

* feat: bring neware driver & battery.json into workstation_dev_YB2

* add bioyond studio draft

* bioyond station with communication init and resource sync

* fix bioyond station and registry

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* frontend_docs

* create/update resources with POST/PUT for big amount/ small amount data

* create/update resources with POST/PUT for big amount/ small amount data

* refactor: add itemized_carrier instead of carrier consists of ResourceHolder

* create warehouse by factory func

* update bioyond launch json

* add child_size for itemized_carrier

* fix bioyond resource io

* Workstation templates: Resources and its CRUD, and workstation tasks (#95)

* coin_cell_station draft

* refactor: rename "station_resource" to "deck"

* add standardized BIOYOND resources: bottle_carrier, bottle

* refactor and add BIOYOND resources tests

* add BIOYOND deck assignment and pass all tests

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92)

* feat: 新威电池测试系统驱动与注册文件

* feat: bring neware driver & battery.json into workstation_dev_YB2

* add bioyond studio draft

* bioyond station with communication init and resource sync

* fix bioyond station and registry

* create/update resources with POST/PUT for big amount/ small amount data

* refactor: add itemized_carrier instead of carrier consists of ResourceHolder

* create warehouse by factory func

* update bioyond launch json

* add child_size for itemized_carrier

* fix bioyond resource io

---------

Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com>
Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com>

* 更新物料接口

* Workstation dev yb2 (#100)

* Refactor and extend reaction station action messages

* Refactor dispensing station tasks to enhance parameter clarity and add batch processing capabilities

- Updated `create_90_10_vial_feeding_task` to include detailed parameters for 90%/10% vial feeding, improving clarity and usability.
- Introduced `create_batch_90_10_vial_feeding_task` for batch processing of 90%/10% vial feeding tasks with JSON formatted input.
- Added `create_batch_diamine_solution_task` for batch preparation of diamine solution, also utilizing JSON formatted input.
- Refined `create_diamine_solution_task` to include additional parameters for better task configuration.
- Enhanced schema descriptions and default values for improved user guidance.

* 修复to_plr_resources

* add update remove

* 支持选择器注册表自动生成
支持转运物料

* 修复资源添加

* 修复transfer_resource_to_another生成

* 更新transfer_resource_to_another参数,支持spot入参

* 新增test_resource动作

* fix host_node error

* fix host_node test_resource error

* fix host_node test_resource error

* 过滤本地动作

* 移动内部action以兼容host node

* 修复同步任务报错不显示的bug

* feat: 允许返回非本节点物料,后面可以通过decoration进行区分,就不进行warning了

* update todo

* modify bioyond/plr converter, bioyond resource registry, and tests

* pass the tests

* update todo

* add conda-pack-build.yml

* add auto install script for conda-pack-build.yml

(cherry picked from commit 172599a)

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* Add version in __init__.py
Update conda-pack-build.yml
Add create_zip_archive.py

* Update conda-pack-build.yml

* Update conda-pack-build.yml (with mamba)

* Update conda-pack-build.yml

* Fix FileNotFoundError

* Try fix 'charmap' codec can't encode characters in position 16-23: character maps to <undefined>

* Fix unilabos msgs search error

* Fix environment_check.py

* Update recipe.yaml

* Update registry. Update uuid loop figure method. Update install docs.

* Fix nested conda pack

* Fix one-key installation path error

* Bump version to 0.10.7

* Workshop bj (#99)

* Add LaiYu Liquid device integration and tests

Introduce LaiYu Liquid device implementation, including backend, controllers, drivers, configuration, and resource files. Add hardware connection, tip pickup, and simplified test scripts, as well as experiment and registry configuration for LaiYu Liquid. Documentation and .gitignore for the device are also included.

* feat(LaiYu_Liquid): 重构设备模块结构并添加硬件文档

refactor: 重新组织LaiYu_Liquid模块目录结构
docs: 添加SOPA移液器和步进电机控制指令文档
fix: 修正设备配置中的最大体积默认值
test: 新增工作台配置测试用例
chore: 删除过时的测试脚本和配置文件

* add

* 重构: 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py 并更新所有导入引用

- 使用 git mv 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py
- 更新所有相关文件中的导入引用
- 保持代码功能不变,仅改善命名一致性
- 测试确认所有导入正常工作

* 修复: 在 core/__init__.py 中添加 LaiYuLiquidBackend 导出

- 添加 LaiYuLiquidBackend 到导入列表
- 添加 LaiYuLiquidBackend 到 __all__ 导出列表
- 确保所有主要类都可以正确导入

* 修复大小写文件夹名字

* 电池装配工站二次开发教程(带目录)上传至dev (#94)

* 电池装配工站二次开发教程

* Update intro.md

* 物料教程

* 更新物料教程,json格式注释

* Update prcxi driver & fix transfer_liquid mix_times (#90)

* Update prcxi driver & fix transfer_liquid mix_times

* fix: correct mix_times type

* Update liquid_handler registry

* test: prcxi.py

* Update registry from pr

* fix ony-key script not exist

* clean files

---------

Co-authored-by: Junhan Chang <changjh@dp.tech>
Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com>
Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com>
Co-authored-by: LccLink <1951855008@qq.com>
Co-authored-by: lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com>
Co-authored-by: shiyubo0410 <shiyubo@dp.tech>

* fix startup env check.
add auto install during one-key installation

* Try fix one-key build on linux

* Complete all one key installation

* fix: rename schema field to resource_schema with serialization and validation aliases (#104)

Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>

* Fix one-key installation build

Install conda-pack before pack command

Add conda-pack to base when building one-key installer

Fix param error when using mamba run

Try fix one-key build on linux

* Fix conda pack on windows

* add plr_to_bioyond, and refactor bioyond stations

* modify default config

* Fix one-key installation build for windows

* Fix workstation startup
Update registry

* Fix/resource UUID and doc fix (#109)

* Fix ResourceTreeSet load error

* Raise error when using unsupported type to create ResourceTreeSet

* Fix children key error

* Fix children key error

* Fix workstation resource not tracking

* Fix workstation deck & children resource dupe

* Fix workstation deck & children resource dupe

* Fix multiple resource error

* Fix resource tree update

* Fix resource tree update

* Force confirm uuid

* Tip more error log

* Refactor Bioyond workstation and experiment workflow (#105)

Refactored the Bioyond workstation classes to improve parameter handling and workflow management. Updated experiment.py to use BioyondReactionStation with deck and material mappings, and enhanced workflow step parameter mapping and execution logic. Adjusted JSON experiment configs, improved workflow sequence handling, and added UUID assignment to PLR materials. Removed unused station_config and material cache logic, and added detailed docstrings and debug output for workflow methods.

* Fix resource get.
Fix resource parent not found.
Mapping uuid for all resources.

* mount parent uuid

* Add logging configuration based on BasicConfig in main function

* fix workstation node error

* fix workstation node error

* Update boot example

* temp fix for resource get

* temp fix for resource get

* provide error info when cant find plr type

* pack repo info

* fix to plr type error

* fix to plr type error

* Update regular container method

* support no size init

* fix comprehensive_station.json

* fix comprehensive_station.json

* fix type conversion

* fix state loading for regular container

* Update deploy-docs.yml

* Update deploy-docs.yml

---------

Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>

* Close #107
Update doc url.

* Fix/update resource (#112)

* cancel upload_registry

* Refactor Bioyond workstation and experiment workflow -fix (#111)

* refactor(bioyond_studio): 优化材料缓存加载和参数验证逻辑

改进材料缓存加载逻辑以支持多种材料类型和详细材料处理
更新工作流参数验证中的字段名从key/value改为Key/DisplayValue
移除未使用的merge_workflow_with_parameters方法
添加get_station_info方法获取工作站基础信息
清理实验文件中的注释代码和更新导入路径

* fix: 修复资源移除时的父资源检查问题

在BaseROS2DeviceNode中,移除资源前添加对父资源是否为None的检查,避免空指针异常
同时更新Bottle和BottleCarrier类以支持**kwargs参数
修正测试文件中Liquid_feeding_beaker的大小写拼写错误

* correct return message

---------

Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>

* fix resource_get in action

* fix(reaction_station): 清空工作流序列和参数避免重复执行 (#113)

在创建任务后清空工作流序列和参数,防止下次执行时累积重复

* Update create_resource device_id

* Update ResourceTracker

add more enumeration in POSE

fix converter in resource_tracker

* Update graphio together with workstation design.

fix(reaction_station): 为步骤参数添加Value字段传个BY后端

fix(bioyond/warehouses): 修正仓库尺寸和物品排列参数

调整仓库的x轴和z轴物品数量以及物品尺寸参数,使其符合4x1x4的规格要求

fix warehouse serialize/deserialize

fix bioyond converter

fix itemized_carrier.unassign_child_resource

allow not-loaded MSG in registry

add layout serializer & converter

warehouseuse A1-D4; add warehouse layout

fix(graphio): 修正bioyond到plr资源转换中的坐标计算错误

Fix resource assignment and type mapping issues

Corrects resource assignment in ItemizedCarrier by using the correct spot key from _ordering. Updates graphio to use 'typeName' instead of 'name' for type mapping in resource_bioyond_to_plr. Renames DummyWorkstation to BioyondWorkstation in workstation_http_service for clarity.

* Update workstation & bioyond example

Refine descriptions in Bioyond reaction station YAML

Updated and clarified field and operation descriptions in the reaction_station_bioyond.yaml file for improved accuracy and consistency. Changes include more precise terminology, clearer parameter explanations, and standardized formatting for operation schemas.

refactor(workstation): 更新反应站参数描述并添加分液站配置文件

修正反应站方法参数描述,使其更准确清晰
添加bioyond_dispensing_station.yaml配置文件

add create_workflow script and test

add invisible_slots to carriers

fix(warehouses): 修正bioyond_warehouse_1x4x4仓库的尺寸参数

调整仓库的num_items_x和num_items_z值以匹配实际布局,并更新物品尺寸参数

save resource get data. allow empty value for layout and cross_section_type

More decks&plates support for bioyond (#115)

refactor(registry): 重构反应站设备配置,简化并更新操作命令

移除旧的自动操作命令,新增针对具体化学操作的命令配置
更新模块路径和配置结构,优化参数定义和描述

fix(dispensing_station): 修正物料信息查询方法调用

将直接调用material_id_query改为通过hardware_interface调用,以符合接口设计规范

* PRCXI Update

修改prcxi连线

prcxi样例图

Create example_prcxi.json

* Update resource extra & uuid.

use ordering to convert identifier to idx

convert identifier to site idx

correct extra key

update extra before transfer

fix multiple instance error

add resource_tree_transfer func

fox itemrized carrier assign child resource

support internal device material transfer

remove extra key

use same callback group

support material extra

support material extra
support update_resource_site in extra

* Update workstation.

modify workstation_architecture docs

bioyond_HR (#133)

* feat: Enhance Bioyond synchronization and resource management

- Implemented synchronization for all material types (consumables, samples, reagents) from Bioyond, logging detailed information for each type.
- Improved error handling and logging during synchronization processes.
- Added functionality to save Bioyond material IDs in UniLab resources for future updates.
- Enhanced the `sync_to_external` method to handle material movements correctly, including querying and creating materials in Bioyond.
- Updated warehouse configurations to support new storage types and improved layout for better resource management.
- Introduced new resource types such as reactors and tip boxes, with detailed specifications.
- Modified warehouse factory to support column offsets for naming conventions (e.g., A05-D08).
- Improved resource tracking by merging extra attributes instead of overwriting them.
- Added a new method for updating resources in Bioyond, ensuring better synchronization of resource changes.

* feat: 添加TipBox和Reactor的配置到bottles.yaml

* fix: 修复液体投料方法中的volume参数处理逻辑

修复solid_feeding_vials方法中的volume参数处理逻辑,优化solvents参数的使用条件

更新液体投料方法,支持通过溶剂信息自动计算体积,添加solvents参数并更新文档描述

Add batch creation methods for vial and solution tasks

添加批量创建90%10%小瓶投料任务和二胺溶液配置任务的功能,更新相关参数和默认值

* 封膜仪、撕膜仪、耗材站接口

* 添加Raman和xrd相关代码

* Resource update & asyncio fix

correct bioyond config

prcxi example

fix append_resource

fix regularcontainer

fix cancel error

fix resource_get param

fix json dumps

support name change during materials change

enable slave mode

change uuid logger to trace level

correct remove_resource stats

disable slave connect websocket

adjust with_children param

modify devices to use correct executor (sleep, create_task)

support sleep and create_task in node

fix run async execution error

* bump version to 0.10.9

update registry

* PRCXI Reset Error Correction (#166)

* change 9320 desk row number to 4

* Updated 9320 host address

* Updated 9320 host address

* Add **kwargs in classes: PRCXI9300Deck and PRCXI9300Container

* Removed all sample_id in prcxi_9320.json to avoid KeyError

* 9320 machine testing settings

* Typo

* Rewrite setup logic to clear error code

* 初始化 step_mode 属性

* 1114物料手册定义教程byxinyu (#165)

* 宜宾奔耀工站deck前端by_Xinyu

* 构建物料教程byxinyu

* 1114物料手册定义教程

* 3d sim (#97)

* 修改lh的json启动

* 修改lh的json启动

* 修改backend,做成sim的通用backend

* 修改yaml的地址,3D模型适配网页生产环境

* 添加laiyu硬件连接

* 修改移液枪的状态判断方法,

修改移液枪的状态判断方法,
添加三轴的表定点与零点之间的转换
添加三轴真实移动的backend

* 修改laiyu移液站

简化移动方法,
取消软件限制位置,
修改当值使用Z轴时也需要重新复位Z轴的问题

* 更新lh以及laiyu workshop

1,现在可以直接通过修改backend,适配其他的移液站,主类依旧使用LiquidHandler,不用重新编写

2,修改枪头判断标准,使用枪头自身判断而不是类的判断,

3,将归零参数用毫米计算,方便手动调整,

4,修改归零方式,上电使用机械归零,确定机械零点,手动归零设置工作区域零点方便计算,二者互不干涉

* 修改枪头动作

* 修改虚拟仿真方法

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: Junhan Chang <changjh@dp.tech>

* 标准化opcua设备接入unilab (#78)

* 初始提交,只保留工作区当前状态

* remove redundant arm_slider meshes

---------

Co-authored-by: Junhan Chang <changjh@dp.tech>

* add new laiyu liquid driver, yaml and json files (#164)

* HR物料同步,前端展示位置修复 (#135)

* 更新Bioyond工作站配置,添加新的物料类型映射和载架定义,优化物料查询逻辑

* 添加Bioyond实验配置文件,定义物料类型映射和设备配置

* 更新bioyond_warehouse_reagent_stack方法,修正试剂堆栈尺寸和布局描述

* 更新Bioyond实验配置,修正物料类型映射,优化设备配置

* 更新Bioyond资源同步逻辑,优化物料入库流程,增强错误处理和日志记录

* 更新Bioyond资源,添加配液站和反应站专用载架,优化仓库工厂函数的排序方式

* 更新Bioyond资源,添加配液站和反应站相关载架,优化试剂瓶和样品瓶配置

* 更新Bioyond实验配置,修正试剂瓶载架ID,确保与设备匹配

* 更新Bioyond资源,移除反应站单烧杯载架,添加反应站单烧瓶载架分类

* Refactor Bioyond resource synchronization and update bottle carrier definitions

- Removed traceback printing in error handling for Bioyond synchronization.
- Enhanced logging for existing Bioyond material ID usage during synchronization.
- Added new bottle carrier definitions for single flask and updated existing ones.
- Refactored dispensing station and reaction station bottle definitions for clarity and consistency.
- Improved resource mapping and error handling in graphio for Bioyond resource conversion.
- Introduced layout parameter in warehouse factory for better warehouse configuration.

* 更新Bioyond仓库工厂,添加排序方式支持,优化坐标计算逻辑

* 更新Bioyond载架和甲板配置,调整样品板尺寸和仓库坐标

* 更新Bioyond资源同步,增强占用位置日志信息,修正坐标转换逻辑

* 更新Bioyond反应站和分配站配置,调整材料类型映射和ID,移除不必要的项

* support name change during materials change

* fix json dumps

* correct tip

* 优化调度器API路径,更新相关方法描述

* 更新 BIOYOND 载架相关文档,调整 API 以支持自带试剂瓶的载架类型,修复资源获取时的子物料处理逻辑

* 实现资源删除时的同步处理,优化出库操作逻辑

* 修复 ItemizedCarrier 中的可见性逻辑

* 保存 Bioyond 原始信息到 unilabos_extra,以便出库时查询

* 根据 resource.capacity 判断是试剂瓶(载架)还是多瓶载架,走不同的奔曜转换

* Fix bioyond bottle_carriers ordering

* 优化 Bioyond 物料同步逻辑,增强坐标解析和位置更新处理

* disable slave connect websocket

* correct remove_resource stats

* change uuid logger to trace level

* enable slave mode

* refactor(bioyond): 统一资源命名并优化物料同步逻辑

- 将DispensingStation和ReactionStation资源统一为PolymerStation命名
- 优化物料同步逻辑,支持耗材类型(typeMode=0)的查询
- 添加物料默认参数配置功能
- 调整仓库坐标布局
- 清理废弃资源定义

* feat(warehouses): 为仓库函数添加col_offset和layout参数

* refactor: 更新实验配置中的物料类型映射命名

将DispensingStation和ReactionStation的物料类型映射统一更名为PolymerStation,保持命名一致性

* fix: 更新实验配置中的载体名称从6VialCarrier到6StockCarrier

* feat(bioyond): 实现物料创建与入库分离逻辑

将物料同步流程拆分为两个独立阶段:transfer阶段只创建物料,add阶段执行入库
简化状态检查接口,仅返回连接状态

* fix(reaction_station): 修正液体进料烧杯体积单位并增强返回结果

将液体进料烧杯的体积单位从μL改为g以匹配实际使用场景
在返回结果中添加merged_workflow和order_params字段,提供更完整的工作流信息

* feat(dispensing_station): 在任务创建返回结果中添加order_params信息

在create_order方法返回结果中增加order_params字段,以便调用方获取完整的任务参数

* fix(dispensing_station): 修改90%物料分配逻辑从分成3份改为直接使用

原逻辑将主称固体平均分成3份作为90%物料,现改为直接使用main_portion

* feat(bioyond): 添加任务编码和任务ID的输出,支持批量任务创建后的状态监控

* refactor(registry): 简化设备配置中的任务结果处理逻辑

将多个单独的任务编码和ID字段合并为统一的return_info字段
更新相关描述以反映新的数据结构

* feat(工作站): 添加HTTP报送服务和任务完成状态跟踪

- 在graphio.py中添加API必需字段
- 实现工作站HTTP服务启动和停止逻辑
- 添加任务完成状态跟踪字典和等待方法
- 重写任务完成报送处理方法记录状态
- 支持批量任务完成等待和报告获取

* refactor(dispensing_station): 移除wait_for_order_completion_and_get_report功能

该功能已被wait_for_multiple_orders_and_get_reports替代,简化代码结构

* fix: 更新任务报告API错误

* fix(workstation_http_service): 修复状态查询中device_id获取逻辑

处理状态查询时安全获取device_id,避免因属性不存在导致的异常

* fix(bioyond_studio): 改进物料入库失败时的错误处理和日志记录

在物料入库API调用失败时,添加更详细的错误信息打印
同时修正station.py中对空响应和失败情况的判断逻辑

* refactor(bioyond): 优化瓶架载体的分配逻辑和注释说明

重构瓶架载体的分配逻辑,使用嵌套循环替代硬编码索引分配
添加更详细的坐标映射说明,明确PLR与Bioyond坐标的对应关系

* fix(bioyond_rpc): 修复物料入库成功时无data字段返回空的问题

当API返回成功但无data字段时,返回包含success标识的字典而非空字典

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@dp.tech>

* nmr

* Update devices

* bump version to 0.10.10

* Update repo files.

* Add get_resource_with_dir & get_resource method

* fix camera & workstation & warehouse & reaction station driver

* update docs, test examples
fix liquid_handler init bug

* bump version to 0.10.11

* Add startup_json_path, disable_browser, port config

* Update oss config

* feat(bioyond_studio): 添加项目API接口支持及优化物料管理功能

添加通用项目API接口方法(_post_project_api, _delete_project_api)用于与LIMS系统交互
实现compute_experiment_design方法用于实验设计计算
新增brief_step_parameters等订单相关接口方法
优化物料转移逻辑,增加异步任务处理
扩展BioyondV1RPC类,添加批量物料操作、订单状态管理等功能

* feat(bioyond): 添加测量小瓶仓库和更新仓库工厂函数参数

* Support unilabos_samples key

* add session_id and normal_exit

* Add result schema and add TypedDict conversion.

* Fix port error

* Add backend api and update doc

* Add get_regular_container func

* Add get_regular_container func

* Transfer_liquid (#176)

* change 9320 desk row number to 4

* Updated 9320 host address

* Updated 9320 host address

* Add **kwargs in classes: PRCXI9300Deck and PRCXI9300Container

* Removed all sample_id in prcxi_9320.json to avoid KeyError

* 9320 machine testing settings

* Typo

* Typo in base_device_node.py

* Enhance liquid handling functionality by adding support for multiple transfer modes (one-to-many, one-to-one, many-to-one) and improving parameter validation. Default channel usage is set when not specified. Adjusted mixing logic to ensure it only occurs when valid conditions are met. Updated documentation for clarity.

* Auto dump logs, fix workstation input schema

* Fix startup with remote resource error

Resource dict fully change to "pose" key

Update oss link

Reduce pylabrobot conversion warning & force enable log dump.

更新 logo 图片

* signal when host node is ready

* fix ros2 future

print all logs to file
fix resource dict dump error

* update version to 0.10.12

* 修改sample_uuid的返回值

* 修改pose标签设定机制

* 添加 aspiate函数返回值

* 返回dispense后的sample_uuid

* 添加self.pending_liquids_dict的重置方法

* 修改prcxi的json文件,解决trach错误问题

* 修改prcxijson,防止PlateT4的硬件错误

* 对laiyu移液站进行部分修改,取消多次初始化的问题

* 修改根据新的物料格式,修改可视化

* 添加切换枪头方法,添加mock振荡与加热方法

* 夹爪添加

* 删除多余的laiyu部分

* 云端可启动夹爪

* Delete __init__.py

* Enhance PRCXI9300 classes with new Container and TipRack implementations, improving state management and initialization logic. Update JSON configuration to reflect type changes for containers and plates.

* 修改上传数据

---------

Co-authored-by: Junhan Chang <changjh@dp.tech>
Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com>
Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com>
Co-authored-by: LccLink <1951855008@qq.com>
Co-authored-by: lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com>
Co-authored-by: shiyubo0410 <shiyubo@dp.tech>
Co-authored-by: hh.(SII) <103566763+Mile-Away@users.noreply.github.com>
Co-authored-by: Xianwei Qi <qxw@stu.pku.edu.cn>
Co-authored-by: WenzheG <wenzheguo32@gmail.com>
Co-authored-by: Harry Liu <113173203+ALITTLELZ@users.noreply.github.com>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: tt <166512503+tt11142023@users.noreply.github.com>
Co-authored-by: xyc <49015816+xiaoyu10031@users.noreply.github.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: zhangshixiang <554662886@qq.com>
Co-authored-by: ALITTLELZ <l_LZlz@163.com>
Xuwznln pushed a commit that referenced this pull request Dec 25, 2025
update registry

do not modify axis globally

Prcix9320 (#207)

* 0.10.7 Update (#101)

* Cleanup registry to be easy-understanding (#76)

* delete deprecated mock devices

* rename categories

* combine chromatographic devices

* rename rviz simulation nodes

* organic virtual devices

* parse vessel_id

* run registry completion before merge

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>

* fix: workstation handlers and vessel_id parsing

* fix: working dir error when input config path
feat: report publish topic when error

* modify default discovery_interval to 15s

* feat: add trace log level

* feat: 添加ChinWe设备控制类,支持串口通信和电机控制功能 (#79)

* fix: drop_tips not using auto resource select

* fix: discard_tips error

* fix: discard_tips

* fix: prcxi_res

* add: prcxi res
fix: startup slow

* feat: workstation example

* fix pumps and liquid_handler handle

* feat: 优化protocol node节点运行日志

* fix all protocol_compilers and remove deprecated devices

* feat: 新增use_remote_resource参数

* fix and remove redundant info

* bugfixes on organic protocols

* fix filter protocol

* fix protocol node

* 临时兼容错误的driver写法

* fix: prcxi import error

* use call_async in all service to avoid deadlock

* fix: figure_resource

* Update recipe.yaml

* add workstation template and battery example

* feat: add sk & ak

* update workstation base

* Create workstation_architecture.md

* refactor: workstation_base 重构为仅含业务逻辑,通信和子设备管理交给 ProtocolNode

* refactor: ProtocolNode→WorkstationNode

* Add:msgs.action (#83)

* update: Workstation dev 将版本号从 0.10.3 更新为 0.10.4 (#84)

* Add:msgs.action

* update: 将版本号从 0.10.3 更新为 0.10.4

* simplify resource system

* uncompleted refactor

* example for use WorkstationBase

* feat: websocket

* feat: websocket test

* feat: workstation example

* feat: action status

* fix: station自己的方法注册错误

* fix: 还原protocol node处理方法

* fix: build

* fix: missing job_id key

* ws test version 1

* ws test version 2

* ws protocol

* 增加物料关系上传日志

* 增加物料关系上传日志

* 修正物料关系上传

* 修复工站的tracker实例追踪失效问题

* 增加handle检测,增加material edge关系上传

* 修复event loop错误

* 修复edge上报错误

* 修复async错误

* 更新schema的title字段

* 主机节点信息等支持自动刷新

* 注册表编辑器

* 修复status密集发送时,消息出错

* 增加addr参数

* fix: addr param

* fix: addr param

* 取消labid 和 强制config输入

* Add action definitions for LiquidHandlerSetGroup and LiquidHandlerTransferGroup

- Created LiquidHandlerSetGroup.action with fields for group name, wells, and volumes.
- Created LiquidHandlerTransferGroup.action with fields for source and target group names and unit volume.
- Both actions include response fields for return information and success status.

* Add LiquidHandlerSetGroup and LiquidHandlerTransferGroup actions to CMakeLists

* Add set_group and transfer_group methods to PRCXI9300Handler and update liquid_handler.yaml

* result_info改为字典类型

* 新增uat的地址替换

* runze multiple pump support

(cherry picked from commit 49354fc)

* remove runze multiple software obtainer

(cherry picked from commit 8bcc92a)

* support multiple backbone

(cherry picked from commit 4771ff2)

* Update runze pump format

* Correct runze multiple backbone

* Update runze_multiple_backbone

* Correct runze pump multiple receive method.

* Correct runze pump multiple receive method.

* 对于PRCXI9320的transfer_group,一对多和多对多

* 移除MQTT,更新launch文档,提供注册表示例文件,更新到0.10.5

* fix import error

* fix dupe upload registry

* refactor ws client

* add server timeout

* Fix: run-column with correct vessel id (#86)

* fix run_column

* Update run_column_protocol.py

(cherry picked from commit e5aa4d9)

* resource_update use resource_add

* 新增版位推荐功能

* 重新规定了版位推荐的入参

* update registry with nested obj

* fix protocol node log_message, added create_resource return value

* fix protocol node log_message, added create_resource return value

* try fix add protocol

* fix resource_add

* 修复移液站错误的aspirate注册表

* Feature/xprbalance-zhida (#80)

* feat(devices): add Zhida GC/MS pretreatment automation workstation

* feat(devices): add mettler_toledo xpr balance

* balance

* 重新补全zhida注册表

* PRCXI9320 json

* PRCXI9320 json

* PRCXI9320 json

* fix resource download

* remove class for resource

* bump version to 0.10.6

* 更新所有注册表

* 修复protocolnode的兼容性

* 修复protocolnode的兼容性

* Update install md

* Add Defaultlayout

* 更新物料接口

* fix dict to tree/nested-dict converter

* coin_cell_station draft

* refactor: rename "station_resource" to "deck"

* add standardized BIOYOND resources: bottle_carrier, bottle

* refactor and add BIOYOND resources tests

* add BIOYOND deck assignment and pass all tests

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92)

* feat: 新威电池测试系统驱动与注册文件

* feat: bring neware driver & battery.json into workstation_dev_YB2

* add bioyond studio draft

* bioyond station with communication init and resource sync

* fix bioyond station and registry

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* frontend_docs

* create/update resources with POST/PUT for big amount/ small amount data

* create/update resources with POST/PUT for big amount/ small amount data

* refactor: add itemized_carrier instead of carrier consists of ResourceHolder

* create warehouse by factory func

* update bioyond launch json

* add child_size for itemized_carrier

* fix bioyond resource io

* Workstation templates: Resources and its CRUD, and workstation tasks (#95)

* coin_cell_station draft

* refactor: rename "station_resource" to "deck"

* add standardized BIOYOND resources: bottle_carrier, bottle

* refactor and add BIOYOND resources tests

* add BIOYOND deck assignment and pass all tests

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92)

* feat: 新威电池测试系统驱动与注册文件

* feat: bring neware driver & battery.json into workstation_dev_YB2

* add bioyond studio draft

* bioyond station with communication init and resource sync

* fix bioyond station and registry

* create/update resources with POST/PUT for big amount/ small amount data

* refactor: add itemized_carrier instead of carrier consists of ResourceHolder

* create warehouse by factory func

* update bioyond launch json

* add child_size for itemized_carrier

* fix bioyond resource io

---------

Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com>
Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com>

* 更新物料接口

* Workstation dev yb2 (#100)

* Refactor and extend reaction station action messages

* Refactor dispensing station tasks to enhance parameter clarity and add batch processing capabilities

- Updated `create_90_10_vial_feeding_task` to include detailed parameters for 90%/10% vial feeding, improving clarity and usability.
- Introduced `create_batch_90_10_vial_feeding_task` for batch processing of 90%/10% vial feeding tasks with JSON formatted input.
- Added `create_batch_diamine_solution_task` for batch preparation of diamine solution, also utilizing JSON formatted input.
- Refined `create_diamine_solution_task` to include additional parameters for better task configuration.
- Enhanced schema descriptions and default values for improved user guidance.

* 修复to_plr_resources

* add update remove

* 支持选择器注册表自动生成
支持转运物料

* 修复资源添加

* 修复transfer_resource_to_another生成

* 更新transfer_resource_to_another参数,支持spot入参

* 新增test_resource动作

* fix host_node error

* fix host_node test_resource error

* fix host_node test_resource error

* 过滤本地动作

* 移动内部action以兼容host node

* 修复同步任务报错不显示的bug

* feat: 允许返回非本节点物料,后面可以通过decoration进行区分,就不进行warning了

* update todo

* modify bioyond/plr converter, bioyond resource registry, and tests

* pass the tests

* update todo

* add conda-pack-build.yml

* add auto install script for conda-pack-build.yml

(cherry picked from commit 172599a)

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* Add version in __init__.py
Update conda-pack-build.yml
Add create_zip_archive.py

* Update conda-pack-build.yml

* Update conda-pack-build.yml (with mamba)

* Update conda-pack-build.yml

* Fix FileNotFoundError

* Try fix 'charmap' codec can't encode characters in position 16-23: character maps to <undefined>

* Fix unilabos msgs search error

* Fix environment_check.py

* Update recipe.yaml

* Update registry. Update uuid loop figure method. Update install docs.

* Fix nested conda pack

* Fix one-key installation path error

* Bump version to 0.10.7

* Workshop bj (#99)

* Add LaiYu Liquid device integration and tests

Introduce LaiYu Liquid device implementation, including backend, controllers, drivers, configuration, and resource files. Add hardware connection, tip pickup, and simplified test scripts, as well as experiment and registry configuration for LaiYu Liquid. Documentation and .gitignore for the device are also included.

* feat(LaiYu_Liquid): 重构设备模块结构并添加硬件文档

refactor: 重新组织LaiYu_Liquid模块目录结构
docs: 添加SOPA移液器和步进电机控制指令文档
fix: 修正设备配置中的最大体积默认值
test: 新增工作台配置测试用例
chore: 删除过时的测试脚本和配置文件

* add

* 重构: 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py 并更新所有导入引用

- 使用 git mv 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py
- 更新所有相关文件中的导入引用
- 保持代码功能不变,仅改善命名一致性
- 测试确认所有导入正常工作

* 修复: 在 core/__init__.py 中添加 LaiYuLiquidBackend 导出

- 添加 LaiYuLiquidBackend 到导入列表
- 添加 LaiYuLiquidBackend 到 __all__ 导出列表
- 确保所有主要类都可以正确导入

* 修复大小写文件夹名字

* 电池装配工站二次开发教程(带目录)上传至dev (#94)

* 电池装配工站二次开发教程

* Update intro.md

* 物料教程

* 更新物料教程,json格式注释

* Update prcxi driver & fix transfer_liquid mix_times (#90)

* Update prcxi driver & fix transfer_liquid mix_times

* fix: correct mix_times type

* Update liquid_handler registry

* test: prcxi.py

* Update registry from pr

* fix ony-key script not exist

* clean files

---------

Co-authored-by: Junhan Chang <changjh@dp.tech>
Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com>
Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com>
Co-authored-by: LccLink <1951855008@qq.com>
Co-authored-by: lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com>
Co-authored-by: shiyubo0410 <shiyubo@dp.tech>

* fix startup env check.
add auto install during one-key installation

* Try fix one-key build on linux

* Complete all one key installation

* fix: rename schema field to resource_schema with serialization and validation aliases (#104)

Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>

* Fix one-key installation build

Install conda-pack before pack command

Add conda-pack to base when building one-key installer

Fix param error when using mamba run

Try fix one-key build on linux

* Fix conda pack on windows

* add plr_to_bioyond, and refactor bioyond stations

* modify default config

* Fix one-key installation build for windows

* Fix workstation startup
Update registry

* Fix/resource UUID and doc fix (#109)

* Fix ResourceTreeSet load error

* Raise error when using unsupported type to create ResourceTreeSet

* Fix children key error

* Fix children key error

* Fix workstation resource not tracking

* Fix workstation deck & children resource dupe

* Fix workstation deck & children resource dupe

* Fix multiple resource error

* Fix resource tree update

* Fix resource tree update

* Force confirm uuid

* Tip more error log

* Refactor Bioyond workstation and experiment workflow (#105)

Refactored the Bioyond workstation classes to improve parameter handling and workflow management. Updated experiment.py to use BioyondReactionStation with deck and material mappings, and enhanced workflow step parameter mapping and execution logic. Adjusted JSON experiment configs, improved workflow sequence handling, and added UUID assignment to PLR materials. Removed unused station_config and material cache logic, and added detailed docstrings and debug output for workflow methods.

* Fix resource get.
Fix resource parent not found.
Mapping uuid for all resources.

* mount parent uuid

* Add logging configuration based on BasicConfig in main function

* fix workstation node error

* fix workstation node error

* Update boot example

* temp fix for resource get

* temp fix for resource get

* provide error info when cant find plr type

* pack repo info

* fix to plr type error

* fix to plr type error

* Update regular container method

* support no size init

* fix comprehensive_station.json

* fix comprehensive_station.json

* fix type conversion

* fix state loading for regular container

* Update deploy-docs.yml

* Update deploy-docs.yml

---------

Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>

* Close #107
Update doc url.

* Fix/update resource (#112)

* cancel upload_registry

* Refactor Bioyond workstation and experiment workflow -fix (#111)

* refactor(bioyond_studio): 优化材料缓存加载和参数验证逻辑

改进材料缓存加载逻辑以支持多种材料类型和详细材料处理
更新工作流参数验证中的字段名从key/value改为Key/DisplayValue
移除未使用的merge_workflow_with_parameters方法
添加get_station_info方法获取工作站基础信息
清理实验文件中的注释代码和更新导入路径

* fix: 修复资源移除时的父资源检查问题

在BaseROS2DeviceNode中,移除资源前添加对父资源是否为None的检查,避免空指针异常
同时更新Bottle和BottleCarrier类以支持**kwargs参数
修正测试文件中Liquid_feeding_beaker的大小写拼写错误

* correct return message

---------

Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>

* fix resource_get in action

* fix(reaction_station): 清空工作流序列和参数避免重复执行 (#113)

在创建任务后清空工作流序列和参数,防止下次执行时累积重复

* Update create_resource device_id

* Update ResourceTracker

add more enumeration in POSE

fix converter in resource_tracker

* Update graphio together with workstation design.

fix(reaction_station): 为步骤参数添加Value字段传个BY后端

fix(bioyond/warehouses): 修正仓库尺寸和物品排列参数

调整仓库的x轴和z轴物品数量以及物品尺寸参数,使其符合4x1x4的规格要求

fix warehouse serialize/deserialize

fix bioyond converter

fix itemized_carrier.unassign_child_resource

allow not-loaded MSG in registry

add layout serializer & converter

warehouseuse A1-D4; add warehouse layout

fix(graphio): 修正bioyond到plr资源转换中的坐标计算错误

Fix resource assignment and type mapping issues

Corrects resource assignment in ItemizedCarrier by using the correct spot key from _ordering. Updates graphio to use 'typeName' instead of 'name' for type mapping in resource_bioyond_to_plr. Renames DummyWorkstation to BioyondWorkstation in workstation_http_service for clarity.

* Update workstation & bioyond example

Refine descriptions in Bioyond reaction station YAML

Updated and clarified field and operation descriptions in the reaction_station_bioyond.yaml file for improved accuracy and consistency. Changes include more precise terminology, clearer parameter explanations, and standardized formatting for operation schemas.

refactor(workstation): 更新反应站参数描述并添加分液站配置文件

修正反应站方法参数描述,使其更准确清晰
添加bioyond_dispensing_station.yaml配置文件

add create_workflow script and test

add invisible_slots to carriers

fix(warehouses): 修正bioyond_warehouse_1x4x4仓库的尺寸参数

调整仓库的num_items_x和num_items_z值以匹配实际布局,并更新物品尺寸参数

save resource get data. allow empty value for layout and cross_section_type

More decks&plates support for bioyond (#115)

refactor(registry): 重构反应站设备配置,简化并更新操作命令

移除旧的自动操作命令,新增针对具体化学操作的命令配置
更新模块路径和配置结构,优化参数定义和描述

fix(dispensing_station): 修正物料信息查询方法调用

将直接调用material_id_query改为通过hardware_interface调用,以符合接口设计规范

* PRCXI Update

修改prcxi连线

prcxi样例图

Create example_prcxi.json

* Update resource extra & uuid.

use ordering to convert identifier to idx

convert identifier to site idx

correct extra key

update extra before transfer

fix multiple instance error

add resource_tree_transfer func

fox itemrized carrier assign child resource

support internal device material transfer

remove extra key

use same callback group

support material extra

support material extra
support update_resource_site in extra

* Update workstation.

modify workstation_architecture docs

bioyond_HR (#133)

* feat: Enhance Bioyond synchronization and resource management

- Implemented synchronization for all material types (consumables, samples, reagents) from Bioyond, logging detailed information for each type.
- Improved error handling and logging during synchronization processes.
- Added functionality to save Bioyond material IDs in UniLab resources for future updates.
- Enhanced the `sync_to_external` method to handle material movements correctly, including querying and creating materials in Bioyond.
- Updated warehouse configurations to support new storage types and improved layout for better resource management.
- Introduced new resource types such as reactors and tip boxes, with detailed specifications.
- Modified warehouse factory to support column offsets for naming conventions (e.g., A05-D08).
- Improved resource tracking by merging extra attributes instead of overwriting them.
- Added a new method for updating resources in Bioyond, ensuring better synchronization of resource changes.

* feat: 添加TipBox和Reactor的配置到bottles.yaml

* fix: 修复液体投料方法中的volume参数处理逻辑

修复solid_feeding_vials方法中的volume参数处理逻辑,优化solvents参数的使用条件

更新液体投料方法,支持通过溶剂信息自动计算体积,添加solvents参数并更新文档描述

Add batch creation methods for vial and solution tasks

添加批量创建90%10%小瓶投料任务和二胺溶液配置任务的功能,更新相关参数和默认值

* 封膜仪、撕膜仪、耗材站接口

* 添加Raman和xrd相关代码

* Resource update & asyncio fix

correct bioyond config

prcxi example

fix append_resource

fix regularcontainer

fix cancel error

fix resource_get param

fix json dumps

support name change during materials change

enable slave mode

change uuid logger to trace level

correct remove_resource stats

disable slave connect websocket

adjust with_children param

modify devices to use correct executor (sleep, create_task)

support sleep and create_task in node

fix run async execution error

* bump version to 0.10.9

update registry

* PRCXI Reset Error Correction (#166)

* change 9320 desk row number to 4

* Updated 9320 host address

* Updated 9320 host address

* Add **kwargs in classes: PRCXI9300Deck and PRCXI9300Container

* Removed all sample_id in prcxi_9320.json to avoid KeyError

* 9320 machine testing settings

* Typo

* Rewrite setup logic to clear error code

* 初始化 step_mode 属性

* 1114物料手册定义教程byxinyu (#165)

* 宜宾奔耀工站deck前端by_Xinyu

* 构建物料教程byxinyu

* 1114物料手册定义教程

* 3d sim (#97)

* 修改lh的json启动

* 修改lh的json启动

* 修改backend,做成sim的通用backend

* 修改yaml的地址,3D模型适配网页生产环境

* 添加laiyu硬件连接

* 修改移液枪的状态判断方法,

修改移液枪的状态判断方法,
添加三轴的表定点与零点之间的转换
添加三轴真实移动的backend

* 修改laiyu移液站

简化移动方法,
取消软件限制位置,
修改当值使用Z轴时也需要重新复位Z轴的问题

* 更新lh以及laiyu workshop

1,现在可以直接通过修改backend,适配其他的移液站,主类依旧使用LiquidHandler,不用重新编写

2,修改枪头判断标准,使用枪头自身判断而不是类的判断,

3,将归零参数用毫米计算,方便手动调整,

4,修改归零方式,上电使用机械归零,确定机械零点,手动归零设置工作区域零点方便计算,二者互不干涉

* 修改枪头动作

* 修改虚拟仿真方法

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: Junhan Chang <changjh@dp.tech>

* 标准化opcua设备接入unilab (#78)

* 初始提交,只保留工作区当前状态

* remove redundant arm_slider meshes

---------

Co-authored-by: Junhan Chang <changjh@dp.tech>

* add new laiyu liquid driver, yaml and json files (#164)

* HR物料同步,前端展示位置修复 (#135)

* 更新Bioyond工作站配置,添加新的物料类型映射和载架定义,优化物料查询逻辑

* 添加Bioyond实验配置文件,定义物料类型映射和设备配置

* 更新bioyond_warehouse_reagent_stack方法,修正试剂堆栈尺寸和布局描述

* 更新Bioyond实验配置,修正物料类型映射,优化设备配置

* 更新Bioyond资源同步逻辑,优化物料入库流程,增强错误处理和日志记录

* 更新Bioyond资源,添加配液站和反应站专用载架,优化仓库工厂函数的排序方式

* 更新Bioyond资源,添加配液站和反应站相关载架,优化试剂瓶和样品瓶配置

* 更新Bioyond实验配置,修正试剂瓶载架ID,确保与设备匹配

* 更新Bioyond资源,移除反应站单烧杯载架,添加反应站单烧瓶载架分类

* Refactor Bioyond resource synchronization and update bottle carrier definitions

- Removed traceback printing in error handling for Bioyond synchronization.
- Enhanced logging for existing Bioyond material ID usage during synchronization.
- Added new bottle carrier definitions for single flask and updated existing ones.
- Refactored dispensing station and reaction station bottle definitions for clarity and consistency.
- Improved resource mapping and error handling in graphio for Bioyond resource conversion.
- Introduced layout parameter in warehouse factory for better warehouse configuration.

* 更新Bioyond仓库工厂,添加排序方式支持,优化坐标计算逻辑

* 更新Bioyond载架和甲板配置,调整样品板尺寸和仓库坐标

* 更新Bioyond资源同步,增强占用位置日志信息,修正坐标转换逻辑

* 更新Bioyond反应站和分配站配置,调整材料类型映射和ID,移除不必要的项

* support name change during materials change

* fix json dumps

* correct tip

* 优化调度器API路径,更新相关方法描述

* 更新 BIOYOND 载架相关文档,调整 API 以支持自带试剂瓶的载架类型,修复资源获取时的子物料处理逻辑

* 实现资源删除时的同步处理,优化出库操作逻辑

* 修复 ItemizedCarrier 中的可见性逻辑

* 保存 Bioyond 原始信息到 unilabos_extra,以便出库时查询

* 根据 resource.capacity 判断是试剂瓶(载架)还是多瓶载架,走不同的奔曜转换

* Fix bioyond bottle_carriers ordering

* 优化 Bioyond 物料同步逻辑,增强坐标解析和位置更新处理

* disable slave connect websocket

* correct remove_resource stats

* change uuid logger to trace level

* enable slave mode

* refactor(bioyond): 统一资源命名并优化物料同步逻辑

- 将DispensingStation和ReactionStation资源统一为PolymerStation命名
- 优化物料同步逻辑,支持耗材类型(typeMode=0)的查询
- 添加物料默认参数配置功能
- 调整仓库坐标布局
- 清理废弃资源定义

* feat(warehouses): 为仓库函数添加col_offset和layout参数

* refactor: 更新实验配置中的物料类型映射命名

将DispensingStation和ReactionStation的物料类型映射统一更名为PolymerStation,保持命名一致性

* fix: 更新实验配置中的载体名称从6VialCarrier到6StockCarrier

* feat(bioyond): 实现物料创建与入库分离逻辑

将物料同步流程拆分为两个独立阶段:transfer阶段只创建物料,add阶段执行入库
简化状态检查接口,仅返回连接状态

* fix(reaction_station): 修正液体进料烧杯体积单位并增强返回结果

将液体进料烧杯的体积单位从μL改为g以匹配实际使用场景
在返回结果中添加merged_workflow和order_params字段,提供更完整的工作流信息

* feat(dispensing_station): 在任务创建返回结果中添加order_params信息

在create_order方法返回结果中增加order_params字段,以便调用方获取完整的任务参数

* fix(dispensing_station): 修改90%物料分配逻辑从分成3份改为直接使用

原逻辑将主称固体平均分成3份作为90%物料,现改为直接使用main_portion

* feat(bioyond): 添加任务编码和任务ID的输出,支持批量任务创建后的状态监控

* refactor(registry): 简化设备配置中的任务结果处理逻辑

将多个单独的任务编码和ID字段合并为统一的return_info字段
更新相关描述以反映新的数据结构

* feat(工作站): 添加HTTP报送服务和任务完成状态跟踪

- 在graphio.py中添加API必需字段
- 实现工作站HTTP服务启动和停止逻辑
- 添加任务完成状态跟踪字典和等待方法
- 重写任务完成报送处理方法记录状态
- 支持批量任务完成等待和报告获取

* refactor(dispensing_station): 移除wait_for_order_completion_and_get_report功能

该功能已被wait_for_multiple_orders_and_get_reports替代,简化代码结构

* fix: 更新任务报告API错误

* fix(workstation_http_service): 修复状态查询中device_id获取逻辑

处理状态查询时安全获取device_id,避免因属性不存在导致的异常

* fix(bioyond_studio): 改进物料入库失败时的错误处理和日志记录

在物料入库API调用失败时,添加更详细的错误信息打印
同时修正station.py中对空响应和失败情况的判断逻辑

* refactor(bioyond): 优化瓶架载体的分配逻辑和注释说明

重构瓶架载体的分配逻辑,使用嵌套循环替代硬编码索引分配
添加更详细的坐标映射说明,明确PLR与Bioyond坐标的对应关系

* fix(bioyond_rpc): 修复物料入库成功时无data字段返回空的问题

当API返回成功但无data字段时,返回包含success标识的字典而非空字典

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@dp.tech>

* nmr

* Update devices

* bump version to 0.10.10

* Update repo files.

* Add get_resource_with_dir & get_resource method

* fix camera & workstation & warehouse & reaction station driver

* update docs, test examples
fix liquid_handler init bug

* bump version to 0.10.11

* Add startup_json_path, disable_browser, port config

* Update oss config

* feat(bioyond_studio): 添加项目API接口支持及优化物料管理功能

添加通用项目API接口方法(_post_project_api, _delete_project_api)用于与LIMS系统交互
实现compute_experiment_design方法用于实验设计计算
新增brief_step_parameters等订单相关接口方法
优化物料转移逻辑,增加异步任务处理
扩展BioyondV1RPC类,添加批量物料操作、订单状态管理等功能

* feat(bioyond): 添加测量小瓶仓库和更新仓库工厂函数参数

* Support unilabos_samples key

* add session_id and normal_exit

* Add result schema and add TypedDict conversion.

* Fix port error

* Add backend api and update doc

* Add get_regular_container func

* Add get_regular_container func

* Transfer_liquid (#176)

* change 9320 desk row number to 4

* Updated 9320 host address

* Updated 9320 host address

* Add **kwargs in classes: PRCXI9300Deck and PRCXI9300Container

* Removed all sample_id in prcxi_9320.json to avoid KeyError

* 9320 machine testing settings

* Typo

* Typo in base_device_node.py

* Enhance liquid handling functionality by adding support for multiple transfer modes (one-to-many, one-to-one, many-to-one) and improving parameter validation. Default channel usage is set when not specified. Adjusted mixing logic to ensure it only occurs when valid conditions are met. Updated documentation for clarity.

* Auto dump logs, fix workstation input schema

* Fix startup with remote resource error

Resource dict fully change to "pose" key

Update oss link

Reduce pylabrobot conversion warning & force enable log dump.

更新 logo 图片

* signal when host node is ready

* fix ros2 future

print all logs to file
fix resource dict dump error

* update version to 0.10.12

* 修改sample_uuid的返回值

* 修改pose标签设定机制

* 添加 aspiate函数返回值

* 返回dispense后的sample_uuid

* 添加self.pending_liquids_dict的重置方法

* 修改prcxi的json文件,解决trach错误问题

* 修改prcxijson,防止PlateT4的硬件错误

* 对laiyu移液站进行部分修改,取消多次初始化的问题

* 修改根据新的物料格式,修改可视化

* 添加切换枪头方法,添加mock振荡与加热方法

* 夹爪添加

* 删除多余的laiyu部分

* 云端可启动夹爪

* Delete __init__.py

* Enhance PRCXI9300 classes with new Container and TipRack implementations, improving state management and initialization logic. Update JSON configuration to reflect type changes for containers and plates.

* 修改上传数据

---------

Co-authored-by: Junhan Chang <changjh@dp.tech>
Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com>
Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com>
Co-authored-by: LccLink <1951855008@qq.com>
Co-authored-by: lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com>
Co-authored-by: shiyubo0410 <shiyubo@dp.tech>
Co-authored-by: hh.(SII) <103566763+Mile-Away@users.noreply.github.com>
Co-authored-by: Xianwei Qi <qxw@stu.pku.edu.cn>
Co-authored-by: WenzheG <wenzheguo32@gmail.com>
Co-authored-by: Harry Liu <113173203+ALITTLELZ@users.noreply.github.com>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: tt <166512503+tt11142023@users.noreply.github.com>
Co-authored-by: xyc <49015816+xiaoyu10031@users.noreply.github.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: zhangshixiang <554662886@qq.com>
Co-authored-by: ALITTLELZ <l_LZlz@163.com>

Add topic config

add camera driver (#191)

* add camera driver

* add init.py file to cameraSII driver

增强新威电池测试系统 OSS 上传功能 / Enhanced Neware Battery Test System OSS Upload (#196)

* feat: neware-oss-upload-enhancement

* feat(neware): enhance OSS upload with metadata and workflow handles

Add post process station and related resources (#195)

* Add post process station and related resources

- Created JSON configuration for post_process_station and its child post_process_deck.
- Added YAML definitions for post_process_station, bottle carriers, bottles, and deck resources.
- Implemented Python classes for bottle carriers, bottles, decks, and warehouses to manage resources in the post process.
- Established a factory method for creating warehouses with customizable dimensions and layouts.
- Defined the structure and behavior of the post_process_deck and its associated warehouses.

* feat(post_process): add post_process_station and related warehouse functionality

- Introduced post_process_station.json to define the post-processing station structure.
- Implemented post_process_warehouse.py to create warehouse configurations with customizable layouts.
- Added warehouses.py for specific warehouse configurations (4x3x1).
- Updated post_process_station.yaml to reflect new module paths for OpcUaClient.
- Refactored bottle carriers and bottles YAML files to point to the new module paths.
- Adjusted deck.yaml to align with the new organizational structure for post_process_deck.

prcxi resource (#202)

* prcxi resource

* prcxi_resource

* Fix upload error not showing.
Support str type category.

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>

Fix upload error not showing.
Support str type category.

feat: introduce `wait_time` command and configurable device communication timeout.

feat: Add `SyringePump` (SY-03B) driver with unified serial/TCP transport for `chinwe` device, including registry and test configurations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants