Compare commits

...

5 Commits

Author SHA1 Message Date
Xuwznln
c85c49817d Fix workstation startup
Update registry
2025-10-13 15:06:30 +08:00
Xuwznln
c70eafa5f0 Fix one-key installation build for windows 2025-10-13 15:06:29 +08:00
Junhan Chang
b64466d443 modify default config 2025-10-13 15:06:26 +08:00
Junhan Chang
ef3f24ed48 add plr_to_bioyond, and refactor bioyond stations 2025-10-13 15:06:25 +08:00
Xuwznln
2a8e8d014b Fix conda pack on windows 2025-10-13 13:19:45 +08:00
18 changed files with 2337 additions and 7015 deletions

View File

@@ -207,8 +207,7 @@ jobs:
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: |
echo Packing unilab environment with conda-pack...
mamba install conda-pack -c conda-forge -y
conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
mamba activate unilab && conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
echo Pack file created:
dir unilab-env-${{ matrix.platform }}.tar.gz

View File

@@ -172,7 +172,7 @@ Examples:
with open(output_path, "w", encoding="utf-8") as f:
f.write(readme_content)
print(f" README.txt created: {output_path}")
print(f" README.txt created: {output_path}")
print(f" Platform: {args.platform}")
print(f" Branch: {args.branch}")

View File

@@ -12,23 +12,13 @@ lab_registry.setup()
type_mapping = {
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
"样品板": "BIOYOND_PolymerStation_6StockCarrier",
"分装板": "BIOYOND_PolymerStation_6VialCarrier",
"样品瓶": "BIOYOND_PolymerStation_Solid_Stock",
"90%分装小瓶": "BIOYOND_PolymerStation_Solid_Vial",
"10%分装小瓶": "BIOYOND_PolymerStation_Liquid_Vial",
}
type_uuid_mapping = {
"烧杯": "",
"试剂瓶": "",
"样品板": "",
"分装板": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
"样品瓶": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
"90%分装小瓶": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
"10%分装小瓶": "3a14196c-76be-2279-4e22-7310d69aed68",
"烧杯": ("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"),
}

View File

