Files
Uni-Lab-OS/CHANGES_2026_03_24.md

6.7 KiB
Raw Blame History

变更说明 2026-03-24

问题背景

BioyondElectrolyteDeck(原 BIOYOND_YB_Deck)迁移后,前端物料未能正常上传/同步。


修复内容

1. unilabos/resources/bioyond/decks.py

  • 补回 setup: bool = False 参数及 if setup: self.setup() 逻辑,与旧版 BIOYOND_YB_Deck 保持一致
  • 工厂函数 bioyond_electrolyte_deck 保留显式调用 deck.setup(),避免重复初始化
# 修复前(缺少 setup 参数,无法通过 setup=True 触发初始化)
def __init__(self, name, size_x, size_y, size_z, category):
    super().__init__(...)

# 修复后
def __init__(self, name, size_x, size_y, size_z, category, setup: bool = False):
    super().__init__(...)
    if setup:
        self.setup()

2. unilabos/resources/graphio.py

  • 修复 resource_bioyond_to_plr 中两处 bottle.tracker.liquids 直接赋值导致的崩溃
  • ResourceHolder(如枪头盒的 TipSpot 槽位)没有 tracker 属性,直接访问会抛出 AttributeError,阻断整个 Bioyond 同步流程
# 修复前
bottle.tracker.liquids = [...]

# 修复后
if hasattr(bottle, 'tracker') and bottle.tracker is not None:
    bottle.tracker.liquids = [...]

3. unilabos/app/main.py

  • 保留 file_path is not None 条件不变(已还原),并补充注释说明原因
  • 该逻辑只在本地文件模式下有意义:本地 graph 文件只含设备结构远端有已保存物料merge 才能将两者合并
  • 远端模式(file_path=None)下,resource_tree_setrequest_startup_json 来自同一份数据merge 为空操作,条件是否加 file_path is not None 对结果没有影响

4. unilabos/devices/workstation/bioyond_studio/station.py 核心修复

  • 当 deck 通过反序列化创建时,不会自动调用 setup(),导致 deck.children 为空,warehouses 始终是 {}
  • 增加兜底逻辑:仓库扫描后仍为空,则主动调用 deck.setup() 初始化仓库
  • 这是导致所有物料放置失败(warehouse '...' 在deck中不存在。可用warehouses: [])的根本原因
# 新增兜底
if not self.deck.warehouses and hasattr(self.deck, "setup") and callable(self.deck.setup):
    logger.info("Deck 无仓库子节点,调用 setup() 初始化仓库")
    self.deck.setup()


补充修复 2026-03-25依华扣电组装工站子物料未上传

问题

CoinCellAssemblyWorkstation.post_init 直接上传空 deck未调用 deck.setup(),导致:

  • 前端子物料(成品弹夹、料盘、瓶架等)不显示
  • 运行时 self.deck.get_resource("成品弹夹") 抛出 ResourceNotFoundError

修复文件

unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py

  • YihuaCoinCellDeck.__init__ 补回 setup: bool = False 参数及 if setup: self.setup() 逻辑

unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py

  • post_init 中增加与 Bioyond 工站相同的兜底逻辑deck 无子节点时调用 deck.setup() 初始化
# post_init 中新增
if self.deck and not self.deck.children and hasattr(self.deck, "setup") and callable(self.deck.setup):
    logger.info("YihuaCoinCellDeck 无子节点,调用 setup() 初始化")
    self.deck.setup()

联动 BugMaterialPlate.create_with_holes 构造顺序错误

现象deck.setup() 被调用后,启动时抛出:

设备后初始化失败: Must specify either `ordered_items` or `ordering`.

根因create_with_holes 原来的逻辑是先构造空的 MaterialPlate 实例,再 assign 洞位:

# 旧错误cls(...) 时 ordered_items=None → ItemizedResource.__init__ 立即报错
plate = cls(name=name, ...)          # ← 这里就崩了
holes = create_ordered_items_2d(...) # ← 根本没走到这里
for hole_name, hole in holes.items():
    plate.assign_child_resource(...)

pylabrobot 的 ItemizedResource.__init__ 强制要求 ordered_itemsordering 必须有一个不为 None,空构造直接失败。

修复:先建洞位,再作为 ordered_items 传给构造函数:

# 新(正确):先建洞位,再一次性传入构造函数
holes = create_ordered_items_2d(klass=MaterialHole, num_items_x=4, ...)
return cls(name=name, ..., ordered_items=holes)

此 bug 此前未被触发,是因为 deck.setup() 从未被调用到——正是上面 post_init 兜底修复引出的联动问题。


补充修复 2026-03-253→2→1 转运资源同步失败

问题

配液工站Bioyond完成分液后调用 transfer_3_to_2_to_1_auto 将分液瓶板转运到扣电工站BatteryStation。物理 LIMS 转运成功,但数字孪生资源树同步始终失败:

[资源同步] ❌ 失败: 目标设备 'BatteryStation' 中未找到资源 'bottle_rack_6x2'

根因

_get_resource_from_device 方法负责跨设备查找资源对象,有两个问题:

  1. 原始路径完全失效:尝试 from unilabos.app.ros2_app import get_device_plr_resource_by_name,但该模块不存在,ImportErrorexcept Exception: pass 静默吞掉
  2. 降级路径搜错地方:遍历 self._plr_resourcesBioyond 自己的资源),不可能找到 BatteryStation 的 bottle_rack_6x2

修复文件

unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py

改用全局设备注册表 registered_devices 跨设备访问目标 deck

# 修复前(失效)
from unilabos.app.ros2_app import get_device_plr_resource_by_name  # 模块不存在
return get_device_plr_resource_by_name(device_id, resource_name)

# 修复后
from unilabos.ros.nodes.base_device_node import registered_devices
device_info = registered_devices.get(device_id)
if device_info is not None:
    driver = device_info.get("driver_instance")  # TypedDict 是 dict必须用 .get()
    if driver is not None:
        deck = getattr(driver, "deck", None)
        if deck is not None:
            res = deck.get_resource(resource_name)

关键细节:DeviceInfoTypeTypedDict(即普通 dict),必须用 device_info.get("driver_instance") 而非 getattr(device_info, "driver_instance", None)——后者对字典永远返回 None


根本原因分析

旧版以本地文件模式启动(有 graph 文件deck 在启动前已通过 merge_remote_resources 获得仓库子节点,反序列化时能正确恢复 warehouses。

新版以远端模式启动(file_path=Nonedeck 反序列化时没有仓库子节点,station.py 扫描为空,所有物料的 warehouse 匹配失败Bioyond 同步的 16 个资源全部无法放置到对应仓库位,前端不显示。