mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-04-26 02:39:52 +00:00
169 lines
6.7 KiB
Markdown
169 lines
6.7 KiB
Markdown
# 变更说明 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-25:3→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 个资源全部无法放置到对应仓库位,前端不显示。
|