@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""
测试修改后的 get_child_identifier 函数
"""
from unilabos.resources.itemized_carrier import ItemizedCarrier, Bottle
from pylabrobot.resources.coordinate import Coordinate
def test_get_child_identifier_with_indices():
"""测试返回x,y,z索引的 get_child_identifier 函数"""
# 创建一些测试瓶子
bottle1 = Bottle("bottle1", diameter=25.0, height=50.0, max_volume=15.0)
bottle1.location = Coordinate(10, 20, 5)
bottle2 = Bottle("bottle2", diameter=25.0, height=50.0, max_volume=15.0)
bottle2.location = Coordinate(50, 20, 5)
bottle3 = Bottle("bottle3", diameter=25.0, height=50.0, max_volume=15.0)
bottle3.location = Coordinate(90, 20, 5)
# 创建载架,指定维度
sites = {
"A1": bottle1,
"A2": bottle2,
"A3": bottle3,
"B1": None, # 空位
"B2": None,
"B3": None
}
carrier = ItemizedCarrier(
name="test_carrier",
size_x=150,
size_y=100,
size_z=30,
num_items_x=3, # 3列
num_items_y=2, # 2行
num_items_z=1, # 1层
sites=sites
)
print("测试载架维度:")
print(f"num_items_x: {carrier.num_items_x}")
print(f"num_items_y: {carrier.num_items_y}")
print(f"num_items_z: {carrier.num_items_z}")
print()
# 测试获取bottle1的标识符信息 (A1 = idx:0, x:0, y:0, z:0)
result1 = carrier.get_child_identifier(bottle1)
print("测试bottle1 (A1):")
print(f" identifier: {result1['identifier']}")
print(f" idx: {result1['idx']}")
print(f" x index: {result1['x']}")
print(f" y index: {result1['y']}")
print(f" z index: {result1['z']}")
# Assert 验证 bottle1 (A1) 的结果
assert result1['identifier'] == 'A1', f"Expected identifier 'A1', got '{result1['identifier']}'"
assert result1['idx'] == 0, f"Expected idx 0, got {result1['idx']}"
assert result1['x'] == 0, f"Expected x index 0, got {result1['x']}"
assert result1['y'] == 0, f"Expected y index 0, got {result1['y']}"
assert result1['z'] == 0, f"Expected z index 0, got {result1['z']}"
print(" ✓ bottle1 (A1) 测试通过")
print()
# 测试获取bottle2的标识符信息 (A2 = idx:1, x:1, y:0, z:0)
result2 = carrier.get_child_identifier(bottle2)
print("测试bottle2 (A2):")
print(f" identifier: {result2['identifier']}")
print(f" idx: {result2['idx']}")
print(f" x index: {result2['x']}")
print(f" y index: {result2['y']}")
print(f" z index: {result2['z']}")
# Assert 验证 bottle2 (A2) 的结果
assert result2['identifier'] == 'A2', f"Expected identifier 'A2', got '{result2['identifier']}'"
assert result2['idx'] == 1, f"Expected idx 1, got {result2['idx']}"
assert result2['x'] == 1, f"Expected x index 1, got {result2['x']}"
assert result2['y'] == 0, f"Expected y index 0, got {result2['y']}"
assert result2['z'] == 0, f"Expected z index 0, got {result2['z']}"
print(" ✓ bottle2 (A2) 测试通过")
print()
# 测试获取bottle3的标识符信息 (A3 = idx:2, x:2, y:0, z:0)
result3 = carrier.get_child_identifier(bottle3)
print("测试bottle3 (A3):")
print(f" identifier: {result3['identifier']}")
print(f" idx: {result3['idx']}")
print(f" x index: {result3['x']}")
print(f" y index: {result3['y']}")
print(f" z index: {result3['z']}")
# Assert 验证 bottle3 (A3) 的结果
assert result3['identifier'] == 'A3', f"Expected identifier 'A3', got '{result3['identifier']}'"
assert result3['idx'] == 2, f"Expected idx 2, got {result3['idx']}"
assert result3['x'] == 2, f"Expected x index 2, got {result3['x']}"
assert result3['y'] == 0, f"Expected y index 0, got {result3['y']}"
assert result3['z'] == 0, f"Expected z index 0, got {result3['z']}"
print(" ✓ bottle3 (A3) 测试通过")
print()
# 测试错误情况:查找不存在的资源
bottle_not_exists = Bottle("bottle_not_exists", diameter=25.0, height=50.0, max_volume=15.0)
try:
carrier.get_child_identifier(bottle_not_exists)
assert False, "应该抛出 ValueError 异常"
except ValueError as e:
print("✓ 正确抛出了 ValueError 异常:", str(e))
assert "is not assigned to this carrier" in str(e), "异常消息应该包含预期的文本"
print("\n🎉 所有测试都通过了!")
if __name__ == "__main__":
test_get_child_identifier_with_indices()

View File

@@ -37,7 +37,7 @@ def _initialize_material_system(self, deck_config: Dict[str, Any], children_conf
**定义在**: `workstation_base.py`
**设计目的**
- 提供外部物料系统如Bioyong、LIMS等集成的标准接口
- 提供外部物料系统如Bioyond、LIMS等集成的标准接口
- 双向同步从外部系统同步到本地deck以及将本地变更同步到外部系统
- 处理外部系统的变更通知
@@ -59,7 +59,7 @@ async def handle_external_change(self, change_info: Dict[str, Any]) -> bool:
**扩展功能**
- HTTP报送接收服务集成
- 具体工作流实现(液体转移、板洗等)
- Bioyong物料系统同步器示例
- Bioyond物料系统同步器示例
- 外部报送处理方法
## 技术栈
@@ -142,11 +142,11 @@ success = workstation.execute_workflow("liquid_transfer", {
### 3. 外部系统集成
```python
class BioyongResourceSynchronizer(ResourceSynchronizer):
"""Bioyong系统同步器"""
class BioyondResourceSynchronizer(ResourceSynchronizer):
"""Bioyond系统同步器"""
async def sync_from_external(self) -> bool:
# 从Bioyong API获取物料
# 从Bioyond API获取物料
external_materials = await self._fetch_bioyong_materials()
# 转换并添加到本地deck

File diff suppressed because it is too large Load Diff

View File

@@ -9,22 +9,6 @@ API_CONFIG = {
"api_host": ""
}
# 站点类型配置
STATION_TYPES = {
"REACTION": "reaction_station", # 仅反应站
"DISPENSING": "dispensing_station", # 仅配液站
"HYBRID": "hybrid_station" # 混合模式
}
# 默认站点配置
DEFAULT_STATION_CONFIG = {
"station_type": STATION_TYPES["REACTION"], # 默认反应站模式
"enable_reaction_station": True, # 是否启用反应站功能
"enable_dispensing_station": False, # 是否启用配液站功能
"station_name": "BioyondReactionStation", # 站点名称
"description": "Bioyond反应工作站" # 站点描述
}
# 工作流映射配置
WORKFLOW_MAPPINGS = {
"reactor_taken_out": "",
@@ -49,52 +33,75 @@ WORKFLOW_TO_SECTION_MAP = {
}
# 库位映射配置
LOCATION_MAPPING = {
'A01': '',
'A02': '',
'A03': '',
'A04': '',
'A05': '',
'A06': '',
'A07': '',
'A08': '',
'B01': '',
'B02': '',
'B03': '',
'B04': '',
'B05': '',
'B06': '',
'B07': '',
'B08': '',
'C01': '',
'C02': '',
'C03': '',
'C04': '',
'C05': '',
'C06': '',
'C07': '',
'C08': '',
'D01': '',
'D02': '',
'D03': '',
'D04': '',
'D05': '',
'D06': '',
'D07': '',
'D08': '',
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"
}
},
"溶液堆栈": {
"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"
}
},
"试剂堆栈": {
"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"
}
}
}
# 物料类型配置
MATERIAL_TYPE_IDS = {
"样品板": "",
"样品": "",
"烧杯": ""
}
MATERIAL_TYPE_MAPPINGS = {
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
"样品板": "BIOYOND_PolymerStation_6VialCarrier",
"烧杯": ("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"),
}
# 步骤参数配置各工作流的步骤UUID
@@ -127,3 +134,5 @@ WORKFLOW_STEP_IDS = {
"observe": ""
}
}
LOCATION_MAPPING = {}

View File

@@ -0,0 +1,824 @@
from datetime import datetime
import json
from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import BioyondException
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
class BioyondDispensingStation(BioyondWorkstation):
def __init__(self, config):
super().__init__(config)
# self.config = config
# self.api_key = config["api_key"]
# self.host = config["api_host"]
#
# # 使用简单的Logger替代原来的logger
# self._logger = SimpleLogger()
# self.is_running = False
# 90%10%小瓶投料任务创建方法
def create_90_10_vial_feeding_task(self,
order_name: str = None,
speed: str = None,
temperature: str = None,
delay_time: str = None,
percent_90_1_assign_material_name: str = None,
percent_90_1_target_weigh: str = None,
percent_90_2_assign_material_name: str = None,
percent_90_2_target_weigh: str = None,
percent_90_3_assign_material_name: str = None,
percent_90_3_target_weigh: str = None,
percent_10_1_assign_material_name: str = None,
percent_10_1_target_weigh: str = None,
percent_10_1_volume: str = None,
percent_10_1_liquid_material_name: str = None,
percent_10_2_assign_material_name: str = None,
percent_10_2_target_weigh: str = None,
percent_10_2_volume: str = None,
percent_10_2_liquid_material_name: str = None,
percent_10_3_assign_material_name: str = None,
percent_10_3_target_weigh: str = None,
percent_10_3_volume: str = None,
percent_10_3_liquid_material_name: str = None,
hold_m_name: str = None) -> dict:
"""
创建90%10%小瓶投料任务
参数说明:
- order_name: 任务名称如果为None则使用默认名称
- speed: 搅拌速度如果为None则使用默认值400
- temperature: 温度如果为None则使用默认值40
- delay_time: 延迟时间如果为None则使用默认值600
- percent_90_1_assign_material_name: 90%_1物料名称
- percent_90_1_target_weigh: 90%_1目标重量
- percent_90_2_assign_material_name: 90%_2物料名称
- percent_90_2_target_weigh: 90%_2目标重量
- percent_90_3_assign_material_name: 90%_3物料名称
- percent_90_3_target_weigh: 90%_3目标重量
- percent_10_1_assign_material_name: 10%_1固体物料名称
- percent_10_1_target_weigh: 10%_1固体目标重量
- percent_10_1_volume: 10%_1液体体积
- percent_10_1_liquid_material_name: 10%_1液体物料名称
- percent_10_2_assign_material_name: 10%_2固体物料名称
- percent_10_2_target_weigh: 10%_2固体目标重量
- percent_10_2_volume: 10%_2液体体积
- percent_10_2_liquid_material_name: 10%_2液体物料名称
- percent_10_3_assign_material_name: 10%_3固体物料名称
- percent_10_3_target_weigh: 10%_3固体目标重量
- percent_10_3_volume: 10%_3液体体积
- percent_10_3_liquid_material_name: 10%_3液体物料名称
- hold_m_name: 库位名称,如"C01"用于查找对应的holdMId
返回: 任务创建结果
异常:
- BioyondException: 各种错误情况下的统一异常
"""
try:
# 1. 参数验证
if not hold_m_name:
raise BioyondException("hold_m_name 是必填参数")
# 检查90%物料参数的完整性
# 90%_1物料如果有物料名称或目标重量就必须有全部参数
if percent_90_1_assign_material_name or percent_90_1_target_weigh:
if not percent_90_1_assign_material_name:
raise BioyondException("90%_1物料如果提供了目标重量必须同时提供物料名称")
if not percent_90_1_target_weigh:
raise BioyondException("90%_1物料如果提供了物料名称必须同时提供目标重量")
# 90%_2物料如果有物料名称或目标重量就必须有全部参数
if percent_90_2_assign_material_name or percent_90_2_target_weigh:
if not percent_90_2_assign_material_name:
raise BioyondException("90%_2物料如果提供了目标重量必须同时提供物料名称")
if not percent_90_2_target_weigh:
raise BioyondException("90%_2物料如果提供了物料名称必须同时提供目标重量")
# 90%_3物料如果有物料名称或目标重量就必须有全部参数
if percent_90_3_assign_material_name or percent_90_3_target_weigh:
if not percent_90_3_assign_material_name:
raise BioyondException("90%_3物料如果提供了目标重量必须同时提供物料名称")
if not percent_90_3_target_weigh:
raise BioyondException("90%_3物料如果提供了物料名称必须同时提供目标重量")
# 检查10%物料参数的完整性
# 10%_1物料如果有物料名称、目标重量、体积或液体物料名称中的任何一个就必须有全部参数
if any([percent_10_1_assign_material_name, percent_10_1_target_weigh, percent_10_1_volume, percent_10_1_liquid_material_name]):
if not percent_10_1_assign_material_name:
raise BioyondException("10%_1物料如果提供了其他参数必须同时提供固体物料名称")
if not percent_10_1_target_weigh:
raise BioyondException("10%_1物料如果提供了其他参数必须同时提供固体目标重量")
if not percent_10_1_volume:
raise BioyondException("10%_1物料如果提供了其他参数必须同时提供液体体积")
if not percent_10_1_liquid_material_name:
raise BioyondException("10%_1物料如果提供了其他参数必须同时提供液体物料名称")
# 10%_2物料如果有物料名称、目标重量、体积或液体物料名称中的任何一个就必须有全部参数
if any([percent_10_2_assign_material_name, percent_10_2_target_weigh, percent_10_2_volume, percent_10_2_liquid_material_name]):
if not percent_10_2_assign_material_name:
raise BioyondException("10%_2物料如果提供了其他参数必须同时提供固体物料名称")
if not percent_10_2_target_weigh:
raise BioyondException("10%_2物料如果提供了其他参数必须同时提供固体目标重量")
if not percent_10_2_volume:
raise BioyondException("10%_2物料如果提供了其他参数必须同时提供液体体积")
if not percent_10_2_liquid_material_name:
raise BioyondException("10%_2物料如果提供了其他参数必须同时提供液体物料名称")
# 10%_3物料如果有物料名称、目标重量、体积或液体物料名称中的任何一个就必须有全部参数
if any([percent_10_3_assign_material_name, percent_10_3_target_weigh, percent_10_3_volume, percent_10_3_liquid_material_name]):
if not percent_10_3_assign_material_name:
raise BioyondException("10%_3物料如果提供了其他参数必须同时提供固体物料名称")
if not percent_10_3_target_weigh:
raise BioyondException("10%_3物料如果提供了其他参数必须同时提供固体目标重量")
if not percent_10_3_volume:
raise BioyondException("10%_3物料如果提供了其他参数必须同时提供液体体积")
if not percent_10_3_liquid_material_name:
raise BioyondException("10%_3物料如果提供了其他参数必须同时提供液体物料名称")
# 2. 生成任务编码和设置默认值
order_code = "task_vial_" + str(int(datetime.now().timestamp()))
if order_name is None:
order_name = "90%10%小瓶投料任务"
if speed is None:
speed = "400"
if temperature is None:
temperature = "40"
if delay_time is None:
delay_time = "600"
# 3. 工作流ID
workflow_id = "3a19310d-16b9-9d81-b109-0748e953694b"
# 4. 查询工作流对应的holdMID
material_info = self.hardware_interface.material_id_query(workflow_id)
if not material_info:
raise BioyondException(f"无法查询工作流 {workflow_id} 的物料信息")
# 获取locations列表
locations = material_info.get("locations", []) if isinstance(material_info, dict) else []
if not locations:
raise BioyondException(f"工作流 {workflow_id} 没有找到库位信息")
# 查找指定名称的库位
hold_mid = None
for location in locations:
if location.get("holdMName") == hold_m_name:
hold_mid = location.get("holdMId")
break
if not hold_mid:
raise BioyondException(f"未找到库位名称为 {hold_m_name} 的库位,请检查名称是否正确")
extend_properties = f"{{\"{ hold_mid }\": {{}}}}"
self.hardware_interface._logger.info(f"找到库位 {hold_m_name} 对应的holdMId: {hold_mid}")
# 5. 构建任务参数
order_data = {
"orderCode": order_code,
"orderName": order_name,
"workflowId": workflow_id,
"borderNumber": 1,
"paramValues": {},
"ExtendProperties": extend_properties
}
# 添加搅拌参数
order_data["paramValues"]["e8264e47-c319-d9d9-8676-4dd5cb382b11"] = [
{"m": 0, "n": 3, "Key": "speed", "Value": speed},
{"m": 0, "n": 3, "Key": "temperature", "Value": temperature}
]
# 添加延迟时间参数
order_data["paramValues"]["dc5dba79-5e4b-8eae-cbc5-e93482e43b1f"] = [
{"m": 0, "n": 4, "Key": "DelayTime", "Value": delay_time}
]
# 添加90%_1参数
if percent_90_1_assign_material_name is not None and percent_90_1_target_weigh is not None:
order_data["paramValues"]["e7d3c0a3-25c2-c42d-c84b-860c4a5ef844"] = [
{"m": 15, "n": 1, "Key": "targetWeigh", "Value": percent_90_1_target_weigh},
{"m": 15, "n": 1, "Key": "assignMaterialName", "Value": percent_90_1_assign_material_name}
]
# 添加90%_2参数
if percent_90_2_assign_material_name is not None and percent_90_2_target_weigh is not None:
order_data["paramValues"]["50b912c4-6c81-0734-1c8b-532428b2a4a5"] = [
{"m": 18, "n": 1, "Key": "targetWeigh", "Value": percent_90_2_target_weigh},
{"m": 18, "n": 1, "Key": "assignMaterialName", "Value": percent_90_2_assign_material_name}
]
# 添加90%_3参数
if percent_90_3_assign_material_name is not None and percent_90_3_target_weigh is not None:
order_data["paramValues"]["9c3674b3-c7cb-946e-fa03-fa2861d8aec4"] = [
{"m": 21, "n": 1, "Key": "targetWeigh", "Value": percent_90_3_target_weigh},
{"m": 21, "n": 1, "Key": "assignMaterialName", "Value": percent_90_3_assign_material_name}
]
# 添加10%_1固体参数
if percent_10_1_assign_material_name is not None and percent_10_1_target_weigh is not None:
order_data["paramValues"]["73a0bfd8-1967-45e9-4bab-c07ccd1a2727"] = [
{"m": 3, "n": 1, "Key": "targetWeigh", "Value": percent_10_1_target_weigh},
{"m": 3, "n": 1, "Key": "assignMaterialName", "Value": percent_10_1_assign_material_name}
]
# 添加10%_1液体参数
if percent_10_1_liquid_material_name is not None and percent_10_1_volume is not None:
order_data["paramValues"]["39634d40-c623-473a-8e5f-bc301aca2522"] = [
{"m": 3, "n": 3, "Key": "volume", "Value": percent_10_1_volume},
{"m": 3, "n": 3, "Key": "assignMaterialName", "Value": percent_10_1_liquid_material_name}
]
# 添加10%_2固体参数
if percent_10_2_assign_material_name is not None and percent_10_2_target_weigh is not None:
order_data["paramValues"]["2d9c16fa-2a19-cd47-a67b-3cadff9e3e3d"] = [
{"m": 7, "n": 1, "Key": "targetWeigh", "Value": percent_10_2_target_weigh},
{"m": 7, "n": 1, "Key": "assignMaterialName", "Value": percent_10_2_assign_material_name}
]
# 添加10%_2液体参数
if percent_10_2_liquid_material_name is not None and percent_10_2_volume is not None:
order_data["paramValues"]["e60541bb-ed68-e839-7305-2b4abe38a13d"] = [
{"m": 7, "n": 3, "Key": "volume", "Value": percent_10_2_volume},
{"m": 7, "n": 3, "Key": "assignMaterialName", "Value": percent_10_2_liquid_material_name}
]
# 添加10%_3固体参数
if percent_10_3_assign_material_name is not None and percent_10_3_target_weigh is not None:
order_data["paramValues"]["27494733-0f71-a916-7cd2-1929a0125f17"] = [
{"m": 11, "n": 1, "Key": "targetWeigh", "Value": percent_10_3_target_weigh},
{"m": 11, "n": 1, "Key": "assignMaterialName", "Value": percent_10_3_assign_material_name}
]
# 添加10%_3液体参数
if percent_10_3_liquid_material_name is not None and percent_10_3_volume is not None:
order_data["paramValues"]["c8798c29-786f-6858-7d7f-5330b890f2a6"] = [
{"m": 11, "n": 3, "Key": "volume", "Value": percent_10_3_volume},
{"m": 11, "n": 3, "Key": "assignMaterialName", "Value": percent_10_3_liquid_material_name}
]
# 6. 转换为JSON字符串并创建任务
json_str = json.dumps([order_data], ensure_ascii=False)
self.hardware_interface._logger.info(f"创建90%10%小瓶投料任务参数: {json_str}")
# 7. 调用create_order方法创建任务
result = self.hardware_interface.create_order(json_str)
self.hardware_interface._logger.info(f"创建90%10%小瓶投料任务结果: {result}")
return json.dumps({"suc": True})
except BioyondException:
# 重新抛出BioyondException
raise
except Exception as e:
# 捕获其他未预期的异常转换为BioyondException
error_msg = f"创建90%10%小瓶投料任务时发生未预期的错误: {str(e)}"
self.hardware_interface._logger.error(error_msg)
raise BioyondException(error_msg)
# 二胺溶液配置任务创建方法
def create_diamine_solution_task(self,
order_name: str = None,
material_name: str = None,
target_weigh: str = None,
volume: str = None,
liquid_material_name: str = "NMP",
speed: str = None,
temperature: str = None,
delay_time: str = None,
hold_m_name: str = None) -> dict:
"""
创建二胺溶液配置任务
参数说明:
- order_name: 任务名称如果为None则使用默认名称
- material_name: 固体物料名称,必填
- target_weigh: 固体目标重量,必填
- volume: 液体体积,必填
- liquid_material_name: 液体物料名称默认为NMP
- speed: 搅拌速度如果为None则使用默认值400
- temperature: 温度如果为None则使用默认值20
- delay_time: 延迟时间如果为None则使用默认值600
- hold_m_name: 库位名称,如"ODA-1"用于查找对应的holdMId
返回: 任务创建结果
异常:
- BioyondException: 各种错误情况下的统一异常
"""
try:
# 1. 参数验证
if not material_name:
raise BioyondException("material_name 是必填参数")
if not target_weigh:
raise BioyondException("target_weigh 是必填参数")
if not volume:
raise BioyondException("volume 是必填参数")
if not hold_m_name:
raise BioyondException("hold_m_name 是必填参数")
# 2. 生成任务编码和设置默认值
order_code = "task_oda_" + str(int(datetime.now().timestamp()))
if order_name is None:
order_name = f"二胺溶液配置-{material_name}"
if speed is None:
speed = "400"
if temperature is None:
temperature = "20"
if delay_time is None:
delay_time = "600"
# 3. 工作流ID - 二胺溶液配置工作流
workflow_id = "3a15d4a1-3bbe-76f9-a458-292896a338f5"
# 4. 查询工作流对应的holdMID
material_info = self.material_id_query(workflow_id)
if not material_info:
raise BioyondException(f"无法查询工作流 {workflow_id} 的物料信息")
# 获取locations列表
locations = material_info.get("locations", []) if isinstance(material_info, dict) else []
if not locations:
raise BioyondException(f"工作流 {workflow_id} 没有找到库位信息")
# 查找指定名称的库位
hold_mid = None
for location in locations:
if location.get("holdMName") == hold_m_name:
hold_mid = location.get("holdMId")
break
if not hold_mid:
raise BioyondException(f"未找到库位名称为 {hold_m_name} 的库位,请检查名称是否正确")
extend_properties = f"{{\"{ hold_mid }\": {{}}}}"
self.hardware_interface._logger.info(f"找到库位 {hold_m_name} 对应的holdMId: {hold_mid}")
# 5. 构建任务参数
order_data = {
"orderCode": order_code,
"orderName": order_name,
"workflowId": workflow_id,
"borderNumber": 1,
"paramValues": {
# 固体物料参数
"3a15d4a1-3bde-f5bc-053f-1ae0bf1f357e": [
{"m": 3, "n": 2, "Key": "targetWeigh", "Value": target_weigh},
{"m": 3, "n": 2, "Key": "assignMaterialName", "Value": material_name}
],
# 液体物料参数
"3a15d4a1-3bde-d584-b309-e661ae8f1c01": [
{"m": 3, "n": 3, "Key": "volume", "Value": volume},
{"m": 3, "n": 3, "Key": "assignMaterialName", "Value": liquid_material_name}
],
# 搅拌参数
"3a15d4a1-3bde-8ec4-1ced-92efc97ed73d": [
{"m": 3, "n": 6, "Key": "speed", "Value": speed},
{"m": 3, "n": 6, "Key": "temperature", "Value": temperature}
],
# 延迟时间参数
"3a15d4a1-3bde-3b92-83ff-8923a0addbbc": [
{"m": 3, "n": 7, "Key": "DelayTime", "Value": delay_time}
]
},
"ExtendProperties": extend_properties
}
# 6. 转换为JSON字符串并创建任务
json_str = json.dumps([order_data], ensure_ascii=False)
self.hardware_interface._logger.info(f"创建二胺溶液配置任务参数: {json_str}")
# 7. 调用create_order方法创建任务
result = self.hardware_interface.create_order(json_str)
self.hardware_interface._logger.info(f"创建二胺溶液配置任务结果: {result}")
return json.dumps({"suc": True})
except BioyondException:
# 重新抛出BioyondException
raise
except Exception as e:
# 捕获其他未预期的异常转换为BioyondException
error_msg = f"创建二胺溶液配置任务时发生未预期的错误: {str(e)}"
self.hardware_interface._logger.error(error_msg)
raise BioyondException(error_msg)
if __name__ == "__main__":
bioyond = BioyondDispensingStation(config={
"api_key": "DE9BDDA0",
"api_host": "http://192.168.1.200:44388"
})
# 示例1使用material_id_query查询工作流对应的holdMID
workflow_id_1 = "3a15d4a1-3bbe-76f9-a458-292896a338f5" # 二胺溶液配置工作流ID
workflow_id_2 = "3a19310d-16b9-9d81-b109-0748e953694b" # 90%10%小瓶投料工作流ID
#示例2创建二胺溶液配置任务 - ODA指定库位名称
# bioyond.create_diamine_solution_task(
# order_code="task_oda_" + str(int(datetime.now().timestamp())),
# order_name="二胺溶液配置-ODA",
# material_name="ODA-1",
# target_weigh="12.000",
# volume="60",
# liquid_material_name= "NMP",
# speed="400",
# temperature="20",
# delay_time="600",
# hold_m_name="烧杯ODA"
# )
# bioyond.create_diamine_solution_task(
# order_code="task_pda_" + str(int(datetime.now().timestamp())),
# order_name="二胺溶液配置-PDA",
# material_name="PDA-1",
# target_weigh="4.178",
# volume="60",
# liquid_material_name= "NMP",
# speed="400",
# temperature="20",
# delay_time="600",
# hold_m_name="烧杯PDA-2"
# )
# bioyond.create_diamine_solution_task(
# order_code="task_mpda_" + str(int(datetime.now().timestamp())),
# order_name="二胺溶液配置-MPDA",
# material_name="MPDA-1",
# target_weigh="3.298",
# volume="50",
# liquid_material_name= "NMP",
# speed="400",
# temperature="20",
# delay_time="600",
# hold_m_name="烧杯MPDA"
# )
bioyond.material_id_query("3a19310d-16b9-9d81-b109-0748e953694b")
bioyond.material_id_query("3a15d4a1-3bbe-76f9-a458-292896a338f5")
#示例4创建90%10%小瓶投料任务
# vial_result = bioyond.create_90_10_vial_feeding_task(
# order_code="task_vial_" + str(int(datetime.now().timestamp())),
# order_name="90%10%小瓶投料-1",
# percent_90_1_assign_material_name="BTDA-1",
# percent_90_1_target_weigh="7.392",
# percent_90_2_assign_material_name="BTDA-1",
# percent_90_2_target_weigh="7.392",
# percent_90_3_assign_material_name="BTDA-2",
# percent_90_3_target_weigh="7.392",
# percent_10_1_assign_material_name="BTDA-2",
# percent_10_1_target_weigh="1.500",
# percent_10_1_volume="20",
# percent_10_1_liquid_material_name="NMP",
# # percent_10_2_assign_material_name="BTDA-c",
# # percent_10_2_target_weigh="1.2",
# # percent_10_2_volume="20",
# # percent_10_2_liquid_material_name="NMP",
# speed="400",
# temperature="60",
# delay_time="1200",
# hold_m_name="8.4分装板-1"
# )
# vial_result = bioyond.create_90_10_vial_feeding_task(
# order_code="task_vial_" + str(int(datetime.now().timestamp())),
# order_name="90%10%小瓶投料-2",
# percent_90_1_assign_material_name="BPDA-1",
# percent_90_1_target_weigh="5.006",
# percent_90_2_assign_material_name="PMDA-1",
# percent_90_2_target_weigh="3.810",
# percent_90_3_assign_material_name="BPDA-1",
# percent_90_3_target_weigh="8.399",
# percent_10_1_assign_material_name="BPDA-1",
# percent_10_1_target_weigh="1.200",
# percent_10_1_volume="20",
# percent_10_1_liquid_material_name="NMP",
# percent_10_2_assign_material_name="BPDA-1",
# percent_10_2_target_weigh="1.200",
# percent_10_2_volume="20",
# percent_10_2_liquid_material_name="NMP",
# speed="400",
# temperature="60",
# delay_time="1200",
# hold_m_name="8.4分装板-2"
# )
#启动调度器
#bioyond.scheduler_start()
#继续调度器
#bioyond.scheduler_continue()
result0 = bioyond.stock_material('{"typeMode": 0, "includeDetail": true}')
result1 = bioyond.stock_material('{"typeMode": 1, "includeDetail": true}')
result2 = bioyond.stock_material('{"typeMode": 2, "includeDetail": true}')
matpos1 = bioyond.query_warehouse_by_material_type("3a14196e-b7a0-a5da-1931-35f3000281e9")
matpos2 = bioyond.query_warehouse_by_material_type("3a14196e-5dfe-6e21-0c79-fe2036d052c4")
matpos3 = bioyond.query_warehouse_by_material_type("3a14196b-24f2-ca49-9081-0cab8021bf1a")
#样品板(里面有样品瓶)
material_data_yp = {
"typeId": "3a14196e-b7a0-a5da-1931-35f3000281e9",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "8.4样品板",
"unit": "",
"quantity": 1,
"details": [
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "BTDA-1",
"quantity": 20,
"x": 1,
"y": 1,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "BPDA-1",
"quantity": 20,
"x": 2,
"y": 1, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "BTDA-2",
"quantity": 20,
"x": 1,
"y": 2, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "PMDA-1",
"quantity": 20,
"x": 2,
"y": 2, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
}
],
"Parameters":"{}"
}
material_data_yp = {
"typeId": "3a14196e-b7a0-a5da-1931-35f3000281e9",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "8.7样品板",
"unit": "",
"quantity": 1,
"details": [
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "mianfen",
"quantity": 13,
"x": 1,
"y": 1,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "mianfen2",
"quantity": 13,
"x": 1,
"y": 2, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
}
],
"Parameters":"{}"
}
#分装板
material_data_fzb_1 = {
"typeId": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "8.7分装板",
"unit": "",
"quantity": 1,
"details": [
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶1",
"quantity": 1,
"x": 1,
"y": 1,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶2",
"quantity": 1,
"x": 1,
"y": 2,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶3",
"quantity": 1,
"x": 1,
"y": 3,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶1",
"quantity": 1,
"x": 2,
"y": 1, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶2",
"quantity": 1,
"x": 2,
"y": 2,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶3",
"quantity": 1,
"x": 2,
"y": 3,
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
}
],
"Parameters":"{}"
}
material_data_fzb_2 = {
"typeId": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "8.4分装板-2",
"unit": "",
"quantity": 1,
"details": [
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶1",
"quantity": 1,
"x": 1,
"y": 1,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶2",
"quantity": 1,
"x": 1,
"y": 2,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶3",
"quantity": 1,
"x": 1,
"y": 3,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶1",
"quantity": 1,
"x": 2,
"y": 1, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶2",
"quantity": 1,
"x": 2,
"y": 2,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶3",
"quantity": 1,
"x": 2,
"y": 3,
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
}
],
"Parameters":"{}"
}
#烧杯
material_data_sb_oda = {
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "mianfen1",
"unit": "",
"quantity": 1,
"Parameters":"{}"
}
material_data_sb_pda_2 = {
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "mianfen2",
"unit": "",
"quantity": 1,
"Parameters":"{}"
}
# material_data_sb_mpda = {
# "typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
# #"code": "物料编码001",
# #"barCode": "物料条码001",
# "name": "烧杯MPDA",
# "unit": "个",
# "quantity": 1,
# "Parameters":"{}"
# }
#result_1 = bioyond.add_material(json.dumps(material_data_yp, ensure_ascii=False))
#result_2 = bioyond.add_material(json.dumps(material_data_fzb_1, ensure_ascii=False))
# result_3 = bioyond.add_material(json.dumps(material_data_fzb_2, ensure_ascii=False))
# result_4 = bioyond.add_material(json.dumps(material_data_sb_oda, ensure_ascii=False))
# result_5 = bioyond.add_material(json.dumps(material_data_sb_pda_2, ensure_ascii=False))
# #result会返回id
# #样品板1id3a1b3e7d-339d-0291-dfd3-13e2a78fe521
# #将指定物料入库到指定库位
#bioyond.material_inbound(result_1, "3a14198e-6929-31f0-8a22-0f98f72260df")
#bioyond.material_inbound(result_2, "3a14198e-6929-46fe-841e-03dd753f1e4a")
# bioyond.material_inbound(result_3, "3a14198e-6929-72ac-32ce-9b50245682b8")
# bioyond.material_inbound(result_4, "3a14198e-d724-e036-afdc-2ae39a7f3383")
# bioyond.material_inbound(result_5, "3a14198e-d724-d818-6d4f-5725191a24b5")
#bioyond.material_outbound(result_1, "3a14198e-6929-31f0-8a22-0f98f72260df")
# bioyond.stock_material('{"typeMode": 2, "includeDetail": true}')
query_order = {"status":"100", "pageCount": "10"}
bioyond.order_query(json.dumps(query_order, ensure_ascii=False))
# id = "3a1bce3c-4f31-c8f3-5525-f3b273bc34dc"
# bioyond.sample_waste_removal(id)

View File

@@ -0,0 +1,207 @@
import json
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
from unilabos.devices.workstation.bioyond_studio.config import (
API_CONFIG, WORKFLOW_MAPPINGS, WORKFLOW_STEP_IDS, MATERIAL_TYPE_MAPPINGS,
STATION_TYPES, DEFAULT_STATION_CONFIG
)
class BioyondReactionStation(BioyondWorkstation):
def __init__(self, config: dict = None):
super().__init__(config)
# 工作流方法
def reactor_taken_out(self):
"""反应器取出"""
self.hardware_interface.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_out"}')
reactor_taken_out_params = {"param_values": {}}
self.hardware_interface.pending_task_params.append(reactor_taken_out_params)
print(f"成功添加反应器取出工作流")
print(f"当前队列长度: {len(self.hardware_interface.pending_task_params)}")
return json.dumps({"suc": True})
def reactor_taken_in(self, assign_material_name: str, cutoff: str = "900000", temperature: float = -10.00):
"""反应器放入"""
self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_in"}')
material_id = self._get_material_id_by_name(assign_material_name)
if isinstance(temperature, str):
temperature = float(temperature)
step_id = WORKFLOW_STEP_IDS["reactor_taken_in"]["config"]
reactor_taken_in_params = {
"param_values": {
step_id: [
{"m": 0, "n": 3, "Key": "cutoff", "Value": cutoff},
{"m": 0, "n": 3, "Key": "temperature", "Value": f"{temperature:.2f}"},
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
]
}
}
self.pending_task_params.append(reactor_taken_in_params)
print(f"成功添加反应器放入参数: material={assign_material_name}->ID:{material_id}, cutoff={cutoff}, temp={temperature:.2f}")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def solid_feeding_vials(self, material_id: str, time: str = "0", torque_variation: str = "1",
assign_material_name: str = None, temperature: float = 25.00):
"""固体进料小瓶"""
self.append_to_workflow_sequence('{"web_workflow_name": "Solid_feeding_vials"}')
material_id_m = self._get_material_id_by_name(assign_material_name)
if isinstance(temperature, str):
temperature = float(temperature)
feeding_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["feeding"]
observe_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["observe"]
solid_feeding_vials_params = {
"param_values": {
feeding_id: [
{"m": 0, "n": 3, "Key": "materialId", "Value": material_id},
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id_m}
],
observe_id: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": torque_variation},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
self.pending_task_params.append(solid_feeding_vials_params)
print(f"成功添加固体进料小瓶参数: material_id={material_id}, time={time}min, temp={temperature:.2f}°C")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def liquid_feeding_vials_non_titration(self, volumeFormula: str, assign_material_name: str,
titration_type: str = "1", time: str = "0",
torque_variation: str = "1", temperature: float = 25.00):
"""液体进料小瓶(非滴定)"""
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_vials(non-titration)"}')
material_id = self._get_material_id_by_name(assign_material_name)
if isinstance(temperature, str):
temperature = float(temperature)
liquid_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["liquid"]
observe_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["observe"]
params = {
"param_values": {
liquid_id: [
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volumeFormula},
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id},
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type}
],
observe_id: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": torque_variation},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
self.pending_task_params.append(params)
print(f"成功添加液体进料小瓶(非滴定)参数: volume={volumeFormula}μL, material={assign_material_name}->ID:{material_id}")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def liquid_feeding_solvents(self, assign_material_name: str, volume: str, titration_type: str = "1",
time: str = "360", torque_variation: str = "2", temperature: float = 25.00):
"""液体进料溶剂"""
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_solvents"}')
material_id = self._get_material_id_by_name(assign_material_name)
if isinstance(temperature, str):
temperature = float(temperature)
liquid_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["liquid"]
observe_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["observe"]
params = {
"param_values": {
liquid_id: [
{"m": 0, "n": 1, "Key": "titrationType", "Value": titration_type},
{"m": 0, "n": 1, "Key": "volume", "Value": volume},
{"m": 0, "n": 1, "Key": "assignMaterialName", "Value": material_id}
],
observe_id: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": torque_variation},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
self.pending_task_params.append(params)
print(f"成功添加液体进料溶剂参数: material={assign_material_name}->ID:{material_id}, volume={volume}μL")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def liquid_feeding_titration(self, volume_formula: str, assign_material_name: str, titration_type: str = "1",
time: str = "90", torque_variation: int = 2, temperature: float = 25.00):
"""液体进料(滴定)"""
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding(titration)"}')
material_id = self._get_material_id_by_name(assign_material_name)
if isinstance(temperature, str):
temperature = float(temperature)
liquid_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["liquid"]
observe_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["observe"]
params = {
"param_values": {
liquid_id: [
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volume_formula},
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type},
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
],
observe_id: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
self.pending_task_params.append(params)
print(f"成功添加液体进料滴定参数: volume={volume_formula}μL, material={assign_material_name}->ID:{material_id}")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def liquid_feeding_beaker(self, volume: str = "35000", assign_material_name: str = "BAPP",
time: str = "0", torque_variation: str = "1", titrationType: str = "1",
temperature: float = 25.00):
"""液体进料烧杯"""
self.append_to_workflow_sequence('{"web_workflow_name": "liquid_feeding_beaker"}')
material_id = self._get_material_id_by_name(assign_material_name)
if isinstance(temperature, str):
temperature = float(temperature)
liquid_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["liquid"]
observe_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["observe"]
params = {
"param_values": {
liquid_id: [
{"m": 0, "n": 2, "Key": "volume", "Value": volume},
{"m": 0, "n": 2, "Key": "assignMaterialName", "Value": material_id},
{"m": 0, "n": 2, "Key": "titrationType", "Value": titrationType}
],
observe_id: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": torque_variation},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
self.pending_task_params.append(params)
print(f"成功添加液体进料烧杯参数: volume={volume}μL, material={assign_material_name}->ID:{material_id}")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,252 @@
workstation.bioyond_dispensing_station:
category:
- workstation
- bioyond
class:
action_value_mappings:
create_90_10_vial_feeding_task:
feedback: {}
goal:
delay_time: delay_time
hold_m_name: hold_m_name
order_name: order_name
percent_10_1_assign_material_name: percent_10_1_assign_material_name
percent_10_1_liquid_material_name: percent_10_1_liquid_material_name
percent_10_1_target_weigh: percent_10_1_target_weigh
percent_10_1_volume: percent_10_1_volume
percent_10_2_assign_material_name: percent_10_2_assign_material_name
percent_10_2_liquid_material_name: percent_10_2_liquid_material_name
percent_10_2_target_weigh: percent_10_2_target_weigh
percent_10_2_volume: percent_10_2_volume
percent_10_3_assign_material_name: percent_10_3_assign_material_name
percent_10_3_liquid_material_name: percent_10_3_liquid_material_name
percent_10_3_target_weigh: percent_10_3_target_weigh
percent_10_3_volume: percent_10_3_volume
percent_90_1_assign_material_name: percent_90_1_assign_material_name
percent_90_1_target_weigh: percent_90_1_target_weigh
percent_90_2_assign_material_name: percent_90_2_assign_material_name
percent_90_2_target_weigh: percent_90_2_target_weigh
percent_90_3_assign_material_name: percent_90_3_assign_material_name
percent_90_3_target_weigh: percent_90_3_target_weigh
speed: speed
temperature: temperature
goal_default:
delay_time: ''
hold_m_name: ''
order_name: ''
percent_10_1_assign_material_name: ''
percent_10_1_liquid_material_name: ''
percent_10_1_target_weigh: ''
percent_10_1_volume: ''
percent_10_2_assign_material_name: ''
percent_10_2_liquid_material_name: ''
percent_10_2_target_weigh: ''
percent_10_2_volume: ''
percent_10_3_assign_material_name: ''
percent_10_3_liquid_material_name: ''
percent_10_3_target_weigh: ''
percent_10_3_volume: ''
percent_90_1_assign_material_name: ''
percent_90_1_target_weigh: ''
percent_90_2_assign_material_name: ''
percent_90_2_target_weigh: ''
percent_90_3_assign_material_name: ''
percent_90_3_target_weigh: ''
speed: ''
temperature: ''
handles: {}
result:
return_info: return_info
schema:
description: ''
properties:
feedback:
properties: {}
required: []
title: DispenStationVialFeed_Feedback
type: object
goal:
properties:
delay_time:
type: string
hold_m_name:
type: string
order_name:
type: string
percent_10_1_assign_material_name:
type: string
percent_10_1_liquid_material_name:
type: string
percent_10_1_target_weigh:
type: string
percent_10_1_volume:
type: string
percent_10_2_assign_material_name:
type: string
percent_10_2_liquid_material_name:
type: string
percent_10_2_target_weigh:
type: string
percent_10_2_volume:
type: string
percent_10_3_assign_material_name:
type: string
percent_10_3_liquid_material_name:
type: string
percent_10_3_target_weigh:
type: string
percent_10_3_volume:
type: string
percent_90_1_assign_material_name:
type: string
percent_90_1_target_weigh:
type: string
percent_90_2_assign_material_name:
type: string
percent_90_2_target_weigh:
type: string
percent_90_3_assign_material_name:
type: string
percent_90_3_target_weigh:
type: string
speed:
type: string
temperature:
type: string
required:
- order_name
- percent_90_1_assign_material_name
- percent_90_1_target_weigh
- percent_90_2_assign_material_name
- percent_90_2_target_weigh
- percent_90_3_assign_material_name
- percent_90_3_target_weigh
- percent_10_1_assign_material_name
- percent_10_1_target_weigh
- percent_10_1_volume
- percent_10_1_liquid_material_name
- percent_10_2_assign_material_name
- percent_10_2_target_weigh
- percent_10_2_volume
- percent_10_2_liquid_material_name
- percent_10_3_assign_material_name
- percent_10_3_target_weigh
- percent_10_3_volume
- percent_10_3_liquid_material_name
- speed
- temperature
- delay_time
- hold_m_name
title: DispenStationVialFeed_Goal
type: object
result:
properties:
return_info:
type: string
required:
- return_info
title: DispenStationVialFeed_Result
type: object
required:
- goal
title: DispenStationVialFeed
type: object
type: DispenStationVialFeed
create_diamine_solution_task:
feedback: {}
goal:
delay_time: delay_time
hold_m_name: hold_m_name
liquid_material_name: liquid_material_name
material_name: material_name
order_name: order_name
speed: speed
target_weigh: target_weigh
temperature: temperature
volume: volume
goal_default:
delay_time: ''
hold_m_name: ''
liquid_material_name: ''
material_name: ''
order_name: ''
speed: ''
target_weigh: ''
temperature: ''
volume: ''
handles: {}
result:
return_info: return_info
schema:
description: ''
properties:
feedback:
properties: {}
required: []
title: DispenStationSolnPrep_Feedback
type: object
goal:
properties:
delay_time:
type: string
hold_m_name:
type: string
liquid_material_name:
type: string
material_name:
type: string
order_name:
type: string
speed:
type: string
target_weigh:
type: string
temperature:
type: string
volume:
type: string
required:
- order_name
- material_name
- target_weigh
- volume
- liquid_material_name
- speed
- temperature
- delay_time
- hold_m_name
title: DispenStationSolnPrep_Goal
type: object
result:
properties:
return_info:
type: string
required:
- return_info
title: DispenStationSolnPrep_Result
type: object
required:
- goal
title: DispenStationSolnPrep
type: object
type: DispenStationSolnPrep
module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation
status_types: {}
type: python
config_info: []
description: ''
handles: []
icon: ''
init_param_schema:
config:
properties:
config:
type: string
required:
- config
type: object
data:
properties: {}
required: []
type: object
version: 1.0.0

File diff suppressed because it is too large Load Diff

View File

@@ -1361,8 +1361,7 @@ laiyu_liquid:
mix_liquid_height: 0.0
mix_rate: 0
mix_stage: ''
mix_times:
- 0
mix_times: 0
mix_vol: 0
none_keys:
- ''
@@ -1492,11 +1491,9 @@ laiyu_liquid:
mix_stage:
type: string
mix_times:
items:
maximum: 2147483647
minimum: -2147483648
type: integer
type: array
maximum: 2147483647
minimum: -2147483648
type: integer
mix_vol:
maximum: 2147483647
minimum: -2147483648

View File

@@ -3994,8 +3994,7 @@ liquid_handler:
mix_liquid_height: 0.0
mix_rate: 0
mix_stage: ''
mix_times:
- 0
mix_times: 0
mix_vol: 0
none_keys:
- ''
@@ -4151,11 +4150,9 @@ liquid_handler:
mix_stage:
type: string
mix_times:
items:
maximum: 2147483647
minimum: -2147483648
type: integer
type: array
maximum: 2147483647
minimum: -2147483648
type: integer
mix_vol:
maximum: 2147483647
minimum: -2147483648
@@ -5015,8 +5012,7 @@ liquid_handler.biomek:
mix_liquid_height: 0.0
mix_rate: 0
mix_stage: ''
mix_times:
- 0
mix_times: 0
mix_vol: 0
none_keys:
- ''
@@ -5159,11 +5155,9 @@ liquid_handler.biomek:
mix_stage:
type: string
mix_times:
items:
maximum: 2147483647
minimum: -2147483648
type: integer
type: array
maximum: 2147483647
minimum: -2147483648
type: integer
mix_vol:
maximum: 2147483647
minimum: -2147483648
@@ -7807,8 +7801,7 @@ liquid_handler.prcxi:
mix_liquid_height: 0.0
mix_rate: 0
mix_stage: ''
mix_times:
- 0
mix_times: 0
mix_vol: 0
none_keys:
- ''
@@ -7937,11 +7930,9 @@ liquid_handler.prcxi:
mix_stage:
type: string
mix_times:
items:
maximum: 2147483647
minimum: -2147483648
type: integer
type: array
maximum: 2147483647
minimum: -2147483648
type: integer
mix_vol:
maximum: 2147483647
minimum: -2147483648

View File

@@ -4,11 +4,11 @@ reaction_station.bioyond:
- reaction_station_bioyond
class:
action_value_mappings:
auto-add_material:
auto-append_to_workflow_sequence:
feedback: {}
goal: {}
goal_default:
material_data: null
web_workflow_name: null
handles: {}
placeholder_keys: {}
result: {}
@@ -18,22 +18,21 @@ reaction_station.bioyond:
feedback: {}
goal:
properties:
material_data:
type: object
web_workflow_name:
type: string
required:
- material_data
- web_workflow_name
type: object
result: {}
required:
- goal
title: add_material参数
title: append_to_workflow_sequence参数
type: object
type: UniLabJsonCommand
auto-create_90_10_vial_feeding_task:
auto-clear_workflows:
feedback: {}
goal: {}
goal_default:
task_data: null
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
@@ -42,470 +41,13 @@ reaction_station.bioyond:
properties:
feedback: {}
goal:
properties:
task_data:
type: string
required:
- task_data
type: object
result: {}
required:
- goal
title: create_90_10_vial_feeding_task参数
type: object
type: UniLabJsonCommand
auto-create_batch_90_10_vial_feeding_task:
feedback: {}
goal: {}
goal_default:
batch_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
batch_data:
type: string
required:
- batch_data
type: object
result: {}
required:
- goal
title: create_batch_90_10_vial_feeding_task参数
type: object
type: UniLabJsonCommand
auto-create_batch_diamine_solution_task:
feedback: {}
goal: {}
goal_default:
batch_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
batch_data:
type: string
required:
- batch_data
type: object
result: {}
required:
- goal
title: create_batch_diamine_solution_task参数
type: object
type: UniLabJsonCommand
auto-create_diamine_solution_task:
feedback: {}
goal: {}
goal_default:
solution_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
solution_data:
type: string
required:
- solution_data
type: object
result: {}
required:
- goal
title: create_diamine_solution_task参数
type: object
type: UniLabJsonCommand
auto-create_order:
feedback: {}
goal: {}
goal_default:
parameters: null
task_name: null
workflow_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
parameters:
type: object
task_name:
type: string
workflow_name:
type: string
required:
- workflow_name
- task_name
type: object
result: {}
required:
- goal
title: create_order参数
type: object
type: UniLabJsonCommand
auto-create_resource:
feedback: {}
goal: {}
goal_default:
resource_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
resource_data:
type: string
required:
- resource_data
type: object
result: {}
required:
- goal
title: create_resource参数
type: object
type: UniLabJsonCommand
auto-delete_material:
feedback: {}
goal: {}
goal_default:
material_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_data:
type: string
required:
- material_data
type: object
result: {}
required:
- goal
title: delete_material参数
type: object
type: UniLabJsonCommand
auto-device_operation:
feedback: {}
goal: {}
goal_default:
device_id: null
operation: null
parameters: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
device_id:
type: string
operation:
type: string
parameters:
type: object
required:
- device_id
- operation
type: object
result: {}
required:
- goal
title: device_operation参数
type: object
type: UniLabJsonCommand
auto-dispensing_material_inbound:
feedback: {}
goal: {}
goal_default:
material_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_data:
type: string
required:
- material_data
type: object
result: {}
required:
- goal
title: dispensing_material_inbound参数
type: object
type: UniLabJsonCommand
auto-dispensing_material_outbound:
feedback: {}
goal: {}
goal_default:
material_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_data:
type: string
required:
- material_data
type: object
result: {}
required:
- goal
title: dispensing_material_outbound参数
type: object
type: UniLabJsonCommand
auto-drip_back:
feedback: {}
goal: {}
goal_default:
assign_material_name: Reactor
temperature: 25.0
time: '0'
torque_variation: '1'
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
assign_material_name:
default: Reactor
type: string
temperature:
default: 25.0
type: number
time:
default: '0'
type: string
torque_variation:
default: '1'
type: string
properties: {}
required: []
type: object
result: {}
required:
- goal
title: drip_back参数
type: object
type: UniLabJsonCommand
auto-execute_bioyond_sync_workflow:
feedback: {}
goal: {}
goal_default:
parameters: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
parameters:
type: object
required:
- parameters
type: object
result: {}
required:
- goal
title: execute_bioyond_sync_workflow参数
type: object
type: UniLabJsonCommandAsync
auto-execute_bioyond_update_workflow:
feedback: {}
goal: {}
goal_default:
parameters: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
parameters:
type: object
required:
- parameters
type: object
result: {}
required:
- goal
title: execute_bioyond_update_workflow参数
type: object
type: UniLabJsonCommandAsync
auto-liquid_feeding_beaker:
feedback: {}
goal: {}
goal_default:
material_name: ''
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: liquid_feeding_beaker参数
type: object
type: UniLabJsonCommand
auto-liquid_feeding_solvents:
feedback: {}
goal: {}
goal_default:
material_name: ''
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: liquid_feeding_solvents参数
type: object
type: UniLabJsonCommand
auto-liquid_feeding_titration:
feedback: {}
goal: {}
goal_default:
material_name: ''
time: '120'
titration_type: '1'
torque_variation: '2'
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
time:
default: '120'
type: string
titration_type:
default: '1'
type: string
torque_variation:
default: '2'
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: liquid_feeding_titration参数
type: object
type: UniLabJsonCommand
auto-liquid_feeding_vials_non_titration:
feedback: {}
goal: {}
goal_default:
material_name: ''
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: liquid_feeding_vials_non_titration参数
title: clear_workflows参数
type: object
type: UniLabJsonCommand
auto-load_bioyond_data_from_file:
@@ -533,74 +75,11 @@ reaction_station.bioyond:
title: load_bioyond_data_from_file参数
type: object
type: UniLabJsonCommand
auto-material_inbound:
feedback: {}
goal: {}
goal_default:
location_name: null
material_id: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
location_name:
type: string
material_id:
type: string
required:
- material_id
- location_name
type: object
result: {}
required:
- goal
title: material_inbound参数
type: object
type: UniLabJsonCommand
auto-material_outbound:
feedback: {}
goal: {}
goal_default:
location_name: null
material_id: null
quantity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
location_name:
type: string
material_id:
type: string
quantity:
type: integer
required:
- material_id
- location_name
- quantity
type: object
result: {}
required:
- goal
title: material_outbound参数
type: object
type: UniLabJsonCommand
auto-merge_workflow_with_parameters:
feedback: {}
goal: {}
goal_default:
name: null
workflows: null
json_str: null
handles: {}
placeholder_keys: {}
result: {}
@@ -610,15 +89,10 @@ reaction_station.bioyond:
feedback: {}
goal:
properties:
name:
json_str:
type: string
workflows:
items:
type: object
type: array
required:
- name
- workflows
- json_str
type: object
result: {}
required:
@@ -626,31 +100,6 @@ reaction_station.bioyond:
title: merge_workflow_with_parameters参数
type: object
type: UniLabJsonCommand
auto-order_query:
feedback: {}
goal: {}
goal_default:
query_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
query_data:
type: string
required:
- query_data
type: object
result: {}
required:
- goal
title: order_query参数
type: object
type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
@@ -676,33 +125,11 @@ reaction_station.bioyond:
title: post_init参数
type: object
type: UniLabJsonCommand
auto-reactor_taken_in:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: reactor_taken_in参数
type: object
type: UniLabJsonCommand
auto-reactor_taken_out:
auto-process_web_workflows:
feedback: {}
goal: {}
goal_default:
order_id: ''
preintake_id: ''
json_str: null
handles: {}
placeholder_keys: {}
result: {}
@@ -712,18 +139,15 @@ reaction_station.bioyond:
feedback: {}
goal:
properties:
order_id:
default: ''
json_str:
type: string
preintake_id:
default: ''
type: string
required: []
required:
- json_str
type: object
result: {}
required:
- goal
title: reactor_taken_out参数
title: process_web_workflows参数
type: object
type: UniLabJsonCommand
auto-reset_workstation:
@@ -747,11 +171,11 @@ reaction_station.bioyond:
title: reset_workstation参数
type: object
type: UniLabJsonCommand
auto-sample_waste_removal:
auto-resource_tree_add:
feedback: {}
goal: {}
goal_default:
waste_data: null
resources: null
handles: {}
placeholder_keys: {}
result: {}
@@ -761,119 +185,42 @@ reaction_station.bioyond:
feedback: {}
goal:
properties:
waste_data:
resources:
items:
type: object
type: array
required:
- resources
type: object
result: {}
required:
- goal
title: resource_tree_add参数
type: object
type: UniLabJsonCommand
auto-set_workflow_sequence:
feedback: {}
goal: {}
goal_default:
json_str: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
json_str:
type: string
required:
- waste_data
- json_str
type: object
result: {}
required:
- goal
title: sample_waste_removal参数
type: object
type: UniLabJsonCommand
auto-solid_feeding_vials:
feedback: {}
goal: {}
goal_default:
material_name: ''
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: solid_feeding_vials参数
type: object
type: UniLabJsonCommand
auto-start_scheduler:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: start_scheduler参数
type: object
type: UniLabJsonCommand
auto-stock_material:
feedback: {}
goal: {}
goal_default:
location: null
material_id: null
quantity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
location:
type: string
material_id:
type: string
quantity:
type: integer
required:
- material_id
- location
- quantity
type: object
result: {}
required:
- goal
title: stock_material参数
type: object
type: UniLabJsonCommand
auto-stop_scheduler:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop_scheduler参数
title: set_workflow_sequence参数
type: object
type: UniLabJsonCommand
auto-transfer_resource_to_another:
@@ -1064,33 +411,6 @@ reaction_station.bioyond:
title: transfer_resource_to_another参数
type: object
type: UniLabJsonCommand
auto-validate_workflow_parameters:
feedback: {}
goal: {}
goal_default:
workflows: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
workflows:
items:
type: object
type: array
required:
- workflows
type: object
result: {}
required:
- goal
title: validate_workflow_parameters参数
type: object
type: UniLabJsonCommand
bioyond_sync:
feedback: {}
goal:
@@ -1407,11 +727,8 @@ reaction_station.bioyond:
module: unilabos.devices.workstation.bioyond_studio.station:BioyondWorkstation
protocol_type: []
status_types:
all_workflows: dict
bioyond_status: dict
device_list: dict
scheduler_status: dict
station_info: dict
workflow_parameter_template: dict
workstation_status: dict
type: python
config_info: []
@@ -1431,24 +748,15 @@ reaction_station.bioyond:
type: object
data:
properties:
all_workflows:
type: object
bioyond_status:
type: object
device_list:
type: object
scheduler_status:
type: object
station_info:
type: object
workflow_parameter_template:
type: object
workstation_status:
type: object
required:
- station_info
- bioyond_status
- workflow_parameter_template
- scheduler_status
- device_list
- all_workflows
- workstation_status
type: object
version: 1.0.0

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,16 @@
import importlib
import inspect
import json
import os.path
import traceback
from typing import Union, Any, Dict, List
from typing import Union, Any, Dict, List, Tuple
import networkx as nx
from pylabrobot.resources import ResourceHolder
from unilabos_msgs.msg import Resource
from unilabos.config.config import BasicConfig
from unilabos.resources.container import RegularContainer
from unilabos.resources.itemized_carrier import ItemizedCarrier
from unilabos.ros.msgs.message_converter import convert_to_ros_msg
from unilabos.ros.nodes.resource_tracker import (
ResourceDictInstance,
@@ -44,6 +47,29 @@ def canonicalize_nodes_data(
if node.get("label") is not None:
node_id = node.pop("label")
node["id"] = node["name"] = node_id
if not isinstance(node.get("config"), dict):
node["config"] = {}
if not node.get("type"):
node["type"] = "device"
print_status(f"Warning: Node {node.get('id', 'unknown')} missing 'type', defaulting to 'device'", "warning")
if not node.get("name"):
node["name"] = node.get("id")
print_status(f"Warning: Node {node.get('id', 'unknown')} missing 'name', defaulting to {node['name']}", "warning")
if not isinstance(node.get("position"), dict):
node["position"] = {"position": {}}
x = node.pop("x", None)
if x is not None:
node["position"]["position"]["x"] = x
y = node.pop("y", None)
if y is not None:
node["position"]["position"]["y"] = y
z = node.pop("z", None)
if z is not None:
node["position"]["position"]["z"] = z
for k in list(node.keys()):
if k not in ["id", "uuid", "name", "description", "schema", "model", "icon", "parent_uuid", "parent", "type", "class", "position", "config", "data"]:
v = node.pop(k)
node["config"][k] = v
# 第二步处理parent_relation
id2idx = {node["id"]: idx for idx, node in enumerate(nodes)}
@@ -301,6 +327,10 @@ def read_graphml(graphml_file: str) -> tuple[nx.Graph, ResourceTreeSet, List[Dic
"nodes": [node.res_content.model_dump(by_alias=True) for node in resource_tree_set.all_nodes],
"links": standardized_links,
}
dump_json_path = os.path.join(BasicConfig.working_dir, os.path.basename(graphml_file).rsplit(".")[0] + ".json")
with open(dump_json_path, "w", encoding="utf-8") as f:
f.write(json.dumps(graph_data, indent=4, ensure_ascii=False))
print_status(f"GraphML converted to JSON and saved to {dump_json_path}", "info")
physical_setup_graph = nx.node_link_graph(graph_data, link="links", multigraph=False)
handle_communications(physical_setup_graph)
@@ -576,13 +606,13 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w
return r
def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict = {}, deck: Any = None) -> list[dict]:
def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[str, Tuple[str, str]] = {}, deck: Any = None) -> list[dict]:
"""
将 bioyond 物料格式转换为 ulab 物料格式
Args:
bioyond_materials: bioyond 系统的物料查询结果列表
type_mapping: 物料类型映射字典,格式 {bioyond_type: plr_class_name}
type_mapping: 物料类型映射字典,格式 {bioyond_type: [plr_class_name, class_uuid]}
location_id_mapping: 库位 ID 到名称的映射字典,格式 {location_id: location_name}
Returns:
@@ -592,7 +622,7 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict =
for material in bioyond_materials:
className = (
type_mapping.get(material.get("typeName"), "RegularContainer") if type_mapping else "RegularContainer"
type_mapping.get(material.get("typeName"), ("RegularContainer", ""))[0] if type_mapping else "RegularContainer"
)
plr_material: ResourcePLR = initialize_resource(
@@ -614,7 +644,7 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict =
# plr_material.unassign_child_resource(bottle)
plr_material.sites[number] = None
plr_material[number] = initialize_resource(
{"name": f'{detail["name"]}_{number}', "class": type_mapping[detail["name"]]}, resource_type=ResourcePLR
{"name": f'{detail["name"]}_{number}', "class": type_mapping[detail["name"]][0]}, resource_type=ResourcePLR
)
else:
bottle.tracker.liquids = [
@@ -645,32 +675,59 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict =
return plr_materials
def resource_plr_to_bioyond(plr_materials: list[ResourcePLR], type_mapping: dict = {}, warehouse_mapping: dict = {}) -> list[dict]:
def resource_plr_to_bioyond(plr_resources: list[ResourcePLR], type_mapping: dict = {}, warehouse_mapping: dict = {}) -> list[dict]:
bioyond_materials = []
for plr_material in plr_materials:
material = {
"name": plr_material.name,
"typeName": plr_material.__class__.__name__,
"code": plr_material.code,
"quantity": 0,
"detail": [],
"locations": [],
}
if hasattr(plr_material, "capacity") and plr_material.capacity > 1:
for idx in range(plr_material.capacity):
bottle = plr_material[idx]
detail = {
"x": (idx // (plr_material.num_items_x * plr_material.num_items_y)) + 1,
"y": ((idx % (plr_material.num_items_x * plr_material.num_items_y)) // plr_material.num_items_x) + 1,
"z": (idx % plr_material.num_items_x) + 1,
for resource in plr_resources:
if hasattr(resource, "capacity") and resource.capacity > 1:
material = {
"typeId": type_mapping.get(resource.model)[1],
"name": resource.name,
"unit": "",
"quantity": 1,
"details": [],
"Parameters": "{}"
}
for bottle in resource.children:
if isinstance(resource, ItemizedCarrier):
site = resource.get_child_identifier(bottle)
else:
site = {"x": bottle.location.x - 1, "y": bottle.location.y - 1}
detail_item = {
"typeId": type_mapping.get(bottle.model)[1],
"name": bottle.name,
"code": bottle.code if hasattr(bottle, "code") else "",
"quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
"x": site["x"] + 1,
"y": site["y"] + 1,
"molecular": 1,
"Parameters": json.dumps({"molecular": 1})
}
material["detail"].append(detail)
material["quantity"] = 1.0
material["details"].append(detail_item)
else:
bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
material["quantity"] = sum(qty for _, qty in bottle.tracker.liquids) if hasattr(plr_material, "tracker") else 0
bottle = resource[0] if resource.capacity > 0 else resource
material = {
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
"name": resource.get("name", ""),
"unit": "",
"quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
"Parameters": "{}"
}
if resource.parent is not None and isinstance(resource.parent, ItemizedCarrier):
site_in_parent = resource.parent.get_child_identifier(resource)
material["locations"] = [
{
"id": warehouse_mapping[resource.parent.name]["site_uuids"][site_in_parent["identifier"]],
"whid": warehouse_mapping[resource.parent.name]["uuid"],
"whName": resource.parent.name,
"x": site_in_parent["z"] + 1,
"y": site_in_parent["y"] + 1,
"z": 1,
"quantity": 0
}
],
print(f"material_data: {material}")
bioyond_materials.append(material)
return bioyond_materials

View File

@@ -163,6 +163,89 @@ class ItemizedCarrier(ResourcePLR):
if hasattr(resource, "unassign"):
resource.unassign()
def get_child_identifier(self, child: ResourcePLR):
"""Get the identifier information for a given child resource.
Args:
child: The Resource object to find the identifier for
Returns:
dict: A dictionary containing:
- identifier: The string identifier (e.g. "A1", "B2")
- idx: The integer index in the sites list
- x: The x index (column index, 0-based)
- y: The y index (row index, 0-based)
- z: The z index (layer index, 0-based)
Raises:
ValueError: If the child resource is not found in this carrier
"""
# Find the child resource in sites
for idx, resource in enumerate(self.sites):
if resource is child:
# Get the identifier from ordering keys
identifier = list(self._ordering.keys())[idx]
# Parse identifier to get x, y, z indices
x_idx, y_idx, z_idx = self._parse_identifier_to_indices(identifier, idx)
return {
"identifier": identifier,
"idx": idx,
"x": x_idx,
"y": y_idx,
"z": z_idx
}
# If not found, raise an error
raise ValueError(f"Resource {child} is not assigned to this carrier")
def _parse_identifier_to_indices(self, identifier: str, idx: int) -> Tuple[int, int, int]:
"""Parse identifier string to get x, y, z indices.
Args:
identifier: String identifier like "A1", "B2", etc.
idx: Linear index as fallback for calculation
Returns:
Tuple of (x_idx, y_idx, z_idx)
"""
# If we have explicit dimensions, calculate from idx
if self.num_items_x > 0 and self.num_items_y > 0:
# Calculate 3D indices from linear index
z_idx = idx // (self.num_items_x * self.num_items_y) if self.num_items_z > 0 else 0
remaining = idx % (self.num_items_x * self.num_items_y)
y_idx = remaining // self.num_items_x
x_idx = remaining % self.num_items_x
return x_idx, y_idx, z_idx
# Fallback: parse from Excel-style identifier
if isinstance(identifier, str) and len(identifier) >= 2:
# Extract row (letter) and column (number)
row_letters = ""
col_numbers = ""
for char in identifier:
if char.isalpha():
row_letters += char
elif char.isdigit():
col_numbers += char
if row_letters and col_numbers:
# Convert letter(s) to row index (A=0, B=1, etc.)
y_idx = 0
for char in row_letters:
y_idx = y_idx * 26 + (ord(char.upper()) - ord('A'))
# Convert number to column index (1-based to 0-based)
x_idx = int(col_numbers) - 1
z_idx = 0 # Default layer
return x_idx, y_idx, z_idx
# If all else fails, assume linear arrangement
return idx, 0, 0
def __getitem__(
self,
identifier: Union[str, int, Sequence[int], Sequence[str], slice, range],