Merge branch 'workstation_dev_YB3' into workstation_dev_YB3

This commit is contained in:
Calvin Cao
2025-10-24 11:44:04 +08:00
committed by GitHub
2 changed files with 171 additions and 14 deletions

View File

@@ -12,6 +12,7 @@ import threading
import json import json
from urllib3 import response from urllib3 import response
from unilabos.devices.workstation.workstation_base import WorkstationBase
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer
from unilabos.devices.workstation.bioyond_studio.config import ( from unilabos.devices.workstation.bioyond_studio.config import (
API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS
@@ -796,20 +797,170 @@ class BioyondCellWorkstation(BioyondWorkstation):
"warningQuantity": data.get("warningQuantity", ""), "warningQuantity": data.get("warningQuantity", ""),
"details": data.get("details", []) "details": data.get("details", [])
} }
logger.info(f"正在创建第 {i}/{total} 个固体物料: {name}")
result = self._post_lims("/api/lims/storage/material", material_data)
if result and result.get("code") == 1:
# data 字段可能是字符串物料ID或字典包含id字段
data = result.get("data")
if isinstance(data, str):
# data 直接是物料ID字符串
material_id = data
elif isinstance(data, dict):
# data 是字典包含id字段
material_id = data.get("id")
else:
material_id = None
if material_id:
created_materials.append({
"name": name,
"materialId": material_id,
"typeId": type_id
})
logger.info(f"✓ 成功创建物料: {name}, ID: {material_id}")
else:
logger.error(f"✗ 创建物料失败: {name}, 未返回ID")
logger.error(f" 响应数据: {result}")
else:
error_msg = result.get("error") or result.get("message", "未知错误")
logger.error(f"✗ 创建物料失败: {name}")
logger.error(f" 错误信息: {error_msg}")
logger.error(f" 完整响应: {result}")
# 避免请求过快
time.sleep(0.3)
logger.info(f"物料创建完成,成功创建 {len(created_materials)}/{total} 个固体物料")
return created_materials
logger.info(f"正在 POST 创建物料: {name}") def _sync_materials_safe(self) -> bool:
"""仅使用 BioyondResourceSynchronizer 执行同步(与 station.py 保持一致)。"""
if hasattr(self, 'resource_synchronizer') and self.resource_synchronizer:
try: try:
# ✅ 真正执行 POST return bool(self.resource_synchronizer.sync_from_external())
result = self._post_lims("/api/lims/storage/material", data)
logger.info(f"响应: {result}")
except Exception as e: except Exception as e:
logger.error(f"✗ 创建物料失败: {name}, 错误: {e}") logger.error(f"同步失败: {e}")
results.append({name: {"error": str(e)}}) return False
time.sleep(0.3) # 避免请求过快 logger.warning("资源同步器未初始化")
return results return False
def _load_warehouse_locations(self, warehouse_name: str) -> tuple[List[str], List[str]]:
"""从配置加载仓库位置信息
Args:
warehouse_name: 仓库名称
Returns:
(location_ids, position_names) 元组
"""
warehouse_mapping = self.bioyond_config.get("warehouse_mapping", WAREHOUSE_MAPPING)
if warehouse_name not in warehouse_mapping:
raise ValueError(f"配置中未找到仓库: {warehouse_name}。可用: {list(warehouse_mapping.keys())}")
site_uuids = warehouse_mapping[warehouse_name].get("site_uuids", {})
if not site_uuids:
raise ValueError(f"仓库 {warehouse_name} 没有配置位置")
# 按顺序获取位置ID和名称
location_ids = []
position_names = []
for key in sorted(site_uuids.keys()):
location_ids.append(site_uuids[key])
position_names.append(key)
return location_ids, position_names
def create_and_inbound_materials(
self,
material_names: Optional[List[str]] = None,
type_id: str = "3a190ca0-b2f6-9aeb-8067-547e72c11469",
warehouse_name: str = "粉末加样头堆栈"
) -> Dict[str, Any]:
"""
传参与默认列表方式创建物料并入库不使用CSV
Args:
material_names: 物料名称列表;默认使用 [LiPF6, LiDFOB, DTD, LiFSI, LiPO2F2]
type_id: 物料类型ID
warehouse_name: 目标仓库名(用于取位置信息)
Returns:
执行结果字典
"""
logger.info("=" * 60)
logger.info(f"开始执行:从参数创建物料并批量入库到 {warehouse_name}")
logger.info("=" * 60)
try:
# 1) 准备物料名称(默认值)
default_materials = ["LiPF6", "LiDFOB", "DTD", "LiFSI", "LiPO2F2"]
mat_names = [m.strip() for m in (material_names or default_materials) if str(m).strip()]
if not mat_names:
return {"success": False, "error": "物料名称列表为空"}
# 2) 加载仓库位置信息
all_location_ids, position_names = self._load_warehouse_locations(warehouse_name)
logger.info(f"✓ 加载 {len(all_location_ids)} 个位置 ({position_names[0]} ~ {position_names[-1]})")
# 限制数量不超过可用位置
if len(mat_names) > len(all_location_ids):
logger.warning(f"物料数量超出位置数量,仅处理前 {len(all_location_ids)}")
mat_names = mat_names[:len(all_location_ids)]
# 3) 创建物料
logger.info(f"\n【步骤1/3】创建 {len(mat_names)} 个固体物料...")
created_materials = self.create_solid_materials(mat_names, type_id)
if not created_materials:
return {"success": False, "error": "没有成功创建任何物料"}
# 4) 批量入库
logger.info(f"\n【步骤2/3】批量入库物料...")
location_ids = all_location_ids[:len(created_materials)]
selected_positions = position_names[:len(created_materials)]
inbound_items = [
{"materialId": mat["materialId"], "locationId": loc_id}
for mat, loc_id in zip(created_materials, location_ids)
]
for material, position in zip(created_materials, selected_positions):
logger.info(f" - {material['name']}{position}")
result = self.storage_batch_inbound(inbound_items)
if result.get("code") != 1:
logger.error(f"✗ 批量入库失败: {result}")
return {"success": False, "error": "批量入库失败", "created_materials": created_materials, "inbound_result": result}
logger.info("✓ 批量入库成功")
# 5) 同步
logger.info(f"\n【步骤3/3】同步物料数据...")
if self._sync_materials_safe():
logger.info("✓ 物料数据同步完成")
else:
logger.warning("⚠ 物料数据同步未完成(可忽略,不影响已创建与入库的数据)")
logger.info("\n" + "=" * 60)
logger.info("流程完成")
logger.info("=" * 60 + "\n")
return {
"success": True,
"created_materials": created_materials,
"inbound_result": result,
"total_created": len(created_materials),
"total_inbound": len(inbound_items),
"warehouse": warehouse_name,
"positions": selected_positions
}
except Exception as e:
logger.error(f"✗ 执行失败: {e}")
return {"success": False, "error": str(e)}
# -------------------------------- # --------------------------------
@@ -824,7 +975,7 @@ if __name__ == "__main__":
for r in results: for r in results:
logger.info(r) logger.info(r)
# 从CSV文件读取物料列表并批量创建入库 # 从CSV文件读取物料列表并批量创建入库
# logger.info(ws.create_and_inbound_materials_from_csv()) result = ws.create_and_inbound_materials()
# 继续后续流程 # 继续后续流程
# logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱

