mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-04-26 03:19:53 +00:00
feat: update coin cell assembly, bioyond cell workstation, and resource configs
This commit is contained in:
168
CHANGES_2026_03_24.md
Normal file
168
CHANGES_2026_03_24.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# 变更说明 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 个资源全部无法放置到对应仓库位,前端不显示。
|
||||||
@@ -621,6 +621,8 @@ def main():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 如果从远端获取了物料信息,则与本地物料进行同步
|
# 如果从远端获取了物料信息,则与本地物料进行同步
|
||||||
|
# 仅在本地文件模式下有意义:本地文件只含设备结构,远端有已保存的物料,需要 merge
|
||||||
|
# 远端模式下 resource_tree_set 与 request_startup_json 来自同一份数据,merge 为空操作
|
||||||
if file_path is not None and request_startup_json and "nodes" in request_startup_json:
|
if file_path is not None and request_startup_json and "nodes" in request_startup_json:
|
||||||
print_status("开始同步远端物料到本地...", "info")
|
print_status("开始同步远端物料到本地...", "info")
|
||||||
remote_tree_set = ResourceTreeSet.from_raw_dict_list(request_startup_json["nodes"])
|
remote_tree_set = ResourceTreeSet.from_raw_dict_list(request_startup_json["nodes"])
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -979,10 +979,10 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
formulation: List[Dict[str, Any]],
|
formulation: List[Dict[str, Any]],
|
||||||
batch_id: str = "",
|
batch_id: str = "",
|
||||||
bottle_type: str = "配液小瓶",
|
bottle_type: str = "配液小瓶",
|
||||||
mix_time: int = 0,
|
mix_time: List[int] = [],
|
||||||
load_shedding_info: float = 0.0,
|
coin_cell_volume: float = 0.0,
|
||||||
pouch_cell_info: float = 0.0,
|
pouch_cell_volume: float = 0.0,
|
||||||
conductivity_info: float = 0.0,
|
conductivity_volume: float = 0.0,
|
||||||
conductivity_bottle_count: int = 0,
|
conductivity_bottle_count: int = 0,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@@ -1003,10 +1003,10 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
]
|
]
|
||||||
batch_id: 批次ID,若为空则用当前时间戳
|
batch_id: 批次ID,若为空则用当前时间戳
|
||||||
bottle_type: 配液瓶类型,默认 "配液小瓶"
|
bottle_type: 配液瓶类型,默认 "配液小瓶"
|
||||||
mix_time: 混匀时间(秒)
|
mix_time: 混匀时间列表(秒),与 formulation 一一对应,不足则补 0
|
||||||
load_shedding_info: 扣电组装分液体积
|
coin_cell_volume: 纽扣电池组装分液体积
|
||||||
pouch_cell_info: 软包组装分液体积
|
pouch_cell_volume: 软包电池注液组装分液体积
|
||||||
conductivity_info: 电导测试分液体积
|
conductivity_volume: 电导率测试分液体积
|
||||||
conductivity_bottle_count: 电导测试分液瓶数
|
conductivity_bottle_count: 电导测试分液瓶数
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -1039,9 +1039,10 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
logger.warning(f"[create_orders_formulation] 第 {idx + 1} 个配方无有效物料,跳过")
|
logger.warning(f"[create_orders_formulation] 第 {idx + 1} 个配方无有效物料,跳过")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
item_mix_time = mix_time[idx] if idx < len(mix_time) else 0
|
||||||
logger.info(f"[create_orders_formulation] 第 {idx + 1} 个配方: orderName={order_name}, "
|
logger.info(f"[create_orders_formulation] 第 {idx + 1} 个配方: orderName={order_name}, "
|
||||||
f"loadShedding={load_shedding_info}, pouchCell={pouch_cell_info}, "
|
f"coinCellVolume={coin_cell_volume}, pouchCellVolume={pouch_cell_volume}, "
|
||||||
f"conductivity={conductivity_info}, totalMass={total_mass}, "
|
f"conductivityVolume={conductivity_volume}, totalMass={total_mass}, "
|
||||||
f"material_count={len(mats)}")
|
f"material_count={len(mats)}")
|
||||||
|
|
||||||
orders.append({
|
orders.append({
|
||||||
@@ -1049,10 +1050,10 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
"orderName": order_name,
|
"orderName": order_name,
|
||||||
"createTime": create_time,
|
"createTime": create_time,
|
||||||
"bottleType": bottle_type,
|
"bottleType": bottle_type,
|
||||||
"mixTime": mix_time,
|
"mixTime": item_mix_time,
|
||||||
"loadSheddingInfo": load_shedding_info,
|
"loadSheddingInfo": coin_cell_volume,
|
||||||
"pouchCellInfo": pouch_cell_info,
|
"pouchCellInfo": pouch_cell_volume,
|
||||||
"conductivityInfo": conductivity_info,
|
"conductivityInfo": conductivity_volume,
|
||||||
"conductivityBottleCount": conductivity_bottle_count,
|
"conductivityBottleCount": conductivity_bottle_count,
|
||||||
"materialInfos": mats,
|
"materialInfos": mats,
|
||||||
"totalMass": round(total_mass, 4),
|
"totalMass": round(total_mass, 4),
|
||||||
@@ -1650,18 +1651,31 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
device_id: 目标设备 ID(如 "BatteryStation")
|
device_id: 目标设备 ID(如 "BatteryStation")
|
||||||
resource_name: 资源名称(如 "electrolyte_buffer")
|
resource_name: 资源名称(如 "bottle_rack_6x2")
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
找到的 PLR Resource 对象,未找到则返回 None
|
找到的 PLR Resource 对象,未找到则返回 None
|
||||||
"""
|
"""
|
||||||
|
# 优先:通过全局设备注册表直接访问目标设备的 deck
|
||||||
|
# DeviceInfoType 是 TypedDict(即普通 dict),必须用 dict.get() 而非 getattr()
|
||||||
try:
|
try:
|
||||||
from unilabos.app.ros2_app import get_device_plr_resource_by_name
|
from unilabos.ros.nodes.base_device_node import registered_devices
|
||||||
return get_device_plr_resource_by_name(device_id, resource_name)
|
device_info = registered_devices.get(device_id)
|
||||||
|
if device_info is not None:
|
||||||
|
driver = device_info.get("driver_instance")
|
||||||
|
if driver is not None:
|
||||||
|
deck = getattr(driver, "deck", None)
|
||||||
|
if deck is not None and hasattr(deck, "get_resource"):
|
||||||
|
try:
|
||||||
|
res = deck.get_resource(resource_name)
|
||||||
|
if res is not None:
|
||||||
|
return res
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 降级:遍历 workstation 已注册的 plr_resources 列表
|
# 降级:遍历 workstation 已注册的 plr_resources 列表(仅当前设备)
|
||||||
try:
|
try:
|
||||||
for res in getattr(self, "_plr_resources", []):
|
for res in getattr(self, "_plr_resources", []):
|
||||||
if res.name == resource_name:
|
if res.name == resource_name:
|
||||||
|
|||||||
Binary file not shown.
@@ -760,10 +760,9 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 创建通信模块
|
# 创建通信模块;同步器将在 post_init 中初始化并执行首次同步
|
||||||
self._create_communication_module(bioyond_config)
|
self._create_communication_module(bioyond_config)
|
||||||
self.resource_synchronizer = BioyondResourceSynchronizer(self)
|
self.resource_synchronizer = None
|
||||||
self.resource_synchronizer.sync_from_external()
|
|
||||||
|
|
||||||
# TODO: self._ros_node里面拿属性
|
# TODO: self._ros_node里面拿属性
|
||||||
|
|
||||||
@@ -802,6 +801,15 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
def post_init(self, ros_node: ROS2WorkstationNode):
|
def post_init(self, ros_node: ROS2WorkstationNode):
|
||||||
self._ros_node = ros_node
|
self._ros_node = ros_node
|
||||||
|
|
||||||
|
# Deck 为空时(反序列化未恢复子节点),主动调用 setup() 初始化仓库
|
||||||
|
if self.deck and not self.deck.children and hasattr(self.deck, "setup") and callable(self.deck.setup):
|
||||||
|
logger.info("Deck 无仓库子节点,调用 setup() 初始化仓库")
|
||||||
|
self.deck.setup()
|
||||||
|
|
||||||
|
# 初始化同步器并执行首次同步(需在仓库初始化之后)
|
||||||
|
self.resource_synchronizer = BioyondResourceSynchronizer(self)
|
||||||
|
self.resource_synchronizer.sync_from_external()
|
||||||
|
|
||||||
# 启动连接监控
|
# 启动连接监控
|
||||||
try:
|
try:
|
||||||
self.connection_monitor = ConnectionMonitor(self)
|
self.connection_monitor = ConnectionMonitor(self)
|
||||||
|
|||||||
@@ -169,23 +169,28 @@ class MaterialPlate(ItemizedResource[MaterialHole]):
|
|||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
) -> "MaterialPlate":
|
) -> "MaterialPlate":
|
||||||
"""工厂方法:创建带 4x4 洞位的料板(仅用于初始 setup,不在反序列化路径调用)"""
|
"""工厂方法:创建带 4x4 洞位的料板(仅用于初始 setup,不在反序列化路径调用)"""
|
||||||
plate = cls(name=name, size_x=size_x, size_y=size_y, size_z=size_z, category=category, model=model)
|
# 默认洞位间距(与 _unilabos_state 默认值保持一致)
|
||||||
|
hole_spacing_x = 24.0
|
||||||
|
hole_spacing_y = 24.0
|
||||||
|
# 先建洞位,再作为 ordered_items 传入构造函数
|
||||||
|
# (ItemizedResource.__init__ 要求 ordered_items 或 ordering 二选一必须有值)
|
||||||
holes = create_ordered_items_2d(
|
holes = create_ordered_items_2d(
|
||||||
klass=MaterialHole,
|
klass=MaterialHole,
|
||||||
num_items_x=4,
|
num_items_x=4,
|
||||||
num_items_y=4,
|
num_items_y=4,
|
||||||
dx=(size_x - 4 * plate._unilabos_state["hole_spacing_x"]) / 2,
|
dx=(size_x - 4 * hole_spacing_x) / 2,
|
||||||
dy=(size_y - 4 * plate._unilabos_state["hole_spacing_y"]) / 2,
|
dy=(size_y - 4 * hole_spacing_y) / 2,
|
||||||
dz=size_z,
|
dz=size_z,
|
||||||
item_dx=plate._unilabos_state["hole_spacing_x"],
|
item_dx=hole_spacing_x,
|
||||||
item_dy=plate._unilabos_state["hole_spacing_y"],
|
item_dy=hole_spacing_y,
|
||||||
size_x=16,
|
size_x=16,
|
||||||
size_y=16,
|
size_y=16,
|
||||||
size_z=16,
|
size_z=16,
|
||||||
)
|
)
|
||||||
for hole_name, hole in holes.items():
|
return cls(
|
||||||
plate.assign_child_resource(hole, location=hole.location)
|
name=name, size_x=size_x, size_y=size_y, size_z=size_z,
|
||||||
return plate
|
ordered_items=holes, category=category, model=model,
|
||||||
|
)
|
||||||
|
|
||||||
def update_locations(self):
|
def update_locations(self):
|
||||||
# TODO:调多次相加
|
# TODO:调多次相加
|
||||||
@@ -542,6 +547,7 @@ class YihuaCoinCellDeck(Deck):
|
|||||||
size_z: float = 100.0,
|
size_z: float = 100.0,
|
||||||
origin: Coordinate = Coordinate(-2200, 0, 0),
|
origin: Coordinate = Coordinate(-2200, 0, 0),
|
||||||
category: str = "coin_cell_deck",
|
category: str = "coin_cell_deck",
|
||||||
|
setup: bool = False,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -550,6 +556,8 @@ class YihuaCoinCellDeck(Deck):
|
|||||||
size_z=100.0,
|
size_z=100.0,
|
||||||
origin=origin,
|
origin=origin,
|
||||||
)
|
)
|
||||||
|
if setup:
|
||||||
|
self.setup()
|
||||||
|
|
||||||
def setup(self) -> None:
|
def setup(self) -> None:
|
||||||
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
|
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
|
||||||
|
|||||||
@@ -193,7 +193,12 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
|
|
||||||
def post_init(self, ros_node: ROS2WorkstationNode):
|
def post_init(self, ros_node: ROS2WorkstationNode):
|
||||||
self._ros_node = ros_node
|
self._ros_node = ros_node
|
||||||
#self.deck = create_a_coin_cell_deck()
|
|
||||||
|
# Deck 为空时(反序列化未恢复子节点),主动调用 setup() 初始化子物料
|
||||||
|
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()
|
||||||
|
|
||||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||||
"resources": [self.deck]
|
"resources": [self.deck]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code,formulation_order_code,formulation_ratio
|
||||||
|
20260325_132011,0.0,12.119999885559082,405.0,3189,20,7,test0008,13163721,,
|
||||||
|
20260325_132301,0.0,12.079999923706055,153.0,3172,20,7,test0008,13200631,,
|
||||||
|
20260325_132516,0.0,12.119999885559082,153.0,3205,20,7,test0008,13224031,,
|
||||||
|
20260325_132758,0.0,12.309999465942383,161.0,3221,20,7,test0008,13251351,,
|
||||||
|
20260325_133215,0.0,12.520000457763672,257.0,3318,20,7,NoRead88,13293861,,
|
||||||
|
20260325_133820,0.0,12.15999984741211,363.0,3269,20,7,NoRead88,13321291,,
|
||||||
|
20260325_134049,0.0,12.100000381469727,149.0,3383,20,7,NoRead88,13381641,,
|
||||||
|
20260325_134327,0.0,12.369999885559082,157.0,3237,20,7,NoRead88,13404651,,
|
||||||
|
20260325_160512,0.0,12.299999237060547,238.0,3577,20,7,NoRead88,16022161,,
|
||||||
|
20260325_160734,0.0,12.40000057220459,155.0,3464,20,7,NoRead88,16045481,,
|
||||||
|
20260325_161010,0.0,12.269999504089355,155.0,3609,20,7,NoRead88,60731181,,
|
||||||
|
20260325_161252,0.0,12.579999923706055,162.0,3496,20,7,NoRead88,16100671,,
|
||||||
|
20260325_161636,0.0,12.619999885559082,223.0,3399,20,7,NoRead88,16135951,,
|
||||||
|
20260325_161909,0.0,12.039999961853027,153.0,3302,20,7,NoRead88,16163351,,
|
||||||
|
20260325_162145,0.0,12.00999927520752,155.0,3350,20,7,NoRead88,16190731,,
|
||||||
|
20260325_162429,0.0,12.329998970031738,163.0,3561,20,7,NoRead88,16214361,,
|
||||||
|
20260325_162841,0.0,12.579999923706055,251.0,3593,20,7,NoRead88,16260311,,
|
||||||
|
20260325_163118,0.0,12.25999927520752,156.0,3545,20,7,NoRead88,16283921,,
|
||||||
|
20260325_163356,0.0,12.220000267028809,157.0,3464,20,7,NoRead88,16311611,,
|
||||||
|
20260325_163641,0.0,12.199999809265137,165.0,3674,20,7,NoRead88,16335401,,
|
||||||
|
20260325_164046,0.0,12.25,244.0,3512,20,7,NoRead88,16380881,,
|
||||||
|
20260325_164321,0.0,12.079999923706055,154.0,3609,20,7,NoRead88,16404401,,
|
||||||
|
20260325_164556,0.0,12.029999732971191,155.0,3593,20,7,NoRead88,16431851,,
|
||||||
|
20260325_164840,0.0,12.100000381469727,163.0,3496,20,7,NoRead88,16455451,,
|
||||||
|
20260325_172206,0.0,12.00999927520752,245.0,3011,20,7,NoRead88,17193041,BSO2026032500006,"{""EMC"": 0.949, ""LiFSI"": 0.051}"
|
||||||
|
20260325_172608,0.0,12.0,242.0,3253,20,7,NoRead88,17233491,BSO2026032500007,"{""EMC"": 0.9582, ""LiFSI"": 0.0418}"
|
||||||
|
20260325_183415,0.0,12.690000534057617,1226.0,3528,20,7,NoRead88,18150131,BSO2026032500011,"{""EMC"": 0.949, ""LiFSI"": 0.051}"
|
||||||
|
20260325_190044,0.0,12.130000114440918,1586.0,3528,20,7,NoRead88,18355771,BSO2026032500012,"{""EMC"": 0.9582, ""LiFSI"": 0.0418}"
|
||||||
|
@@ -196,11 +196,11 @@ bioyond_cell:
|
|||||||
batch_id: ''
|
batch_id: ''
|
||||||
bottle_type: 配液小瓶
|
bottle_type: 配液小瓶
|
||||||
conductivity_bottle_count: 0
|
conductivity_bottle_count: 0
|
||||||
conductivity_info: 0.0
|
conductivity_volume: 0.0
|
||||||
formulation: null
|
formulation: null
|
||||||
load_shedding_info: 0.0
|
coin_cell_volume: 0.0
|
||||||
mix_time: 0
|
mix_time: []
|
||||||
pouch_cell_info: 0.0
|
pouch_cell_volume: 0.0
|
||||||
handles:
|
handles:
|
||||||
output:
|
output:
|
||||||
- data_key: total_orders
|
- data_key: total_orders
|
||||||
@@ -239,9 +239,9 @@ bioyond_cell:
|
|||||||
default: 0
|
default: 0
|
||||||
description: 电导测试分液瓶数
|
description: 电导测试分液瓶数
|
||||||
type: integer
|
type: integer
|
||||||
conductivity_info:
|
conductivity_volume:
|
||||||
default: 0.0
|
default: 0.0
|
||||||
description: 电导测试分液体积
|
description: 电导率测试分液体积
|
||||||
type: number
|
type: number
|
||||||
formulation:
|
formulation:
|
||||||
description: 配方列表,每个元素代表一个订单(一瓶)
|
description: 配方列表,每个元素代表一个订单(一瓶)
|
||||||
@@ -269,17 +269,19 @@ bioyond_cell:
|
|||||||
- materials
|
- materials
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
load_shedding_info:
|
coin_cell_volume:
|
||||||
default: 0.0
|
default: 0.0
|
||||||
description: 扣电组装分液体积
|
description: 纽扣电池组装分液体积
|
||||||
type: number
|
type: number
|
||||||
mix_time:
|
mix_time:
|
||||||
default: 0
|
default: []
|
||||||
description: 混匀时间(秒)
|
description: 混匀时间列表(秒),与 formulation 一一对应
|
||||||
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
pouch_cell_info:
|
type: array
|
||||||
|
pouch_cell_volume:
|
||||||
default: 0.0
|
default: 0.0
|
||||||
description: 软包组装分液体积
|
description: 软包电池注液组装分液体积
|
||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- formulation
|
- formulation
|
||||||
|
|||||||
@@ -104,8 +104,11 @@ class BioyondElectrolyteDeck(Deck):
|
|||||||
size_y: float = 1400.0,
|
size_y: float = 1400.0,
|
||||||
size_z: float = 2670.0,
|
size_z: float = 2670.0,
|
||||||
category: str = "deck",
|
category: str = "deck",
|
||||||
|
setup: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(name=name, size_x=4150.0, size_y=1400.0, size_z=2670.0)
|
super().__init__(name=name, size_x=4150.0, size_y=1400.0, size_z=2670.0)
|
||||||
|
if setup:
|
||||||
|
self.setup()
|
||||||
|
|
||||||
def setup(self) -> None:
|
def setup(self) -> None:
|
||||||
# 添加仓库
|
# 添加仓库
|
||||||
|
|||||||
@@ -797,6 +797,7 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
|||||||
bottle = plr_material[number] = initialize_resource(
|
bottle = plr_material[number] = initialize_resource(
|
||||||
{"name": f'{detail["name"]}_{number}', "class": reverse_type_mapping[typeName][0]}, resource_type=ResourcePLR
|
{"name": f'{detail["name"]}_{number}', "class": reverse_type_mapping[typeName][0]}, resource_type=ResourcePLR
|
||||||
)
|
)
|
||||||
|
if hasattr(bottle, 'tracker') and bottle.tracker is not None:
|
||||||
bottle.tracker.liquids = [
|
bottle.tracker.liquids = [
|
||||||
(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)
|
(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)
|
||||||
]
|
]
|
||||||
@@ -808,6 +809,7 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
|||||||
# 只对有 capacity 属性的容器(液体容器)处理液体追踪
|
# 只对有 capacity 属性的容器(液体容器)处理液体追踪
|
||||||
if hasattr(plr_material, 'capacity'):
|
if hasattr(plr_material, 'capacity'):
|
||||||
bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
|
bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
|
||||||
|
if hasattr(bottle, 'tracker') and bottle.tracker is not None:
|
||||||
bottle.tracker.liquids = [
|
bottle.tracker.liquids = [
|
||||||
(material["name"], float(material.get("quantity", 0)) if material.get("quantity") else 0)
|
(material["name"], float(material.get("quantity", 0)) if material.get("quantity") else 0)
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user