mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-08 07:55:12 +00:00
Compare commits
1 Commits
fix/workst
...
78729ef86c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78729ef86c |
@@ -848,7 +848,7 @@ class MessageProcessor:
|
|||||||
device_action_groups[key_add].append(item["uuid"])
|
device_action_groups[key_add].append(item["uuid"])
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[资源同步] 跨站Transfer: {item['uuid'][:8]} from {device_old_id} to {device_id}"
|
f"[MessageProcessor] Resource migrated: {item['uuid'][:8]} from {device_old_id} to {device_id}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 正常update
|
# 正常update
|
||||||
@@ -863,11 +863,11 @@ class MessageProcessor:
|
|||||||
device_action_groups[key] = []
|
device_action_groups[key] = []
|
||||||
device_action_groups[key].append(item["uuid"])
|
device_action_groups[key].append(item["uuid"])
|
||||||
|
|
||||||
logger.trace(f"[资源同步] 动作 {action} 分组数量: {len(device_action_groups)}, 总数量: {len(resource_uuid_list)}")
|
logger.info(f"触发物料更新 {action} 分组数量: {len(device_action_groups)}, 总数量: {len(resource_uuid_list)}")
|
||||||
|
|
||||||
# 为每个(device_id, action)创建独立的更新线程
|
# 为每个(device_id, action)创建独立的更新线程
|
||||||
for (device_id, actual_action), items in device_action_groups.items():
|
for (device_id, actual_action), items in device_action_groups.items():
|
||||||
logger.trace(f"[资源同步] {device_id} 物料动作 {actual_action} 数量: {len(items)}")
|
logger.info(f"设备 {device_id} 物料更新 {actual_action} 数量: {len(items)}")
|
||||||
|
|
||||||
def _notify_resource_tree(dev_id, act, item_list):
|
def _notify_resource_tree(dev_id, act, item_list):
|
||||||
try:
|
try:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,9 @@
|
|||||||
# 工作站抽象基类物料系统架构说明
|
# 工作站抽象基类物料系统架构说明
|
||||||
|
|
||||||
|
## 设计理念
|
||||||
|
|
||||||
|
基于用户需求"请你帮我系统思考一下,工作站抽象基类的物料系统基类该如何构建",我们最终确定了一个**PyLabRobot Deck为中心**的简化架构。
|
||||||
|
|
||||||
### 核心原则
|
### 核心原则
|
||||||
|
|
||||||
1. **PyLabRobot为物料管理核心**:使用PyLabRobot的Deck系统作为物料管理的基础,利用其成熟的Resource体系
|
1. **PyLabRobot为物料管理核心**:使用PyLabRobot的Deck系统作为物料管理的基础,利用其成熟的Resource体系
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,12 @@
|
|||||||
|
import pubchempy as pcp
|
||||||
|
|
||||||
|
cas = "21324-40-3" # 示例
|
||||||
|
comps = pcp.get_compounds(cas, namespace="name")
|
||||||
|
if not comps:
|
||||||
|
raise ValueError("No hit")
|
||||||
|
|
||||||
|
c = comps[0]
|
||||||
|
|
||||||
|
print("Canonical SMILES:", c.canonical_smiles)
|
||||||
|
print("Isomeric SMILES:", c.isomeric_smiles)
|
||||||
|
print("MW:", c.molecular_weight)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
material_name
|
||||||
|
LiPF6
|
||||||
|
LiDFOB
|
||||||
|
DTD
|
||||||
|
LiFSI
|
||||||
|
LiPO2F2
|
||||||
|
|
||||||
|
@@ -1,157 +0,0 @@
|
|||||||
# 批量出库 Excel 模板使用说明
|
|
||||||
|
|
||||||
**文件**: `outbound_template.xlsx`
|
|
||||||
**用途**: 配合 `auto_batch_outbound_from_xlsx()` 方法进行批量出库操作
|
|
||||||
**API 端点**: `/api/lims/storage/auto-batch-out-bound`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Excel 列说明
|
|
||||||
|
|
||||||
| 列名 | 说明 | 示例 | 必填 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| `locationId` | **库位 ID(UUID)** | `3a19da43-57b5-294f-d663-154a1cc32270` | ✅ 是 |
|
|
||||||
| `warehouseId` | **仓库 ID 或名称** | `配液站内试剂仓库` | ✅ 是 |
|
|
||||||
| `quantity` | **出库数量** | `1.0`, `2.0` | ✅ 是 |
|
|
||||||
| `x` | **X 坐标(库位横向位置)** | `1`, `2`, `3` | ✅ 是 |
|
|
||||||
| `y` | **Y 坐标(库位纵向位置)** | `1`, `2`, `3` | ✅ 是 |
|
|
||||||
| `z` | **Z 坐标(库位层数/高度)** | `1`, `2`, `3` | ✅ 是 |
|
|
||||||
| `备注说明` | 可选备注信息 | `配液站内试剂仓库-A01` | ❌ 否 |
|
|
||||||
|
|
||||||
### 📐 坐标说明
|
|
||||||
|
|
||||||
**x, y, z** 是库位在仓库内的**三维坐标**:
|
|
||||||
|
|
||||||
```
|
|
||||||
仓库(例如 WH4)
|
|
||||||
├── Z=1(第1层/加样头面)
|
|
||||||
│ ├── X=1, Y=1(位置 A)
|
|
||||||
│ ├── X=2, Y=1(位置 B)
|
|
||||||
│ ├── X=3, Y=1(位置 C)
|
|
||||||
│ └── ...
|
|
||||||
│
|
|
||||||
└── Z=2(第2层/原液瓶面)
|
|
||||||
├── X=1, Y=1(位置 A)
|
|
||||||
├── X=2, Y=1(位置 B)
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
- **warehouseId**: 指定哪个仓库(WH3, WH4, 配液站等)
|
|
||||||
- **x, y, z**: 在该仓库内的三维坐标
|
|
||||||
- **locationId**: 该坐标位置的唯一 UUID
|
|
||||||
|
|
||||||
### 🎯 起点与终点
|
|
||||||
|
|
||||||
**重要说明**:批量出库模板**只规定了出库的"起点"**(从哪里取物料),**没有指定"终点"**(放到哪里)。
|
|
||||||
|
|
||||||
```
|
|
||||||
出库流程:
|
|
||||||
起点(Excel 指定) → ?终点(LIMS/工作流决定)
|
|
||||||
↓
|
|
||||||
locationId, x, y, z → 由 LIMS 系统或当前工作流自动分配
|
|
||||||
```
|
|
||||||
|
|
||||||
**终点由以下方式确定:**
|
|
||||||
- **LIMS 系统自动分配**:根据当前任务自动规划目标位置
|
|
||||||
- **工作流预定义**:在创建出库任务时已绑定目标位置
|
|
||||||
- **暂存区**:默认放到出库暂存区,等待下一步操作
|
|
||||||
|
|
||||||
💡 **对比**:上料操作(`auto_feeding4to3`)则有 `targetWH` 参数可以指定目标仓库
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 如何获取 UUID?
|
|
||||||
|
|
||||||
### 方法 1:从配置文件获取
|
|
||||||
|
|
||||||
参考 `yibin_electrolyte_config.json` 中的 `warehouse_mapping`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"warehouse_mapping": {
|
|
||||||
"配液站内试剂仓库": {
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a19da43-57b5-294f-d663-154a1cc32270",
|
|
||||||
"B01": "3a19da43-57b5-7394-5f49-54efe2c9bef2",
|
|
||||||
"C01": "3a19da43-57b5-5e75-552f-8dbd0ad1075f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"手动堆栈": {
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
|
||||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方法 2:通过 API 查询
|
|
||||||
|
|
||||||
```python
|
|
||||||
material_info = hardware_interface.material_id_query(workflow_id)
|
|
||||||
locations = material_info.get("locations", [])
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 填写示例
|
|
||||||
|
|
||||||
### 示例 1:从配液站内试剂仓库出库
|
|
||||||
|
|
||||||
| locationId | warehouseId | quantity | x | y | z | 备注说明 |
|
|
||||||
|------------|-------------|----------|---|---|---|----------|
|
|
||||||
| `3a19da43-57b5-294f-d663-154a1cc32270` | 配液站内试剂仓库 | 1 | 1 | 1 | 1 | A01 位置 |
|
|
||||||
| `3a19da43-57b5-7394-5f49-54efe2c9bef2` | 配液站内试剂仓库 | 2 | 2 | 1 | 1 | B01 位置 |
|
|
||||||
|
|
||||||
### 示例 2:从手动堆栈出库
|
|
||||||
|
|
||||||
| locationId | warehouseId | quantity | x | y | z | 备注说明 |
|
|
||||||
|------------|-------------|----------|---|---|---|----------|
|
|
||||||
| `3a19deae-2c7a-36f5-5e41-02c5b66feaea` | 手动堆栈 | 1 | 1 | 1 | 1 | A01 |
|
|
||||||
| `3a19deae-2c7a-dc6d-c41e-ef285d946cfe` | 手动堆栈 | 1 | 1 | 2 | 1 | A02 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💻 使用方法
|
|
||||||
|
|
||||||
```python
|
|
||||||
from bioyond_cell_workstation import BioyondCellWorkstation
|
|
||||||
|
|
||||||
# 初始化工作站
|
|
||||||
workstation = BioyondCellWorkstation(config=config, deck=deck)
|
|
||||||
|
|
||||||
# 调用批量出库方法
|
|
||||||
result = workstation.auto_batch_outbound_from_xlsx(
|
|
||||||
xlsx_path="outbound_template.xlsx"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ 注意事项
|
|
||||||
|
|
||||||
1. **locationId 必须是有效的 UUID**,不能使用库位名称
|
|
||||||
2. **x, y, z 坐标必须与 locationId 对应**,表示该库位在仓库内的位置
|
|
||||||
3. **quantity 必须是数字**,可以是整数或浮点数
|
|
||||||
4. Excel 文件必须包含表头行
|
|
||||||
5. 空行会被自动跳过
|
|
||||||
6. 确保 UUID 与实际库位对应,否则 API 会报错
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 相关文件
|
|
||||||
|
|
||||||
- **配置文件**: `yibin_electrolyte_config.json`
|
|
||||||
- **Python 代码**: `bioyond_cell_workstation.py` (L630-695)
|
|
||||||
- **生成脚本**: `create_outbound_template.py`
|
|
||||||
- **上料模板**: `material_template.xlsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 重新生成模板
|
|
||||||
|
|
||||||
```bash
|
|
||||||
conda activate newunilab
|
|
||||||
python create_outbound_template.py
|
|
||||||
```
|
|
||||||
@@ -49,14 +49,6 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.api_key = config["api_key"]
|
self.api_key = config["api_key"]
|
||||||
self.host = config["api_host"]
|
self.host = config["api_host"]
|
||||||
|
|
||||||
# 初始化 location_mapping
|
|
||||||
# 直接从 warehouse_mapping 构建,确保数据源所谓的单一和结构化
|
|
||||||
self.location_mapping = {}
|
|
||||||
warehouse_mapping = self.config.get("warehouse_mapping", {})
|
|
||||||
for warehouse_name, warehouse_config in warehouse_mapping.items():
|
|
||||||
if "site_uuids" in warehouse_config:
|
|
||||||
self.location_mapping.update(warehouse_config["site_uuids"])
|
|
||||||
self._logger = SimpleLogger()
|
self._logger = SimpleLogger()
|
||||||
self.material_cache = {}
|
self.material_cache = {}
|
||||||
self._load_material_cache()
|
self._load_material_cache()
|
||||||
@@ -184,40 +176,7 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
print(f"add material data: {response['data']}")
|
print(f"add material data: {response['data']}")
|
||||||
|
return response.get("data", {})
|
||||||
# 自动更新缓存
|
|
||||||
data = response.get("data", {})
|
|
||||||
if data:
|
|
||||||
if isinstance(data, str):
|
|
||||||
# 如果返回的是字符串,通常是ID
|
|
||||||
mat_id = data
|
|
||||||
name = params.get("name")
|
|
||||||
else:
|
|
||||||
# 如果返回的是字典,尝试获取name和id
|
|
||||||
name = data.get("name") or params.get("name")
|
|
||||||
mat_id = data.get("id")
|
|
||||||
|
|
||||||
if name and mat_id:
|
|
||||||
self.material_cache[name] = mat_id
|
|
||||||
print(f"已自动更新缓存: {name} -> {mat_id}")
|
|
||||||
|
|
||||||
# 处理返回数据中的 details (如果有)
|
|
||||||
# 有些 API 返回结构可能直接包含 details,或者在 data 字段中
|
|
||||||
details = data.get("details", []) if isinstance(data, dict) else []
|
|
||||||
if not details and isinstance(data, dict):
|
|
||||||
details = data.get("detail", [])
|
|
||||||
|
|
||||||
if details:
|
|
||||||
for detail in details:
|
|
||||||
d_name = detail.get("name")
|
|
||||||
# 尝试从不同字段获取 ID
|
|
||||||
d_id = detail.get("id") or detail.get("detailMaterialId")
|
|
||||||
|
|
||||||
if d_name and d_id:
|
|
||||||
self.material_cache[d_name] = d_id
|
|
||||||
print(f"已自动更新 detail 缓存: {d_name} -> {d_id}")
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def query_matial_type_id(self, data) -> list:
|
def query_matial_type_id(self, data) -> list:
|
||||||
"""查找物料typeid"""
|
"""查找物料typeid"""
|
||||||
@@ -244,7 +203,7 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
params={
|
params={
|
||||||
"apiKey": self.api_key,
|
"apiKey": self.api_key,
|
||||||
"requestTime": self.get_current_time_iso8601(),
|
"requestTime": self.get_current_time_iso8601(),
|
||||||
"data": 0,
|
"data": {},
|
||||||
})
|
})
|
||||||
if not response or response['code'] != 1:
|
if not response or response['code'] != 1:
|
||||||
return []
|
return []
|
||||||
@@ -314,19 +273,12 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
|
|
||||||
if not response or response['code'] != 1:
|
if not response or response['code'] != 1:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# 自动更新缓存 - 移除被删除的物料
|
|
||||||
for name, mid in list(self.material_cache.items()):
|
|
||||||
if mid == material_id:
|
|
||||||
del self.material_cache[name]
|
|
||||||
print(f"已从缓存移除物料: {name}")
|
|
||||||
break
|
|
||||||
|
|
||||||
return response.get("data", {})
|
return response.get("data", {})
|
||||||
|
|
||||||
def material_outbound(self, material_id: str, location_name: str, quantity: int) -> dict:
|
def material_outbound(self, material_id: str, location_name: str, quantity: int) -> dict:
|
||||||
"""指定库位出库物料(通过库位名称)"""
|
"""指定库位出库物料(通过库位名称)"""
|
||||||
location_id = self.location_mapping.get(location_name, location_name)
|
# location_name 参数实际上应该直接是 location_id (UUID)
|
||||||
|
location_id = location_name
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"materialId": material_id,
|
"materialId": material_id,
|
||||||
@@ -1152,10 +1104,6 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
for detail_material in detail_materials:
|
for detail_material in detail_materials:
|
||||||
detail_name = detail_material.get("name")
|
detail_name = detail_material.get("name")
|
||||||
detail_id = detail_material.get("detailMaterialId")
|
detail_id = detail_material.get("detailMaterialId")
|
||||||
if not detail_id:
|
|
||||||
# 尝试其他可能的字段
|
|
||||||
detail_id = detail_material.get("id")
|
|
||||||
|
|
||||||
if detail_name and detail_id:
|
if detail_name and detail_id:
|
||||||
self.material_cache[detail_name] = detail_id
|
self.material_cache[detail_name] = detail_id
|
||||||
print(f"加载detail材料: {detail_name} -> ID: {detail_id}")
|
print(f"加载detail材料: {detail_name} -> ID: {detail_id}")
|
||||||
@@ -1176,14 +1124,6 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
print(f"从缓存找到材料: {material_name_or_id} -> ID: {material_id}")
|
print(f"从缓存找到材料: {material_name_or_id} -> ID: {material_id}")
|
||||||
return material_id
|
return material_id
|
||||||
|
|
||||||
# 如果缓存中没有,尝试刷新缓存
|
|
||||||
print(f"缓存中未找到材料 '{material_name_or_id}',尝试刷新缓存...")
|
|
||||||
self.refresh_material_cache()
|
|
||||||
if material_name_or_id in self.material_cache:
|
|
||||||
material_id = self.material_cache[material_name_or_id]
|
|
||||||
print(f"刷新缓存后找到材料: {material_name_or_id} -> ID: {material_id}")
|
|
||||||
return material_id
|
|
||||||
|
|
||||||
print(f"警告: 未在缓存中找到材料名称 '{material_name_or_id}',将使用原值")
|
print(f"警告: 未在缓存中找到材料名称 '{material_name_or_id}',将使用原值")
|
||||||
return material_name_or_id
|
return material_name_or_id
|
||||||
|
|
||||||
|
|||||||
@@ -1,329 +0,0 @@
|
|||||||
# config.py
|
|
||||||
"""
|
|
||||||
Bioyond工作站配置文件
|
|
||||||
包含API配置、工作流映射、物料类型映射、仓库库位映射等所有配置信息
|
|
||||||
"""
|
|
||||||
|
|
||||||
from unilabos.resources.bioyond.decks import BIOYOND_PolymerReactionStation_Deck
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# 基础配置
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
# API配置
|
|
||||||
API_CONFIG = {
|
|
||||||
"api_key": "DE9BDDA0",
|
|
||||||
"api_host": "http://192.168.1.200:44402"
|
|
||||||
}
|
|
||||||
|
|
||||||
# HTTP 报送服务配置
|
|
||||||
HTTP_SERVICE_CONFIG = {
|
|
||||||
"http_service_host": "127.0.0.1", # 监听地址
|
|
||||||
"http_service_port": 8080, # 监听端口
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deck配置 - 反应站工作台配置
|
|
||||||
DECK_CONFIG = BIOYOND_PolymerReactionStation_Deck(setup=True)
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# 工作流配置
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
# 工作流ID映射
|
|
||||||
WORKFLOW_MAPPINGS = {
|
|
||||||
"reactor_taken_out": "3a16081e-4788-ca37-eff4-ceed8d7019d1",
|
|
||||||
"reactor_taken_in": "3a160df6-76b3-0957-9eb0-cb496d5721c6",
|
|
||||||
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
|
|
||||||
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
|
|
||||||
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
|
|
||||||
"Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6",
|
|
||||||
"liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
|
||||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a",
|
|
||||||
}
|
|
||||||
|
|
||||||
# 工作流名称到显示名称的映射
|
|
||||||
WORKFLOW_TO_SECTION_MAP = {
|
|
||||||
'reactor_taken_in': '反应器放入',
|
|
||||||
'reactor_taken_out': '反应器取出',
|
|
||||||
'Solid_feeding_vials': '固体投料-小瓶',
|
|
||||||
'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)',
|
|
||||||
'Liquid_feeding_solvents': '液体投料-溶剂',
|
|
||||||
'Liquid_feeding(titration)': '液体投料-滴定',
|
|
||||||
'liquid_feeding_beaker': '液体投料-烧杯',
|
|
||||||
'Drip_back': '液体回滴'
|
|
||||||
}
|
|
||||||
|
|
||||||
# 工作流步骤ID配置
|
|
||||||
WORKFLOW_STEP_IDS = {
|
|
||||||
"reactor_taken_in": {
|
|
||||||
"config": "60a06f85-c5b3-29eb-180f-4f62dd7e2154"
|
|
||||||
},
|
|
||||||
"liquid_feeding_beaker": {
|
|
||||||
"liquid": "6808cda7-fee7-4092-97f0-5f9c2ffa60e3",
|
|
||||||
"observe": "1753c0de-dffc-4ee6-8458-805a2e227362"
|
|
||||||
},
|
|
||||||
"liquid_feeding_vials_non_titration": {
|
|
||||||
"liquid": "62ea6e95-3d5d-43db-bc1e-9a1802673861",
|
|
||||||
"observe": "3a167d99-6172-b67b-5f22-a7892197142e"
|
|
||||||
},
|
|
||||||
"liquid_feeding_solvents": {
|
|
||||||
"liquid": "1fcea355-2545-462b-b727-350b69a313bf",
|
|
||||||
"observe": "0553dfb3-9ac5-4ace-8e00-2f11029919a8"
|
|
||||||
},
|
|
||||||
"solid_feeding_vials": {
|
|
||||||
"feeding": "f7ae7448-4f20-4c1d-8096-df6fbadd787a",
|
|
||||||
"observe": "263c7ed5-7277-426b-bdff-d6fbf77bcc05"
|
|
||||||
},
|
|
||||||
"liquid_feeding_titration": {
|
|
||||||
"liquid": "a00ec41b-e666-4422-9c20-bfcd3cd15c54",
|
|
||||||
"observe": "ac738ff6-4c58-4155-87b1-d6f65a2c9ab5"
|
|
||||||
},
|
|
||||||
"drip_back": {
|
|
||||||
"liquid": "371be86a-ab77-4769-83e5-54580547c48a",
|
|
||||||
"observe": "ce024b9d-bd20-47b8-9f78-ca5ce7f44cf1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 工作流动作名称配置
|
|
||||||
ACTION_NAMES = {
|
|
||||||
"reactor_taken_in": {
|
|
||||||
"config": "通量-配置",
|
|
||||||
"stirring": "反应模块-开始搅拌"
|
|
||||||
},
|
|
||||||
"solid_feeding_vials": {
|
|
||||||
"feeding": "粉末加样模块-投料",
|
|
||||||
"observe": "反应模块-观察搅拌结果"
|
|
||||||
},
|
|
||||||
"liquid_feeding_vials_non_titration": {
|
|
||||||
"liquid": "稀释液瓶加液位-液体投料",
|
|
||||||
"observe": "反应模块-滴定结果观察"
|
|
||||||
},
|
|
||||||
"liquid_feeding_solvents": {
|
|
||||||
"liquid": "试剂AB放置位-试剂吸液分液",
|
|
||||||
"observe": "反应模块-观察搅拌结果"
|
|
||||||
},
|
|
||||||
"liquid_feeding_titration": {
|
|
||||||
"liquid": "稀释液瓶加液位-稀释液吸液分液",
|
|
||||||
"observe": "反应模块-滴定结果观察"
|
|
||||||
},
|
|
||||||
"liquid_feeding_beaker": {
|
|
||||||
"liquid": "烧杯溶液放置位-烧杯吸液分液",
|
|
||||||
"observe": "反应模块-观察搅拌结果"
|
|
||||||
},
|
|
||||||
"drip_back": {
|
|
||||||
"liquid": "试剂AB放置位-试剂吸液分液",
|
|
||||||
"observe": "反应模块-向下滴定结果观察"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# 仓库配置
|
|
||||||
# ============================================================================
|
|
||||||
# 说明:
|
|
||||||
# - 出库和入库操作都需要UUID
|
|
||||||
WAREHOUSE_MAPPING = {
|
|
||||||
# ========== 反应站仓库 ==========
|
|
||||||
|
|
||||||
# 堆栈1左 - 反应站左侧堆栈 (4行×4列=16个库位, A01~D04)
|
|
||||||
"堆栈1左": {
|
|
||||||
"uuid": "3a14aa17-0d49-dce4-486e-4b5c85c8b366",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a14aa17-0d49-11d7-a6e1-f236b3e5e5a3",
|
|
||||||
"A02": "3a14aa17-0d49-4bc5-8836-517b75473f5f",
|
|
||||||
"A03": "3a14aa17-0d49-c2bc-6222-5cee8d2d94f8",
|
|
||||||
"A04": "3a14aa17-0d49-3ce2-8e9a-008c38d116fb",
|
|
||||||
"B01": "3a14aa17-0d49-f49c-6b66-b27f185a3b32",
|
|
||||||
"B02": "3a14aa17-0d49-cf46-df85-a979c9c9920c",
|
|
||||||
"B03": "3a14aa17-0d49-7698-4a23-f7ffb7d48ba3",
|
|
||||||
"B04": "3a14aa17-0d49-1231-99be-d5870e6478e9",
|
|
||||||
"C01": "3a14aa17-0d49-be34-6fae-4aed9d48b70b",
|
|
||||||
"C02": "3a14aa17-0d49-11d7-0897-34921dcf6b7c",
|
|
||||||
"C03": "3a14aa17-0d49-9840-0bd5-9c63c1bb2c29",
|
|
||||||
"C04": "3a14aa17-0d49-8335-3bff-01da69ea4911",
|
|
||||||
"D01": "3a14aa17-0d49-2bea-c8e5-2b32094935d5",
|
|
||||||
"D02": "3a14aa17-0d49-cff4-e9e8-5f5f0bc1ef32",
|
|
||||||
"D03": "3a14aa17-0d49-4948-cb0a-78f30d1ca9b8",
|
|
||||||
"D04": "3a14aa17-0d49-fd2f-9dfb-a29b11e84099",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
# 堆栈1右 - 反应站右侧堆栈 (4行×4列=16个库位, A05~D08)
|
|
||||||
"堆栈1右": {
|
|
||||||
"uuid": "3a14aa17-0d49-dce4-486e-4b5c85c8b366",
|
|
||||||
"site_uuids": {
|
|
||||||
"A05": "3a14aa17-0d49-2c61-edc8-72a8ca7192dd",
|
|
||||||
"A06": "3a14aa17-0d49-60c8-2b00-40b17198f397",
|
|
||||||
"A07": "3a14aa17-0d49-ec5b-0b75-634dce8eed25",
|
|
||||||
"A08": "3a14aa17-0d49-3ec9-55b3-f3189c4ec53d",
|
|
||||||
"B05": "3a14aa17-0d49-6a4e-abcf-4c113eaaeaad",
|
|
||||||
"B06": "3a14aa17-0d49-e3f6-2dd6-28c2e8194fbe",
|
|
||||||
"B07": "3a14aa17-0d49-11a6-b861-ee895121bf52",
|
|
||||||
"B08": "3a14aa17-0d49-9c7d-1145-d554a6e482f0",
|
|
||||||
"C05": "3a14aa17-0d49-45c4-7a34-5105bc3e2368",
|
|
||||||
"C06": "3a14aa17-0d49-867e-39ab-31b3fe9014be",
|
|
||||||
"C07": "3a14aa17-0d49-ec56-c4b4-39fd9b2131e7",
|
|
||||||
"C08": "3a14aa17-0d49-1128-d7d9-ffb1231c98c0",
|
|
||||||
"D05": "3a14aa17-0d49-e843-f961-ea173326a14b",
|
|
||||||
"D06": "3a14aa17-0d49-4d26-a985-f188359c4f8b",
|
|
||||||
"D07": "3a14aa17-0d49-223a-b520-bc092bb42fe0",
|
|
||||||
"D08": "3a14aa17-0d49-4fa3-401a-6a444e1cca22",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
# 站内试剂存放堆栈
|
|
||||||
"站内试剂存放堆栈": {
|
|
||||||
"uuid": "3a14aa3b-9fab-9d8e-d1a7-828f01f51f0c",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a14aa3b-9fab-adac-7b9c-e1ee446b51d5",
|
|
||||||
"A02": "3a14aa3b-9fab-ca72-febc-b7c304476c78"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
# 测量小瓶仓库(测密度)
|
|
||||||
"测量小瓶仓库": {
|
|
||||||
"uuid": "3a15012f-705b-c0de-3f9e-950c205f9921",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a15012f-705e-0524-3161-c523b5aebc97",
|
|
||||||
"A02": "3a15012f-705e-7cd1-32ab-ad4fd1ab75c8",
|
|
||||||
"A03": "3a15012f-705e-a5d6-edac-bdbfec236260",
|
|
||||||
"B01": "3a15012f-705e-e0ee-80e0-10a6b3fc500d",
|
|
||||||
"B02": "3a15012f-705e-e499-180d-de06d60d0b21",
|
|
||||||
"B03": "3a15012f-705e-eff6-63f1-09f742096b26"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# 站内Tip盒堆栈 - 用于存放枪头盒 (耗材)
|
|
||||||
"站内Tip盒堆栈": {
|
|
||||||
"uuid": "3a14aa3a-2d3c-b5c1-9ddf-7c4a957d459a",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a14aa3a-2d3d-e700-411a-0ddf85e1f18a",
|
|
||||||
"A02": "3a14aa3a-2d3d-a7ce-099a-d5632fdafa24",
|
|
||||||
"A03": "3a14aa3a-2d3d-bdf6-a702-c60b38b08501",
|
|
||||||
"B01": "3a14aa3a-2d3d-d704-f076-2a8d5bc72cb8",
|
|
||||||
"B02": "3a14aa3a-2d3d-c350-2526-0778d173a5ac",
|
|
||||||
"B03": "3a14aa3a-2d3d-bc38-b356-f0de2e44e0c7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
# ========== 配液站仓库 ==========
|
|
||||||
"粉末堆栈": {
|
|
||||||
"uuid": "3a14198e-6928-121f-7ca6-88ad3ae7e6a0",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a14198e-6929-31f0-8a22-0f98f72260df",
|
|
||||||
"A02": "3a14198e-6929-4379-affa-9a2935c17f99",
|
|
||||||
"A03": "3a14198e-6929-56da-9a1c-7f5fbd4ae8af",
|
|
||||||
"A04": "3a14198e-6929-5e99-2b79-80720f7cfb54",
|
|
||||||
"B01": "3a14198e-6929-f525-9a1b-1857552b28ee",
|
|
||||||
"B02": "3a14198e-6929-bf98-0fd5-26e1d68bf62d",
|
|
||||||
"B03": "3a14198e-6929-2d86-a468-602175a2b5aa",
|
|
||||||
"B04": "3a14198e-6929-1a98-ae57-e97660c489ad",
|
|
||||||
"C01": "3a14198e-6929-46fe-841e-03dd753f1e4a",
|
|
||||||
"C02": "3a14198e-6929-72ac-32ce-9b50245682b8",
|
|
||||||
"C03": "3a14198e-6929-8a0b-b686-6f4a2955c4e2",
|
|
||||||
"C04": "3a14198e-6929-a0ec-5f15-c0f9f339f963",
|
|
||||||
"D01": "3a14198e-6929-1bc9-a9bd-3b7ca66e7f95",
|
|
||||||
"D02": "3a14198e-6929-3bd8-e6c7-4a9fd93be118",
|
|
||||||
"D03": "3a14198e-6929-dde1-fc78-34a84b71afdf",
|
|
||||||
"D04": "3a14198e-6929-7ac8-915a-fea51cb2e884"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"溶液堆栈": {
|
|
||||||
"uuid": "3a14198e-d723-2c13-7d12-50143e190a23",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a14198e-d724-e036-afdc-2ae39a7f3383",
|
|
||||||
"A02": "3a14198e-d724-d818-6d4f-5725191a24b5",
|
|
||||||
"A03": "3a14198e-d724-b5bb-adf3-4c5a0da6fb31",
|
|
||||||
"A04": "3a14198e-d724-d378-d266-2508a224a19f",
|
|
||||||
"B01": "3a14198e-d724-afa4-fc82-0ac8a9016791",
|
|
||||||
"B02": "3a14198e-d724-be8a-5e0b-012675e195c6",
|
|
||||||
"B03": "3a14198e-d724-ab4e-48cb-817c3c146707",
|
|
||||||
"B04": "3a14198e-d724-f56e-468b-0110a8feb36a",
|
|
||||||
"C01": "3a14198e-d724-ca48-bb9e-7e85751e55b6",
|
|
||||||
"C02": "3a14198e-d724-cc1e-5c2c-228a130f40a8",
|
|
||||||
"C03": "3a14198e-d724-7f18-1853-39d0c62e1d33",
|
|
||||||
"C04": "3a14198e-d724-0cf1-dea9-a1f40fe7e13c",
|
|
||||||
"D01": "3a14198e-d724-df6d-5e32-5483b3cab583",
|
|
||||||
"D02": "3a14198e-d724-1e28-c885-574c3df468d0",
|
|
||||||
"D03": "3a14198e-d724-28a2-a760-baa896f46b66",
|
|
||||||
"D04": "3a14198e-d724-0ddd-9654-f9352a421de9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"试剂堆栈": {
|
|
||||||
"uuid": "3a14198c-c2cc-0290-e086-44a428fba248",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a14198c-c2cf-8b40-af28-b467808f1c36", # x=1, y=1, code=0001-0001
|
|
||||||
"A02": "3a14198c-c2d0-dc7d-b8d0-e1d88cee3094", # x=1, y=2, code=0001-0002
|
|
||||||
"A03": "3a14198c-c2d0-354f-39ad-642e1a72fcb8", # x=1, y=3, code=0001-0003
|
|
||||||
"A04": "3a14198c-c2d0-725e-523d-34c037ac2440", # x=1, y=4, code=0001-0004
|
|
||||||
"B01": "3a14198c-c2d0-f3e7-871a-e470d144296f", # x=2, y=1, code=0001-0005
|
|
||||||
"B02": "3a14198c-c2d0-2070-efc8-44e245f10c6f", # x=2, y=2, code=0001-0006
|
|
||||||
"B03": "3a14198c-c2d0-1559-105d-0ea30682cab4", # x=2, y=3, code=0001-0007
|
|
||||||
"B04": "3a14198c-c2d0-efce-0939-69ca5a7dfd39" # x=2, y=4, code=0001-0008
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# 物料类型配置
|
|
||||||
# ============================================================================
|
|
||||||
# 说明:
|
|
||||||
# - 格式: PyLabRobot资源类型名称 → Bioyond系统typeId的UUID
|
|
||||||
# - 这个映射基于 resource.model 属性 (不是显示名称!)
|
|
||||||
# - UUID为空表示该类型暂未在Bioyond系统中定义
|
|
||||||
MATERIAL_TYPE_MAPPINGS = {
|
|
||||||
# ================================================配液站资源============================================================
|
|
||||||
# ==================================================样品===============================================================
|
|
||||||
"BIOYOND_PolymerStation_1FlaskCarrier": ("烧杯", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), # 配液站-样品-烧杯
|
|
||||||
"BIOYOND_PolymerStation_1BottleCarrier": ("试剂瓶", "3a14196b-8bcf-a460-4f74-23f21ca79e72"), # 配液站-样品-试剂瓶
|
|
||||||
"BIOYOND_PolymerStation_6StockCarrier": ("分装板", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), # 配液站-样品-分装板
|
|
||||||
"BIOYOND_PolymerStation_Liquid_Vial": ("10%分装小瓶", "3a14196c-76be-2279-4e22-7310d69aed68"), # 配液站-样品-分装板-第一排小瓶
|
|
||||||
"BIOYOND_PolymerStation_Solid_Vial": ("90%分装小瓶", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), # 配液站-样品-分装板-第二排小瓶
|
|
||||||
# ==================================================试剂===============================================================
|
|
||||||
"BIOYOND_PolymerStation_8StockCarrier": ("样品板", "3a14196e-b7a0-a5da-1931-35f3000281e9"), # 配液站-试剂-样品板(8孔)
|
|
||||||
"BIOYOND_PolymerStation_Solid_Stock": ("样品瓶", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), # 配液站-试剂-样品板-样品瓶
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# 动态生成的库位UUID映射(从WAREHOUSE_MAPPING中提取)
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
LOCATION_MAPPING = {}
|
|
||||||
for warehouse_name, warehouse_config in WAREHOUSE_MAPPING.items():
|
|
||||||
if "site_uuids" in warehouse_config:
|
|
||||||
LOCATION_MAPPING.update(warehouse_config["site_uuids"])
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# 物料默认参数配置
|
|
||||||
# ============================================================================
|
|
||||||
# 说明:
|
|
||||||
# - 为特定物料名称自动添加默认参数(如密度、分子量、单位等)
|
|
||||||
# - 格式: 物料名称 → {参数字典}
|
|
||||||
# - 在创建或更新物料时,会自动合并这些参数到 Parameters 字段
|
|
||||||
# - unit: 物料的计量单位(会用于 unit 字段)
|
|
||||||
# - density/densityUnit: 密度信息(会添加到 Parameters 中)
|
|
||||||
|
|
||||||
MATERIAL_DEFAULT_PARAMETERS = {
|
|
||||||
# 溶剂类
|
|
||||||
"NMP": {
|
|
||||||
"unit": "毫升",
|
|
||||||
"density": "1.03",
|
|
||||||
"densityUnit": "g/mL",
|
|
||||||
"description": "N-甲基吡咯烷酮 (N-Methyl-2-pyrrolidone)"
|
|
||||||
},
|
|
||||||
# 可以继续添加其他物料...
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# 物料类型默认参数配置
|
|
||||||
# ============================================================================
|
|
||||||
# 说明:
|
|
||||||
# - 为特定物料类型(UUID)自动添加默认参数
|
|
||||||
# - 格式: Bioyond类型UUID → {参数字典}
|
|
||||||
# - 优先级低于按名称匹配的配置
|
|
||||||
MATERIAL_TYPE_PARAMETERS = {
|
|
||||||
# 示例:
|
|
||||||
# "3a14196b-24f2-ca49-9081-0cab8021bf1a": { # 烧杯
|
|
||||||
# "unit": "个"
|
|
||||||
# }
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,7 @@ import time
|
|||||||
from typing import Optional, Dict, Any, List
|
from typing import Optional, Dict, Any, List
|
||||||
from typing_extensions import TypedDict
|
from typing_extensions import TypedDict
|
||||||
import requests
|
import requests
|
||||||
import pint
|
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG
|
||||||
|
|
||||||
|
|
||||||
from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import BioyondException
|
from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import BioyondException
|
||||||
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
|
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
|
||||||
@@ -26,89 +25,13 @@ class ComputeExperimentDesignReturn(TypedDict):
|
|||||||
class BioyondDispensingStation(BioyondWorkstation):
|
class BioyondDispensingStation(BioyondWorkstation):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: dict = None,
|
config,
|
||||||
deck=None,
|
# 桌子
|
||||||
protocol_type=None,
|
deck,
|
||||||
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""初始化配液站
|
super().__init__(config, deck, *args, **kwargs)
|
||||||
|
|
||||||
Args:
|
|
||||||
config: 配置字典,应包含material_type_mappings等配置
|
|
||||||
deck: Deck对象
|
|
||||||
protocol_type: 协议类型(由ROS系统传递,此处忽略)
|
|
||||||
**kwargs: 其他可能的参数
|
|
||||||
"""
|
|
||||||
if config is None:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
# 将 kwargs 合并到 config 中 (处理扁平化配置如 api_key)
|
|
||||||
config.update(kwargs)
|
|
||||||
|
|
||||||
if deck is None and config:
|
|
||||||
deck = config.get('deck')
|
|
||||||
|
|
||||||
# 🔧 修复: 确保 Deck 上的 warehouses 具有正确的 UUID (必须在 super().__init__ 之前执行,因为父类会触发同步)
|
|
||||||
# 从配置中读取 warehouse_mapping,并应用到实际的 deck 资源上
|
|
||||||
if config and "warehouse_mapping" in config and deck:
|
|
||||||
warehouse_mapping = config["warehouse_mapping"]
|
|
||||||
print(f"正在根据配置更新 Deck warehouse UUIDs... (共有 {len(warehouse_mapping)} 个配置)")
|
|
||||||
|
|
||||||
user_deck = deck
|
|
||||||
# 初始化 warehouses 字典
|
|
||||||
if not hasattr(user_deck, "warehouses") or user_deck.warehouses is None:
|
|
||||||
user_deck.warehouses = {}
|
|
||||||
|
|
||||||
# 1. 尝试从 children 中查找匹配的资源
|
|
||||||
for child in user_deck.children:
|
|
||||||
# 简单判断: 如果名字在 mapping 中,就认为是 warehouse
|
|
||||||
if child.name in warehouse_mapping:
|
|
||||||
user_deck.warehouses[child.name] = child
|
|
||||||
print(f" - 从子资源中找到 warehouse: {child.name}")
|
|
||||||
|
|
||||||
# 2. 如果还是没找到,且 Deck 类有 setup 方法,尝试调用 setup (针对 Deck 对象正确但未初始化的情况)
|
|
||||||
if not user_deck.warehouses and hasattr(user_deck, "setup"):
|
|
||||||
print(" - 尝试调用 deck.setup() 初始化仓库...")
|
|
||||||
try:
|
|
||||||
user_deck.setup()
|
|
||||||
# setup 后重新检查
|
|
||||||
if hasattr(user_deck, "warehouses") and user_deck.warehouses:
|
|
||||||
print(f" - setup() 成功,找到 {len(user_deck.warehouses)} 个仓库")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" - 调用 setup() 失败: {e}")
|
|
||||||
|
|
||||||
# 3. 如果仍然为空,可能需要手动创建 (仅针对特定已知的 Deck 类型进行补救,这里暂时只打印警告)
|
|
||||||
if not user_deck.warehouses:
|
|
||||||
print(" - ⚠️ 仍然无法找到任何 warehouse 资源!")
|
|
||||||
|
|
||||||
for wh_name, wh_config in warehouse_mapping.items():
|
|
||||||
target_uuid = wh_config.get("uuid")
|
|
||||||
|
|
||||||
# 尝试在 deck.warehouses 中查找
|
|
||||||
wh_resource = None
|
|
||||||
if hasattr(user_deck, "warehouses") and wh_name in user_deck.warehouses:
|
|
||||||
wh_resource = user_deck.warehouses[wh_name]
|
|
||||||
|
|
||||||
# 如果没找到,尝试在所有子资源中查找
|
|
||||||
if not wh_resource:
|
|
||||||
wh_resource = user_deck.get_resource(wh_name)
|
|
||||||
|
|
||||||
if wh_resource:
|
|
||||||
if target_uuid:
|
|
||||||
current_uuid = getattr(wh_resource, "uuid", None)
|
|
||||||
print(f"✅ 更新仓库 '{wh_name}' UUID: {current_uuid} -> {target_uuid}")
|
|
||||||
|
|
||||||
# 动态添加 uuid 属性
|
|
||||||
wh_resource.uuid = target_uuid
|
|
||||||
# 同时也确保 category 正确,避免 graphio 识别错误
|
|
||||||
# wh_resource.category = "warehouse"
|
|
||||||
else:
|
|
||||||
print(f"⚠️ 仓库 '{wh_name}' 在配置中没有 UUID")
|
|
||||||
else:
|
|
||||||
print(f"❌ 在 Deck 中未找到配置的仓库: '{wh_name}'")
|
|
||||||
|
|
||||||
super().__init__(bioyond_config=config, deck=deck)
|
|
||||||
|
|
||||||
# self.config = config
|
# self.config = config
|
||||||
# self.api_key = config["api_key"]
|
# self.api_key = config["api_key"]
|
||||||
# self.host = config["api_host"]
|
# self.host = config["api_host"]
|
||||||
@@ -120,41 +43,6 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
# 用于跟踪任务完成状态的字典: {orderCode: {status, order_id, timestamp}}
|
# 用于跟踪任务完成状态的字典: {orderCode: {status, order_id, timestamp}}
|
||||||
self.order_completion_status = {}
|
self.order_completion_status = {}
|
||||||
|
|
||||||
# 初始化 pint 单位注册表
|
|
||||||
self.ureg = pint.UnitRegistry()
|
|
||||||
|
|
||||||
# 化合物信息
|
|
||||||
self.compound_info = {
|
|
||||||
"MolWt": {
|
|
||||||
"MDA": 108.14 * self.ureg.g / self.ureg.mol,
|
|
||||||
"TDA": 122.16 * self.ureg.g / self.ureg.mol,
|
|
||||||
"PAPP": 521.62 * self.ureg.g / self.ureg.mol,
|
|
||||||
"BTDA": 322.23 * self.ureg.g / self.ureg.mol,
|
|
||||||
"BPDA": 294.22 * self.ureg.g / self.ureg.mol,
|
|
||||||
"6FAP": 366.26 * self.ureg.g / self.ureg.mol,
|
|
||||||
"PMDA": 218.12 * self.ureg.g / self.ureg.mol,
|
|
||||||
"MPDA": 108.14 * self.ureg.g / self.ureg.mol,
|
|
||||||
"SIDA": 248.51 * self.ureg.g / self.ureg.mol,
|
|
||||||
"ODA": 200.236 * self.ureg.g / self.ureg.mol,
|
|
||||||
"4,4'-ODA": 200.236 * self.ureg.g / self.ureg.mol,
|
|
||||||
"134": 292.34 * self.ureg.g / self.ureg.mol,
|
|
||||||
},
|
|
||||||
"FuncGroup": {
|
|
||||||
"MDA": "Amine",
|
|
||||||
"TDA": "Amine",
|
|
||||||
"PAPP": "Amine",
|
|
||||||
"BTDA": "Anhydride",
|
|
||||||
"BPDA": "Anhydride",
|
|
||||||
"6FAP": "Amine",
|
|
||||||
"MPDA": "Amine",
|
|
||||||
"SIDA": "Amine",
|
|
||||||
"PMDA": "Anhydride",
|
|
||||||
"ODA": "Amine",
|
|
||||||
"4,4'-ODA": "Amine",
|
|
||||||
"134": "Amine",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _post_project_api(self, endpoint: str, data: Any) -> Dict[str, Any]:
|
def _post_project_api(self, endpoint: str, data: Any) -> Dict[str, Any]:
|
||||||
"""项目接口通用POST调用
|
"""项目接口通用POST调用
|
||||||
|
|
||||||
@@ -166,7 +54,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
dict: 服务端响应,失败时返回 {code:0,message,...}
|
dict: 服务端响应,失败时返回 {code:0,message,...}
|
||||||
"""
|
"""
|
||||||
request_data = {
|
request_data = {
|
||||||
"apiKey": self.bioyond_config["api_key"],
|
"apiKey": API_CONFIG["api_key"],
|
||||||
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
||||||
"data": data
|
"data": data
|
||||||
}
|
}
|
||||||
@@ -197,7 +85,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
dict: 服务端响应,失败时返回 {code:0,message,...}
|
dict: 服务端响应,失败时返回 {code:0,message,...}
|
||||||
"""
|
"""
|
||||||
request_data = {
|
request_data = {
|
||||||
"apiKey": self.bioyond_config["api_key"],
|
"apiKey": API_CONFIG["api_key"],
|
||||||
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
||||||
"data": data
|
"data": data
|
||||||
}
|
}
|
||||||
@@ -230,22 +118,20 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
ratio = json.loads(ratio)
|
ratio = json.loads(ratio)
|
||||||
except Exception:
|
except Exception:
|
||||||
ratio = {}
|
ratio = {}
|
||||||
|
root = str(Path(__file__).resolve().parents[3])
|
||||||
|
if root not in sys.path:
|
||||||
|
sys.path.append(root)
|
||||||
|
try:
|
||||||
|
mod = importlib.import_module("tem.compute")
|
||||||
|
except Exception as e:
|
||||||
|
raise BioyondException(f"无法导入计算模块: {e}")
|
||||||
try:
|
try:
|
||||||
wp = float(wt_percent) if isinstance(wt_percent, str) else wt_percent
|
wp = float(wt_percent) if isinstance(wt_percent, str) else wt_percent
|
||||||
mt = float(m_tot) if isinstance(m_tot, str) else m_tot
|
mt = float(m_tot) if isinstance(m_tot, str) else m_tot
|
||||||
tp = float(titration_percent) if isinstance(titration_percent, str) else titration_percent
|
tp = float(titration_percent) if isinstance(titration_percent, str) else titration_percent
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise BioyondException(f"参数解析失败: {e}")
|
raise BioyondException(f"参数解析失败: {e}")
|
||||||
|
res = mod.generate_experiment_design(ratio=ratio, wt_percent=wp, m_tot=mt, titration_percent=tp)
|
||||||
# 2. 调用内部计算方法
|
|
||||||
res = self._generate_experiment_design(
|
|
||||||
ratio=ratio,
|
|
||||||
wt_percent=wp,
|
|
||||||
m_tot=mt,
|
|
||||||
titration_percent=tp
|
|
||||||
)
|
|
||||||
|
|
||||||
# 3. 构造返回结果
|
|
||||||
out = {
|
out = {
|
||||||
"solutions": res.get("solutions", []),
|
"solutions": res.get("solutions", []),
|
||||||
"titration": res.get("titration", {}),
|
"titration": res.get("titration", {}),
|
||||||
@@ -254,248 +140,11 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
"return_info": json.dumps(res, ensure_ascii=False)
|
"return_info": json.dumps(res, ensure_ascii=False)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|
||||||
except BioyondException:
|
except BioyondException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise BioyondException(str(e))
|
raise BioyondException(str(e))
|
||||||
|
|
||||||
def _generate_experiment_design(
|
|
||||||
self,
|
|
||||||
ratio: dict,
|
|
||||||
wt_percent: float = 0.25,
|
|
||||||
m_tot: float = 70,
|
|
||||||
titration_percent: float = 0.03,
|
|
||||||
) -> dict:
|
|
||||||
"""内部方法:生成实验设计
|
|
||||||
|
|
||||||
根据FuncGroup自动区分二胺和二酐,每种二胺单独配溶液,严格按照ratio顺序投料。
|
|
||||||
|
|
||||||
参数:
|
|
||||||
ratio: 化合物配比字典,格式: {"compound_name": ratio_value}
|
|
||||||
wt_percent: 固体重量百分比
|
|
||||||
m_tot: 反应混合物总质量(g)
|
|
||||||
titration_percent: 滴定溶液百分比
|
|
||||||
|
|
||||||
返回:
|
|
||||||
包含实验设计详细参数的字典
|
|
||||||
"""
|
|
||||||
# 溶剂密度
|
|
||||||
ρ_solvent = 1.03 * self.ureg.g / self.ureg.ml
|
|
||||||
# 二酐溶解度
|
|
||||||
solubility = 0.02 * self.ureg.g / self.ureg.ml
|
|
||||||
# 投入固体时最小溶剂体积
|
|
||||||
V_min = 30 * self.ureg.ml
|
|
||||||
m_tot = m_tot * self.ureg.g
|
|
||||||
|
|
||||||
# 保持ratio中的顺序
|
|
||||||
compound_names = list(ratio.keys())
|
|
||||||
compound_ratios = list(ratio.values())
|
|
||||||
|
|
||||||
# 验证所有化合物是否在 compound_info 中定义
|
|
||||||
undefined_compounds = [name for name in compound_names if name not in self.compound_info["MolWt"]]
|
|
||||||
if undefined_compounds:
|
|
||||||
available = list(self.compound_info["MolWt"].keys())
|
|
||||||
raise ValueError(
|
|
||||||
f"以下化合物未在 compound_info 中定义: {undefined_compounds}。"
|
|
||||||
f"可用的化合物: {available}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 获取各化合物的分子量和官能团类型
|
|
||||||
molecular_weights = [self.compound_info["MolWt"][name] for name in compound_names]
|
|
||||||
func_groups = [self.compound_info["FuncGroup"][name] for name in compound_names]
|
|
||||||
|
|
||||||
# 记录化合物信息用于调试
|
|
||||||
self.hardware_interface._logger.info(f"化合物名称: {compound_names}")
|
|
||||||
self.hardware_interface._logger.info(f"官能团类型: {func_groups}")
|
|
||||||
|
|
||||||
# 按原始顺序分离二胺和二酐
|
|
||||||
ordered_compounds = list(zip(compound_names, compound_ratios, molecular_weights, func_groups))
|
|
||||||
diamine_compounds = [(name, ratio_val, mw, i) for i, (name, ratio_val, mw, fg) in enumerate(ordered_compounds) if fg == "Amine"]
|
|
||||||
anhydride_compounds = [(name, ratio_val, mw, i) for i, (name, ratio_val, mw, fg) in enumerate(ordered_compounds) if fg == "Anhydride"]
|
|
||||||
|
|
||||||
if not diamine_compounds or not anhydride_compounds:
|
|
||||||
raise ValueError(
|
|
||||||
f"需要同时包含二胺(Amine)和二酐(Anhydride)化合物。"
|
|
||||||
f"当前二胺: {[c[0] for c in diamine_compounds]}, "
|
|
||||||
f"当前二酐: {[c[0] for c in anhydride_compounds]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 计算加权平均分子量 (基于摩尔比)
|
|
||||||
total_molar_ratio = sum(compound_ratios)
|
|
||||||
weighted_molecular_weight = sum(ratio_val * mw for ratio_val, mw in zip(compound_ratios, molecular_weights))
|
|
||||||
|
|
||||||
# 取最后一个二酐用于滴定
|
|
||||||
titration_anhydride = anhydride_compounds[-1]
|
|
||||||
solid_anhydrides = anhydride_compounds[:-1] if len(anhydride_compounds) > 1 else []
|
|
||||||
|
|
||||||
# 二胺溶液配制参数 - 每种二胺单独配制
|
|
||||||
diamine_solutions = []
|
|
||||||
total_diamine_volume = 0 * self.ureg.ml
|
|
||||||
|
|
||||||
# 计算反应物的总摩尔量
|
|
||||||
n_reactant = m_tot * wt_percent / weighted_molecular_weight
|
|
||||||
|
|
||||||
for name, ratio_val, mw, order_index in diamine_compounds:
|
|
||||||
# 跳过 SIDA
|
|
||||||
if name == "SIDA":
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 计算该二胺需要的摩尔数
|
|
||||||
n_diamine_needed = n_reactant * ratio_val
|
|
||||||
|
|
||||||
# 二胺溶液配制参数 (每种二胺固定配制参数)
|
|
||||||
m_diamine_solid = 5.0 * self.ureg.g # 每种二胺固体质量
|
|
||||||
V_solvent_for_this = 20 * self.ureg.ml # 每种二胺溶剂体积
|
|
||||||
m_solvent_for_this = ρ_solvent * V_solvent_for_this
|
|
||||||
|
|
||||||
# 计算该二胺溶液的浓度
|
|
||||||
c_diamine = (m_diamine_solid / mw) / V_solvent_for_this
|
|
||||||
|
|
||||||
# 计算需要移取的溶液体积
|
|
||||||
V_diamine_needed = n_diamine_needed / c_diamine
|
|
||||||
|
|
||||||
diamine_solutions.append({
|
|
||||||
"name": name,
|
|
||||||
"order": order_index,
|
|
||||||
"solid_mass": m_diamine_solid.magnitude,
|
|
||||||
"solvent_volume": V_solvent_for_this.magnitude,
|
|
||||||
"concentration": c_diamine.magnitude,
|
|
||||||
"volume_needed": V_diamine_needed.magnitude,
|
|
||||||
"molar_ratio": ratio_val
|
|
||||||
})
|
|
||||||
|
|
||||||
total_diamine_volume += V_diamine_needed
|
|
||||||
|
|
||||||
# 按原始顺序排序
|
|
||||||
diamine_solutions.sort(key=lambda x: x["order"])
|
|
||||||
|
|
||||||
# 计算滴定二酐的质量
|
|
||||||
titration_name, titration_ratio, titration_mw, _ = titration_anhydride
|
|
||||||
m_titration_anhydride = n_reactant * titration_ratio * titration_mw
|
|
||||||
m_titration_90 = m_titration_anhydride * (1 - titration_percent)
|
|
||||||
m_titration_10 = m_titration_anhydride * titration_percent
|
|
||||||
|
|
||||||
# 计算其他固体二酐的质量 (按顺序)
|
|
||||||
solid_anhydride_masses = []
|
|
||||||
for name, ratio_val, mw, order_index in solid_anhydrides:
|
|
||||||
mass = n_reactant * ratio_val * mw
|
|
||||||
solid_anhydride_masses.append({
|
|
||||||
"name": name,
|
|
||||||
"order": order_index,
|
|
||||||
"mass": mass.magnitude,
|
|
||||||
"molar_ratio": ratio_val
|
|
||||||
})
|
|
||||||
|
|
||||||
# 按原始顺序排序
|
|
||||||
solid_anhydride_masses.sort(key=lambda x: x["order"])
|
|
||||||
|
|
||||||
# 计算溶剂用量
|
|
||||||
total_diamine_solution_mass = sum(
|
|
||||||
sol["volume_needed"] * ρ_solvent for sol in diamine_solutions
|
|
||||||
) * self.ureg.ml
|
|
||||||
|
|
||||||
# 预估滴定溶剂量、计算补加溶剂量
|
|
||||||
m_solvent_titration = m_titration_10 / solubility * ρ_solvent
|
|
||||||
m_solvent_add = m_tot * (1 - wt_percent) - total_diamine_solution_mass - m_solvent_titration
|
|
||||||
|
|
||||||
# 检查最小溶剂体积要求
|
|
||||||
total_liquid_volume = (total_diamine_solution_mass + m_solvent_add) / ρ_solvent
|
|
||||||
m_tot_min = V_min / total_liquid_volume * m_tot
|
|
||||||
|
|
||||||
# 如果需要,按比例放大
|
|
||||||
scale_factor = 1.0
|
|
||||||
if m_tot_min > m_tot:
|
|
||||||
scale_factor = (m_tot_min / m_tot).magnitude
|
|
||||||
m_titration_90 *= scale_factor
|
|
||||||
m_titration_10 *= scale_factor
|
|
||||||
m_solvent_add *= scale_factor
|
|
||||||
m_solvent_titration *= scale_factor
|
|
||||||
|
|
||||||
# 更新二胺溶液用量
|
|
||||||
for sol in diamine_solutions:
|
|
||||||
sol["volume_needed"] *= scale_factor
|
|
||||||
|
|
||||||
# 更新固体二酐用量
|
|
||||||
for anhydride in solid_anhydride_masses:
|
|
||||||
anhydride["mass"] *= scale_factor
|
|
||||||
|
|
||||||
m_tot = m_tot_min
|
|
||||||
|
|
||||||
# 生成投料顺序
|
|
||||||
feeding_order = []
|
|
||||||
|
|
||||||
# 1. 固体二酐 (按顺序)
|
|
||||||
for anhydride in solid_anhydride_masses:
|
|
||||||
feeding_order.append({
|
|
||||||
"step": len(feeding_order) + 1,
|
|
||||||
"type": "solid_anhydride",
|
|
||||||
"name": anhydride["name"],
|
|
||||||
"amount": anhydride["mass"],
|
|
||||||
"order": anhydride["order"]
|
|
||||||
})
|
|
||||||
|
|
||||||
# 2. 二胺溶液 (按顺序)
|
|
||||||
for sol in diamine_solutions:
|
|
||||||
feeding_order.append({
|
|
||||||
"step": len(feeding_order) + 1,
|
|
||||||
"type": "diamine_solution",
|
|
||||||
"name": sol["name"],
|
|
||||||
"amount": sol["volume_needed"],
|
|
||||||
"order": sol["order"]
|
|
||||||
})
|
|
||||||
|
|
||||||
# 3. 主要二酐粉末
|
|
||||||
feeding_order.append({
|
|
||||||
"step": len(feeding_order) + 1,
|
|
||||||
"type": "main_anhydride",
|
|
||||||
"name": titration_name,
|
|
||||||
"amount": m_titration_90.magnitude,
|
|
||||||
"order": titration_anhydride[3]
|
|
||||||
})
|
|
||||||
|
|
||||||
# 4. 补加溶剂
|
|
||||||
if m_solvent_add > 0:
|
|
||||||
feeding_order.append({
|
|
||||||
"step": len(feeding_order) + 1,
|
|
||||||
"type": "additional_solvent",
|
|
||||||
"name": "溶剂",
|
|
||||||
"amount": m_solvent_add.magnitude,
|
|
||||||
"order": 999
|
|
||||||
})
|
|
||||||
|
|
||||||
# 5. 滴定二酐溶液
|
|
||||||
feeding_order.append({
|
|
||||||
"step": len(feeding_order) + 1,
|
|
||||||
"type": "titration_anhydride",
|
|
||||||
"name": f"{titration_name} 滴定液",
|
|
||||||
"amount": m_titration_10.magnitude,
|
|
||||||
"titration_solvent": m_solvent_titration.magnitude,
|
|
||||||
"order": titration_anhydride[3]
|
|
||||||
})
|
|
||||||
|
|
||||||
# 返回实验设计结果
|
|
||||||
results = {
|
|
||||||
"total_mass": m_tot.magnitude,
|
|
||||||
"scale_factor": scale_factor,
|
|
||||||
"solutions": diamine_solutions,
|
|
||||||
"solids": solid_anhydride_masses,
|
|
||||||
"titration": {
|
|
||||||
"name": titration_name,
|
|
||||||
"main_portion": m_titration_90.magnitude,
|
|
||||||
"titration_portion": m_titration_10.magnitude,
|
|
||||||
"titration_solvent": m_solvent_titration.magnitude,
|
|
||||||
},
|
|
||||||
"solvents": {
|
|
||||||
"additional_solvent": m_solvent_add.magnitude,
|
|
||||||
"total_liquid_volume": total_liquid_volume.magnitude
|
|
||||||
},
|
|
||||||
"feeding_order": feeding_order,
|
|
||||||
"minimum_required_mass": m_tot_min.magnitude
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
# 90%10%小瓶投料任务创建方法
|
# 90%10%小瓶投料任务创建方法
|
||||||
def create_90_10_vial_feeding_task(self,
|
def create_90_10_vial_feeding_task(self,
|
||||||
order_name: str = None,
|
order_name: str = None,
|
||||||
@@ -1312,108 +961,6 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
'actualVolume': actual_volume
|
'actualVolume': actual_volume
|
||||||
}
|
}
|
||||||
|
|
||||||
def _simplify_report(self, report) -> Dict[str, Any]:
|
|
||||||
"""简化实验报告,只保留关键信息,去除冗余的工作流参数"""
|
|
||||||
if not isinstance(report, dict):
|
|
||||||
return report
|
|
||||||
|
|
||||||
data = report.get('data', {})
|
|
||||||
if not isinstance(data, dict):
|
|
||||||
return report
|
|
||||||
|
|
||||||
# 提取关键信息
|
|
||||||
simplified = {
|
|
||||||
'name': data.get('name'),
|
|
||||||
'code': data.get('code'),
|
|
||||||
'requester': data.get('requester'),
|
|
||||||
'workflowName': data.get('workflowName'),
|
|
||||||
'workflowStep': data.get('workflowStep'),
|
|
||||||
'requestTime': data.get('requestTime'),
|
|
||||||
'startPreparationTime': data.get('startPreparationTime'),
|
|
||||||
'completeTime': data.get('completeTime'),
|
|
||||||
'useTime': data.get('useTime'),
|
|
||||||
'status': data.get('status'),
|
|
||||||
'statusName': data.get('statusName'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# 提取物料信息(简化版)
|
|
||||||
pre_intakes = data.get('preIntakes', [])
|
|
||||||
if pre_intakes and isinstance(pre_intakes, list):
|
|
||||||
first_intake = pre_intakes[0]
|
|
||||||
sample_materials = first_intake.get('sampleMaterials', [])
|
|
||||||
|
|
||||||
# 简化物料信息
|
|
||||||
simplified_materials = []
|
|
||||||
for material in sample_materials:
|
|
||||||
if isinstance(material, dict):
|
|
||||||
mat_info = {
|
|
||||||
'materialName': material.get('materialName'),
|
|
||||||
'materialTypeName': material.get('materialTypeName'),
|
|
||||||
'materialCode': material.get('materialCode'),
|
|
||||||
'materialLocation': material.get('materialLocation'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# 解析parameters中的关键信息(如密度、加料历史等)
|
|
||||||
params_str = material.get('parameters', '{}')
|
|
||||||
try:
|
|
||||||
params = json.loads(params_str) if isinstance(params_str, str) else params_str
|
|
||||||
if isinstance(params, dict):
|
|
||||||
# 只保留关键参数
|
|
||||||
if 'density' in params:
|
|
||||||
mat_info['density'] = params['density']
|
|
||||||
if 'feedingHistory' in params:
|
|
||||||
mat_info['feedingHistory'] = params['feedingHistory']
|
|
||||||
if 'liquidVolume' in params:
|
|
||||||
mat_info['liquidVolume'] = params['liquidVolume']
|
|
||||||
if 'm_diamine_tot' in params:
|
|
||||||
mat_info['m_diamine_tot'] = params['m_diamine_tot']
|
|
||||||
if 'wt_diamine' in params:
|
|
||||||
mat_info['wt_diamine'] = params['wt_diamine']
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
simplified_materials.append(mat_info)
|
|
||||||
|
|
||||||
simplified['sampleMaterials'] = simplified_materials
|
|
||||||
|
|
||||||
# 提取extraProperties中的实际值
|
|
||||||
extra_props = first_intake.get('extraProperties', {})
|
|
||||||
if isinstance(extra_props, dict):
|
|
||||||
simplified_extra = {}
|
|
||||||
for key, value in extra_props.items():
|
|
||||||
try:
|
|
||||||
parsed_value = json.loads(value) if isinstance(value, str) else value
|
|
||||||
simplified_extra[key] = parsed_value
|
|
||||||
except:
|
|
||||||
simplified_extra[key] = value
|
|
||||||
simplified['extraProperties'] = simplified_extra
|
|
||||||
|
|
||||||
return {
|
|
||||||
'data': simplified,
|
|
||||||
'code': report.get('code'),
|
|
||||||
'message': report.get('message'),
|
|
||||||
'timestamp': report.get('timestamp')
|
|
||||||
}
|
|
||||||
|
|
||||||
def scheduler_start(self) -> dict:
|
|
||||||
"""启动调度器 - 启动Bioyond工作站的任务调度器,开始执行队列中的任务
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 包含return_info的字典,return_info为整型(1=成功)
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
BioyondException: 调度器启动失败时抛出异常
|
|
||||||
"""
|
|
||||||
result = self.hardware_interface.scheduler_start()
|
|
||||||
self.hardware_interface._logger.info(f"调度器启动结果: {result}")
|
|
||||||
|
|
||||||
if result != 1:
|
|
||||||
error_msg = "启动调度器失败: 有未处理错误,调度无法启动。请检查Bioyond系统状态。"
|
|
||||||
self.hardware_interface._logger.error(error_msg)
|
|
||||||
raise BioyondException(error_msg)
|
|
||||||
|
|
||||||
return {"return_info": result}
|
|
||||||
|
|
||||||
# 等待多个任务完成并获取实验报告
|
# 等待多个任务完成并获取实验报告
|
||||||
def wait_for_multiple_orders_and_get_reports(self,
|
def wait_for_multiple_orders_and_get_reports(self,
|
||||||
batch_create_result: str = None,
|
batch_create_result: str = None,
|
||||||
@@ -1455,12 +1002,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
|
|
||||||
# 验证batch_create_result参数
|
# 验证batch_create_result参数
|
||||||
if not batch_create_result or batch_create_result == "":
|
if not batch_create_result or batch_create_result == "":
|
||||||
raise BioyondException(
|
raise BioyondException("batch_create_result参数为空,请确保从batch_create节点正确连接handle")
|
||||||
"batch_create_result参数为空,请确保:\n"
|
|
||||||
"1. batch_create节点与wait节点之间正确连接了handle\n"
|
|
||||||
"2. batch_create节点成功执行并返回了结果\n"
|
|
||||||
"3. 检查上游batch_create任务是否成功创建了订单"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 解析batch_create_result JSON对象
|
# 解析batch_create_result JSON对象
|
||||||
try:
|
try:
|
||||||
@@ -1489,17 +1031,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
|
|
||||||
# 验证提取的数据
|
# 验证提取的数据
|
||||||
if not order_codes:
|
if not order_codes:
|
||||||
self.hardware_interface._logger.error(
|
raise BioyondException("batch_create_result中未找到order_codes字段或为空")
|
||||||
f"batch_create任务未生成任何订单。batch_create_result内容: {batch_create_result}"
|
|
||||||
)
|
|
||||||
raise BioyondException(
|
|
||||||
"batch_create_result中未找到order_codes或为空。\n"
|
|
||||||
"可能的原因:\n"
|
|
||||||
"1. batch_create任务执行失败(检查任务是否报错)\n"
|
|
||||||
"2. 物料配置问题(如'物料样品板分配失败')\n"
|
|
||||||
"3. Bioyond系统状态异常\n"
|
|
||||||
f"请检查batch_create任务的执行结果"
|
|
||||||
)
|
|
||||||
if not order_ids:
|
if not order_ids:
|
||||||
raise BioyondException("batch_create_result中未找到order_ids字段或为空")
|
raise BioyondException("batch_create_result中未找到order_ids字段或为空")
|
||||||
|
|
||||||
@@ -1582,8 +1114,6 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
self.hardware_interface._logger.info(
|
self.hardware_interface._logger.info(
|
||||||
f"成功获取任务 {order_code} 的实验报告"
|
f"成功获取任务 {order_code} 的实验报告"
|
||||||
)
|
)
|
||||||
# 简化报告,去除冗余信息
|
|
||||||
report = self._simplify_report(report)
|
|
||||||
|
|
||||||
reports.append({
|
reports.append({
|
||||||
"order_code": order_code,
|
"order_code": order_code,
|
||||||
@@ -1758,7 +1288,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
|||||||
f"开始执行批量物料转移: {len(transfer_groups)}组任务 -> {target_device_id}"
|
f"开始执行批量物料转移: {len(transfer_groups)}组任务 -> {target_device_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
warehouse_mapping = self.bioyond_config.get("warehouse_mapping", {})
|
from .config import WAREHOUSE_MAPPING
|
||||||
results = []
|
results = []
|
||||||
successful_count = 0
|
successful_count = 0
|
||||||
failed_count = 0
|
failed_count = 0
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@ Bioyond Workstation Implementation
|
|||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import threading
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Optional, Union
|
from typing import Dict, Any, List, Optional, Union
|
||||||
import json
|
import json
|
||||||
@@ -28,90 +27,6 @@ from pylabrobot.resources.resource import Resource as ResourcePLR
|
|||||||
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
||||||
|
|
||||||
|
|
||||||
class ConnectionMonitor:
|
|
||||||
"""Bioyond连接监控器"""
|
|
||||||
def __init__(self, workstation, check_interval=30):
|
|
||||||
self.workstation = workstation
|
|
||||||
self.check_interval = check_interval
|
|
||||||
self._running = False
|
|
||||||
self._thread = None
|
|
||||||
self._last_status = "unknown"
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
if self._running:
|
|
||||||
return
|
|
||||||
self._running = True
|
|
||||||
self._thread = threading.Thread(target=self._monitor_loop, daemon=True, name="BioyondConnectionMonitor")
|
|
||||||
self._thread.start()
|
|
||||||
logger.info("Bioyond连接监控器已启动")
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self._running = False
|
|
||||||
if self._thread:
|
|
||||||
self._thread.join(timeout=2)
|
|
||||||
logger.info("Bioyond连接监控器已停止")
|
|
||||||
|
|
||||||
def _monitor_loop(self):
|
|
||||||
while self._running:
|
|
||||||
try:
|
|
||||||
# 使用 lightweight API 检查连接
|
|
||||||
# query_matial_type_list 是比较快的查询
|
|
||||||
start_time = time.time()
|
|
||||||
result = self.workstation.hardware_interface.material_type_list()
|
|
||||||
|
|
||||||
status = "online" if result else "offline"
|
|
||||||
msg = "Connection established" if status == "online" else "Failed to get material type list"
|
|
||||||
|
|
||||||
if status != self._last_status:
|
|
||||||
logger.info(f"Bioyond连接状态变更: {self._last_status} -> {status}")
|
|
||||||
self._publish_event(status, msg)
|
|
||||||
self._last_status = status
|
|
||||||
|
|
||||||
# 发布心跳 (可选,或者只在状态变更时发布)
|
|
||||||
# self._publish_event(status, msg)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Bioyond连接检查异常: {e}")
|
|
||||||
if self._last_status != "error":
|
|
||||||
self._publish_event("error", str(e))
|
|
||||||
self._last_status = "error"
|
|
||||||
|
|
||||||
time.sleep(self.check_interval)
|
|
||||||
|
|
||||||
def _publish_event(self, status, message):
|
|
||||||
try:
|
|
||||||
if hasattr(self.workstation, "_ros_node") and self.workstation._ros_node:
|
|
||||||
event_data = {
|
|
||||||
"status": status,
|
|
||||||
"message": message,
|
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
# 动态发布消息,需要在 ROS2DeviceNode 中有对应支持
|
|
||||||
# 这里假设通用事件发布机制,使用 String 类型的 topic
|
|
||||||
# 话题: /<namespace>/events/device_status
|
|
||||||
ns = self.workstation._ros_node.namespace
|
|
||||||
topic = f"{ns}/events/device_status"
|
|
||||||
|
|
||||||
# 使用 ROS2DeviceNode 的发布功能
|
|
||||||
# 如果没有预定义的 publisher,需要动态创建
|
|
||||||
# 注意:workstation base node 可能没有自动创建 arbitrary publishers 的机制
|
|
||||||
# 这里我们先尝试用 String json 发布
|
|
||||||
|
|
||||||
# 在 ROS2DeviceNode 中通常需要先 create_publisher
|
|
||||||
# 为了简单起见,我们检查是否已有 publisher,没有则创建
|
|
||||||
if not hasattr(self.workstation, "_device_status_pub"):
|
|
||||||
self.workstation._device_status_pub = self.workstation._ros_node.create_publisher(
|
|
||||||
String, topic, 10
|
|
||||||
)
|
|
||||||
|
|
||||||
self.workstation._device_status_pub.publish(
|
|
||||||
convert_to_ros_msg(String, json.dumps(event_data, ensure_ascii=False))
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"发布设备状态事件失败: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
class BioyondResourceSynchronizer(ResourceSynchronizer):
|
class BioyondResourceSynchronizer(ResourceSynchronizer):
|
||||||
"""Bioyond资源同步器
|
"""Bioyond资源同步器
|
||||||
|
|
||||||
@@ -257,8 +172,9 @@ class BioyondResourceSynchronizer(ResourceSynchronizer):
|
|||||||
else:
|
else:
|
||||||
logger.info(f"[同步→Bioyond] ➕ 物料不存在于 Bioyond,将创建新物料并入库")
|
logger.info(f"[同步→Bioyond] ➕ 物料不存在于 Bioyond,将创建新物料并入库")
|
||||||
|
|
||||||
# 第1步:从配置中获取仓库配置
|
# 第1步:获取仓库配置
|
||||||
warehouse_mapping = self.bioyond_config.get("warehouse_mapping", {})
|
from .config import WAREHOUSE_MAPPING
|
||||||
|
warehouse_mapping = WAREHOUSE_MAPPING
|
||||||
|
|
||||||
# 确定目标仓库名称
|
# 确定目标仓库名称
|
||||||
parent_name = None
|
parent_name = None
|
||||||
@@ -320,20 +236,14 @@ class BioyondResourceSynchronizer(ResourceSynchronizer):
|
|||||||
# 第2步:转换为 Bioyond 格式
|
# 第2步:转换为 Bioyond 格式
|
||||||
logger.info(f"[同步→Bioyond] 🔄 转换物料为 Bioyond 格式...")
|
logger.info(f"[同步→Bioyond] 🔄 转换物料为 Bioyond 格式...")
|
||||||
|
|
||||||
# 从配置中获取物料默认参数
|
# 导入物料默认参数配置
|
||||||
material_default_params = self.workstation.bioyond_config.get("material_default_parameters", {})
|
from .config import MATERIAL_DEFAULT_PARAMETERS
|
||||||
material_type_params = self.workstation.bioyond_config.get("material_type_parameters", {})
|
|
||||||
|
|
||||||
# 合并参数配置:物料名称参数 + typeId参数(转换为 type:<uuid> 格式)
|
|
||||||
merged_params = material_default_params.copy()
|
|
||||||
for type_id, params in material_type_params.items():
|
|
||||||
merged_params[f"type:{type_id}"] = params
|
|
||||||
|
|
||||||
bioyond_material = resource_plr_to_bioyond(
|
bioyond_material = resource_plr_to_bioyond(
|
||||||
[resource],
|
[resource],
|
||||||
type_mapping=self.workstation.bioyond_config["material_type_mappings"],
|
type_mapping=self.workstation.bioyond_config["material_type_mappings"],
|
||||||
warehouse_mapping=self.workstation.bioyond_config["warehouse_mapping"],
|
warehouse_mapping=self.workstation.bioyond_config["warehouse_mapping"],
|
||||||
material_params=merged_params
|
material_params=MATERIAL_DEFAULT_PARAMETERS
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
logger.info(f"[同步→Bioyond] 🔧 准备覆盖locations字段,目标仓库: {parent_name}, 库位: {update_site}, UUID: {target_location_uuid[:8]}...")
|
logger.info(f"[同步→Bioyond] 🔧 准备覆盖locations字段,目标仓库: {parent_name}, 库位: {update_site}, UUID: {target_location_uuid[:8]}...")
|
||||||
@@ -556,20 +466,13 @@ class BioyondResourceSynchronizer(ResourceSynchronizer):
|
|||||||
return material_bioyond_id
|
return material_bioyond_id
|
||||||
|
|
||||||
# 转换为 Bioyond 格式
|
# 转换为 Bioyond 格式
|
||||||
# 从配置中获取物料默认参数
|
from .config import MATERIAL_DEFAULT_PARAMETERS
|
||||||
material_default_params = self.workstation.bioyond_config.get("material_default_parameters", {})
|
|
||||||
material_type_params = self.workstation.bioyond_config.get("material_type_parameters", {})
|
|
||||||
|
|
||||||
# 合并参数配置:物料名称参数 + typeId参数(转换为 type:<uuid> 格式)
|
|
||||||
merged_params = material_default_params.copy()
|
|
||||||
for type_id, params in material_type_params.items():
|
|
||||||
merged_params[f"type:{type_id}"] = params
|
|
||||||
|
|
||||||
bioyond_material = resource_plr_to_bioyond(
|
bioyond_material = resource_plr_to_bioyond(
|
||||||
[resource],
|
[resource],
|
||||||
type_mapping=self.workstation.bioyond_config["material_type_mappings"],
|
type_mapping=self.workstation.bioyond_config["material_type_mappings"],
|
||||||
warehouse_mapping=self.workstation.bioyond_config["warehouse_mapping"],
|
warehouse_mapping=self.workstation.bioyond_config["warehouse_mapping"],
|
||||||
material_params=merged_params
|
material_params=MATERIAL_DEFAULT_PARAMETERS
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
# ⚠️ 关键:创建物料时不设置 locations,让 Bioyond 系统暂不分配库位
|
# ⚠️ 关键:创建物料时不设置 locations,让 Bioyond 系统暂不分配库位
|
||||||
@@ -623,7 +526,8 @@ class BioyondResourceSynchronizer(ResourceSynchronizer):
|
|||||||
logger.info(f"[物料入库] 目标库位: {update_site}")
|
logger.info(f"[物料入库] 目标库位: {update_site}")
|
||||||
|
|
||||||
# 获取仓库配置和目标库位 UUID
|
# 获取仓库配置和目标库位 UUID
|
||||||
warehouse_mapping = self.workstation.bioyond_config.get("warehouse_mapping", {})
|
from .config import WAREHOUSE_MAPPING
|
||||||
|
warehouse_mapping = WAREHOUSE_MAPPING
|
||||||
|
|
||||||
parent_name = None
|
parent_name = None
|
||||||
target_location_uuid = None
|
target_location_uuid = None
|
||||||
@@ -678,44 +582,6 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
集成Bioyond物料管理的工作站实现
|
集成Bioyond物料管理的工作站实现
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _publish_task_status(
|
|
||||||
self,
|
|
||||||
task_id: str,
|
|
||||||
task_type: str,
|
|
||||||
status: str,
|
|
||||||
result: dict = None,
|
|
||||||
progress: float = 0.0,
|
|
||||||
task_code: str = None
|
|
||||||
):
|
|
||||||
"""发布任务状态事件"""
|
|
||||||
try:
|
|
||||||
if not getattr(self, "_ros_node", None):
|
|
||||||
return
|
|
||||||
|
|
||||||
event_data = {
|
|
||||||
"task_id": task_id,
|
|
||||||
"task_code": task_code,
|
|
||||||
"task_type": task_type,
|
|
||||||
"status": status,
|
|
||||||
"progress": progress,
|
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
}
|
|
||||||
if result:
|
|
||||||
event_data["result"] = result
|
|
||||||
|
|
||||||
topic = f"{self._ros_node.namespace}/events/task_status"
|
|
||||||
|
|
||||||
if not hasattr(self, "_task_status_pub"):
|
|
||||||
self._task_status_pub = self._ros_node.create_publisher(
|
|
||||||
String, topic, 10
|
|
||||||
)
|
|
||||||
|
|
||||||
self._task_status_pub.publish(
|
|
||||||
convert_to_ros_msg(String, json.dumps(event_data, ensure_ascii=False))
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"发布任务状态事件失败: {e}")
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bioyond_config: Optional[Dict[str, Any]] = None,
|
bioyond_config: Optional[Dict[str, Any]] = None,
|
||||||
@@ -737,28 +603,10 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
raise ValueError("Deck 配置不能为空,请在配置文件中添加正确的 deck 配置")
|
raise ValueError("Deck 配置不能为空,请在配置文件中添加正确的 deck 配置")
|
||||||
|
|
||||||
# 初始化 warehouses 属性
|
# 初始化 warehouses 属性
|
||||||
if not hasattr(self.deck, "warehouses") or self.deck.warehouses is None:
|
self.deck.warehouses = {}
|
||||||
self.deck.warehouses = {}
|
for resource in self.deck.children:
|
||||||
|
if isinstance(resource, WareHouse):
|
||||||
# 仅当 warehouses 为空时尝试重新扫描(避免覆盖子类的修复)
|
self.deck.warehouses[resource.name] = resource
|
||||||
if not self.deck.warehouses:
|
|
||||||
for resource in self.deck.children:
|
|
||||||
# 兼容性增强: 只要是仓库类别或者是 WareHouse 实例均可
|
|
||||||
is_warehouse = isinstance(resource, WareHouse) or getattr(resource, "category", "") == "warehouse"
|
|
||||||
|
|
||||||
# 如果配置中有定义,也可以认定为 warehouse
|
|
||||||
if not is_warehouse and "warehouse_mapping" in bioyond_config:
|
|
||||||
if resource.name in bioyond_config["warehouse_mapping"]:
|
|
||||||
is_warehouse = True
|
|
||||||
|
|
||||||
if is_warehouse:
|
|
||||||
self.deck.warehouses[resource.name] = resource
|
|
||||||
# 确保 category 被正确设置,方便后续使用
|
|
||||||
if getattr(resource, "category", "") != "warehouse":
|
|
||||||
try:
|
|
||||||
resource.category = "warehouse"
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 创建通信模块
|
# 创建通信模块
|
||||||
self._create_communication_module(bioyond_config)
|
self._create_communication_module(bioyond_config)
|
||||||
@@ -777,22 +625,18 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
self._set_workflow_mappings(bioyond_config["workflow_mappings"])
|
self._set_workflow_mappings(bioyond_config["workflow_mappings"])
|
||||||
|
|
||||||
# 准备 HTTP 报送接收服务配置(延迟到 post_init 启动)
|
# 准备 HTTP 报送接收服务配置(延迟到 post_init 启动)
|
||||||
# 从 bioyond_config 中的 http_service_config 获取
|
# 从 bioyond_config 中获取,如果没有则使用默认值
|
||||||
http_service_cfg = bioyond_config.get("http_service_config", {})
|
|
||||||
self._http_service_config = {
|
self._http_service_config = {
|
||||||
"host": http_service_cfg.get("http_service_host", "127.0.0.1"),
|
"host": bioyond_config.get("http_service_host", bioyond_config.get("HTTP_host", "")),
|
||||||
"port": http_service_cfg.get("http_service_port", 8080)
|
"port": bioyond_config.get("http_service_port", bioyond_config.get("HTTP_port", 0))
|
||||||
}
|
}
|
||||||
self.http_service = None # 将在 post_init 启动
|
self.http_service = None # 将在 post_init 中启动
|
||||||
self.connection_monitor = None # 将在 post_init 启动
|
|
||||||
|
|
||||||
logger.info(f"Bioyond工作站初始化完成")
|
logger.info(f"Bioyond工作站初始化完成")
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""析构函数:清理资源,停止 HTTP 服务"""
|
"""析构函数:清理资源,停止 HTTP 服务"""
|
||||||
try:
|
try:
|
||||||
if hasattr(self, 'connection_monitor') and self.connection_monitor:
|
|
||||||
self.connection_monitor.stop()
|
|
||||||
if hasattr(self, 'http_service') and self.http_service is not None:
|
if hasattr(self, 'http_service') and self.http_service is not None:
|
||||||
logger.info("正在停止 HTTP 报送服务...")
|
logger.info("正在停止 HTTP 报送服务...")
|
||||||
self.http_service.stop()
|
self.http_service.stop()
|
||||||
@@ -802,13 +646,6 @@ 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
|
||||||
|
|
||||||
# 启动连接监控
|
|
||||||
try:
|
|
||||||
self.connection_monitor = ConnectionMonitor(self)
|
|
||||||
self.connection_monitor.start()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"启动连接监控失败: {e}")
|
|
||||||
|
|
||||||
# 启动 HTTP 报送接收服务(现在 device_id 已可用)
|
# 启动 HTTP 报送接收服务(现在 device_id 已可用)
|
||||||
# ⚠️ 检查子类是否已经自己管理 HTTP 服务
|
# ⚠️ 检查子类是否已经自己管理 HTTP 服务
|
||||||
if self.bioyond_config.get("_disable_auto_http_service"):
|
if self.bioyond_config.get("_disable_auto_http_service"):
|
||||||
@@ -853,14 +690,14 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
|
|
||||||
def _create_communication_module(self, config: Optional[Dict[str, Any]] = None) -> None:
|
def _create_communication_module(self, config: Optional[Dict[str, Any]] = None) -> None:
|
||||||
"""创建Bioyond通信模块"""
|
"""创建Bioyond通信模块"""
|
||||||
# 直接使用传入的配置,不再使用默认值
|
# 使用传入的 config 参数(来自 bioyond_config)
|
||||||
# 所有配置必须从 JSON 文件中提供
|
# 不再依赖全局变量 API_CONFIG 等
|
||||||
if config:
|
if config:
|
||||||
self.bioyond_config = config
|
self.bioyond_config = config
|
||||||
else:
|
else:
|
||||||
# 如果没有配置,使用空字典(会导致后续错误,但这是预期的)
|
# 如果没有传入配置,创建空配置(用于测试或兼容性)
|
||||||
self.bioyond_config = {}
|
self.bioyond_config = {}
|
||||||
print("警告: 未提供 bioyond_config,请确保在 JSON 配置文件中提供完整配置")
|
|
||||||
|
|
||||||
self.hardware_interface = BioyondV1RPC(self.bioyond_config)
|
self.hardware_interface = BioyondV1RPC(self.bioyond_config)
|
||||||
|
|
||||||
@@ -1174,15 +1011,7 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
|
|
||||||
workflow_id = self._get_workflow(actual_workflow_name)
|
workflow_id = self._get_workflow(actual_workflow_name)
|
||||||
if workflow_id:
|
if workflow_id:
|
||||||
# 兼容 BioyondReactionStation 中 workflow_sequence 被重写为 property 的情况
|
self.workflow_sequence.append(workflow_id)
|
||||||
if isinstance(self.workflow_sequence, list):
|
|
||||||
self.workflow_sequence.append(workflow_id)
|
|
||||||
elif hasattr(self, "_cached_workflow_sequence") and isinstance(self._cached_workflow_sequence, list):
|
|
||||||
self._cached_workflow_sequence.append(workflow_id)
|
|
||||||
else:
|
|
||||||
print(f"❌ 无法添加工作流: workflow_sequence 类型错误 {type(self.workflow_sequence)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
print(f"添加工作流到执行顺序: {actual_workflow_name} -> {workflow_id}")
|
print(f"添加工作流到执行顺序: {actual_workflow_name} -> {workflow_id}")
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -1383,22 +1212,6 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
# TODO: 根据实际业务需求处理步骤完成逻辑
|
# TODO: 根据实际业务需求处理步骤完成逻辑
|
||||||
# 例如:更新数据库、触发后续流程等
|
# 例如:更新数据库、触发后续流程等
|
||||||
|
|
||||||
# 发布任务状态事件 (running/progress update)
|
|
||||||
self._publish_task_status(
|
|
||||||
task_id=data.get('orderCode'), # 使用 OrderCode 作为关联 ID
|
|
||||||
task_code=data.get('orderCode'),
|
|
||||||
task_type="bioyond_step",
|
|
||||||
status="running",
|
|
||||||
progress=0.5, # 步骤完成视为任务进行中
|
|
||||||
result={"step_name": data.get('stepName'), "step_id": data.get('stepId')}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 更新物料信息
|
|
||||||
# 步骤完成后,物料状态可能发生变化(如位置、用量等),触发同步
|
|
||||||
logger.info(f"[步骤完成报送] 触发物料同步...")
|
|
||||||
self.resource_synchronizer.sync_from_external()
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"processed": True,
|
"processed": True,
|
||||||
"step_id": data.get('stepId'),
|
"step_id": data.get('stepId'),
|
||||||
@@ -1433,17 +1246,6 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
|
|
||||||
# TODO: 根据实际业务需求处理通量完成逻辑
|
# TODO: 根据实际业务需求处理通量完成逻辑
|
||||||
|
|
||||||
# 发布任务状态事件
|
|
||||||
self._publish_task_status(
|
|
||||||
task_id=data.get('orderCode'),
|
|
||||||
task_code=data.get('orderCode'),
|
|
||||||
task_type="bioyond_sample",
|
|
||||||
status="running",
|
|
||||||
progress=0.7,
|
|
||||||
result={"sample_id": data.get('sampleId'), "status": status_desc}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"processed": True,
|
"processed": True,
|
||||||
"sample_id": data.get('sampleId'),
|
"sample_id": data.get('sampleId'),
|
||||||
@@ -1483,32 +1285,6 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
# TODO: 根据实际业务需求处理任务完成逻辑
|
# TODO: 根据实际业务需求处理任务完成逻辑
|
||||||
# 例如:更新物料库存、生成报表等
|
# 例如:更新物料库存、生成报表等
|
||||||
|
|
||||||
# 映射状态到事件状态
|
|
||||||
event_status = "completed"
|
|
||||||
if str(data.get('status')) in ["-11", "-12"]:
|
|
||||||
event_status = "error"
|
|
||||||
elif str(data.get('status')) == "30":
|
|
||||||
event_status = "completed"
|
|
||||||
else:
|
|
||||||
event_status = "running" # 其他状态视为运行中(或根据实际定义)
|
|
||||||
|
|
||||||
# 发布任务状态事件
|
|
||||||
self._publish_task_status(
|
|
||||||
task_id=data.get('orderCode'),
|
|
||||||
task_code=data.get('orderCode'),
|
|
||||||
task_type="bioyond_order",
|
|
||||||
status=event_status,
|
|
||||||
progress=1.0 if event_status in ["completed", "error"] else 0.9,
|
|
||||||
result={"order_name": data.get('orderName'), "status": status_desc, "materials_count": len(used_materials)}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 更新物料信息
|
|
||||||
# 任务完成后,且状态为完成时,触发同步以更新最终物料状态
|
|
||||||
if event_status == "completed":
|
|
||||||
logger.info(f"[任务完成报送] 触发物料同步...")
|
|
||||||
self.resource_synchronizer.sync_from_external()
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"processed": True,
|
"processed": True,
|
||||||
"order_code": data.get('orderCode'),
|
"order_code": data.get('orderCode'),
|
||||||
|
|||||||
Binary file not shown.
@@ -459,12 +459,12 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
# 验证必需字段
|
# 验证必需字段
|
||||||
if 'brand' in request_data:
|
if 'brand' in request_data:
|
||||||
if request_data['brand'] == "bioyond": # 奔曜
|
if request_data['brand'] == "bioyond": # 奔曜
|
||||||
material_data = request_data["text"]
|
error_msg = request_data["text"]
|
||||||
logger.info(f"收到奔曜物料变更报送: {material_data}")
|
logger.info(f"收到奔曜错误处理报送: {error_msg}")
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message=f"物料变更报送已收到: {material_data}",
|
message=f"错误处理报送已收到: {error_msg}",
|
||||||
acknowledgment_id=f"MATERIAL_{int(time.time() * 1000)}_{material_data.get('id', 'unknown')}",
|
acknowledgment_id=f"ERROR_{int(time.time() * 1000)}_{error_msg.get('action_id', 'unknown')}",
|
||||||
data=None
|
data=None
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
589
unilabos/registry/devices/bioyond.yaml
Normal file
589
unilabos/registry/devices/bioyond.yaml
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
workstation.bioyond_dispensing_station:
|
||||||
|
category:
|
||||||
|
- workstation
|
||||||
|
- bioyond
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-batch_create_90_10_vial_feeding_tasks:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
delay_time: null
|
||||||
|
hold_m_name: null
|
||||||
|
liquid_material_name: NMP
|
||||||
|
speed: null
|
||||||
|
temperature: null
|
||||||
|
titration: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
delay_time:
|
||||||
|
type: string
|
||||||
|
hold_m_name:
|
||||||
|
type: string
|
||||||
|
liquid_material_name:
|
||||||
|
default: NMP
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
type: string
|
||||||
|
temperature:
|
||||||
|
type: string
|
||||||
|
titration:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- titration
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: batch_create_90_10_vial_feeding_tasks参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-batch_create_diamine_solution_tasks:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
delay_time: null
|
||||||
|
liquid_material_name: NMP
|
||||||
|
solutions: null
|
||||||
|
speed: null
|
||||||
|
temperature: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
delay_time:
|
||||||
|
type: string
|
||||||
|
liquid_material_name:
|
||||||
|
default: NMP
|
||||||
|
type: string
|
||||||
|
solutions:
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
type: string
|
||||||
|
temperature:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- solutions
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: batch_create_diamine_solution_tasks参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-brief_step_parameters:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
data: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: brief_step_parameters参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-compute_experiment_design:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
m_tot: '70'
|
||||||
|
ratio: null
|
||||||
|
titration_percent: '0.03'
|
||||||
|
wt_percent: '0.25'
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
m_tot:
|
||||||
|
default: '70'
|
||||||
|
type: string
|
||||||
|
ratio:
|
||||||
|
type: object
|
||||||
|
titration_percent:
|
||||||
|
default: '0.03'
|
||||||
|
type: string
|
||||||
|
wt_percent:
|
||||||
|
default: '0.25'
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ratio
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
feeding_order:
|
||||||
|
items: {}
|
||||||
|
title: Feeding Order
|
||||||
|
type: array
|
||||||
|
return_info:
|
||||||
|
title: Return Info
|
||||||
|
type: string
|
||||||
|
solutions:
|
||||||
|
items: {}
|
||||||
|
title: Solutions
|
||||||
|
type: array
|
||||||
|
solvents:
|
||||||
|
additionalProperties: true
|
||||||
|
title: Solvents
|
||||||
|
type: object
|
||||||
|
titration:
|
||||||
|
additionalProperties: true
|
||||||
|
title: Titration
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- solutions
|
||||||
|
- titration
|
||||||
|
- solvents
|
||||||
|
- feeding_order
|
||||||
|
- return_info
|
||||||
|
title: ComputeExperimentDesignReturn
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: compute_experiment_design参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-process_order_finish_report:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
report_request: null
|
||||||
|
used_materials: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
report_request:
|
||||||
|
type: string
|
||||||
|
used_materials:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- report_request
|
||||||
|
- used_materials
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: process_order_finish_report参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-project_order_report:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
order_id: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- order_id
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: project_order_report参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-query_resource_by_name:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
material_name: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
material_name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- material_name
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: query_resource_by_name参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-transfer_materials_to_reaction_station:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
target_device_id: null
|
||||||
|
transfer_groups: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
target_device_id:
|
||||||
|
type: string
|
||||||
|
transfer_groups:
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- target_device_id
|
||||||
|
- transfer_groups
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: transfer_materials_to_reaction_station参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_for_multiple_orders_and_get_reports:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
batch_create_result: null
|
||||||
|
check_interval: 10
|
||||||
|
timeout: 7200
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
batch_create_result:
|
||||||
|
type: string
|
||||||
|
check_interval:
|
||||||
|
default: 10
|
||||||
|
type: integer
|
||||||
|
timeout:
|
||||||
|
default: 7200
|
||||||
|
type: integer
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_for_multiple_orders_and_get_reports参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-workflow_sample_locations:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
workflow_id: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
workflow_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- workflow_id
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: workflow_sample_locations参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
create_90_10_vial_feeding_task:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
delay_time: delay_time
|
||||||
|
hold_m_name: hold_m_name
|
||||||
|
order_name: order_name
|
||||||
|
percent_10_1_assign_material_name: percent_10_1_assign_material_name
|
||||||
|
percent_10_1_liquid_material_name: percent_10_1_liquid_material_name
|
||||||
|
percent_10_1_target_weigh: percent_10_1_target_weigh
|
||||||
|
percent_10_1_volume: percent_10_1_volume
|
||||||
|
percent_10_2_assign_material_name: percent_10_2_assign_material_name
|
||||||
|
percent_10_2_liquid_material_name: percent_10_2_liquid_material_name
|
||||||
|
percent_10_2_target_weigh: percent_10_2_target_weigh
|
||||||
|
percent_10_2_volume: percent_10_2_volume
|
||||||
|
percent_10_3_assign_material_name: percent_10_3_assign_material_name
|
||||||
|
percent_10_3_liquid_material_name: percent_10_3_liquid_material_name
|
||||||
|
percent_10_3_target_weigh: percent_10_3_target_weigh
|
||||||
|
percent_10_3_volume: percent_10_3_volume
|
||||||
|
percent_90_1_assign_material_name: percent_90_1_assign_material_name
|
||||||
|
percent_90_1_target_weigh: percent_90_1_target_weigh
|
||||||
|
percent_90_2_assign_material_name: percent_90_2_assign_material_name
|
||||||
|
percent_90_2_target_weigh: percent_90_2_target_weigh
|
||||||
|
percent_90_3_assign_material_name: percent_90_3_assign_material_name
|
||||||
|
percent_90_3_target_weigh: percent_90_3_target_weigh
|
||||||
|
speed: speed
|
||||||
|
temperature: temperature
|
||||||
|
goal_default:
|
||||||
|
delay_time: ''
|
||||||
|
hold_m_name: ''
|
||||||
|
order_name: ''
|
||||||
|
percent_10_1_assign_material_name: ''
|
||||||
|
percent_10_1_liquid_material_name: ''
|
||||||
|
percent_10_1_target_weigh: ''
|
||||||
|
percent_10_1_volume: ''
|
||||||
|
percent_10_2_assign_material_name: ''
|
||||||
|
percent_10_2_liquid_material_name: ''
|
||||||
|
percent_10_2_target_weigh: ''
|
||||||
|
percent_10_2_volume: ''
|
||||||
|
percent_10_3_assign_material_name: ''
|
||||||
|
percent_10_3_liquid_material_name: ''
|
||||||
|
percent_10_3_target_weigh: ''
|
||||||
|
percent_10_3_volume: ''
|
||||||
|
percent_90_1_assign_material_name: ''
|
||||||
|
percent_90_1_target_weigh: ''
|
||||||
|
percent_90_2_assign_material_name: ''
|
||||||
|
percent_90_2_target_weigh: ''
|
||||||
|
percent_90_3_assign_material_name: ''
|
||||||
|
percent_90_3_target_weigh: ''
|
||||||
|
speed: ''
|
||||||
|
temperature: ''
|
||||||
|
handles: {}
|
||||||
|
result:
|
||||||
|
return_info: return_info
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: DispenStationVialFeed_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
delay_time:
|
||||||
|
type: string
|
||||||
|
hold_m_name:
|
||||||
|
type: string
|
||||||
|
order_name:
|
||||||
|
type: string
|
||||||
|
percent_10_1_assign_material_name:
|
||||||
|
type: string
|
||||||
|
percent_10_1_liquid_material_name:
|
||||||
|
type: string
|
||||||
|
percent_10_1_target_weigh:
|
||||||
|
type: string
|
||||||
|
percent_10_1_volume:
|
||||||
|
type: string
|
||||||
|
percent_10_2_assign_material_name:
|
||||||
|
type: string
|
||||||
|
percent_10_2_liquid_material_name:
|
||||||
|
type: string
|
||||||
|
percent_10_2_target_weigh:
|
||||||
|
type: string
|
||||||
|
percent_10_2_volume:
|
||||||
|
type: string
|
||||||
|
percent_10_3_assign_material_name:
|
||||||
|
type: string
|
||||||
|
percent_10_3_liquid_material_name:
|
||||||
|
type: string
|
||||||
|
percent_10_3_target_weigh:
|
||||||
|
type: string
|
||||||
|
percent_10_3_volume:
|
||||||
|
type: string
|
||||||
|
percent_90_1_assign_material_name:
|
||||||
|
type: string
|
||||||
|
percent_90_1_target_weigh:
|
||||||
|
type: string
|
||||||
|
percent_90_2_assign_material_name:
|
||||||
|
type: string
|
||||||
|
percent_90_2_target_weigh:
|
||||||
|
type: string
|
||||||
|
percent_90_3_assign_material_name:
|
||||||
|
type: string
|
||||||
|
percent_90_3_target_weigh:
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
type: string
|
||||||
|
temperature:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- order_name
|
||||||
|
- percent_90_1_assign_material_name
|
||||||
|
- percent_90_1_target_weigh
|
||||||
|
- percent_90_2_assign_material_name
|
||||||
|
- percent_90_2_target_weigh
|
||||||
|
- percent_90_3_assign_material_name
|
||||||
|
- percent_90_3_target_weigh
|
||||||
|
- percent_10_1_assign_material_name
|
||||||
|
- percent_10_1_target_weigh
|
||||||
|
- percent_10_1_volume
|
||||||
|
- percent_10_1_liquid_material_name
|
||||||
|
- percent_10_2_assign_material_name
|
||||||
|
- percent_10_2_target_weigh
|
||||||
|
- percent_10_2_volume
|
||||||
|
- percent_10_2_liquid_material_name
|
||||||
|
- percent_10_3_assign_material_name
|
||||||
|
- percent_10_3_target_weigh
|
||||||
|
- percent_10_3_volume
|
||||||
|
- percent_10_3_liquid_material_name
|
||||||
|
- speed
|
||||||
|
- temperature
|
||||||
|
- delay_time
|
||||||
|
- hold_m_name
|
||||||
|
title: DispenStationVialFeed_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: DispenStationVialFeed_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: DispenStationVialFeed
|
||||||
|
type: object
|
||||||
|
type: DispenStationVialFeed
|
||||||
|
create_diamine_solution_task:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
delay_time: delay_time
|
||||||
|
hold_m_name: hold_m_name
|
||||||
|
liquid_material_name: liquid_material_name
|
||||||
|
material_name: material_name
|
||||||
|
order_name: order_name
|
||||||
|
speed: speed
|
||||||
|
target_weigh: target_weigh
|
||||||
|
temperature: temperature
|
||||||
|
volume: volume
|
||||||
|
goal_default:
|
||||||
|
delay_time: ''
|
||||||
|
hold_m_name: ''
|
||||||
|
liquid_material_name: ''
|
||||||
|
material_name: ''
|
||||||
|
order_name: ''
|
||||||
|
speed: ''
|
||||||
|
target_weigh: ''
|
||||||
|
temperature: ''
|
||||||
|
volume: ''
|
||||||
|
handles: {}
|
||||||
|
result:
|
||||||
|
return_info: return_info
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: DispenStationSolnPrep_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
delay_time:
|
||||||
|
type: string
|
||||||
|
hold_m_name:
|
||||||
|
type: string
|
||||||
|
liquid_material_name:
|
||||||
|
type: string
|
||||||
|
material_name:
|
||||||
|
type: string
|
||||||
|
order_name:
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
type: string
|
||||||
|
target_weigh:
|
||||||
|
type: string
|
||||||
|
temperature:
|
||||||
|
type: string
|
||||||
|
volume:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- order_name
|
||||||
|
- material_name
|
||||||
|
- target_weigh
|
||||||
|
- volume
|
||||||
|
- liquid_material_name
|
||||||
|
- speed
|
||||||
|
- temperature
|
||||||
|
- delay_time
|
||||||
|
- hold_m_name
|
||||||
|
title: DispenStationSolnPrep_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: DispenStationSolnPrep_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: DispenStationSolnPrep
|
||||||
|
type: object
|
||||||
|
type: DispenStationSolnPrep
|
||||||
|
module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation
|
||||||
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
config_info: []
|
||||||
|
description: ''
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
config:
|
||||||
|
type: string
|
||||||
|
deck:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- config
|
||||||
|
- deck
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
version: 1.0.0
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,71 @@ bioyond_dispensing_station:
|
|||||||
title: brief_step_parameters参数
|
title: brief_step_parameters参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-compute_experiment_design:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
m_tot: '70'
|
||||||
|
ratio: null
|
||||||
|
titration_percent: '0.03'
|
||||||
|
wt_percent: '0.25'
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
m_tot:
|
||||||
|
default: '70'
|
||||||
|
type: string
|
||||||
|
ratio:
|
||||||
|
type: object
|
||||||
|
titration_percent:
|
||||||
|
default: '0.03'
|
||||||
|
type: string
|
||||||
|
wt_percent:
|
||||||
|
default: '0.25'
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ratio
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
feeding_order:
|
||||||
|
items: {}
|
||||||
|
title: Feeding Order
|
||||||
|
type: array
|
||||||
|
return_info:
|
||||||
|
title: Return Info
|
||||||
|
type: string
|
||||||
|
solutions:
|
||||||
|
items: {}
|
||||||
|
title: Solutions
|
||||||
|
type: array
|
||||||
|
solvents:
|
||||||
|
additionalProperties: true
|
||||||
|
title: Solvents
|
||||||
|
type: object
|
||||||
|
titration:
|
||||||
|
additionalProperties: true
|
||||||
|
title: Titration
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- solutions
|
||||||
|
- titration
|
||||||
|
- solvents
|
||||||
|
- feeding_order
|
||||||
|
- return_info
|
||||||
|
title: ComputeExperimentDesignReturn
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: compute_experiment_design参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-process_order_finish_report:
|
auto-process_order_finish_report:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -109,6 +174,35 @@ bioyond_dispensing_station:
|
|||||||
title: query_resource_by_name参数
|
title: query_resource_by_name参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-transfer_materials_to_reaction_station:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
target_device_id: null
|
||||||
|
transfer_groups: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
target_device_id:
|
||||||
|
type: string
|
||||||
|
transfer_groups:
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- target_device_id
|
||||||
|
- transfer_groups
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: transfer_materials_to_reaction_station参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-workflow_sample_locations:
|
auto-workflow_sample_locations:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -300,99 +394,6 @@ bioyond_dispensing_station:
|
|||||||
title: BatchCreateDiamineSolutionTasks
|
title: BatchCreateDiamineSolutionTasks
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
compute_experiment_design:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
m_tot: m_tot
|
|
||||||
ratio: ratio
|
|
||||||
titration_percent: titration_percent
|
|
||||||
wt_percent: wt_percent
|
|
||||||
goal_default:
|
|
||||||
m_tot: '70'
|
|
||||||
ratio: ''
|
|
||||||
titration_percent: '0.03'
|
|
||||||
wt_percent: '0.25'
|
|
||||||
handles:
|
|
||||||
output:
|
|
||||||
- data_key: solutions
|
|
||||||
data_source: executor
|
|
||||||
data_type: array
|
|
||||||
handler_key: solutions
|
|
||||||
io_type: sink
|
|
||||||
label: Solution Data From Python
|
|
||||||
- data_key: titration
|
|
||||||
data_source: executor
|
|
||||||
data_type: object
|
|
||||||
handler_key: titration
|
|
||||||
io_type: sink
|
|
||||||
label: Titration Data From Calculation Node
|
|
||||||
- data_key: solvents
|
|
||||||
data_source: executor
|
|
||||||
data_type: object
|
|
||||||
handler_key: solvents
|
|
||||||
io_type: sink
|
|
||||||
label: Solvents Data From Calculation Node
|
|
||||||
- data_key: feeding_order
|
|
||||||
data_source: executor
|
|
||||||
data_type: array
|
|
||||||
handler_key: feeding_order
|
|
||||||
io_type: sink
|
|
||||||
label: Feeding Order Data From Calculation Node
|
|
||||||
result:
|
|
||||||
feeding_order: feeding_order
|
|
||||||
return_info: return_info
|
|
||||||
solutions: solutions
|
|
||||||
solvents: solvents
|
|
||||||
titration: titration
|
|
||||||
schema:
|
|
||||||
description: 计算实验设计,输出solutions/titration/solvents/feeding_order用于后续节点。
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
m_tot:
|
|
||||||
default: '70'
|
|
||||||
description: 总质量(g)
|
|
||||||
type: string
|
|
||||||
ratio:
|
|
||||||
description: 组分摩尔比的对象,保持输入顺序,如{"MDA":1,"BTDA":1}
|
|
||||||
type: string
|
|
||||||
titration_percent:
|
|
||||||
default: '0.03'
|
|
||||||
description: 滴定比例(10%部分)
|
|
||||||
type: string
|
|
||||||
wt_percent:
|
|
||||||
default: '0.25'
|
|
||||||
description: 目标固含质量分数
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- ratio
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
feeding_order:
|
|
||||||
type: array
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
solutions:
|
|
||||||
type: array
|
|
||||||
solvents:
|
|
||||||
type: object
|
|
||||||
titration:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- solutions
|
|
||||||
- titration
|
|
||||||
- solvents
|
|
||||||
- feeding_order
|
|
||||||
- return_info
|
|
||||||
title: ComputeExperimentDesign_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: ComputeExperimentDesign
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
create_90_10_vial_feeding_task:
|
create_90_10_vial_feeding_task:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -619,89 +620,6 @@ bioyond_dispensing_station:
|
|||||||
title: DispenStationSolnPrep
|
title: DispenStationSolnPrep
|
||||||
type: object
|
type: object
|
||||||
type: DispenStationSolnPrep
|
type: DispenStationSolnPrep
|
||||||
scheduler_start:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
|
||||||
description: 启动调度器 - 启动Bioyond配液站的任务调度器,开始执行队列中的任务
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
return_info:
|
|
||||||
description: 调度器启动结果,成功返回1,失败返回0
|
|
||||||
type: integer
|
|
||||||
required:
|
|
||||||
- return_info
|
|
||||||
title: scheduler_start结果
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: scheduler_start参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
transfer_materials_to_reaction_station:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
target_device_id: target_device_id
|
|
||||||
transfer_groups: transfer_groups
|
|
||||||
goal_default:
|
|
||||||
target_device_id: ''
|
|
||||||
transfer_groups: ''
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys:
|
|
||||||
target_device_id: unilabos_devices
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: 将配液站完成的物料(溶液、样品等)转移到指定反应站的堆栈库位。支持配置多组转移任务,每组包含物料名称、目标堆栈和目标库位。
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
target_device_id:
|
|
||||||
description: 目标反应站设备ID(从设备列表中选择,所有转移组都使用同一个目标设备)
|
|
||||||
type: string
|
|
||||||
transfer_groups:
|
|
||||||
description: 转移任务组列表,每组包含物料名称、目标堆栈和目标库位,可以添加多组
|
|
||||||
items:
|
|
||||||
properties:
|
|
||||||
materials:
|
|
||||||
description: 物料名称(手动输入,系统将通过RPC查询验证)
|
|
||||||
type: string
|
|
||||||
target_sites:
|
|
||||||
description: 目标库位(手动输入,如"A01")
|
|
||||||
type: string
|
|
||||||
target_stack:
|
|
||||||
description: 目标堆栈名称(从列表选择)
|
|
||||||
enum:
|
|
||||||
- 堆栈1左
|
|
||||||
- 堆栈1右
|
|
||||||
- 站内试剂存放堆栈
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- materials
|
|
||||||
- target_stack
|
|
||||||
- target_sites
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
required:
|
|
||||||
- target_device_id
|
|
||||||
- transfer_groups
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: transfer_materials_to_reaction_station参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
wait_for_multiple_orders_and_get_reports:
|
wait_for_multiple_orders_and_get_reports:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -770,7 +688,7 @@ bioyond_dispensing_station:
|
|||||||
title: WaitForMultipleOrdersAndGetReports
|
title: WaitForMultipleOrdersAndGetReports
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.workstation.bioyond_studio.dispensing_station.dispensing_station:BioyondDispensingStation
|
module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation
|
||||||
status_types: {}
|
status_types: {}
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
@@ -781,16 +699,15 @@ bioyond_dispensing_station:
|
|||||||
config:
|
config:
|
||||||
properties:
|
properties:
|
||||||
config:
|
config:
|
||||||
type: object
|
type: string
|
||||||
deck:
|
deck:
|
||||||
type: string
|
type: string
|
||||||
protocol_type:
|
required:
|
||||||
type: string
|
- config
|
||||||
required: []
|
- deck
|
||||||
type: object
|
type: object
|
||||||
data:
|
data:
|
||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
model: {}
|
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ coincellassemblyworkstation_device:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
bottle_num:
|
bottle_num:
|
||||||
type: string
|
type: integer
|
||||||
required:
|
required:
|
||||||
- bottle_num
|
- bottle_num
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@@ -9278,13 +9278,7 @@ liquid_handler.prcxi:
|
|||||||
z: 0.0
|
z: 0.0
|
||||||
sample_id: ''
|
sample_id: ''
|
||||||
type: ''
|
type: ''
|
||||||
handles:
|
handles: {}
|
||||||
input:
|
|
||||||
- data_key: wells
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: input_wells
|
|
||||||
label: InputWells
|
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
wells: unilabos_resources
|
wells: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
|
|||||||
@@ -49,7 +49,32 @@ opcua_example:
|
|||||||
title: load_config参数
|
title: load_config参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-refresh_node_values:
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-print_cache_stats:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
goal_default: {}
|
goal_default: {}
|
||||||
@@ -67,7 +92,32 @@ opcua_example:
|
|||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
- goal
|
- goal
|
||||||
title: refresh_node_values参数
|
title: print_cache_stats参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-read_node:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
node_name: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
node_name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- node_name
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: read_node参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-set_node_value:
|
auto-set_node_value:
|
||||||
@@ -99,50 +149,9 @@ opcua_example:
|
|||||||
title: set_node_value参数
|
title: set_node_value参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-start_node_refresh:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: start_node_refresh参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-stop_node_refresh:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: stop_node_refresh参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
module: unilabos.device_comms.opcua_client.client:OpcUaClient
|
module: unilabos.device_comms.opcua_client.client:OpcUaClient
|
||||||
status_types:
|
status_types:
|
||||||
|
cache_stats: dict
|
||||||
node_value: String
|
node_value: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
@@ -152,15 +161,23 @@ opcua_example:
|
|||||||
init_param_schema:
|
init_param_schema:
|
||||||
config:
|
config:
|
||||||
properties:
|
properties:
|
||||||
|
cache_timeout:
|
||||||
|
default: 5.0
|
||||||
|
type: number
|
||||||
config_path:
|
config_path:
|
||||||
type: string
|
type: string
|
||||||
|
deck:
|
||||||
|
type: string
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
refresh_interval:
|
subscription_interval:
|
||||||
default: 1.0
|
default: 500
|
||||||
type: number
|
type: integer
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
|
use_subscription:
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
@@ -168,9 +185,12 @@ opcua_example:
|
|||||||
type: object
|
type: object
|
||||||
data:
|
data:
|
||||||
properties:
|
properties:
|
||||||
|
cache_stats:
|
||||||
|
type: object
|
||||||
node_value:
|
node_value:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- node_value
|
- node_value
|
||||||
|
- cache_stats
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -4,81 +4,6 @@ reaction_station.bioyond:
|
|||||||
- reaction_station_bioyond
|
- reaction_station_bioyond
|
||||||
class:
|
class:
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
add_time_constraint:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
duration: duration
|
|
||||||
end_point: end_point
|
|
||||||
end_step_key: end_step_key
|
|
||||||
start_point: start_point
|
|
||||||
start_step_key: start_step_key
|
|
||||||
goal_default:
|
|
||||||
duration: 0
|
|
||||||
end_point: 0
|
|
||||||
end_step_key: ''
|
|
||||||
start_point: 0
|
|
||||||
start_step_key: ''
|
|
||||||
handles: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: 添加时间约束 - 在两个工作流之间添加时间约束
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
duration:
|
|
||||||
description: 时间(秒)
|
|
||||||
type: integer
|
|
||||||
end_point:
|
|
||||||
default: Start
|
|
||||||
description: 终点计时点 (Start=开始前, End=结束后)
|
|
||||||
enum:
|
|
||||||
- Start
|
|
||||||
- End
|
|
||||||
type: string
|
|
||||||
end_step_key:
|
|
||||||
description: 终点步骤Key (可选, 默认为空则自动选择)
|
|
||||||
type: string
|
|
||||||
start_point:
|
|
||||||
default: Start
|
|
||||||
description: 起点计时点 (Start=开始前, End=结束后)
|
|
||||||
enum:
|
|
||||||
- Start
|
|
||||||
- End
|
|
||||||
type: string
|
|
||||||
start_step_key:
|
|
||||||
description: 起点步骤Key (例如 "feeding", "liquid", 可选, 默认为空则自动选择)
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- duration
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: add_time_constraint参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-clear_workflows:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: clear_workflows参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-create_order:
|
auto-create_order:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -206,35 +131,6 @@ reaction_station.bioyond:
|
|||||||
title: process_web_workflows参数
|
title: process_web_workflows参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-set_reactor_temperature:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
reactor_id: null
|
|
||||||
temperature: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
reactor_id:
|
|
||||||
type: integer
|
|
||||||
temperature:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- reactor_id
|
|
||||||
- temperature
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: set_reactor_temperature参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-skip_titration_steps:
|
auto-skip_titration_steps:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -260,27 +156,6 @@ reaction_station.bioyond:
|
|||||||
title: skip_titration_steps参数
|
title: skip_titration_steps参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-sync_workflow_sequence_from_bioyond:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: sync_workflow_sequence_from_bioyond参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-wait_for_multiple_orders_and_get_reports:
|
auto-wait_for_multiple_orders_and_get_reports:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -313,33 +188,6 @@ reaction_station.bioyond:
|
|||||||
title: wait_for_multiple_orders_and_get_reports参数
|
title: wait_for_multiple_orders_and_get_reports参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
auto-workflow_sequence:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
value: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
value:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
required:
|
|
||||||
- value
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: workflow_sequence参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-workflow_step_query:
|
auto-workflow_step_query:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -365,36 +213,6 @@ reaction_station.bioyond:
|
|||||||
title: workflow_step_query参数
|
title: workflow_step_query参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
clean_all_server_workflows:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
result:
|
|
||||||
code: code
|
|
||||||
message: message
|
|
||||||
schema:
|
|
||||||
description: 清空服务端所有非核心工作流 (保留核心流程)
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
code:
|
|
||||||
description: 操作结果代码(1表示成功)
|
|
||||||
type: integer
|
|
||||||
message:
|
|
||||||
description: 结果描述
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: clean_all_server_workflows参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
drip_back:
|
drip_back:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -429,19 +247,13 @@ reaction_station.bioyond:
|
|||||||
description: 观察时间(分钟)
|
description: 观察时间(分钟)
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
description: 是否滴定(NO=否, YES=是)
|
description: 是否滴定(1=否, 2=是)
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
description: 是否观察 (NO=否, YES=是)
|
description: 是否观察 (1=否, 2=是)
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
volume:
|
volume:
|
||||||
description: 分液公式(mL)
|
description: 分液公式(μL)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- volume
|
- volume
|
||||||
@@ -541,19 +353,13 @@ reaction_station.bioyond:
|
|||||||
description: 观察时间(分钟)
|
description: 观察时间(分钟)
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
description: 是否滴定(NO=否, YES=是)
|
description: 是否滴定(1=否, 2=是)
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
description: 是否观察 (NO=否, YES=是)
|
description: 是否观察 (1=否, 2=是)
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
volume:
|
volume:
|
||||||
description: 分液公式(mL)
|
description: 分液公式(μL)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- volume
|
- volume
|
||||||
@@ -597,7 +403,7 @@ reaction_station.bioyond:
|
|||||||
label: Solvents Data From Calculation Node
|
label: Solvents Data From Calculation Node
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 液体投料-溶剂。可以直接提供volume(mL),或通过solvents对象自动从additional_solvent(mL)计算volume。
|
description: 液体投料-溶剂。可以直接提供volume(μL),或通过solvents对象自动从additional_solvent(mL)计算volume。
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -617,21 +423,15 @@ reaction_station.bioyond:
|
|||||||
description: 观察时间(分钟),默认360
|
description: 观察时间(分钟),默认360
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
default: 'NO'
|
default: '1'
|
||||||
description: 是否滴定(NO=否, YES=是),默认NO
|
description: 是否滴定(1=否, 2=是),默认1
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
default: 'YES'
|
default: '2'
|
||||||
description: 是否观察 (NO=否, YES=是),默认YES
|
description: 是否观察 (1=否, 2=是),默认2
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
volume:
|
volume:
|
||||||
description: 分液量(mL)。可直接提供,或通过solvents参数自动计算
|
description: 分液量(μL)。可直接提供,或通过solvents参数自动计算
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- assign_material_name
|
- assign_material_name
|
||||||
@@ -704,21 +504,15 @@ reaction_station.bioyond:
|
|||||||
description: 观察时间(分钟),默认90
|
description: 观察时间(分钟),默认90
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
default: 'YES'
|
default: '2'
|
||||||
description: 是否滴定(NO=否, YES=是),默认YES
|
description: 是否滴定(1=否, 2=是),默认2
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
default: 'YES'
|
default: '2'
|
||||||
description: 是否观察 (NO=否, YES=是),默认YES
|
description: 是否观察 (1=否, 2=是),默认2
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
volume_formula:
|
volume_formula:
|
||||||
description: 分液公式(mL)。可直接提供固定公式,或留空由系统根据x_value、feeding_order_data、extracted_actuals自动生成
|
description: 分液公式(μL)。可直接提供固定公式,或留空由系统根据x_value、feeding_order_data、extracted_actuals自动生成
|
||||||
type: string
|
type: string
|
||||||
x_value:
|
x_value:
|
||||||
description: 公式中的x值,手工输入,格式为"{{1-2-3}}"(包含双花括号)。用于自动公式计算
|
description: 公式中的x值,手工输入,格式为"{{1-2-3}}"(包含双花括号)。用于自动公式计算
|
||||||
@@ -766,19 +560,13 @@ reaction_station.bioyond:
|
|||||||
description: 观察时间(分钟)
|
description: 观察时间(分钟)
|
||||||
type: string
|
type: string
|
||||||
titration_type:
|
titration_type:
|
||||||
description: 是否滴定(NO=否, YES=是)
|
description: 是否滴定(1=否, 2=是)
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
description: 是否观察 (NO=否, YES=是)
|
description: 是否观察 (1=否, 2=是)
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
volume_formula:
|
volume_formula:
|
||||||
description: 分液公式(mL)
|
description: 分液公式(μL)
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- volume_formula
|
- volume_formula
|
||||||
@@ -892,35 +680,6 @@ reaction_station.bioyond:
|
|||||||
title: reactor_taken_out参数
|
title: reactor_taken_out参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
scheduler_start:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
|
||||||
description: 启动调度器 - 启动Bioyond工作站的任务调度器,开始执行队列中的任务
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
return_info:
|
|
||||||
description: 调度器启动结果,成功返回1,失败返回0
|
|
||||||
type: integer
|
|
||||||
required:
|
|
||||||
- return_info
|
|
||||||
title: scheduler_start结果
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: scheduler_start参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
solid_feeding_vials:
|
solid_feeding_vials:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -947,11 +706,7 @@ reaction_station.bioyond:
|
|||||||
description: 物料名称(用于获取试剂瓶位ID)
|
description: 物料名称(用于获取试剂瓶位ID)
|
||||||
type: string
|
type: string
|
||||||
material_id:
|
material_id:
|
||||||
description: 粉末类型ID,Salt=盐(21分钟),Flour=面粉(27分钟),BTDA=BTDA(38分钟)
|
description: 粉末类型ID,1=盐(21分钟),2=面粉(27分钟),3=BTDA(38分钟)
|
||||||
enum:
|
|
||||||
- Salt
|
|
||||||
- Flour
|
|
||||||
- BTDA
|
|
||||||
type: string
|
type: string
|
||||||
temperature:
|
temperature:
|
||||||
description: 温度设定(°C)
|
description: 温度设定(°C)
|
||||||
@@ -960,10 +715,7 @@ reaction_station.bioyond:
|
|||||||
description: 观察时间(分钟)
|
description: 观察时间(分钟)
|
||||||
type: string
|
type: string
|
||||||
torque_variation:
|
torque_variation:
|
||||||
description: 是否观察 (NO=否, YES=是)
|
description: 是否观察 (1=否, 2=是)
|
||||||
enum:
|
|
||||||
- 'NO'
|
|
||||||
- 'YES'
|
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- assign_material_name
|
- assign_material_name
|
||||||
@@ -978,10 +730,10 @@ reaction_station.bioyond:
|
|||||||
title: solid_feeding_vials参数
|
title: solid_feeding_vials参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.workstation.bioyond_studio.reaction_station.reaction_station:BioyondReactionStation
|
module: unilabos.devices.workstation.bioyond_studio.reaction_station:BioyondReactionStation
|
||||||
protocol_type: []
|
protocol_type: []
|
||||||
status_types:
|
status_types:
|
||||||
workflow_sequence: str
|
workflow_sequence: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: Bioyond反应站
|
description: Bioyond反应站
|
||||||
@@ -1001,7 +753,9 @@ reaction_station.bioyond:
|
|||||||
data:
|
data:
|
||||||
properties:
|
properties:
|
||||||
workflow_sequence:
|
workflow_sequence:
|
||||||
type: string
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
required:
|
required:
|
||||||
- workflow_sequence
|
- workflow_sequence
|
||||||
type: object
|
type: object
|
||||||
@@ -1037,7 +791,7 @@ reaction_station.reactor:
|
|||||||
title: update_metrics参数
|
title: update_metrics参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.workstation.bioyond_studio.reaction_station.reaction_station:BioyondReactor
|
module: unilabos.devices.workstation.bioyond_studio.reaction_station:BioyondReactor
|
||||||
status_types: {}
|
status_types: {}
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
|
|||||||
@@ -124,25 +124,11 @@ class Registry:
|
|||||||
"output": [
|
"output": [
|
||||||
{
|
{
|
||||||
"handler_key": "labware",
|
"handler_key": "labware",
|
||||||
"data_type": "resource",
|
|
||||||
"label": "Labware",
|
"label": "Labware",
|
||||||
"data_source": "executor",
|
|
||||||
"data_key": "created_resource_tree.@flatten",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handler_key": "liquid_slots",
|
|
||||||
"data_type": "resource",
|
"data_type": "resource",
|
||||||
"label": "LiquidSlots",
|
"data_source": "handle",
|
||||||
"data_source": "executor",
|
"data_key": "liquid",
|
||||||
"data_key": "liquid_input_resource_tree.@flatten",
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"handler_key": "materials",
|
|
||||||
"data_type": "resource",
|
|
||||||
"label": "AllMaterials",
|
|
||||||
"data_source": "executor",
|
|
||||||
"data_key": "[created_resource_tree,liquid_input_resource_tree].@flatten.@flatten",
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"placeholder_keys": {
|
"placeholder_keys": {
|
||||||
@@ -200,17 +186,7 @@ class Registry:
|
|||||||
"resources": "unilabos_resources",
|
"resources": "unilabos_resources",
|
||||||
},
|
},
|
||||||
"goal_default": {},
|
"goal_default": {},
|
||||||
"handles": {
|
"handles": {},
|
||||||
"input": [
|
|
||||||
{
|
|
||||||
"handler_key": "input_resources",
|
|
||||||
"data_type": "resource",
|
|
||||||
"label": "InputResources",
|
|
||||||
"data_source": "handle",
|
|
||||||
"data_key": "resources", # 不为空
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -479,11 +455,7 @@ class Registry:
|
|||||||
return status_schema
|
return status_schema
|
||||||
|
|
||||||
def _generate_unilab_json_command_schema(
|
def _generate_unilab_json_command_schema(
|
||||||
self,
|
self, method_args: List[Dict[str, Any]], method_name: str, return_annotation: Any = None
|
||||||
method_args: List[Dict[str, Any]],
|
|
||||||
method_name: str,
|
|
||||||
return_annotation: Any = None,
|
|
||||||
previous_schema: Dict[str, Any] | None = None,
|
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
根据UniLabJsonCommand方法信息生成JSON Schema,暂不支持嵌套类型
|
根据UniLabJsonCommand方法信息生成JSON Schema,暂不支持嵌套类型
|
||||||
@@ -492,7 +464,6 @@ class Registry:
|
|||||||
method_args: 方法信息字典,包含args等
|
method_args: 方法信息字典,包含args等
|
||||||
method_name: 方法名称
|
method_name: 方法名称
|
||||||
return_annotation: 返回类型注解,用于生成result schema(仅支持TypedDict)
|
return_annotation: 返回类型注解,用于生成result schema(仅支持TypedDict)
|
||||||
previous_schema: 之前的 schema,用于保留 goal/feedback/result 下一级字段的 description
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON Schema格式的参数schema
|
JSON Schema格式的参数schema
|
||||||
@@ -526,7 +497,7 @@ class Registry:
|
|||||||
if return_annotation is not None and self._is_typed_dict(return_annotation):
|
if return_annotation is not None and self._is_typed_dict(return_annotation):
|
||||||
result_schema = self._generate_typed_dict_result_schema(return_annotation)
|
result_schema = self._generate_typed_dict_result_schema(return_annotation)
|
||||||
|
|
||||||
final_schema = {
|
return {
|
||||||
"title": f"{method_name}参数",
|
"title": f"{method_name}参数",
|
||||||
"description": f"",
|
"description": f"",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -534,39 +505,6 @@ class Registry:
|
|||||||
"required": ["goal"],
|
"required": ["goal"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# 保留之前 schema 中 goal/feedback/result 下一级字段的 description
|
|
||||||
if previous_schema:
|
|
||||||
self._preserve_field_descriptions(final_schema, previous_schema)
|
|
||||||
|
|
||||||
return final_schema
|
|
||||||
|
|
||||||
def _preserve_field_descriptions(
|
|
||||||
self, new_schema: Dict[str, Any], previous_schema: Dict[str, Any]
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
保留之前 schema 中 goal/feedback/result 下一级字段的 description
|
|
||||||
|
|
||||||
Args:
|
|
||||||
new_schema: 新生成的 schema(会被修改)
|
|
||||||
previous_schema: 之前的 schema
|
|
||||||
"""
|
|
||||||
for section in ["goal", "feedback", "result"]:
|
|
||||||
new_section = new_schema.get("properties", {}).get(section, {})
|
|
||||||
prev_section = previous_schema.get("properties", {}).get(section, {})
|
|
||||||
|
|
||||||
if not new_section or not prev_section:
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_props = new_section.get("properties", {})
|
|
||||||
prev_props = prev_section.get("properties", {})
|
|
||||||
|
|
||||||
for field_name, field_schema in new_props.items():
|
|
||||||
if field_name in prev_props:
|
|
||||||
prev_field = prev_props[field_name]
|
|
||||||
# 保留字段的 description
|
|
||||||
if "description" in prev_field and prev_field["description"]:
|
|
||||||
field_schema["description"] = prev_field["description"]
|
|
||||||
|
|
||||||
def _is_typed_dict(self, annotation: Any) -> bool:
|
def _is_typed_dict(self, annotation: Any) -> bool:
|
||||||
"""
|
"""
|
||||||
检查类型注解是否是TypedDict
|
检查类型注解是否是TypedDict
|
||||||
@@ -735,10 +673,13 @@ class Registry:
|
|||||||
sorted(device_config["class"]["status_types"].items())
|
sorted(device_config["class"]["status_types"].items())
|
||||||
)
|
)
|
||||||
if complete_registry:
|
if complete_registry:
|
||||||
# 保存原有的 action 配置(用于保留 schema 的 description 和 handles 等)
|
# 保存原有的description信息
|
||||||
old_action_configs = {}
|
old_descriptions = {}
|
||||||
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
|
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
|
||||||
old_action_configs[action_name] = action_config
|
if "description" in action_config.get("schema", {}):
|
||||||
|
description = action_config["schema"]["description"]
|
||||||
|
if len(description):
|
||||||
|
old_descriptions[action_name] = action_config["schema"]["description"]
|
||||||
|
|
||||||
device_config["class"]["action_value_mappings"] = {
|
device_config["class"]["action_value_mappings"] = {
|
||||||
k: v
|
k: v
|
||||||
@@ -754,15 +695,10 @@ class Registry:
|
|||||||
"feedback": {},
|
"feedback": {},
|
||||||
"result": {},
|
"result": {},
|
||||||
"schema": self._generate_unilab_json_command_schema(
|
"schema": self._generate_unilab_json_command_schema(
|
||||||
v["args"],
|
v["args"], k, v.get("return_annotation")
|
||||||
k,
|
|
||||||
v.get("return_annotation"),
|
|
||||||
# 传入旧的 schema 以保留字段 description
|
|
||||||
old_action_configs.get(f"auto-{k}", {}).get("schema"),
|
|
||||||
),
|
),
|
||||||
"goal_default": {i["name"]: i["default"] for i in v["args"]},
|
"goal_default": {i["name"]: i["default"] for i in v["args"]},
|
||||||
# 保留原有的 handles 配置
|
"handles": [],
|
||||||
"handles": old_action_configs.get(f"auto-{k}", {}).get("handles", []),
|
|
||||||
"placeholder_keys": {
|
"placeholder_keys": {
|
||||||
i["name"]: (
|
i["name"]: (
|
||||||
"unilabos_resources"
|
"unilabos_resources"
|
||||||
@@ -786,14 +722,12 @@ class Registry:
|
|||||||
if k not in device_config["class"]["action_value_mappings"]
|
if k not in device_config["class"]["action_value_mappings"]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# 恢复原有的 description 信息(非 auto- 开头的动作)
|
# 恢复原有的description信息(auto开头的不修改)
|
||||||
for action_name, old_config in old_action_configs.items():
|
for action_name, description in old_descriptions.items():
|
||||||
if action_name in device_config["class"]["action_value_mappings"]: # 有一些会被删除
|
if action_name in device_config["class"]["action_value_mappings"]: # 有一些会被删除
|
||||||
old_schema = old_config.get("schema", {})
|
device_config["class"]["action_value_mappings"][action_name]["schema"][
|
||||||
if "description" in old_schema and old_schema["description"]:
|
"description"
|
||||||
device_config["class"]["action_value_mappings"][action_name]["schema"][
|
] = description
|
||||||
"description"
|
|
||||||
] = old_schema["description"]
|
|
||||||
device_config["init_param_schema"] = {}
|
device_config["init_param_schema"] = {}
|
||||||
device_config["init_param_schema"]["config"] = self._generate_unilab_json_command_schema(
|
device_config["init_param_schema"]["config"] = self._generate_unilab_json_command_schema(
|
||||||
enhanced_info["init_params"], "__init__"
|
enhanced_info["init_params"], "__init__"
|
||||||
|
|||||||
@@ -20,17 +20,6 @@ BIOYOND_PolymerStation_Liquid_Vial:
|
|||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
BIOYOND_PolymerStation_Measurement_Vial:
|
|
||||||
category:
|
|
||||||
- bottles
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Measurement_Vial
|
|
||||||
type: pylabrobot
|
|
||||||
description: 聚合站-测量小瓶(测密度)
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
version: 1.0.0
|
|
||||||
BIOYOND_PolymerStation_Reactor:
|
BIOYOND_PolymerStation_Reactor:
|
||||||
category:
|
category:
|
||||||
- bottles
|
- bottles
|
||||||
|
|||||||
@@ -84,12 +84,12 @@ def bioyond_warehouse_reagent_storage(name: str) -> WareHouse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_tipbox_storage(name: str) -> WareHouse:
|
def bioyond_warehouse_tipbox_storage(name: str) -> WareHouse:
|
||||||
"""创建BioYond站内Tip盒堆栈(A01~B03, 2行×3列)"""
|
"""创建BioYond站内Tip盒堆栈(A01~B03),用于存放枪头盒"""
|
||||||
return warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=3, # 3列(01-03)
|
num_items_x=3, # 3列(01-03)
|
||||||
num_items_y=2, # 2行(A-B)
|
num_items_y=2, # 2行(A-B)
|
||||||
num_items_z=1, # 1层
|
num_items_z=1, # 1层
|
||||||
dx=10.0,
|
dx=10.0,
|
||||||
dy=10.0,
|
dy=10.0,
|
||||||
dz=10.0,
|
dz=10.0,
|
||||||
|
|||||||
@@ -193,20 +193,3 @@ def BIOYOND_PolymerStation_Flask(
|
|||||||
barcode=barcode,
|
barcode=barcode,
|
||||||
model="BIOYOND_PolymerStation_Flask",
|
model="BIOYOND_PolymerStation_Flask",
|
||||||
)
|
)
|
||||||
|
|
||||||
def BIOYOND_PolymerStation_Measurement_Vial(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 25.0,
|
|
||||||
height: float = 60.0,
|
|
||||||
max_volume: float = 20000.0, # 20mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建测量小瓶"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="BIOYOND_PolymerStation_Measurement_Vial",
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -18,12 +18,9 @@ from unilabos.resources.bioyond.YB_warehouses import (
|
|||||||
bioyond_warehouse_1x8x4,
|
bioyond_warehouse_1x8x4,
|
||||||
bioyond_warehouse_reagent_storage,
|
bioyond_warehouse_reagent_storage,
|
||||||
# bioyond_warehouse_liquid_preparation,
|
# bioyond_warehouse_liquid_preparation,
|
||||||
|
bioyond_warehouse_tipbox_storage, # 新增:Tip盒堆栈
|
||||||
bioyond_warehouse_density_vial,
|
bioyond_warehouse_density_vial,
|
||||||
)
|
)
|
||||||
from unilabos.resources.bioyond.warehouses import (
|
|
||||||
bioyond_warehouse_tipbox_storage_left, # 新增:Tip盒堆栈(左)
|
|
||||||
bioyond_warehouse_tipbox_storage_right, # 新增:Tip盒堆栈(右)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BIOYOND_PolymerReactionStation_Deck(Deck):
|
class BIOYOND_PolymerReactionStation_Deck(Deck):
|
||||||
@@ -50,22 +47,24 @@ class BIOYOND_PolymerReactionStation_Deck(Deck):
|
|||||||
"堆栈1右": bioyond_warehouse_1x4x4_right("堆栈1右"), # 右侧堆栈: A05~D08
|
"堆栈1右": bioyond_warehouse_1x4x4_right("堆栈1右"), # 右侧堆栈: A05~D08
|
||||||
"站内试剂存放堆栈": bioyond_warehouse_reagent_storage("站内试剂存放堆栈"), # A01~A02
|
"站内试剂存放堆栈": bioyond_warehouse_reagent_storage("站内试剂存放堆栈"), # A01~A02
|
||||||
# "移液站内10%分装液体准备仓库": bioyond_warehouse_liquid_preparation("移液站内10%分装液体准备仓库"), # A01~B04
|
# "移液站内10%分装液体准备仓库": bioyond_warehouse_liquid_preparation("移液站内10%分装液体准备仓库"), # A01~B04
|
||||||
"站内Tip盒堆栈(左)": bioyond_warehouse_tipbox_storage_left("站内Tip盒堆栈(左)"), # A02~B03
|
"站内Tip盒堆栈": bioyond_warehouse_tipbox_storage("站内Tip盒堆栈"), # A01~B03, 存放枪头盒.
|
||||||
"站内Tip盒堆栈(右)": bioyond_warehouse_tipbox_storage_right("站内Tip盒堆栈(右)"), # A01~B01
|
|
||||||
"测量小瓶仓库(测密度)": bioyond_warehouse_density_vial("测量小瓶仓库(测密度)"), # A01~B03
|
"测量小瓶仓库(测密度)": bioyond_warehouse_density_vial("测量小瓶仓库(测密度)"), # A01~B03
|
||||||
}
|
}
|
||||||
self.warehouse_locations = {
|
self.warehouse_locations = {
|
||||||
"堆栈1左": Coordinate(-200.0, 400.0, 0.0), # 左侧位置
|
"堆栈1左": Coordinate(0.0, 430.0, 0.0), # 左侧位置
|
||||||
"堆栈1右": Coordinate(2350.0, 400.0, 0.0), # 右侧位置
|
"堆栈1右": Coordinate(2500.0, 430.0, 0.0), # 右侧位置
|
||||||
"站内试剂存放堆栈": Coordinate(640.0, 400.0, 0.0),
|
"站内试剂存放堆栈": Coordinate(640.0, 480.0, 0.0),
|
||||||
"站内Tip盒堆栈(左)": Coordinate(300.0, 100.0, 0.0),
|
# "移液站内10%分装液体准备仓库": Coordinate(1200.0, 600.0, 0.0),
|
||||||
"站内Tip盒堆栈(右)": Coordinate(2250.0, 100.0, 0.0), # 向右偏移 2 * item_dx (137.0)
|
"站内Tip盒堆栈": Coordinate(300.0, 150.0, 0.0),
|
||||||
"测量小瓶仓库(测密度)": Coordinate(1000.0, 530.0, 0.0),
|
"测量小瓶仓库(测密度)": Coordinate(922.0, 552.0, 0.0),
|
||||||
}
|
}
|
||||||
|
self.warehouses["站内试剂存放堆栈"].rotation = Rotation(z=90)
|
||||||
|
self.warehouses["测量小瓶仓库(测密度)"].rotation = Rotation(z=270)
|
||||||
|
|
||||||
for warehouse_name, warehouse in self.warehouses.items():
|
for warehouse_name, warehouse in self.warehouses.items():
|
||||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||||
|
|
||||||
|
|
||||||
class BIOYOND_PolymerPreparationStation_Deck(Deck):
|
class BIOYOND_PolymerPreparationStation_Deck(Deck):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -93,9 +92,9 @@ class BIOYOND_PolymerPreparationStation_Deck(Deck):
|
|||||||
"溶液堆栈": bioyond_warehouse_1x4x4("溶液堆栈"), # 4行×4列 (A01-D04)
|
"溶液堆栈": bioyond_warehouse_1x4x4("溶液堆栈"), # 4行×4列 (A01-D04)
|
||||||
}
|
}
|
||||||
self.warehouse_locations = {
|
self.warehouse_locations = {
|
||||||
"粉末堆栈": Coordinate(-200.0, 400.0, 0.0),
|
"粉末堆栈": Coordinate(0.0, 450.0, 0.0),
|
||||||
"试剂堆栈": Coordinate(1750.0, 160.0, 0.0),
|
"试剂堆栈": Coordinate(1850.0, 200.0, 0.0),
|
||||||
"溶液堆栈": Coordinate(2350.0, 400.0, 0.0),
|
"溶液堆栈": Coordinate(2500.0, 450.0, 0.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
for warehouse_name, warehouse in self.warehouses.items():
|
for warehouse_name, warehouse in self.warehouses.items():
|
||||||
@@ -149,7 +148,6 @@ class BIOYOND_YB_Deck(Deck):
|
|||||||
|
|
||||||
for warehouse_name, warehouse in self.warehouses.items():
|
for warehouse_name, warehouse in self.warehouses.items():
|
||||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||||
|
|
||||||
def YB_Deck(name: str) -> Deck:
|
def YB_Deck(name: str) -> Deck:
|
||||||
by=BIOYOND_YB_Deck(name=name)
|
by=BIOYOND_YB_Deck(name=name)
|
||||||
by.setup()
|
by.setup()
|
||||||
|
|||||||
@@ -46,80 +46,48 @@ def bioyond_warehouse_1x4x4_right(name: str) -> WareHouse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_density_vial(name: str) -> WareHouse:
|
def bioyond_warehouse_density_vial(name: str) -> WareHouse:
|
||||||
"""创建测量小瓶仓库(测密度) - 竖向排列2列3行
|
"""创建测量小瓶仓库(测密度) A01~B03"""
|
||||||
布局(从下到上,从左到右):
|
|
||||||
| A03 | B03 | ← 顶部
|
|
||||||
| A02 | B02 | ← 中部
|
|
||||||
| A01 | B01 | ← 底部
|
|
||||||
"""
|
|
||||||
return warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=2, # 2列(A, B)
|
num_items_x=3, # 3列(01-03)
|
||||||
num_items_y=3, # 3行(01-03,从下到上)
|
num_items_y=2, # 2行(A-B)
|
||||||
num_items_z=1, # 1层
|
num_items_z=1, # 1层
|
||||||
dx=10.0,
|
dx=10.0,
|
||||||
dy=10.0,
|
dy=10.0,
|
||||||
dz=10.0,
|
dz=10.0,
|
||||||
item_dx=40.0, # 列间距(A到B的横向距离)
|
item_dx=40.0,
|
||||||
item_dy=40.0, # 行间距(01到02到03的竖向距离)
|
item_dy=40.0,
|
||||||
item_dz=50.0,
|
item_dz=50.0,
|
||||||
# ⭐ 竖向warehouse:槽位尺寸也是竖向的(小瓶已经是正方形,无需调整)
|
# 用更小的 resource_size 来表现 "小点的孔位"
|
||||||
resource_size_x=30.0,
|
resource_size_x=30.0,
|
||||||
resource_size_y=30.0,
|
resource_size_y=30.0,
|
||||||
resource_size_z=12.0,
|
resource_size_z=12.0,
|
||||||
category="warehouse",
|
category="warehouse",
|
||||||
col_offset=0,
|
col_offset=0,
|
||||||
layout="vertical-col-major", # ⭐ 竖向warehouse专用布局
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_reagent_storage(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond站内试剂存放堆栈 - 竖向排列1列2行
|
|
||||||
布局(竖向,从下到上):
|
|
||||||
| A02 | ← 顶部
|
|
||||||
| A01 | ← 底部
|
|
||||||
"""
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=1, # 1列
|
|
||||||
num_items_y=2, # 2行(01-02,从下到上)
|
|
||||||
num_items_z=1, # 1层
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=96.0, # 列间距(这里只有1列,不重要)
|
|
||||||
item_dy=137.0, # 行间距(A01到A02的竖向距离)
|
|
||||||
item_dz=120.0,
|
|
||||||
# ⭐ 竖向warehouse:交换槽位尺寸,使槽位框也是竖向的
|
|
||||||
resource_size_x=86.0, # 原来的 resource_size_y
|
|
||||||
resource_size_y=127.0, # 原来的 resource_size_x
|
|
||||||
resource_size_z=25.0,
|
|
||||||
category="warehouse",
|
|
||||||
layout="vertical-col-major", # ⭐ 竖向warehouse专用布局
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_tipbox_storage_left(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond站内Tip盒堆栈左侧部分(A02~B03),2列2行"""
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=2, # 2列
|
|
||||||
num_items_y=2, # 2行(A-B)
|
|
||||||
num_items_z=1, # 1层
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
col_offset=1, # 从02开始: A02, A03
|
|
||||||
layout="row-major",
|
layout="row-major",
|
||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_tipbox_storage_right(name: str) -> WareHouse:
|
def bioyond_warehouse_reagent_storage(name: str) -> WareHouse:
|
||||||
"""创建BioYond站内Tip盒堆栈右侧部分(A01~B01),1列2行"""
|
"""创建BioYond站内试剂存放堆栈(A01~A02, 1行×2列)"""
|
||||||
return warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=1, # 1列
|
num_items_x=2, # 2列(01-02)
|
||||||
|
num_items_y=1, # 1行(A)
|
||||||
|
num_items_z=1, # 1层
|
||||||
|
dx=10.0,
|
||||||
|
dy=10.0,
|
||||||
|
dz=10.0,
|
||||||
|
item_dx=137.0,
|
||||||
|
item_dy=96.0,
|
||||||
|
item_dz=120.0,
|
||||||
|
category="warehouse",
|
||||||
|
)
|
||||||
|
|
||||||
|
def bioyond_warehouse_tipbox_storage(name: str) -> WareHouse:
|
||||||
|
"""创建BioYond站内Tip盒堆栈(A01~B03),用于存放枪头盒"""
|
||||||
|
return warehouse_factory(
|
||||||
|
name=name,
|
||||||
|
num_items_x=3, # 3列(01-03)
|
||||||
num_items_y=2, # 2行(A-B)
|
num_items_y=2, # 2行(A-B)
|
||||||
num_items_z=1, # 1层
|
num_items_z=1, # 1层
|
||||||
dx=10.0,
|
dx=10.0,
|
||||||
@@ -129,7 +97,7 @@ def bioyond_warehouse_tipbox_storage_right(name: str) -> WareHouse:
|
|||||||
item_dy=96.0,
|
item_dy=96.0,
|
||||||
item_dz=120.0,
|
item_dz=120.0,
|
||||||
category="warehouse",
|
category="warehouse",
|
||||||
col_offset=0, # 从01开始: A01
|
col_offset=0,
|
||||||
layout="row-major",
|
layout="row-major",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class RegularContainer(Container):
|
|||||||
def get_regular_container(name="container"):
|
def get_regular_container(name="container"):
|
||||||
r = RegularContainer(name=name)
|
r = RegularContainer(name=name)
|
||||||
r.category = "container"
|
r.category = "container"
|
||||||
return r
|
return RegularContainer(name=name)
|
||||||
|
|
||||||
#
|
#
|
||||||
# class RegularContainer(object):
|
# class RegularContainer(object):
|
||||||
|
|||||||
@@ -779,22 +779,6 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
|||||||
if not locations:
|
if not locations:
|
||||||
logger.debug(f"[物料位置] {unique_name} 没有location信息,跳过warehouse放置")
|
logger.debug(f"[物料位置] {unique_name} 没有location信息,跳过warehouse放置")
|
||||||
|
|
||||||
# ⭐ 预先检查:如果物料的任何location在竖向warehouse中,提前交换尺寸
|
|
||||||
# 这样可以避免多个location时尺寸不一致的问题
|
|
||||||
needs_size_swap = False
|
|
||||||
for loc in locations:
|
|
||||||
wh_name_check = loc.get("whName")
|
|
||||||
if wh_name_check in ["站内试剂存放堆栈", "测量小瓶仓库(测密度)"]:
|
|
||||||
needs_size_swap = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if needs_size_swap and hasattr(plr_material, 'size_x') and hasattr(plr_material, 'size_y'):
|
|
||||||
original_x = plr_material.size_x
|
|
||||||
original_y = plr_material.size_y
|
|
||||||
plr_material.size_x = original_y
|
|
||||||
plr_material.size_y = original_x
|
|
||||||
logger.debug(f" 物料 {unique_name} 将放入竖向warehouse,预先交换尺寸: {original_x}×{original_y} → {plr_material.size_x}×{plr_material.size_y}")
|
|
||||||
|
|
||||||
for loc in locations:
|
for loc in locations:
|
||||||
wh_name = loc.get("whName")
|
wh_name = loc.get("whName")
|
||||||
logger.debug(f"[物料位置] {unique_name} 尝试放置到 warehouse: {wh_name} (Bioyond坐标: x={loc.get('x')}, y={loc.get('y')}, z={loc.get('z')})")
|
logger.debug(f"[物料位置] {unique_name} 尝试放置到 warehouse: {wh_name} (Bioyond坐标: x={loc.get('x')}, y={loc.get('y')}, z={loc.get('z')})")
|
||||||
@@ -811,20 +795,12 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
|||||||
logger.warning(f"物料 {material['name']} 的列号 x={x_val} 超出范围,无法映射到堆栈1左或堆栈1右")
|
logger.warning(f"物料 {material['name']} 的列号 x={x_val} 超出范围,无法映射到堆栈1左或堆栈1右")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 特殊处理: Bioyond的"站内Tip盒堆栈"也需要进行拆分映射
|
|
||||||
if wh_name == "站内Tip盒堆栈":
|
|
||||||
y_val = loc.get("y", 1)
|
|
||||||
if y_val == 1:
|
|
||||||
wh_name = "站内Tip盒堆栈(右)"
|
|
||||||
elif y_val in [2, 3]:
|
|
||||||
wh_name = "站内Tip盒堆栈(左)"
|
|
||||||
y = y - 1 # 调整列号,因为左侧仓库对应的 Bioyond y=2 实际上是它的第1列
|
|
||||||
|
|
||||||
if hasattr(deck, "warehouses") and wh_name in deck.warehouses:
|
if hasattr(deck, "warehouses") and wh_name in deck.warehouses:
|
||||||
warehouse = deck.warehouses[wh_name]
|
warehouse = deck.warehouses[wh_name]
|
||||||
logger.debug(f"[Warehouse匹配] 找到warehouse: {wh_name} (容量: {warehouse.capacity}, 行×列: {warehouse.num_items_x}×{warehouse.num_items_y})")
|
logger.debug(f"[Warehouse匹配] 找到warehouse: {wh_name} (容量: {warehouse.capacity}, 行×列: {warehouse.num_items_x}×{warehouse.num_items_y})")
|
||||||
|
|
||||||
# Bioyond坐标映射 (重要!): x→行(1=A,2=B...), y→列(1=01,2=02...), z→层(通常=1)
|
# Bioyond坐标映射 (重要!): x→行(1=A,2=B...), y→列(1=01,2=02...), z→层(通常=1)
|
||||||
|
# PyLabRobot warehouse是列优先存储: A01,B01,C01,D01, A02,B02,C02,D02, ...
|
||||||
x = loc.get("x", 1) # 行号 (1-based: 1=A, 2=B, 3=C, 4=D)
|
x = loc.get("x", 1) # 行号 (1-based: 1=A, 2=B, 3=C, 4=D)
|
||||||
y = loc.get("y", 1) # 列号 (1-based: 1=01, 2=02, 3=03...)
|
y = loc.get("y", 1) # 列号 (1-based: 1=01, 2=02, 3=03...)
|
||||||
z = loc.get("z", 1) # 层号 (1-based, 通常为1)
|
z = loc.get("z", 1) # 层号 (1-based, 通常为1)
|
||||||
@@ -833,23 +809,12 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
|||||||
if wh_name == "堆栈1右":
|
if wh_name == "堆栈1右":
|
||||||
y = y - 4 # 将5-8映射到1-4
|
y = y - 4 # 将5-8映射到1-4
|
||||||
|
|
||||||
# 特殊处理竖向warehouse(站内试剂存放堆栈、测量小瓶仓库)
|
# 特殊处理:对于1行×N列的横向warehouse(如站内试剂存放堆栈)
|
||||||
# 这些warehouse使用 vertical-col-major 布局
|
# Bioyond的y坐标表示线性位置序号,而不是列号
|
||||||
if wh_name in ["站内试剂存放堆栈", "测量小瓶仓库(测密度)"]:
|
if warehouse.num_items_y == 1:
|
||||||
# vertical-col-major 布局的坐标映射:
|
# 1行warehouse: 直接用y作为线性索引
|
||||||
# - Bioyond的x(1=A,2=B)对应warehouse的列(col, x方向)
|
idx = y - 1
|
||||||
# - Bioyond的y(1=01,2=02,3=03)对应warehouse的行(row, y方向),从下到上
|
logger.debug(f"1行warehouse {wh_name}: y={y} → idx={idx}")
|
||||||
# vertical-col-major 中: row=0 对应底部,row=n-1 对应顶部
|
|
||||||
# Bioyond y=1(01) 对应底部 → row=0, y=2(02) 对应中间 → row=1
|
|
||||||
# 索引计算: idx = row * num_cols + col
|
|
||||||
col_idx = x - 1 # Bioyond的x(A,B) → col索引(0,1)
|
|
||||||
row_idx = y - 1 # Bioyond的y(01,02,03) → row索引(0,1,2)
|
|
||||||
layer_idx = z - 1
|
|
||||||
|
|
||||||
idx = layer_idx * (warehouse.num_items_x * warehouse.num_items_y) + row_idx * warehouse.num_items_y + col_idx
|
|
||||||
logger.debug(f"🔍 竖向warehouse {wh_name}: Bioyond(x={x},y={y},z={z}) → warehouse(col={col_idx},row={row_idx},layer={layer_idx}) → idx={idx}, capacity={warehouse.capacity}")
|
|
||||||
|
|
||||||
# 普通横向warehouse的处理
|
|
||||||
else:
|
else:
|
||||||
# 多行warehouse: 根据 layout 使用不同的索引计算
|
# 多行warehouse: 根据 layout 使用不同的索引计算
|
||||||
row_idx = x - 1 # x表示行: 转为0-based
|
row_idx = x - 1 # x表示行: 转为0-based
|
||||||
@@ -873,7 +838,6 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
|||||||
|
|
||||||
if 0 <= idx < warehouse.capacity:
|
if 0 <= idx < warehouse.capacity:
|
||||||
if warehouse[idx] is None or isinstance(warehouse[idx], ResourceHolder):
|
if warehouse[idx] is None or isinstance(warehouse[idx], ResourceHolder):
|
||||||
# 物料尺寸已在放入warehouse前根据需要进行了交换
|
|
||||||
warehouse[idx] = plr_material
|
warehouse[idx] = plr_material
|
||||||
logger.debug(f"✅ 物料 {unique_name} 放置到 {wh_name}[{idx}] (Bioyond坐标: x={loc.get('x')}, y={loc.get('y')})")
|
logger.debug(f"✅ 物料 {unique_name} 放置到 {wh_name}[{idx}] (Bioyond坐标: x={loc.get('x')}, y={loc.get('y')})")
|
||||||
else:
|
else:
|
||||||
@@ -1047,24 +1011,11 @@ def resource_plr_to_bioyond(plr_resources: list[ResourcePLR], type_mapping: dict
|
|||||||
logger.debug(f" 📭 [单瓶物料] {resource.name} 无液体,使用资源名: {material_name}")
|
logger.debug(f" 📭 [单瓶物料] {resource.name} 无液体,使用资源名: {material_name}")
|
||||||
|
|
||||||
# 🎯 处理物料默认参数和单位
|
# 🎯 处理物料默认参数和单位
|
||||||
# 优先级: typeId参数 > 物料名称参数 > 默认值
|
# 检查是否有该物料名称的默认参数配置
|
||||||
default_unit = "个" # 默认单位
|
default_unit = "个" # 默认单位
|
||||||
material_parameters = {}
|
material_parameters = {}
|
||||||
|
|
||||||
# 1️⃣ 首先检查是否有 typeId 对应的参数配置(从 material_params 中获取,key 格式为 "type:<typeId>")
|
if material_name in material_params:
|
||||||
type_params_key = f"type:{type_id}"
|
|
||||||
if type_params_key in material_params:
|
|
||||||
params_config = material_params[type_params_key].copy()
|
|
||||||
|
|
||||||
# 提取 unit 字段(如果有)
|
|
||||||
if "unit" in params_config:
|
|
||||||
default_unit = params_config.pop("unit") # 从参数中移除,放到外层
|
|
||||||
|
|
||||||
# 剩余的字段放入 Parameters
|
|
||||||
material_parameters = params_config
|
|
||||||
logger.debug(f" 🔧 [物料参数-按typeId] 为 typeId={type_id[:8]}... 应用配置: unit={default_unit}, parameters={material_parameters}")
|
|
||||||
# 2️⃣ 其次检查是否有该物料名称的默认参数配置
|
|
||||||
elif material_name in material_params:
|
|
||||||
params_config = material_params[material_name].copy()
|
params_config = material_params[material_name].copy()
|
||||||
|
|
||||||
# 提取 unit 字段(如果有)
|
# 提取 unit 字段(如果有)
|
||||||
@@ -1073,7 +1024,7 @@ def resource_plr_to_bioyond(plr_resources: list[ResourcePLR], type_mapping: dict
|
|||||||
|
|
||||||
# 剩余的字段放入 Parameters
|
# 剩余的字段放入 Parameters
|
||||||
material_parameters = params_config
|
material_parameters = params_config
|
||||||
logger.debug(f" 🔧 [物料参数-按名称] 为 {material_name} 应用配置: unit={default_unit}, parameters={material_parameters}")
|
logger.debug(f" 🔧 [物料参数] 为 {material_name} 应用配置: unit={default_unit}, parameters={material_parameters}")
|
||||||
|
|
||||||
# 转换为 JSON 字符串
|
# 转换为 JSON 字符串
|
||||||
parameters_json = json.dumps(material_parameters) if material_parameters else "{}"
|
parameters_json = json.dumps(material_parameters) if material_parameters else "{}"
|
||||||
@@ -1200,7 +1151,11 @@ def initialize_resource(resource_config: dict, resource_type: Any = None) -> Uni
|
|||||||
if resource_class_config["type"] == "pylabrobot":
|
if resource_class_config["type"] == "pylabrobot":
|
||||||
resource_plr = RESOURCE(name=resource_config["name"])
|
resource_plr = RESOURCE(name=resource_config["name"])
|
||||||
if resource_type != ResourcePLR:
|
if resource_type != ResourcePLR:
|
||||||
tree_sets = ResourceTreeSet.from_plr_resources([resource_plr], known_newly_created=True)
|
tree_sets = ResourceTreeSet.from_plr_resources([resource_plr])
|
||||||
|
# r = resource_plr_to_ulab(resource_plr=resource_plr, parent_name=resource_config.get("parent", None))
|
||||||
|
# # r = resource_plr_to_ulab(resource_plr=resource_plr)
|
||||||
|
# if resource_config.get("position") is not None:
|
||||||
|
# r["position"] = resource_config["position"]
|
||||||
r = tree_sets.dump()
|
r = tree_sets.dump()
|
||||||
else:
|
else:
|
||||||
r = resource_plr
|
r = resource_plr
|
||||||
|
|||||||
@@ -50,45 +50,12 @@ class Bottle(Well):
|
|||||||
self.barcode = barcode
|
self.barcode = barcode
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
def serialize(self) -> dict:
|
||||||
# Pylabrobot expects barcode to be an object with serialize(), but here it is a str.
|
|
||||||
# We temporarily unset it to avoid AttributeError in super().serialize().
|
|
||||||
_barcode = self.barcode
|
|
||||||
self.barcode = None
|
|
||||||
try:
|
|
||||||
data = super().serialize()
|
|
||||||
finally:
|
|
||||||
self.barcode = _barcode
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
**data,
|
**super().serialize(),
|
||||||
"diameter": self.diameter,
|
"diameter": self.diameter,
|
||||||
"height": self.height,
|
"height": self.height,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def deserialize(cls, data: dict, allow_marshal: bool = False):
|
|
||||||
# Extract barcode before calling parent deserialize to avoid type error
|
|
||||||
barcode_data = data.pop("barcode", None)
|
|
||||||
|
|
||||||
# Call parent deserialize
|
|
||||||
instance = super(Bottle, cls).deserialize(data, allow_marshal=allow_marshal)
|
|
||||||
|
|
||||||
# Set barcode as string (not as Barcode object)
|
|
||||||
if barcode_data:
|
|
||||||
if isinstance(barcode_data, str):
|
|
||||||
instance.barcode = barcode_data
|
|
||||||
elif isinstance(barcode_data, dict):
|
|
||||||
# If it's a dict (Barcode serialized format), extract the data field
|
|
||||||
instance.barcode = barcode_data.get("data", "")
|
|
||||||
else:
|
|
||||||
instance.barcode = ""
|
|
||||||
|
|
||||||
# Set additional attributes
|
|
||||||
instance.diameter = data.get("diameter", instance._size_x)
|
|
||||||
instance.height = data.get("height", instance._size_z)
|
|
||||||
|
|
||||||
return instance
|
|
||||||
|
|
||||||
T = TypeVar("T", bound=ResourceHolder)
|
T = TypeVar("T", bound=ResourceHolder)
|
||||||
|
|
||||||
S = TypeVar("S", bound=ResourceHolder)
|
S = TypeVar("S", bound=ResourceHolder)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import inspect
|
import inspect
|
||||||
import traceback
|
import traceback
|
||||||
import uuid
|
import uuid
|
||||||
from pydantic import BaseModel, field_serializer, field_validator, ValidationError
|
from pydantic import BaseModel, field_serializer, field_validator
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from typing import List, Tuple, Any, Dict, Literal, Optional, cast, TYPE_CHECKING, Union
|
from typing import List, Tuple, Any, Dict, Literal, Optional, cast, TYPE_CHECKING, Union
|
||||||
|
|
||||||
@@ -147,8 +147,8 @@ class ResourceDictInstance(object):
|
|||||||
if not content.get("extra"): # MagicCode
|
if not content.get("extra"): # MagicCode
|
||||||
content["extra"] = {}
|
content["extra"] = {}
|
||||||
if "position" in content:
|
if "position" in content:
|
||||||
pose = content.get("pose", {})
|
pose = content.get("pose",{})
|
||||||
if "position" not in pose:
|
if "position" not in pose :
|
||||||
if "position" in content["position"]:
|
if "position" in content["position"]:
|
||||||
pose["position"] = content["position"]["position"]
|
pose["position"] = content["position"]["position"]
|
||||||
else:
|
else:
|
||||||
@@ -157,14 +157,10 @@ class ResourceDictInstance(object):
|
|||||||
pose["size"] = {
|
pose["size"] = {
|
||||||
"width": content["config"].get("size_x", 0),
|
"width": content["config"].get("size_x", 0),
|
||||||
"height": content["config"].get("size_y", 0),
|
"height": content["config"].get("size_y", 0),
|
||||||
"depth": content["config"].get("size_z", 0),
|
"depth": content["config"].get("size_z", 0)
|
||||||
}
|
}
|
||||||
content["pose"] = pose
|
content["pose"] = pose
|
||||||
try:
|
return ResourceDictInstance(ResourceDict.model_validate(content))
|
||||||
res_dict = ResourceDict.model_validate(content)
|
|
||||||
return ResourceDictInstance(res_dict)
|
|
||||||
except ValidationError as err:
|
|
||||||
raise err
|
|
||||||
|
|
||||||
def get_plr_nested_dict(self) -> Dict[str, Any]:
|
def get_plr_nested_dict(self) -> Dict[str, Any]:
|
||||||
"""获取资源实例的嵌套字典表示"""
|
"""获取资源实例的嵌套字典表示"""
|
||||||
@@ -326,7 +322,7 @@ class ResourceTreeSet(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_plr_resources(cls, resources: List["PLRResource"], known_newly_created=False) -> "ResourceTreeSet":
|
def from_plr_resources(cls, resources: List["PLRResource"]) -> "ResourceTreeSet":
|
||||||
"""
|
"""
|
||||||
从plr资源创建ResourceTreeSet
|
从plr资源创建ResourceTreeSet
|
||||||
"""
|
"""
|
||||||
@@ -343,8 +339,6 @@ class ResourceTreeSet(object):
|
|||||||
}
|
}
|
||||||
if source in replace_info:
|
if source in replace_info:
|
||||||
return replace_info[source]
|
return replace_info[source]
|
||||||
elif source is None:
|
|
||||||
return ""
|
|
||||||
else:
|
else:
|
||||||
print("转换pylabrobot的时候,出现未知类型", source)
|
print("转换pylabrobot的时候,出现未知类型", source)
|
||||||
return source
|
return source
|
||||||
@@ -355,8 +349,7 @@ class ResourceTreeSet(object):
|
|||||||
if not uid:
|
if not uid:
|
||||||
uid = str(uuid.uuid4())
|
uid = str(uuid.uuid4())
|
||||||
res.unilabos_uuid = uid
|
res.unilabos_uuid = uid
|
||||||
if not known_newly_created:
|
logger.warning(f"{res}没有uuid,请设置后再传入,默认填充{uid}!\n{traceback.format_exc()}")
|
||||||
logger.warning(f"{res}没有uuid,请设置后再传入,默认填充{uid}!\n{traceback.format_exc()}")
|
|
||||||
|
|
||||||
# 获取unilabos_extra,默认为空字典
|
# 获取unilabos_extra,默认为空字典
|
||||||
extra = getattr(res, "unilabos_extra", {})
|
extra = getattr(res, "unilabos_extra", {})
|
||||||
@@ -455,13 +448,7 @@ class ResourceTreeSet(object):
|
|||||||
from pylabrobot.utils.object_parsing import find_subclass
|
from pylabrobot.utils.object_parsing import find_subclass
|
||||||
|
|
||||||
# 类型映射
|
# 类型映射
|
||||||
TYPE_MAP = {
|
TYPE_MAP = {"plate": "Plate", "well": "Well", "deck": "Deck", "container": "RegularContainer", "tip_spot": "TipSpot"}
|
||||||
"plate": "Plate",
|
|
||||||
"well": "Well",
|
|
||||||
"deck": "Deck",
|
|
||||||
"container": "RegularContainer",
|
|
||||||
"tip_spot": "TipSpot",
|
|
||||||
}
|
|
||||||
|
|
||||||
def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict, name_to_extra: dict):
|
def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict, name_to_extra: dict):
|
||||||
"""一次遍历收集 name_to_uuid, all_states 和 name_to_extra"""
|
"""一次遍历收集 name_to_uuid, all_states 和 name_to_extra"""
|
||||||
@@ -621,16 +608,6 @@ class ResourceTreeSet(object):
|
|||||||
"""
|
"""
|
||||||
return [tree.root_node for tree in self.trees]
|
return [tree.root_node for tree in self.trees]
|
||||||
|
|
||||||
@property
|
|
||||||
def root_nodes_uuid(self) -> List[ResourceDictInstance]:
|
|
||||||
"""
|
|
||||||
获取所有树的根节点
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
所有根节点的资源实例列表
|
|
||||||
"""
|
|
||||||
return [tree.root_node.res_content.uuid for tree in self.trees]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all_nodes(self) -> List[ResourceDictInstance]:
|
def all_nodes(self) -> List[ResourceDictInstance]:
|
||||||
"""
|
"""
|
||||||
@@ -941,33 +918,6 @@ class DeviceNodeResourceTracker(object):
|
|||||||
|
|
||||||
return self._traverse_and_process(resource, process)
|
return self._traverse_and_process(resource, process)
|
||||||
|
|
||||||
def loop_find_with_uuid(self, resource, target_uuid: str):
|
|
||||||
"""
|
|
||||||
递归遍历资源树,根据 uuid 查找并返回对应的资源
|
|
||||||
|
|
||||||
Args:
|
|
||||||
resource: 资源对象(可以是list、dict或实例)
|
|
||||||
target_uuid: 要查找的uuid
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
找到的资源对象,未找到则返回None
|
|
||||||
"""
|
|
||||||
found_resource = None
|
|
||||||
|
|
||||||
def process(res):
|
|
||||||
nonlocal found_resource
|
|
||||||
if found_resource is not None:
|
|
||||||
return 0 # 已找到,跳过后续处理
|
|
||||||
current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid")
|
|
||||||
if current_uuid and current_uuid == target_uuid:
|
|
||||||
found_resource = res
|
|
||||||
logger.trace(f"找到资源UUID: {target_uuid}")
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
self._traverse_and_process(resource, process)
|
|
||||||
return found_resource
|
|
||||||
|
|
||||||
def loop_set_extra(self, resource, name_to_extra_map: Dict[str, dict]) -> int:
|
def loop_set_extra(self, resource, name_to_extra_map: Dict[str, dict]) -> int:
|
||||||
"""
|
"""
|
||||||
递归遍历资源树,根据 name 设置所有节点的 extra
|
递归遍历资源树,根据 name 设置所有节点的 extra
|
||||||
@@ -1153,7 +1103,7 @@ class DeviceNodeResourceTracker(object):
|
|||||||
for key in keys_to_remove:
|
for key in keys_to_remove:
|
||||||
self.resource2parent_resource.pop(key, None)
|
self.resource2parent_resource.pop(key, None)
|
||||||
|
|
||||||
logger.trace(f"[ResourceTracker] 成功移除资源: {resource}")
|
logger.debug(f"成功移除资源: {resource}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def clear_resource(self):
|
def clear_resource(self):
|
||||||
|
|||||||
@@ -43,10 +43,6 @@ def warehouse_factory(
|
|||||||
if layout == "row-major":
|
if layout == "row-major":
|
||||||
# 行优先:row=0(A行) 应该显示在上方,需要较小的 y 值
|
# 行优先:row=0(A行) 应该显示在上方,需要较小的 y 值
|
||||||
y = dy + row * item_dy
|
y = dy + row * item_dy
|
||||||
elif layout == "vertical-col-major":
|
|
||||||
# 竖向warehouse: row=0 对应顶部(y小),row=n-1 对应底部(y大)
|
|
||||||
# 但标签 01 应该在底部,所以使用反向映射
|
|
||||||
y = dy + (num_items_y - row - 1) * item_dy
|
|
||||||
else:
|
else:
|
||||||
# 列优先:保持原逻辑(row=0 对应较大的 y)
|
# 列优先:保持原逻辑(row=0 对应较大的 y)
|
||||||
y = dy + (num_items_y - row - 1) * item_dy
|
y = dy + (num_items_y - row - 1) * item_dy
|
||||||
|
|||||||
@@ -159,14 +159,10 @@ _msg_converter: Dict[Type, Any] = {
|
|||||||
else Pose()
|
else Pose()
|
||||||
),
|
),
|
||||||
config=json.dumps(x.get("config", {})),
|
config=json.dumps(x.get("config", {})),
|
||||||
data=json.dumps(obtain_data_with_uuid(x)),
|
data=json.dumps(x.get("data", {})),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def obtain_data_with_uuid(x: dict):
|
|
||||||
data = x.get("data", {})
|
|
||||||
data["unilabos_uuid"] = x.get("uuid", None)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def json_or_yaml_loads(data: str) -> Any:
|
def json_or_yaml_loads(data: str) -> Any:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -392,12 +392,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
parent_resource = self.resource_tracker.figure_resource(
|
parent_resource = self.resource_tracker.figure_resource(
|
||||||
{"name": bind_parent_id}
|
{"name": bind_parent_id}
|
||||||
)
|
)
|
||||||
for r in rts.root_nodes:
|
for r in rts.root_nodes:
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
r.res_content.parent_uuid = parent_resource.unilabos_uuid
|
r.res_content.parent_uuid = parent_resource.unilabos_uuid
|
||||||
else:
|
|
||||||
for r in rts.root_nodes:
|
|
||||||
r.res_content.parent_uuid = self.uuid
|
|
||||||
|
|
||||||
if len(LIQUID_INPUT_SLOT) and LIQUID_INPUT_SLOT[0] == -1 and len(rts.root_nodes) == 1 and isinstance(rts.root_nodes[0], RegularContainer):
|
if len(LIQUID_INPUT_SLOT) and LIQUID_INPUT_SLOT[0] == -1 and len(rts.root_nodes) == 1 and isinstance(rts.root_nodes[0], RegularContainer):
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
@@ -433,14 +430,11 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
})
|
})
|
||||||
tree_response: SerialCommand.Response = await client.call_async(request)
|
tree_response: SerialCommand.Response = await client.call_async(request)
|
||||||
uuid_maps = json.loads(tree_response.response)
|
uuid_maps = json.loads(tree_response.response)
|
||||||
plr_instances = rts.to_plr_resources()
|
self.resource_tracker.loop_update_uuid(input_resources, uuid_maps)
|
||||||
for plr_instance in plr_instances:
|
|
||||||
self.resource_tracker.loop_update_uuid(plr_instance, uuid_maps)
|
|
||||||
rts: ResourceTreeSet = ResourceTreeSet.from_plr_resources(plr_instances)
|
|
||||||
self.lab_logger().info(f"Resource tree added. UUID mapping: {len(uuid_maps)} nodes")
|
self.lab_logger().info(f"Resource tree added. UUID mapping: {len(uuid_maps)} nodes")
|
||||||
final_response = {
|
final_response = {
|
||||||
"created_resource_tree": rts.dump(),
|
"created_resources": rts.dump(),
|
||||||
"liquid_input_resource_tree": [],
|
"liquid_input_resources": [],
|
||||||
}
|
}
|
||||||
res.response = json.dumps(final_response)
|
res.response = json.dumps(final_response)
|
||||||
# 如果driver自己就有assign的方法,那就使用driver自己的assign方法
|
# 如果driver自己就有assign的方法,那就使用driver自己的assign方法
|
||||||
@@ -466,7 +460,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
return res
|
return res
|
||||||
try:
|
try:
|
||||||
if len(rts.root_nodes) == 1 and parent_resource is not None:
|
if len(rts.root_nodes) == 1 and parent_resource is not None:
|
||||||
plr_instance = plr_instances[0]
|
plr_instance = rts.to_plr_resources()[0]
|
||||||
if isinstance(plr_instance, Plate):
|
if isinstance(plr_instance, Plate):
|
||||||
empty_liquid_info_in: List[Tuple[Optional[str], float]] = [(None, 0)] * plr_instance.num_items
|
empty_liquid_info_in: List[Tuple[Optional[str], float]] = [(None, 0)] * plr_instance.num_items
|
||||||
if len(ADD_LIQUID_TYPE) == 1 and len(LIQUID_VOLUME) == 1 and len(LIQUID_INPUT_SLOT) > 1:
|
if len(ADD_LIQUID_TYPE) == 1 and len(LIQUID_VOLUME) == 1 and len(LIQUID_INPUT_SLOT) > 1:
|
||||||
@@ -491,7 +485,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
input_wells = []
|
input_wells = []
|
||||||
for r in LIQUID_INPUT_SLOT:
|
for r in LIQUID_INPUT_SLOT:
|
||||||
input_wells.append(plr_instance.children[r])
|
input_wells.append(plr_instance.children[r])
|
||||||
final_response["liquid_input_resource_tree"] = ResourceTreeSet.from_plr_resources(input_wells).dump()
|
final_response["liquid_input_resources"] = ResourceTreeSet.from_plr_resources(input_wells).dump()
|
||||||
res.response = json.dumps(final_response)
|
res.response = json.dumps(final_response)
|
||||||
if issubclass(parent_resource.__class__, Deck) and hasattr(parent_resource, "assign_child_at_slot") and "slot" in other_calling_param:
|
if issubclass(parent_resource.__class__, Deck) and hasattr(parent_resource, "assign_child_at_slot") and "slot" in other_calling_param:
|
||||||
other_calling_param["slot"] = int(other_calling_param["slot"])
|
other_calling_param["slot"] = int(other_calling_param["slot"])
|
||||||
@@ -625,7 +619,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
) # type: ignore
|
) # type: ignore
|
||||||
raw_nodes = json.loads(response.response)
|
raw_nodes = json.loads(response.response)
|
||||||
tree_set = ResourceTreeSet.from_raw_dict_list(raw_nodes)
|
tree_set = ResourceTreeSet.from_raw_dict_list(raw_nodes)
|
||||||
self.lab_logger().trace(f"获取资源结果: {len(tree_set.trees)} 个资源树 {tree_set.root_nodes}")
|
self.lab_logger().debug(f"获取资源结果: {len(tree_set.trees)} 个资源树")
|
||||||
return tree_set
|
return tree_set
|
||||||
|
|
||||||
async def get_resource_with_dir(self, resource_id: str, with_children: bool = True) -> "ResourcePLR":
|
async def get_resource_with_dir(self, resource_id: str, with_children: bool = True) -> "ResourcePLR":
|
||||||
@@ -659,71 +653,61 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
|
|
||||||
def transfer_to_new_resource(
|
def transfer_to_new_resource(
|
||||||
self, plr_resource: "ResourcePLR", tree: ResourceTreeInstance, additional_add_params: Dict[str, Any]
|
self, plr_resource: "ResourcePLR", tree: ResourceTreeInstance, additional_add_params: Dict[str, Any]
|
||||||
) -> Optional["ResourcePLR"]:
|
):
|
||||||
parent_uuid = tree.root_node.res_content.parent_uuid
|
parent_uuid = tree.root_node.res_content.parent_uuid
|
||||||
if not parent_uuid:
|
if parent_uuid:
|
||||||
self.lab_logger().warning(
|
parent_resource: ResourcePLR = self.resource_tracker.uuid_to_resources.get(parent_uuid)
|
||||||
f"物料{plr_resource} parent未知,挂载到当前节点下,额外参数:{additional_add_params}"
|
if parent_resource is None:
|
||||||
)
|
|
||||||
return None
|
|
||||||
if parent_uuid == self.uuid:
|
|
||||||
self.lab_logger().warning(
|
|
||||||
f"物料{plr_resource}请求挂载到{self.identifier},额外参数:{additional_add_params}"
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
parent_resource: ResourcePLR = self.resource_tracker.uuid_to_resources.get(parent_uuid)
|
|
||||||
if parent_resource is None:
|
|
||||||
self.lab_logger().warning(
|
|
||||||
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
# 特殊兼容所有plr的物料的assign方法,和create_resource append_resource后期同步
|
|
||||||
additional_params = {}
|
|
||||||
extra = getattr(plr_resource, "unilabos_extra", {})
|
|
||||||
if len(extra):
|
|
||||||
self.lab_logger().info(f"发现物料{plr_resource}额外参数: " + str(extra))
|
|
||||||
if "update_resource_site" in extra:
|
|
||||||
additional_add_params["site"] = extra["update_resource_site"]
|
|
||||||
site = additional_add_params.get("site", None)
|
|
||||||
spec = inspect.signature(parent_resource.assign_child_resource)
|
|
||||||
if "spot" in spec.parameters:
|
|
||||||
ordering_dict: Dict[str, Any] = getattr(parent_resource, "_ordering")
|
|
||||||
if ordering_dict:
|
|
||||||
site = list(ordering_dict.keys()).index(site)
|
|
||||||
additional_params["spot"] = site
|
|
||||||
old_parent = plr_resource.parent
|
|
||||||
if old_parent is not None:
|
|
||||||
# plr并不支持同一个deck的加载和卸载
|
|
||||||
self.lab_logger().warning(f"物料{plr_resource}请求从{old_parent}卸载")
|
|
||||||
old_parent.unassign_child_resource(plr_resource)
|
|
||||||
self.lab_logger().warning(
|
self.lab_logger().warning(
|
||||||
f"物料{plr_resource}请求挂载到{parent_resource},额外参数:{additional_params}"
|
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# 特殊兼容所有plr的物料的assign方法,和create_resource append_resource后期同步
|
||||||
|
additional_params = {}
|
||||||
|
extra = getattr(plr_resource, "unilabos_extra", {})
|
||||||
|
if len(extra):
|
||||||
|
self.lab_logger().info(f"发现物料{plr_resource}额外参数: " + str(extra))
|
||||||
|
if "update_resource_site" in extra:
|
||||||
|
additional_add_params["site"] = extra["update_resource_site"]
|
||||||
|
site = additional_add_params.get("site", None)
|
||||||
|
spec = inspect.signature(parent_resource.assign_child_resource)
|
||||||
|
if "spot" in spec.parameters:
|
||||||
|
ordering_dict: Dict[str, Any] = getattr(parent_resource, "_ordering")
|
||||||
|
if ordering_dict:
|
||||||
|
site = list(ordering_dict.keys()).index(site)
|
||||||
|
additional_params["spot"] = site
|
||||||
|
old_parent = plr_resource.parent
|
||||||
|
if old_parent is not None:
|
||||||
|
# plr并不支持同一个deck的加载和卸载
|
||||||
|
self.lab_logger().warning(f"物料{plr_resource}请求从{old_parent}卸载")
|
||||||
|
old_parent.unassign_child_resource(plr_resource)
|
||||||
|
self.lab_logger().warning(
|
||||||
|
f"物料{plr_resource}请求挂载到{parent_resource},额外参数:{additional_params}"
|
||||||
|
)
|
||||||
|
|
||||||
# ⭐ assign 之前,需要从 resources 列表中移除
|
# ⭐ assign 之前,需要从 resources 列表中移除
|
||||||
# 因为资源将不再是顶级资源,而是成为 parent_resource 的子资源
|
# 因为资源将不再是顶级资源,而是成为 parent_resource 的子资源
|
||||||
# 如果不移除,figure_resource 会找到两次:一次在 resources,一次在 parent 的 children
|
# 如果不移除,figure_resource 会找到两次:一次在 resources,一次在 parent 的 children
|
||||||
resource_id = id(plr_resource)
|
resource_id = id(plr_resource)
|
||||||
for i, r in enumerate(self.resource_tracker.resources):
|
for i, r in enumerate(self.resource_tracker.resources):
|
||||||
if id(r) == resource_id:
|
if id(r) == resource_id:
|
||||||
self.resource_tracker.resources.pop(i)
|
self.resource_tracker.resources.pop(i)
|
||||||
self.lab_logger().debug(
|
self.lab_logger().debug(
|
||||||
f"从顶级资源列表中移除 {plr_resource.name}(即将成为 {parent_resource.name} 的子资源)"
|
f"从顶级资源列表中移除 {plr_resource.name}(即将成为 {parent_resource.name} 的子资源)"
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
parent_resource.assign_child_resource(plr_resource, location=None, **additional_params)
|
parent_resource.assign_child_resource(plr_resource, location=None, **additional_params)
|
||||||
|
|
||||||
func = getattr(self.driver_instance, "resource_tree_transfer", None)
|
func = getattr(self.driver_instance, "resource_tree_transfer", None)
|
||||||
if callable(func):
|
if callable(func):
|
||||||
# 分别是 物料的原来父节点,当前物料的状态,物料的新父节点(此时物料已经重新assign了)
|
# 分别是 物料的原来父节点,当前物料的状态,物料的新父节点(此时物料已经重新assign了)
|
||||||
func(old_parent, plr_resource, parent_resource)
|
func(old_parent, plr_resource, parent_resource)
|
||||||
return parent_resource
|
except Exception as e:
|
||||||
except Exception as e:
|
self.lab_logger().warning(
|
||||||
self.lab_logger().warning(
|
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}"
|
||||||
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}"
|
)
|
||||||
)
|
|
||||||
|
|
||||||
async def s2c_resource_tree(self, req: SerialCommand_Request, res: SerialCommand_Response):
|
async def s2c_resource_tree(self, req: SerialCommand_Request, res: SerialCommand_Response):
|
||||||
"""
|
"""
|
||||||
@@ -738,7 +722,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
|
|
||||||
def _handle_add(
|
def _handle_add(
|
||||||
plr_resources: List[ResourcePLR], tree_set: ResourceTreeSet, additional_add_params: Dict[str, Any]
|
plr_resources: List[ResourcePLR], tree_set: ResourceTreeSet, additional_add_params: Dict[str, Any]
|
||||||
) -> Tuple[Dict[str, Any], List[ResourcePLR]]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
处理资源添加操作的内部函数
|
处理资源添加操作的内部函数
|
||||||
|
|
||||||
@@ -750,20 +734,15 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
Returns:
|
Returns:
|
||||||
操作结果字典
|
操作结果字典
|
||||||
"""
|
"""
|
||||||
parents = [] # 放的是被变更的物料 / 被变更的物料父级
|
|
||||||
for plr_resource, tree in zip(plr_resources, tree_set.trees):
|
for plr_resource, tree in zip(plr_resources, tree_set.trees):
|
||||||
self.resource_tracker.add_resource(plr_resource)
|
self.resource_tracker.add_resource(plr_resource)
|
||||||
parent = self.transfer_to_new_resource(plr_resource, tree, additional_add_params)
|
self.transfer_to_new_resource(plr_resource, tree, additional_add_params)
|
||||||
if parent is not None:
|
|
||||||
parents.append(parent)
|
|
||||||
else:
|
|
||||||
parents.append(plr_resource)
|
|
||||||
|
|
||||||
func = getattr(self.driver_instance, "resource_tree_add", None)
|
func = getattr(self.driver_instance, "resource_tree_add", None)
|
||||||
if callable(func):
|
if callable(func):
|
||||||
func(plr_resources)
|
func(plr_resources)
|
||||||
|
|
||||||
return {"success": True, "action": "add"}, parents
|
return {"success": True, "action": "add"}
|
||||||
|
|
||||||
def _handle_remove(resources_uuid: List[str]) -> Dict[str, Any]:
|
def _handle_remove(resources_uuid: List[str]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@@ -798,11 +777,11 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
if plr_resource.parent is not None:
|
if plr_resource.parent is not None:
|
||||||
plr_resource.parent.unassign_child_resource(plr_resource)
|
plr_resource.parent.unassign_child_resource(plr_resource)
|
||||||
self.resource_tracker.remove_resource(plr_resource)
|
self.resource_tracker.remove_resource(plr_resource)
|
||||||
self.lab_logger().info(f"[资源同步] 移除物料 {plr_resource} 及其子节点")
|
self.lab_logger().info(f"移除物料 {plr_resource} 及其子节点")
|
||||||
|
|
||||||
for other_plr_resource in other_plr_resources:
|
for other_plr_resource in other_plr_resources:
|
||||||
self.resource_tracker.remove_resource(other_plr_resource)
|
self.resource_tracker.remove_resource(other_plr_resource)
|
||||||
self.lab_logger().info(f"[资源同步] 移除物料 {other_plr_resource} 及其子节点")
|
self.lab_logger().info(f"移除物料 {other_plr_resource} 及其子节点")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
@@ -834,16 +813,11 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
original_instance: ResourcePLR = self.resource_tracker.figure_resource(
|
original_instance: ResourcePLR = self.resource_tracker.figure_resource(
|
||||||
{"uuid": tree.root_node.res_content.uuid}, try_mode=False
|
{"uuid": tree.root_node.res_content.uuid}, try_mode=False
|
||||||
)
|
)
|
||||||
original_parent_resource = original_instance.parent
|
|
||||||
original_parent_resource_uuid = getattr(original_parent_resource, "unilabos_uuid", None)
|
|
||||||
target_parent_resource_uuid = tree.root_node.res_content.uuid_parent
|
|
||||||
not_same_parent = original_parent_resource_uuid != target_parent_resource_uuid and original_parent_resource is not None
|
|
||||||
old_name = original_instance.name
|
|
||||||
new_name = plr_resource.name
|
|
||||||
parent_appended = False
|
|
||||||
|
|
||||||
# Update操作中包含改名:需要先remove再add,这里更新父节点即可
|
# Update操作中包含改名:需要先remove再add
|
||||||
if not not_same_parent and old_name != new_name:
|
if original_instance.name != plr_resource.name:
|
||||||
|
old_name = original_instance.name
|
||||||
|
new_name = plr_resource.name
|
||||||
self.lab_logger().info(f"物料改名操作:{old_name} -> {new_name}")
|
self.lab_logger().info(f"物料改名操作:{old_name} -> {new_name}")
|
||||||
|
|
||||||
# 收集所有相关的uuid(包括子节点)
|
# 收集所有相关的uuid(包括子节点)
|
||||||
@@ -852,10 +826,12 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
_handle_add([original_instance], tree_set, additional_add_params)
|
_handle_add([original_instance], tree_set, additional_add_params)
|
||||||
|
|
||||||
self.lab_logger().info(f"物料改名完成:{old_name} -> {new_name}")
|
self.lab_logger().info(f"物料改名完成:{old_name} -> {new_name}")
|
||||||
original_instances.append(original_parent_resource)
|
|
||||||
parent_appended = True
|
|
||||||
|
|
||||||
# 常规更新:不涉及改名
|
# 常规更新:不涉及改名
|
||||||
|
original_parent_resource = original_instance.parent
|
||||||
|
original_parent_resource_uuid = getattr(original_parent_resource, "unilabos_uuid", None)
|
||||||
|
target_parent_resource_uuid = tree.root_node.res_content.uuid_parent
|
||||||
|
|
||||||
self.lab_logger().info(
|
self.lab_logger().info(
|
||||||
f"物料{original_instance} 原始父节点{original_parent_resource_uuid} "
|
f"物料{original_instance} 原始父节点{original_parent_resource_uuid} "
|
||||||
f"目标父节点{target_parent_resource_uuid} 更新"
|
f"目标父节点{target_parent_resource_uuid} 更新"
|
||||||
@@ -866,12 +842,13 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
original_instance.unilabos_extra = getattr(plr_resource, "unilabos_extra") # type: ignore # noqa: E501
|
original_instance.unilabos_extra = getattr(plr_resource, "unilabos_extra") # type: ignore # noqa: E501
|
||||||
|
|
||||||
# 如果父节点变化,需要重新挂载
|
# 如果父节点变化,需要重新挂载
|
||||||
if not_same_parent:
|
if (
|
||||||
parent = self.transfer_to_new_resource(original_instance, tree, additional_add_params)
|
original_parent_resource_uuid != target_parent_resource_uuid
|
||||||
original_instances.append(parent)
|
and original_parent_resource is not None
|
||||||
parent_appended = True
|
):
|
||||||
|
self.transfer_to_new_resource(original_instance, tree, additional_add_params)
|
||||||
else:
|
else:
|
||||||
# 判断是否变更了resource_site,重新登记
|
# 判断是否变更了resource_site
|
||||||
target_site = original_instance.unilabos_extra.get("update_resource_site")
|
target_site = original_instance.unilabos_extra.get("update_resource_site")
|
||||||
sites = original_instance.parent.sites if original_instance.parent is not None and hasattr(original_instance.parent, "sites") else None
|
sites = original_instance.parent.sites if original_instance.parent is not None and hasattr(original_instance.parent, "sites") else None
|
||||||
site_names = list(original_instance.parent._ordering.keys()) if original_instance.parent is not None and hasattr(original_instance.parent, "sites") else []
|
site_names = list(original_instance.parent._ordering.keys()) if original_instance.parent is not None and hasattr(original_instance.parent, "sites") else []
|
||||||
@@ -879,10 +856,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
site_index = sites.index(original_instance)
|
site_index = sites.index(original_instance)
|
||||||
site_name = site_names[site_index]
|
site_name = site_names[site_index]
|
||||||
if site_name != target_site:
|
if site_name != target_site:
|
||||||
parent = self.transfer_to_new_resource(original_instance, tree, additional_add_params)
|
self.transfer_to_new_resource(original_instance, tree, additional_add_params)
|
||||||
if parent is not None:
|
|
||||||
original_instances.append(parent)
|
|
||||||
parent_appended = True
|
|
||||||
|
|
||||||
# 加载状态
|
# 加载状态
|
||||||
original_instance.load_all_state(states)
|
original_instance.load_all_state(states)
|
||||||
@@ -890,8 +864,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
self.lab_logger().info(
|
self.lab_logger().info(
|
||||||
f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] " f"及其子节点 {child_count} 个"
|
f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] " f"及其子节点 {child_count} 个"
|
||||||
)
|
)
|
||||||
if not parent_appended:
|
original_instances.append(original_instance)
|
||||||
original_instances.append(original_instance)
|
|
||||||
|
|
||||||
# 调用driver的update回调
|
# 调用driver的update回调
|
||||||
func = getattr(self.driver_instance, "resource_tree_update", None)
|
func = getattr(self.driver_instance, "resource_tree_update", None)
|
||||||
@@ -908,8 +881,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
action = i.get("action") # remove, add, update
|
action = i.get("action") # remove, add, update
|
||||||
resources_uuid: List[str] = i.get("data") # 资源数据
|
resources_uuid: List[str] = i.get("data") # 资源数据
|
||||||
additional_add_params = i.get("additional_add_params", {}) # 额外参数
|
additional_add_params = i.get("additional_add_params", {}) # 额外参数
|
||||||
self.lab_logger().trace(
|
self.lab_logger().info(
|
||||||
f"[资源同步] 处理 {action}, " f"resources count: {len(resources_uuid)}"
|
f"[Resource Tree Update] Processing {action} operation, " f"resources count: {len(resources_uuid)}"
|
||||||
)
|
)
|
||||||
tree_set = None
|
tree_set = None
|
||||||
if action in ["add", "update"]:
|
if action in ["add", "update"]:
|
||||||
@@ -921,20 +894,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
if tree_set is None:
|
if tree_set is None:
|
||||||
raise ValueError("tree_set不能为None")
|
raise ValueError("tree_set不能为None")
|
||||||
plr_resources = tree_set.to_plr_resources()
|
plr_resources = tree_set.to_plr_resources()
|
||||||
result, parents = _handle_add(plr_resources, tree_set, additional_add_params)
|
result = _handle_add(plr_resources, tree_set, additional_add_params)
|
||||||
parents: List[Optional["ResourcePLR"]] = [i for i in parents if i is not None]
|
new_tree_set = ResourceTreeSet.from_plr_resources(plr_resources)
|
||||||
# de_dupe_parents = list(set(parents))
|
|
||||||
# Fix unhashable type error for WareHouse
|
|
||||||
de_dupe_parents = []
|
|
||||||
_seen_ids = set()
|
|
||||||
for p in parents:
|
|
||||||
if id(p) not in _seen_ids:
|
|
||||||
_seen_ids.add(id(p))
|
|
||||||
de_dupe_parents.append(p)
|
|
||||||
new_tree_set = ResourceTreeSet.from_plr_resources(de_dupe_parents) # 去重
|
|
||||||
for tree in new_tree_set.trees:
|
|
||||||
if tree.root_node.res_content.uuid_parent is None and self.node_name != "host_node":
|
|
||||||
tree.root_node.res_content.parent_uuid = self.uuid
|
|
||||||
r = SerialCommand.Request()
|
r = SerialCommand.Request()
|
||||||
r.command = json.dumps(
|
r.command = json.dumps(
|
||||||
{"data": {"data": new_tree_set.dump()}, "action": "update"}) # 和Update Resource一致
|
{"data": {"data": new_tree_set.dump()}, "action": "update"}) # 和Update Resource一致
|
||||||
@@ -953,10 +914,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
plr_resources.append(ResourceTreeSet([tree]).to_plr_resources()[0])
|
plr_resources.append(ResourceTreeSet([tree]).to_plr_resources()[0])
|
||||||
result, original_instances = _handle_update(plr_resources, tree_set, additional_add_params)
|
result, original_instances = _handle_update(plr_resources, tree_set, additional_add_params)
|
||||||
if not BasicConfig.no_update_feedback:
|
if not BasicConfig.no_update_feedback:
|
||||||
new_tree_set = ResourceTreeSet.from_plr_resources(original_instances) # 去重
|
new_tree_set = ResourceTreeSet.from_plr_resources(original_instances)
|
||||||
for tree in new_tree_set.trees:
|
|
||||||
if tree.root_node.res_content.uuid_parent is None and self.node_name != "host_node":
|
|
||||||
tree.root_node.res_content.parent_uuid = self.uuid
|
|
||||||
r = SerialCommand.Request()
|
r = SerialCommand.Request()
|
||||||
r.command = json.dumps(
|
r.command = json.dumps(
|
||||||
{"data": {"data": new_tree_set.dump()}, "action": "update"}) # 和Update Resource一致
|
{"data": {"data": new_tree_set.dump()}, "action": "update"}) # 和Update Resource一致
|
||||||
@@ -976,15 +934,15 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
# 返回处理结果
|
# 返回处理结果
|
||||||
result_json = {"results": results, "total": len(data)}
|
result_json = {"results": results, "total": len(data)}
|
||||||
res.response = json.dumps(result_json, ensure_ascii=False, cls=TypeEncoder)
|
res.response = json.dumps(result_json, ensure_ascii=False, cls=TypeEncoder)
|
||||||
# self.lab_logger().info(f"[Resource Tree Update] Completed processing {len(data)} operations")
|
self.lab_logger().info(f"[Resource Tree Update] Completed processing {len(data)} operations")
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
error_msg = f"Invalid JSON format: {str(e)}"
|
error_msg = f"Invalid JSON format: {str(e)}"
|
||||||
self.lab_logger().error(f"[资源同步] {error_msg}")
|
self.lab_logger().error(f"[Resource Tree Update] {error_msg}")
|
||||||
res.response = json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
|
res.response = json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Unexpected error: {str(e)}"
|
error_msg = f"Unexpected error: {str(e)}"
|
||||||
self.lab_logger().error(f"[资源同步] {error_msg}")
|
self.lab_logger().error(f"[Resource Tree Update] {error_msg}")
|
||||||
self.lab_logger().error(traceback.format_exc())
|
self.lab_logger().error(traceback.format_exc())
|
||||||
res.response = json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
|
res.response = json.dumps({"success": False, "error": error_msg}, ensure_ascii=False)
|
||||||
|
|
||||||
@@ -1305,8 +1263,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
ACTION, action_paramtypes = self.get_real_function(self.driver_instance, action_name)
|
ACTION, action_paramtypes = self.get_real_function(self.driver_instance, action_name)
|
||||||
|
|
||||||
action_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
|
action_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
|
||||||
self.lab_logger().debug(f"任务 {ACTION.__name__} 接收到原始目标: {str(action_kwargs)[:1000]}")
|
self.lab_logger().debug(f"任务 {ACTION.__name__} 接收到原始目标: {action_kwargs}")
|
||||||
self.lab_logger().trace(f"任务 {ACTION.__name__} 接收到原始目标: {action_kwargs}")
|
|
||||||
error_skip = False
|
error_skip = False
|
||||||
# 向Host查询物料当前状态,如果是host本身的增加物料的请求,则直接跳过
|
# 向Host查询物料当前状态,如果是host本身的增加物料的请求,则直接跳过
|
||||||
if action_name not in ["create_resource_detailed", "create_resource"]:
|
if action_name not in ["create_resource_detailed", "create_resource"]:
|
||||||
@@ -1322,14 +1279,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
# 批量查询资源
|
# 批量查询资源
|
||||||
queried_resources = []
|
queried_resources = []
|
||||||
for resource_data in resource_inputs:
|
for resource_data in resource_inputs:
|
||||||
unilabos_uuid = resource_data.get("data", {}).get("unilabos_uuid")
|
plr_resource = await self.get_resource_with_dir(
|
||||||
if unilabos_uuid is None:
|
resource_id=resource_data["id"], with_children=True
|
||||||
plr_resource = await self.get_resource_with_dir(
|
)
|
||||||
resource_id=resource_data["id"], with_children=True
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
resource_tree = await self.get_resource([unilabos_uuid])
|
|
||||||
plr_resource = resource_tree.to_plr_resources()[0]
|
|
||||||
if "sample_id" in resource_data:
|
if "sample_id" in resource_data:
|
||||||
plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"]
|
plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"]
|
||||||
queried_resources.append(plr_resource)
|
queried_resources.append(plr_resource)
|
||||||
@@ -1378,8 +1330,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
execution_success = True
|
execution_success = True
|
||||||
except Exception as _:
|
except Exception as _:
|
||||||
execution_error = traceback.format_exc()
|
execution_error = traceback.format_exc()
|
||||||
error(f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{str(action_kwargs)[:1000]}")
|
error(
|
||||||
trace(f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}")
|
f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}"
|
||||||
|
)
|
||||||
|
|
||||||
future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs)
|
future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs)
|
||||||
future.add_done_callback(_handle_future_exception)
|
future.add_done_callback(_handle_future_exception)
|
||||||
@@ -1399,9 +1352,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
except Exception as _:
|
except Exception as _:
|
||||||
execution_error = traceback.format_exc()
|
execution_error = traceback.format_exc()
|
||||||
error(
|
error(
|
||||||
f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{str(action_kwargs)[:1000]}")
|
f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}"
|
||||||
trace(
|
)
|
||||||
f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}")
|
|
||||||
|
|
||||||
future.add_done_callback(_handle_future_exception)
|
future.add_done_callback(_handle_future_exception)
|
||||||
|
|
||||||
@@ -1469,7 +1421,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
for r in rs:
|
for r in rs:
|
||||||
res = self.resource_tracker.parent_resource(r) # 获取 resource 对象
|
res = self.resource_tracker.parent_resource(r) # 获取 resource 对象
|
||||||
else:
|
else:
|
||||||
res = self.resource_tracker.parent_resource(rs)
|
res = self.resource_tracker.parent_resource(r)
|
||||||
if id(res) not in seen:
|
if id(res) not in seen:
|
||||||
seen.add(id(res))
|
seen.add(id(res))
|
||||||
unique_resources.append(res)
|
unique_resources.append(res)
|
||||||
@@ -1545,7 +1497,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
resource_data = function_args[arg_name]
|
resource_data = function_args[arg_name]
|
||||||
if isinstance(resource_data, dict) and "id" in resource_data:
|
if isinstance(resource_data, dict) and "id" in resource_data:
|
||||||
try:
|
try:
|
||||||
function_args[arg_name] = self._convert_resources_sync(resource_data["uuid"])[0]
|
converted_resource = self._convert_resource_sync(resource_data)
|
||||||
|
function_args[arg_name] = converted_resource
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.lab_logger().error(
|
self.lab_logger().error(
|
||||||
f"转换ResourceSlot参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
f"转换ResourceSlot参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||||
@@ -1559,8 +1512,12 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
resource_list = function_args[arg_name]
|
resource_list = function_args[arg_name]
|
||||||
if isinstance(resource_list, list):
|
if isinstance(resource_list, list):
|
||||||
try:
|
try:
|
||||||
uuids = [r["uuid"] for r in resource_list if isinstance(r, dict) and "id" in r]
|
converted_resources = []
|
||||||
function_args[arg_name] = self._convert_resources_sync(*uuids) if uuids else []
|
for resource_data in resource_list:
|
||||||
|
if isinstance(resource_data, dict) and "id" in resource_data:
|
||||||
|
converted_resource = self._convert_resource_sync(resource_data)
|
||||||
|
converted_resources.append(converted_resource)
|
||||||
|
function_args[arg_name] = converted_resources
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.lab_logger().error(
|
self.lab_logger().error(
|
||||||
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||||
@@ -1573,27 +1530,20 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
f"执行动作时JSON缺少function_name或function_args: {ex}\n原JSON: {string}\n{traceback.format_exc()}"
|
f"执行动作时JSON缺少function_name或function_args: {ex}\n原JSON: {string}\n{traceback.format_exc()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _convert_resources_sync(self, *uuids: str) -> List["ResourcePLR"]:
|
def _convert_resource_sync(self, resource_data: Dict[str, Any]):
|
||||||
"""同步转换资源 UUID 为实例
|
"""同步转换资源数据为实例"""
|
||||||
|
# 创建资源查询请求
|
||||||
|
r = SerialCommand.Request()
|
||||||
|
r.command = json.dumps(
|
||||||
|
{
|
||||||
|
"id": resource_data.get("id", None),
|
||||||
|
"uuid": resource_data.get("uuid", None),
|
||||||
|
"with_children": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Args:
|
# 同步调用资源查询服务
|
||||||
*uuids: 一个或多个资源 UUID
|
future = self._resource_clients["resource_get"].call_async(r)
|
||||||
|
|
||||||
Returns:
|
|
||||||
单个 UUID 时返回单个资源实例,多个 UUID 时返回资源实例列表
|
|
||||||
"""
|
|
||||||
if not uuids:
|
|
||||||
raise ValueError("至少需要提供一个 UUID")
|
|
||||||
|
|
||||||
uuids_list = list(uuids)
|
|
||||||
future = self._resource_clients["c2s_update_resource_tree"].call_async(SerialCommand.Request(
|
|
||||||
command=json.dumps(
|
|
||||||
{
|
|
||||||
"data": {"data": uuids_list, "with_children": True},
|
|
||||||
"action": "get",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
))
|
|
||||||
|
|
||||||
# 等待结果(使用while循环,每次sleep 0.05秒,最多等待30秒)
|
# 等待结果(使用while循环,每次sleep 0.05秒,最多等待30秒)
|
||||||
timeout = 30.0
|
timeout = 30.0
|
||||||
@@ -1603,40 +1553,27 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
elapsed += 0.05
|
elapsed += 0.05
|
||||||
|
|
||||||
if not future.done():
|
if not future.done():
|
||||||
raise Exception(f"资源查询超时: {uuids_list}")
|
raise Exception(f"资源查询超时: {resource_data}")
|
||||||
|
|
||||||
response = future.result()
|
response = future.result()
|
||||||
if response is None:
|
if response is None:
|
||||||
raise Exception(f"资源查询返回空结果: {uuids_list}")
|
raise Exception(f"资源查询返回空结果: {resource_data}")
|
||||||
|
|
||||||
raw_data = json.loads(response.response)
|
raw_data = json.loads(response.response)
|
||||||
|
|
||||||
# 转换为 PLR 资源
|
# 转换为 PLR 资源
|
||||||
tree_set = ResourceTreeSet.from_raw_dict_list(raw_data)
|
tree_set = ResourceTreeSet.from_raw_dict_list(raw_data)
|
||||||
if not len(tree_set.trees):
|
plr_resource = tree_set.to_plr_resources()[0]
|
||||||
raise Exception(f"资源查询返回空树: {raw_data}")
|
|
||||||
plr_resources = tree_set.to_plr_resources()
|
|
||||||
|
|
||||||
# 通过资源跟踪器获取本地实例
|
# 通过资源跟踪器获取本地实例
|
||||||
figured_resources: List[ResourcePLR] = []
|
res = self.resource_tracker.figure_resource(plr_resource, try_mode=True)
|
||||||
for plr_resource, tree in zip(plr_resources, tree_set.trees):
|
if len(res) == 0:
|
||||||
res = self.resource_tracker.figure_resource(plr_resource, try_mode=True)
|
self.lab_logger().warning(f"资源转换未能索引到实例: {resource_data},返回新建实例")
|
||||||
if len(res) == 0:
|
return plr_resource
|
||||||
self.lab_logger().warning(f"资源转换未能索引到实例: {tree.root_node.res_content},返回新建实例")
|
elif len(res) == 1:
|
||||||
figured_resources.append(plr_resource)
|
return res[0]
|
||||||
elif len(res) == 1:
|
else:
|
||||||
figured_resources.append(res[0])
|
raise ValueError(f"资源转换得到多个实例: {res}")
|
||||||
else:
|
|
||||||
raise ValueError(f"资源转换得到多个实例: {res}")
|
|
||||||
|
|
||||||
mapped_plr_resources = []
|
|
||||||
for uuid in uuids_list:
|
|
||||||
for plr_resource in figured_resources:
|
|
||||||
r = self.resource_tracker.loop_find_with_uuid(plr_resource, uuid)
|
|
||||||
mapped_plr_resources.append(r)
|
|
||||||
break
|
|
||||||
|
|
||||||
return mapped_plr_resources
|
|
||||||
|
|
||||||
async def _execute_driver_command_async(self, string: str):
|
async def _execute_driver_command_async(self, string: str):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialComma
|
|||||||
from unique_identifier_msgs.msg import UUID
|
from unique_identifier_msgs.msg import UUID
|
||||||
|
|
||||||
from unilabos.registry.registry import lab_registry
|
from unilabos.registry.registry import lab_registry
|
||||||
from unilabos.resources.container import RegularContainer
|
|
||||||
from unilabos.resources.graphio import initialize_resource
|
from unilabos.resources.graphio import initialize_resource
|
||||||
from unilabos.resources.registry import add_schema
|
from unilabos.resources.registry import add_schema
|
||||||
from unilabos.ros.initialize_device import initialize_device_from_dict
|
from unilabos.ros.initialize_device import initialize_device_from_dict
|
||||||
@@ -362,7 +361,8 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
request.command = ""
|
request.command = ""
|
||||||
future = sclient.call_async(request)
|
future = sclient.call_async(request)
|
||||||
# Use timeout for result as well
|
# Use timeout for result as well
|
||||||
future.result()
|
future.result(timeout_sec=5.0)
|
||||||
|
self.lab_logger().debug(f"[Host Node] Re-register completed for {device_namespace}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Gracefully handle destruction during shutdown
|
# Gracefully handle destruction during shutdown
|
||||||
if "destruction was requested" in str(e) or self._shutting_down:
|
if "destruction was requested" in str(e) or self._shutting_down:
|
||||||
@@ -586,10 +586,11 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
assert len(response) == 1, "Create Resource应当只返回一个结果"
|
new_li = []
|
||||||
for i in response:
|
for i in response:
|
||||||
res = json.loads(i)
|
res = json.loads(i)
|
||||||
return res
|
new_li.append(res)
|
||||||
|
return {"resources": new_li, "liquid_input_resources": new_li}
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
pass
|
pass
|
||||||
_n = "\n"
|
_n = "\n"
|
||||||
@@ -794,8 +795,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
assign_sample_id(action_kwargs)
|
assign_sample_id(action_kwargs)
|
||||||
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
||||||
|
|
||||||
self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}")
|
self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
||||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
|
||||||
action_client.wait_for_server()
|
action_client.wait_for_server()
|
||||||
goal_uuid_obj = UUID(uuid=list(u.bytes))
|
goal_uuid_obj = UUID(uuid=list(u.bytes))
|
||||||
|
|
||||||
@@ -1133,11 +1133,11 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
|
|
||||||
接收序列化的 ResourceTreeSet 数据并进行处理
|
接收序列化的 ResourceTreeSet 数据并进行处理
|
||||||
"""
|
"""
|
||||||
|
self.lab_logger().info(f"[Host Node-Resource] Resource tree add request received")
|
||||||
try:
|
try:
|
||||||
# 解析请求数据
|
# 解析请求数据
|
||||||
data = json.loads(request.command)
|
data = json.loads(request.command)
|
||||||
action = data["action"]
|
action = data["action"]
|
||||||
self.lab_logger().info(f"[Host Node-Resource] Resource tree {action} request received")
|
|
||||||
data = data["data"]
|
data = data["data"]
|
||||||
if action == "add":
|
if action == "add":
|
||||||
await self._resource_tree_action_add_callback(data, response)
|
await self._resource_tree_action_add_callback(data, response)
|
||||||
@@ -1243,7 +1243,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
data = json.loads(request.command)
|
data = json.loads(request.command)
|
||||||
if "uuid" in data and data["uuid"] is not None:
|
if "uuid" in data and data["uuid"] is not None:
|
||||||
http_req = http_client.resource_tree_get([data["uuid"]], data["with_children"])
|
http_req = http_client.resource_tree_get([data["uuid"]], data["with_children"])
|
||||||
elif "id" in data:
|
elif "id" in data and data["id"].startswith("/"):
|
||||||
http_req = http_client.resource_get(data["id"], data["with_children"])
|
http_req = http_client.resource_get(data["id"], data["with_children"])
|
||||||
else:
|
else:
|
||||||
raise ValueError("没有使用正确的物料 id 或 uuid")
|
raise ValueError("没有使用正确的物料 id 或 uuid")
|
||||||
@@ -1453,16 +1453,10 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_resource(
|
def test_resource(
|
||||||
self, resource: ResourceSlot = None, resources: List[ResourceSlot] = None, device: DeviceSlot = None, devices: List[DeviceSlot] = None
|
self, resource: ResourceSlot, resources: List[ResourceSlot], device: DeviceSlot, devices: List[DeviceSlot]
|
||||||
) -> TestResourceReturn:
|
) -> TestResourceReturn:
|
||||||
if resources is None:
|
|
||||||
resources = []
|
|
||||||
if devices is None:
|
|
||||||
devices = []
|
|
||||||
if resource is None:
|
|
||||||
resource = RegularContainer("test_resource传入None")
|
|
||||||
return {
|
return {
|
||||||
"resources": ResourceTreeSet.from_plr_resources([resource, *resources], known_newly_created=True).dump(),
|
"resources": ResourceTreeSet.from_plr_resources([resource, *resources]).dump(),
|
||||||
"devices": [device, *devices],
|
"devices": [device, *devices],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1514,7 +1508,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
|
|
||||||
# 构建服务地址
|
# 构建服务地址
|
||||||
srv_address = f"/srv{namespace}/s2c_resource_tree"
|
srv_address = f"/srv{namespace}/s2c_resource_tree"
|
||||||
self.lab_logger().trace(f"[Host Node-Resource] Host -> {device_id} ResourceTree {action} operation started -------")
|
self.lab_logger().info(f"[Host Node-Resource] Notifying {device_id} for resource tree {action} operation")
|
||||||
|
|
||||||
# 创建服务客户端
|
# 创建服务客户端
|
||||||
sclient = self.create_client(SerialCommand, srv_address)
|
sclient = self.create_client(SerialCommand, srv_address)
|
||||||
@@ -1549,7 +1543,9 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
||||||
response = future.result()
|
response = future.result()
|
||||||
self.lab_logger().trace(f"[Host Node-Resource] Host -> {device_id} ResourceTree {action} operation completed -------")
|
self.lab_logger().info(
|
||||||
|
f"[Host Node-Resource] Resource tree {action} notification completed for {device_id}"
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -9,125 +9,49 @@
|
|||||||
"parent": null,
|
"parent": null,
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "bioyond_dispensing_station",
|
"class": "bioyond_dispensing_station",
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
"config": {
|
||||||
"api_key": "YOUR_API_KEY",
|
"config": {
|
||||||
"api_host": "http://your-api-host:port",
|
"api_key": "DE9BDDA0",
|
||||||
"material_type_mappings": {
|
"api_host": "http://192.168.1.200:44388",
|
||||||
"BIOYOND_PolymerStation_1FlaskCarrier": [
|
"material_type_mappings": {
|
||||||
"烧杯",
|
"BIOYOND_PolymerStation_1FlaskCarrier": [
|
||||||
"uuid-placeholder-flask"
|
"烧杯",
|
||||||
],
|
"3a14196b-24f2-ca49-9081-0cab8021bf1a"
|
||||||
"BIOYOND_PolymerStation_1BottleCarrier": [
|
],
|
||||||
"试剂瓶",
|
"BIOYOND_PolymerStation_1BottleCarrier": [
|
||||||
"uuid-placeholder-bottle"
|
"试剂瓶",
|
||||||
],
|
"3a14196b-8bcf-a460-4f74-23f21ca79e72"
|
||||||
"BIOYOND_PolymerStation_6StockCarrier": [
|
],
|
||||||
"分装板",
|
"BIOYOND_PolymerStation_6StockCarrier": [
|
||||||
"uuid-placeholder-stock-6"
|
"分装板",
|
||||||
],
|
"3a14196e-5dfe-6e21-0c79-fe2036d052c4"
|
||||||
"BIOYOND_PolymerStation_Liquid_Vial": [
|
],
|
||||||
"10%分装小瓶",
|
"BIOYOND_PolymerStation_Liquid_Vial": [
|
||||||
"uuid-placeholder-liquid-vial"
|
"10%分装小瓶",
|
||||||
],
|
"3a14196c-76be-2279-4e22-7310d69aed68"
|
||||||
"BIOYOND_PolymerStation_Solid_Vial": [
|
],
|
||||||
"90%分装小瓶",
|
"BIOYOND_PolymerStation_Solid_Vial": [
|
||||||
"uuid-placeholder-solid-vial"
|
"90%分装小瓶",
|
||||||
],
|
"3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"
|
||||||
"BIOYOND_PolymerStation_8StockCarrier": [
|
],
|
||||||
"样品板",
|
"BIOYOND_PolymerStation_8StockCarrier": [
|
||||||
"uuid-placeholder-stock-8"
|
"样品板",
|
||||||
],
|
"3a14196e-b7a0-a5da-1931-35f3000281e9"
|
||||||
"BIOYOND_PolymerStation_Solid_Stock": [
|
],
|
||||||
"样品瓶",
|
"BIOYOND_PolymerStation_Solid_Stock": [
|
||||||
"uuid-placeholder-solid-stock"
|
"样品瓶",
|
||||||
]
|
"3a14196a-cf7d-8aea-48d8-b9662c7dba94"
|
||||||
},
|
]
|
||||||
"warehouse_mapping": {
|
|
||||||
"粉末堆栈": {
|
|
||||||
"uuid": "uuid-placeholder-powder-stack",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "uuid-placeholder-powder-A01",
|
|
||||||
"A02": "uuid-placeholder-powder-A02",
|
|
||||||
"A03": "uuid-placeholder-powder-A03",
|
|
||||||
"A04": "uuid-placeholder-powder-A04",
|
|
||||||
"B01": "uuid-placeholder-powder-B01",
|
|
||||||
"B02": "uuid-placeholder-powder-B02",
|
|
||||||
"B03": "uuid-placeholder-powder-B03",
|
|
||||||
"B04": "uuid-placeholder-powder-B04",
|
|
||||||
"C01": "uuid-placeholder-powder-C01",
|
|
||||||
"C02": "uuid-placeholder-powder-C02",
|
|
||||||
"C03": "uuid-placeholder-powder-C03",
|
|
||||||
"C04": "uuid-placeholder-powder-C04",
|
|
||||||
"D01": "uuid-placeholder-powder-D01",
|
|
||||||
"D02": "uuid-placeholder-powder-D02",
|
|
||||||
"D03": "uuid-placeholder-powder-D03",
|
|
||||||
"D04": "uuid-placeholder-powder-D04"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"溶液堆栈": {
|
|
||||||
"uuid": "uuid-placeholder-liquid-stack",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "uuid-placeholder-liquid-A01",
|
|
||||||
"A02": "uuid-placeholder-liquid-A02",
|
|
||||||
"A03": "uuid-placeholder-liquid-A03",
|
|
||||||
"A04": "uuid-placeholder-liquid-A04",
|
|
||||||
"B01": "uuid-placeholder-liquid-B01",
|
|
||||||
"B02": "uuid-placeholder-liquid-B02",
|
|
||||||
"B03": "uuid-placeholder-liquid-B03",
|
|
||||||
"B04": "uuid-placeholder-liquid-B04",
|
|
||||||
"C01": "uuid-placeholder-liquid-C01",
|
|
||||||
"C02": "uuid-placeholder-liquid-C02",
|
|
||||||
"C03": "uuid-placeholder-liquid-C03",
|
|
||||||
"C04": "uuid-placeholder-liquid-C04",
|
|
||||||
"D01": "uuid-placeholder-liquid-D01",
|
|
||||||
"D02": "uuid-placeholder-liquid-D02",
|
|
||||||
"D03": "uuid-placeholder-liquid-D03",
|
|
||||||
"D04": "uuid-placeholder-liquid-D04"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"试剂堆栈": {
|
|
||||||
"uuid": "uuid-placeholder-reagent-stack",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "uuid-placeholder-reagent-A01",
|
|
||||||
"A02": "uuid-placeholder-reagent-A02",
|
|
||||||
"A03": "uuid-placeholder-reagent-A03",
|
|
||||||
"A04": "uuid-placeholder-reagent-A04",
|
|
||||||
"B01": "uuid-placeholder-reagent-B01",
|
|
||||||
"B02": "uuid-placeholder-reagent-B02",
|
|
||||||
"B03": "uuid-placeholder-reagent-B03",
|
|
||||||
"B04": "uuid-placeholder-reagent-B04"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http_service_config": {
|
"deck": {
|
||||||
"http_service_host": "127.0.0.1",
|
"data": {
|
||||||
"http_service_port": 8080
|
"_resource_child_name": "Bioyond_Dispensing_Deck",
|
||||||
},
|
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck"
|
||||||
"material_default_parameters": {
|
|
||||||
"NMP": {
|
|
||||||
"unit": "毫升",
|
|
||||||
"density": "1.03",
|
|
||||||
"densityUnit": "g/mL",
|
|
||||||
"description": "N-甲基吡咯烷酮 (N-Methyl-2-pyrrolidone)"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"material_type_parameters": {}
|
"protocol_type": []
|
||||||
},
|
},
|
||||||
"deck": {
|
|
||||||
"data": {
|
|
||||||
"_resource_child_name": "Bioyond_Dispensing_Deck",
|
|
||||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"size_x": 2700.0,
|
|
||||||
"size_y": 1080.0,
|
|
||||||
"size_z": 1500.0,
|
|
||||||
"protocol_type": [],
|
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,200 +14,60 @@
|
|||||||
],
|
],
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "reaction_station.bioyond",
|
"class": "reaction_station.bioyond",
|
||||||
"position": {
|
"position": {"x": 0, "y": 3800, "z": 0},
|
||||||
"x": 0,
|
|
||||||
"y": 1100,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
"config": {
|
||||||
"api_key": "YOUR_API_KEY",
|
"config": {
|
||||||
"api_host": "http://your-api-host:port",
|
"api_key": "DE9BDDA0",
|
||||||
"workflow_mappings": {
|
"api_host": "http://192.168.1.200:44402",
|
||||||
"reactor_taken_out": "workflow-uuid-reactor-out",
|
"workflow_mappings": {
|
||||||
"reactor_taken_in": "workflow-uuid-reactor-in",
|
"reactor_taken_out": "3a16081e-4788-ca37-eff4-ceed8d7019d1",
|
||||||
"Solid_feeding_vials": "workflow-uuid-solid-vials",
|
"reactor_taken_in": "3a160df6-76b3-0957-9eb0-cb496d5721c6",
|
||||||
"Liquid_feeding_vials(non-titration)": "workflow-uuid-liquid-vials",
|
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
|
||||||
"Liquid_feeding_solvents": "workflow-uuid-solvents",
|
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
|
||||||
"Liquid_feeding(titration)": "workflow-uuid-titration",
|
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
|
||||||
"liquid_feeding_beaker": "workflow-uuid-beaker",
|
"Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6",
|
||||||
"Drip_back": "workflow-uuid-drip-back"
|
"liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
||||||
},
|
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||||
"material_type_mappings": {
|
|
||||||
"BIOYOND_PolymerStation_Reactor": [
|
|
||||||
"反应器",
|
|
||||||
"uuid-placeholder-reactor"
|
|
||||||
],
|
|
||||||
"BIOYOND_PolymerStation_1BottleCarrier": [
|
|
||||||
"试剂瓶",
|
|
||||||
"uuid-placeholder-bottle"
|
|
||||||
],
|
|
||||||
"BIOYOND_PolymerStation_1FlaskCarrier": [
|
|
||||||
"烧杯",
|
|
||||||
"uuid-placeholder-beaker"
|
|
||||||
],
|
|
||||||
"BIOYOND_PolymerStation_6StockCarrier": [
|
|
||||||
"样品板",
|
|
||||||
"uuid-placeholder-sample-plate"
|
|
||||||
],
|
|
||||||
"BIOYOND_PolymerStation_Solid_Vial": [
|
|
||||||
"90%分装小瓶",
|
|
||||||
"uuid-placeholder-solid-vial"
|
|
||||||
],
|
|
||||||
"BIOYOND_PolymerStation_Liquid_Vial": [
|
|
||||||
"10%分装小瓶",
|
|
||||||
"uuid-placeholder-liquid-vial"
|
|
||||||
],
|
|
||||||
"BIOYOND_PolymerStation_TipBox": [
|
|
||||||
"枪头盒",
|
|
||||||
"uuid-placeholder-tipbox"
|
|
||||||
],
|
|
||||||
"BIOYOND_PolymerStation_Measurement_Vial": [
|
|
||||||
"测量小瓶",
|
|
||||||
"uuid-placeholder-measure-vial"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"warehouse_mapping": {
|
|
||||||
"堆栈1左": {
|
|
||||||
"uuid": "uuid-placeholder-stack1-left",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "uuid-placeholder-site-A01",
|
|
||||||
"A02": "uuid-placeholder-site-A02",
|
|
||||||
"A03": "uuid-placeholder-site-A03",
|
|
||||||
"A04": "uuid-placeholder-site-A04",
|
|
||||||
"B01": "uuid-placeholder-site-B01",
|
|
||||||
"B02": "uuid-placeholder-site-B02",
|
|
||||||
"B03": "uuid-placeholder-site-B03",
|
|
||||||
"B04": "uuid-placeholder-site-B04",
|
|
||||||
"C01": "uuid-placeholder-site-C01",
|
|
||||||
"C02": "uuid-placeholder-site-C02",
|
|
||||||
"C03": "uuid-placeholder-site-C03",
|
|
||||||
"C04": "uuid-placeholder-site-C04",
|
|
||||||
"D01": "uuid-placeholder-site-D01",
|
|
||||||
"D02": "uuid-placeholder-site-D02",
|
|
||||||
"D03": "uuid-placeholder-site-D03",
|
|
||||||
"D04": "uuid-placeholder-site-D04"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"堆栈1右": {
|
"material_type_mappings": {
|
||||||
"uuid": "uuid-placeholder-stack1-right",
|
"BIOYOND_PolymerStation_Reactor": [
|
||||||
"site_uuids": {
|
"反应器",
|
||||||
"A05": "uuid-placeholder-site-A05",
|
"3a14233b-902d-0d7b-4533-3f60f1c41c1b"
|
||||||
"A06": "uuid-placeholder-site-A06",
|
],
|
||||||
"A07": "uuid-placeholder-site-A07",
|
"BIOYOND_PolymerStation_1BottleCarrier": [
|
||||||
"A08": "uuid-placeholder-site-A08",
|
"试剂瓶",
|
||||||
"B05": "uuid-placeholder-site-B05",
|
"3a14233b-56e3-6c53-a8ab-fcaac163a9ba"
|
||||||
"B06": "uuid-placeholder-site-B06",
|
],
|
||||||
"B07": "uuid-placeholder-site-B07",
|
"BIOYOND_PolymerStation_1FlaskCarrier": [
|
||||||
"B08": "uuid-placeholder-site-B08",
|
"烧杯",
|
||||||
"C05": "uuid-placeholder-site-C05",
|
"3a14233b-f0a9-ba84-eaa9-0d4718b361b6"
|
||||||
"C06": "uuid-placeholder-site-C06",
|
],
|
||||||
"C07": "uuid-placeholder-site-C07",
|
"BIOYOND_PolymerStation_6StockCarrier": [
|
||||||
"C08": "uuid-placeholder-site-C08",
|
"样品板",
|
||||||
"D05": "uuid-placeholder-site-D05",
|
"3a142339-80de-8f25-6093-1b1b1b6c322e"
|
||||||
"D06": "uuid-placeholder-site-D06",
|
],
|
||||||
"D07": "uuid-placeholder-site-D07",
|
"BIOYOND_PolymerStation_Solid_Vial": [
|
||||||
"D08": "uuid-placeholder-site-D08"
|
"90%分装小瓶",
|
||||||
}
|
"3a14233a-26e1-28f8-af6a-60ca06ba0165"
|
||||||
},
|
],
|
||||||
"站内试剂存放堆栈": {
|
"BIOYOND_PolymerStation_Liquid_Vial": [
|
||||||
"uuid": "uuid-placeholder-reagent-stack",
|
"10%分装小瓶",
|
||||||
"site_uuids": {
|
"3a14233a-84a3-088d-6676-7cb4acd57c64"
|
||||||
"A01": "uuid-placeholder-reagent-A01",
|
],
|
||||||
"A02": "uuid-placeholder-reagent-A02"
|
"BIOYOND_PolymerStation_TipBox": [
|
||||||
}
|
"枪头盒",
|
||||||
},
|
"3a143890-9d51-60ac-6d6f-6edb43c12041"
|
||||||
"测量小瓶仓库(测密度)": {
|
]
|
||||||
"uuid": "uuid-placeholder-density-stack",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "uuid-placeholder-density-A01",
|
|
||||||
"A02": "uuid-placeholder-density-A02",
|
|
||||||
"A03": "uuid-placeholder-density-A03",
|
|
||||||
"B01": "uuid-placeholder-density-B01",
|
|
||||||
"B02": "uuid-placeholder-density-B02",
|
|
||||||
"B03": "uuid-placeholder-density-B03"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"站内Tip盒堆栈(左)": {
|
|
||||||
"uuid": "uuid-placeholder-tipstack-left",
|
|
||||||
"site_uuids": {
|
|
||||||
"A02": "uuid-placeholder-tip-A02",
|
|
||||||
"A03": "uuid-placeholder-tip-A03",
|
|
||||||
"B02": "uuid-placeholder-tip-B02",
|
|
||||||
"B03": "uuid-placeholder-tip-B03"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"站内Tip盒堆栈(右)": {
|
|
||||||
"uuid": "uuid-placeholder-tipstack-right",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "uuid-placeholder-tip-A01",
|
|
||||||
"B01": "uuid-placeholder-tip-B01"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"workflow_to_section_map": {
|
"deck": {
|
||||||
"reactor_taken_in": "反应器放入",
|
"data": {
|
||||||
"reactor_taken_out": "反应器取出",
|
"_resource_child_name": "Bioyond_Deck",
|
||||||
"Solid_feeding_vials": "固体投料-小瓶",
|
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerReactionStation_Deck"
|
||||||
"Liquid_feeding_vials(non-titration)": "液体投料-小瓶(非滴定)",
|
|
||||||
"Liquid_feeding_solvents": "液体投料-溶剂",
|
|
||||||
"Liquid_feeding(titration)": "液体投料-滴定",
|
|
||||||
"liquid_feeding_beaker": "液体投料-烧杯",
|
|
||||||
"Drip_back": "液体回滴"
|
|
||||||
},
|
|
||||||
"action_names": {
|
|
||||||
"reactor_taken_in": {
|
|
||||||
"config": "通量-配置",
|
|
||||||
"stirring": "反应模块-开始搅拌"
|
|
||||||
},
|
|
||||||
"solid_feeding_vials": {
|
|
||||||
"feeding": "粉末加样模块-投料",
|
|
||||||
"observe": "反应模块-观察搅拌结果"
|
|
||||||
},
|
|
||||||
"liquid_feeding_vials_non_titration": {
|
|
||||||
"liquid": "稀释液瓶加液位-液体投料",
|
|
||||||
"observe": "反应模块-滴定结果观察"
|
|
||||||
},
|
|
||||||
"liquid_feeding_solvents": {
|
|
||||||
"liquid": "试剂AB放置位-试剂吸液分液",
|
|
||||||
"observe": "反应模块-观察搅拌结果"
|
|
||||||
},
|
|
||||||
"liquid_feeding_titration": {
|
|
||||||
"liquid": "稀释液瓶加液位-稀释液吸液分液",
|
|
||||||
"observe": "反应模块-滴定结果观察"
|
|
||||||
},
|
|
||||||
"liquid_feeding_beaker": {
|
|
||||||
"liquid": "烧杯溶液放置位-烧杯吸液分液",
|
|
||||||
"observe": "反应模块-观察搅拌结果"
|
|
||||||
},
|
|
||||||
"drip_back": {
|
|
||||||
"liquid": "试剂AB放置位-试剂吸液分液",
|
|
||||||
"observe": "反应模块-向下滴定结果观察"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http_service_config": {
|
"protocol_type": []
|
||||||
"http_service_host": "127.0.0.1",
|
|
||||||
"http_service_port": 8080
|
|
||||||
},
|
|
||||||
"material_default_parameters": {
|
|
||||||
"NMP": {
|
|
||||||
"unit": "毫升",
|
|
||||||
"density": "1.03",
|
|
||||||
"densityUnit": "g/mL",
|
|
||||||
"description": "N-甲基吡咯烷酮 (N-Methyl-2-pyrrolidone)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"material_type_parameters": {}
|
|
||||||
},
|
},
|
||||||
"deck": {
|
|
||||||
"data": {
|
|
||||||
"_resource_child_name": "Bioyond_Deck",
|
|
||||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerReactionStation_Deck"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"size_x": 2700.0,
|
|
||||||
"size_y": 1080.0,
|
|
||||||
"size_z": 2500.0,
|
|
||||||
"protocol_type": [],
|
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -217,11 +77,7 @@
|
|||||||
"parent": "reaction_station_bioyond",
|
"parent": "reaction_station_bioyond",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "reaction_station.reactor",
|
"class": "reaction_station.reactor",
|
||||||
"position": {
|
"position": {"x": 1150, "y": 380, "z": 0},
|
||||||
"x": 1150,
|
|
||||||
"y": 300,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {},
|
"config": {},
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
@@ -232,11 +88,7 @@
|
|||||||
"parent": "reaction_station_bioyond",
|
"parent": "reaction_station_bioyond",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "reaction_station.reactor",
|
"class": "reaction_station.reactor",
|
||||||
"position": {
|
"position": {"x": 1365, "y": 380, "z": 0},
|
||||||
"x": 1365,
|
|
||||||
"y": 300,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {},
|
"config": {},
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
@@ -247,11 +99,7 @@
|
|||||||
"parent": "reaction_station_bioyond",
|
"parent": "reaction_station_bioyond",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "reaction_station.reactor",
|
"class": "reaction_station.reactor",
|
||||||
"position": {
|
"position": {"x": 1580, "y": 380, "z": 0},
|
||||||
"x": 1580,
|
|
||||||
"y": 300,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {},
|
"config": {},
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
@@ -262,11 +110,7 @@
|
|||||||
"parent": "reaction_station_bioyond",
|
"parent": "reaction_station_bioyond",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "reaction_station.reactor",
|
"class": "reaction_station.reactor",
|
||||||
"position": {
|
"position": {"x": 1790, "y": 380, "z": 0},
|
||||||
"x": 1790,
|
|
||||||
"y": 300,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {},
|
"config": {},
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
@@ -277,11 +121,7 @@
|
|||||||
"parent": "reaction_station_bioyond",
|
"parent": "reaction_station_bioyond",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "reaction_station.reactor",
|
"class": "reaction_station.reactor",
|
||||||
"position": {
|
"position": {"x": 2010, "y": 380, "z": 0},
|
||||||
"x": 2010,
|
|
||||||
"y": 300,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {},
|
"config": {},
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
@@ -294,7 +134,7 @@
|
|||||||
"class": "BIOYOND_PolymerReactionStation_Deck",
|
"class": "BIOYOND_PolymerReactionStation_Deck",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 1100,
|
"y": 0,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
|||||||
@@ -192,6 +192,21 @@ def configure_logger(loglevel=None, working_dir=None):
|
|||||||
# 添加处理器到根日志记录器
|
# 添加处理器到根日志记录器
|
||||||
root_logger.addHandler(console_handler)
|
root_logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# 降低第三方库的日志级别,避免过多输出
|
||||||
|
# pymodbus 库的日志太详细,设置为 WARNING
|
||||||
|
logging.getLogger('pymodbus').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('pymodbus.logging').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('pymodbus.logging.base').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('pymodbus.logging.decoders').setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
# websockets 库的日志输出较多,设置为 WARNING
|
||||||
|
logging.getLogger('websockets').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('websockets.client').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('websockets.server').setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
# ROS 节点的状态更新日志过于频繁,设置为 INFO
|
||||||
|
logging.getLogger('unilabos.ros.nodes.presets.host_node').setLevel(logging.INFO)
|
||||||
|
|
||||||
# 如果指定了工作目录,添加文件处理器
|
# 如果指定了工作目录,添加文件处理器
|
||||||
if working_dir is not None:
|
if working_dir is not None:
|
||||||
logs_dir = os.path.join(working_dir, "logs")
|
logs_dir = os.path.join(working_dir, "logs")
|
||||||
|
|||||||
Reference in New Issue
Block a user