View File

@@ -19,9 +19,9 @@ API_CONFIG = {
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
# HTTP 服务配置 # HTTP 服务配置
"HTTP_host": os.getenv("unilab_HTTP_HOST", "172.21.32.164"), # HTTP服务监听地址0.0.0.0 表示监听所有网络接口) "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址0.0.0.0 表示监听所有网络接口)
"HTTP_port": int(os.getenv("unilab_HTTP_PORT", "8080")), "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
"report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.172"), # 报送给 Bioyond 的本机IP地址留空则自动检测
# 调试模式 # 调试模式
"debug_mode": False, "debug_mode": False,
} }
@@ -57,7 +57,14 @@ WAREHOUSE_MAPPING = {
# 物料类型配置 # 物料类型配置
MATERIAL_TYPE_MAPPINGS = { MATERIAL_TYPE_MAPPINGS = {
"20ml分液瓶": ("YB_6x20ml_DispensingVialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), "烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
"样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
"90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
"10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
"20ml分液瓶": ("BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
"100ml液体": ("BIOYOND_PolymerStation_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"), "100ml液体": ("BIOYOND_PolymerStation_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
"": ("BIOYOND_PolymerStation_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), "": ("BIOYOND_PolymerStation_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
"高粘液": ("BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"), "高粘液": ("BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
@@ -73,7 +80,6 @@ MATERIAL_TYPE_MAPPINGS = {
"适配器块": ("BIOYOND_PolymerStation_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), "适配器块": ("BIOYOND_PolymerStation_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"),
"枪头盒": ("BIOYOND_PolymerStation_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), "枪头盒": ("BIOYOND_PolymerStation_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"),
"枪头": ("BIOYOND_PolymerStation_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"), "枪头": ("BIOYOND_PolymerStation_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"),
# YB信息
} }
SOLID_LIQUID_MAPPINGS = { SOLID_LIQUID_MAPPINGS = {