mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-08 07:55:12 +00:00
Compare commits
32 Commits
feat/unila
...
42f7010134
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42f7010134 | ||
|
|
190b2d2518 | ||
|
|
2901d72b4b | ||
|
|
6ad0157b50 | ||
|
|
55b678cd37 | ||
|
|
8101a22a0f | ||
|
|
667138baac | ||
|
|
01adf7ca92 | ||
|
|
f606062696 | ||
|
|
67d1c4acce | ||
|
|
7206e42bf1 | ||
|
|
e92d933968 | ||
|
|
f0ebcc60bb | ||
|
|
e2097f0b22 | ||
|
|
fd73731130 | ||
|
|
ab7f2081c9 | ||
|
|
9e850d8a81 | ||
|
|
1af6ffafc6 | ||
|
|
35fc2f5ea6 | ||
|
|
d3d8ba6500 | ||
|
|
5a7845d8ca | ||
|
|
9c4d0256cf | ||
|
|
de7c80c3c2 | ||
|
|
e70c545ec8 | ||
|
|
2c2d1e5569 | ||
|
|
4638611fe7 | ||
|
|
37641c4389 | ||
|
|
ab697ce973 | ||
|
|
d4724b8664 | ||
|
|
2f25063bf1 | ||
|
|
00b4b9cd87 | ||
|
|
d2352cc514 |
14486
bioyond_yihua_YB.json
Normal file
14486
bioyond_yihua_YB.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -24,13 +24,13 @@
|
||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||
},
|
||||
"material_type_mappings": {
|
||||
"烧杯": ["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"]
|
||||
"烧杯": ["YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"],
|
||||
"试剂瓶": ["YB_1BottleCarrier", ""],
|
||||
"样品板": ["YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"],
|
||||
"分装板": ["YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"],
|
||||
"样品瓶": ["YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"],
|
||||
"90%分装小瓶": ["YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"],
|
||||
"10%分装小瓶": ["YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"]
|
||||
}
|
||||
},
|
||||
"deck": {
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||
},
|
||||
"material_type_mappings": {
|
||||
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
|
||||
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
|
||||
"样品板": "BIOYOND_PolymerStation_6VialCarrier"
|
||||
"烧杯": "YB_1FlaskCarrier",
|
||||
"试剂瓶": "YB_1BottleCarrier",
|
||||
"样品板": "YB_6VialCarrier"
|
||||
}
|
||||
},
|
||||
"deck": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier
|
||||
from unilabos.resources.bioyond.bottles import BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Solution_Beaker, BIOYOND_PolymerStation_Reagent_Bottle
|
||||
from unilabos.resources.bioyond.bottles import YB_Solid_Vial, YB_Solution_Beaker, YB_Reagent_Bottle
|
||||
|
||||
|
||||
def test_bottle_carrier() -> "BottleCarrier":
|
||||
@@ -16,9 +16,9 @@ def test_bottle_carrier() -> "BottleCarrier":
|
||||
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
|
||||
|
||||
# 创建瓶子和烧杯
|
||||
powder_bottle = BIOYOND_PolymerStation_Solid_Vial("powder_bottle_01")
|
||||
solution_beaker = BIOYOND_PolymerStation_Solution_Beaker("solution_beaker_01")
|
||||
reagent_bottle = BIOYOND_PolymerStation_Reagent_Bottle("reagent_bottle_01")
|
||||
powder_bottle = YB_Solid_Vial("powder_bottle_01")
|
||||
solution_beaker = YB_Solution_Beaker("solution_beaker_01")
|
||||
reagent_bottle = YB_Reagent_Bottle("reagent_bottle_01")
|
||||
|
||||
print(f"\n创建的物料:")
|
||||
print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL")
|
||||
|
||||
@@ -12,13 +12,13 @@ lab_registry.setup()
|
||||
|
||||
|
||||
type_mapping = {
|
||||
"烧杯": ("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"),
|
||||
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("YB_1BottleCarrier", ""),
|
||||
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ lab_registry.setup()
|
||||
|
||||
|
||||
type_mapping = {
|
||||
"烧杯": ("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"),
|
||||
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("YB_1BottleCarrier", ""),
|
||||
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,715 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime, timezone
|
||||
import requests
|
||||
from pathlib import Path
|
||||
import pandas as pd
|
||||
import time
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import re
|
||||
import threading
|
||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
||||
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
||||
from unilabos.utils.log import logger
|
||||
from pylabrobot.resources.deck import Deck
|
||||
|
||||
|
||||
def _iso_utc_now_ms() -> str:
|
||||
# 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z
|
||||
dt = datetime.now(timezone.utc)
|
||||
return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(dt.microsecond/1000):03d}Z"
|
||||
|
||||
|
||||
class BioyondWorkstation(WorkstationBase):
|
||||
"""
|
||||
集成 Bioyond LIMS 的工作站示例,
|
||||
覆盖:入库(2.17/2.18) → 新建实验(2.14) → 启动调度(2.7) →
|
||||
运行中推送:物料变更(2.24)、步骤完成(2.21)、订单完成(2.23) →
|
||||
查询实验(2.5/2.6) → 3-2-1 转运(2.32) → 样品/废料取出(2.28)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bioyond_config: Optional[Dict[str, Any]] = None,
|
||||
station_resource: Optional[Dict[str, Any]] = None,
|
||||
debug_mode: bool = False, # 增加调试模式开关
|
||||
*args, **kwargs,
|
||||
):
|
||||
self.bioyond_config = bioyond_config or {
|
||||
"base_url": "http://192.168.1.200:44386",
|
||||
"api_key": "8A819E5C",
|
||||
"timeout": 30,
|
||||
"report_token": "CHANGE_ME_TOKEN"
|
||||
}
|
||||
|
||||
self.http_service_started = False
|
||||
self.debug_mode = debug_mode
|
||||
super().__init__(deck=Deck, station_resource=station_resource, *args, **kwargs)
|
||||
logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})")
|
||||
|
||||
# 实例化并在后台线程启动 HTTP 报送服务
|
||||
self.order_status = {}
|
||||
try:
|
||||
t = threading.Thread(target=self._start_http_service_bg, daemon=True, name="unilab_http")
|
||||
t.start()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"unilab-server后台启动报送服务失败: {e}")
|
||||
|
||||
@property
|
||||
def device_id(self) -> str:
|
||||
try:
|
||||
return getattr(self, "_ros_node").device_id # 兼容 ROS 场景
|
||||
except Exception:
|
||||
return "bioyond_workstation"
|
||||
|
||||
def _start_http_service_bg(self, host: str = "192.168.1.104", port: int = 8080) -> None:
|
||||
logger.info("进入 _start_http_service_bg 函数")
|
||||
try:
|
||||
self.service = WorkstationHTTPService(self, host=host, port=port)
|
||||
logger.info("WorkstationHTTPService 实例化完成")
|
||||
self.service.start()
|
||||
self.http_service_started = True
|
||||
logger.info(f"unilab_HTTP 服务成功启动: {host}:{port}")
|
||||
|
||||
#一直挂着,直到进程退出
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
self.http_service_started = False
|
||||
logger.error(f"启动unilab_HTTP服务失败: {e}", exc_info=True)
|
||||
|
||||
# -------------------- 基础HTTP封装 --------------------
|
||||
def _url(self, path: str) -> str:
|
||||
return f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}"
|
||||
|
||||
def _post_lims(self, path: str, data: Optional[Any] = None) -> Dict[str, Any]:
|
||||
"""LIMS API:大多数接口用 {apiKey/requestTime,data} 包装"""
|
||||
payload = {
|
||||
"apiKey": self.bioyond_config["api_key"],
|
||||
"requestTime": _iso_utc_now_ms()
|
||||
}
|
||||
if data is not None:
|
||||
payload["data"] = data
|
||||
|
||||
if self.debug_mode:
|
||||
# 模拟返回,不发真实请求
|
||||
logger.info(f"[DEBUG] POST {path} with payload={payload}")
|
||||
return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"}
|
||||
|
||||
try:
|
||||
r = requests.post(
|
||||
self._url(path),
|
||||
json=payload,
|
||||
timeout=self.bioyond_config.get("timeout", 30),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
logger.error(f"POST {path} 失败: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
# --- 修正:_post_report / _post_report_raw 同样走 debug_mode ---
|
||||
def _post_report(self, path: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
payload = {
|
||||
"token": self.bioyond_config.get("report_token", ""),
|
||||
"request_time": _iso_utc_now_ms(),
|
||||
"data": data
|
||||
}
|
||||
if self.debug_mode:
|
||||
logger.info(f"[DEBUG] POST {path} with payload={payload}")
|
||||
return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"}
|
||||
try:
|
||||
r = requests.post(self._url(path), json=payload,
|
||||
timeout=self.bioyond_config.get("timeout", 30),
|
||||
headers={"Content-Type": "application/json"})
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
logger.error(f"POST {path} 失败: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def _post_report_raw(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if self.debug_mode:
|
||||
logger.info(f"[DEBUG] POST {path} with body={body}")
|
||||
return {"debug": True, "url": self._url(path), "payload": body, "status": "ok"}
|
||||
try:
|
||||
r = requests.post(self._url(path), json=body,
|
||||
timeout=self.bioyond_config.get("timeout", 30),
|
||||
headers={"Content-Type": "application/json"})
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
logger.error(f"POST {path} 失败: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
# -------------------- 单点接口封装 --------------------
|
||||
# 2.17 入库物料(单个)
|
||||
def storage_inbound(self, material_id: str, location_id: str) -> Dict[str, Any]:
|
||||
return self._post_lims("/api/lims/storage/inbound", {
|
||||
"materialId": material_id,
|
||||
"locationId": location_id
|
||||
})
|
||||
|
||||
# 2.18 批量入库(多个)
|
||||
def storage_batch_inbound(self, items: List[Dict[str, str]]) -> Dict[str, Any]:
|
||||
"""
|
||||
items = [{"materialId": "...", "locationId": "..."}, ...]
|
||||
"""
|
||||
return self._post_lims("/api/lims/storage/batch-inbound", items)
|
||||
|
||||
# 3.30 自动化上料(Excel -> JSON -> POST /api/lims/order/auto-feeding4to3)
|
||||
def auto_feeding4to3_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
根据固定模板解析 Excel:
|
||||
- 四号手套箱加样头面 (2-13行, 3-7列)
|
||||
- 四号手套箱原液瓶面 (15-23行, 3-9列)
|
||||
- 三号手套箱人工堆栈 (26-40行, 3-7列)
|
||||
"""
|
||||
path = Path(xlsx_path)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"未找到 Excel 文件:{path}")
|
||||
|
||||
try:
|
||||
df = pd.read_excel(path, sheet_name=0, header=None, engine="openpyxl")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"读取 Excel 失败:{e}")
|
||||
|
||||
items: List[Dict[str, Any]] = []
|
||||
|
||||
# 四号手套箱 - 加样头面(2-13行, 3-7列)
|
||||
for _, row in df.iloc[1:13, 2:7].iterrows():
|
||||
item = {
|
||||
"sourceWHName": "四号手套箱堆栈",
|
||||
"posX": int(row[2]),
|
||||
"posY": int(row[3]),
|
||||
"posZ": int(row[4]),
|
||||
"materialName": str(row[5]).strip() if pd.notna(row[5]) else "",
|
||||
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
||||
}
|
||||
if item["materialName"]:
|
||||
items.append(item)
|
||||
|
||||
# 四号手套箱 - 原液瓶面(15-23行, 3-9列)
|
||||
for _, row in df.iloc[14:23, 2:9].iterrows():
|
||||
item = {
|
||||
"sourceWHName": "四号手套箱堆栈",
|
||||
"posX": int(row[2]),
|
||||
"posY": int(row[3]),
|
||||
"posZ": int(row[4]),
|
||||
"materialName": str(row[5]).strip() if pd.notna(row[5]) else "",
|
||||
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
||||
"materialType": str(row[7]).strip() if pd.notna(row[7]) else "",
|
||||
"targetWH": str(row[8]).strip() if pd.notna(row[8]) else "",
|
||||
}
|
||||
if item["materialName"]:
|
||||
items.append(item)
|
||||
|
||||
# 三号手套箱人工堆栈(26-40行, 3-7列)
|
||||
for _, row in df.iloc[25:40, 2:7].iterrows():
|
||||
item = {
|
||||
"sourceWHName": "三号手套箱人工堆栈",
|
||||
"posX": int(row[2]),
|
||||
"posY": int(row[3]),
|
||||
"posZ": int(row[4]),
|
||||
"materialType": str(row[5]).strip() if pd.notna(row[5]) else "",
|
||||
"materialId": str(row[6]).strip() if pd.notna(row[6]) else "",
|
||||
"quantity": 1 # 默认数量1
|
||||
}
|
||||
if item["materialId"] or item["materialType"]:
|
||||
items.append(item)
|
||||
|
||||
return self._post_lims("/api/lims/order/auto-feeding4to3", items)
|
||||
|
||||
|
||||
|
||||
def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
3.31 自动化下料(Excel -> JSON -> POST /api/lims/storage/auto-batch-out-bound)
|
||||
"""
|
||||
path = Path(xlsx_path)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"未找到 Excel 文件:{path}")
|
||||
|
||||
try:
|
||||
df = pd.read_excel(path, sheet_name=0, engine="openpyxl")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"读取 Excel 失败:{e}")
|
||||
|
||||
def pick(names: List[str]) -> Optional[str]:
|
||||
for n in names:
|
||||
if n in df.columns:
|
||||
return n
|
||||
return None
|
||||
|
||||
c_loc = pick(["locationId", "库位ID", "库位Id", "库位id"])
|
||||
c_wh = pick(["warehouseId", "仓库ID", "仓库Id", "仓库id"])
|
||||
c_qty = pick(["数量", "quantity"])
|
||||
c_x = pick(["x", "X", "posX", "坐标X"])
|
||||
c_y = pick(["y", "Y", "posY", "坐标Y"])
|
||||
c_z = pick(["z", "Z", "posZ", "坐标Z"])
|
||||
|
||||
required = [c_loc, c_wh, c_qty, c_x, c_y, c_z]
|
||||
if any(c is None for c in required):
|
||||
raise KeyError("Excel 缺少必要列:locationId/warehouseId/数量/x/y/z(支持多别名,至少要能匹配到)。")
|
||||
|
||||
def as_int(v, d=0):
|
||||
try:
|
||||
if pd.isna(v): return d
|
||||
return int(v)
|
||||
except Exception:
|
||||
try:
|
||||
return int(float(v))
|
||||
except Exception:
|
||||
return d
|
||||
|
||||
def as_float(v, d=0.0):
|
||||
try:
|
||||
if pd.isna(v): return d
|
||||
return float(v)
|
||||
except Exception:
|
||||
return d
|
||||
|
||||
def as_str(v, d=""):
|
||||
if v is None or (isinstance(v, float) and pd.isna(v)): return d
|
||||
s = str(v).strip()
|
||||
return s if s else d
|
||||
|
||||
items: List[Dict[str, Any]] = []
|
||||
for _, row in df.iterrows():
|
||||
items.append({
|
||||
"locationId": as_str(row[c_loc]),
|
||||
"warehouseId": as_str(row[c_wh]),
|
||||
"quantity": as_float(row[c_qty]),
|
||||
"x": as_int(row[c_x]),
|
||||
"y": as_int(row[c_y]),
|
||||
"z": as_int(row[c_z]),
|
||||
})
|
||||
|
||||
return self._post_lims("/api/lims/storage/auto-batch-out-bound", items)
|
||||
|
||||
# 2.14 新建实验
|
||||
def create_orders(self, xlsx_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
从 Excel 解析并创建实验(2.14)
|
||||
约定:
|
||||
- batchId = Excel 文件名(不含扩展名)
|
||||
- 物料列:所有以 "(g)" 结尾(不再读取“总质量(g)”列)
|
||||
- totalMass 自动计算为所有物料质量之和
|
||||
- createTime 缺失或为空时自动填充为当前日期(YYYY/M/D)
|
||||
"""
|
||||
path = Path(xlsx_path)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"未找到 Excel 文件:{path}")
|
||||
|
||||
try:
|
||||
df = pd.read_excel(path, sheet_name=0, engine="openpyxl")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"读取 Excel 失败:{e}")
|
||||
|
||||
# 列名容错:返回可选列名,找不到则返回 None
|
||||
def _pick(col_names: List[str]) -> Optional[str]:
|
||||
for c in col_names:
|
||||
if c in df.columns:
|
||||
return c
|
||||
return None
|
||||
|
||||
col_order_name = _pick(["配方ID", "orderName", "订单编号"])
|
||||
col_create_time = _pick(["创建日期", "createTime"])
|
||||
col_bottle_type = _pick(["配液瓶类型", "bottleType"])
|
||||
col_mix_time = _pick(["混匀时间(s)", "mixTime"])
|
||||
col_load = _pick(["扣电组装分液体积", "loadSheddingInfo"])
|
||||
col_pouch = _pick(["软包组装分液体积", "pouchCellInfo"])
|
||||
col_cond = _pick(["电导测试分液体积", "conductivityInfo"])
|
||||
col_cond_cnt = _pick(["电导测试分液瓶数", "conductivityBottleCount"])
|
||||
|
||||
# 物料列:所有以 (g) 结尾
|
||||
material_cols = [c for c in df.columns if isinstance(c, str) and c.endswith("(g)")]
|
||||
if not material_cols:
|
||||
raise KeyError("未发现任何以“(g)”结尾的物料列,请检查表头。")
|
||||
|
||||
batch_id = path.stem
|
||||
|
||||
def _to_ymd_slash(v) -> str:
|
||||
# 统一为 "YYYY/M/D";为空或解析失败则用当前日期
|
||||
if v is None or (isinstance(v, float) and pd.isna(v)) or str(v).strip() == "":
|
||||
ts = datetime.now()
|
||||
else:
|
||||
try:
|
||||
ts = pd.to_datetime(v)
|
||||
except Exception:
|
||||
ts = datetime.now()
|
||||
return f"{ts.year}/{ts.month}/{ts.day}"
|
||||
|
||||
def _as_int(val, default=0) -> int:
|
||||
try:
|
||||
if pd.isna(val):
|
||||
return default
|
||||
return int(val)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def _as_str(val, default="") -> str:
|
||||
if val is None or (isinstance(val, float) and pd.isna(val)):
|
||||
return default
|
||||
s = str(val).strip()
|
||||
return s if s else default
|
||||
|
||||
orders: List[Dict[str, Any]] = []
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
mats: List[Dict[str, Any]] = []
|
||||
total_mass = 0.0
|
||||
|
||||
for mcol in material_cols:
|
||||
val = row.get(mcol, None)
|
||||
if val is None or (isinstance(val, float) and pd.isna(val)):
|
||||
continue
|
||||
try:
|
||||
mass = float(val)
|
||||
except Exception:
|
||||
continue
|
||||
if mass > 0:
|
||||
mats.append({"name": mcol.replace("(g)", ""), "mass": mass})
|
||||
total_mass += mass
|
||||
|
||||
order_data = {
|
||||
"batchId": batch_id,
|
||||
"orderName": _as_str(row[col_order_name], default=f"{batch_id}_order_{idx+1}") if col_order_name else f"{batch_id}_order_{idx+1}",
|
||||
"createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None),
|
||||
"bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶",
|
||||
"mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0,
|
||||
"loadSheddingInfo": _as_int(row[col_load]) if col_load else 0,
|
||||
"pouchCellInfo": _as_int(row[col_pouch]) if col_pouch else 0,
|
||||
"conductivityInfo": _as_int(row[col_cond]) if col_cond else 0,
|
||||
"conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0,
|
||||
"materialInfos": mats,
|
||||
"totalMass": round(total_mass, 4) # 自动汇总
|
||||
}
|
||||
orders.append(order_data)
|
||||
|
||||
# print(orders)
|
||||
|
||||
response = self._post_lims("/api/lims/order/orders", orders)
|
||||
self.order_status[response["data"]["orderCode"]] = "running"
|
||||
|
||||
while True:
|
||||
time.sleep(5)
|
||||
if self.order_status.get(response["data"]["orderCode"], None) == "finished":
|
||||
logger.info(f"配液实验已完成 ,即将执行 3-2-1 转运")
|
||||
break
|
||||
logger.info(f"等待配液实验完成")
|
||||
|
||||
self.transfer_3_to_2_to_1()
|
||||
r321 = self.wait_for_transfer_task()
|
||||
logger.info(f"3-2-1 转运完成,返回结果")
|
||||
return r321
|
||||
|
||||
|
||||
# 2.7 启动调度
|
||||
def scheduler_start(self) -> Dict[str, Any]:
|
||||
return self._post_lims("/api/lims/scheduler/start")
|
||||
# 3.10 停止调度
|
||||
def scheduler_stop(self) -> Dict[str, Any]:
|
||||
"""
|
||||
停止调度 (3.10)
|
||||
请求体只包含 apiKey 和 requestTime
|
||||
"""
|
||||
return self._post_lims("/api/lims/scheduler/stop")
|
||||
# 2.9 继续调度
|
||||
def scheduler_continue(self) -> Dict[str, Any]:
|
||||
"""
|
||||
继续调度 (2.9)
|
||||
请求体只包含 apiKey 和 requestTime
|
||||
"""
|
||||
return self._post_lims("/api/lims/scheduler/continue")
|
||||
|
||||
|
||||
|
||||
# 2.24 物料变更推送
|
||||
def report_material_change(self, material_obj: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
material_obj 按 2.24 的裸对象格式(包含 id/typeName/locations/detail 等)
|
||||
"""
|
||||
return self._post_report_raw("/report/material_change", material_obj)
|
||||
|
||||
# 2.21 步骤完成推送(BS → LIMS)
|
||||
def report_step_finish(self,
|
||||
order_code: str,
|
||||
order_name: str,
|
||||
step_name: str,
|
||||
step_id: str,
|
||||
sample_id: str,
|
||||
start_time: str,
|
||||
end_time: str,
|
||||
execution_status: str = "completed") -> Dict[str, Any]:
|
||||
data = {
|
||||
"orderCode": order_code,
|
||||
"orderName": order_name,
|
||||
"stepName": step_name,
|
||||
"stepId": step_id,
|
||||
"sampleId": sample_id,
|
||||
"startTime": start_time,
|
||||
"endTime": end_time,
|
||||
"executionStatus": execution_status
|
||||
}
|
||||
return self._post_report("/report/step_finish", data)
|
||||
|
||||
# 2.23 订单完成推送(BS → LIMS)
|
||||
def report_order_finish(self,
|
||||
order_code: str,
|
||||
order_name: str,
|
||||
start_time: str,
|
||||
end_time: str,
|
||||
status: str = "30", # 30 完成 / -11 异常停止 / -12 人工停止
|
||||
workflow_status: str = "Finished",
|
||||
completion_time: Optional[str] = None,
|
||||
used_materials: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]:
|
||||
data = {
|
||||
"orderCode": order_code,
|
||||
"orderName": order_name,
|
||||
"startTime": start_time,
|
||||
"endTime": end_time,
|
||||
"status": status,
|
||||
"workflowStatus": workflow_status,
|
||||
"completionTime": completion_time or end_time,
|
||||
"usedMaterials": used_materials or []
|
||||
}
|
||||
return self._post_report("/report/order_finish", data)
|
||||
|
||||
# 2.5 批量查询实验报告(用于轮询是否完成)
|
||||
def order_list(self,
|
||||
status: Optional[str] = None,
|
||||
begin_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None,
|
||||
filter_text: Optional[str] = None,
|
||||
skip: int = 0, page: int = 10) -> Dict[str, Any]:
|
||||
data: Dict[str, Any] = {"skipCount": skip, "pageCount": page}
|
||||
if status is not None: # 80 成功 / 90 失败 / 100 执行中
|
||||
data["status"] = status
|
||||
if begin_time:
|
||||
data["timeType"] = "CreationTime"
|
||||
data["beginTime"] = begin_time
|
||||
if end_time:
|
||||
data["endTime"] = end_time
|
||||
if filter_text:
|
||||
data["filter"] = filter_text
|
||||
return self._post_lims("/api/lims/order/order-list", data)
|
||||
|
||||
# 2.6 实验报告查询(根据任务ID拿详情)
|
||||
def order_report(self, order_id: str) -> Dict[str, Any]:
|
||||
return self._post_lims("/api/lims/order/order-report", order_id)
|
||||
|
||||
# 2.32 3-2-1 物料转运
|
||||
def transfer_3_to_2_to_1(self,
|
||||
# source_wh_id: Optional[str] = None,
|
||||
source_wh_id: Optional[str] = '3a19debc-84b4-0359-e2d4-b3beea49348b',
|
||||
source_x: int = 1, source_y: int = 1, source_z: int = 1) -> Dict[str, Any]:
|
||||
payload: Dict[str, Any] = {
|
||||
"sourcePosX": source_x, "sourcePosY": source_y, "sourcePosZ": source_z
|
||||
}
|
||||
if source_wh_id:
|
||||
payload["sourceWHID"] = source_wh_id
|
||||
return self._post_lims("/api/lims/order/transfer-task3To2To1", payload)
|
||||
|
||||
# 2.28 样品/废料取出
|
||||
def take_out(self,
|
||||
order_id: str,
|
||||
preintake_ids: Optional[List[str]] = None,
|
||||
material_ids: Optional[List[str]] = None) -> Dict[str, Any]:
|
||||
data = {
|
||||
"orderId": order_id,
|
||||
"preintakeIds": preintake_ids or [],
|
||||
"materialIds": material_ids or []
|
||||
}
|
||||
return self._post_lims("/api/lims/order/take-out", data)
|
||||
|
||||
# --------(可选)占位方法:文档未定义的“1号站内部流程 / 1-2转运”--------
|
||||
def start_station1_internal_flow(self, **kwargs) -> None:
|
||||
logger.info("启动1号站内部流程(占位,按现场系统填充具体指令)")
|
||||
|
||||
|
||||
# 3.x 1→2 物料转运
|
||||
def transfer_1_to_2(self) -> Dict[str, Any]:
|
||||
"""
|
||||
1→2 物料转运
|
||||
URL: /api/lims/order/transfer-task1To2
|
||||
只需要 apiKey 和 requestTime
|
||||
"""
|
||||
return self._post_lims("/api/lims/order/transfer-task1To2")
|
||||
|
||||
|
||||
# -------------------- 整体编排 --------------------
|
||||
def run_full_workflow(self,
|
||||
inbound_items: List[Dict[str, str]],
|
||||
orders: List[Dict[str, Any]],
|
||||
poll_filter_code: Optional[str] = None,
|
||||
poll_timeout_s: int = 600,
|
||||
poll_interval_s: int = 5,
|
||||
transfer_source: Optional[Dict[str, Any]] = None,
|
||||
takeout_order_id: Optional[str] = None) -> None:
|
||||
"""
|
||||
一键串联:
|
||||
1) 入库 3-4 个物料 → 2) 新建实验 → 3) 启动调度
|
||||
运行中(如需):4) 物料变更推送 5) 步骤完成推送 6) 订单完成推送
|
||||
完成后:查询实验(2.5/2.6)→ 7) 3-2-1 转运 → 8) 1号站内部流程
|
||||
→ 9) 1-2 转运 → 10) 样品/废料取出
|
||||
"""
|
||||
# 1. 入库(多于1个就用批量接口 2.18)
|
||||
if len(inbound_items) == 1:
|
||||
r = self.storage_inbound(inbound_items[0]["materialId"], inbound_items[0]["locationId"])
|
||||
logger.info(f"单个入库结果: {r}")
|
||||
else:
|
||||
r = self.storage_batch_inbound(inbound_items)
|
||||
logger.info(f"批量入库结果: {r}")
|
||||
|
||||
# 2. 新建实验(2.14)
|
||||
r = self.create_orders(orders)
|
||||
logger.info(f"新建实验结果: {r}")
|
||||
|
||||
# 3. 启动调度(2.7)
|
||||
r = self.scheduler_start()
|
||||
logger.info(f"启动调度结果: {r}")
|
||||
|
||||
# —— 运行中各类推送(2.24 / 2.21 / 2.23),通常由实际任务驱动,这里提供调用方式 —— #
|
||||
# self.report_material_change({...})
|
||||
# self.report_step_finish(order_code="BSO...", order_name="配液分液", step_name="xxx", step_id="...", sample_id="...",
|
||||
# start_time=_iso_utc_now_ms(), end_time=_iso_utc_now_ms(), execution_status="completed")
|
||||
# self.report_order_finish(order_code="BSO...", order_name="配液分液", start_time="...", end_time=_iso_utc_now_ms())
|
||||
|
||||
# 完成后才能转运:用 2.5 批量查询配合 filter=任务编码 轮询到 status=80(成功)
|
||||
if poll_filter_code:
|
||||
import time
|
||||
deadline = time.time() + poll_timeout_s
|
||||
while time.time() < deadline:
|
||||
res = self.order_list(status="80", filter_text=poll_filter_code, page=5)
|
||||
if isinstance(res, dict) and res.get("data", {}).get("items"):
|
||||
logger.info(f"实验 {poll_filter_code} 已完成:{res['data']['items'][0]}")
|
||||
break
|
||||
time.sleep(poll_interval_s)
|
||||
else:
|
||||
logger.warning(f"等待实验 {poll_filter_code} 完成超时(未到 status=80)")
|
||||
|
||||
# 7. 启动 3-2-1 转运(2.32)
|
||||
if transfer_source:
|
||||
r = self.transfer_3_to_2_to_1(
|
||||
source_wh_id=transfer_source.get("sourceWHID"),
|
||||
source_x=transfer_source.get("sourcePosX", 1),
|
||||
source_y=transfer_source.get("sourcePosY", 1),
|
||||
source_z=transfer_source.get("sourcePosZ", 1),
|
||||
)
|
||||
logger.info(f"3-2-1 转运结果: {r}")
|
||||
|
||||
# 8. 1号站内部流程(占位)
|
||||
self.start_station1_internal_flow()
|
||||
|
||||
# 9. 1→2 转运(占位)
|
||||
self.transfer_1_to_2()
|
||||
|
||||
# 10. 样品/废料取出(2.28)
|
||||
if takeout_order_id:
|
||||
r = self.take_out(order_id=takeout_order_id)
|
||||
logger.info(f"样品/废料取出结果: {r}")
|
||||
|
||||
# 2.5 批量查询实验报告
|
||||
def order_list_v2(self,
|
||||
timeType: str = "string",
|
||||
beginTime: str = "",
|
||||
endTime: str = "",
|
||||
status: str = "",
|
||||
filter: str = "物料转移任务",
|
||||
skipCount: int = 0,
|
||||
pageCount: int = 1,
|
||||
sorting: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
批量查询实验报告的详细信息 (2.5)
|
||||
URL: /api/lims/order/order-list
|
||||
参数默认值和接口文档保持一致
|
||||
"""
|
||||
data: Dict[str, Any] = {
|
||||
"timeType": timeType,
|
||||
"beginTime": beginTime,
|
||||
"endTime": endTime,
|
||||
"status": status,
|
||||
"filter": filter,
|
||||
"skipCount": skipCount,
|
||||
"pageCount": pageCount,
|
||||
"sorting": sorting
|
||||
}
|
||||
return self._post_lims("/api/lims/order/order-list", data)
|
||||
|
||||
|
||||
def wait_for_transfer_task(self, timeout: int = 600, interval: int = 3) -> bool:
|
||||
"""
|
||||
轮询查询物料转移任务是否成功完成 (status=80)
|
||||
- timeout: 最大等待秒数 (默认600秒)
|
||||
- interval: 轮询间隔秒数 (默认3秒)
|
||||
返回 True 表示找到并成功完成,False 表示超时未找到
|
||||
"""
|
||||
now = datetime.now()
|
||||
beginTime = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
endTime = (now + timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
print(beginTime, endTime)
|
||||
|
||||
deadline = time.time() + timeout
|
||||
|
||||
while time.time() < deadline:
|
||||
result = self.order_list_v2(
|
||||
timeType="string",
|
||||
beginTime=beginTime,
|
||||
endTime=endTime,
|
||||
status="",
|
||||
filter="物料转移任务",
|
||||
skipCount=0,
|
||||
pageCount=1,
|
||||
sorting=""
|
||||
)
|
||||
print(result)
|
||||
|
||||
items = result.get("data", {}).get("items", [])
|
||||
for item in items:
|
||||
name = item.get("name", "")
|
||||
status = item.get("status")
|
||||
if name.startswith("物料转移任务") and status == 80:
|
||||
logger.info(f"硬件转移动作完成: {name}")
|
||||
return True
|
||||
|
||||
time.sleep(interval)
|
||||
|
||||
logger.warning("超时未找到成功的物料转移任务")
|
||||
return False
|
||||
|
||||
|
||||
# --------------------------------
|
||||
if __name__ == "__main__":
|
||||
ws = BioyondWorkstation()
|
||||
# ws.scheduler_stop()
|
||||
ws.scheduler_start()
|
||||
logger.info("调度启动完成")
|
||||
|
||||
# ws.scheduler_continue()
|
||||
# 3.30 上料:读取模板 Excel 自动解析并 POST
|
||||
r1 = ws.auto_feeding4to3_from_xlsx(r"C:\ML\GitHub\Uni-Lab-OS\unilabos\devices\workstation\bioyond_cell\样品导入模板 (8).xlsx")
|
||||
ws.wait_for_transfer_task()
|
||||
logger.info("4号箱向3号箱转运物料转移任务已完成")
|
||||
|
||||
# ws.scheduler_start()
|
||||
# print(r1["payload"]["data"]) # 调试模式下可直接看到要发的 JSON items
|
||||
|
||||
# 新建实验
|
||||
res = ws.create_orders("C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_cell/2025092501.xlsx")
|
||||
# ws.scheduler_start()
|
||||
# print(res)
|
||||
|
||||
#1号站启动
|
||||
ws.transfer_1_to_2()
|
||||
ws.wait_for_transfer_task()
|
||||
logger.info("1号站向2号站转移任务完成")
|
||||
logger.info("全流程结束")
|
||||
|
||||
# 3.31 下料:同理
|
||||
# r2 = ws.auto_batch_outbound_from_xlsx(r"C:/path/样品导入模板 (8).xlsx")
|
||||
# print(r2["payload"]["data"])
|
||||
@@ -1,8 +1,23 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "bioyond_cell_workstation",
|
||||
"name": "配液分液工站",
|
||||
"children": [
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "bioyond_cell",
|
||||
"config": {
|
||||
"protocol_type": [],
|
||||
"station_resource": {}
|
||||
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "BatteryStation",
|
||||
"name": "扣电工作站",
|
||||
"name": "扣电组装工作站",
|
||||
"children": [
|
||||
"coin_cell_deck"
|
||||
],
|
||||
@@ -0,0 +1,7 @@
|
||||
material_name
|
||||
LiPF6
|
||||
LiDFOB
|
||||
DTD
|
||||
LiFSI
|
||||
LiPO2F2
|
||||
|
||||
|
Binary file not shown.
@@ -47,8 +47,8 @@ class BioyondV1RPC(BaseRequest):
|
||||
super().__init__()
|
||||
print("开始初始化 BioyondV1RPC")
|
||||
self.config = config
|
||||
self.api_key = config["api_key"]
|
||||
self.host = config["api_host"]
|
||||
self.api_key = config.get("api_key", "")
|
||||
self.host = config.get("api_host", "") or config.get("base_url", "")
|
||||
self._logger = SimpleLogger()
|
||||
self.material_cache = {}
|
||||
self._load_material_cache()
|
||||
@@ -61,7 +61,7 @@ class BioyondV1RPC(BaseRequest):
|
||||
|
||||
:return: 当前时间的 ISO 8601 格式字符串
|
||||
"""
|
||||
current_time = datetime.now(timezone.utc).isoformat(
|
||||
current_time = datetime.now().isoformat(
|
||||
timespec='milliseconds'
|
||||
)
|
||||
# 替换时区部分为 'Z'
|
||||
|
||||
@@ -2,137 +2,242 @@
|
||||
"""
|
||||
配置文件 - 包含所有配置信息和映射关系
|
||||
"""
|
||||
import os
|
||||
|
||||
# API配置
|
||||
# ==================== API 基础配置 ====================
|
||||
|
||||
|
||||
# ==================== 完整的 Bioyond 配置 ====================
|
||||
# BioyondCellWorkstation 默认配置(包含所有必需参数)
|
||||
API_CONFIG = {
|
||||
"api_key": "",
|
||||
"api_host": ""
|
||||
}
|
||||
|
||||
# 工作流映射配置
|
||||
WORKFLOW_MAPPINGS = {
|
||||
"reactor_taken_out": "",
|
||||
"reactor_taken_in": "",
|
||||
"Solid_feeding_vials": "",
|
||||
"Liquid_feeding_vials(non-titration)": "",
|
||||
"Liquid_feeding_solvents": "",
|
||||
"Liquid_feeding(titration)": "",
|
||||
"liquid_feeding_beaker": "",
|
||||
"Drip_back": "",
|
||||
}
|
||||
|
||||
# 工作流名称到DisplaySectionName的映射
|
||||
WORKFLOW_TO_SECTION_MAP = {
|
||||
'reactor_taken_in': '反应器放入',
|
||||
'liquid_feeding_beaker': '液体投料-烧杯',
|
||||
'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)',
|
||||
'Liquid_feeding_solvents': '液体投料-溶剂',
|
||||
'Solid_feeding_vials': '固体投料-小瓶',
|
||||
'Liquid_feeding(titration)': '液体投料-滴定',
|
||||
'reactor_taken_out': '反应器取出'
|
||||
# API 连接配置
|
||||
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),
|
||||
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
|
||||
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
|
||||
|
||||
# 报送配置
|
||||
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
||||
|
||||
# HTTP 服务配置
|
||||
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口)
|
||||
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
||||
"report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.22"), # 报送给 Bioyond 的本机IP地址(留空则自动检测)
|
||||
# 调试模式
|
||||
"debug_mode": False,
|
||||
}
|
||||
|
||||
# 库位映射配置
|
||||
WAREHOUSE_MAPPING = {
|
||||
"粉末堆栈": {
|
||||
"粉末加样头堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
# 样品板
|
||||
"A1": "3a14198e-6929-31f0-8a22-0f98f72260df",
|
||||
"A2": "3a14198e-6929-4379-affa-9a2935c17f99",
|
||||
"A3": "3a14198e-6929-56da-9a1c-7f5fbd4ae8af",
|
||||
"A4": "3a14198e-6929-5e99-2b79-80720f7cfb54",
|
||||
"B1": "3a14198e-6929-f525-9a1b-1857552b28ee",
|
||||
"B2": "3a14198e-6929-bf98-0fd5-26e1d68bf62d",
|
||||
"B3": "3a14198e-6929-2d86-a468-602175a2b5aa",
|
||||
"B4": "3a14198e-6929-1a98-ae57-e97660c489ad",
|
||||
# 分装板
|
||||
"C1": "3a14198e-6929-46fe-841e-03dd753f1e4a",
|
||||
"C2": "3a14198e-6929-1bc9-a9bd-3b7ca66e7f95",
|
||||
"C3": "3a14198e-6929-72ac-32ce-9b50245682b8",
|
||||
"C4": "3a14198e-6929-3bd8-e6c7-4a9fd93be118",
|
||||
"D1": "3a14198e-6929-8a0b-b686-6f4a2955c4e2",
|
||||
"D2": "3a14198e-6929-dde1-fc78-34a84b71afdf",
|
||||
"D3": "3a14198e-6929-a0ec-5f15-c0f9f339f963",
|
||||
"D4": "3a14198e-6929-7ac8-915a-fea51cb2e884"
|
||||
"A01": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"B01": "3a19da56-1379-2424-d751-fe6e94cef938",
|
||||
"C01": "3a19da56-1379-271c-03e3-6bdb590e395e",
|
||||
"D01": "3a19da56-1379-277f-2b1b-0d11f7cf92c6",
|
||||
"E01": "3a19da56-1379-2f1c-a15b-e01db90eb39a",
|
||||
"F01": "3a19da56-1379-3fa1-846b-088158ac0b3d",
|
||||
"G01": "3a19da56-1379-5aeb-d0cd-d3b4609d66e1",
|
||||
"H01": "3a19da56-1379-6077-8258-bdc036870b78",
|
||||
"I01": "3a19da56-1379-863b-a120-f606baf04617",
|
||||
"J01": "3a19da56-1379-8a74-74e5-35a9b41d4fd5",
|
||||
"K01": "3a19da56-1379-b270-b7af-f18773918abe",
|
||||
"L01": "3a19da56-1379-ba54-6d78-fd770a671ffc",
|
||||
"M01": "3a19da56-1379-c22d-c96f-0ceb5eb54a04",
|
||||
"N01": "3a19da56-1379-d64e-c6c5-c72ea4829888",
|
||||
"O01": "3a19da56-1379-d887-1a3c-6f9cce90f90e",
|
||||
"P01": "3a19da56-1379-e77d-0e65-7463b238a3b9",
|
||||
"Q01": "3a19da56-1379-edf6-1472-802ddb628774",
|
||||
"R01": "3a19da56-1379-f281-0273-e0ef78f0fd97",
|
||||
"S01": "3a19da56-1379-f924-7f68-df1fa51489f4",
|
||||
"T01": "3a19da56-1379-ff7c-1745-07e200b44ce2"
|
||||
}
|
||||
},
|
||||
"溶液堆栈": {
|
||||
"配液站内试剂仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A1": "3a14198e-d724-e036-afdc-2ae39a7f3383",
|
||||
"A2": "3a14198e-d724-afa4-fc82-0ac8a9016791",
|
||||
"A3": "3a14198e-d724-ca48-bb9e-7e85751e55b6",
|
||||
"A4": "3a14198e-d724-df6d-5e32-5483b3cab583",
|
||||
"B1": "3a14198e-d724-d818-6d4f-5725191a24b5",
|
||||
"B2": "3a14198e-d724-be8a-5e0b-012675e195c6",
|
||||
"B3": "3a14198e-d724-cc1e-5c2c-228a130f40a8",
|
||||
"B4": "3a14198e-d724-1e28-c885-574c3df468d0",
|
||||
"C1": "3a14198e-d724-b5bb-adf3-4c5a0da6fb31",
|
||||
"C2": "3a14198e-d724-ab4e-48cb-817c3c146707",
|
||||
"C3": "3a14198e-d724-7f18-1853-39d0c62e1d33",
|
||||
"C4": "3a14198e-d724-28a2-a760-baa896f46b66",
|
||||
"D1": "3a14198e-d724-d378-d266-2508a224a19f",
|
||||
"D2": "3a14198e-d724-f56e-468b-0110a8feb36a",
|
||||
"D3": "3a14198e-d724-0cf1-dea9-a1f40fe7e13c",
|
||||
"D4": "3a14198e-d724-0ddd-9654-f9352a421de9"
|
||||
"A01": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||
"B01": "3a19da43-57b5-7394-5f49-54efe2c9bef2",
|
||||
"C01": "3a19da43-57b5-5e75-552f-8dbd0ad1075f",
|
||||
"A02": "3a19da43-57b5-8441-db94-b4d3875a4b6c",
|
||||
"B02": "3a19da43-57b5-3e41-c181-5119dddaf50c",
|
||||
"C02": "3a19da43-57b5-269b-282d-fba61fe8ce96",
|
||||
"A03": "3a19da43-57b5-7c1e-d02e-c40e8c33f8a1",
|
||||
"B03": "3a19da43-57b5-659f-621f-1dcf3f640363",
|
||||
"C03": "3a19da43-57b5-855a-6e71-f398e376dee1",
|
||||
}
|
||||
},
|
||||
"试剂堆栈": {
|
||||
"试剂替换仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A1": "3a14198c-c2cf-8b40-af28-b467808f1c36",
|
||||
"A2": "3a14198c-c2d0-f3e7-871a-e470d144296f",
|
||||
"A3": "3a14198c-c2d0-dc7d-b8d0-e1d88cee3094",
|
||||
"A4": "3a14198c-c2d0-2070-efc8-44e245f10c6f",
|
||||
"B1": "3a14198c-c2d0-354f-39ad-642e1a72fcb8",
|
||||
"B2": "3a14198c-c2d0-1559-105d-0ea30682cab4",
|
||||
"B3": "3a14198c-c2d0-725e-523d-34c037ac2440",
|
||||
"B4": "3a14198c-c2d0-efce-0939-69ca5a7dfd39"
|
||||
"A01": "3a19da51-8f4e-30f3-ea08-4f8498e9b097",
|
||||
"B01": "3a19da51-8f4e-1da7-beb0-80a4a01e67a8",
|
||||
"C01": "3a19da51-8f4e-337d-2675-bfac46880b06",
|
||||
"D01": "3a19da51-8f4e-e514-b92c-9c44dc5e489d",
|
||||
"E01": "3a19da51-8f4e-22d1-dd5b-9774ddc80402",
|
||||
"F01": "3a19da51-8f4e-273a-4871-dff41c29bfd9",
|
||||
"G01": "3a19da51-8f4e-b32f-454f-74bc1a665653",
|
||||
"H01": "3a19da51-8f4e-8c93-68c9-0b4382320f59",
|
||||
"I01": "3a19da51-8f4e-360c-0149-291b47c6089b",
|
||||
"J01": "3a19da51-8f4e-4152-9bca-8d64df8c1af0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"自动堆栈-左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"A02": "3a19debc-84b5-033b-b31f-6b87f7c2bf52",
|
||||
"B01": "3a19debc-84b5-3924-172f-719ab01b125c",
|
||||
"B02": "3a19debc-84b5-aad8-70c6-b8c6bb2d8750"
|
||||
}
|
||||
},
|
||||
"自动堆栈-右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19debe-5200-7df2-1dd9-7d202f158864",
|
||||
"A02": "3a19debe-5200-573b-6120-8b51f50e1e50",
|
||||
"B01": "3a19debe-5200-7cd8-7666-851b0a97e309",
|
||||
"B02": "3a19debe-5200-e6d3-96a3-baa6e3d5e484"
|
||||
}
|
||||
},
|
||||
"手动堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# 物料类型配置
|
||||
MATERIAL_TYPE_MAPPINGS = {
|
||||
"烧杯": ("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"),
|
||||
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("YB_1BottleCarrier", ""),
|
||||
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
"20ml分液瓶": ("YB_20ml_Dispensing_Vial", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"),
|
||||
"100ml液体": ("YB_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
|
||||
"液": ("YB_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
||||
"高粘液": ("YB_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
|
||||
"加样头(大)": ("YB_Large_Dispense_Head", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
"5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"),
|
||||
"5ml分液瓶": ("YB_5ml_Dispensing_Vial", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"),
|
||||
"20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"),
|
||||
"配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"),
|
||||
"配液瓶(小)": ("YB_Small_Solution_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"),
|
||||
"配液瓶(大)板": ("YB_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"),
|
||||
"配液瓶(大)": ("YB_Large_Solution_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"),
|
||||
"加样头(大)板": ("YB_6x_LargeDispenseHeadCarrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"),
|
||||
"适配器块": ("YB_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"),
|
||||
"枪头盒": ("YB_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"),
|
||||
"枪头": ("YB_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"),
|
||||
}
|
||||
|
||||
# 步骤参数配置(各工作流的步骤UUID)
|
||||
WORKFLOW_STEP_IDS = {
|
||||
"reactor_taken_in": {
|
||||
"config": ""
|
||||
SOLID_LIQUID_MAPPINGS = {
|
||||
# 固体
|
||||
"LiDFOB": {
|
||||
"typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
"code": "",
|
||||
"barCode": "",
|
||||
"name": "LiDFOB",
|
||||
"unit": "g",
|
||||
"parameters": "",
|
||||
"quantity": "2",
|
||||
"warningQuantity": "1",
|
||||
"details": []
|
||||
},
|
||||
"liquid_feeding_beaker": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
},
|
||||
"liquid_feeding_vials_non_titration": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
},
|
||||
"liquid_feeding_solvents": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
},
|
||||
"solid_feeding_vials": {
|
||||
"feeding": "",
|
||||
"observe": ""
|
||||
},
|
||||
"liquid_feeding_titration": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
},
|
||||
"drip_back": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
}
|
||||
# "LiPF6": {
|
||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
# "code": "",
|
||||
# "barCode": "",
|
||||
# "name": "LiPF6",
|
||||
# "unit": "g",
|
||||
# "parameters": "",
|
||||
# "quantity": 2,
|
||||
# "warningQuantity": 1,
|
||||
# "details": []
|
||||
# },
|
||||
# "LiFSI": {
|
||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
# "code": "",
|
||||
# "barCode": "",
|
||||
# "name": "LiFSI",
|
||||
# "unit": "g",
|
||||
# "parameters": "",
|
||||
# "quantity": 2,
|
||||
# "warningQuantity": 1,
|
||||
# "details": []
|
||||
# },
|
||||
# "DTC": {
|
||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
# "code": "",
|
||||
# "barCode": "",
|
||||
# "name": "DTC",
|
||||
# "unit": "g",
|
||||
# "parameters": "",
|
||||
# "quantity": 2,
|
||||
# "warningQuantity": 1,
|
||||
# "details": []
|
||||
# },
|
||||
# "LiPO2F2": {
|
||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
# "code": "",
|
||||
# "barCode": "",
|
||||
# "name": "LiPO2F2",
|
||||
# "unit": "g",
|
||||
# "parameters": "",
|
||||
# "quantity": 2,
|
||||
# "warningQuantity": 1,
|
||||
# "details": []
|
||||
# },
|
||||
# 液体
|
||||
# "SA": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "EC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "VC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "AND": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "HTCN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "DENE": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "TMSP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "TMSB": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "EP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "DEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "EMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "SN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "DMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "FEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
}
|
||||
|
||||
WORKFLOW_MAPPINGS = {}
|
||||
|
||||
LOCATION_MAPPING = {}
|
||||
@@ -20,6 +20,7 @@ from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNo
|
||||
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
||||
from pylabrobot.resources.resource import Resource as ResourcePLR
|
||||
|
||||
from unilabos.resources.bioyond.decks import YB_Deck
|
||||
from unilabos.devices.workstation.bioyond_studio.config import (
|
||||
API_CONFIG, WORKFLOW_MAPPINGS, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING
|
||||
)
|
||||
@@ -135,7 +136,7 @@ class BioyondWorkstation(WorkstationBase):
|
||||
# 初始化父类
|
||||
super().__init__(
|
||||
# 桌子
|
||||
deck=deck,
|
||||
deck=YB_Deck("YB_Deck14"),
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
@@ -164,8 +165,8 @@ class BioyondWorkstation(WorkstationBase):
|
||||
self.workflow_sequence = []
|
||||
self.pending_task_params = []
|
||||
|
||||
if "workflow_mappings" in bioyond_config:
|
||||
self._set_workflow_mappings(bioyond_config["workflow_mappings"])
|
||||
if self.bioyond_config and "workflow_mappings" in self.bioyond_config:
|
||||
self._set_workflow_mappings(self.bioyond_config["workflow_mappings"])
|
||||
logger.info(f"Bioyond工作站初始化完成")
|
||||
|
||||
def post_init(self, ros_node: ROS2WorkstationNode):
|
||||
@@ -467,7 +468,7 @@ def create_bioyond_workstation_example():
|
||||
"""创建Bioyond工作站示例"""
|
||||
|
||||
# 配置参数
|
||||
device_id = "bioyond_workstation_001"
|
||||
device_id = "bioyond_cell_workstation_001"
|
||||
|
||||
# 子资源配置
|
||||
children = {
|
||||
@@ -486,8 +487,8 @@ def create_bioyond_workstation_example():
|
||||
|
||||
# Bioyond配置
|
||||
bioyond_config = {
|
||||
"base_url": "http://bioyond.example.com/api",
|
||||
"api_key": "your_api_key_here",
|
||||
"base_url": "http://172.16.11.219:44388",
|
||||
"api_key": "8A819E5C",
|
||||
"sync_interval": 60, # 60秒同步一次
|
||||
"timeout": 30
|
||||
}
|
||||
@@ -511,4 +512,18 @@ def create_bioyond_workstation_example():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
by = create_bioyond_workstation_example()
|
||||
by.get_workstation_status()
|
||||
by.get_device_status()
|
||||
by.get_bioyond_status()
|
||||
by.get_station_info()
|
||||
by.get_workstation_status()
|
||||
by.get_bioyond_status()
|
||||
by.get_station_info()
|
||||
by.get_workstation_status()
|
||||
by.get_bioyond_status()
|
||||
by.get_station_info()
|
||||
by.get_workstation_status()
|
||||
by.get_bioyond_status()
|
||||
by.get_station_info()
|
||||
pass
|
||||
@@ -24,6 +24,13 @@ class ElectrodeSheetState(TypedDict):
|
||||
thickness: float # 厚度 (mm)
|
||||
mass: float # 质量 (g)
|
||||
material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)
|
||||
height: float
|
||||
electrolyte_name: str
|
||||
data_electrolyte_code: str
|
||||
open_circuit_voltage: float
|
||||
assembly_pressure: float
|
||||
electrolyte_volume: float
|
||||
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
class ElectrodeSheet(Resource):
|
||||
@@ -61,6 +68,7 @@ class ElectrodeSheet(Resource):
|
||||
info=None
|
||||
)
|
||||
|
||||
# TODO: 这个还要不要?给self._unilabos_state赋值的?
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""格式不变"""
|
||||
super().load_state(state)
|
||||
@@ -146,10 +154,10 @@ class MaterialHole(Resource):
|
||||
):
|
||||
"""放置极片"""
|
||||
# TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题
|
||||
if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]:
|
||||
raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}")
|
||||
if len(self.children) >= self._unilabos_state["max_sheets"]:
|
||||
raise ValueError(f"洞位已满,无法放置更多极片")
|
||||
#if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]:
|
||||
# raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}")
|
||||
#if len(self.children) >= self._unilabos_state["max_sheets"]:
|
||||
# raise ValueError(f"洞位已满,无法放置更多极片")
|
||||
super().assign_child_resource(resource, location, reassign)
|
||||
|
||||
# 根据children的编号取物料对象。
|
||||
@@ -164,8 +172,6 @@ class MaterialPlateState(TypedDict):
|
||||
hole_diameter: float
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
|
||||
|
||||
class MaterialPlate(ItemizedResource[MaterialHole]):
|
||||
"""料板类 - 4x4个洞位,每个洞位放1个极片"""
|
||||
|
||||
@@ -323,12 +329,13 @@ class PlateSlot(ResourceStack):
|
||||
|
||||
class ClipMagazineHole(Container):
|
||||
"""子弹夹洞位类"""
|
||||
children: List[ElectrodeSheet] = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
diameter: float,
|
||||
depth: float,
|
||||
max_sheets: int = 100,
|
||||
category: str = "clip_magazine_hole",
|
||||
):
|
||||
"""初始化子弹夹洞位
|
||||
@@ -337,6 +344,7 @@ class ClipMagazineHole(Container):
|
||||
name: 洞位名称
|
||||
diameter: 洞直径 (mm)
|
||||
depth: 洞深度 (mm)
|
||||
max_sheets: 最大极片数量
|
||||
category: 类别
|
||||
"""
|
||||
super().__init__(
|
||||
@@ -348,143 +356,46 @@ class ClipMagazineHole(Container):
|
||||
)
|
||||
self.diameter = diameter
|
||||
self.depth = depth
|
||||
self.max_sheets = max_sheets
|
||||
self._sheets: List[ElectrodeSheet] = []
|
||||
|
||||
def can_add_sheet(self, sheet: ElectrodeSheet) -> bool:
|
||||
"""检查是否可以添加极片
|
||||
|
||||
根据洞的深度和极片的厚度来判断是否可以添加极片
|
||||
"""
|
||||
# 检查极片直径是否适合洞的直径
|
||||
if sheet._unilabos_state["diameter"] > self.diameter:
|
||||
return False
|
||||
|
||||
# 计算当前已添加极片的总厚度
|
||||
current_thickness = sum(s._unilabos_state["thickness"] for s in self.children)
|
||||
|
||||
# 检查添加新极片后总厚度是否超过洞的深度
|
||||
if current_thickness + sheet._unilabos_state["thickness"] > self.depth:
|
||||
return False
|
||||
|
||||
return True
|
||||
"""检查是否可以添加极片"""
|
||||
return (len(self._sheets) < self.max_sheets and
|
||||
sheet.diameter <= self.diameter)
|
||||
|
||||
def add_sheet(self, sheet: ElectrodeSheet) -> None:
|
||||
"""添加极片"""
|
||||
if not self.can_add_sheet(sheet):
|
||||
raise ValueError(f"无法向洞位 {self.name} 添加极片")
|
||||
self._sheets.append(sheet)
|
||||
|
||||
def assign_child_resource(
|
||||
self,
|
||||
resource: ElectrodeSheet,
|
||||
location: Optional[Coordinate] = None,
|
||||
reassign: bool = True,
|
||||
):
|
||||
"""放置极片到洞位中
|
||||
|
||||
Args:
|
||||
resource: 要放置的极片
|
||||
location: 极片在洞位中的位置(对于洞位,通常为None)
|
||||
reassign: 是否允许重新分配
|
||||
"""
|
||||
# 检查是否可以添加极片
|
||||
if not self.can_add_sheet(resource):
|
||||
raise ValueError(f"无法向洞位 {self.name} 添加极片:直径或厚度不匹配")
|
||||
|
||||
# 调用父类方法实际执行分配
|
||||
super().assign_child_resource(resource, location, reassign)
|
||||
|
||||
def unassign_child_resource(self, resource: ElectrodeSheet):
|
||||
"""从洞位中移除极片
|
||||
|
||||
Args:
|
||||
resource: 要移除的极片
|
||||
"""
|
||||
if resource not in self.children:
|
||||
raise ValueError(f"极片 {resource.name} 不在洞位 {self.name} 中")
|
||||
|
||||
# 调用父类方法实际执行移除
|
||||
super().unassign_child_resource(resource)
|
||||
|
||||
def take_sheet(self) -> ElectrodeSheet:
|
||||
"""取出极片"""
|
||||
if len(self._sheets) == 0:
|
||||
raise ValueError(f"洞位 {self.name} 没有极片")
|
||||
return self._sheets.pop()
|
||||
|
||||
def get_sheet_count(self) -> int:
|
||||
"""获取极片数量"""
|
||||
return len(self._sheets)
|
||||
|
||||
def serialize_state(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"sheet_count": len(self.children),
|
||||
"sheets": [sheet.serialize() for sheet in self.children],
|
||||
"sheet_count": len(self._sheets),
|
||||
"sheets": [sheet.serialize() for sheet in self._sheets],
|
||||
}
|
||||
class ClipMagazine_four(ItemizedResource[ClipMagazineHole]):
|
||||
"""子弹夹类 - 有4个洞位,每个洞位放多个极片"""
|
||||
children: List[ClipMagazineHole]
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
hole_spacing: float = 25.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
category: str = "clip_magazine_four",
|
||||
model: Optional[str] = None,
|
||||
):
|
||||
"""初始化子弹夹
|
||||
|
||||
Args:
|
||||
name: 子弹夹名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
hole_diameter: 洞直径 (mm)
|
||||
hole_depth: 洞深度 (mm)
|
||||
hole_spacing: 洞位间距 (mm)
|
||||
max_sheets_per_hole: 每个洞位最大极片数量
|
||||
category: 类别
|
||||
model: 型号
|
||||
"""
|
||||
# 创建4个洞位,排成2x2布局
|
||||
holes = create_ordered_items_2d(
|
||||
klass=ClipMagazineHole,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
dx=(size_x - 2 * hole_spacing) / 2, # 居中
|
||||
dy=(size_y - hole_spacing) / 2, # 居中
|
||||
dz=size_z - 0,
|
||||
item_dx=hole_spacing,
|
||||
item_dy=hole_spacing,
|
||||
diameter=hole_diameter,
|
||||
depth=hole_depth,
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
ordered_items=holes,
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
|
||||
# 保存洞位的直径和深度
|
||||
self.hole_diameter = hole_diameter
|
||||
self.hole_depth = hole_depth
|
||||
self.max_sheets_per_hole = max_sheets_per_hole
|
||||
|
||||
def serialize(self) -> dict:
|
||||
return {
|
||||
**super().serialize(),
|
||||
"hole_diameter": self.hole_diameter,
|
||||
"hole_depth": self.hole_depth,
|
||||
"max_sheets_per_hole": self.max_sheets_per_hole,
|
||||
}
|
||||
# TODO: 这个要改
|
||||
class ClipMagazine(ItemizedResource[ClipMagazineHole]):
|
||||
class ClipMagazine(Resource):
|
||||
"""子弹夹类 - 有6个洞位,每个洞位放多个极片"""
|
||||
children: List[ClipMagazineHole]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
hole_spacing: float = 25.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
category: str = "clip_magazine",
|
||||
@@ -514,8 +425,8 @@ class ClipMagazine(ItemizedResource[ClipMagazineHole]):
|
||||
dz=size_z - 0,
|
||||
item_dx=hole_spacing,
|
||||
item_dy=hole_spacing,
|
||||
diameter=hole_diameter,
|
||||
depth=hole_depth,
|
||||
diameter=0,
|
||||
depth=0,
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
@@ -528,7 +439,6 @@ class ClipMagazine(ItemizedResource[ClipMagazineHole]):
|
||||
model=model,
|
||||
)
|
||||
|
||||
# 保存洞位的直径和深度
|
||||
self.hole_diameter = hole_diameter
|
||||
self.hole_depth = hole_depth
|
||||
self.max_sheets_per_hole = max_sheets_per_hole
|
||||
@@ -545,9 +455,9 @@ class BatteryState(TypedDict):
|
||||
"""电池状态字典"""
|
||||
diameter: float
|
||||
height: float
|
||||
|
||||
electrolyte_name: str
|
||||
assembly_pressure: float
|
||||
electrolyte_volume: float
|
||||
electrolyte_name: str
|
||||
|
||||
class Battery(Resource):
|
||||
"""电池类 - 可容纳极片"""
|
||||
@@ -556,6 +466,9 @@ class Battery(Resource):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x=1,
|
||||
size_y=1,
|
||||
size_z=1,
|
||||
category: str = "battery",
|
||||
):
|
||||
"""初始化电池
|
||||
@@ -576,7 +489,13 @@ class Battery(Resource):
|
||||
size_z=1,
|
||||
category=category,
|
||||
)
|
||||
self._unilabos_state: BatteryState = BatteryState()
|
||||
self._unilabos_state: BatteryState = BatteryState(
|
||||
diameter = 1.0,
|
||||
height = 1.0,
|
||||
assembly_pressure = 1.0,
|
||||
electrolyte_volume = 1.0,
|
||||
electrolyte_name = "DP001"
|
||||
)
|
||||
|
||||
def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool:
|
||||
to_add_name = bottle._unilabos_state["electrolyte_name"]
|
||||
@@ -664,6 +583,7 @@ class BatteryPressSlot(Resource):
|
||||
reassign: bool = True,
|
||||
):
|
||||
"""放置极片"""
|
||||
# TODO: 让高京看下槽位只有一个电池时是否这么写。
|
||||
if self.has_battery():
|
||||
raise ValueError(f"槽位已含有一个电池,无法再放置其他电池")
|
||||
super().assign_child_resource(resource, location, reassign)
|
||||
@@ -672,6 +592,7 @@ class BatteryPressSlot(Resource):
|
||||
def get_battery_info(self, index: int) -> Battery:
|
||||
return self.children[0]
|
||||
|
||||
# TODO:这个移液枪架子看一下从哪继承
|
||||
class TipBox64State(TypedDict):
|
||||
"""电池状态字典"""
|
||||
tip_diameter: float = 5.0
|
||||
@@ -730,15 +651,6 @@ class TipBox64(TipRack):
|
||||
make_tip=make_tip,
|
||||
)
|
||||
self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate()
|
||||
# 记录网格参数用于前端渲染
|
||||
self._grid_params = {
|
||||
"num_items_x": 8,
|
||||
"num_items_y": 8,
|
||||
"dx": 8.0,
|
||||
"dy": 8.0,
|
||||
"item_dx": 9.0,
|
||||
"item_dy": 9.0,
|
||||
}
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
@@ -750,12 +662,6 @@ class TipBox64(TipRack):
|
||||
with_tips=True,
|
||||
)
|
||||
|
||||
def serialize(self) -> dict:
|
||||
return {
|
||||
**super().serialize(),
|
||||
**self._grid_params,
|
||||
}
|
||||
|
||||
|
||||
|
||||
class WasteTipBoxstate(TypedDict):
|
||||
@@ -833,34 +739,19 @@ class BottleRackState(TypedDict):
|
||||
name_to_index: dict
|
||||
|
||||
|
||||
class BottleRackState(TypedDict):
|
||||
""" bottle_diameter: 瓶子直径 (mm)
|
||||
bottle_height: 瓶子高度 (mm)
|
||||
position_spacing: 位置间距 (mm)"""
|
||||
bottle_diameter: float
|
||||
bottle_height: float
|
||||
position_spacing: float
|
||||
name_to_index: dict
|
||||
|
||||
|
||||
class BottleRack(Resource):
|
||||
"""瓶架类 - 12个待配位置+12个已配位置"""
|
||||
children: List[Resource] = []
|
||||
children: List[Bottle] = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
category: str = "bottle_rack",
|
||||
model: Optional[str] = None,
|
||||
num_items_x: int = 3,
|
||||
num_items_y: int = 4,
|
||||
position_spacing: float = 35.0,
|
||||
orientation: str = "horizontal",
|
||||
padding_x: float = 20.0,
|
||||
padding_y: float = 20.0,
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
category: str = "bottle_rack",
|
||||
model: Optional[str] = None,
|
||||
):
|
||||
"""初始化瓶架
|
||||
|
||||
@@ -880,42 +771,13 @@ class BottleRack(Resource):
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
# 初始化状态
|
||||
self._unilabos_state: BottleRackState = BottleRackState(
|
||||
bottle_diameter=30.0,
|
||||
bottle_height=100.0,
|
||||
position_spacing=position_spacing,
|
||||
name_to_index={},
|
||||
)
|
||||
# 基于网格生成瓶位坐标映射(居中摆放)
|
||||
# 使用内边距,避免点跑到容器外(前端渲染不按mm等比缩放时更稳妥)
|
||||
origin_x = padding_x
|
||||
origin_y = padding_y
|
||||
self.index_to_pos = {}
|
||||
for j in range(num_items_y):
|
||||
for i in range(num_items_x):
|
||||
idx = j * num_items_x + i
|
||||
if orientation == "vertical":
|
||||
# 纵向:沿 y 方向优先排列
|
||||
self.index_to_pos[idx] = Coordinate(
|
||||
x=origin_x + j * position_spacing,
|
||||
y=origin_y + i * position_spacing,
|
||||
z=0,
|
||||
)
|
||||
else:
|
||||
# 横向(默认):沿 x 方向优先排列
|
||||
self.index_to_pos[idx] = Coordinate(
|
||||
x=origin_x + i * position_spacing,
|
||||
y=origin_y + j * position_spacing,
|
||||
z=0,
|
||||
)
|
||||
# TODO: 添加瓶位坐标映射
|
||||
self.index_to_pos = {
|
||||
0: Coordinate.zero(),
|
||||
1: Coordinate(x=1, y=2, z=3) # 添加
|
||||
}
|
||||
self.name_to_index = {}
|
||||
self.name_to_pos = {}
|
||||
self.num_items_x = num_items_x
|
||||
self.num_items_y = num_items_y
|
||||
self.orientation = orientation
|
||||
self.padding_x = padding_x
|
||||
self.padding_y = padding_y
|
||||
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""格式不变"""
|
||||
@@ -925,23 +787,20 @@ class BottleRack(Resource):
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""格式不变"""
|
||||
data = super().serialize_state()
|
||||
data.update(
|
||||
self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||
return data
|
||||
|
||||
# TODO: 这里有些问题要重新写一下
|
||||
def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True):
|
||||
capacity = self.num_items_x * self.num_items_y
|
||||
assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子"
|
||||
def assign_child_resource(self, resource: Bottle, location=Coordinate.zero(), reassign = True):
|
||||
assert len(self.children) <= 12, "瓶架已满,无法添加更多瓶子"
|
||||
index = len(self.children)
|
||||
location = self.index_to_pos.get(index, Coordinate.zero())
|
||||
location = Coordinate(x=20 + (index % 4) * 15, y=20 + (index // 4) * 15, z=0)
|
||||
self.name_to_pos[resource.name] = location
|
||||
self.name_to_index[resource.name] = index
|
||||
return super().assign_child_resource(resource, location, reassign)
|
||||
|
||||
def assign_child_resource(self, resource: Resource, index: int):
|
||||
capacity = self.num_items_x * self.num_items_y
|
||||
assert 0 <= index < capacity, "无效的瓶子索引"
|
||||
|
||||
def assign_child_resource_by_index(self, resource: Bottle, index: int):
|
||||
assert 0 <= index < 12, "无效的瓶子索引"
|
||||
self.name_to_index[resource.name] = index
|
||||
location = self.index_to_pos[index]
|
||||
return super().assign_child_resource(resource, location)
|
||||
@@ -950,16 +809,9 @@ class BottleRack(Resource):
|
||||
super().unassign_child_resource(resource)
|
||||
self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None)
|
||||
|
||||
def serialize(self) -> dict:
|
||||
return {
|
||||
**super().serialize(),
|
||||
"num_items_x": self.num_items_x,
|
||||
"num_items_y": self.num_items_y,
|
||||
"position_spacing": self._unilabos_state.get("position_spacing", 35.0),
|
||||
"orientation": self.orientation,
|
||||
"padding_x": self.padding_x,
|
||||
"padding_y": self.padding_y,
|
||||
}
|
||||
# def serialize(self):
|
||||
# self.children.sort(key=lambda x: self.name_to_index.get(x.name, 0))
|
||||
# return super().serialize()
|
||||
|
||||
|
||||
class BottleState(TypedDict):
|
||||
@@ -1095,16 +947,20 @@ class CoincellDeck(Deck):
|
||||
# plate.assign_child_resource(hole)
|
||||
# return plate
|
||||
|
||||
def create_a_liaopan():
|
||||
liaopan = MaterialPlate(name="liaopan", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
|
||||
for i in range(16):
|
||||
jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1)
|
||||
liaopan1.children[i].assign_child_resource(jipian, location=None)
|
||||
return liaopan
|
||||
import json
|
||||
|
||||
def create_a_coin_cell_deck():
|
||||
deck = Deck(size_x=1200,
|
||||
size_y=800,
|
||||
if __name__ == "__main__":
|
||||
#electrode1 = BatteryPressSlot()
|
||||
#print(electrode1.get_size_x())
|
||||
#print(electrode1.get_size_y())
|
||||
#print(electrode1.get_size_z())
|
||||
#jipian = ElectrodeSheet()
|
||||
#jipian._unilabos_state["diameter"] = 18
|
||||
#print(jipian.serialize())
|
||||
#print(jipian.serialize_state())
|
||||
|
||||
deck = CoincellDeck(size_x=1000,
|
||||
size_y=1000,
|
||||
size_z=900)
|
||||
|
||||
#liaopan = TipBox64(name="liaopan")
|
||||
@@ -1115,172 +971,33 @@ def create_a_coin_cell_deck():
|
||||
deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0))
|
||||
#创建一个极片
|
||||
for i in range(16):
|
||||
jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1)
|
||||
jipian = ElectrodeSheet(name=f"jipian1_{i}", size_x= 12, size_y=12, size_z=0.1)
|
||||
liaopan1.children[i].assign_child_resource(jipian, location=None)
|
||||
#
|
||||
#创建一个4*4的物料板
|
||||
liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
|
||||
#把物料板放到桌子上
|
||||
deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0))
|
||||
|
||||
#创建一个4*4的物料板
|
||||
liaopan3 = MaterialPlate(name="liaopan3", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
|
||||
liaopan3 = MaterialPlate(name="电池料盘", size_x=120.8, size_y=160.5, size_z=10.0, fill=True)
|
||||
#把物料板放到桌子上
|
||||
deck.assign_child_resource(liaopan3, Coordinate(x=1000, y=0, z=0))
|
||||
|
||||
print(deck)
|
||||
|
||||
return deck
|
||||
deck.assign_child_resource(liaopan3, Coordinate(x=100, y=100, z=0))
|
||||
|
||||
|
||||
import json
|
||||
|
||||
if __name__ == "__main__":
|
||||
electrode1 = BatteryPressSlot()
|
||||
#print(electrode1.get_size_x())
|
||||
#print(electrode1.get_size_y())
|
||||
#print(electrode1.get_size_z())
|
||||
#jipian = ElectrodeSheet()
|
||||
#jipian._unilabos_state["diameter"] = 18
|
||||
#print(jipian.serialize())
|
||||
#print(jipian.serialize_state())
|
||||
|
||||
deck = CoincellDeck()
|
||||
"""======================================子弹夹============================================"""
|
||||
zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10)
|
||||
deck.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0))
|
||||
zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10)
|
||||
deck.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0))
|
||||
zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10)
|
||||
deck.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0))
|
||||
zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10)
|
||||
deck.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0))
|
||||
zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10)
|
||||
deck.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0))
|
||||
zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10)
|
||||
deck.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0))
|
||||
zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10)
|
||||
deck.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0))
|
||||
zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10)
|
||||
deck.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0))
|
||||
for i in range(4):
|
||||
jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
zip_dan_jia2.children[i].assign_child_resource(jipian, location=None)
|
||||
for i in range(4):
|
||||
jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
zip_dan_jia.children[i].assign_child_resource(jipian2, location=None)
|
||||
for i in range(6):
|
||||
jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None)
|
||||
for i in range(6):
|
||||
jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None)
|
||||
for i in range(6):
|
||||
jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None)
|
||||
for i in range(6):
|
||||
jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None)
|
||||
for i in range(6):
|
||||
jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None)
|
||||
for i in range(6):
|
||||
jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None)
|
||||
"""======================================子弹夹============================================"""
|
||||
#liaopan = TipBox64(name="liaopan")
|
||||
"""======================================物料板============================================"""
|
||||
#创建一个4*4的物料板
|
||||
liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||
deck.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0))
|
||||
for i in range(16):
|
||||
jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
liaopan1.children[i].assign_child_resource(jipian_1, location=None)
|
||||
|
||||
liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||
deck.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0))
|
||||
|
||||
liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||
deck.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0))
|
||||
|
||||
liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||
deck.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0))
|
||||
for i in range(16):
|
||||
jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||
liaopan4.children[i].assign_child_resource(jipian_4, location=None)
|
||||
liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||
deck.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0))
|
||||
liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||
deck.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0))
|
||||
#liaopan.children[3].assign_child_resource(jipian, location=None)
|
||||
"""======================================物料板============================================"""
|
||||
"""======================================瓶架,移液枪============================================"""
|
||||
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
|
||||
bottle_rack_3x4 = BottleRack(
|
||||
name="bottle_rack_3x4",
|
||||
size_x=210.0,
|
||||
size_y=140.0,
|
||||
size_z=100.0,
|
||||
num_items_x=3,
|
||||
num_items_y=4,
|
||||
position_spacing=35.0,
|
||||
orientation="vertical",
|
||||
)
|
||||
deck.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0))
|
||||
|
||||
bottle_rack_6x2 = BottleRack(
|
||||
name="bottle_rack_6x2",
|
||||
size_x=120.0,
|
||||
size_y=250.0,
|
||||
size_z=100.0,
|
||||
num_items_x=6,
|
||||
num_items_y=2,
|
||||
position_spacing=35.0,
|
||||
orientation="vertical",
|
||||
)
|
||||
deck.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0))
|
||||
|
||||
bottle_rack_6x2_2 = BottleRack(
|
||||
name="bottle_rack_6x2_2",
|
||||
size_x=120.0,
|
||||
size_y=250.0,
|
||||
size_z=100.0,
|
||||
num_items_x=6,
|
||||
num_items_y=2,
|
||||
position_spacing=35.0,
|
||||
orientation="vertical",
|
||||
)
|
||||
deck.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0))
|
||||
|
||||
|
||||
# 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位
|
||||
for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y):
|
||||
sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1)
|
||||
bottle_rack_3x4.assign_child_resource(sheet, index=idx)
|
||||
|
||||
for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y):
|
||||
sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1)
|
||||
bottle_rack_6x2.assign_child_resource(sheet, index=idx)
|
||||
|
||||
tip_box = TipBox64(name="tip_box_64")
|
||||
deck.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0))
|
||||
|
||||
waste_tip_box = WasteTipBox(name="waste_tip_box")
|
||||
deck.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0))
|
||||
"""======================================瓶架,移液枪============================================"""
|
||||
print(deck)
|
||||
|
||||
|
||||
from unilabos.resources.graphio import convert_resources_from_type
|
||||
from unilabos.config.config import BasicConfig
|
||||
BasicConfig.ak = "56bbed5b-6e30-438c-b06d-f69eaa63bb45"
|
||||
BasicConfig.sk = "238222fe-0bf7-4350-a426-e5ced8011dcf"
|
||||
BasicConfig.ak = "4d5ce6ae-7234-4639-834e-93899b9caf94"
|
||||
BasicConfig.sk = "505d3b0a-620e-459a-9905-1efcffce382a"
|
||||
from unilabos.app.web.client import http_client
|
||||
|
||||
resources = convert_resources_from_type([deck], [Resource])
|
||||
|
||||
# 检查序列化后的资源
|
||||
|
||||
json.dump({"nodes": resources, "links": []}, open("button_battery_decks_unilab.json", "w"), indent=2)
|
||||
json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2)
|
||||
|
||||
|
||||
#print(resources)
|
||||
@@ -21,7 +21,7 @@ from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
||||
class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
def __init__(
|
||||
self,
|
||||
deck: CoincellDeck,
|
||||
station_resource: CoincellDeck,
|
||||
address: str = "192.168.1.20",
|
||||
port: str = "502",
|
||||
debug_mode: bool = True,
|
||||
@@ -30,12 +30,12 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
):
|
||||
super().__init__(
|
||||
#桌子
|
||||
deck=deck,
|
||||
station_resource=station_resource,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
self.debug_mode = debug_mode
|
||||
self.deck = deck
|
||||
self.station_resource = station_resource
|
||||
""" 连接初始化 """
|
||||
modbus_client = TCPClient(addr=address, port=port)
|
||||
print("modbus_client", modbus_client)
|
||||
@@ -60,6 +60,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
self.csv_export_thread = None
|
||||
self.csv_export_running = False
|
||||
self.csv_export_file = None
|
||||
self.coin_num_N = 0 #已组装电池数量
|
||||
#创建一个物料台面,包含两个极片板
|
||||
#self.deck = create_a_coin_cell_deck()
|
||||
|
||||
@@ -74,7 +75,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
self._ros_node = ros_node
|
||||
#self.deck = create_a_coin_cell_deck()
|
||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||
"resources": [self.deck]
|
||||
"resources": [self.station_resource]
|
||||
})
|
||||
|
||||
# 批量操作在这里写
|
||||
@@ -84,7 +85,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
|
||||
|
||||
async def fill_plate(self):
|
||||
plate_1: MaterialPlate = self.deck.children[0].children[0]
|
||||
plate_1: MaterialPlate = self.station_resource.children[0].children[0]
|
||||
#plate_1
|
||||
return await self._ros_node.update_resource(plate_1)
|
||||
|
||||
@@ -341,7 +342,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
def modify_deck_name(self, resource_name: str):
|
||||
# figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name})
|
||||
# print(f"!!! figure_res: {type(figure_res)}")
|
||||
self.deck.children[1]
|
||||
self.station_resource.children[1]
|
||||
return
|
||||
|
||||
@property
|
||||
@@ -606,7 +607,8 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
print("waiting for start_cmd")
|
||||
time.sleep(1)
|
||||
|
||||
def func_pack_send_bottle_num(self, bottle_num: int):
|
||||
def func_pack_send_bottle_num(self, bottle_num):
|
||||
bottle_num = int(bottle_num)
|
||||
#发送电解液平台数
|
||||
print("启动")
|
||||
while (self._unilab_rece_electrolyte_bottle_num()) == False:
|
||||
@@ -654,16 +656,25 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
# self.success = True
|
||||
# return self.success
|
||||
|
||||
def func_pack_send_msg_cmd(self, elec_use_num) -> bool:
|
||||
def func_pack_send_msg_cmd(self, elec_use_num, elec_vol, assembly_type, assembly_pressure) -> bool:
|
||||
"""UNILAB写参数"""
|
||||
while (self.request_rec_msg_status) == False:
|
||||
print("wait for request_rec_msg_status to True")
|
||||
time.sleep(1)
|
||||
self.success = False
|
||||
#self._unilab_send_msg_electrolyte_num(elec_num)
|
||||
time.sleep(1)
|
||||
#设置平行样数目
|
||||
self._unilab_send_msg_electrolyte_use_num(elec_use_num)
|
||||
time.sleep(1)
|
||||
#发送电解液加注量
|
||||
self._unilab_send_msg_electrolyte_vol(elec_vol)
|
||||
time.sleep(1)
|
||||
#发送电解液组装类型
|
||||
self._unilab_send_msg_assembly_type(assembly_type)
|
||||
time.sleep(1)
|
||||
#发送电池压制力
|
||||
self._unilab_send_msg_assembly_pressure(assembly_pressure)
|
||||
time.sleep(1)
|
||||
self._unilab_send_msg_succ_cmd(True)
|
||||
time.sleep(1)
|
||||
while (self.request_rec_msg_status) == True:
|
||||
@@ -697,6 +708,23 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
print("data_electrolyte_code", data_electrolyte_code)
|
||||
print("data_coin_cell_code", data_coin_cell_code)
|
||||
#接收完信息后,读取完毕标志位置True
|
||||
liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8")
|
||||
#把物料解绑后放到另一盘上
|
||||
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2)
|
||||
battery._unilabos_state = {
|
||||
"electrolyte_name": data_coin_cell_code,
|
||||
"data_electrolyte_code": data_electrolyte_code,
|
||||
"open_circuit_voltage": data_open_circuit_voltage,
|
||||
"assembly_pressure": data_assembly_pressure,
|
||||
"electrolyte_volume": data_electrolyte_volume
|
||||
}
|
||||
liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None)
|
||||
#print(jipian2.parent)
|
||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||
"resources": [self.station_resource]
|
||||
})
|
||||
|
||||
|
||||
self._unilab_rec_msg_succ_cmd(True)
|
||||
time.sleep(1)
|
||||
#等待允许读取标志位置False
|
||||
@@ -756,7 +784,8 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
|
||||
|
||||
|
||||
def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool:
|
||||
def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="D:\\coin_cell_data") -> bool:
|
||||
elec_num, elec_use_num, elec_vol, assembly_type, assembly_pressure = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type), int(assembly_pressure)
|
||||
summary_csv_file = os.path.join(file_path, "duandian.csv")
|
||||
# 如果断点文件存在,先读取之前的进度
|
||||
if os.path.exists(summary_csv_file):
|
||||
@@ -784,54 +813,38 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
elec_num_N = 0
|
||||
elec_use_num_N = 0
|
||||
coin_num_N = 0
|
||||
|
||||
print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}")
|
||||
|
||||
for i in range(20):
|
||||
print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}")
|
||||
print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}")
|
||||
print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}")
|
||||
|
||||
#如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。
|
||||
if read_status_flag == False:
|
||||
pass
|
||||
#初始化
|
||||
self.func_pack_device_init()
|
||||
#self.func_pack_device_init()
|
||||
#切换自动
|
||||
self.func_pack_device_auto()
|
||||
#self.func_pack_device_auto()
|
||||
#启动,小车收回
|
||||
self.func_pack_device_start()
|
||||
#self.func_pack_device_start()
|
||||
#发送电解液瓶数量,启动搬运,多搬运没事
|
||||
self.func_pack_send_bottle_num(elec_num)
|
||||
#self.func_pack_send_bottle_num(elec_num)
|
||||
last_i = elec_num_N
|
||||
last_j = elec_use_num_N
|
||||
for i in range(last_i, elec_num):
|
||||
print(f"开始第{last_i+i+1}瓶电解液的组装")
|
||||
#第一个循环从上次断点继续,后续循环从0开始
|
||||
j_start = last_j if i == last_i else 0
|
||||
self.func_pack_send_msg_cmd(elec_use_num-j_start)
|
||||
self.func_pack_send_msg_cmd(elec_use_num-j_start, elec_vol, assembly_type, assembly_pressure)
|
||||
|
||||
for j in range(j_start, elec_use_num):
|
||||
print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装")
|
||||
#读取电池组装数据并存入csv
|
||||
self.func_pack_get_msg_cmd(file_path)
|
||||
time.sleep(1)
|
||||
|
||||
#这里定义物料系统
|
||||
# TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑
|
||||
liaopan1 = self.deck.get_resource("liaopan1")
|
||||
liaopan4 = self.deck.get_resource("liaopan4")
|
||||
jipian1 = liaopan1.children[coin_num_N].children[0]
|
||||
jipian4 = liaopan4.children[coin_num_N].children[0]
|
||||
#print(jipian1)
|
||||
#从料盘上去物料解绑后放到另一盘上
|
||||
jipian1.parent.unassign_child_resource(jipian1)
|
||||
jipian4.parent.unassign_child_resource(jipian4)
|
||||
|
||||
#print(jipian2.parent)
|
||||
battery = Battery(name = f"battery_{coin_num_N}")
|
||||
battery.assign_child_resource(jipian1, location=None)
|
||||
battery.assign_child_resource(jipian4, location=None)
|
||||
|
||||
zidanjia6 = self.deck.get_resource("zi_dan_jia6")
|
||||
|
||||
zidanjia6.children[0].assign_child_resource(battery, location=None)
|
||||
|
||||
|
||||
|
||||
# 生成断点文件
|
||||
# 生成包含elec_num_N、coin_num_N、timestamp的CSV文件
|
||||
@@ -842,6 +855,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp])
|
||||
csvfile.flush()
|
||||
coin_num_N += 1
|
||||
self.coin_num_N = coin_num_N
|
||||
elec_use_num_N += 1
|
||||
elec_num_N += 1
|
||||
elec_use_num_N = 0
|
||||
@@ -878,34 +892,22 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
|
||||
def fun_wuliao_test(self) -> bool:
|
||||
#找到data_init中构建的2个物料盘
|
||||
#liaopan1 = self.deck.get_resource("liaopan1")
|
||||
#liaopan4 = self.deck.get_resource("liaopan4")
|
||||
#for coin_num_N in range(16):
|
||||
# liaopan1 = self.deck.get_resource("liaopan1")
|
||||
# liaopan4 = self.deck.get_resource("liaopan4")
|
||||
# jipian1 = liaopan1.children[coin_num_N].children[0]
|
||||
# jipian4 = liaopan4.children[coin_num_N].children[0]
|
||||
# #print(jipian1)
|
||||
# #从料盘上去物料解绑后放到另一盘上
|
||||
# jipian1.parent.unassign_child_resource(jipian1)
|
||||
# jipian4.parent.unassign_child_resource(jipian4)
|
||||
#
|
||||
# #print(jipian2.parent)
|
||||
# battery = Battery(name = f"battery_{coin_num_N}")
|
||||
# battery.assign_child_resource(jipian1, location=None)
|
||||
# battery.assign_child_resource(jipian4, location=None)
|
||||
#
|
||||
# zidanjia6 = self.deck.get_resource("zi_dan_jia6")
|
||||
# zidanjia6.children[0].assign_child_resource(battery, location=None)
|
||||
# ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||
# "resources": [self.deck]
|
||||
# })
|
||||
# time.sleep(2)
|
||||
for i in range(20):
|
||||
print(f"输出{i}")
|
||||
time.sleep(2)
|
||||
|
||||
liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8")
|
||||
for i in range(16):
|
||||
battery = ElectrodeSheet(name=f"battery_{i}", size_x=16, size_y=16, size_z=2)
|
||||
battery._unilabos_state = {
|
||||
"diameter": 20.0,
|
||||
"height": 20.0,
|
||||
"assembly_pressure": i,
|
||||
"electrolyte_volume": 20.0,
|
||||
"electrolyte_name": f"DP{i}"
|
||||
}
|
||||
liaopan3.children[i].assign_child_resource(battery, location=None)
|
||||
|
||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||
"resources": [self.station_resource]
|
||||
})
|
||||
time.sleep(4)
|
||||
# 数据读取与输出
|
||||
def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"):
|
||||
# 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环
|
||||
@@ -1012,7 +1014,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
# else:
|
||||
# print("子弹夹洞位0没有极片")
|
||||
#
|
||||
# #把电解液从瓶中取到电池夹子中
|
||||
# # TODO:#把电解液从瓶中取到电池夹子中
|
||||
# battery_site = deck.get_resource("battery_press_1")
|
||||
# clip_magazine_battery = deck.get_resource("clip_magazine_battery")
|
||||
# if battery_site.has_battery():
|
||||
@@ -1119,24 +1121,52 @@ if __name__ == "__main__":
|
||||
#print("success")
|
||||
#创建一个物料台面
|
||||
|
||||
#deck = create_a_coin_cell_deck()
|
||||
deck = create_a_coin_cell_deck()
|
||||
#deck = create_a_full_coin_cell_deck()
|
||||
|
||||
|
||||
##在台面上找到料盘和极片
|
||||
#liaopan1 = deck.get_resource("liaopan1")
|
||||
#liaopan2 = deck.get_resource("liaopan2")
|
||||
#jipian1 = liaopan1.children[1].children[0]
|
||||
#
|
||||
##print(jipian1)
|
||||
##
|
||||
#print(jipian1)
|
||||
##把物料解绑后放到另一盘上
|
||||
#jipian1.parent.unassign_child_resource(jipian1)
|
||||
#liaopan2.children[1].assign_child_resource(jipian1, location=None)
|
||||
##print(jipian2.parent)
|
||||
|
||||
liaopan1 = deck.get_resource("liaopan1")
|
||||
liaopan2 = deck.get_resource("liaopan2")
|
||||
for i in range(16):
|
||||
#找到liaopan1上每一个jipian
|
||||
jipian_linshi = liaopan1.children[i].children[0]
|
||||
#把物料解绑后放到另一盘上
|
||||
print("极片:", jipian_linshi)
|
||||
jipian_linshi.parent.unassign_child_resource(jipian_linshi)
|
||||
liaopan2.children[i].assign_child_resource(jipian_linshi, location=None)
|
||||
|
||||
|
||||
from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type
|
||||
#with open("./button_battery_station_resources_unilab.json", "r", encoding="utf-8") as f:
|
||||
# bioyond_resources_unilab = json.load(f)
|
||||
#print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源")
|
||||
#ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource])
|
||||
#print(f"转换结果类型: {type(ulab_resources)}")
|
||||
#print(ulab_resources)
|
||||
|
||||
with open("./button_battery_decks_unilab.json", "r", encoding="utf-8") as f:
|
||||
bioyond_resources_unilab = json.load(f)
|
||||
print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源")
|
||||
ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource])
|
||||
print(f"转换结果类型: {type(ulab_resources)}")
|
||||
print(ulab_resources)
|
||||
|
||||
|
||||
from unilabos.resources.graphio import convert_resources_from_type
|
||||
from unilabos.config.config import BasicConfig
|
||||
BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55"
|
||||
BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561"
|
||||
from unilabos.app.web.client import http_client
|
||||
|
||||
resources = convert_resources_from_type([deck], [Resource])
|
||||
json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2)
|
||||
|
||||
#print(resources)
|
||||
http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
|
||||
|
||||
http_client.resource_add(resources)
|
||||
@@ -0,0 +1,63 @@
|
||||
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
|
||||
COIL_SYS_START_CMD,BOOL,,,,coil,8010,
|
||||
COIL_SYS_STOP_CMD,BOOL,,,,coil,8020,
|
||||
COIL_SYS_RESET_CMD,BOOL,,,,coil,8030,
|
||||
COIL_SYS_HAND_CMD,BOOL,,,,coil,8040,
|
||||
COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050,
|
||||
COIL_SYS_INIT_CMD,BOOL,,,,coil,8060,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700,
|
||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd
|
||||
COIL_SYS_START_STATUS,BOOL,,,,coil,8210,
|
||||
COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220,
|
||||
COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230,
|
||||
COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240,
|
||||
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250,
|
||||
COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260,
|
||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500,
|
||||
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status
|
||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000,
|
||||
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num
|
||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol
|
||||
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type
|
||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure
|
||||
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num
|
||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage
|
||||
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004,
|
||||
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006,
|
||||
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008,
|
||||
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight
|
||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time
|
||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure
|
||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume
|
||||
REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code()
|
||||
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code()
|
||||
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code()
|
||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure
|
||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content
|
||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content
|
||||
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720,
|
||||
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520,
|
||||
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496,
|
||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000,
|
||||
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730,
|
||||
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530,
|
||||
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8
|
||||
COIL_ALUMINUM_FOIL,BOOL,,ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,,coil,8340,
|
||||
REG_MSG_NE_PLATE_MATRIX,INT16,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ,,hold_register,440,
|
||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,<EFBFBD><EFBFBD>Ĥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ,,hold_register,450,
|
||||
REG_MSG_TIP_BOX_MATRIX,INT16,,<EFBFBD><EFBFBD>Һǹͷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ,,hold_register,480,
|
||||
REG_MSG_NE_PLATE_NUM,INT16,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,,hold_register,443,
|
||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,<EFBFBD><EFBFBD>Ĥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,,hold_register,453,
|
||||
REG_MSG_PRESS_MODE,BOOL,,ѹ<EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>false:ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>True:<3A><><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>,,coil,8360,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD>ģʽ
|
||||
,,,,,,,
|
||||
,BOOL,,<EFBFBD>Ӿ<EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8300,<EFBFBD>Ӿ<EFBFBD><EFBFBD><EFBFBD>λ
|
||||
,BOOL,,<EFBFBD><EFBFBD><EFBFBD>죨false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8310,<EFBFBD>Ӿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD>֣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8320,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>Ҳ֣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8420,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҳ<EFBFBD>
|
||||
,BOOL,,<EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD>֪<EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8350,<EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD>֪
|
||||
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>false:<3A><><EFBFBD>ε<EFBFBD>Һ<EFBFBD><D2BA>true:<3A><><EFBFBD>ε<EFBFBD>Һ<EFBFBD><D2BA>,,coil,8370,<EFBFBD><EFBFBD>Һģʽ
|
||||
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8380,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD>false:<3A><>װ<EFBFBD><D7B0>true:<3A><>װ<EFBFBD><D7B0>,,coil,8390,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ
|
||||
,BOOL,,ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ࣨfalse:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8400,ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̰<EFBFBD><EFBFBD>̷<EFBFBD>ʽ<EFBFBD><EFBFBD>false:ˮƽ<CBAE><C6BD><EFBFBD>̣<EFBFBD>true:<3A>ѵ<EFBFBD><D1B5><EFBFBD><EFBFBD>̣<EFBFBD>,,coil,8410,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>̷<EFBFBD>ʽ
|
||||
|
@@ -0,0 +1,691 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "BatteryStation",
|
||||
"name": "扣电工作站",
|
||||
"children": [
|
||||
"coin_cell_deck"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "bettery_station_registry",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"debug_mode": false,
|
||||
"_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定",
|
||||
"protocol_type": [],
|
||||
"station_resource": {
|
||||
"data": {
|
||||
"_resource_child_name": "coin_cell_deck",
|
||||
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck"
|
||||
}
|
||||
},
|
||||
|
||||
"address": "192.168.1.20",
|
||||
"port": 502
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "coin_cell_deck",
|
||||
"name": "coin_cell_deck",
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"\u7535\u6c60\u6599\u76d8"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "CoincellDeck",
|
||||
"size_x": 1000,
|
||||
"size_y": 1000,
|
||||
"size_z": 900,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "coin_cell_deck",
|
||||
"barcode": null
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8",
|
||||
"name": "\u7535\u6c60\u6599\u76d8",
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_0_0",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_0_1",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_0_2",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_0_3",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_1_0",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_1_1",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_1_2",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_1_3",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_2_0",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_2_1",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_2_2",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_2_3",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_3_0",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_3_1",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_3_2",
|
||||
"\u7535\u6c60\u6599\u76d8_materialhole_3_3"
|
||||
],
|
||||
"parent": "coin_cell_deck",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialPlate",
|
||||
"size_x": 120.8,
|
||||
"size_y": 160.5,
|
||||
"size_z": 10.0,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {
|
||||
"A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
|
||||
"B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
|
||||
"C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
|
||||
"D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
|
||||
"A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
|
||||
"B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
|
||||
"C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
|
||||
"D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
|
||||
"A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
|
||||
"B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
|
||||
"C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
|
||||
"D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
|
||||
"A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
|
||||
"B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
|
||||
"C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
|
||||
"D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 12.4,
|
||||
"y": 104.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 12.4,
|
||||
"y": 80.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 12.4,
|
||||
"y": 56.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 12.4,
|
||||
"y": 32.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 36.4,
|
||||
"y": 104.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 36.4,
|
||||
"y": 80.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 36.4,
|
||||
"y": 56.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 36.4,
|
||||
"y": 32.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 60.4,
|
||||
"y": 104.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 60.4,
|
||||
"y": 80.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 60.4,
|
||||
"y": 56.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 60.4,
|
||||
"y": 32.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 84.4,
|
||||
"y": 104.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 84.4,
|
||||
"y": 80.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 84.4,
|
||||
"y": 56.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3",
|
||||
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "\u7535\u6c60\u6599\u76d8",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 84.4,
|
||||
"y": 32.25,
|
||||
"z": 10.0
|
||||
},
|
||||
"config": {
|
||||
"type": "MaterialHole",
|
||||
"size_x": 16,
|
||||
"size_y": 16,
|
||||
"size_z": 16,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "material_hole",
|
||||
"model": null,
|
||||
"barcode": null
|
||||
},
|
||||
"data": {
|
||||
"diameter": 20,
|
||||
"depth": 10,
|
||||
"max_sheets": 1,
|
||||
"info": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
"""
|
||||
工作站基类
|
||||
Workstation Base Class - 简化版
|
||||
|
||||
基于PLR Deck的简化工作站架构
|
||||
专注于核心物料系统和工作流管理
|
||||
"""
|
||||
|
||||
import collections
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from pylabrobot.resources import Deck, Plate, Resource as PLRResource
|
||||
|
||||
from pylabrobot.resources.coordinate import Coordinate
|
||||
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
||||
|
||||
from unilabos.utils.log import logger
|
||||
|
||||
|
||||
class WorkflowStatus(Enum):
|
||||
"""工作流状态"""
|
||||
|
||||
IDLE = "idle"
|
||||
INITIALIZING = "initializing"
|
||||
RUNNING = "running"
|
||||
PAUSED = "paused"
|
||||
STOPPING = "stopping"
|
||||
STOPPED = "stopped"
|
||||
ERROR = "error"
|
||||
COMPLETED = "completed"
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowInfo:
|
||||
"""工作流信息"""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
estimated_duration: float # 预估持续时间(秒)
|
||||
required_materials: List[str] # 所需物料类型
|
||||
output_product: str # 输出产品类型
|
||||
parameters_schema: Dict[str, Any] # 参数架构
|
||||
|
||||
|
||||
class WorkStationContainer(Plate):
|
||||
"""
|
||||
WorkStation 专用 Container 类,继承自 Plate和TipRack
|
||||
注意这个物料必须通过plr_additional_res_reg.py注册到edge,才能正常序列化
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
category: str,
|
||||
ordering: collections.OrderedDict,
|
||||
model: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
这里的初始化入参要和plr的保持一致
|
||||
"""
|
||||
super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model)
|
||||
self._unilabos_state = {} # 必须有此行,自己的类描述的是物料的
|
||||
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""从给定的状态加载工作台信息。"""
|
||||
super().load_state(state)
|
||||
self._unilabos_state = state
|
||||
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
data = super().serialize_state()
|
||||
data.update(
|
||||
self._unilabos_state
|
||||
) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||
return data
|
||||
|
||||
|
||||
def get_workstation_plate_resource(name: str) -> PLRResource: # 要给定一个返回plr的方法
|
||||
"""
|
||||
用于获取一些模板,例如返回一个带有特定信息/子物料的 Plate,这里需要到注册表注册,例如unilabos/registry/resources/organic/workstation.yaml
|
||||
可以直接运行该函数或者利用注册表补全机制,来检查是否资源出错
|
||||
:param name: 资源名称
|
||||
:return: Resource对象
|
||||
"""
|
||||
plate = WorkStationContainer(
|
||||
name, size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()
|
||||
)
|
||||
tip_rack = WorkStationContainer(
|
||||
"tip_rack_inside_plate",
|
||||
size_x=50,
|
||||
size_y=50,
|
||||
size_z=10,
|
||||
category="tip_rack",
|
||||
ordering=collections.OrderedDict(),
|
||||
)
|
||||
plate.assign_child_resource(tip_rack, Coordinate.zero())
|
||||
return plate
|
||||
|
||||
|
||||
class ResourceSynchronizer(ABC):
|
||||
"""资源同步器基类
|
||||
|
||||
负责与外部物料系统的同步,并对 self.deck 做修改
|
||||
"""
|
||||
|
||||
def __init__(self, workstation: "WorkstationBase"):
|
||||
self.workstation = workstation
|
||||
|
||||
@abstractmethod
|
||||
async def sync_from_external(self) -> bool:
|
||||
"""从外部系统同步物料到本地deck"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def sync_to_external(self, plr_resource: PLRResource) -> bool:
|
||||
"""将本地物料同步到外部系统"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def handle_external_change(self, change_info: Dict[str, Any]) -> bool:
|
||||
"""处理外部系统的变更通知"""
|
||||
pass
|
||||
|
||||
|
||||
class WorkstationBase(ABC):
|
||||
"""工作站基类 - 简化版
|
||||
|
||||
核心功能:
|
||||
1. 基于 PLR Deck 的物料系统,支持格式转换
|
||||
2. 可选的资源同步器支持外部物料系统
|
||||
3. 简化的工作流管理
|
||||
"""
|
||||
|
||||
_ros_node: ROS2WorkstationNode
|
||||
|
||||
@property
|
||||
def _children(self) -> Dict[str, Any]: # 不要删除这个下划线,不然会自动导入注册表,后面改成装饰器识别
|
||||
return self._ros_node.children
|
||||
|
||||
async def update_resource_example(self):
|
||||
return await self._ros_node.update_resource([get_workstation_plate_resource("test")])
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
station_resource: PLRResource,
|
||||
*args,
|
||||
**kwargs, # 必须有kwargs
|
||||
):
|
||||
# 基本配置
|
||||
print(station_resource)
|
||||
self.deck_config = station_resource
|
||||
|
||||
# PLR 物料系统
|
||||
self.deck: Optional[Deck] = None
|
||||
self.plr_resources: Dict[str, PLRResource] = {}
|
||||
|
||||
# 资源同步器(可选)
|
||||
# self.resource_synchronizer = ResourceSynchronizer(self) # 要在driver中自行初始化,只有workstation用
|
||||
|
||||
# 硬件接口
|
||||
self.hardware_interface: Union[Any, str] = None
|
||||
|
||||
# 工作流状态
|
||||
self.current_workflow_status = WorkflowStatus.IDLE
|
||||
self.current_workflow_info = None
|
||||
self.workflow_start_time = None
|
||||
self.workflow_parameters = {}
|
||||
|
||||
# 支持的工作流(静态预定义)
|
||||
self.supported_workflows: Dict[str, WorkflowInfo] = {}
|
||||
|
||||
# 初始化物料系统
|
||||
self._initialize_material_system()
|
||||
|
||||
# 注册支持的工作流
|
||||
# self._register_supported_workflows()
|
||||
|
||||
# logger.info(f"工作站 {device_id} 初始化完成(简化版)")
|
||||
|
||||
def _initialize_material_system(self):
|
||||
"""初始化物料系统 - 使用 graphio 转换"""
|
||||
try:
|
||||
from unilabos.resources.graphio import resource_ulab_to_plr
|
||||
|
||||
# # 1. 合并 deck_config 和 children 创建完整的资源树
|
||||
# complete_resource_config = self._create_complete_resource_config()
|
||||
|
||||
# # 2. 使用 graphio 转换为 PLR 资源
|
||||
# self.deck = resource_ulab_to_plr(complete_resource_config, plr_model=True)
|
||||
|
||||
# # 3. 建立资源映射
|
||||
# self._build_resource_mappings(self.deck)
|
||||
|
||||
# # 4. 如果有资源同步器,执行初始同步
|
||||
# if self.resource_synchronizer:
|
||||
# # 这里可以异步执行,暂时跳过
|
||||
# pass
|
||||
|
||||
# logger.info(f"工作站 {self.device_id} 物料系统初始化成功,创建了 {len(self.plr_resources)} 个资源")
|
||||
pass
|
||||
except Exception as e:
|
||||
# logger.error(f"工作站 {self.device_id} 物料系统初始化失败: {e}")
|
||||
raise
|
||||
|
||||
def _create_complete_resource_config(self) -> Dict[str, Any]:
|
||||
"""创建完整的资源配置 - 合并 deck_config 和 children"""
|
||||
# 创建主 deck 配置
|
||||
deck_resource = {
|
||||
"id": f"{self.device_id}_deck",
|
||||
"name": f"{self.device_id}_deck",
|
||||
"type": "deck",
|
||||
"position": {"x": 0, "y": 0, "z": 0},
|
||||
"config": {
|
||||
"size_x": self.deck_config.get("size_x", 1000.0),
|
||||
"size_y": self.deck_config.get("size_y", 1000.0),
|
||||
"size_z": self.deck_config.get("size_z", 100.0),
|
||||
**{k: v for k, v in self.deck_config.items() if k not in ["size_x", "size_y", "size_z"]},
|
||||
},
|
||||
"data": {},
|
||||
"children": [],
|
||||
"parent": None,
|
||||
}
|
||||
|
||||
# 添加子资源
|
||||
if self._children:
|
||||
children_list = []
|
||||
for child_id, child_config in self._children.items():
|
||||
child_resource = self._normalize_child_resource(child_id, child_config, deck_resource["id"])
|
||||
children_list.append(child_resource)
|
||||
deck_resource["children"] = children_list
|
||||
|
||||
return deck_resource
|
||||
|
||||
def _normalize_child_resource(self, resource_id: str, config: Dict[str, Any], parent_id: str) -> Dict[str, Any]:
|
||||
"""标准化子资源配置"""
|
||||
return {
|
||||
"id": resource_id,
|
||||
"name": config.get("name", resource_id),
|
||||
"type": config.get("type", "container"),
|
||||
"position": self._normalize_position(config.get("position", {})),
|
||||
"config": config.get("config", {}),
|
||||
"data": config.get("data", {}),
|
||||
"children": [], # 简化版本:只支持一层子资源
|
||||
"parent": parent_id,
|
||||
}
|
||||
|
||||
def _normalize_position(self, position: Any) -> Dict[str, float]:
|
||||
"""标准化位置信息"""
|
||||
if isinstance(position, dict):
|
||||
return {
|
||||
"x": float(position.get("x", 0)),
|
||||
"y": float(position.get("y", 0)),
|
||||
"z": float(position.get("z", 0)),
|
||||
}
|
||||
elif isinstance(position, (list, tuple)) and len(position) >= 2:
|
||||
return {
|
||||
"x": float(position[0]),
|
||||
"y": float(position[1]),
|
||||
"z": float(position[2]) if len(position) > 2 else 0.0,
|
||||
}
|
||||
else:
|
||||
return {"x": 0.0, "y": 0.0, "z": 0.0}
|
||||
|
||||
def _build_resource_mappings(self, deck: Deck):
|
||||
"""递归构建资源映射"""
|
||||
|
||||
def add_resource_recursive(resource: PLRResource):
|
||||
if hasattr(resource, "name"):
|
||||
self.plr_resources[resource.name] = resource
|
||||
|
||||
if hasattr(resource, "children"):
|
||||
for child in resource.children:
|
||||
add_resource_recursive(child)
|
||||
|
||||
add_resource_recursive(deck)
|
||||
|
||||
# ============ 硬件接口管理 ============
|
||||
|
||||
def set_hardware_interface(self, hardware_interface: Union[Any, str]):
|
||||
"""设置硬件接口"""
|
||||
self.hardware_interface = hardware_interface
|
||||
logger.info(f"工作站 {self.device_id} 硬件接口设置: {type(hardware_interface).__name__}")
|
||||
|
||||
def set_workstation_node(self, workstation_node: "ROS2WorkstationNode"):
|
||||
"""设置协议节点引用(用于代理模式)"""
|
||||
self._ros_node = workstation_node
|
||||
logger.info(f"工作站 {self.device_id} 关联协议节点")
|
||||
|
||||
# ============ 设备操作接口 ============
|
||||
|
||||
def call_device_method(self, method: str, *args, **kwargs) -> Any:
|
||||
"""调用设备方法的统一接口"""
|
||||
# 1. 代理模式:通过协议节点转发
|
||||
if isinstance(self.hardware_interface, str) and self.hardware_interface.startswith("proxy:"):
|
||||
if not self._ros_node:
|
||||
raise RuntimeError("代理模式需要设置workstation_node")
|
||||
|
||||
device_id = self.hardware_interface[6:] # 移除 "proxy:" 前缀
|
||||
return self._ros_node.call_device_method(device_id, method, *args, **kwargs)
|
||||
|
||||
# 2. 直接模式:直接调用硬件接口方法
|
||||
elif self.hardware_interface and hasattr(self.hardware_interface, method):
|
||||
return getattr(self.hardware_interface, method)(*args, **kwargs)
|
||||
|
||||
else:
|
||||
raise AttributeError(f"硬件接口不支持方法: {method}")
|
||||
|
||||
def get_device_status(self) -> Dict[str, Any]:
|
||||
"""获取设备状态"""
|
||||
try:
|
||||
return self.call_device_method("get_status")
|
||||
except AttributeError:
|
||||
# 如果设备不支持get_status方法,返回基础状态
|
||||
return {
|
||||
"status": "unknown",
|
||||
"interface_type": type(self.hardware_interface).__name__,
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
|
||||
def is_device_available(self) -> bool:
|
||||
"""检查设备是否可用"""
|
||||
try:
|
||||
self.get_device_status()
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
# ============ 物料系统接口 ============
|
||||
|
||||
def get_deck(self) -> Deck:
|
||||
"""获取主 Deck"""
|
||||
return self.deck
|
||||
|
||||
def get_all_resources(self) -> Dict[str, PLRResource]:
|
||||
"""获取所有 PLR 资源"""
|
||||
return self.plr_resources.copy()
|
||||
|
||||
def find_resource_by_name(self, name: str) -> Optional[PLRResource]:
|
||||
"""按名称查找资源"""
|
||||
return self.plr_resources.get(name)
|
||||
|
||||
def find_resources_by_type(self, resource_type: type) -> List[PLRResource]:
|
||||
"""按类型查找资源"""
|
||||
return [res for res in self.plr_resources.values() if isinstance(res, resource_type)]
|
||||
|
||||
async def sync_with_external_system(self) -> bool:
|
||||
"""与外部物料系统同步"""
|
||||
if not self.resource_synchronizer:
|
||||
logger.info(f"工作站 {self.device_id} 没有配置资源同步器")
|
||||
return True
|
||||
|
||||
try:
|
||||
success = await self.resource_synchronizer.sync_from_external()
|
||||
if success:
|
||||
logger.info(f"工作站 {self.device_id} 外部同步成功")
|
||||
else:
|
||||
logger.warning(f"工作站 {self.device_id} 外部同步失败")
|
||||
return success
|
||||
except Exception as e:
|
||||
logger.error(f"工作站 {self.device_id} 外部同步异常: {e}")
|
||||
return False
|
||||
|
||||
# ============ 简化的工作流控制 ============
|
||||
|
||||
def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
|
||||
"""执行工作流"""
|
||||
try:
|
||||
# 设置工作流状态
|
||||
self.current_workflow_status = WorkflowStatus.INITIALIZING
|
||||
self.workflow_parameters = parameters
|
||||
self.workflow_start_time = time.time()
|
||||
|
||||
# 委托给子类实现
|
||||
success = self._execute_workflow_impl(workflow_name, parameters)
|
||||
|
||||
if success:
|
||||
self.current_workflow_status = WorkflowStatus.RUNNING
|
||||
logger.info(f"工作站 {self.device_id} 工作流 {workflow_name} 启动成功")
|
||||
else:
|
||||
self.current_workflow_status = WorkflowStatus.ERROR
|
||||
logger.error(f"工作站 {self.device_id} 工作流 {workflow_name} 启动失败")
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
self.current_workflow_status = WorkflowStatus.ERROR
|
||||
logger.error(f"工作站 {self.device_id} 执行工作流失败: {e}")
|
||||
return False
|
||||
|
||||
def stop_workflow(self, emergency: bool = False) -> bool:
|
||||
"""停止工作流"""
|
||||
try:
|
||||
if self.current_workflow_status in [WorkflowStatus.IDLE, WorkflowStatus.STOPPED]:
|
||||
logger.warning(f"工作站 {self.device_id} 没有正在运行的工作流")
|
||||
return True
|
||||
|
||||
self.current_workflow_status = WorkflowStatus.STOPPING
|
||||
|
||||
# 委托给子类实现
|
||||
success = self._stop_workflow_impl(emergency)
|
||||
|
||||
if success:
|
||||
self.current_workflow_status = WorkflowStatus.STOPPED
|
||||
logger.info(f"工作站 {self.device_id} 工作流停止成功 (紧急: {emergency})")
|
||||
else:
|
||||
self.current_workflow_status = WorkflowStatus.ERROR
|
||||
logger.error(f"工作站 {self.device_id} 工作流停止失败")
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
self.current_workflow_status = WorkflowStatus.ERROR
|
||||
logger.error(f"工作站 {self.device_id} 停止工作流失败: {e}")
|
||||
return False
|
||||
|
||||
# ============ 状态属性 ============
|
||||
|
||||
@property
|
||||
def workflow_status(self) -> WorkflowStatus:
|
||||
"""获取当前工作流状态"""
|
||||
return self.current_workflow_status
|
||||
|
||||
@property
|
||||
def is_busy(self) -> bool:
|
||||
"""检查工作站是否忙碌"""
|
||||
return self.current_workflow_status in [
|
||||
WorkflowStatus.INITIALIZING,
|
||||
WorkflowStatus.RUNNING,
|
||||
WorkflowStatus.STOPPING,
|
||||
]
|
||||
|
||||
@property
|
||||
def workflow_runtime(self) -> float:
|
||||
"""获取工作流运行时间(秒)"""
|
||||
if self.workflow_start_time is None:
|
||||
return 0.0
|
||||
return time.time() - self.workflow_start_time
|
||||
|
||||
# ============ 抽象方法 - 子类必须实现 ============
|
||||
|
||||
# @abstractmethod
|
||||
# def _register_supported_workflows(self):
|
||||
# """注册支持的工作流 - 子类必须实现"""
|
||||
# pass
|
||||
|
||||
# @abstractmethod
|
||||
# def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
|
||||
# """执行工作流的具体实现 - 子类必须实现"""
|
||||
# pass
|
||||
|
||||
# @abstractmethod
|
||||
# def _stop_workflow_impl(self, emergency: bool = False) -> bool:
|
||||
# """停止工作流的具体实现 - 子类必须实现"""
|
||||
# pass
|
||||
|
||||
class WorkstationExample(WorkstationBase):
|
||||
"""工作站示例实现"""
|
||||
|
||||
def _register_supported_workflows(self):
|
||||
"""注册支持的工作流"""
|
||||
self.supported_workflows["example_workflow"] = WorkflowInfo(
|
||||
name="example_workflow",
|
||||
description="这是一个示例工作流",
|
||||
estimated_duration=300.0,
|
||||
required_materials=["sample_plate"],
|
||||
output_product="processed_plate",
|
||||
parameters_schema={"param1": "string", "param2": "integer"},
|
||||
)
|
||||
|
||||
def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
|
||||
"""执行工作流的具体实现"""
|
||||
if workflow_name not in self.supported_workflows:
|
||||
logger.error(f"工作站 {self.device_id} 不支持工作流: {workflow_name}")
|
||||
return False
|
||||
|
||||
# 这里添加实际的工作流逻辑
|
||||
logger.info(f"工作站 {self.device_id} 正在执行工作流: {workflow_name} with parameters {parameters}")
|
||||
return True
|
||||
|
||||
def _stop_workflow_impl(self, emergency: bool = False) -> bool:
|
||||
"""停止工作流的具体实现"""
|
||||
# 这里添加实际的停止逻辑
|
||||
logger.info(f"工作站 {self.device_id} 正在停止工作流 (紧急: {emergency})")
|
||||
return True
|
||||
@@ -668,7 +668,7 @@ __all__ = [
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 简单测试HTTP服务
|
||||
class BioyondWorkstation:
|
||||
class DummyWorkstation:
|
||||
device_id = "WS-001"
|
||||
|
||||
def process_step_finish_report(self, report_request):
|
||||
|
||||
@@ -242,8 +242,11 @@ workstation.bioyond_dispensing_station:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
deck:
|
||||
type: string
|
||||
required:
|
||||
- config
|
||||
- deck
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
|
||||
834
unilabos/registry/devices/bioyond_cell.yaml
Normal file
834
unilabos/registry/devices/bioyond_cell.yaml
Normal file
@@ -0,0 +1,834 @@
|
||||
bioyond_cell:
|
||||
category:
|
||||
- bioyond_cell
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-auto_batch_outbound_from_xlsx:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
type: string
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: auto_batch_outbound_from_xlsx参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-auto_feeding4to3:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
WH3_x1_y1_z3_1_materialId: ''
|
||||
WH3_x1_y1_z3_1_materialType: ''
|
||||
WH3_x1_y1_z3_1_quantity: 0
|
||||
WH3_x1_y2_z3_4_materialId: ''
|
||||
WH3_x1_y2_z3_4_materialType: ''
|
||||
WH3_x1_y2_z3_4_quantity: 0
|
||||
WH3_x1_y3_z3_7_materialId: ''
|
||||
WH3_x1_y3_z3_7_materialType: ''
|
||||
WH3_x1_y3_z3_7_quantity: 0
|
||||
WH3_x1_y4_z3_10_materialId: ''
|
||||
WH3_x1_y4_z3_10_materialType: ''
|
||||
WH3_x1_y4_z3_10_quantity: 0
|
||||
WH3_x1_y5_z3_13_materialId: ''
|
||||
WH3_x1_y5_z3_13_materialType: ''
|
||||
WH3_x1_y5_z3_13_quantity: 0
|
||||
WH3_x2_y1_z3_2_materialId: ''
|
||||
WH3_x2_y1_z3_2_materialType: ''
|
||||
WH3_x2_y1_z3_2_quantity: 0
|
||||
WH3_x2_y2_z3_5_materialId: ''
|
||||
WH3_x2_y2_z3_5_materialType: ''
|
||||
WH3_x2_y2_z3_5_quantity: 0
|
||||
WH3_x2_y3_z3_8_materialId: ''
|
||||
WH3_x2_y3_z3_8_materialType: ''
|
||||
WH3_x2_y3_z3_8_quantity: 0
|
||||
WH3_x2_y4_z3_11_materialId: ''
|
||||
WH3_x2_y4_z3_11_materialType: ''
|
||||
WH3_x2_y4_z3_11_quantity: 0
|
||||
WH3_x2_y5_z3_14_materialId: ''
|
||||
WH3_x2_y5_z3_14_materialType: ''
|
||||
WH3_x2_y5_z3_14_quantity: 0
|
||||
WH3_x3_y1_z3_3_materialId: ''
|
||||
WH3_x3_y1_z3_3_materialType: ''
|
||||
WH3_x3_y1_z3_3_quantity: 0
|
||||
WH3_x3_y2_z3_6_materialId: ''
|
||||
WH3_x3_y2_z3_6_materialType: ''
|
||||
WH3_x3_y2_z3_6_quantity: 0
|
||||
WH3_x3_y3_z3_9_materialId: ''
|
||||
WH3_x3_y3_z3_9_materialType: ''
|
||||
WH3_x3_y3_z3_9_quantity: 0
|
||||
WH3_x3_y4_z3_12_materialId: ''
|
||||
WH3_x3_y4_z3_12_materialType: ''
|
||||
WH3_x3_y4_z3_12_quantity: 0
|
||||
WH3_x3_y5_z3_15_materialId: ''
|
||||
WH3_x3_y5_z3_15_materialType: ''
|
||||
WH3_x3_y5_z3_15_quantity: 0
|
||||
WH4_x1_y1_z1_1_materialName: ''
|
||||
WH4_x1_y1_z1_1_quantity: 0.0
|
||||
WH4_x1_y1_z2_1_materialName: ''
|
||||
WH4_x1_y1_z2_1_materialType: ''
|
||||
WH4_x1_y1_z2_1_quantity: 0.0
|
||||
WH4_x1_y1_z2_1_targetWH: ''
|
||||
WH4_x1_y2_z1_6_materialName: ''
|
||||
WH4_x1_y2_z1_6_quantity: 0.0
|
||||
WH4_x1_y2_z2_4_materialName: ''
|
||||
WH4_x1_y2_z2_4_materialType: ''
|
||||
WH4_x1_y2_z2_4_quantity: 0.0
|
||||
WH4_x1_y2_z2_4_targetWH: ''
|
||||
WH4_x1_y3_z1_11_materialName: ''
|
||||
WH4_x1_y3_z1_11_quantity: 0.0
|
||||
WH4_x1_y3_z2_7_materialName: ''
|
||||
WH4_x1_y3_z2_7_materialType: ''
|
||||
WH4_x1_y3_z2_7_quantity: 0.0
|
||||
WH4_x1_y3_z2_7_targetWH: ''
|
||||
WH4_x2_y1_z1_2_materialName: ''
|
||||
WH4_x2_y1_z1_2_quantity: 0.0
|
||||
WH4_x2_y1_z2_2_materialName: ''
|
||||
WH4_x2_y1_z2_2_materialType: ''
|
||||
WH4_x2_y1_z2_2_quantity: 0.0
|
||||
WH4_x2_y1_z2_2_targetWH: ''
|
||||
WH4_x2_y2_z1_7_materialName: ''
|
||||
WH4_x2_y2_z1_7_quantity: 0.0
|
||||
WH4_x2_y2_z2_5_materialName: ''
|
||||
WH4_x2_y2_z2_5_materialType: ''
|
||||
WH4_x2_y2_z2_5_quantity: 0.0
|
||||
WH4_x2_y2_z2_5_targetWH: ''
|
||||
WH4_x2_y3_z1_12_materialName: ''
|
||||
WH4_x2_y3_z1_12_quantity: 0.0
|
||||
WH4_x2_y3_z2_8_materialName: ''
|
||||
WH4_x2_y3_z2_8_materialType: ''
|
||||
WH4_x2_y3_z2_8_quantity: 0.0
|
||||
WH4_x2_y3_z2_8_targetWH: ''
|
||||
WH4_x3_y1_z1_3_materialName: ''
|
||||
WH4_x3_y1_z1_3_quantity: 0.0
|
||||
WH4_x3_y1_z2_3_materialName: ''
|
||||
WH4_x3_y1_z2_3_materialType: ''
|
||||
WH4_x3_y1_z2_3_quantity: 0.0
|
||||
WH4_x3_y1_z2_3_targetWH: ''
|
||||
WH4_x3_y2_z1_8_materialName: ''
|
||||
WH4_x3_y2_z1_8_quantity: 0.0
|
||||
WH4_x3_y2_z2_6_materialName: ''
|
||||
WH4_x3_y2_z2_6_materialType: ''
|
||||
WH4_x3_y2_z2_6_quantity: 0.0
|
||||
WH4_x3_y2_z2_6_targetWH: ''
|
||||
WH4_x3_y3_z2_9_materialName: ''
|
||||
WH4_x3_y3_z2_9_materialType: ''
|
||||
WH4_x3_y3_z2_9_quantity: 0.0
|
||||
WH4_x3_y3_z2_9_targetWH: ''
|
||||
WH4_x4_y1_z1_4_materialName: ''
|
||||
WH4_x4_y1_z1_4_quantity: 0.0
|
||||
WH4_x4_y2_z1_9_materialName: ''
|
||||
WH4_x4_y2_z1_9_quantity: 0.0
|
||||
WH4_x5_y1_z1_5_materialName: ''
|
||||
WH4_x5_y1_z1_5_quantity: 0.0
|
||||
WH4_x5_y2_z1_10_materialName: ''
|
||||
WH4_x5_y2_z1_10_quantity: 0.0
|
||||
xlsx_path: unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
WH3_x1_y1_z3_1_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y1_z3_1_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y1_z3_1_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x1_y2_z3_4_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y2_z3_4_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y2_z3_4_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x1_y3_z3_7_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y3_z3_7_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y3_z3_7_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x1_y4_z3_10_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y4_z3_10_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y4_z3_10_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x1_y5_z3_13_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y5_z3_13_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x1_y5_z3_13_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x2_y1_z3_2_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y1_z3_2_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y1_z3_2_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x2_y2_z3_5_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y2_z3_5_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y2_z3_5_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x2_y3_z3_8_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y3_z3_8_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y3_z3_8_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x2_y4_z3_11_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y4_z3_11_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y4_z3_11_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x2_y5_z3_14_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y5_z3_14_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x2_y5_z3_14_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x3_y1_z3_3_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y1_z3_3_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y1_z3_3_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x3_y2_z3_6_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y2_z3_6_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y2_z3_6_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x3_y3_z3_9_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y3_z3_9_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y3_z3_9_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x3_y4_z3_12_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y4_z3_12_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y4_z3_12_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH3_x3_y5_z3_15_materialId:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y5_z3_15_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH3_x3_y5_z3_15_quantity:
|
||||
default: 0
|
||||
type: number
|
||||
WH4_x1_y1_z1_1_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y1_z1_1_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x1_y1_z2_1_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y1_z2_1_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y1_z2_1_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x1_y1_z2_1_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y2_z1_6_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y2_z1_6_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x1_y2_z2_4_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y2_z2_4_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y2_z2_4_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x1_y2_z2_4_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y3_z1_11_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y3_z1_11_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x1_y3_z2_7_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y3_z2_7_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x1_y3_z2_7_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x1_y3_z2_7_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y1_z1_2_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y1_z1_2_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x2_y1_z2_2_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y1_z2_2_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y1_z2_2_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x2_y1_z2_2_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y2_z1_7_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y2_z1_7_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x2_y2_z2_5_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y2_z2_5_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y2_z2_5_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x2_y2_z2_5_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y3_z1_12_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y3_z1_12_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x2_y3_z2_8_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y3_z2_8_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x2_y3_z2_8_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x2_y3_z2_8_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y1_z1_3_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y1_z1_3_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x3_y1_z2_3_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y1_z2_3_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y1_z2_3_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x3_y1_z2_3_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y2_z1_8_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y2_z1_8_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x3_y2_z2_6_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y2_z2_6_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y2_z2_6_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x3_y2_z2_6_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y3_z2_9_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y3_z2_9_materialType:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x3_y3_z2_9_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x3_y3_z2_9_targetWH:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x4_y1_z1_4_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x4_y1_z1_4_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x4_y2_z1_9_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x4_y2_z1_9_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x5_y1_z1_5_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x5_y1_z1_5_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
WH4_x5_y2_z1_10_materialName:
|
||||
default: ''
|
||||
type: string
|
||||
WH4_x5_y2_z1_10_quantity:
|
||||
default: 0.0
|
||||
type: number
|
||||
xlsx_path:
|
||||
default: unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: auto_feeding4to3参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-auto_feeding4to3_from_xlsx:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
type: string
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: auto_feeding4to3_from_xlsx参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_orders:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
type: string
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_orders参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-order_list_v2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
beginTime: ''
|
||||
endTime: ''
|
||||
filter: ''
|
||||
pageCount: 1
|
||||
skipCount: 0
|
||||
sorting: ''
|
||||
status: ''
|
||||
timeType: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
beginTime:
|
||||
default: ''
|
||||
type: string
|
||||
endTime:
|
||||
default: ''
|
||||
type: string
|
||||
filter:
|
||||
default: ''
|
||||
type: string
|
||||
pageCount:
|
||||
default: 1
|
||||
type: integer
|
||||
skipCount:
|
||||
default: 0
|
||||
type: integer
|
||||
sorting:
|
||||
default: ''
|
||||
type: string
|
||||
status:
|
||||
default: ''
|
||||
type: string
|
||||
timeType:
|
||||
default: ''
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: order_list_v2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-report_material_change:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_obj: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_obj:
|
||||
type: object
|
||||
required:
|
||||
- material_obj
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: report_material_change参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_continue:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_continue参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_start:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_start参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_stop:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_stop参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-storage_batch_inbound:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
items: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- items
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: storage_batch_inbound参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-storage_inbound:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
location_id: null
|
||||
material_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
location_id:
|
||||
type: string
|
||||
material_id:
|
||||
type: string
|
||||
required:
|
||||
- material_id
|
||||
- location_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: storage_inbound参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_1_to_2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_1_to_2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_3_to_2_to_1:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
source_x: 1
|
||||
source_y: 1
|
||||
source_z: 1
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
source_wh_id:
|
||||
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
type: string
|
||||
source_x:
|
||||
default: 1
|
||||
type: integer
|
||||
source_y:
|
||||
default: 1
|
||||
type: integer
|
||||
source_z:
|
||||
default: 1
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_3_to_2_to_1参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_transfer_task:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
filter_text: null
|
||||
interval: 5
|
||||
timeout: 3000
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
filter_text:
|
||||
type: string
|
||||
interval:
|
||||
default: 5
|
||||
type: integer
|
||||
timeout:
|
||||
default: 3000
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_transfer_task参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: ''
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
bioyond_config:
|
||||
type: string
|
||||
station_resource:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
registry_type: device
|
||||
version: 1.0.0
|
||||
@@ -1,231 +1,3 @@
|
||||
hplc.agilent:
|
||||
category:
|
||||
- characterization_chromatic
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-check_status:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 检查安捷伦HPLC设备状态的函数。用于监控设备的运行状态、连接状态、错误信息等关键指标。该函数定期查询设备状态,确保系统稳定运行,及时发现和报告设备异常。适用于自动化流程中的设备监控、故障诊断、系统维护等场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: check_status参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-extract_data_from_txt:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
file_path: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 从文本文件中提取分析数据的函数。用于解析安捷伦HPLC生成的结果文件,提取峰面积、保留时间、浓度等关键分析数据。支持多种文件格式的自动识别和数据结构化处理,为后续数据分析和报告生成提供标准化的数据格式。适用于批量数据处理、结果验证、质量控制等分析工作流程。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
file_path:
|
||||
type: string
|
||||
required:
|
||||
- file_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: extract_data_from_txt参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-start_sequence:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
params: null
|
||||
resource: null
|
||||
wf_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动安捷伦HPLC分析序列的函数。用于执行预定义的分析方法序列,包括样品进样、色谱分离、检测等完整的分析流程。支持参数配置、资源分配、工作流程管理等功能,实现全自动的样品分析。适用于批量样品处理、标准化分析、质量检测等需要连续自动分析的应用场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
params:
|
||||
type: string
|
||||
resource:
|
||||
type: object
|
||||
wf_name:
|
||||
type: string
|
||||
required:
|
||||
- wf_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: start_sequence参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-try_close_sub_device:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
device_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 尝试关闭HPLC子设备的函数。用于安全地关闭泵、检测器、进样器等各个子模块,确保设备正常断开连接并保护硬件安全。该函数提供错误处理和状态确认机制,避免强制关闭可能造成的设备损坏。适用于设备维护、系统重启、紧急停机等需要安全关闭设备的场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
device_name:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: try_close_sub_device参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-try_open_sub_device:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
device_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 尝试打开HPLC子设备的函数。用于初始化和连接泵、检测器、进样器等各个子模块,建立设备通信并进行自检。该函数提供连接验证和错误恢复机制,确保子设备正常启动并准备就绪。适用于设备初始化、系统启动、设备重连等需要建立设备连接的场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
device_name:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: try_open_sub_device参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
execute_command_from_outer:
|
||||
feedback: {}
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
||||
status_types:
|
||||
could_run: bool
|
||||
data_file: String
|
||||
device_status: str
|
||||
driver_init_ok: bool
|
||||
finish_status: str
|
||||
is_running: bool
|
||||
status_text: str
|
||||
success: bool
|
||||
type: python
|
||||
config_info: []
|
||||
description: 安捷伦高效液相色谱(HPLC)分析设备,用于复杂化合物的分离、检测和定量分析。该设备通过UI自动化技术控制安捷伦ChemStation软件,实现全自动的样品分析流程。具备序列启动、设备状态监控、数据文件提取、结果处理等功能。支持多样品批量处理和实时状态反馈,适用于药物分析、环境检测、食品安全、化学研究等需要高精度色谱分析的实验室应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
driver_debug:
|
||||
default: false
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
could_run:
|
||||
type: boolean
|
||||
data_file:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
device_status:
|
||||
type: string
|
||||
driver_init_ok:
|
||||
type: boolean
|
||||
finish_status:
|
||||
type: string
|
||||
is_running:
|
||||
type: boolean
|
||||
status_text:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- status_text
|
||||
- device_status
|
||||
- could_run
|
||||
- driver_init_ok
|
||||
- is_running
|
||||
- success
|
||||
- finish_status
|
||||
- data_file
|
||||
type: object
|
||||
version: 1.0.0
|
||||
hplc.agilent-zhida:
|
||||
category:
|
||||
- characterization_chromatic
|
||||
|
||||
@@ -1,194 +1 @@
|
||||
raman.home_made:
|
||||
category:
|
||||
- characterization_optic
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-ccd_time:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
int_time: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 设置CCD检测器积分时间的函数。用于配置拉曼光谱仪的信号采集时间,控制光谱数据的质量和信噪比。较长的积分时间可获得更高的信号强度和更好的光谱质量,但会增加测量时间。该函数允许根据样品特性和测量要求动态调整检测参数,优化测量效果。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
int_time:
|
||||
type: string
|
||||
required:
|
||||
- int_time
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: ccd_time参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-laser_on_power:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
output_voltage_laser: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 设置激光器输出功率的函数。用于控制拉曼光谱仪激光器的功率输出,调节激光强度以适应不同样品的测量需求。适当的激光功率能够获得良好的拉曼信号同时避免样品损伤。该函数支持精确的功率控制,确保测量结果的稳定性和重现性。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
output_voltage_laser:
|
||||
type: string
|
||||
required:
|
||||
- output_voltage_laser
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: laser_on_power参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-raman_without_background:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
int_time: null
|
||||
laser_power: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 执行无背景扣除的拉曼光谱测量函数。用于直接采集样品的拉曼光谱信号,不进行背景校正处理。该函数配置积分时间和激光功率参数,获取原始光谱数据用于后续的数据处理分析。适用于对光谱数据质量要求较高或需要自定义背景处理流程的测量场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
int_time:
|
||||
type: string
|
||||
laser_power:
|
||||
type: string
|
||||
required:
|
||||
- int_time
|
||||
- laser_power
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: raman_without_background参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-raman_without_background_average:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
average: null
|
||||
int_time: null
|
||||
laser_power: null
|
||||
sample_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 执行多次平均的无背景拉曼光谱测量函数。通过多次测量取平均值来提高光谱数据的信噪比和测量精度,减少随机噪声影响。该函数支持自定义平均次数、积分时间、激光功率等参数,并可为样品指定名称便于数据管理。适用于对测量精度要求较高的定量分析和研究应用。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
average:
|
||||
type: string
|
||||
int_time:
|
||||
type: string
|
||||
laser_power:
|
||||
type: string
|
||||
sample_name:
|
||||
type: string
|
||||
required:
|
||||
- sample_name
|
||||
- int_time
|
||||
- laser_power
|
||||
- average
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: raman_without_background_average参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
raman_cmd:
|
||||
feedback: {}
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 拉曼光谱分析设备,用于物质的分子结构和化学成分表征。该设备集成激光器和CCD检测器,通过串口通信控制激光功率和光谱采集。具备背景扣除、多次平均、自动数据处理等功能,支持高精度的拉曼光谱测量。适用于材料表征、化学分析、质量控制、研究开发等需要分子指纹识别和结构分析的实验应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
baudrate_ccd:
|
||||
default: 921600
|
||||
type: string
|
||||
baudrate_laser:
|
||||
default: 9600
|
||||
type: string
|
||||
port_ccd:
|
||||
type: string
|
||||
port_laser:
|
||||
type: string
|
||||
required:
|
||||
- port_laser
|
||||
- port_ccd
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
{}
|
||||
|
||||
@@ -75,31 +75,6 @@ reaction_station.bioyond:
|
||||
title: load_bioyond_data_from_file参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-merge_workflow_with_parameters:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
json_str: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
json_str:
|
||||
type: string
|
||||
required:
|
||||
- json_str
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: merge_workflow_with_parameters参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-post_init:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -729,6 +704,7 @@ reaction_station.bioyond:
|
||||
status_types:
|
||||
all_workflows: dict
|
||||
bioyond_status: dict
|
||||
station_info: dict
|
||||
workstation_status: dict
|
||||
type: python
|
||||
config_info: []
|
||||
@@ -742,8 +718,6 @@ reaction_station.bioyond:
|
||||
type: string
|
||||
deck:
|
||||
type: string
|
||||
station_config:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
@@ -752,11 +726,14 @@ reaction_station.bioyond:
|
||||
type: object
|
||||
bioyond_status:
|
||||
type: object
|
||||
station_info:
|
||||
type: object
|
||||
workstation_status:
|
||||
type: object
|
||||
required:
|
||||
- bioyond_status
|
||||
- all_workflows
|
||||
- station_info
|
||||
- workstation_status
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -834,174 +834,3 @@ linear_motion.toyo_xyz.sim:
|
||||
mesh: toyo_xyz
|
||||
type: device
|
||||
version: 1.0.0
|
||||
motor.iCL42:
|
||||
category:
|
||||
- robot_linear_motion
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-execute_run_motor:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
mode: null
|
||||
position: null
|
||||
velocity: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 步进电机执行运动函数。直接执行电机运动命令,包括位置设定、速度控制和路径规划。该函数处理底层的电机控制协议,消除警告信息,设置运动参数并启动电机运行。适用于需要直接控制电机运动的应用场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
position:
|
||||
type: number
|
||||
velocity:
|
||||
type: integer
|
||||
required:
|
||||
- mode
|
||||
- position
|
||||
- velocity
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: execute_run_motor参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-init_device:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: iCL42电机设备初始化函数。建立与iCL42步进电机驱动器的串口通信连接,配置通信参数包括波特率、数据位、校验位等。该函数是电机使用前的必要步骤,确保驱动器处于可控状态并准备接收运动指令。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: init_device参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-run_motor:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
mode: null
|
||||
position: null
|
||||
velocity: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 步进电机运动控制函数。根据指定的运动模式、目标位置和速度参数控制电机运动。支持多种运动模式和精确的位置控制,自动处理运动轨迹规划和执行。该函数提供异步执行和状态反馈,确保运动的准确性和可靠性。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
position:
|
||||
type: number
|
||||
velocity:
|
||||
type: integer
|
||||
required:
|
||||
- mode
|
||||
- position
|
||||
- velocity
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: run_motor参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
execute_command_from_outer:
|
||||
feedback: {}
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
module: unilabos.devices.motor.iCL42:iCL42Driver
|
||||
status_types:
|
||||
is_executing_run: bool
|
||||
motor_position: int
|
||||
success: bool
|
||||
type: python
|
||||
config_info: []
|
||||
description: iCL42步进电机驱动器,用于实验室设备的精密线性运动控制。该设备通过串口通信控制iCL42型步进电机驱动器,支持多种运动模式和精确的位置、速度控制。具备位置反馈、运行状态监控和故障检测功能。适用于自动进样器、样品传送、精密定位平台等需要准确线性运动控制的实验室自动化设备。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
device_address:
|
||||
default: 1
|
||||
type: integer
|
||||
device_com:
|
||||
default: COM9
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
is_executing_run:
|
||||
type: boolean
|
||||
motor_position:
|
||||
type: integer
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- motor_position
|
||||
- is_executing_run
|
||||
- success
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,46 +1,130 @@
|
||||
BIOYOND_PolymerStation_1BottleCarrier:
|
||||
1BottleCarrier:
|
||||
category:
|
||||
- bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1BottleCarrier
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_1BottleCarrier
|
||||
type: pylabrobot
|
||||
description: BIOYOND_PolymerStation_1BottleCarrier
|
||||
description: 1BottleCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_1FlaskCarrier:
|
||||
1FlaskCarrier:
|
||||
category:
|
||||
- bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1FlaskCarrier
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_1FlaskCarrier
|
||||
type: pylabrobot
|
||||
description: BIOYOND_PolymerStation_1FlaskCarrier
|
||||
description: 1FlaskCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_6StockCarrier:
|
||||
6StockCarrier:
|
||||
category:
|
||||
- bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6StockCarrier
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_6StockCarrier
|
||||
type: pylabrobot
|
||||
description: BIOYOND_PolymerStation_6StockCarrier
|
||||
description: 6StockCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_6VialCarrier:
|
||||
6VialCarrier:
|
||||
category:
|
||||
- bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6VialCarrier
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_6VialCarrier
|
||||
type: pylabrobot
|
||||
description: BIOYOND_PolymerStation_6VialCarrier
|
||||
description: 6VialCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
6x5ml_DispensingVialCarrier:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_6x5ml_DispensingVialCarrier
|
||||
type: pylabrobot
|
||||
description: 6x5ml_DispensingVialCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
6x20ml_DispensingVialCarrier:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_6x20ml_DispensingVialCarrier
|
||||
type: pylabrobot
|
||||
description: 6x20ml_DispensingVialCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
6x_SmallSolutionBottleCarrier:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_6x_SmallSolutionBottleCarrier
|
||||
type: pylabrobot
|
||||
description: 6x_SmallSolutionBottleCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
4x_LargeSolutionBottleCarrier:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_4x_LargeSolutionBottleCarrier
|
||||
type: pylabrobot
|
||||
description: 4x_LargeSolutionBottleCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
6x_LargeDispenseHeadCarrier:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_6x_LargeDispenseHeadCarrier
|
||||
type: pylabrobot
|
||||
description: 6x_LargeDispenseHeadCarrier
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
AdapterBlock:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_AdapterBlock
|
||||
type: pylabrobot
|
||||
description: AdapterBlock
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
TipBox:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottle_carriers:YB_TipBox
|
||||
type: pylabrobot
|
||||
description: TipBox
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
|
||||
@@ -1,50 +1,281 @@
|
||||
BIOYOND_PolymerStation_Liquid_Vial:
|
||||
Liquid_Vial:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Liquid_Vial
|
||||
module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_Reagent_Bottle:
|
||||
Reagent_Bottle:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Reagent_Bottle
|
||||
module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_Solid_Stock:
|
||||
Solid_Stock:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Stock
|
||||
module: unilabos.resources.bioyond.bottles:YB_Solid_Stock
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_Solid_Vial:
|
||||
Solid_Vial:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Vial
|
||||
module: unilabos.resources.bioyond.bottles:YB_Solid_Vial
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
BIOYOND_PolymerStation_Solution_Beaker:
|
||||
Solution_Beaker:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solution_Beaker
|
||||
module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
100ml_Liquid_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
Liquid_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
High_Viscosity_Liquid_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
Large_Dispense_Head:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
5ml_Dispensing_Vial:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
20ml_Dispensing_Vial:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
Small_Solution_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
Large_Solution_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
Pipette_Tip:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
|
||||
YB_Liquid_Vial:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Reagent_Bottle:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Solid_Stock:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Solid_Stock
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Solid_Vial:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Solid_Vial
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Solution_Beaker:
|
||||
category:
|
||||
- bottles
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_100ml_Liquid_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Liquid_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_High_Viscosity_Liquid_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Large_Dispense_Head:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_5ml_Dispensing_Vial:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_20ml_Dispensing_Vial:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Small_Solution_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Large_Solution_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
YB_Pipette_Tip:
|
||||
category:
|
||||
- yb3
|
||||
class:
|
||||
module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip
|
||||
type: pylabrobot
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
version: 1.0.0
|
||||
|
||||
@@ -22,3 +22,15 @@ BIOYOND_PolymerReactionStation_Deck:
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_Deck16:
|
||||
category:
|
||||
- deck
|
||||
class:
|
||||
module: unilabos.resources.bioyond.decks:YB_Deck
|
||||
type: pylabrobot
|
||||
description: BIOYOND PolymerReactionStation Deck
|
||||
handles: []
|
||||
icon: 配液站.webp
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -2,11 +2,17 @@ from pylabrobot.resources import create_homogeneous_resources, Coordinate, Resou
|
||||
|
||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||
from unilabos.resources.bioyond.bottles import (
|
||||
BIOYOND_PolymerStation_Solid_Stock,
|
||||
BIOYOND_PolymerStation_Solid_Vial,
|
||||
BIOYOND_PolymerStation_Liquid_Vial,
|
||||
BIOYOND_PolymerStation_Solution_Beaker,
|
||||
BIOYOND_PolymerStation_Reagent_Bottle
|
||||
YB_Solid_Stock,
|
||||
YB_Solid_Vial,
|
||||
YB_Liquid_Vial,
|
||||
YB_Solution_Beaker,
|
||||
YB_Reagent_Bottle,
|
||||
YB_5ml_Dispensing_Vial,
|
||||
YB_20ml_Dispensing_Vial,
|
||||
YB_Small_Solution_Bottle,
|
||||
YB_Large_Solution_Bottle,
|
||||
YB_Large_Dispense_Head,
|
||||
YB_Pipette_Tip
|
||||
)
|
||||
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
||||
|
||||
@@ -51,13 +57,13 @@ def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier:
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="BIOYOND_Electrolyte_6VialCarrier",
|
||||
model="Electrolyte_6VialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 3
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
for i in range(6):
|
||||
carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_vial_{i+1}")
|
||||
carrier[i] = YB_Solid_Vial(f"{name}_vial_{i+1}")
|
||||
return carrier
|
||||
|
||||
|
||||
@@ -89,16 +95,16 @@ def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier:
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="BIOYOND_Electrolyte_1BottleCarrier",
|
||||
model="Electrolyte_1BottleCarrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = BIOYOND_PolymerStation_Solution_Beaker(f"{name}_beaker_1")
|
||||
carrier[0] = YB_Solution_Beaker(f"{name}_beaker_1")
|
||||
return carrier
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_6StockCarrier(name: str) -> BottleCarrier:
|
||||
def YB_6StockCarrier(name: str) -> BottleCarrier:
|
||||
"""6瓶载架 - 2x3布局"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
@@ -138,18 +144,18 @@ def BIOYOND_PolymerStation_6StockCarrier(name: str) -> BottleCarrier:
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="BIOYOND_PolymerStation_6VialCarrier",
|
||||
model="6StockCarrier",
|
||||
)
|
||||
carrier.num_items_x = 3
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
|
||||
for i in range(6):
|
||||
carrier[i] = BIOYOND_PolymerStation_Solid_Stock(f"{name}_vial_{ordering[i]}")
|
||||
carrier[i] = YB_Solid_Stock(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier:
|
||||
def YB_6VialCarrier(name: str) -> BottleCarrier:
|
||||
"""6瓶载架 - 2x3布局"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
@@ -189,20 +195,20 @@ def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier:
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="BIOYOND_PolymerStation_6VialCarrier",
|
||||
model="6VialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 3
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
|
||||
for i in range(3):
|
||||
carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_solidvial_{ordering[i]}")
|
||||
carrier[i] = YB_Solid_Vial(f"{name}_solidvial_{ordering[i]}")
|
||||
for i in range(3, 6):
|
||||
carrier[i] = BIOYOND_PolymerStation_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}")
|
||||
carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier:
|
||||
def YB_1BottleCarrier(name: str) -> BottleCarrier:
|
||||
"""1瓶载架 - 单个中央位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
@@ -230,16 +236,16 @@ def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier:
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="BIOYOND_PolymerStation_1BottleCarrier",
|
||||
model="1BottleCarrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_flask_1")
|
||||
carrier[0] = YB_Reagent_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier:
|
||||
def YB_1FlaskCarrier(name: str) -> BottleCarrier:
|
||||
"""1瓶载架 - 单个中央位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
@@ -267,10 +273,348 @@ def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier:
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="BIOYOND_PolymerStation_1FlaskCarrier",
|
||||
model="1FlaskCarrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_bottle_1")
|
||||
carrier[0] = YB_Reagent_Bottle(f"{name}_bottle_1")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier:
|
||||
"""5ml分液瓶板 - 4x2布局,8个位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 15.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="6x5ml_DispensingVialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier:
|
||||
"""20ml分液瓶板 - 4x2布局,8个位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 70.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 20.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="6x20ml_DispensingVialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier:
|
||||
"""配液瓶(小)板 - 4x2布局,8个位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 65.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 35.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="6x_SmallSolutionBottleCarrier",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_Small_Solution_Bottle(f"{name}_bottle_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier:
|
||||
"""配液瓶(大)板 - 2x2布局,4个位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 95.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 55.0
|
||||
bottle_spacing_x = 60.0 # X方向间距
|
||||
bottle_spacing_y = 60.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="4x_LargeSolutionBottleCarrier",
|
||||
)
|
||||
carrier.num_items_x = 2
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "B1", "B2"]
|
||||
for i in range(4):
|
||||
carrier[i] = YB_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier:
|
||||
"""加样头(大)板 - 1x1布局,1个位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 95.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 35.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=1,
|
||||
num_items_y=1,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="6x_LargeDispenseHeadCarrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_Large_Dispense_Head(f"{name}_head_1")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_AdapterBlock(name: str) -> BottleCarrier:
|
||||
"""适配器块 - 单个中央位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 30.0
|
||||
|
||||
# 适配器尺寸
|
||||
adapter_diameter = 80.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - adapter_diameter) / 2
|
||||
center_y = (carrier_size_y - adapter_diameter) / 2
|
||||
center_z = 0.0
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(center_x, center_y, center_z)],
|
||||
resource_size_x=adapter_diameter,
|
||||
resource_size_y=adapter_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="AdapterBlock",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
# 适配器块本身不包含瓶子,只是一个支撑结构
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_TipBox(name: str) -> BottleCarrier:
|
||||
"""枪头盒 - 8x12布局,96个位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 55.0
|
||||
|
||||
# 枪头尺寸
|
||||
tip_diameter = 10.0
|
||||
tip_spacing_x = 9.0 # X方向间距
|
||||
tip_spacing_y = 9.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2
|
||||
start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=tip_spacing_x,
|
||||
item_dy=tip_spacing_y,
|
||||
size_x=tip_diameter,
|
||||
size_y=tip_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="TipBox",
|
||||
)
|
||||
carrier.num_items_x = 12
|
||||
carrier.num_items_y = 8
|
||||
carrier.num_items_z = 1
|
||||
# 创建96个枪头
|
||||
for i in range(96):
|
||||
row = chr(65 + i // 12) # A-H
|
||||
col = (i % 12) + 1 # 1-12
|
||||
carrier[i] = YB_Pipette_Tip(f"{name}_tip_{row}{col}")
|
||||
return carrier
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||
# 工厂函数
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_Solid_Stock(
|
||||
def YB_Solid_Stock(
|
||||
name: str,
|
||||
diameter: float = 20.0,
|
||||
height: float = 100.0,
|
||||
@@ -12,15 +12,15 @@ def BIOYOND_PolymerStation_Solid_Stock(
|
||||
"""创建粉末瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
diameter=diameter,# 未知
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="BIOYOND_PolymerStation_Solid_Stock",
|
||||
model="Solid_Stock",
|
||||
)
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_Solid_Vial(
|
||||
def YB_Solid_Vial(
|
||||
name: str,
|
||||
diameter: float = 25.0,
|
||||
height: float = 60.0,
|
||||
@@ -34,11 +34,11 @@ def BIOYOND_PolymerStation_Solid_Vial(
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="BIOYOND_PolymerStation_Solid_Vial",
|
||||
model="Solid_Vial",
|
||||
)
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_Liquid_Vial(
|
||||
def YB_Liquid_Vial(
|
||||
name: str,
|
||||
diameter: float = 25.0,
|
||||
height: float = 60.0,
|
||||
@@ -52,11 +52,11 @@ def BIOYOND_PolymerStation_Liquid_Vial(
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="BIOYOND_PolymerStation_Liquid_Vial",
|
||||
model="Liquid_Vial",
|
||||
)
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_Solution_Beaker(
|
||||
def YB_Solution_Beaker(
|
||||
name: str,
|
||||
diameter: float = 60.0,
|
||||
height: float = 70.0,
|
||||
@@ -70,11 +70,11 @@ def BIOYOND_PolymerStation_Solution_Beaker(
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="BIOYOND_PolymerStation_Solution_Beaker",
|
||||
model="Solution_Beaker",
|
||||
)
|
||||
|
||||
|
||||
def BIOYOND_PolymerStation_Reagent_Bottle(
|
||||
def YB_Reagent_Bottle(
|
||||
name: str,
|
||||
diameter: float = 70.0,
|
||||
height: float = 120.0,
|
||||
@@ -88,5 +88,168 @@ def BIOYOND_PolymerStation_Reagent_Bottle(
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="BIOYOND_PolymerStation_Reagent_Bottle",
|
||||
model="Reagent_Bottle",
|
||||
)
|
||||
|
||||
|
||||
def YB_100ml_Liquid_Bottle(
|
||||
name: str,
|
||||
diameter: float = 50.0,
|
||||
height: float = 80.0,
|
||||
max_volume: float = 100000.0, # 100mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建100ml液体瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="100ml_Liquid_Bottle",
|
||||
)
|
||||
|
||||
|
||||
def YB_Liquid_Bottle(
|
||||
name: str,
|
||||
diameter: float = 40.0,
|
||||
height: float = 70.0,
|
||||
max_volume: float = 50000.0, # 50mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建液体瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="Liquid_Bottle",
|
||||
)
|
||||
|
||||
|
||||
def YB_High_Viscosity_Liquid_Bottle(
|
||||
name: str,
|
||||
diameter: float = 45.0,
|
||||
height: float = 75.0,
|
||||
max_volume: float = 60000.0, # 60mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建高粘液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="High_Viscosity_Liquid_Bottle",
|
||||
)
|
||||
|
||||
|
||||
def YB_Large_Dispense_Head(
|
||||
name: str,
|
||||
diameter: float = 35.0,
|
||||
height: float = 90.0,
|
||||
max_volume: float = 50000.0, # 50mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建加样头(大)"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="Large_Dispense_Head",
|
||||
)
|
||||
|
||||
|
||||
def YB_5ml_Dispensing_Vial(
|
||||
name: str,
|
||||
diameter: float = 15.0,
|
||||
height: float = 45.0,
|
||||
max_volume: float = 5000.0, # 5mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建5ml分液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="5ml_Dispensing_Vial",
|
||||
)
|
||||
|
||||
|
||||
def YB_20ml_Dispensing_Vial(
|
||||
name: str,
|
||||
diameter: float = 20.0,
|
||||
height: float = 65.0,
|
||||
max_volume: float = 20000.0, # 20mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建20ml分液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="20ml_Dispensing_Vial",
|
||||
)
|
||||
|
||||
|
||||
def YB_Small_Solution_Bottle(
|
||||
name: str,
|
||||
diameter: float = 35.0,
|
||||
height: float = 60.0,
|
||||
max_volume: float = 40000.0, # 40mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建配液瓶(小)"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="Small_Solution_Bottle",
|
||||
)
|
||||
|
||||
|
||||
def YB_Large_Solution_Bottle(
|
||||
name: str,
|
||||
diameter: float = 55.0,
|
||||
height: float = 90.0,
|
||||
max_volume: float = 150000.0, # 150mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建配液瓶(大)"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="Large_Solution_Bottle",
|
||||
)
|
||||
|
||||
|
||||
def YB_Pipette_Tip(
|
||||
name: str,
|
||||
diameter: float = 10.0,
|
||||
height: float = 50.0,
|
||||
max_volume: float = 1000.0, # 1mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建枪头"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="Pipette_Tip",
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from os import name
|
||||
from pylabrobot.resources import Deck, Coordinate, Rotation
|
||||
|
||||
from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling
|
||||
from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling, bioyond_warehouse_1x2x2, bioyond_warehouse_1x3x3, bioyond_warehouse_10x1x1, bioyond_warehouse_3x3x1, bioyond_warehouse_3x3x1_2, bioyond_warehouse_5x1x1
|
||||
|
||||
|
||||
class BIOYOND_PolymerReactionStation_Deck(Deck):
|
||||
@@ -66,3 +67,60 @@ class BIOYOND_PolymerPreparationStation_Deck(Deck):
|
||||
|
||||
for warehouse_name, warehouse in self.warehouses.items():
|
||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||
|
||||
class BIOYOND_YB_Deck(Deck):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "YB_Deck",
|
||||
size_x: float = 4150,
|
||||
size_y: float = 1400.0,
|
||||
size_z: float = 2670.0,
|
||||
category: str = "deck",
|
||||
setup: bool = False
|
||||
) -> None:
|
||||
super().__init__(name=name, size_x=4150.0, size_y=1400.0, size_z=2670.0)
|
||||
if setup:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
# 添加仓库
|
||||
self.warehouses = {
|
||||
"321窗口": bioyond_warehouse_1x2x2("321窗口"),
|
||||
"43窗口": bioyond_warehouse_1x2x2("43窗口"),
|
||||
"手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"),
|
||||
"手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"),
|
||||
"加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"),
|
||||
"加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"),
|
||||
|
||||
"15ml配液堆栈左": bioyond_warehouse_3x3x1("15ml配液堆栈左"),
|
||||
"母液加样右": bioyond_warehouse_3x3x1_2("母液加样右"),
|
||||
"大瓶母液堆栈左": bioyond_warehouse_5x1x1("大瓶母液堆栈左"),
|
||||
"大瓶母液堆栈右": bioyond_warehouse_5x1x1("大瓶母液堆栈右"),
|
||||
}
|
||||
# warehouse 的位置
|
||||
self.warehouse_locations = {
|
||||
"321窗口": Coordinate(-150.0, 158.0, 0.0),
|
||||
"43窗口": Coordinate(4160.0, 158.0, 0.0),
|
||||
"手动传递窗左": Coordinate(-150.0, 877.0, 0.0),
|
||||
"手动传递窗右": Coordinate(4160.0, 877.0, 0.0),
|
||||
"加样头堆栈左": Coordinate(385.0, 1300.0, 0.0),
|
||||
"加样头堆栈右": Coordinate(2187.0, 1300.0, 0.0),
|
||||
|
||||
"15ml配液堆栈左": Coordinate(749.0, 355.0, 0.0),
|
||||
"母液加样右": Coordinate(2152.0, 333.0, 0.0),
|
||||
"大瓶母液堆栈左": Coordinate(1164.0, 676.0, 0.0),
|
||||
"大瓶母液堆栈右": Coordinate(2717.0, 676.0, 0.0),
|
||||
}
|
||||
|
||||
for warehouse_name, warehouse in self.warehouses.items():
|
||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||
|
||||
def YB_Deck(name: str) -> Deck:
|
||||
by=BIOYOND_YB_Deck(name=name)
|
||||
by.setup()
|
||||
return by
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4,
|
||||
num_items_x=1,
|
||||
num_items_y=4,
|
||||
num_items_z=1,
|
||||
num_items_z=4,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
@@ -34,7 +34,113 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||
category="warehouse",
|
||||
removed_positions=None
|
||||
)
|
||||
# 定义benyond的堆栈
|
||||
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="YB_warehouse",
|
||||
)
|
||||
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=10,
|
||||
num_items_y=1,
|
||||
num_items_z=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_1x3x3(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
num_items_y=3,
|
||||
num_items_z=3,
|
||||
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_2x1x3(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_y=1,
|
||||
num_items_z=3,
|
||||
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_3x3x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=3,
|
||||
num_items_z=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_5x1x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=5,
|
||||
num_items_y=1,
|
||||
num_items_z=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_3x3x1_2(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=3,
|
||||
num_items_z=1,
|
||||
dx=12.0,
|
||||
dy=12.0,
|
||||
dz=12.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
||||
"""创建BioYond开关盖加液模块台面"""
|
||||
|
||||
@@ -624,6 +624,8 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
||||
Returns:
|
||||
pylabrobot 格式的物料列表
|
||||
"""
|
||||
print("1:bioyond_materials:",bioyond_materials)
|
||||
# print("2:type_mapping:",type_mapping)
|
||||
plr_materials = []
|
||||
|
||||
for material in bioyond_materials:
|
||||
|
||||
Reference in New Issue
Block a user