Files
Uni-Lab-OS/CHANGES_2026_03_24.md

169 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 变更说明 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()`,避免重复初始化
```python
# 修复前(缺少 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 同步流程
```python
# 修复前
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_set``request_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: []`)的根本原因
```python
# 新增兜底
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()` 初始化
```python
# 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()
```
### 联动 Bug`MaterialPlate.create_with_holes` 构造顺序错误
**现象**`deck.setup()` 被调用后,启动时抛出:
```
设备后初始化失败: Must specify either `ordered_items` or `ordering`.
```
**根因**`create_with_holes` 原来的逻辑是先构造空的 `MaterialPlate` 实例,再 assign 洞位:
```python
# 旧错误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_items``ordering` 必须有一个不为 `None`,空构造直接失败。
**修复**:先建洞位,再作为 `ordered_items` 传给构造函数:
```python
# 新(正确):先建洞位,再一次性传入构造函数
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`,但该模块不存在,`ImportError``except Exception: pass` 静默吞掉
2. **降级路径搜错地方**:遍历 `self._plr_resources`Bioyond 自己的资源),不可能找到 BatteryStation 的 `bottle_rack_6x2`
### 修复文件
**`unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py`**
改用全局设备注册表 `registered_devices` 跨设备访问目标 deck
```python
# 修复前(失效)
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)
```
关键细节:`DeviceInfoType``TypedDict`(即普通 `dict`),必须用 `device_info.get("driver_instance")` 而非 `getattr(device_info, "driver_instance", None)`——后者对字典永远返回 `None`
---
## 根本原因分析
旧版以**本地文件模式**启动(有 `graph` 文件deck 在启动前已通过 `merge_remote_resources` 获得仓库子节点,反序列化时能正确恢复 warehouses。
新版以**远端模式**启动(`file_path=None`deck 反序列化时没有仓库子节点,`station.py` 扫描为空,所有物料的 warehouse 匹配失败Bioyond 同步的 16 个资源全部无法放置到对应仓库位,前端不显示。