mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 13:25:13 +00:00
Compare commits
14 Commits
015fee4171
...
workstatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f355722281 | ||
|
|
936834f8c3 | ||
|
|
915a6a04c3 | ||
|
|
48b51c3a4a | ||
|
|
acef0b8ca2 | ||
|
|
97788b4e07 | ||
|
|
39cc280c91 | ||
|
|
d2a30fe33b | ||
|
|
096875e910 | ||
|
|
ee4ed26846 | ||
|
|
19dffcb5db | ||
|
|
b441362cd2 | ||
|
|
ed53ef2f64 | ||
|
|
0c9f26e8fc |
32
fix_datatype.py
Normal file
32
fix_datatype.py
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
filepath = r'd:\UniLab\Uni-Lab-OS\unilabos\device_comms\modbus_plc\modbus.py'
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace the DataType placeholder with actual enum
|
||||
find_pattern = r'# DataType will be accessed via client instance.*?DataType = None # Placeholder.*?\n'
|
||||
replacement = '''# Define DataType enum for pymodbus 2.5.3 compatibility
|
||||
class DataType(Enum):
|
||||
INT16 = "int16"
|
||||
UINT16 = "uint16"
|
||||
INT32 = "int32"
|
||||
UINT32 = "uint32"
|
||||
INT64 = "int64"
|
||||
UINT64 = "uint64"
|
||||
FLOAT32 = "float32"
|
||||
FLOAT64 = "float64"
|
||||
STRING = "string"
|
||||
BOOL = "bool"
|
||||
|
||||
'''
|
||||
|
||||
new_content = re.sub(find_pattern, replacement, content, flags=re.DOTALL)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print('File updated successfully!')
|
||||
@@ -1,101 +1,98 @@
|
||||
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "bioyond_cell_workstation",
|
||||
"name": "配液分液工站",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"YB_Bioyond_Deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class": "bioyond_cell",
|
||||
"config": {
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "YB_Bioyond_Deck",
|
||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
|
||||
}
|
||||
},
|
||||
"protocol_type": []
|
||||
"nodes": [
|
||||
{
|
||||
"id": "bioyond_cell_workstation",
|
||||
"name": "配液分液工站",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"YB_Bioyond_Deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class": "bioyond_cell",
|
||||
"config": {
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "YB_Bioyond_Deck",
|
||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
"protocol_type": []
|
||||
},
|
||||
{
|
||||
"id": "YB_Bioyond_Deck",
|
||||
"name": "YB_Bioyond_Deck",
|
||||
"children": [],
|
||||
"parent": "bioyond_cell_workstation",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_YB_Deck",
|
||||
"position": {
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "YB_Bioyond_Deck",
|
||||
"name": "YB_Bioyond_Deck",
|
||||
"children": [],
|
||||
"parent": "bioyond_cell_workstation",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_YB_Deck",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "BIOYOND_YB_Deck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "BIOYOND_YB_Deck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "BatteryStation",
|
||||
"name": "扣电工作站",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"coin_cell_deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class":"coincellassemblyworkstation_device",
|
||||
"config": {
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "YB_YH_Deck",
|
||||
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
|
||||
}
|
||||
},
|
||||
"protocol_type": []
|
||||
},
|
||||
"position": {
|
||||
"size": {"height": 1450, "width": 1450, "depth": 2100},
|
||||
"position": {
|
||||
"x": -1500,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "YB_YH_Deck",
|
||||
"name": "YB_YH_Deck",
|
||||
"children": [],
|
||||
"parent": "BatteryStation",
|
||||
"type": "deck",
|
||||
"class": "CoincellDeck",
|
||||
"config": {
|
||||
"type": "CoincellDeck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "BatteryStation",
|
||||
"name": "扣电工作站",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"coin_cell_deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class":"coincellassemblyworkstation_device",
|
||||
"config": {
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "YB_YH_Deck",
|
||||
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
"protocol_type": []
|
||||
},
|
||||
"position": {
|
||||
"size": {"height": 1450, "width": 1450, "depth": 2100},
|
||||
"position": {
|
||||
"x": -1500,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
{
|
||||
"id": "YB_YH_Deck",
|
||||
"name": "YB_YH_Deck",
|
||||
"children": [],
|
||||
"parent": "BatteryStation",
|
||||
"type": "deck",
|
||||
"class": "CoincellDeck",
|
||||
"config": {
|
||||
"type": "CoincellDeck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
|
||||
|
||||
72
test/experiments/reaction_station_bioyond.json
Normal file
72
test/experiments/reaction_station_bioyond.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "reaction_station_bioyond",
|
||||
"name": "reaction_station_bioyond",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"Bioyond_Deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class": "reaction_station.bioyond",
|
||||
"config": {
|
||||
"config": {
|
||||
"api_key": "DE9BDDA0",
|
||||
"api_host": "http://192.168.1.200:44402",
|
||||
"workflow_mappings": {
|
||||
"reactor_taken_out": "3a16081e-4788-ca37-eff4-ceed8d7019d1",
|
||||
"reactor_taken_in": "3a160df6-76b3-0957-9eb0-cb496d5721c6",
|
||||
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
|
||||
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
|
||||
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
|
||||
"Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6",
|
||||
"liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||
},
|
||||
"material_type_mappings": {
|
||||
"烧杯": ["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": {
|
||||
"data": {
|
||||
"_resource_child_name": "Bioyond_Deck",
|
||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerReactionStation_Deck"
|
||||
}
|
||||
},
|
||||
"protocol_type": []
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "Bioyond_Deck",
|
||||
"name": "Bioyond_Deck",
|
||||
"children": [
|
||||
],
|
||||
"parent": "reaction_station_bioyond",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_PolymerReactionStation_Deck",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "BIOYOND_PolymerReactionStation_Deck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -367,37 +367,10 @@ def main():
|
||||
graph, resource_tree_set, resource_links = read_node_link_json(request_startup_json)
|
||||
else:
|
||||
if not os.path.isfile(file_path):
|
||||
# 尝试从 main.py 向上两级目录查找
|
||||
temp_file_path = os.path.abspath(str(os.path.join(__file__, "..", "..", file_path)))
|
||||
if os.path.isfile(temp_file_path):
|
||||
print_status(f"使用相对路径{temp_file_path}", "info")
|
||||
file_path = temp_file_path
|
||||
else:
|
||||
# 尝试在 working_dir 中查找
|
||||
working_dir_file_path = os.path.join(working_dir, file_path)
|
||||
if os.path.isfile(working_dir_file_path):
|
||||
print_status(f"在工作目录中找到文件: {working_dir_file_path}", "info")
|
||||
file_path = working_dir_file_path
|
||||
else:
|
||||
# 尝试使用文件名在 working_dir 中查找
|
||||
file_name = os.path.basename(file_path)
|
||||
working_dir_file_path = os.path.join(working_dir, file_name)
|
||||
if os.path.isfile(working_dir_file_path):
|
||||
print_status(f"在工作目录中找到文件: {working_dir_file_path}", "info")
|
||||
file_path = working_dir_file_path
|
||||
# 最终检查文件是否存在
|
||||
if not os.path.isfile(file_path):
|
||||
print_status(
|
||||
f"无法找到设备加载文件: {file_path}\n"
|
||||
f"已尝试在以下位置查找:\n"
|
||||
f" 1. 原始路径: {args_dict.get('graph', BasicConfig.startup_json_path)}\n"
|
||||
f" 2. 相对路径: {os.path.abspath(str(os.path.join(__file__, '..', '..', args_dict.get('graph', BasicConfig.startup_json_path) or '')))}\n"
|
||||
f" 3. 工作目录: {os.path.join(working_dir, args_dict.get('graph', BasicConfig.startup_json_path) or '')}\n"
|
||||
f" 4. 工作目录(仅文件名): {os.path.join(working_dir, os.path.basename(args_dict.get('graph', BasicConfig.startup_json_path) or ''))}\n"
|
||||
f"请使用 -g 参数指定正确的文件路径,或在工作目录 {working_dir} 中放置文件",
|
||||
"error"
|
||||
)
|
||||
os._exit(1)
|
||||
if file_path.endswith(".json"):
|
||||
graph, resource_tree_set, resource_links = read_node_link_json(file_path)
|
||||
else:
|
||||
|
||||
29
unilabos/devices/battery/battery.json
Normal file
29
unilabos/devices/battery/battery.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
||||
"name": "Neware Battery Test System",
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "neware_battery_test_system",
|
||||
"position": {
|
||||
"x": 620.6111111111111,
|
||||
"y": 171,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"ip": "127.0.0.1",
|
||||
"port": 502,
|
||||
"machine_id": 1,
|
||||
"devtype": "27",
|
||||
"timeout": 20,
|
||||
"size_x": 500.0,
|
||||
"size_y": 500.0,
|
||||
"size_z": 2000.0
|
||||
},
|
||||
"data": {},
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
@@ -13,8 +13,6 @@
|
||||
- 状态类型: working/stop/finish/protect/pause/false/unknown
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
@@ -23,6 +21,7 @@ from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
|
||||
from pylabrobot.resources import ResourceHolder, Coordinate, create_ordered_items_2d, Deck, Plate
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
|
||||
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
||||
|
||||
@@ -57,6 +56,13 @@ class BatteryTestPositionState(TypedDict):
|
||||
status: str # 通道状态
|
||||
color: str # 状态对应颜色
|
||||
|
||||
# 额外的inquire协议字段
|
||||
relativetime: float # 相对时间 (s)
|
||||
open_or_close: int # 0=关闭, 1=打开
|
||||
step_type: str # 步骤类型
|
||||
cycle_id: int # 循环ID
|
||||
step_id: int # 步骤ID
|
||||
log_code: str # 日志代码
|
||||
|
||||
|
||||
class BatteryTestPosition(ResourceHolder):
|
||||
@@ -136,9 +142,9 @@ class NewareBatteryTestSystem:
|
||||
devtype: str = None,
|
||||
timeout: int = None,
|
||||
|
||||
size_x: float = 50,
|
||||
size_y: float = 50,
|
||||
size_z: float = 20,
|
||||
size_x: float = 500.0,
|
||||
size_y: float = 500.0,
|
||||
size_z: float = 2000.0,
|
||||
):
|
||||
"""
|
||||
初始化新威电池测试系统
|
||||
@@ -156,12 +162,6 @@ class NewareBatteryTestSystem:
|
||||
self.machine_id = machine_id
|
||||
self.devtype = devtype or self.DEVTYPE
|
||||
self.timeout = timeout or self.TIMEOUT
|
||||
|
||||
# 存储设备物理尺寸
|
||||
self.size_x = size_x
|
||||
self.size_y = size_y
|
||||
self.size_z = size_z
|
||||
|
||||
self._last_status_update = None
|
||||
self._cached_status = {}
|
||||
self._ros_node: Optional[ROS2WorkstationNode] = None # ROS节点引用,由框架设置
|
||||
@@ -192,9 +192,8 @@ class NewareBatteryTestSystem:
|
||||
def _setup_material_management(self):
|
||||
"""设置物料管理系统"""
|
||||
# 第1盘:5行8列网格 (A1-E8) - 5行对应subdevid 1-5,8列对应chlid 1-8
|
||||
# 先给物料设置一个最大的Deck,并设置其在空间中的位置
|
||||
|
||||
deck_main = Deck("ADeckName", 2000, 1800, 100, origin=Coordinate(2000,2000,0))
|
||||
# 先给物料设置一个最大的Deck
|
||||
deck_main = Deck("ADeckName", 200, 200, 200)
|
||||
|
||||
plate1_resources: Dict[str, BatteryTestPosition] = create_ordered_items_2d(
|
||||
BatteryTestPosition,
|
||||
@@ -203,8 +202,8 @@ class NewareBatteryTestSystem:
|
||||
dx=10,
|
||||
dy=10,
|
||||
dz=0,
|
||||
item_dx=65,
|
||||
item_dy=65
|
||||
item_dx=45,
|
||||
item_dy=45
|
||||
)
|
||||
plate1 = Plate("P1", 400, 300, 50, ordered_items=plate1_resources)
|
||||
deck_main.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
||||
@@ -233,15 +232,11 @@ class NewareBatteryTestSystem:
|
||||
num_items_y=5, # 5行(对应subdevid 6-10,即A-E)
|
||||
dx=10,
|
||||
dy=10,
|
||||
dz=0,
|
||||
dz=100, # Z轴偏移100mm
|
||||
item_dx=65,
|
||||
item_dy=65
|
||||
)
|
||||
|
||||
plate2 = Plate("P2", 400, 300, 50, ordered_items=plate2_resources)
|
||||
deck_main.assign_child_resource(plate2, location=Coordinate(0, 350, 0))
|
||||
|
||||
|
||||
# 为第2盘资源添加P2_前缀
|
||||
self.station_resources_plate2 = {}
|
||||
for name, resource in plate2_resources.items():
|
||||
@@ -311,132 +306,55 @@ class NewareBatteryTestSystem:
|
||||
|
||||
def _update_plate_resources(self, subunits: Dict):
|
||||
"""更新两盘电池资源的状态"""
|
||||
# 第1盘:subdevid 1-5 映射到 8列5行网格 (列0-7, 行0-4)
|
||||
# 第1盘:subdevid 1-5 映射到 P1_A1-P1_E8 (5行8列)
|
||||
for subdev_id in range(1, 6): # subdevid 1-5
|
||||
status_row = subunits.get(subdev_id, {})
|
||||
|
||||
for chl_id in range(1, 9): # chlid 1-8
|
||||
try:
|
||||
# 根据用户描述:第一个是(0,0),最后一个是(7,4)
|
||||
# 说明是8列5行,列从0开始,行从0开始
|
||||
col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 列0-7)
|
||||
row_idx = (subdev_id - 1) # 0-4 (subdevid 1-5 -> 行0-4)
|
||||
|
||||
# 尝试多种可能的资源命名格式
|
||||
possible_names = [
|
||||
f"P1_batterytestposition_{col_idx}_{row_idx}", # 用户提到的格式
|
||||
f"P1_{self.LETTERS[row_idx]}{col_idx + 1}", # 原有的A1-E8格式
|
||||
f"P1_{self.LETTERS[row_idx].lower()}{col_idx + 1}", # 小写字母格式
|
||||
]
|
||||
|
||||
r = None
|
||||
resource_name = None
|
||||
for name in possible_names:
|
||||
if name in self.station_resources:
|
||||
r = self.station_resources[name]
|
||||
resource_name = name
|
||||
break
|
||||
# 计算在5×8网格中的位置
|
||||
row_idx = (subdev_id - 1) # 0-4 (对应A-E)
|
||||
col_idx = (chl_id - 1) # 0-7 (对应1-8)
|
||||
resource_name = f"P1_{self.LETTERS[row_idx]}{col_idx + 1}"
|
||||
|
||||
r = self.station_resources.get(resource_name)
|
||||
if r:
|
||||
status_channel = status_row.get(chl_id, {})
|
||||
metrics = status_channel.get("metrics", {})
|
||||
# 构建BatteryTestPosition状态数据(移除capacity和energy)
|
||||
channel_state = {
|
||||
# 基本测量数据
|
||||
"voltage": metrics.get("voltage_V", 0.0),
|
||||
"current": metrics.get("current_A", 0.0),
|
||||
"time": metrics.get("totaltime_s", 0.0),
|
||||
|
||||
# 状态信息
|
||||
"status": status_channel.get("state", "unknown"),
|
||||
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
||||
|
||||
# 通道名称标识
|
||||
"Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
|
||||
|
||||
"voltage": status_channel.get("voltage_V", 0.0),
|
||||
"current": status_channel.get("current_A", 0.0),
|
||||
"time": status_channel.get("totaltime_s", 0.0),
|
||||
}
|
||||
r.load_state(channel_state)
|
||||
|
||||
# 调试信息
|
||||
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||
self._ros_node.lab_logger().debug(
|
||||
f"更新P1资源状态: {resource_name} <- subdev{subdev_id}/chl{chl_id} "
|
||||
f"状态:{channel_state['status']}"
|
||||
)
|
||||
else:
|
||||
# 如果找不到资源,记录调试信息
|
||||
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||
self._ros_node.lab_logger().debug(
|
||||
f"P1未找到资源: subdev{subdev_id}/chl{chl_id} -> 尝试的名称: {possible_names}"
|
||||
)
|
||||
except (KeyError, IndexError) as e:
|
||||
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||
self._ros_node.lab_logger().debug(f"P1映射错误: subdev{subdev_id}/chl{chl_id} - {e}")
|
||||
except (KeyError, IndexError):
|
||||
continue
|
||||
|
||||
# 第2盘:subdevid 6-10 映射到 8列5行网格 (列0-7, 行0-4)
|
||||
# 第2盘:subdevid 6-10 映射到 P2_A1-P2_E8 (5行8列)
|
||||
for subdev_id in range(6, 11): # subdevid 6-10
|
||||
status_row = subunits.get(subdev_id, {})
|
||||
|
||||
for chl_id in range(1, 9): # chlid 1-8
|
||||
try:
|
||||
col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 列0-7)
|
||||
row_idx = (subdev_id - 6) # 0-4 (subdevid 6-10 -> 行0-4)
|
||||
|
||||
# 尝试多种可能的资源命名格式
|
||||
possible_names = [
|
||||
f"P2_batterytestposition_{col_idx}_{row_idx}", # 用户提到的格式
|
||||
f"P2_{self.LETTERS[row_idx]}{col_idx + 1}", # 原有的A1-E8格式
|
||||
f"P2_{self.LETTERS[row_idx].lower()}{col_idx + 1}", # 小写字母格式
|
||||
]
|
||||
|
||||
r = None
|
||||
resource_name = None
|
||||
for name in possible_names:
|
||||
if name in self.station_resources:
|
||||
r = self.station_resources[name]
|
||||
resource_name = name
|
||||
break
|
||||
# 计算在5×8网格中的位置
|
||||
row_idx = (subdev_id - 6) # 0-4 (subdevid 6->0, 7->1, ..., 10->4) (对应A-E)
|
||||
col_idx = (chl_id - 1) # 0-7 (对应1-8)
|
||||
resource_name = f"P2_{self.LETTERS[row_idx]}{col_idx + 1}"
|
||||
|
||||
r = self.station_resources.get(resource_name)
|
||||
if r:
|
||||
status_channel = status_row.get(chl_id, {})
|
||||
metrics = status_channel.get("metrics", {})
|
||||
# 构建BatteryTestPosition状态数据(移除capacity和energy)
|
||||
channel_state = {
|
||||
# 基本测量数据
|
||||
"voltage": metrics.get("voltage_V", 0.0),
|
||||
"current": metrics.get("current_A", 0.0),
|
||||
"time": metrics.get("totaltime_s", 0.0),
|
||||
|
||||
# 状态信息
|
||||
"status": status_channel.get("state", "unknown"),
|
||||
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
||||
|
||||
# 通道名称标识
|
||||
"Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
|
||||
|
||||
"voltage": status_channel.get("voltage_V", 0.0),
|
||||
"current": status_channel.get("current_A", 0.0),
|
||||
"time": status_channel.get("totaltime_s", 0.0),
|
||||
}
|
||||
r.load_state(channel_state)
|
||||
|
||||
# 调试信息
|
||||
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||
self._ros_node.lab_logger().debug(
|
||||
f"更新P2资源状态: {resource_name} <- subdev{subdev_id}/chl{chl_id} "
|
||||
f"状态:{channel_state['status']}"
|
||||
)
|
||||
else:
|
||||
# 如果找不到资源,记录调试信息
|
||||
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||
self._ros_node.lab_logger().debug(
|
||||
f"P2未找到资源: subdev{subdev_id}/chl{chl_id} -> 尝试的名称: {possible_names}"
|
||||
)
|
||||
except (KeyError, IndexError) as e:
|
||||
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||
self._ros_node.lab_logger().debug(f"P2映射错误: subdev{subdev_id}/chl{chl_id} - {e}")
|
||||
except (KeyError, IndexError):
|
||||
continue
|
||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||
"resources": list(self.station_resources.values())
|
||||
})
|
||||
|
||||
@property
|
||||
def connection_info(self) -> Dict[str, str]:
|
||||
@@ -572,45 +490,6 @@ class NewareBatteryTestSystem:
|
||||
|
||||
|
||||
|
||||
def debug_resource_names(self) -> dict:
|
||||
"""
|
||||
调试方法:显示所有资源的实际名称(ROS2动作)
|
||||
|
||||
Returns:
|
||||
dict: ROS2动作结果格式,包含所有资源名称信息
|
||||
"""
|
||||
try:
|
||||
debug_info = {
|
||||
"total_resources": len(self.station_resources),
|
||||
"plate1_resources": len(self.station_resources_plate1),
|
||||
"plate2_resources": len(self.station_resources_plate2),
|
||||
"plate1_names": list(self.station_resources_plate1.keys())[:10], # 显示前10个
|
||||
"plate2_names": list(self.station_resources_plate2.keys())[:10], # 显示前10个
|
||||
"all_resource_names": list(self.station_resources.keys())[:20], # 显示前20个
|
||||
}
|
||||
|
||||
# 检查是否有用户提到的命名格式
|
||||
batterytestposition_names = [name for name in self.station_resources.keys()
|
||||
if "batterytestposition" in name]
|
||||
debug_info["batterytestposition_names"] = batterytestposition_names[:10]
|
||||
|
||||
success_msg = f"资源调试信息获取成功,共{debug_info['total_resources']}个资源"
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(success_msg)
|
||||
self._ros_node.lab_logger().info(f"调试信息: {debug_info}")
|
||||
|
||||
return {
|
||||
"return_info": success_msg,
|
||||
"success": True,
|
||||
"debug_data": debug_info
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"获取资源调试信息失败: {str(e)}"
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(error_msg)
|
||||
return {"return_info": error_msg, "success": False}
|
||||
|
||||
# ========================
|
||||
# 辅助方法
|
||||
# ========================
|
||||
@@ -659,228 +538,6 @@ class NewareBatteryTestSystem:
|
||||
except Exception as e:
|
||||
print(f" 获取状态失败: {e}")
|
||||
|
||||
# ========================
|
||||
# CSV批量提交功能(新增)
|
||||
# ========================
|
||||
|
||||
def _ensure_local_import_path(self):
|
||||
"""确保本地模块导入路径"""
|
||||
base_dir = os.path.dirname(__file__)
|
||||
if base_dir not in sys.path:
|
||||
sys.path.insert(0, base_dir)
|
||||
|
||||
def _canon(self, bs: str) -> str:
|
||||
"""规范化电池体系名称"""
|
||||
return str(bs).strip().replace('-', '_').upper()
|
||||
|
||||
def _compute_values(self, row):
|
||||
"""
|
||||
计算活性物质质量和容量
|
||||
|
||||
Args:
|
||||
row: DataFrame行数据
|
||||
|
||||
Returns:
|
||||
tuple: (活性物质质量mg, 容量mAh)
|
||||
"""
|
||||
pw = float(row['Pole_Weight'])
|
||||
cm = float(row['集流体质量'])
|
||||
am = row['活性物质含量']
|
||||
if isinstance(am, str) and am.endswith('%'):
|
||||
amv = float(am.rstrip('%')) / 100.0
|
||||
else:
|
||||
amv = float(am)
|
||||
act_mass = (pw - cm) * amv
|
||||
sc = float(row['克容量mah/g'])
|
||||
cap = act_mass * sc / 1000.0
|
||||
return round(act_mass, 2), round(cap, 3)
|
||||
|
||||
def _get_xml_builder(self, gen_mod, key: str):
|
||||
"""
|
||||
获取对应电池体系的XML生成函数
|
||||
|
||||
Args:
|
||||
gen_mod: generate_xml_content模块
|
||||
key: 电池体系标识
|
||||
|
||||
Returns:
|
||||
callable: XML生成函数
|
||||
"""
|
||||
fmap = {
|
||||
'LB6': gen_mod.xml_LB6,
|
||||
'GR_LI': gen_mod.xml_Gr_Li,
|
||||
'LFP_LI': gen_mod.xml_LFP_Li,
|
||||
'LFP_GR': gen_mod.xml_LFP_Gr,
|
||||
'811_LI_002': gen_mod.xml_811_Li_002,
|
||||
'811_LI_005': gen_mod.xml_811_Li_005,
|
||||
'SIGR_LI_STEP': gen_mod.xml_SiGr_Li_Step,
|
||||
'SIGR_LI': gen_mod.xml_SiGr_Li_Step,
|
||||
'811_SIGR': gen_mod.xml_811_SiGr,
|
||||
}
|
||||
if key not in fmap:
|
||||
raise ValueError(f"未定义电池体系映射: {key}")
|
||||
return fmap[key]
|
||||
|
||||
def _save_xml(self, xml: str, path: str):
|
||||
"""
|
||||
保存XML文件
|
||||
|
||||
Args:
|
||||
xml: XML内容
|
||||
path: 文件路径
|
||||
"""
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
f.write(xml)
|
||||
|
||||
def submit_from_csv(self, csv_path: str, output_dir: str = ".") -> dict:
|
||||
"""
|
||||
从CSV文件批量提交Neware测试任务(设备动作)
|
||||
|
||||
Args:
|
||||
csv_path (str): 输入CSV文件路径
|
||||
output_dir (str): 输出目录,用于存储XML文件和备份,默认当前目录
|
||||
|
||||
Returns:
|
||||
dict: 执行结果 {"return_info": str, "success": bool, "submitted_count": int}
|
||||
"""
|
||||
try:
|
||||
# 确保可以导入本地模块
|
||||
self._ensure_local_import_path()
|
||||
import pandas as pd
|
||||
import generate_xml_content as gen_mod
|
||||
from neware_driver import start_test
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(f"开始从CSV文件提交任务: {csv_path}")
|
||||
|
||||
# 读取CSV文件
|
||||
if not os.path.exists(csv_path):
|
||||
error_msg = f"CSV文件不存在: {csv_path}"
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(error_msg)
|
||||
return {"return_info": error_msg, "success": False, "submitted_count": 0}
|
||||
|
||||
df = pd.read_csv(csv_path, encoding='gbk')
|
||||
|
||||
# 验证必需列
|
||||
required = [
|
||||
'Battery_Code', 'Pole_Weight', '集流体质量', '活性物质含量',
|
||||
'克容量mah/g', '电池体系', '设备号', '排号', '通道号'
|
||||
]
|
||||
missing = [c for c in required if c not in df.columns]
|
||||
if missing:
|
||||
error_msg = f"CSV缺少必需列: {missing}"
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(error_msg)
|
||||
return {"return_info": error_msg, "success": False, "submitted_count": 0}
|
||||
|
||||
# 创建输出目录
|
||||
xml_dir = os.path.join(output_dir, 'xml_dir')
|
||||
backup_dir = os.path.join(output_dir, 'backup_dir')
|
||||
os.makedirs(xml_dir, exist_ok=True)
|
||||
os.makedirs(backup_dir, exist_ok=True)
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(
|
||||
f"输出目录: XML={xml_dir}, 备份={backup_dir}"
|
||||
)
|
||||
|
||||
# 逐行处理CSV数据
|
||||
submitted_count = 0
|
||||
results = []
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
try:
|
||||
coin_id = str(row['Battery_Code'])
|
||||
|
||||
# 计算活性物质质量和容量
|
||||
act_mass, cap_mAh = self._compute_values(row)
|
||||
|
||||
if cap_mAh < 0:
|
||||
error_msg = (
|
||||
f"容量为负数: Battery_Code={coin_id}, "
|
||||
f"活性物质质量mg={act_mass}, 容量mah={cap_mAh}"
|
||||
)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().warning(error_msg)
|
||||
results.append(f"行{idx+1} 失败: {error_msg}")
|
||||
continue
|
||||
|
||||
# 获取电池体系对应的XML生成函数
|
||||
key = self._canon(row['电池体系'])
|
||||
builder = self._get_xml_builder(gen_mod, key)
|
||||
|
||||
# 生成XML内容
|
||||
xml_content = builder(act_mass, cap_mAh)
|
||||
|
||||
# 获取设备信息
|
||||
devid = int(row['设备号'])
|
||||
subdevid = int(row['排号'])
|
||||
chlid = int(row['通道号'])
|
||||
|
||||
# 保存XML文件
|
||||
recipe_path = os.path.join(
|
||||
xml_dir,
|
||||
f"{coin_id}_{devid}_{subdevid}_{chlid}.xml"
|
||||
)
|
||||
self._save_xml(xml_content, recipe_path)
|
||||
|
||||
# 提交测试任务
|
||||
resp = start_test(
|
||||
ip=self.ip,
|
||||
port=self.port,
|
||||
devid=devid,
|
||||
subdevid=subdevid,
|
||||
chlid=chlid,
|
||||
CoinID=coin_id,
|
||||
recipe_path=recipe_path,
|
||||
backup_dir=backup_dir
|
||||
)
|
||||
|
||||
submitted_count += 1
|
||||
results.append(f"行{idx+1} {coin_id}: {resp}")
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(
|
||||
f"已提交 {coin_id} (设备{devid}-{subdevid}-{chlid}): {resp}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"行{idx+1} 处理失败: {str(e)}"
|
||||
results.append(error_msg)
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(error_msg)
|
||||
|
||||
# 汇总结果
|
||||
success_msg = (
|
||||
f"批量提交完成: 成功{submitted_count}个,共{len(df)}行。"
|
||||
f"\n详细结果:\n" + "\n".join(results)
|
||||
)
|
||||
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().info(
|
||||
f"批量提交完成: 成功{submitted_count}/{len(df)}"
|
||||
)
|
||||
|
||||
return {
|
||||
"return_info": success_msg,
|
||||
"success": True,
|
||||
"submitted_count": submitted_count,
|
||||
"total_count": len(df),
|
||||
"results": results
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"批量提交失败: {str(e)}"
|
||||
if self._ros_node:
|
||||
self._ros_node.lab_logger().error(error_msg)
|
||||
return {
|
||||
"return_info": error_msg,
|
||||
"success": False,
|
||||
"submitted_count": 0
|
||||
}
|
||||
|
||||
|
||||
def get_device_summary(self) -> dict:
|
||||
"""
|
||||
获取设备级别的摘要统计(设备动作)
|
||||
@@ -1,8 +0,0 @@
|
||||
from .neware_battery_test_system import NewareBatteryTestSystem
|
||||
from .neware_driver import build_start_command, start_test
|
||||
|
||||
__all__ = [
|
||||
"NewareBatteryTestSystem",
|
||||
"build_start_command",
|
||||
"start_test",
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
Timestamp,Battery_Count,Assembly_Time,Open_Circuit_Voltage,Pole_Weight,Assembly_Pressure,Battery_Code,Electrolyte_Code,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʺ<EFBFBD><EFBFBD><EFBFBD>,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>mah/g,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ,<EFBFBD>豸<EFBFBD><EFBFBD>,<EFBFBD>ź<EFBFBD>,ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
2025/10/29 17:32,7,5,0.11299999803304672,18.049999237060547,3593,Li000595,Si-Gr001,9.2,0.954,469,SiGr_Li,1,1,2
|
||||
2025/10/30 17:49,2,5,0,13.109999895095825,4094,YS101224,NoRead88,5.2,0.92,190,SiGr_Li,2,1,1
|
||||
|
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
||||
"name": "Neware Battery Test System",
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "neware_battery_test_system",
|
||||
"position": {
|
||||
"x": 620.0,
|
||||
"y": 200.0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"ip": "127.0.0.1",
|
||||
"port": 502,
|
||||
"machine_id": 1,
|
||||
"devtype": "27",
|
||||
"timeout": 20,
|
||||
"size_x": 500.0,
|
||||
"size_y": 500.0,
|
||||
"size_z": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"功能说明": "新威电池测试系统,提供720通道监控和CSV批量提交功能",
|
||||
"监控功能": "支持720个通道的实时状态监控、2盘电池物料管理、状态导出等",
|
||||
"提交功能": "通过submit_from_csv action从CSV文件批量提交测试任务。CSV必须包含: Battery_Code, Pole_Weight, 集流体质量, 活性物质含量, 克容量mah/g, 电池体系, 设备号, 排号, 通道号"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
import socket
|
||||
END_MARKS = [b"\r\n#\r\n", b"</bts>"] # 读到任一标志即可判定完整响应
|
||||
|
||||
def build_start_command(devid, subdevid, chlid, CoinID,
|
||||
ip_in_xml="127.0.0.1",
|
||||
devtype:int=27,
|
||||
recipe_path:str=f"D:\\HHM_test\\A001.xml",
|
||||
backup_dir:str=f"D:\\HHM_test\\backup") -> str:
|
||||
lines = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<bts version="1.0">',
|
||||
' <cmd>start</cmd>',
|
||||
' <list count="1">',
|
||||
f' <start ip="{ip_in_xml}" devtype="{devtype}" devid="{devid}" subdevid="{subdevid}" chlid="{chlid}" barcode="{CoinID}">{recipe_path}</start>',
|
||||
f' <backup backupdir="{backup_dir}" remotedir="" filenametype="1" customfilename="" createdirbydate="0" filetype="0" backupontime="1" backupontimeinterval="1" backupfree="0" />',
|
||||
' </list>',
|
||||
'</bts>',
|
||||
]
|
||||
# TCP 模式:请求必须以 #\r\n 结束(协议要求)
|
||||
return "\r\n".join(lines) + "\r\n#\r\n"
|
||||
|
||||
def recv_until_marks(sock: socket.socket, timeout=60):
|
||||
sock.settimeout(timeout) # 上限给足,协议允许到 30s:contentReference[oaicite:2]{index=2}
|
||||
buf = bytearray()
|
||||
while True:
|
||||
chunk = sock.recv(8192)
|
||||
if not chunk:
|
||||
break
|
||||
buf += chunk
|
||||
# 读到结束标志就停,避免等对端断开
|
||||
for m in END_MARKS:
|
||||
if m in buf:
|
||||
return bytes(buf)
|
||||
# 保险:读到完整 XML 结束标签也停
|
||||
if b"</bts>" in buf:
|
||||
return bytes(buf)
|
||||
return bytes(buf)
|
||||
|
||||
def start_test(ip="127.0.0.1", port=502, devid=3, subdevid=2, chlid=1, CoinID="A001", recipe_path=f"D:\\HHM_test\\A001.xml", backup_dir=f"D:\\HHM_test\\backup"):
|
||||
xml_cmd = build_start_command(devid=devid, subdevid=subdevid, chlid=chlid, CoinID=CoinID, recipe_path=recipe_path, backup_dir=backup_dir)
|
||||
#print(xml_cmd)
|
||||
with socket.create_connection((ip, port), timeout=60) as s:
|
||||
s.sendall(xml_cmd.encode("utf-8"))
|
||||
data = recv_until_marks(s, timeout=60)
|
||||
return data.decode("utf-8", errors="replace")
|
||||
|
||||
if __name__ == "__main__":
|
||||
resp = start_test(ip="127.0.0.1", port=502, devid=4, subdevid=10, chlid=1, CoinID="A001", recipe_path=f"D:\\HHM_test\\A001.xml", backup_dir=f"D:\\HHM_test\\backup")
|
||||
print(resp)
|
||||
@@ -1,282 +1,649 @@
|
||||
import sys
|
||||
import threading
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import re
|
||||
import time
|
||||
from typing import Optional, List, Dict, Tuple
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Contains drivers for:
|
||||
1. SyringePump: Runze Fluid SY-03B (ASCII)
|
||||
2. EmmMotor: Emm V5.0 Closed-loop Stepper (Modbus-RTU variant)
|
||||
3. XKCSensor: XKC Non-contact Level Sensor (Modbus-RTU)
|
||||
"""
|
||||
|
||||
class ChinweDevice:
|
||||
import socket
|
||||
import serial
|
||||
import time
|
||||
import threading
|
||||
import struct
|
||||
import re
|
||||
import traceback
|
||||
import queue
|
||||
from typing import Optional, Dict, List, Any
|
||||
|
||||
try:
|
||||
from unilabos.device_comms.universal_driver import UniversalDriver
|
||||
except ImportError:
|
||||
import logging
|
||||
class UniversalDriver:
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
def execute_command_from_outer(self, command: str):
|
||||
pass
|
||||
|
||||
# ==============================================================================
|
||||
# 1. Transport Layer (通信层)
|
||||
# ==============================================================================
|
||||
|
||||
class TransportManager:
|
||||
"""
|
||||
ChinWe设备控制类
|
||||
提供串口通信、电机控制、传感器数据读取等功能
|
||||
统一通信管理类。
|
||||
自动识别 串口 (Serial) 或 网络 (TCP) 连接。
|
||||
"""
|
||||
|
||||
def __init__(self, port: str, baudrate: int = 115200, debug: bool = False):
|
||||
"""
|
||||
初始化ChinWe设备
|
||||
|
||||
Args:
|
||||
port: 串口名称,如果为None则自动检测
|
||||
baudrate: 波特率,默认115200
|
||||
"""
|
||||
self.debug = debug
|
||||
def __init__(self, port: str, baudrate: int = 9600, timeout: float = 3.0, logger=None):
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.serial_port: Optional[serial.Serial] = None
|
||||
self._voltage: float = 0.0
|
||||
self._ec_value: float = 0.0
|
||||
self._ec_adc_value: int = 0
|
||||
self.timeout = timeout
|
||||
self.logger = logger
|
||||
self.lock = threading.RLock() # 线程锁,确保多设备共用一个连接时不冲突
|
||||
|
||||
self.is_tcp = False
|
||||
self.serial = None
|
||||
self.socket = None
|
||||
|
||||
# 简单判断: 如果包含 ':' (如 192.168.1.1:8899) 或者看起来像 IP,则认为是 TCP
|
||||
if ':' in self.port or (self.port.count('.') == 3 and not self.port.startswith('/')):
|
||||
self.is_tcp = True
|
||||
self._connect_tcp()
|
||||
else:
|
||||
self._connect_serial()
|
||||
|
||||
def _log(self, msg):
|
||||
if self.logger:
|
||||
pass
|
||||
# self.logger.debug(f"[Transport] {msg}")
|
||||
|
||||
def _connect_tcp(self):
|
||||
try:
|
||||
if ':' in self.port:
|
||||
host, p = self.port.split(':')
|
||||
self.tcp_host = host
|
||||
self.tcp_port = int(p)
|
||||
else:
|
||||
self.tcp_host = self.port
|
||||
self.tcp_port = 8899 # 默认端口
|
||||
|
||||
# if self.logger: self.logger.info(f"Connecting TCP {self.tcp_host}:{self.tcp_port} ...")
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.settimeout(self.timeout)
|
||||
self.socket.connect((self.tcp_host, self.tcp_port))
|
||||
except Exception as e:
|
||||
raise ConnectionError(f"TCP connection failed: {e}")
|
||||
|
||||
def _connect_serial(self):
|
||||
try:
|
||||
# if self.logger: self.logger.info(f"Opening Serial {self.port} (Baud: {self.baudrate}) ...")
|
||||
self.serial = serial.Serial(
|
||||
port=self.port,
|
||||
baudrate=self.baudrate,
|
||||
timeout=self.timeout
|
||||
)
|
||||
except Exception as e:
|
||||
raise ConnectionError(f"Serial open failed: {e}")
|
||||
|
||||
def close(self):
|
||||
"""关闭连接"""
|
||||
if self.is_tcp and self.socket:
|
||||
try: self.socket.close()
|
||||
except: pass
|
||||
elif not self.is_tcp and self.serial and self.serial.is_open:
|
||||
self.serial.close()
|
||||
|
||||
def clear_buffer(self):
|
||||
"""清空缓冲区 (Thread-safe)"""
|
||||
with self.lock:
|
||||
if self.is_tcp:
|
||||
self.socket.setblocking(False)
|
||||
try:
|
||||
while True:
|
||||
if not self.socket.recv(1024): break
|
||||
except: pass
|
||||
finally: self.socket.settimeout(self.timeout)
|
||||
else:
|
||||
self.serial.reset_input_buffer()
|
||||
|
||||
def write(self, data: bytes):
|
||||
"""发送原始字节"""
|
||||
with self.lock:
|
||||
if self.is_tcp:
|
||||
self.socket.sendall(data)
|
||||
else:
|
||||
self.serial.write(data)
|
||||
|
||||
def read(self, size: int) -> bytes:
|
||||
"""读取指定长度字节"""
|
||||
if self.is_tcp:
|
||||
data = b''
|
||||
start = time.time()
|
||||
while len(data) < size:
|
||||
if time.time() - start > self.timeout: break
|
||||
try:
|
||||
chunk = self.socket.recv(size - len(data))
|
||||
if not chunk: break
|
||||
data += chunk
|
||||
except socket.timeout: break
|
||||
return data
|
||||
else:
|
||||
return self.serial.read(size)
|
||||
|
||||
def send_ascii_command(self, command: str) -> str:
|
||||
"""
|
||||
发送 ASCII 字符串命令 (如注射泵指令),读取直到 '\r'。
|
||||
"""
|
||||
with self.lock:
|
||||
data = command.encode('ascii') if isinstance(command, str) else command
|
||||
self.clear_buffer()
|
||||
self.write(data)
|
||||
|
||||
# Read until \r
|
||||
if self.is_tcp:
|
||||
resp = b''
|
||||
start = time.time()
|
||||
while True:
|
||||
if time.time() - start > self.timeout: break
|
||||
try:
|
||||
char = self.socket.recv(1)
|
||||
if not char: break
|
||||
resp += char
|
||||
if char == b'\r': break
|
||||
except: break
|
||||
return resp.decode('ascii', errors='ignore').strip()
|
||||
else:
|
||||
return self.serial.read_until(b'\r').decode('ascii', errors='ignore').strip()
|
||||
|
||||
# ==============================================================================
|
||||
# 2. Syringe Pump Driver (注射泵)
|
||||
# ==============================================================================
|
||||
|
||||
class SyringePump:
|
||||
"""SY-03B 注射泵驱动 (ASCII协议)"""
|
||||
|
||||
CMD_INITIALIZE = "Z{speed},{drain_port},{output_port}R"
|
||||
CMD_SWITCH_VALVE = "I{port}R"
|
||||
CMD_ASPIRATE = "P{vol}R"
|
||||
CMD_DISPENSE = "D{vol}R"
|
||||
CMD_DISPENSE_ALL = "A0R"
|
||||
CMD_STOP = "TR"
|
||||
CMD_QUERY_STATUS = "Q"
|
||||
CMD_QUERY_PLUNGER = "?0"
|
||||
|
||||
def __init__(self, device_id: int, transport: TransportManager):
|
||||
if not 1 <= device_id <= 15:
|
||||
pass # Allow all IDs for now
|
||||
self.id = str(device_id)
|
||||
self.transport = transport
|
||||
|
||||
def _send(self, template: str, **kwargs) -> str:
|
||||
cmd = f"/{self.id}" + template.format(**kwargs) + "\r"
|
||||
return self.transport.send_ascii_command(cmd)
|
||||
|
||||
def is_busy(self) -> bool:
|
||||
"""查询繁忙状态"""
|
||||
resp = self._send(self.CMD_QUERY_STATUS)
|
||||
# 响应如 /0` (Ready, 0x60) 或 /0@ (Busy, 0x40)
|
||||
if len(resp) >= 3:
|
||||
status_byte = ord(resp[2])
|
||||
# Bit 5: 1=Ready, 0=Busy
|
||||
return (status_byte & 0x20) == 0
|
||||
return False
|
||||
|
||||
def wait_until_idle(self, timeout=30):
|
||||
"""阻塞等待直到空闲"""
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
if not self.is_busy(): return
|
||||
time.sleep(0.5)
|
||||
# raise TimeoutError(f"Pump {self.id} wait idle timeout")
|
||||
pass
|
||||
|
||||
def initialize(self, drain_port=0, output_port=0, speed=10):
|
||||
"""初始化"""
|
||||
self._send(self.CMD_INITIALIZE, speed=speed, drain_port=drain_port, output_port=output_port)
|
||||
|
||||
def switch_valve(self, port: int):
|
||||
"""切换阀门 (1-8)"""
|
||||
self._send(self.CMD_SWITCH_VALVE, port=port)
|
||||
|
||||
def aspirate(self, steps: int):
|
||||
"""吸液 (相对步数)"""
|
||||
self._send(self.CMD_ASPIRATE, vol=steps)
|
||||
|
||||
def dispense(self, steps: int):
|
||||
"""排液 (相对步数)"""
|
||||
self._send(self.CMD_DISPENSE, vol=steps)
|
||||
|
||||
def stop(self):
|
||||
"""停止"""
|
||||
self._send(self.CMD_STOP)
|
||||
|
||||
def get_position(self) -> int:
|
||||
"""获取柱塞位置 (步数)"""
|
||||
resp = self._send(self.CMD_QUERY_PLUNGER)
|
||||
m = re.search(r'\d+', resp)
|
||||
return int(m.group()) if m else -1
|
||||
|
||||
# ==============================================================================
|
||||
# 3. Stepper Motor Driver (步进电机)
|
||||
# ==============================================================================
|
||||
|
||||
class EmmMotor:
|
||||
"""Emm V5.0 闭环步进电机驱动"""
|
||||
|
||||
def __init__(self, device_id: int, transport: TransportManager):
|
||||
self.id = device_id
|
||||
self.transport = transport
|
||||
|
||||
def _send(self, func_code: int, payload: list) -> bytes:
|
||||
with self.transport.lock:
|
||||
self.transport.clear_buffer()
|
||||
# 格式: [ID] [Func] [Data...] [Check=0x6B]
|
||||
body = [self.id, func_code] + payload
|
||||
body.append(0x6B) # Checksum
|
||||
self.transport.write(bytes(body))
|
||||
|
||||
# 根据指令不同,读取不同长度响应
|
||||
read_len = 10 if func_code in [0x31, 0x32, 0x35, 0x24, 0x27] else 4
|
||||
return self.transport.read(read_len)
|
||||
|
||||
def enable(self, on=True):
|
||||
"""使能 (True=锁轴, False=松轴)"""
|
||||
state = 1 if on else 0
|
||||
self._send(0xF3, [0xAB, state, 0])
|
||||
|
||||
def run_speed(self, speed_rpm: int, direction=0, acc=10):
|
||||
"""速度模式运行"""
|
||||
sp = struct.pack('>H', int(speed_rpm))
|
||||
self._send(0xF6, [direction, sp[0], sp[1], acc, 0])
|
||||
|
||||
def run_position(self, pulses: int, speed_rpm: int, direction=0, acc=10, absolute=False):
|
||||
"""位置模式运行"""
|
||||
sp = struct.pack('>H', int(speed_rpm))
|
||||
pl = struct.pack('>I', int(pulses))
|
||||
is_abs = 1 if absolute else 0
|
||||
self._send(0xFD, [direction, sp[0], sp[1], acc, pl[0], pl[1], pl[2], pl[3], is_abs, 0])
|
||||
|
||||
def stop(self):
|
||||
"""停止"""
|
||||
self._send(0xFE, [0x98, 0])
|
||||
|
||||
def set_zero(self):
|
||||
"""清零位置"""
|
||||
self._send(0x0A, [])
|
||||
|
||||
def get_position(self) -> int:
|
||||
"""获取当前脉冲位置"""
|
||||
resp = self._send(0x32, [])
|
||||
if len(resp) >= 8:
|
||||
sign = resp[2]
|
||||
val = struct.unpack('>I', resp[3:7])[0]
|
||||
return -val if sign == 1 else val
|
||||
return 0
|
||||
|
||||
# ==============================================================================
|
||||
# 4. Liquid Sensor Driver (液位传感器)
|
||||
# ==============================================================================
|
||||
|
||||
class XKCSensor:
|
||||
"""XKC RS485 液位传感器 (Modbus RTU)"""
|
||||
|
||||
def __init__(self, device_id: int, transport: TransportManager, threshold: int = 300):
|
||||
self.id = device_id
|
||||
self.transport = transport
|
||||
self.threshold = threshold
|
||||
|
||||
def _crc(self, data: bytes) -> bytes:
|
||||
crc = 0xFFFF
|
||||
for byte in data:
|
||||
crc ^= byte
|
||||
for _ in range(8):
|
||||
if crc & 0x0001: crc = (crc >> 1) ^ 0xA001
|
||||
else: crc >>= 1
|
||||
return struct.pack('<H', crc)
|
||||
|
||||
def read_level(self) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
读取液位。
|
||||
返回: {'level': bool, 'rssi': int}
|
||||
"""
|
||||
with self.transport.lock:
|
||||
self.transport.clear_buffer()
|
||||
# Modbus Read Registers: 01 03 00 01 00 02 CRC
|
||||
payload = struct.pack('>HH', 0x0001, 0x0002)
|
||||
msg = struct.pack('BB', self.id, 0x03) + payload
|
||||
msg += self._crc(msg)
|
||||
self.transport.write(msg)
|
||||
|
||||
# Read header
|
||||
h = self.transport.read(3) # Addr, Func, Len
|
||||
if len(h) < 3: return None
|
||||
length = h[2]
|
||||
|
||||
# Read body + CRC
|
||||
body = self.transport.read(length + 2)
|
||||
if len(body) < length + 2:
|
||||
# Firmware bug fix specific to some modules
|
||||
if len(body) == 4 and length == 4:
|
||||
pass
|
||||
else:
|
||||
return None
|
||||
|
||||
data = body[:-2]
|
||||
if len(data) == 2:
|
||||
rssi = data[1]
|
||||
elif len(data) >= 4:
|
||||
rssi = (data[2] << 8) | data[3]
|
||||
else:
|
||||
return None
|
||||
|
||||
return {
|
||||
'level': rssi > self.threshold,
|
||||
'rssi': rssi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# 5. Main Device Class (ChinweDevice)
|
||||
# ==============================================================================
|
||||
|
||||
class ChinweDevice(UniversalDriver):
|
||||
"""
|
||||
ChinWe 工作站主驱动
|
||||
继承自 UniversalDriver,管理所有子设备(泵、电机、传感器)
|
||||
"""
|
||||
|
||||
def __init__(self, port: str = "192.168.1.200:8899", baudrate: int = 9600,
|
||||
pump_ids: List[int] = None, motor_ids: List[int] = None,
|
||||
sensor_id: int = 6, sensor_threshold: int = 300,
|
||||
timeout: float = 10.0):
|
||||
"""
|
||||
初始化 ChinWe 工作站
|
||||
:param port: 串口号 或 IP:Port
|
||||
:param baudrate: 串口波特率
|
||||
:param pump_ids: 注射泵 ID列表 (默认 [1, 2, 3])
|
||||
:param motor_ids: 步进电机 ID列表 (默认 [4, 5])
|
||||
:param sensor_id: 液位传感器 ID (默认 6)
|
||||
:param sensor_threshold: 传感器液位判定阈值
|
||||
:param timeout: 通信超时时间 (默认 10秒)
|
||||
"""
|
||||
super().__init__()
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.timeout = timeout
|
||||
self.mgr = None
|
||||
self._is_connected = False
|
||||
self.connect()
|
||||
|
||||
|
||||
# 默认配置
|
||||
if pump_ids is None: pump_ids = [1, 2, 3]
|
||||
if motor_ids is None: motor_ids = [4, 5]
|
||||
|
||||
# 配置信息
|
||||
self.pump_ids = pump_ids
|
||||
self.motor_ids = motor_ids
|
||||
self.sensor_id = sensor_id
|
||||
self.sensor_threshold = sensor_threshold
|
||||
|
||||
# 子设备实例容器
|
||||
self.pumps: Dict[int, SyringePump] = {}
|
||||
self.motors: Dict[int, EmmMotor] = {}
|
||||
self.sensor: Optional[XKCSensor] = None
|
||||
|
||||
# 轮询线程控制
|
||||
self._stop_event = threading.Event()
|
||||
self._poll_thread = None
|
||||
|
||||
# 实时状态缓存
|
||||
self.status_cache = {
|
||||
"sensor_rssi": 0,
|
||||
"sensor_level": False,
|
||||
"connected": False
|
||||
}
|
||||
|
||||
# 自动连接
|
||||
if self.port:
|
||||
self.connect()
|
||||
|
||||
def connect(self) -> bool:
|
||||
if self._is_connected: return True
|
||||
try:
|
||||
self.logger.info(f"Connecting to {self.port} (timeout={self.timeout})...")
|
||||
self.mgr = TransportManager(self.port, baudrate=self.baudrate, timeout=self.timeout, logger=self.logger)
|
||||
|
||||
# 初始化所有泵
|
||||
for pid in self.pump_ids:
|
||||
self.pumps[pid] = SyringePump(pid, self.mgr)
|
||||
|
||||
# 初始化所有电机
|
||||
for mid in self.motor_ids:
|
||||
self.motors[mid] = EmmMotor(mid, self.mgr)
|
||||
|
||||
# 初始化传感器
|
||||
self.sensor = XKCSensor(self.sensor_id, self.mgr, self.sensor_threshold)
|
||||
|
||||
self._is_connected = True
|
||||
self.status_cache["connected"] = True
|
||||
|
||||
# 启动轮询线程
|
||||
self._start_polling()
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Connection failed: {e}")
|
||||
self._is_connected = False
|
||||
self.status_cache["connected"] = False
|
||||
return False
|
||||
|
||||
def disconnect(self):
|
||||
self._stop_event.set()
|
||||
if self._poll_thread:
|
||||
self._poll_thread.join(timeout=2.0)
|
||||
|
||||
if self.mgr:
|
||||
self.mgr.close()
|
||||
|
||||
self._is_connected = False
|
||||
self.status_cache["connected"] = False
|
||||
self.logger.info("Disconnected.")
|
||||
|
||||
def _start_polling(self):
|
||||
"""启动传感器轮询线程"""
|
||||
if self._poll_thread and self._poll_thread.is_alive():
|
||||
return
|
||||
|
||||
self._stop_event.clear()
|
||||
self._poll_thread = threading.Thread(target=self._polling_loop, daemon=True, name="ChinwePoll")
|
||||
self._poll_thread.start()
|
||||
|
||||
def _polling_loop(self):
|
||||
"""轮询主循环"""
|
||||
self.logger.info("Sensor polling started.")
|
||||
error_count = 0
|
||||
while not self._stop_event.is_set():
|
||||
if not self._is_connected or not self.sensor:
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
try:
|
||||
# 获取传感器数据
|
||||
data = self.sensor.read_level()
|
||||
if data:
|
||||
self.status_cache["sensor_rssi"] = data['rssi']
|
||||
self.status_cache["sensor_level"] = data['level']
|
||||
error_count = 0
|
||||
else:
|
||||
error_count += 1
|
||||
|
||||
# 降低轮询频率防止总线拥塞
|
||||
time.sleep(0.2)
|
||||
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
if error_count > 10: # 连续错误记录日志
|
||||
# self.logger.error(f"Polling error: {e}")
|
||||
error_count = 0
|
||||
time.sleep(1)
|
||||
|
||||
# --- 对外暴露属性 (Properties) ---
|
||||
|
||||
@property
|
||||
def sensor_level(self) -> bool:
|
||||
return self.status_cache["sensor_level"]
|
||||
|
||||
@property
|
||||
def sensor_rssi(self) -> int:
|
||||
return self.status_cache["sensor_rssi"]
|
||||
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
"""获取连接状态"""
|
||||
return self._is_connected and self.serial_port and self.serial_port.is_open
|
||||
|
||||
@property
|
||||
def voltage(self) -> float:
|
||||
"""获取电源电压值"""
|
||||
return self._voltage
|
||||
|
||||
@property
|
||||
def ec_value(self) -> float:
|
||||
"""获取电导率值 (ms/cm)"""
|
||||
return self._ec_value
|
||||
return self._is_connected
|
||||
|
||||
@property
|
||||
def ec_adc_value(self) -> int:
|
||||
"""获取EC ADC原始值"""
|
||||
return self._ec_adc_value
|
||||
|
||||
# --- 对外功能指令 (Actions) ---
|
||||
|
||||
@property
|
||||
def device_status(self) -> Dict[str, any]:
|
||||
"""
|
||||
获取设备状态信息
|
||||
|
||||
Returns:
|
||||
包含设备状态的字典
|
||||
"""
|
||||
return {
|
||||
"connected": self.is_connected,
|
||||
"port": self.port,
|
||||
"baudrate": self.baudrate,
|
||||
"voltage": self.voltage,
|
||||
"ec_value": self.ec_value,
|
||||
"ec_adc_value": self.ec_adc_value
|
||||
}
|
||||
|
||||
def connect(self, port: Optional[str] = None, baudrate: Optional[int] = None) -> bool:
|
||||
"""
|
||||
连接到串口设备
|
||||
|
||||
Args:
|
||||
port: 串口名称,如果为None则使用初始化时的port或自动检测
|
||||
baudrate: 波特率,如果为None则使用初始化时的baudrate
|
||||
|
||||
Returns:
|
||||
连接是否成功
|
||||
"""
|
||||
if self.is_connected:
|
||||
def pump_initialize(self, pump_id: int, drain_port=0, output_port=0, speed=10):
|
||||
"""指定泵初始化"""
|
||||
pump_id = int(pump_id)
|
||||
if pump_id in self.pumps:
|
||||
self.pumps[pump_id].initialize(drain_port, output_port, speed)
|
||||
self.pumps[pump_id].wait_until_idle()
|
||||
return True
|
||||
|
||||
target_port = port or self.port
|
||||
target_baudrate = baudrate or self.baudrate
|
||||
|
||||
try:
|
||||
self.serial_port = serial.Serial(target_port, target_baudrate, timeout=0.5)
|
||||
self._is_connected = True
|
||||
self.port = target_port
|
||||
self.baudrate = target_baudrate
|
||||
connect_allow_times = 5
|
||||
while not self.serial_port.is_open and connect_allow_times > 0:
|
||||
time.sleep(0.5)
|
||||
connect_allow_times -= 1
|
||||
print(f"尝试连接到 {target_port} @ {target_baudrate},剩余尝试次数: {connect_allow_times}", self.debug)
|
||||
raise ValueError("串口未打开,请检查设备连接")
|
||||
print(f"已连接到 {target_port} @ {target_baudrate}", self.debug)
|
||||
threading.Thread(target=self._read_data, daemon=True).start()
|
||||
return False
|
||||
|
||||
def pump_aspirate(self, pump_id: int, volume: int, valve_port: int):
|
||||
"""
|
||||
泵吸液 (阻塞)
|
||||
:param valve_port: 阀门端口 (1-8)
|
||||
"""
|
||||
pump_id = int(pump_id)
|
||||
valve_port = int(valve_port)
|
||||
if pump_id in self.pumps:
|
||||
pump = self.pumps[pump_id]
|
||||
# 1. 切换阀门
|
||||
pump.switch_valve(valve_port)
|
||||
pump.wait_until_idle()
|
||||
# 2. 吸液
|
||||
pump.aspirate(volume)
|
||||
pump.wait_until_idle()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"ChinweDevice连接失败: {e}")
|
||||
self._is_connected = False
|
||||
return False
|
||||
|
||||
def disconnect(self) -> bool:
|
||||
return False
|
||||
|
||||
def pump_dispense(self, pump_id: int, volume: int, valve_port: int):
|
||||
"""
|
||||
断开串口连接
|
||||
|
||||
Returns:
|
||||
断开是否成功
|
||||
泵排液 (阻塞)
|
||||
:param valve_port: 阀门端口 (1-8)
|
||||
"""
|
||||
if self.serial_port and self.serial_port.is_open:
|
||||
try:
|
||||
self.serial_port.close()
|
||||
self._is_connected = False
|
||||
print("已断开串口连接")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"断开连接失败: {e}")
|
||||
return False
|
||||
pump_id = int(pump_id)
|
||||
valve_port = int(valve_port)
|
||||
if pump_id in self.pumps:
|
||||
pump = self.pumps[pump_id]
|
||||
# 1. 切换阀门
|
||||
pump.switch_valve(valve_port)
|
||||
pump.wait_until_idle()
|
||||
# 2. 排液
|
||||
pump.dispense(volume)
|
||||
pump.wait_until_idle()
|
||||
return True
|
||||
return False
|
||||
|
||||
def pump_valve(self, pump_id: int, port: int):
|
||||
"""泵切换阀门 (阻塞)"""
|
||||
pump_id = int(pump_id)
|
||||
port = int(port)
|
||||
if pump_id in self.pumps:
|
||||
pump = self.pumps[pump_id]
|
||||
pump.switch_valve(port)
|
||||
pump.wait_until_idle()
|
||||
return True
|
||||
return False
|
||||
|
||||
def motor_run_continuous(self, motor_id: int, speed: int, direction: str = "顺时针"):
|
||||
"""
|
||||
电机一直旋转 (速度模式)
|
||||
:param direction: "顺时针" or "逆时针"
|
||||
"""
|
||||
motor_id = int(motor_id)
|
||||
if motor_id not in self.motors: return False
|
||||
|
||||
dir_val = 0 if direction == "顺时针" else 1
|
||||
self.motors[motor_id].run_speed(speed, dir_val)
|
||||
return True
|
||||
|
||||
def _send_motor_command(self, command: str) -> bool:
|
||||
|
||||
def motor_rotate_quarter(self, motor_id: int, speed: int = 60, direction: str = "顺时针"):
|
||||
"""
|
||||
发送电机控制命令
|
||||
|
||||
Args:
|
||||
command: 电机命令字符串,例如 "M 1 CW 1.5"
|
||||
|
||||
Returns:
|
||||
发送是否成功
|
||||
电机旋转1/4圈 (阻塞)
|
||||
假设电机设置为 3200 脉冲/圈,1/4圈 = 800脉冲
|
||||
"""
|
||||
if not self.is_connected:
|
||||
print("设备未连接")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.serial_port.write((command + "\n").encode('utf-8'))
|
||||
print(f"发送命令: {command}")
|
||||
motor_id = int(motor_id)
|
||||
if motor_id not in self.motors: return False
|
||||
|
||||
pulses = 800
|
||||
dir_val = 0 if direction == "顺时针" else 1
|
||||
|
||||
self.motors[motor_id].run_position(pulses, speed, dir_val, absolute=False)
|
||||
|
||||
# 预估时间阻塞 (单位: 分钟 -> 秒)
|
||||
# Time(s) = revs / (RPM/60). revs = 0.25. time = 15 / RPM.
|
||||
estimated_time = 15.0 / max(1, speed)
|
||||
time.sleep(estimated_time + 0.5)
|
||||
|
||||
return True
|
||||
|
||||
def motor_stop(self, motor_id: int):
|
||||
"""电机停止"""
|
||||
motor_id = int(motor_id)
|
||||
if motor_id in self.motors:
|
||||
self.motors[motor_id].stop()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"发送命令失败: {e}")
|
||||
return False
|
||||
|
||||
def rotate_motor(self, motor_id: int, turns: float, clockwise: bool = True) -> bool:
|
||||
"""
|
||||
使电机转动指定圈数
|
||||
|
||||
Args:
|
||||
motor_id: 电机ID(1, 2, 3...)
|
||||
turns: 转动圈数,支持小数
|
||||
clockwise: True为顺时针,False为逆时针
|
||||
|
||||
Returns:
|
||||
命令发送是否成功
|
||||
"""
|
||||
if clockwise:
|
||||
command = f"M {motor_id} CW {turns}"
|
||||
else:
|
||||
command = f"M {motor_id} CCW {turns}"
|
||||
return self._send_motor_command(command)
|
||||
return False
|
||||
|
||||
def set_motor_speed(self, motor_id: int, speed: float) -> bool:
|
||||
def wait_sensor_level(self, target_state: str = "有液", timeout: int = 30) -> bool:
|
||||
"""
|
||||
设置电机转速(如果设备支持)
|
||||
|
||||
Args:
|
||||
motor_id: 电机ID(1, 2, 3...)
|
||||
speed: 转速值
|
||||
|
||||
Returns:
|
||||
命令发送是否成功
|
||||
等待传感器达到指定电平
|
||||
:param target_state: "有液" or "无液"
|
||||
"""
|
||||
command = f"M {motor_id} SPEED {speed}"
|
||||
return self._send_motor_command(command)
|
||||
target_bool = True if target_state == "有液" else False
|
||||
|
||||
def _read_data(self) -> List[str]:
|
||||
"""
|
||||
读取串口数据并解析
|
||||
|
||||
Returns:
|
||||
读取到的数据行列表
|
||||
"""
|
||||
print("开始读取串口数据...")
|
||||
if not self.is_connected:
|
||||
return []
|
||||
|
||||
data_lines = []
|
||||
try:
|
||||
while self.serial_port.in_waiting:
|
||||
time.sleep(0.1) # 等待数据稳定
|
||||
try:
|
||||
line = self.serial_port.readline().decode('utf-8', errors='ignore').strip()
|
||||
if line:
|
||||
data_lines.append(line)
|
||||
self._parse_sensor_data(line)
|
||||
except Exception as ex:
|
||||
print(f"解码数据错误: {ex}")
|
||||
except Exception as e:
|
||||
print(f"读取串口数据错误: {e}")
|
||||
|
||||
return data_lines
|
||||
|
||||
def _parse_sensor_data(self, line: str) -> None:
|
||||
"""
|
||||
解析传感器数据
|
||||
|
||||
Args:
|
||||
line: 接收到的数据行
|
||||
"""
|
||||
# 解析电源电压
|
||||
if "电源电压" in line:
|
||||
try:
|
||||
val = float(line.split(":")[1].replace("V", "").strip())
|
||||
self._voltage = val
|
||||
if self.debug:
|
||||
print(f"电源电压更新: {val}V")
|
||||
except Exception:
|
||||
pass
|
||||
self.logger.info(f"Wait sensor: {target_state} ({target_bool}), timeout: {timeout}")
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
if self.sensor_level == target_bool:
|
||||
return True
|
||||
time.sleep(0.1)
|
||||
self.logger.warning("Wait sensor level timeout")
|
||||
return False
|
||||
|
||||
# 解析电导率和ADC原始值(支持两种格式)
|
||||
if "电导率" in line and "ADC原始值" in line:
|
||||
try:
|
||||
# 支持格式如:电导率:2.50ms/cm, ADC原始值:2052
|
||||
ec_match = re.search(r"电导率[::]\s*([\d\.]+)", line)
|
||||
adc_match = re.search(r"ADC原始值[::]\s*(\d+)", line)
|
||||
if ec_match:
|
||||
ec_val = float(ec_match.group(1))
|
||||
self._ec_value = ec_val
|
||||
if self.debug:
|
||||
print(f"电导率更新: {ec_val:.2f} ms/cm")
|
||||
if adc_match:
|
||||
adc_val = int(adc_match.group(1))
|
||||
self._ec_adc_value = adc_val
|
||||
if self.debug:
|
||||
print(f"EC ADC原始值更新: {adc_val}")
|
||||
except Exception:
|
||||
pass
|
||||
# 仅电导率,无ADC原始值
|
||||
elif "电导率" in line:
|
||||
try:
|
||||
val = float(line.split(":")[1].replace("ms/cm", "").strip())
|
||||
self._ec_value = val
|
||||
if self.debug:
|
||||
print(f"电导率更新: {val:.2f} ms/cm")
|
||||
except Exception:
|
||||
pass
|
||||
# 仅ADC原始值(如有分开回传场景)
|
||||
elif "ADC原始值" in line:
|
||||
try:
|
||||
adc_val = int(line.split(":")[1].strip())
|
||||
self._ec_adc_value = adc_val
|
||||
if self.debug:
|
||||
print(f"EC ADC原始值更新: {adc_val}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def spin_when_ec_ge_0():
|
||||
pass
|
||||
|
||||
def wait_time(self, duration: int) -> bool:
|
||||
"""
|
||||
等待指定时间 (秒)
|
||||
:param duration: 秒
|
||||
"""
|
||||
self.logger.info(f"Waiting for {duration} seconds...")
|
||||
time.sleep(duration)
|
||||
return True
|
||||
|
||||
def execute_command_from_outer(self, command_dict: Dict[str, Any]) -> bool:
|
||||
"""支持标准 JSON 指令调用"""
|
||||
return super().execute_command_from_outer(command_dict)
|
||||
|
||||
def main():
|
||||
"""测试函数"""
|
||||
print("=== ChinWe设备测试 ===")
|
||||
|
||||
# 创建设备实例
|
||||
device = ChinweDevice("/dev/tty.usbserial-A5069RR4", debug=True)
|
||||
try:
|
||||
# 测试5: 发送电机命令
|
||||
print("\n5. 发送电机命令测试:")
|
||||
print(" 5.3 使用通用函数控制电机20顺时针转2圈:")
|
||||
device.rotate_motor(2, 20.0, clockwise=True)
|
||||
time.sleep(0.5)
|
||||
finally:
|
||||
time.sleep(10)
|
||||
# 测试7: 断开连接
|
||||
print("\n7. 断开连接:")
|
||||
device.disconnect()
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# Test
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
dev = ChinweDevice(port="192.168.31.201:8899")
|
||||
try:
|
||||
if dev.is_connected:
|
||||
print(f"Status: Level={dev.sensor_level}, RSSI={dev.sensor_rssi}")
|
||||
|
||||
# Test pump 1
|
||||
# dev.pump_valve(1, 1)
|
||||
# dev.pump_move(1, 1000, "aspirate")
|
||||
|
||||
# Test motor 4
|
||||
# dev.motor_run(4, 60, 0, 2)
|
||||
|
||||
for _ in range(5):
|
||||
print(f"Level={dev.sensor_level}, RSSI={dev.sensor_rssi}")
|
||||
time.sleep(1)
|
||||
finally:
|
||||
dev.disconnect()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:32:09.2148671+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a082-c44a-60be-68647a35e6f1",
|
||||
"orderCode": "BSO2025122400024",
|
||||
"orderName": "DP20251224001",
|
||||
"startTime": "2025-12-24T14:51:50.549848",
|
||||
"endTime": "2025-12-24T15:32:09.000765",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"completionTime": "2025-12-24T15:32:09.000765",
|
||||
"usedMaterials": [
|
||||
{
|
||||
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-4da7-cf62-3a40-7e5879255c0c",
|
||||
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a7-2850-42c8-a7a2de8ff4bf",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||
"locationId": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c",
|
||||
"typemode": "0",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 3.51,
|
||||
"realQuantity": 3.5155000000000000000000000000
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 0.33,
|
||||
"realQuantity": 0.3336000000000000000000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:32:09.9999039+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a0a2-f7a9-9360-610021c9479d",
|
||||
"orderCode": "BSO2025122400025",
|
||||
"orderName": "DP20251224002",
|
||||
"startTime": "2025-12-24T14:53:03.44259",
|
||||
"endTime": "2025-12-24T15:32:09.828261",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"completionTime": "2025-12-24T15:32:09.828261",
|
||||
"usedMaterials": [
|
||||
{
|
||||
"materialId": "3a1e614b-4da7-6527-9f1c-b39e3de8ff2b",
|
||||
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a8-8474-cac8-0fd7d349e4b2",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||
"locationId": null,
|
||||
"typemode": "0",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 0.7,
|
||||
"realQuantity": 0
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 1.15,
|
||||
"realQuantity": 1.1627000000000000000000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:34:00.4139986+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a0cd-81ca-9f7f-2f4e93af01cd",
|
||||
"orderCode": "BSO2025122400026",
|
||||
"orderName": "DP20251224003",
|
||||
"startTime": "2025-12-24T14:54:24.443344",
|
||||
"endTime": "2025-12-24T15:34:00.26321",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"completionTime": "2025-12-24T15:34:00.26321",
|
||||
"usedMaterials": [
|
||||
{
|
||||
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||
"locationId": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-4da8-b678-f204-207076f09c83",
|
||||
"locationId": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a8-e3f2-dee0-fa97b600b652",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||
"locationId": null,
|
||||
"typemode": "0",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 2.0,
|
||||
"realQuantity": 2.0075000000000000000000000000
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 1.2,
|
||||
"realQuantity": 1.2126000000000000000000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import pubchempy as pcp
|
||||
|
||||
cas = "21324-40-3" # 示例
|
||||
comps = pcp.get_compounds(cas, namespace="name")
|
||||
if not comps:
|
||||
raise ValueError("No hit")
|
||||
|
||||
c = comps[0]
|
||||
|
||||
print("Canonical SMILES:", c.canonical_smiles)
|
||||
print("Isomeric SMILES:", c.isomeric_smiles)
|
||||
print("MW:", c.molecular_weight)
|
||||
@@ -0,0 +1,113 @@
|
||||
# Bioyond Cell 工作站 - 多订单返回示例
|
||||
|
||||
本文档说明了 `create_orders` 函数如何收集并返回所有订单的完成报文。
|
||||
|
||||
## 问题描述
|
||||
|
||||
之前的实现只会等待并返回第一个订单的完成报文,如果有多个订单(例如从 Excel 解析出 3 个订单),只能得到第一个订单的推送信息。
|
||||
|
||||
## 解决方案
|
||||
|
||||
修改后的 `create_orders` 函数现在会:
|
||||
|
||||
1. **提取所有 orderCode**:从 LIMS 接口返回的 `data` 列表中提取所有订单编号
|
||||
2. **逐个等待完成**:遍历所有 orderCode,调用 `wait_for_order_finish` 等待每个订单完成
|
||||
3. **收集所有报文**:将每个订单的完成报文存入 `all_reports` 列表
|
||||
4. **统一返回**:返回包含所有订单报文的 JSON 格式数据
|
||||
|
||||
## 返回格式
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "all_completed",
|
||||
"total_orders": 3,
|
||||
"reports": [
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:32:09.2148671+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a082-c44a-60be-68647a35e6f1",
|
||||
"orderCode": "BSO2025122400024",
|
||||
"orderName": "DP20251224001",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"usedMaterials": [...]
|
||||
}
|
||||
},
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:32:09.9999039+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a0a2-f7a9-9360-610021c9479d",
|
||||
"orderCode": "BSO2025122400025",
|
||||
"orderName": "DP20251224002",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"usedMaterials": [...]
|
||||
}
|
||||
},
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:34:00.4139986+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a0cd-81ca-9f7f-2f4e93af01cd",
|
||||
"orderCode": "BSO2025122400026",
|
||||
"orderName": "DP20251224003",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"usedMaterials": [...]
|
||||
}
|
||||
}
|
||||
],
|
||||
"original_response": {...}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 调用 create_orders
|
||||
result = workstation.create_orders("20251224.xlsx")
|
||||
|
||||
# 访问返回数据
|
||||
print(f"总订单数: {result['total_orders']}")
|
||||
print(f"状态: {result['status']}")
|
||||
|
||||
# 遍历所有订单的报文
|
||||
for i, report in enumerate(result['reports'], 1):
|
||||
order_data = report.get('data', {})
|
||||
print(f"\n订单 {i}:")
|
||||
print(f" orderCode: {order_data.get('orderCode')}")
|
||||
print(f" orderName: {order_data.get('orderName')}")
|
||||
print(f" status: {order_data.get('status')}")
|
||||
print(f" 使用物料数: {len(order_data.get('usedMaterials', []))}")
|
||||
```
|
||||
|
||||
## 控制台输出示例
|
||||
|
||||
```
|
||||
[create_orders] 即将提交订单数量: 3
|
||||
[create_orders] 接口返回: {...}
|
||||
[create_orders] 等待 3 个订单完成: ['BSO2025122400024', 'BSO2025122400025', 'BSO2025122400026']
|
||||
[create_orders] 正在等待第 1/3 个订单: BSO2025122400024
|
||||
[create_orders] ✓ 订单 BSO2025122400024 完成
|
||||
[create_orders] 正在等待第 2/3 个订单: BSO2025122400025
|
||||
[create_orders] ✓ 订单 BSO2025122400025 完成
|
||||
[create_orders] 正在等待第 3/3 个订单: BSO2025122400026
|
||||
[create_orders] ✓ 订单 BSO2025122400026 完成
|
||||
[create_orders] 所有订单已完成,共收集 3 个报文
|
||||
实验记录本========================create_orders========================
|
||||
返回报文数量: 3
|
||||
报文 1: orderCode=BSO2025122400024, status=30
|
||||
报文 2: orderCode=BSO2025122400025, status=30
|
||||
报文 3: orderCode=BSO2025122400026, status=30
|
||||
========================
|
||||
```
|
||||
|
||||
## 关键改进
|
||||
|
||||
1. ✅ **等待所有订单**:不再只等待第一个订单,而是遍历所有 orderCode
|
||||
2. ✅ **收集完整报文**:每个订单的完整推送报文都被保存在 `reports` 数组中
|
||||
3. ✅ **详细日志**:清晰显示正在等待哪个订单,以及完成情况
|
||||
4. ✅ **错误处理**:即使某个订单失败,也会记录其状态信息
|
||||
5. ✅ **统一格式**:返回的 JSON 格式便于后续处理和分析
|
||||
@@ -8,8 +8,10 @@ import os
|
||||
# BioyondCellWorkstation 默认配置(包含所有必需参数)
|
||||
API_CONFIG = {
|
||||
# API 连接配置
|
||||
# "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机
|
||||
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),# 仿真机
|
||||
# 实机
|
||||
#"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.118:44389"),
|
||||
# 仿真机
|
||||
"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")),
|
||||
|
||||
@@ -17,7 +19,7 @@ API_CONFIG = {
|
||||
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
||||
|
||||
# HTTP 服务配置
|
||||
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.6"), # HTTP服务监听地址,监听计算机飞连ip地址
|
||||
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.206"), # HTTP服务监听地址,监听计算机飞连ip地址
|
||||
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
||||
"debug_mode": False,# 调试模式
|
||||
}
|
||||
@@ -131,6 +133,46 @@ WAREHOUSE_MAPPING = {
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
"手动传递窗右": {
|
||||
"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",
|
||||
}
|
||||
},
|
||||
"手动传递窗左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"4号手套箱内部堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# Modbus CSV 地址映射说明
|
||||
|
||||
本文档说明 `coin_cell_assembly_a.csv` 文件如何将命名节点映射到实际的 Modbus 地址,以及如何在代码中使用它们。
|
||||
|
||||
## 1. CSV 文件结构
|
||||
|
||||
地址表文件位于同级目录下:`coin_cell_assembly_a.csv`
|
||||
|
||||
每一行定义了一个 Modbus 节点,包含以下关键列:
|
||||
|
||||
| 列名 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **Name** | **节点名称** (代码中引用的 Key) | `COIL_ALUMINUM_FOIL` |
|
||||
| **DataType** | 数据类型 (BOOL, INT16, FLOAT32, STRING) | `BOOL` |
|
||||
| **Comment** | 注释说明 | `使用铝箔垫` |
|
||||
| **Attribute** | 属性 (通常留空或用于额外标记) | |
|
||||
| **DeviceType** | Modbus 寄存器类型 (`coil`, `hold_register`) | `coil` |
|
||||
| **Address** | **Modbus 地址** (十进制) | `8340` |
|
||||
|
||||
### 示例行 (铝箔垫片)
|
||||
|
||||
```csv
|
||||
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,8340,
|
||||
```
|
||||
|
||||
- **名称**: `COIL_ALUMINUM_FOIL`
|
||||
- **类型**: `coil` (线圈,读写单个位)
|
||||
- **地址**: `8340`
|
||||
|
||||
---
|
||||
|
||||
## 2. 加载与注册流程
|
||||
|
||||
在 `coin_cell_assembly.py` 的初始化代码中:
|
||||
|
||||
1. **加载 CSV**: `BaseClient.load_csv()` 读取 CSV 并解析每行定义。
|
||||
2. **注册节点**: `modbus_client.register_node_list()` 将解析后的节点注册到 Modbus 客户端实例中。
|
||||
|
||||
```python
|
||||
# 代码位置: coin_cell_assembly.py (L174-175)
|
||||
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv'))
|
||||
self.client = modbus_client.register_node_list(self.nodes)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码中的使用方式
|
||||
|
||||
注册后,通过 `self.client.use_node('节点名称')` 即可获取该节点对象并进行读写操作,无需关心具体地址。
|
||||
|
||||
### 控制铝箔垫片 (COIL_ALUMINUM_FOIL)
|
||||
|
||||
```python
|
||||
# 代码位置: qiming_coin_cell_code 函数 (L1048)
|
||||
self.client.use_node('COIL_ALUMINUM_FOIL').write(not lvbodian)
|
||||
```
|
||||
|
||||
- **写入 True**: 对应 Modbus 功能码 05 (Write Single Coil),向地址 `8340` 写入 `1` (ON)。
|
||||
- **写入 False**: 向地址 `8340` 写入 `0` (OFF)。
|
||||
|
||||
> **注意**: 代码中使用了 `not lvbodian`,这意味着逻辑是反转的。如果 `lvbodian` 参数为 `True` (默认),写入的是 `False` (不使用铝箔垫)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 地址转换注意事项 (Modbus vs PLC)
|
||||
|
||||
CSV 中的 `Address` 列(如 `8340`)是 **Modbus 协议地址**。
|
||||
|
||||
如果使用 InoProShop (汇川 PLC 编程软件),看到的可能是 **PLC 内部地址** (如 `%QX...` 或 `%MW...`)。这两者之间通常需要转换。
|
||||
|
||||
### 常见的转换规则 (示例)
|
||||
|
||||
- **Coil (线圈) %QX**:
|
||||
- `Modbus地址 = 字节地址 * 8 + 位偏移`
|
||||
- *例子*: `%QX834.0` -> `834 * 8 + 0` = `6672`
|
||||
- *注意*: 如果 CSV 中配置的是 `8340`,这可能是一个自定义映射,或者是基于不同规则(如直接对应 Word 地址的某种映射,或者可能就是地址写错了/使用了非标准映射)。
|
||||
|
||||
- **Register (寄存器) %MW**:
|
||||
- 通常直接对应,或者有偏移量 (如 Modbus 40001 = PLC MW0)。
|
||||
|
||||
### 验证方法
|
||||
由于 `test_unilab_interact.py` 中发现 `8450` (CSV风格) 不工作,而 `6760` (%QX845.0 计算值) 工作正常,**建议对 CSV 中的其他地址也进行核实**,特别是像 `8340` 这样以 0 结尾看起来像是 "字节地址+0" 的数值,可能实际上应该是 `%QX834.0` 对应的 `6672`。
|
||||
|
||||
如果发现设备控制无反应,请尝试按照标准的 Modbus 计算方式转换 PLC 地址。
|
||||
@@ -634,6 +634,12 @@ class CoincellDeck(Deck):
|
||||
self.assign_child_resource(waste_tip_box, Coordinate(x=778.0, y=622.0, z=0))
|
||||
|
||||
|
||||
def YH_Deck(name=""):
|
||||
cd = CoincellDeck(name=name)
|
||||
cd.setup()
|
||||
return cd
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
deck = create_coin_cell_deck()
|
||||
print(deck)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,64 +0,0 @@
|
||||
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
|
||||
COIL_SYS_START_CMD,BOOL,,,,coil,9010,
|
||||
COIL_SYS_STOP_CMD,BOOL,,,,coil,9020,
|
||||
COIL_SYS_RESET_CMD,BOOL,,,,coil,9030,
|
||||
COIL_SYS_HAND_CMD,BOOL,,,,coil,9040,
|
||||
COIL_SYS_AUTO_CMD,BOOL,,,,coil,9050,
|
||||
COIL_SYS_INIT_CMD,BOOL,,,,coil,9060,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,9700,
|
||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,9710,unilab_rec_msg_succ_cmd
|
||||
COIL_SYS_START_STATUS,BOOL,,,,coil,9210,
|
||||
COIL_SYS_STOP_STATUS,BOOL,,,,coil,9220,
|
||||
COIL_SYS_RESET_STATUS,BOOL,,,,coil,9230,
|
||||
COIL_SYS_HAND_STATUS,BOOL,,,,coil,9240,
|
||||
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,9250,
|
||||
COIL_SYS_INIT_STATUS,BOOL,,,,coil,9260,
|
||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,9500,
|
||||
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,9510,request_send_msg_status
|
||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,17000,
|
||||
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,17002,unilab_send_msg_electrolyte_num
|
||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,17004,unilab_send_msg_electrolyte_vol
|
||||
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,17006,unilab_send_msg_assembly_type
|
||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,17008,unilab_send_msg_assembly_pressure
|
||||
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,16000,data_assembly_coin_cell_num
|
||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,16002,data_open_circuit_voltage
|
||||
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,16004,
|
||||
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,16006,
|
||||
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,16008,
|
||||
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,16010,data_pole_weight
|
||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,16012,data_assembly_time
|
||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,16014,data_assembly_pressure
|
||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,16016,data_electrolyte_volume
|
||||
REG_DATA_COIN_NUM,INT16,,,,hold_register,16018,data_coin_num
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,16020,data_electrolyte_code()
|
||||
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,16030,data_coin_cell_code()
|
||||
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,18004,data_stack_vision_code()
|
||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,16050,data_glove_box_pressure
|
||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,16052,data_glove_box_water_content
|
||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,16054,data_glove_box_o2_content
|
||||
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9720,
|
||||
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9520,
|
||||
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,17496,
|
||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,16000,
|
||||
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,9730,
|
||||
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,9530,
|
||||
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,16018,ASSEMBLY_TYPE7or8
|
||||
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,9340,
|
||||
REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,17440,
|
||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,17450,
|
||||
REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,17480,
|
||||
REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,17443,
|
||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,17453,
|
||||
REG_MSG_PRESS_MODE,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,9360,电池压制模式
|
||||
,,,,,,,
|
||||
,BOOL,,视觉对位(false:使用,true:忽略),,coil,9300,视觉对位
|
||||
,BOOL,,复检(false:使用,true:忽略),,coil,9310,视觉复检
|
||||
,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,9320,手套箱左仓
|
||||
,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,9420,手套箱右仓
|
||||
,BOOL,,真空检知(false:使用,true:忽略),,coil,9350,真空检知
|
||||
,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,9370,滴液模式
|
||||
,BOOL,,正极片称重(false:使用,true:忽略),,coil,9380,正极片称重
|
||||
,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,9390,正负极反装
|
||||
,BOOL,,压制清洁(false:使用,true:忽略),,coil,9400,压制清洁
|
||||
,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,9410,负极片摆盘方式
|
||||
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁(false:使用,true:忽略),,coil,9460,
|
||||
|
Binary file not shown.
@@ -1,64 +0,0 @@
|
||||
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,,使用铝箔垫,,coil,8340,
|
||||
REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,440,
|
||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,450,
|
||||
REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,480,
|
||||
REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,443,
|
||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,453,
|
||||
REG_MSG_PRESS_MODE,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,8360,电池压制模式
|
||||
,,,,,,,
|
||||
,BOOL,,视觉对位(false:使用,true:忽略),,coil,8300,视觉对位
|
||||
,BOOL,,复检(false:使用,true:忽略),,coil,8310,视觉复检
|
||||
,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,8320,手套箱左仓
|
||||
,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,8420,手套箱右仓
|
||||
,BOOL,,真空检知(false:使用,true:忽略),,coil,8350,真空检知
|
||||
,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,8370,滴液模式
|
||||
,BOOL,,正极片称重(false:使用,true:忽略),,coil,8380,正极片称重
|
||||
,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,8390,正负极反装
|
||||
,BOOL,,压制清洁(false:使用,true:忽略),,coil,8400,压制清洁
|
||||
,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,8410,负极片摆盘方式
|
||||
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁(false:使用,true:忽略),,coil,8460,
|
||||
|
@@ -0,0 +1,130 @@
|
||||
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
|
||||
REG_UNILAB_INTERACT,BOOL,,,,coil,8450,
|
||||
,,,,,coil,8320,
|
||||
COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340,
|
||||
REG_MSG_NE_PLATE_MATRIX,INT16,,,,hold_register,440,
|
||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,,,hold_register,450,
|
||||
REG_MSG_TIP_BOX_MATRIX,INT16,,,,hold_register,480,
|
||||
REG_MSG_NE_PLATE_NUM,INT16,,,,hold_register,443,
|
||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,,,hold_register,453,
|
||||
REG_MSG_PRESS_MODE,BOOL,,,,coil,8360,
|
||||
,BOOL,,,,coil,8300,
|
||||
,BOOL,,,,coil,8310,
|
||||
COIL_GB_L_IGNORE_CMD,BOOL,,,,coil,8320,
|
||||
COIL_GB_R_IGNORE_CMD,BOOL,,,,coil,8420,
|
||||
,BOOL,,,,coil,8350,
|
||||
COIL_ELECTROLYTE_DUAL_DROP_MODE,BOOL,,,,coil,8370,
|
||||
,BOOL,,,,coil,8380,
|
||||
,BOOL,,,,coil,8390,
|
||||
,BOOL,,,,coil,8400,
|
||||
,BOOL,,,,coil,8410,
|
||||
REG_MSG_DUAL_DROP_FIRST_VOLUME,INT16,,,,hold_register,4001,
|
||||
COIL_DUAL_DROP_SUCTION_TIMING,BOOL,,,,coil,8430,
|
||||
COIL_DUAL_DROP_START_TIMING,BOOL,,,,coil,8470,
|
||||
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,,,coil,8460,
|
||||
COIL_ALARM_100_SYSTEM_ERROR,BOOL,,,,coil,1000,异常100-系统异常
|
||||
COIL_ALARM_101_EMERGENCY_STOP,BOOL,,,,coil,1010,异常101-急停
|
||||
COIL_ALARM_111_GLOVEBOX_EMERGENCY_STOP,BOOL,,,,coil,1110,异常111-手套箱急停
|
||||
COIL_ALARM_112_GLOVEBOX_GRATING_BLOCKED,BOOL,,,,coil,1120,异常112-手套箱内光栅遮挡
|
||||
COIL_ALARM_160_PIPETTE_TIP_SHORTAGE,BOOL,,,,coil,1600,异常160-移液枪头缺料
|
||||
COIL_ALARM_161_POSITIVE_SHELL_SHORTAGE,BOOL,,,,coil,1610,异常161-正极壳缺料
|
||||
COIL_ALARM_162_ALUMINUM_FOIL_SHORTAGE,BOOL,,,,coil,1620,异常162-铝箔垫缺料
|
||||
COIL_ALARM_163_POSITIVE_PLATE_SHORTAGE,BOOL,,,,coil,1630,异常163-正极片缺料
|
||||
COIL_ALARM_164_SEPARATOR_SHORTAGE,BOOL,,,,coil,1640,异常164-隔膜缺料
|
||||
COIL_ALARM_165_NEGATIVE_PLATE_SHORTAGE,BOOL,,,,coil,1650,异常165-负极片缺料
|
||||
COIL_ALARM_166_FLAT_WASHER_SHORTAGE,BOOL,,,,coil,1660,异常166-平垫缺料
|
||||
COIL_ALARM_167_SPRING_WASHER_SHORTAGE,BOOL,,,,coil,1670,异常167-弹垫缺料
|
||||
COIL_ALARM_168_NEGATIVE_SHELL_SHORTAGE,BOOL,,,,coil,1680,异常168-负极壳缺料
|
||||
COIL_ALARM_169_FINISHED_BATTERY_FULL,BOOL,,,,coil,1690,异常169-成品电池满料
|
||||
COIL_ALARM_201_SERVO_AXIS_01_ERROR,BOOL,,,,coil,2010,异常201-伺服轴01异常
|
||||
COIL_ALARM_202_SERVO_AXIS_02_ERROR,BOOL,,,,coil,2020,异常202-伺服轴02异常
|
||||
COIL_ALARM_203_SERVO_AXIS_03_ERROR,BOOL,,,,coil,2030,异常203-伺服轴03异常
|
||||
COIL_ALARM_204_SERVO_AXIS_04_ERROR,BOOL,,,,coil,2040,异常204-伺服轴04异常
|
||||
COIL_ALARM_205_SERVO_AXIS_05_ERROR,BOOL,,,,coil,2050,异常205-伺服轴05异常
|
||||
COIL_ALARM_206_SERVO_AXIS_06_ERROR,BOOL,,,,coil,2060,异常206-伺服轴06异常
|
||||
COIL_ALARM_207_SERVO_AXIS_07_ERROR,BOOL,,,,coil,2070,异常207-伺服轴07异常
|
||||
COIL_ALARM_208_SERVO_AXIS_08_ERROR,BOOL,,,,coil,2080,异常208-伺服轴08异常
|
||||
COIL_ALARM_209_SERVO_AXIS_09_ERROR,BOOL,,,,coil,2090,异常209-伺服轴09异常
|
||||
COIL_ALARM_210_SERVO_AXIS_10_ERROR,BOOL,,,,coil,2100,异常210-伺服轴10异常
|
||||
COIL_ALARM_211_SERVO_AXIS_11_ERROR,BOOL,,,,coil,2110,异常211-伺服轴11异常
|
||||
COIL_ALARM_212_SERVO_AXIS_12_ERROR,BOOL,,,,coil,2120,异常212-伺服轴12异常
|
||||
COIL_ALARM_213_SERVO_AXIS_13_ERROR,BOOL,,,,coil,2130,异常213-伺服轴13异常
|
||||
COIL_ALARM_214_SERVO_AXIS_14_ERROR,BOOL,,,,coil,2140,异常214-伺服轴14异常
|
||||
COIL_ALARM_250_OTHER_COMPONENT_ERROR,BOOL,,,,coil,2500,异常250-其他元件异常
|
||||
COIL_ALARM_251_PIPETTE_COMM_ERROR,BOOL,,,,coil,2510,异常251-移液枪通讯异常
|
||||
COIL_ALARM_252_PIPETTE_ALARM,BOOL,,,,coil,2520,异常252-移液枪报警
|
||||
COIL_ALARM_256_ELECTRIC_GRIPPER_ERROR,BOOL,,,,coil,2560,异常256-电爪异常
|
||||
COIL_ALARM_262_RB_UNKNOWN_POSITION_ERROR,BOOL,,,,coil,2620,异常262-RB报警:未知点位错误
|
||||
COIL_ALARM_263_RB_XYZ_PARAM_LIMIT_ERROR,BOOL,,,,coil,2630,异常263-RB报警:X、Y、Z参数超限制
|
||||
COIL_ALARM_264_RB_VISION_PARAM_ERROR,BOOL,,,,coil,2640,异常264-RB报警:视觉参数误差过大
|
||||
COIL_ALARM_265_RB_NOZZLE_1_PICK_FAIL,BOOL,,,,coil,2650,异常265-RB报警:1#吸嘴取料失败
|
||||
COIL_ALARM_266_RB_NOZZLE_2_PICK_FAIL,BOOL,,,,coil,2660,异常266-RB报警:2#吸嘴取料失败
|
||||
COIL_ALARM_267_RB_NOZZLE_3_PICK_FAIL,BOOL,,,,coil,2670,异常267-RB报警:3#吸嘴取料失败
|
||||
COIL_ALARM_268_RB_NOZZLE_4_PICK_FAIL,BOOL,,,,coil,2680,异常268-RB报警:4#吸嘴取料失败
|
||||
COIL_ALARM_269_RB_TRAY_PICK_FAIL,BOOL,,,,coil,2690,异常269-RB报警:取物料盘失败
|
||||
COIL_ALARM_280_RB_COLLISION_ERROR,BOOL,,,,coil,2800,异常280-RB碰撞异常
|
||||
COIL_ALARM_290_VISION_SYSTEM_COMM_ERROR,BOOL,,,,coil,2900,异常290-视觉系统通讯异常
|
||||
COIL_ALARM_291_VISION_ALIGNMENT_NG,BOOL,,,,coil,2910,异常291-视觉对位NG异常
|
||||
COIL_ALARM_292_BARCODE_SCANNER_COMM_ERROR,BOOL,,,,coil,2920,异常292-扫码枪通讯异常
|
||||
COIL_ALARM_310_OCV_TRANSFER_NOZZLE_SUCTION_ERROR,BOOL,,,,coil,3100,异常310-开电移载吸嘴吸真空异常
|
||||
COIL_ALARM_311_OCV_TRANSFER_NOZZLE_BREAK_ERROR,BOOL,,,,coil,3110,异常311-开电移载吸嘴破真空异常
|
||||
COIL_ALARM_312_WEIGHT_TRANSFER_NOZZLE_SUCTION_ERROR,BOOL,,,,coil,3120,异常312-称重移载吸嘴吸真空异常
|
||||
COIL_ALARM_313_WEIGHT_TRANSFER_NOZZLE_BREAK_ERROR,BOOL,,,,coil,3130,异常313-称重移载吸嘴破真空异常
|
||||
COIL_ALARM_340_OCV_NOZZLE_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3400,异常340-开路电压吸嘴移载气缸异常
|
||||
COIL_ALARM_342_OCV_NOZZLE_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3420,异常342-开路电压吸嘴升降气缸异常
|
||||
COIL_ALARM_344_OCV_CRIMPING_CYLINDER_ERROR,BOOL,,,,coil,3440,异常344-开路电压旋压气缸异常
|
||||
COIL_ALARM_350_WEIGHT_NOZZLE_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3500,异常350-称重吸嘴移载气缸异常
|
||||
COIL_ALARM_352_WEIGHT_NOZZLE_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3520,异常352-称重吸嘴升降气缸异常
|
||||
COIL_ALARM_354_CLEANING_CLOTH_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3540,异常354-清洗无尘布移载气缸异常
|
||||
COIL_ALARM_356_CLEANING_CLOTH_PRESS_CYLINDER_ERROR,BOOL,,,,coil,3560,异常356-清洗无尘布压紧气缸异常
|
||||
COIL_ALARM_360_ELECTROLYTE_BOTTLE_POSITION_CYLINDER_ERROR,BOOL,,,,coil,3600,异常360-电解液瓶定位气缸异常
|
||||
COIL_ALARM_362_PIPETTE_TIP_BOX_POSITION_CYLINDER_ERROR,BOOL,,,,coil,3620,异常362-移液枪头盒定位气缸异常
|
||||
COIL_ALARM_364_REAGENT_BOTTLE_GRIPPER_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3640,异常364-试剂瓶夹爪升降气缸异常
|
||||
COIL_ALARM_366_REAGENT_BOTTLE_GRIPPER_CYLINDER_ERROR,BOOL,,,,coil,3660,异常366-试剂瓶夹爪气缸异常
|
||||
COIL_ALARM_370_PRESS_MODULE_BLOW_CYLINDER_ERROR,BOOL,,,,coil,3700,异常370-压制模块吹气气缸异常
|
||||
COIL_ALARM_151_ELECTROLYTE_BOTTLE_POSITION_ERROR,BOOL,,,,coil,1510,异常151-电解液瓶定位在籍异常
|
||||
COIL_ALARM_152_ELECTROLYTE_BOTTLE_CAP_ERROR,BOOL,,,,coil,1520,异常152-电解液瓶盖在籍异常
|
||||
|
@@ -0,0 +1,6 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||
20260110_153356,0.0,23.889999389648438,17.0,3609,40,7,deaoR,
|
||||
20260110_153640,3.430999994277954,23.719999313354492,162.0,3560,40,7,deaoR,
|
||||
20260110_162905,0.0,23.920000076293945,772.0,3625,30,7,LG600001,YS103130
|
||||
20260110_163526,3.430999994277954,24.010000228881836,234.0,3690,30,7,LG600001,YS102964
|
||||
20260110_164530,0.0,23.589998245239258,219.0,3690,30,7,LG600001,YS102857
|
||||
|
107
unilabos/devices/workstation/coin_cell_assembly/电池资源冲突修复说明.md
Normal file
107
unilabos/devices/workstation/coin_cell_assembly/电池资源冲突修复说明.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 电池组装资源冲突问题修复说明
|
||||
|
||||
## 问题描述
|
||||
|
||||
在运行 `func_allpack_cmd` 函数时,遇到以下错误:
|
||||
|
||||
```
|
||||
ValueError: Resource 'battery_0' already assigned to deck
|
||||
```
|
||||
|
||||
**错误位置**:`coin_cell_assembly.py` 第 849 行
|
||||
```python
|
||||
liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None)
|
||||
```
|
||||
|
||||
## 原因分析
|
||||
|
||||
1. **资源名称冲突**:
|
||||
- 每次创建电池资源使用固定格式 `battery_{coin_num_N}`
|
||||
- 如果程序重启或断点恢复,`coin_num_N` 可能重置为 0
|
||||
- Deck 上可能已存在 `battery_0` 等同名资源
|
||||
|
||||
2. **缺少冲突处理**:
|
||||
- 在分配资源前没有检查目标位置是否已有资源
|
||||
- 没有清理机制来移除旧资源
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 使用时间戳确保资源名称唯一
|
||||
|
||||
```python
|
||||
# 之前
|
||||
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", ...)
|
||||
|
||||
# 修复后
|
||||
timestamp_suffix = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
||||
battery_name = f"battery_{self.coin_num_N}_{timestamp_suffix}"
|
||||
battery = ElectrodeSheet(name=battery_name, ...)
|
||||
```
|
||||
|
||||
### 2. 添加资源冲突检查和清理
|
||||
|
||||
```python
|
||||
# 检查目标位置是否已有资源
|
||||
target_slot = liaopan3.children[self.coin_num_N]
|
||||
if target_slot.children:
|
||||
logger.warning(f"位置 {self.coin_num_N} 已有资源,将先卸载旧资源")
|
||||
try:
|
||||
# 卸载所有现有子资源
|
||||
for child in list(target_slot.children):
|
||||
target_slot.unassign_child_resource(child)
|
||||
logger.info(f"已卸载旧资源: {child.name}")
|
||||
except Exception as e:
|
||||
logger.error(f"卸载旧资源时出错: {e}")
|
||||
```
|
||||
|
||||
### 3. 增强错误处理
|
||||
|
||||
```python
|
||||
# 分配新资源到目标位置
|
||||
try:
|
||||
target_slot.assign_child_resource(battery, location=None)
|
||||
logger.info(f"成功分配电池 {battery_name} 到位置 {self.coin_num_N}")
|
||||
except Exception as e:
|
||||
logger.error(f"分配电池资源失败: {e}")
|
||||
raise
|
||||
```
|
||||
|
||||
## 修复效果
|
||||
|
||||
✅ **不再出现重复资源名称错误**
|
||||
- 每个电池资源都有唯一的时间戳后缀
|
||||
- 即使 `coin_num_N` 相同,资源名称也不会冲突
|
||||
|
||||
✅ **自动清理旧资源**
|
||||
- 在分配新资源前检查目标位置
|
||||
- 自动卸载已存在的旧资源
|
||||
|
||||
✅ **增强日志记录**
|
||||
- 记录资源卸载操作
|
||||
- 记录资源分配成功/失败
|
||||
- 便于调试和问题追踪
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **正常运行测试**:
|
||||
```python
|
||||
workstation.func_allpack_cmd(
|
||||
elec_num=1,
|
||||
elec_use_num=1,
|
||||
elec_vol=20,
|
||||
file_path="..."
|
||||
)
|
||||
```
|
||||
|
||||
2. **断点恢复测试**:
|
||||
- 运行一次后中断
|
||||
- 再次运行相同参数
|
||||
- 验证不会出现资源冲突错误
|
||||
|
||||
3. **连续运行测试**:
|
||||
- 连续多次运行
|
||||
- 验证每次都能正常分配资源
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `coin_cell_assembly.py` - 第 838-875 行(`func_pack_get_msg_cmd` 函数)
|
||||
@@ -1,589 +0,0 @@
|
||||
workstation.bioyond_dispensing_station:
|
||||
category:
|
||||
- workstation
|
||||
- bioyond
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-batch_create_90_10_vial_feeding_tasks:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
delay_time: null
|
||||
hold_m_name: null
|
||||
liquid_material_name: NMP
|
||||
speed: null
|
||||
temperature: null
|
||||
titration: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
hold_m_name:
|
||||
type: string
|
||||
liquid_material_name:
|
||||
default: NMP
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
titration:
|
||||
type: string
|
||||
required:
|
||||
- titration
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: batch_create_90_10_vial_feeding_tasks参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-batch_create_diamine_solution_tasks:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
delay_time: null
|
||||
liquid_material_name: NMP
|
||||
solutions: null
|
||||
speed: null
|
||||
temperature: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
liquid_material_name:
|
||||
default: NMP
|
||||
type: string
|
||||
solutions:
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
required:
|
||||
- solutions
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: batch_create_diamine_solution_tasks参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-brief_step_parameters:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
data: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: brief_step_parameters参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-compute_experiment_design:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
m_tot: '70'
|
||||
ratio: null
|
||||
titration_percent: '0.03'
|
||||
wt_percent: '0.25'
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
m_tot:
|
||||
default: '70'
|
||||
type: string
|
||||
ratio:
|
||||
type: object
|
||||
titration_percent:
|
||||
default: '0.03'
|
||||
type: string
|
||||
wt_percent:
|
||||
default: '0.25'
|
||||
type: string
|
||||
required:
|
||||
- ratio
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
feeding_order:
|
||||
items: {}
|
||||
title: Feeding Order
|
||||
type: array
|
||||
return_info:
|
||||
title: Return Info
|
||||
type: string
|
||||
solutions:
|
||||
items: {}
|
||||
title: Solutions
|
||||
type: array
|
||||
solvents:
|
||||
additionalProperties: true
|
||||
title: Solvents
|
||||
type: object
|
||||
titration:
|
||||
additionalProperties: true
|
||||
title: Titration
|
||||
type: object
|
||||
required:
|
||||
- solutions
|
||||
- titration
|
||||
- solvents
|
||||
- feeding_order
|
||||
- return_info
|
||||
title: ComputeExperimentDesignReturn
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: compute_experiment_design参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_order_finish_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
report_request: null
|
||||
used_materials: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
report_request:
|
||||
type: string
|
||||
used_materials:
|
||||
type: string
|
||||
required:
|
||||
- report_request
|
||||
- used_materials
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: process_order_finish_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-project_order_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
order_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
order_id:
|
||||
type: string
|
||||
required:
|
||||
- order_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: project_order_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-query_resource_by_name:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_name:
|
||||
type: string
|
||||
required:
|
||||
- material_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: query_resource_by_name参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_materials_to_reaction_station:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
target_device_id: null
|
||||
transfer_groups: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
target_device_id:
|
||||
type: string
|
||||
transfer_groups:
|
||||
type: array
|
||||
required:
|
||||
- target_device_id
|
||||
- transfer_groups
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_materials_to_reaction_station参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_multiple_orders_and_get_reports:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
batch_create_result: null
|
||||
check_interval: 10
|
||||
timeout: 7200
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
batch_create_result:
|
||||
type: string
|
||||
check_interval:
|
||||
default: 10
|
||||
type: integer
|
||||
timeout:
|
||||
default: 7200
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_multiple_orders_and_get_reports参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-workflow_sample_locations:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
workflow_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_id:
|
||||
type: string
|
||||
required:
|
||||
- workflow_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: workflow_sample_locations参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
create_90_10_vial_feeding_task:
|
||||
feedback: {}
|
||||
goal:
|
||||
delay_time: delay_time
|
||||
hold_m_name: hold_m_name
|
||||
order_name: order_name
|
||||
percent_10_1_assign_material_name: percent_10_1_assign_material_name
|
||||
percent_10_1_liquid_material_name: percent_10_1_liquid_material_name
|
||||
percent_10_1_target_weigh: percent_10_1_target_weigh
|
||||
percent_10_1_volume: percent_10_1_volume
|
||||
percent_10_2_assign_material_name: percent_10_2_assign_material_name
|
||||
percent_10_2_liquid_material_name: percent_10_2_liquid_material_name
|
||||
percent_10_2_target_weigh: percent_10_2_target_weigh
|
||||
percent_10_2_volume: percent_10_2_volume
|
||||
percent_10_3_assign_material_name: percent_10_3_assign_material_name
|
||||
percent_10_3_liquid_material_name: percent_10_3_liquid_material_name
|
||||
percent_10_3_target_weigh: percent_10_3_target_weigh
|
||||
percent_10_3_volume: percent_10_3_volume
|
||||
percent_90_1_assign_material_name: percent_90_1_assign_material_name
|
||||
percent_90_1_target_weigh: percent_90_1_target_weigh
|
||||
percent_90_2_assign_material_name: percent_90_2_assign_material_name
|
||||
percent_90_2_target_weigh: percent_90_2_target_weigh
|
||||
percent_90_3_assign_material_name: percent_90_3_assign_material_name
|
||||
percent_90_3_target_weigh: percent_90_3_target_weigh
|
||||
speed: speed
|
||||
temperature: temperature
|
||||
goal_default:
|
||||
delay_time: ''
|
||||
hold_m_name: ''
|
||||
order_name: ''
|
||||
percent_10_1_assign_material_name: ''
|
||||
percent_10_1_liquid_material_name: ''
|
||||
percent_10_1_target_weigh: ''
|
||||
percent_10_1_volume: ''
|
||||
percent_10_2_assign_material_name: ''
|
||||
percent_10_2_liquid_material_name: ''
|
||||
percent_10_2_target_weigh: ''
|
||||
percent_10_2_volume: ''
|
||||
percent_10_3_assign_material_name: ''
|
||||
percent_10_3_liquid_material_name: ''
|
||||
percent_10_3_target_weigh: ''
|
||||
percent_10_3_volume: ''
|
||||
percent_90_1_assign_material_name: ''
|
||||
percent_90_1_target_weigh: ''
|
||||
percent_90_2_assign_material_name: ''
|
||||
percent_90_2_target_weigh: ''
|
||||
percent_90_3_assign_material_name: ''
|
||||
percent_90_3_target_weigh: ''
|
||||
speed: ''
|
||||
temperature: ''
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: DispenStationVialFeed_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
hold_m_name:
|
||||
type: string
|
||||
order_name:
|
||||
type: string
|
||||
percent_10_1_assign_material_name:
|
||||
type: string
|
||||
percent_10_1_liquid_material_name:
|
||||
type: string
|
||||
percent_10_1_target_weigh:
|
||||
type: string
|
||||
percent_10_1_volume:
|
||||
type: string
|
||||
percent_10_2_assign_material_name:
|
||||
type: string
|
||||
percent_10_2_liquid_material_name:
|
||||
type: string
|
||||
percent_10_2_target_weigh:
|
||||
type: string
|
||||
percent_10_2_volume:
|
||||
type: string
|
||||
percent_10_3_assign_material_name:
|
||||
type: string
|
||||
percent_10_3_liquid_material_name:
|
||||
type: string
|
||||
percent_10_3_target_weigh:
|
||||
type: string
|
||||
percent_10_3_volume:
|
||||
type: string
|
||||
percent_90_1_assign_material_name:
|
||||
type: string
|
||||
percent_90_1_target_weigh:
|
||||
type: string
|
||||
percent_90_2_assign_material_name:
|
||||
type: string
|
||||
percent_90_2_target_weigh:
|
||||
type: string
|
||||
percent_90_3_assign_material_name:
|
||||
type: string
|
||||
percent_90_3_target_weigh:
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
required:
|
||||
- order_name
|
||||
- percent_90_1_assign_material_name
|
||||
- percent_90_1_target_weigh
|
||||
- percent_90_2_assign_material_name
|
||||
- percent_90_2_target_weigh
|
||||
- percent_90_3_assign_material_name
|
||||
- percent_90_3_target_weigh
|
||||
- percent_10_1_assign_material_name
|
||||
- percent_10_1_target_weigh
|
||||
- percent_10_1_volume
|
||||
- percent_10_1_liquid_material_name
|
||||
- percent_10_2_assign_material_name
|
||||
- percent_10_2_target_weigh
|
||||
- percent_10_2_volume
|
||||
- percent_10_2_liquid_material_name
|
||||
- percent_10_3_assign_material_name
|
||||
- percent_10_3_target_weigh
|
||||
- percent_10_3_volume
|
||||
- percent_10_3_liquid_material_name
|
||||
- speed
|
||||
- temperature
|
||||
- delay_time
|
||||
- hold_m_name
|
||||
title: DispenStationVialFeed_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: DispenStationVialFeed_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: DispenStationVialFeed
|
||||
type: object
|
||||
type: DispenStationVialFeed
|
||||
create_diamine_solution_task:
|
||||
feedback: {}
|
||||
goal:
|
||||
delay_time: delay_time
|
||||
hold_m_name: hold_m_name
|
||||
liquid_material_name: liquid_material_name
|
||||
material_name: material_name
|
||||
order_name: order_name
|
||||
speed: speed
|
||||
target_weigh: target_weigh
|
||||
temperature: temperature
|
||||
volume: volume
|
||||
goal_default:
|
||||
delay_time: ''
|
||||
hold_m_name: ''
|
||||
liquid_material_name: ''
|
||||
material_name: ''
|
||||
order_name: ''
|
||||
speed: ''
|
||||
target_weigh: ''
|
||||
temperature: ''
|
||||
volume: ''
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: DispenStationSolnPrep_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
hold_m_name:
|
||||
type: string
|
||||
liquid_material_name:
|
||||
type: string
|
||||
material_name:
|
||||
type: string
|
||||
order_name:
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
target_weigh:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
volume:
|
||||
type: string
|
||||
required:
|
||||
- order_name
|
||||
- material_name
|
||||
- target_weigh
|
||||
- volume
|
||||
- liquid_material_name
|
||||
- speed
|
||||
- temperature
|
||||
- delay_time
|
||||
- hold_m_name
|
||||
title: DispenStationSolnPrep_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: DispenStationSolnPrep_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: DispenStationSolnPrep
|
||||
type: object
|
||||
type: DispenStationSolnPrep
|
||||
module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: ''
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
deck:
|
||||
type: string
|
||||
required:
|
||||
- config
|
||||
- deck
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
@@ -32,112 +32,7 @@ bioyond_cell:
|
||||
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: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
@@ -147,323 +42,8 @@ bioyond_cell:
|
||||
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: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025122301.xlsx
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
@@ -571,7 +151,14 @@ bioyond_cell:
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles: {}
|
||||
handles:
|
||||
output:
|
||||
- data_key: total_orders
|
||||
data_source: executor
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: sink
|
||||
label: 配液瓶数
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
@@ -591,6 +178,38 @@ bioyond_cell:
|
||||
title: create_orders参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_orders_v2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles:
|
||||
output:
|
||||
- data_key: total_orders
|
||||
data_source: executor
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: sink
|
||||
label: 配液瓶数
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 从Excel解析并创建实验(V2版本)
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
type: string
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_orders_v2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_sample:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -821,154 +440,6 @@ bioyond_cell:
|
||||
title: resource_tree_transfer参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-run_feeding_stage:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles:
|
||||
input: []
|
||||
output:
|
||||
- data_key: feeding_materials
|
||||
data_source: executor
|
||||
data_type: resource
|
||||
handler_key: feeding_materials
|
||||
label: Feeding Materials
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
properties:
|
||||
feeding_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- feeding_materials
|
||||
type: object
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: run_feeding_stage参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-run_liquid_preparation_stage:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles:
|
||||
input:
|
||||
- data_key: feeding_materials
|
||||
data_source: handle
|
||||
data_type: resource
|
||||
handler_key: feeding_materials
|
||||
label: Feeding Materials
|
||||
output:
|
||||
- data_key: liquid_materials
|
||||
data_source: executor
|
||||
data_type: resource
|
||||
handler_key: liquid_materials
|
||||
label: Liquid Materials
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
properties:
|
||||
feeding_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
liquid_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- liquid_materials
|
||||
type: object
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
feeding_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: run_liquid_preparation_stage参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-run_transfer_stage:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles:
|
||||
input:
|
||||
- data_key: liquid_materials
|
||||
data_source: handle
|
||||
data_type: resource
|
||||
handler_key: liquid_materials
|
||||
label: Liquid Materials
|
||||
output:
|
||||
- data_key: transfer_materials
|
||||
data_source: executor
|
||||
data_type: resource
|
||||
handler_key: transfer_materials
|
||||
label: Transfer Materials
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
properties:
|
||||
liquid_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
transfer_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
transfer_summary:
|
||||
type: object
|
||||
required:
|
||||
- transfer_materials
|
||||
type: object
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
liquid_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
liquid_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
transfer_materials:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
transfer_summary:
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: run_transfer_stage参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_continue:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -1032,6 +503,31 @@ bioyond_cell:
|
||||
title: scheduler_start参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_start_and_auto_feeding:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 组合函数:先启动调度,然后执行自动化上料
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_start_and_auto_feeding参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_stop:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -1130,6 +626,47 @@ bioyond_cell:
|
||||
title: transfer_1_to_2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_3_to_2:
|
||||
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: 3-2 物料转运,从3号位置转运到2号位置
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
source_wh_id:
|
||||
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
description: 来源仓库ID
|
||||
type: string
|
||||
source_x:
|
||||
default: 1
|
||||
description: 来源位置X坐标
|
||||
type: integer
|
||||
source_y:
|
||||
default: 1
|
||||
description: 来源位置Y坐标
|
||||
type: integer
|
||||
source_z:
|
||||
default: 1
|
||||
description: 来源位置Z坐标
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_3_to_2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_3_to_2_to_1:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -1260,7 +797,7 @@ bioyond_cell:
|
||||
device_id: String
|
||||
type: python
|
||||
config_info: []
|
||||
description: 配液工站
|
||||
description: ''
|
||||
handles: []
|
||||
icon: benyao2.webp
|
||||
init_param_schema:
|
||||
|
||||
344
unilabos/registry/devices/chinwe.yaml
Normal file
344
unilabos/registry/devices/chinwe.yaml
Normal file
@@ -0,0 +1,344 @@
|
||||
separator.chinwe:
|
||||
category:
|
||||
- separator
|
||||
- chinwe
|
||||
class:
|
||||
action_value_mappings:
|
||||
motor_rotate_quarter:
|
||||
goal:
|
||||
direction: 顺时针
|
||||
motor_id: 4
|
||||
speed: 60
|
||||
handles: {}
|
||||
schema:
|
||||
description: 电机旋转 1/4 圈
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
direction:
|
||||
default: 顺时针
|
||||
description: 旋转方向
|
||||
enum:
|
||||
- 顺时针
|
||||
- 逆时针
|
||||
type: string
|
||||
motor_id:
|
||||
default: '4'
|
||||
description: 选择电机 (4:搅拌, 5:旋钮)
|
||||
enum:
|
||||
- '4'
|
||||
- '5'
|
||||
type: string
|
||||
speed:
|
||||
default: 60
|
||||
description: 速度 (RPM)
|
||||
type: integer
|
||||
required:
|
||||
- motor_id
|
||||
- speed
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
motor_run_continuous:
|
||||
goal:
|
||||
direction: 顺时针
|
||||
motor_id: 4
|
||||
speed: 60
|
||||
handles: {}
|
||||
schema:
|
||||
description: 电机一直旋转 (速度模式)
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
direction:
|
||||
default: 顺时针
|
||||
description: 旋转方向
|
||||
enum:
|
||||
- 顺时针
|
||||
- 逆时针
|
||||
type: string
|
||||
motor_id:
|
||||
default: '4'
|
||||
description: 选择电机 (4:搅拌, 5:旋钮)
|
||||
enum:
|
||||
- '4'
|
||||
- '5'
|
||||
type: string
|
||||
speed:
|
||||
default: 60
|
||||
description: 速度 (RPM)
|
||||
type: integer
|
||||
required:
|
||||
- motor_id
|
||||
- speed
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
motor_stop:
|
||||
goal:
|
||||
motor_id: 4
|
||||
handles: {}
|
||||
schema:
|
||||
description: 停止指定步进电机
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
motor_id:
|
||||
default: '4'
|
||||
description: 选择电机
|
||||
enum:
|
||||
- '4'
|
||||
- '5'
|
||||
title: '注: 4=搅拌, 5=旋钮'
|
||||
type: string
|
||||
required:
|
||||
- motor_id
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pump_aspirate:
|
||||
goal:
|
||||
pump_id: 1
|
||||
valve_port: 1
|
||||
volume: 1000
|
||||
handles: {}
|
||||
schema:
|
||||
description: 注射泵吸液
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
pump_id:
|
||||
default: '1'
|
||||
description: 选择泵
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
type: string
|
||||
valve_port:
|
||||
default: '1'
|
||||
description: 阀门端口
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
- '4'
|
||||
- '5'
|
||||
- '6'
|
||||
- '7'
|
||||
- '8'
|
||||
type: string
|
||||
volume:
|
||||
default: 1000
|
||||
description: 吸液步数
|
||||
type: integer
|
||||
required:
|
||||
- pump_id
|
||||
- volume
|
||||
- valve_port
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pump_dispense:
|
||||
goal:
|
||||
pump_id: 1
|
||||
valve_port: 1
|
||||
volume: 1000
|
||||
handles: {}
|
||||
schema:
|
||||
description: 注射泵排液
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
pump_id:
|
||||
default: '1'
|
||||
description: 选择泵
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
type: string
|
||||
valve_port:
|
||||
default: '1'
|
||||
description: 阀门端口
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
- '4'
|
||||
- '5'
|
||||
- '6'
|
||||
- '7'
|
||||
- '8'
|
||||
type: string
|
||||
volume:
|
||||
default: 1000
|
||||
description: 排液步数
|
||||
type: integer
|
||||
required:
|
||||
- pump_id
|
||||
- volume
|
||||
- valve_port
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pump_initialize:
|
||||
goal:
|
||||
drain_port: 0
|
||||
output_port: 0
|
||||
pump_id: 1
|
||||
speed: 10
|
||||
handles: {}
|
||||
schema:
|
||||
description: 初始化指定注射泵
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
drain_port:
|
||||
default: 0
|
||||
description: 排液口索引
|
||||
type: integer
|
||||
output_port:
|
||||
default: 0
|
||||
description: 输出口索引
|
||||
type: integer
|
||||
pump_id:
|
||||
default: '1'
|
||||
description: 选择泵
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
title: '注: 1号泵, 2号泵, 3号泵'
|
||||
type: string
|
||||
speed:
|
||||
default: 10
|
||||
description: 运动速度
|
||||
type: integer
|
||||
required:
|
||||
- pump_id
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
pump_valve:
|
||||
goal:
|
||||
port: 1
|
||||
pump_id: 1
|
||||
handles: {}
|
||||
schema:
|
||||
description: 切换指定泵的阀门端口
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
port:
|
||||
default: '1'
|
||||
description: 阀门端口号 (1-8)
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
- '4'
|
||||
- '5'
|
||||
- '6'
|
||||
- '7'
|
||||
- '8'
|
||||
type: string
|
||||
pump_id:
|
||||
default: '1'
|
||||
description: 选择泵
|
||||
enum:
|
||||
- '1'
|
||||
- '2'
|
||||
- '3'
|
||||
type: string
|
||||
required:
|
||||
- pump_id
|
||||
- port
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
wait_sensor_level:
|
||||
goal:
|
||||
target_state: 有液
|
||||
timeout: 30
|
||||
handles: {}
|
||||
schema:
|
||||
description: 等待传感器液位条件
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
target_state:
|
||||
default: 有液
|
||||
description: 目标液位状态
|
||||
enum:
|
||||
- 有液
|
||||
- 无液
|
||||
type: string
|
||||
timeout:
|
||||
default: 30
|
||||
description: 超时时间 (秒)
|
||||
type: integer
|
||||
required:
|
||||
- target_state
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
wait_time:
|
||||
goal:
|
||||
duration: 10
|
||||
handles: {}
|
||||
schema:
|
||||
description: 等待指定时间
|
||||
properties:
|
||||
goal:
|
||||
properties:
|
||||
duration:
|
||||
default: 10
|
||||
description: 等待时间 (秒)
|
||||
type: integer
|
||||
required:
|
||||
- duration
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.separator.chinwe:ChinweDevice
|
||||
status_types:
|
||||
is_connected: bool
|
||||
sensor_level: bool
|
||||
sensor_rssi: int
|
||||
type: python
|
||||
config_info: []
|
||||
description: ChinWe 简易工作站控制器 (3泵, 2电机, 1传感器)
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
goal:
|
||||
baudrate:
|
||||
default: 9600
|
||||
description: 串口波特率
|
||||
type: integer
|
||||
motor_ids:
|
||||
default:
|
||||
- 4
|
||||
- 5
|
||||
description: 步进电机ID列表
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
port:
|
||||
default: 192.168.1.200:8899
|
||||
description: 串口号或 IP:Port
|
||||
type: string
|
||||
pump_ids:
|
||||
default:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
description: 注射泵ID列表
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
sensor_id:
|
||||
default: 6
|
||||
description: XKC传感器ID
|
||||
type: integer
|
||||
sensor_threshold:
|
||||
default: 300
|
||||
description: 传感器液位判定阈值
|
||||
type: integer
|
||||
timeout:
|
||||
default: 10
|
||||
description: 通信超时时间 (秒)
|
||||
type: integer
|
||||
version: 2.1.0
|
||||
@@ -115,6 +115,117 @@ coincellassemblyworkstation_device:
|
||||
title: func_allpack_cmd参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_allpack_cmd_simp:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
assembly_pressure: 4200
|
||||
assembly_type: 7
|
||||
battery_clean_ignore: false
|
||||
battery_pressure_mode: true
|
||||
dual_drop_first_volume: 25
|
||||
dual_drop_mode: false
|
||||
dual_drop_start_timing: false
|
||||
dual_drop_suction_timing: false
|
||||
elec_num: null
|
||||
elec_use_num: null
|
||||
elec_vol: 50
|
||||
file_path: /Users/sml/work
|
||||
fujipian_juzhendianwei: 0
|
||||
fujipian_panshu: 0
|
||||
gemo_juzhendianwei: 0
|
||||
gemopanshu: 0
|
||||
lvbodian: true
|
||||
qiangtou_juzhendianwei: 0
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 简化版电池组装函数,整合了参数设置和双滴模式
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
default: 4200
|
||||
description: 电池压制力(N)
|
||||
type: integer
|
||||
assembly_type:
|
||||
default: 7
|
||||
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
|
||||
type: integer
|
||||
battery_clean_ignore:
|
||||
default: false
|
||||
description: 是否忽略电池清洁步骤
|
||||
type: boolean
|
||||
battery_pressure_mode:
|
||||
default: true
|
||||
description: 是否启用压力模式
|
||||
type: boolean
|
||||
dual_drop_first_volume:
|
||||
default: 25
|
||||
description: 二次滴液第一次排液体积(μL)
|
||||
type: integer
|
||||
dual_drop_mode:
|
||||
default: false
|
||||
description: 电解液添加模式(false=单次滴液, true=二次滴液)
|
||||
type: boolean
|
||||
dual_drop_start_timing:
|
||||
default: false
|
||||
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
|
||||
type: boolean
|
||||
dual_drop_suction_timing:
|
||||
default: false
|
||||
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
|
||||
type: boolean
|
||||
elec_num:
|
||||
description: 电解液瓶数
|
||||
type: string
|
||||
elec_use_num:
|
||||
description: 每瓶电解液组装电池数
|
||||
type: string
|
||||
elec_vol:
|
||||
default: 50
|
||||
description: 电解液吸液量(μL)
|
||||
type: integer
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
description: 实验记录保存路径
|
||||
type: string
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
description: 负极片矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
description: 负极片盘数
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
description: 隔膜盘数
|
||||
type: integer
|
||||
lvbodian:
|
||||
default: true
|
||||
description: 是否使用铝箔垫片
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位。盘位置从1开始计数,有效范围:1-32, 64-96 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
required:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_allpack_cmd_simp参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_get_csv_export_status:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -178,6 +289,27 @@ coincellassemblyworkstation_device:
|
||||
title: func_pack_device_init参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_device_init_auto_start_combined:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 组合函数:设备初始化 + 切换自动模式 + 启动
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_device_init_auto_start_combined参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_device_start:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -250,7 +382,15 @@ coincellassemblyworkstation_device:
|
||||
goal: {}
|
||||
goal_default:
|
||||
bottle_num: null
|
||||
handles: {}
|
||||
handles:
|
||||
input:
|
||||
- data_key: bottle_num
|
||||
data_source: workflow
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: source
|
||||
label: 配液瓶数
|
||||
required: true
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
@@ -260,7 +400,7 @@ coincellassemblyworkstation_device:
|
||||
goal:
|
||||
properties:
|
||||
bottle_num:
|
||||
type: string
|
||||
type: integer
|
||||
required:
|
||||
- bottle_num
|
||||
type: object
|
||||
@@ -353,6 +493,125 @@ coincellassemblyworkstation_device:
|
||||
title: func_read_data_and_output参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_sendbottle_allpack_multi:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
assembly_pressure: 4200
|
||||
assembly_type: 7
|
||||
battery_clean_ignore: false
|
||||
battery_pressure_mode: true
|
||||
dual_drop_first_volume: 25
|
||||
dual_drop_mode: false
|
||||
dual_drop_start_timing: false
|
||||
dual_drop_suction_timing: false
|
||||
elec_num: null
|
||||
elec_use_num: null
|
||||
elec_vol: 50
|
||||
file_path: /Users/sml/work
|
||||
fujipian_juzhendianwei: 0
|
||||
fujipian_panshu: 0
|
||||
gemo_juzhendianwei: 0
|
||||
gemopanshu: 0
|
||||
lvbodian: true
|
||||
qiangtou_juzhendianwei: 0
|
||||
handles:
|
||||
input:
|
||||
- data_key: elec_num
|
||||
data_source: workflow
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: source
|
||||
label: 配液瓶数
|
||||
required: true
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 发送瓶数+简化组装函数(适用于第二批次及后续批次),合并了发送瓶数和简化组装流程
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
default: 4200
|
||||
description: 电池压制力(N)
|
||||
type: integer
|
||||
assembly_type:
|
||||
default: 7
|
||||
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
|
||||
type: integer
|
||||
battery_clean_ignore:
|
||||
default: false
|
||||
description: 是否忽略电池清洁步骤
|
||||
type: boolean
|
||||
battery_pressure_mode:
|
||||
default: true
|
||||
description: 是否启用压力模式
|
||||
type: boolean
|
||||
dual_drop_first_volume:
|
||||
default: 25
|
||||
description: 二次滴液第一次排液体积(μL)
|
||||
type: integer
|
||||
dual_drop_mode:
|
||||
default: false
|
||||
description: 电解液添加模式(false=单次滴液, true=二次滴液)
|
||||
type: boolean
|
||||
dual_drop_start_timing:
|
||||
default: false
|
||||
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
|
||||
type: boolean
|
||||
dual_drop_suction_timing:
|
||||
default: false
|
||||
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
|
||||
type: boolean
|
||||
elec_num:
|
||||
description: 电解液瓶数,如果在workflow中已通过handles连接上游(create_orders的bottle_count输出),则此参数会自动从上游获取,无需手动填写;如果单独使用此函数(没有上游连接),则必须手动填写电解液瓶数
|
||||
type: string
|
||||
elec_use_num:
|
||||
description: 每瓶电解液组装电池数
|
||||
type: string
|
||||
elec_vol:
|
||||
default: 50
|
||||
description: 电解液吸液量(μL)
|
||||
type: integer
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
description: 实验记录保存路径
|
||||
type: string
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
description: 负极片矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
description: 负极片盘数
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
description: 隔膜盘数
|
||||
type: integer
|
||||
lvbodian:
|
||||
default: true
|
||||
description: 是否使用铝箔垫片
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位。盘位置从1开始计数,有效范围:1-32, 64-96 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
required:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_sendbottle_allpack_multi参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_stop_read_data:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -477,171 +736,6 @@ coincellassemblyworkstation_device:
|
||||
title: qiming_coin_cell_code参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-run_coin_cell_assembly_workflow:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_config:
|
||||
type: object
|
||||
required: []
|
||||
type: object
|
||||
goal_default:
|
||||
workflow_config: {}
|
||||
handles:
|
||||
input:
|
||||
- data_key: workflow_config
|
||||
data_source: handle
|
||||
data_type: resource
|
||||
handler_key: WorkflowConfig
|
||||
label: Workflow Config
|
||||
output:
|
||||
- data_key: qiming
|
||||
data_source: executor
|
||||
data_type: resource
|
||||
handler_key: QimingResult
|
||||
label: Qiming Result
|
||||
- data_key: workflow_steps
|
||||
data_source: executor
|
||||
data_type: resource
|
||||
handler_key: WorkflowSteps
|
||||
label: Workflow Steps
|
||||
- data_key: packaging
|
||||
data_source: executor
|
||||
data_type: resource
|
||||
handler_key: PackagingResult
|
||||
label: Packaging Result
|
||||
- data_key: finish
|
||||
data_source: executor
|
||||
data_type: resource
|
||||
handler_key: FinishResult
|
||||
label: Finish Result
|
||||
placeholder_keys: {}
|
||||
result:
|
||||
properties:
|
||||
finish:
|
||||
properties:
|
||||
send_finished:
|
||||
type: object
|
||||
stop:
|
||||
type: object
|
||||
required:
|
||||
- send_finished
|
||||
- stop
|
||||
type: object
|
||||
packaging:
|
||||
properties:
|
||||
bottle_num:
|
||||
type: integer
|
||||
command:
|
||||
type: object
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- bottle_num
|
||||
- command
|
||||
- result
|
||||
type: object
|
||||
qiming:
|
||||
properties:
|
||||
params:
|
||||
type: object
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- params
|
||||
- success
|
||||
type: object
|
||||
workflow_steps:
|
||||
type: object
|
||||
required:
|
||||
- qiming
|
||||
- workflow_steps
|
||||
- packaging
|
||||
- finish
|
||||
type: object
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_config:
|
||||
type: object
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
finish:
|
||||
properties:
|
||||
send_finished:
|
||||
type: object
|
||||
stop:
|
||||
type: object
|
||||
required:
|
||||
- send_finished
|
||||
- stop
|
||||
type: object
|
||||
packaging:
|
||||
properties:
|
||||
bottle_num:
|
||||
type: integer
|
||||
command:
|
||||
type: object
|
||||
result:
|
||||
type: object
|
||||
required:
|
||||
- bottle_num
|
||||
- command
|
||||
- result
|
||||
type: object
|
||||
qiming:
|
||||
properties:
|
||||
params:
|
||||
type: object
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- params
|
||||
- success
|
||||
type: object
|
||||
workflow_steps:
|
||||
type: object
|
||||
required:
|
||||
- qiming
|
||||
- workflow_steps
|
||||
- packaging
|
||||
- finish
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: run_coin_cell_assembly_workflow参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-run_packaging_workflow:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
workflow_config: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_config:
|
||||
type: object
|
||||
required:
|
||||
- workflow_config
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: run_packaging_workflow参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
||||
status_types:
|
||||
data_assembly_coin_cell_num: int
|
||||
@@ -665,7 +759,7 @@ coincellassemblyworkstation_device:
|
||||
sys_status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: 扣电工站
|
||||
description: ''
|
||||
handles: []
|
||||
icon: koudian.webp
|
||||
init_param_schema:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
||||
neware_battery_test_system:
|
||||
category:
|
||||
- neware_battery_test_system
|
||||
- neware
|
||||
- battery_test
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-post_init:
|
||||
@@ -72,38 +70,6 @@ neware_battery_test_system:
|
||||
title: test_connection参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
debug_resource_names:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
schema:
|
||||
description: 调试方法:显示所有资源的实际名称
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 资源调试信息
|
||||
type: string
|
||||
success:
|
||||
description: 是否成功
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
export_status_json:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -253,9 +219,7 @@ neware_battery_test_system:
|
||||
goal_default:
|
||||
string: ''
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
success: success
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
@@ -288,56 +252,6 @@ neware_battery_test_system:
|
||||
title: StrSingleInput
|
||||
type: object
|
||||
type: StrSingleInput
|
||||
submit_from_csv:
|
||||
feedback: {}
|
||||
goal:
|
||||
csv_path: string
|
||||
output_dir: string
|
||||
goal_default:
|
||||
csv_path: ''
|
||||
output_dir: .
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
submitted_count: submitted_count
|
||||
success: success
|
||||
schema:
|
||||
description: 从CSV文件批量提交Neware测试任务
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
csv_path:
|
||||
description: 输入CSV文件的绝对路径
|
||||
type: string
|
||||
output_dir:
|
||||
description: 输出目录(用于存储XML和备份文件),默认当前目录
|
||||
type: string
|
||||
required:
|
||||
- csv_path
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 执行结果详细信息
|
||||
type: string
|
||||
submitted_count:
|
||||
description: 成功提交的任务数量
|
||||
type: integer
|
||||
success:
|
||||
description: 是否成功
|
||||
type: boolean
|
||||
total_count:
|
||||
description: CSV文件中的总行数
|
||||
type: integer
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
test_connection_action:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -370,7 +284,7 @@ neware_battery_test_system:
|
||||
- goal
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
|
||||
module: unilabos.devices.battery.neware_battery_test_system:NewareBatteryTestSystem
|
||||
status_types:
|
||||
channel_status: dict
|
||||
connection_info: dict
|
||||
@@ -380,7 +294,7 @@ neware_battery_test_system:
|
||||
total_channels: int
|
||||
type: python
|
||||
config_info: []
|
||||
description: 新威电池测试系统驱动,提供720个通道的电池测试状态监控、物料管理和CSV批量提交功能。支持TCP通信实现远程控制,包含完整的物料管理系统(2盘电池状态映射),以及从CSV文件批量提交测试任务的能力。
|
||||
description: 新威电池测试系统驱动,支持720个通道的电池测试状态监控和数据导出。通过TCP通信实现远程控制,包含完整的物料管理系统,支持2盘电池的状态映射和监控。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -396,13 +310,13 @@ neware_battery_test_system:
|
||||
port:
|
||||
type: integer
|
||||
size_x:
|
||||
default: 50
|
||||
default: 500.0
|
||||
type: number
|
||||
size_y:
|
||||
default: 50
|
||||
default: 500.0
|
||||
type: number
|
||||
size_z:
|
||||
default: 20
|
||||
default: 2000.0
|
||||
type: number
|
||||
timeout:
|
||||
type: integer
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ BIOYOND_PolymerReactionStation_Deck:
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_Deck11:
|
||||
BIOYOND_YB_Deck:
|
||||
category:
|
||||
- deck
|
||||
class:
|
||||
@@ -34,3 +34,15 @@ YB_Deck11:
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
CoincellDeck:
|
||||
category:
|
||||
- deck
|
||||
class:
|
||||
module: unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:YH_Deck
|
||||
type: pylabrobot
|
||||
description: BIOYOND PolymerReactionStation Deck
|
||||
handles: []
|
||||
icon: koudian.webp
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
"""Battery-related resource classes for coin cell assembly"""
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,45 +1,56 @@
|
||||
"""
|
||||
瓶架类定义 - 用于纽扣电池组装工作站
|
||||
Bottle Carrier Resource Classes
|
||||
"""
|
||||
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
||||
|
||||
from __future__ import annotations
|
||||
from pylabrobot.resources import ResourceHolder
|
||||
from pylabrobot.resources.utils import create_ordered_items_2d
|
||||
from unilabos.resources.itemized_carrier import ItemizedCarrier
|
||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||
from unilabos.resources.bioyond.YB_bottles import (
|
||||
YB_pei_ye_xiao_Bottle,
|
||||
)
|
||||
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
||||
|
||||
|
||||
def YIHUA_Electrolyte_12VialCarrier(name: str) -> ItemizedCarrier:
|
||||
"""依华电解液12瓶架 - 3x4布局
|
||||
|
||||
Args:
|
||||
name: 瓶架名称
|
||||
|
||||
Returns:
|
||||
ItemizedCarrier: 包含12个瓶位的瓶架
|
||||
"""
|
||||
def YIHUA_Electrolyte_12VialCarrier(name: str) -> BottleCarrier:
|
||||
"""12瓶载架 - 2x6布局"""
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 120.0
|
||||
carrier_size_y = 250.0
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 35.0
|
||||
bottle_spacing_x = 35.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (6 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=3,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
num_items_x=2,
|
||||
num_items_y=6,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=70.0,
|
||||
item_dy=26.67,
|
||||
size_x=60.0,
|
||||
size_y=20.0,
|
||||
size_z=70.0,
|
||||
)
|
||||
|
||||
return ItemizedCarrier(
|
||||
name=name,
|
||||
size_x=300.0,
|
||||
size_y=100.0,
|
||||
size_z=80.0,
|
||||
num_items_x=4,
|
||||
num_items_y=3,
|
||||
sites=sites,
|
||||
category="bottle_carrier",
|
||||
)
|
||||
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="Electrolyte_12VialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 2
|
||||
carrier.num_items_y = 6
|
||||
carrier.num_items_z = 1
|
||||
for i in range(12):
|
||||
carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_vial_{i+1}")
|
||||
return carrier
|
||||
|
||||
@@ -1,35 +1,52 @@
|
||||
"""
|
||||
电极片类定义
|
||||
Electrode Sheet Resource Classes
|
||||
"""
|
||||
from typing import Any, Dict, Optional, TypedDict
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Any, Dict, Optional
|
||||
from pylabrobot.resources.resource import Resource
|
||||
from pylabrobot.resources import Resource as ResourcePLR
|
||||
from pylabrobot.resources import Container
|
||||
|
||||
|
||||
class ElectrodeSheet(Resource):
|
||||
"""电极片类 - 用于纽扣电池组装"""
|
||||
|
||||
electrode_colors = {
|
||||
"PositiveCan": "#ff0000",
|
||||
"PositiveElectrode": "#cc3333",
|
||||
"NegativeCan": "#000000",
|
||||
"NegativeElectrode": "#666666",
|
||||
"SpringWasher": "#8b7355",
|
||||
"FlatWasher": "a9a9a9",
|
||||
"AluminumFoil": "#ffcccc",
|
||||
"Battery": "#00ff00",
|
||||
}
|
||||
|
||||
class ElectrodeSheetState(TypedDict):
|
||||
diameter: float # 直径 (mm)
|
||||
thickness: float # 厚度 (mm)
|
||||
mass: float # 质量 (g)
|
||||
material_type: str # 材料类型(铜、铝、不锈钢、弹簧钢等)
|
||||
color: str # 材料类型对应的颜色
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
|
||||
class ElectrodeSheet(ResourcePLR):
|
||||
"""极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float = 12.0,
|
||||
size_y: float = 12.0,
|
||||
size_z: float = 0.1,
|
||||
name: str = "极片",
|
||||
size_x: float = 10,
|
||||
size_y: float = 10,
|
||||
size_z: float = 10,
|
||||
category: str = "electrode_sheet",
|
||||
electrode_type: str = "anode", # "anode" 负极, "cathode" 正极, "separator" 隔膜
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""初始化电极片
|
||||
|
||||
"""初始化极片
|
||||
|
||||
Args:
|
||||
name: 电极片名称
|
||||
size_x: X方向尺寸 (mm)
|
||||
size_y: Y方向尺寸 (mm)
|
||||
size_z: Z方向尺寸/厚度 (mm)
|
||||
name: 极片名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
category: 类别
|
||||
electrode_type: 电极类型
|
||||
model: 型号
|
||||
**kwargs: 其他参数传递给父类
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
@@ -37,31 +54,142 @@ class ElectrodeSheet(Resource):
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
category=category,
|
||||
model=model,
|
||||
**kwargs
|
||||
)
|
||||
self._electrode_type = electrode_type
|
||||
self._unilabos_state: Dict[str, Any] = {
|
||||
"electrode_type": electrode_type,
|
||||
"material": "",
|
||||
"thickness": size_z,
|
||||
}
|
||||
|
||||
@property
|
||||
def electrode_type(self) -> str:
|
||||
"""获取电极类型"""
|
||||
return self._electrode_type
|
||||
|
||||
self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(
|
||||
diameter=14,
|
||||
thickness=0.1,
|
||||
mass=0.5,
|
||||
material_type="copper",
|
||||
color="#8b4513",
|
||||
info=None
|
||||
)
|
||||
|
||||
# TODO: 这个还要不要?给self._unilabos_state赋值的?
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""加载状态"""
|
||||
"""格式不变"""
|
||||
super().load_state(state)
|
||||
if isinstance(state, dict):
|
||||
self._unilabos_state.update(state)
|
||||
|
||||
def serialize_state(self) -> Dict[str, Any]:
|
||||
"""序列化状态"""
|
||||
self._unilabos_state = state
|
||||
#序列化
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""格式不变"""
|
||||
data = super().serialize_state()
|
||||
data.update(self._unilabos_state)
|
||||
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||
return data
|
||||
|
||||
|
||||
def PositiveCan(name: str) -> ElectrodeSheet:
|
||||
"""创建正极壳"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=3.0, model="PositiveCan")
|
||||
sheet.load_state({"diameter": 20.0, "thickness": 0.5, "mass": 0.5, "material_type": "aluminum", "color": electrode_colors["PositiveCan"], "info": None})
|
||||
return sheet
|
||||
|
||||
|
||||
def PositiveElectrode(name: str) -> ElectrodeSheet:
|
||||
"""创建正极片"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="PositiveElectrode")
|
||||
sheet.load_state({"material_type": "positive_electrode", "color": electrode_colors["PositiveElectrode"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def NegativeCan(name: str) -> ElectrodeSheet:
|
||||
"""创建负极壳"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=2.0, model="NegativeCan")
|
||||
sheet.load_state({"material_type": "steel", "color": electrode_colors["NegativeCan"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def NegativeElectrode(name: str) -> ElectrodeSheet:
|
||||
"""创建负极片"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="NegativeElectrode")
|
||||
sheet.load_state({"material_type": "negative_electrode", "color": electrode_colors["NegativeElectrode"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def SpringWasher(name: str) -> ElectrodeSheet:
|
||||
"""创建弹片"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.5, model="SpringWasher")
|
||||
sheet.load_state({"material_type": "spring_steel", "color": electrode_colors["SpringWasher"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def FlatWasher(name: str) -> ElectrodeSheet:
|
||||
"""创建垫片"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.2, model="FlatWasher")
|
||||
sheet.load_state({"material_type": "steel", "color": electrode_colors["FlatWasher"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def AluminumFoil(name: str) -> ElectrodeSheet:
|
||||
"""创建铝箔"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.05, model="AluminumFoil")
|
||||
sheet.load_state({"material_type": "aluminum", "color": electrode_colors["AluminumFoil"]})
|
||||
return sheet
|
||||
|
||||
|
||||
class BatteryState(TypedDict):
|
||||
color: str # 材料类型对应的颜色
|
||||
electrolyte_name: str
|
||||
data_electrolyte_code: str
|
||||
open_circuit_voltage: float
|
||||
assembly_pressure: float
|
||||
electrolyte_volume: float
|
||||
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
|
||||
class Battery(Container):
|
||||
"""电池类 - 包含组装好的电池"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "电池",
|
||||
size_x: float = 12,
|
||||
size_y: float = 12,
|
||||
size_z: float = 6,
|
||||
category: str = "battery",
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""初始化电池
|
||||
|
||||
Args:
|
||||
name: 电池名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
category: 类别
|
||||
model: 型号
|
||||
**kwargs: 其他参数传递给父类
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
category=category,
|
||||
model=model,
|
||||
**kwargs
|
||||
)
|
||||
self._unilabos_state: BatteryState = BatteryState(
|
||||
color=electrode_colors["Battery"],
|
||||
electrolyte_name="无",
|
||||
data_electrolyte_code="",
|
||||
open_circuit_voltage=0.0,
|
||||
assembly_pressure=0.0,
|
||||
electrolyte_volume=0.0,
|
||||
info=None
|
||||
)
|
||||
|
||||
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
|
||||
@@ -1,152 +1,344 @@
|
||||
"""
|
||||
弹夹架类定义 - 用于纽扣电池组装工作站
|
||||
Magazine Holder Resource Classes
|
||||
"""
|
||||
from typing import Dict, List, Optional, OrderedDict, Union, Callable
|
||||
import math
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import List, Optional
|
||||
from pylabrobot.resources.coordinate import Coordinate
|
||||
from pylabrobot.resources import ResourceHolder
|
||||
from pylabrobot.resources.utils import create_ordered_items_2d
|
||||
from unilabos.resources.itemized_carrier import ItemizedCarrier
|
||||
from pylabrobot.resources import Resource, ResourceStack, ItemizedResource
|
||||
from pylabrobot.resources.carrier import create_homogeneous_resources
|
||||
|
||||
from unilabos.resources.battery.electrode_sheet import (
|
||||
PositiveCan, PositiveElectrode,
|
||||
NegativeCan, NegativeElectrode,
|
||||
SpringWasher, FlatWasher,
|
||||
AluminumFoil,
|
||||
Battery
|
||||
)
|
||||
|
||||
|
||||
def MagazineHolder_4_Cathode(name: str) -> ItemizedCarrier:
|
||||
"""正极&铝箔弹夹 - 4个洞位 (2x2布局)
|
||||
class Magazine(ResourceStack):
|
||||
"""子弹夹洞位类"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
direction: str = 'z',
|
||||
resources: Optional[List[Resource]] = None,
|
||||
max_sheets: int = 100,
|
||||
**kwargs
|
||||
):
|
||||
"""初始化子弹夹洞位
|
||||
|
||||
Args:
|
||||
name: 洞位名称
|
||||
direction: 堆叠方向
|
||||
resources: 资源列表
|
||||
max_sheets: 最大极片数量
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
direction=direction,
|
||||
resources=resources,
|
||||
)
|
||||
self.max_sheets = max_sheets
|
||||
|
||||
@property
|
||||
def size_x(self) -> float:
|
||||
return self.get_size_x()
|
||||
|
||||
@property
|
||||
def size_y(self) -> float:
|
||||
return self.get_size_y()
|
||||
|
||||
@property
|
||||
def size_z(self) -> float:
|
||||
return self.get_size_z()
|
||||
|
||||
def serialize(self) -> dict:
|
||||
return {
|
||||
**super().serialize(),
|
||||
"size_x": self.size_x or 10.0,
|
||||
"size_y": self.size_y or 10.0,
|
||||
"size_z": self.size_z or 10.0,
|
||||
"max_sheets": self.max_sheets,
|
||||
}
|
||||
|
||||
|
||||
class MagazineHolder(ItemizedResource):
|
||||
"""子弹夹类 - 有多个洞位,每个洞位放多个极片"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
ordered_items: Optional[Dict[str, Magazine]] = None,
|
||||
ordering: Optional[OrderedDict[str, str]] = None,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
cross_section_type: str = "circle",
|
||||
category: str = "magazine_holder",
|
||||
model: Optional[str] = None,
|
||||
):
|
||||
"""初始化子弹夹
|
||||
|
||||
Args:
|
||||
name: 子弹夹名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
hole_diameter: 洞直径 (mm)
|
||||
hole_depth: 洞深度 (mm)
|
||||
max_sheets_per_hole: 每个洞位最大极片数量
|
||||
category: 类别
|
||||
model: 型号
|
||||
"""
|
||||
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
ordered_items=ordered_items,
|
||||
ordering=ordering,
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
|
||||
# 保存洞位的直径和深度
|
||||
self.hole_diameter = hole_diameter
|
||||
self.hole_depth = hole_depth
|
||||
self.max_sheets_per_hole = max_sheets_per_hole
|
||||
self.cross_section_type = cross_section_type
|
||||
|
||||
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,
|
||||
"cross_section_type": self.cross_section_type,
|
||||
}
|
||||
|
||||
|
||||
def magazine_factory(
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
locations: List[Coordinate],
|
||||
klasses: Optional[List[Callable[[str], str]]] = None,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
category: str = "magazine_holder",
|
||||
model: Optional[str] = None,
|
||||
) -> 'MagazineHolder':
|
||||
"""工厂函数:创建子弹夹
|
||||
|
||||
Args:
|
||||
name: 弹夹名称
|
||||
|
||||
Returns:
|
||||
ItemizedCarrier: 包含4个槽位的弹夹架
|
||||
name: 子弹夹名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
locations: 洞位坐标列表
|
||||
klasses: 每个洞位中极片的类列表
|
||||
hole_diameter: 洞直径 (mm)
|
||||
hole_depth: 洞深度 (mm)
|
||||
max_sheets_per_hole: 每个洞位最大极片数量
|
||||
category: 类别
|
||||
model: 型号
|
||||
"""
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=0.0,
|
||||
item_dx=50.0,
|
||||
item_dy=30.0,
|
||||
size_x=40.0,
|
||||
size_y=25.0,
|
||||
size_z=40.0,
|
||||
for loc in locations:
|
||||
loc.x -= hole_diameter / 2
|
||||
loc.y -= hole_diameter / 2
|
||||
|
||||
# 创建洞位
|
||||
_sites = create_homogeneous_resources(
|
||||
klass=Magazine,
|
||||
locations=locations,
|
||||
resource_size_x=hole_diameter,
|
||||
resource_size_y=hole_diameter,
|
||||
name_prefix=name,
|
||||
max_sheets=max_sheets_per_hole,
|
||||
)
|
||||
|
||||
return ItemizedCarrier(
|
||||
# 生成编号键
|
||||
keys = [f"A{i+1}" for i in range(len(locations))]
|
||||
sites = dict(zip(keys, _sites.values()))
|
||||
|
||||
holder = MagazineHolder(
|
||||
name=name,
|
||||
size_x=120.0,
|
||||
size_y=80.0,
|
||||
size_z=50.0,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
sites=sites,
|
||||
category="magazine_holder",
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
ordered_items=sites,
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
|
||||
if klasses is not None:
|
||||
for i, klass in enumerate(klasses):
|
||||
hole_key = keys[i]
|
||||
hole = holder.children[i]
|
||||
for j in reversed(range(max_sheets_per_hole)):
|
||||
item_name = f"{hole_key}_sheet{j+1}"
|
||||
item = klass(name=item_name)
|
||||
hole.assign_child_resource(item)
|
||||
return holder
|
||||
|
||||
def MagazineHolder_6_Cathode(name: str) -> ItemizedCarrier:
|
||||
"""正极壳&平垫片弹夹 - 6个洞位 (2x3布局)
|
||||
|
||||
Args:
|
||||
name: 弹夹名称
|
||||
|
||||
Returns:
|
||||
ItemizedCarrier: 包含6个槽位的弹夹架
|
||||
"""
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=0.0,
|
||||
item_dx=40.0,
|
||||
item_dy=30.0,
|
||||
size_x=35.0,
|
||||
size_y=25.0,
|
||||
size_z=40.0,
|
||||
)
|
||||
|
||||
return ItemizedCarrier(
|
||||
|
||||
def MagazineHolder_6_Cathode(
|
||||
name: str,
|
||||
size_x: float = 80.0,
|
||||
size_y: float = 80.0,
|
||||
size_z: float = 40.0,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
hole_spacing: float = 20.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
) -> MagazineHolder:
|
||||
"""创建6孔子弹夹 - 六边形排布"""
|
||||
center_x = size_x / 2
|
||||
center_y = size_y / 2
|
||||
|
||||
locations = []
|
||||
|
||||
# 周围6个孔,按六边形排布
|
||||
for i in range(6):
|
||||
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||
x = center_x + hole_spacing * math.cos(angle)
|
||||
y = center_y + hole_spacing * math.sin(angle)
|
||||
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||
|
||||
return magazine_factory(
|
||||
name=name,
|
||||
size_x=150.0,
|
||||
size_y=80.0,
|
||||
size_z=50.0,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
sites=sites,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
locations=locations,
|
||||
klasses=[FlatWasher, PositiveCan, PositiveCan, FlatWasher, PositiveCan, PositiveCan],
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category="magazine_holder",
|
||||
model="MagazineHolder_6_Cathode",
|
||||
)
|
||||
|
||||
|
||||
def MagazineHolder_6_Anode(name: str) -> ItemizedCarrier:
|
||||
"""负极壳&弹垫片弹夹 - 6个洞位 (2x3布局)
|
||||
|
||||
Args:
|
||||
name: 弹夹名称
|
||||
|
||||
Returns:
|
||||
ItemizedCarrier: 包含6个槽位的弹夹架
|
||||
"""
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=0.0,
|
||||
item_dx=40.0,
|
||||
item_dy=30.0,
|
||||
size_x=35.0,
|
||||
size_y=25.0,
|
||||
size_z=40.0,
|
||||
)
|
||||
|
||||
return ItemizedCarrier(
|
||||
def MagazineHolder_6_Anode(
|
||||
name: str,
|
||||
size_x: float = 80.0,
|
||||
size_y: float = 80.0,
|
||||
size_z: float = 40.0,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
hole_spacing: float = 20.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
) -> MagazineHolder:
|
||||
"""创建6孔子弹夹 - 六边形排布"""
|
||||
center_x = size_x / 2
|
||||
center_y = size_y / 2
|
||||
|
||||
locations = []
|
||||
|
||||
# 周围6个孔,按六边形排布
|
||||
for i in range(6):
|
||||
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||
x = center_x + hole_spacing * math.cos(angle)
|
||||
y = center_y + hole_spacing * math.sin(angle)
|
||||
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||
|
||||
return magazine_factory(
|
||||
name=name,
|
||||
size_x=150.0,
|
||||
size_y=80.0,
|
||||
size_z=50.0,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
sites=sites,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
locations=locations,
|
||||
klasses=[SpringWasher, NegativeCan, NegativeCan, SpringWasher, NegativeCan, NegativeCan],
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category="magazine_holder",
|
||||
model="MagazineHolder_6_Anode",
|
||||
)
|
||||
|
||||
|
||||
def MagazineHolder_6_Battery(name: str) -> ItemizedCarrier:
|
||||
"""成品弹夹 - 6个洞位 (3x2布局)
|
||||
|
||||
Args:
|
||||
name: 弹夹名称
|
||||
|
||||
Returns:
|
||||
ItemizedCarrier: 包含6个槽位的弹夹架
|
||||
"""
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=0.0,
|
||||
item_dx=33.0,
|
||||
item_dy=40.0,
|
||||
size_x=30.0,
|
||||
size_y=35.0,
|
||||
size_z=40.0,
|
||||
)
|
||||
|
||||
return ItemizedCarrier(
|
||||
def MagazineHolder_6_Battery(
|
||||
name: str,
|
||||
size_x: float = 80.0,
|
||||
size_y: float = 80.0,
|
||||
size_z: float = 40.0,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
hole_spacing: float = 20.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
) -> MagazineHolder:
|
||||
"""创建6孔子弹夹 - 六边形排布"""
|
||||
center_x = size_x / 2
|
||||
center_y = size_y / 2
|
||||
|
||||
locations = []
|
||||
|
||||
# 周围6个孔,按六边形排布
|
||||
for i in range(6):
|
||||
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||
x = center_x + hole_spacing * math.cos(angle)
|
||||
y = center_y + hole_spacing * math.sin(angle)
|
||||
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||
|
||||
return magazine_factory(
|
||||
name=name,
|
||||
size_x=120.0,
|
||||
size_y=100.0,
|
||||
size_z=50.0,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
sites=sites,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
locations=locations,
|
||||
klasses=None, # 初始化时,不放入装好的电池
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category="magazine_holder",
|
||||
model="MagazineHolder_6_Battery",
|
||||
)
|
||||
|
||||
|
||||
def MagazineHolder_4_Cathode(
|
||||
name: str,
|
||||
) -> MagazineHolder:
|
||||
"""创建4孔子弹夹 - 正方形四角排布"""
|
||||
size_x: float = 80.0
|
||||
size_y: float = 80.0
|
||||
size_z: float = 10.0
|
||||
hole_diameter: float = 14.0
|
||||
hole_depth: float = 10.0
|
||||
hole_spacing: float = 25.0
|
||||
max_sheets_per_hole: int = 100
|
||||
|
||||
# 计算4个洞位的坐标(正方形四角排布)
|
||||
center_x = size_x / 2
|
||||
center_y = size_y / 2
|
||||
offset = hole_spacing / 2
|
||||
|
||||
locations = [
|
||||
Coordinate(center_x - offset, center_y - offset, size_z - hole_depth), # 左下
|
||||
Coordinate(center_x + offset, center_y - offset, size_z - hole_depth), # 右下
|
||||
Coordinate(center_x - offset, center_y + offset, size_z - hole_depth), # 左上
|
||||
Coordinate(center_x + offset, center_y + offset, size_z - hole_depth), # 右上
|
||||
]
|
||||
|
||||
return magazine_factory(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
locations=locations,
|
||||
klasses=[AluminumFoil, PositiveElectrode, PositiveElectrode, PositiveElectrode],
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category="magazine_holder",
|
||||
model="MagazineHolder_4_Cathode",
|
||||
)
|
||||
|
||||
548
unilabos/resources/bioyond/README_WAREHOUSE.md
Normal file
548
unilabos/resources/bioyond/README_WAREHOUSE.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# Bioyond 仓库系统开发指南
|
||||
|
||||
本文档详细说明 Bioyond 仓库(Warehouse)系统的架构、配置和使用方法,帮助开发者快速理解和维护仓库相关代码。
|
||||
|
||||
## 📚 目录
|
||||
|
||||
- [系统架构](#系统架构)
|
||||
- [核心概念](#核心概念)
|
||||
- [三层映射关系](#三层映射关系)
|
||||
- [warehouse_factory 详解](#warehouse_factory-详解)
|
||||
- [创建新仓库](#创建新仓库)
|
||||
- [常见问题](#常见问题)
|
||||
- [调试技巧](#调试技巧)
|
||||
|
||||
---
|
||||
|
||||
## 系统架构
|
||||
|
||||
Bioyond 仓库系统采用**三层架构**,实现从前端显示到后端 API 的完整映射:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 前端显示层 (YB_warehouses.py) │
|
||||
│ - warehouse_factory 自动生成库位网格 │
|
||||
│ - 生成库位名称:A01, B02, C03... │
|
||||
│ - 存储在 WareHouse.sites 字典中 │
|
||||
└────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Deck 布局层 (decks.py) │
|
||||
│ - 定义仓库在 Deck 上的物理位置 │
|
||||
│ - 组织多个仓库形成完整布局 │
|
||||
└────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ UUID 映射层 (config.py) │
|
||||
│ - 将库位名称映射到 Bioyond 系统 UUID │
|
||||
│ - 用于 API 调用时的物料入库操作 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 仓库(Warehouse)
|
||||
|
||||
仓库是一个**三维网格**,用于存放物料。由以下参数定义:
|
||||
|
||||
- **num_items_x**: 列数(X 轴)
|
||||
- **num_items_y**: 行数(Y 轴)
|
||||
- **num_items_z**: 层数(Z 轴)
|
||||
|
||||
例如:`5行×3列×1层` = 5×3×1 = 15个库位
|
||||
|
||||
### 库位(Site)
|
||||
|
||||
库位是仓库中的单个存储位置,由**字母行+数字列**命名:
|
||||
|
||||
- **字母行**:A, B, C, D, E, F...(对应 Y 轴)
|
||||
- **数字列**:01, 02, 03, 04...(对应 X 轴或 Z 轴)
|
||||
|
||||
示例:`A01`, `B02`, `C03`
|
||||
|
||||
### 布局模式(Layout)
|
||||
|
||||
控制库位的排序和 Y 坐标计算:
|
||||
|
||||
| 模式 | 说明 | 生成顺序 | Y 坐标计算 | 显示效果 |
|
||||
|------|------|----------|-----------|---------|
|
||||
| `col-major` | 列优先(默认) | A01, B01, C01, A02... | `dy + (num_y - row - 1) * item_dy` | A 可能在下 |
|
||||
| `row-major` | 行优先 | A01, A02, A03, B01... | `dy + row * item_dy` | **A 在上** ✓ |
|
||||
|
||||
**重要:** 使用 `row-major` 可以避免上下颠倒问题!
|
||||
|
||||
---
|
||||
|
||||
## 三层映射关系
|
||||
|
||||
### 示例:手动传递窗右(A01-E03)
|
||||
|
||||
#### 1️⃣ 前端显示层 - [`YB_warehouses.py`](YB_warehouses.py)
|
||||
|
||||
```python
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建 5行×3列×1层 仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行
|
||||
num_items_z=1, # 1层
|
||||
row_offset=row_offset,
|
||||
layout="row-major",
|
||||
)
|
||||
```
|
||||
|
||||
**自动生成的库位:** A01, A02, A03, B01, B02, B03, ..., E01, E02, E03
|
||||
|
||||
#### 2️⃣ Deck 布局层 - [`decks.py`](decks.py)
|
||||
|
||||
```python
|
||||
self.warehouses = {
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0),
|
||||
}
|
||||
self.warehouse_locations = {
|
||||
"手动传递窗右": Coordinate(4160.0, 877.0, 0.0),
|
||||
}
|
||||
```
|
||||
|
||||
**作用:**
|
||||
- 创建仓库实例
|
||||
- 设置在 Deck 上的物理坐标
|
||||
|
||||
#### 3️⃣ UUID 映射层 - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
# ... 其他库位
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**作用:**
|
||||
- 用户拖拽物料到"手动传递窗右"的"A01"位置时
|
||||
- 系统查找 `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]`
|
||||
- 获取 UUID `"3a19deae-2c7a-36f5-5e41-02c5b66feaea"`
|
||||
- 调用 Bioyond API 将物料入库到该 UUID 位置
|
||||
|
||||
---
|
||||
|
||||
## 实际配置案例
|
||||
|
||||
### 案例:手动传递窗左/右的完整配置
|
||||
|
||||
本案例展示如何为"手动传递窗右"和"手动传递窗左"建立完整的三层映射。
|
||||
|
||||
#### 背景需求
|
||||
- **手动传递窗右**: 需要 A01-E03(5行×3列=15个库位)
|
||||
- **手动传递窗左**: 需要 F01-J03(5行×3列=15个库位)
|
||||
- 这两个仓库共享同一个物理堆栈的 UUID("手动堆栈")
|
||||
|
||||
#### 实施步骤
|
||||
|
||||
**1️⃣ 修复前端布局** - [`YB_warehouses.py`](YB_warehouses.py)
|
||||
|
||||
```python
|
||||
# 创建新的 5×3×1 仓库函数(之前是错误的 1×3×3)
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建5行×3列×1层仓库,支持行偏移生成不同字母行"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行 ← 修正
|
||||
num_items_z=1, # 1层 ← 修正
|
||||
row_offset=row_offset, # ← 支持 F-J 行
|
||||
layout="row-major", # ← 避免上下颠倒
|
||||
)
|
||||
```
|
||||
|
||||
**2️⃣ 更新 Deck 配置** - [`decks.py`](decks.py)
|
||||
|
||||
```python
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_5x3x1, # 新增导入
|
||||
)
|
||||
|
||||
class BIOYOND_YB_Deck(Deck):
|
||||
def setup(self) -> None:
|
||||
self.warehouses = {
|
||||
# 修改前: bioyond_warehouse_1x3x3 (错误尺寸)
|
||||
# 修改后: bioyond_warehouse_5x3x1 (正确尺寸)
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
|
||||
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
|
||||
}
|
||||
```
|
||||
|
||||
**3️⃣ 添加 UUID 映射** - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
# 保持原有的"手动堆栈"配置不变(A01-J03共30个库位)
|
||||
"手动堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
# ... A02-E03 共15个
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||
# ... F02-J03 共15个
|
||||
}
|
||||
},
|
||||
|
||||
# [新增] 手动传递窗右 - 复用"手动堆栈"的 A01-E03 UUID
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea", # ← 与手动堆栈A01相同
|
||||
"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-J03 UUID
|
||||
"手动传递窗左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a", # ← 与手动堆栈F01相同
|
||||
"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"
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 关键要点
|
||||
|
||||
1. **UUID 可以复用**: 三个仓库(手动堆栈、手动传递窗右、手动传递窗左)可以共享相同的物理库位 UUID
|
||||
2. **库位名称必须匹配**: 前端生成的库位名称(如 F01)必须与 config.py 中的键名完全一致
|
||||
3. **row_offset 的妙用**:
|
||||
- `row_offset=0` → 生成 A-E 行
|
||||
- `row_offset=5` → 生成 F-J 行(跳过前5个字母)
|
||||
|
||||
#### 验证结果
|
||||
|
||||
配置完成后,拖拽测试:
|
||||
|
||||
| 拖拽位置 | 前端库位 | 查找路径 | UUID | 结果 |
|
||||
|---------|---------|---------|------|------|
|
||||
| 手动传递窗右/A01 | A01 | `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 正确入库 |
|
||||
| 手动传递窗左/F01 | F01 | `WAREHOUSE_MAPPING["手动传递窗左"]["site_uuids"]["F01"]` | `3a19...c4a` | ✅ 正确入库 |
|
||||
| 手动堆栈/A01 | A01 | `WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 仍然正常 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## warehouse_factory 详解
|
||||
|
||||
### 函数签名
|
||||
|
||||
```python
|
||||
def warehouse_factory(
|
||||
name: str,
|
||||
num_items_x: int = 1, # 列数
|
||||
num_items_y: int = 4, # 行数
|
||||
num_items_z: int = 4, # 层数
|
||||
dx: float = 137.0, # X 起始偏移
|
||||
dy: float = 96.0, # Y 起始偏移
|
||||
dz: float = 120.0, # Z 起始偏移
|
||||
item_dx: float = 10.0, # X 间距
|
||||
item_dy: float = 10.0, # Y 间距
|
||||
item_dz: float = 10.0, # Z 间距
|
||||
col_offset: int = 0, # 列偏移(影响数字)
|
||||
row_offset: int = 0, # 行偏移(影响字母)
|
||||
layout: str = "col-major", # 布局模式
|
||||
) -> WareHouse:
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
#### 尺寸参数
|
||||
- **num_items_x, y, z**: 定义仓库的网格尺寸
|
||||
- **注意**: 当 `num_items_z > 1` 时,Z 轴会被映射为数字列
|
||||
|
||||
#### 位置参数
|
||||
- **dx, dy, dz**: 第一个库位的起始坐标
|
||||
- **item_dx, dy, dz**: 库位之间的间距
|
||||
|
||||
#### 偏移参数
|
||||
- **col_offset**: 列起始偏移,用于生成 A05-D08 等命名
|
||||
```python
|
||||
col_offset=4 # 生成 A05, A06, A07, A08
|
||||
```
|
||||
|
||||
- **row_offset**: 行起始偏移,用于生成 F01-J03 等命名
|
||||
```python
|
||||
row_offset=5 # 生成 F01, F02, F03(跳过 A-E)
|
||||
```
|
||||
|
||||
#### 布局参数
|
||||
- **layout**:
|
||||
- `"col-major"`: 列优先(默认),可能导致上下颠倒
|
||||
- `"row-major"`: 行优先,**推荐使用**,A 显示在上
|
||||
|
||||
### 库位生成逻辑
|
||||
|
||||
```python
|
||||
# row-major 模式(推荐)
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}"
|
||||
for j in range(num_y)
|
||||
for i in range(num_x)]
|
||||
|
||||
# 示例:num_y=2, num_x=3, row_offset=0, col_offset=0
|
||||
# 生成:A01, A02, A03, B01, B02, B03
|
||||
```
|
||||
|
||||
### Y 坐标计算
|
||||
|
||||
```python
|
||||
if layout == "row-major":
|
||||
# A 在上(Y 较小)
|
||||
y = dy + row * item_dy
|
||||
else:
|
||||
# A 在下(Y 较大)- 不推荐
|
||||
y = dy + (num_items_y - row - 1) * item_dy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 创建新仓库
|
||||
|
||||
### 步骤 1: 在 YB_warehouses.py 中创建函数
|
||||
|
||||
```python
|
||||
def bioyond_warehouse_3x4x1(name: str) -> WareHouse:
|
||||
"""创建 3行×4列×1层 仓库
|
||||
|
||||
布局:
|
||||
A01 | A02 | A03 | A04
|
||||
B01 | B02 | B03 | B04
|
||||
C01 | C02 | C03 | C04
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列
|
||||
num_items_y=3, # 3行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # ⭐ 推荐使用
|
||||
)
|
||||
```
|
||||
|
||||
### 步骤 2: 在 decks.py 中使用
|
||||
|
||||
```python
|
||||
# 1. 导入函数
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_3x4x1, # 新增
|
||||
)
|
||||
|
||||
# 2. 在 setup() 中添加
|
||||
self.warehouses = {
|
||||
"我的新仓库": bioyond_warehouse_3x4x1("我的新仓库"),
|
||||
}
|
||||
self.warehouse_locations = {
|
||||
"我的新仓库": Coordinate(100.0, 200.0, 0.0),
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 3: 在 config.py 中配置 UUID(可选)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
"我的新仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "从 Bioyond 系统获取的 UUID",
|
||||
"A02": "从 Bioyond 系统获取的 UUID",
|
||||
# ... 其他 11 个库位
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 如果不需要拖拽入库功能,可跳过此步骤。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么库位显示上下颠倒(C 在上,A 在下)?
|
||||
|
||||
**原因:** 使用了默认的 `col-major` 布局。
|
||||
|
||||
**解决:** 在 `warehouse_factory` 中添加 `layout="row-major"`
|
||||
|
||||
```python
|
||||
return warehouse_factory(
|
||||
...
|
||||
layout="row-major", # ← 添加这行
|
||||
)
|
||||
```
|
||||
|
||||
### Q2: 我需要 1×3×3 还是 3×3×1?
|
||||
|
||||
**判断方法:**
|
||||
- **1×3×3**: 1列×3行×3**层**(垂直堆叠,有高度)
|
||||
- **3×3×1**: 3行×3列×1**层**(平面网格)
|
||||
|
||||
**推荐:** 大多数情况使用 `X×Y×1`(平面网格)更直观。
|
||||
|
||||
### Q3: 如何生成 F01-J03 而非 A01-E03?
|
||||
|
||||
**方法:** 使用 `row_offset` 参数
|
||||
|
||||
```python
|
||||
bioyond_warehouse_5x3x1("仓库名", row_offset=5)
|
||||
# row_offset=5 跳过 A-E,从 F 开始
|
||||
```
|
||||
|
||||
### Q4: 拖拽物料后找不到 UUID 怎么办?
|
||||
|
||||
**检查清单:**
|
||||
1. `config.py` 中是否有该仓库的配置?
|
||||
2. 仓库名称是否完全匹配?
|
||||
3. 库位名称(如 A01)是否在 `site_uuids` 中?
|
||||
|
||||
**示例错误:**
|
||||
```python
|
||||
# decks.py
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1(...)
|
||||
|
||||
# config.py - ❌ 名称不匹配
|
||||
"手动传递窗": { ... } # 缺少"右"字
|
||||
```
|
||||
|
||||
### Q5: 库位重叠怎么办?
|
||||
|
||||
**原因:** 间距(`item_dx/dy/dz`)太小。
|
||||
|
||||
**解决:** 增大间距参数
|
||||
|
||||
```python
|
||||
item_dx=150.0, # 增大 X 间距
|
||||
item_dy=130.0, # 增大 Y 间距
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 查看生成的库位
|
||||
|
||||
```python
|
||||
warehouse = bioyond_warehouse_5x3x1("测试仓库")
|
||||
print(list(warehouse.sites.keys()))
|
||||
# 输出:['A01', 'A02', 'A03', 'B01', 'B02', ...]
|
||||
```
|
||||
|
||||
### 2. 检查库位坐标
|
||||
|
||||
```python
|
||||
for name, site in warehouse.sites.items():
|
||||
print(f"{name}: {site.location}")
|
||||
# 输出:
|
||||
# A01: Coordinate(x=10.0, y=10.0, z=120.0)
|
||||
# A02: Coordinate(x=147.0, y=10.0, z=120.0)
|
||||
# ...
|
||||
```
|
||||
|
||||
### 3. 验证 UUID 映射
|
||||
|
||||
```python
|
||||
from unilabos.devices.workstation.bioyond_studio.config import WAREHOUSE_MAPPING
|
||||
|
||||
warehouse_name = "手动传递窗右"
|
||||
location_code = "A01"
|
||||
|
||||
if warehouse_name in WAREHOUSE_MAPPING:
|
||||
uuid = WAREHOUSE_MAPPING[warehouse_name]["site_uuids"].get(location_code)
|
||||
print(f"{warehouse_name}/{location_code} → {uuid}")
|
||||
else:
|
||||
print(f"❌ 未找到仓库: {warehouse_name}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件关系图
|
||||
|
||||
```
|
||||
unilabos/
|
||||
├── resources/
|
||||
│ ├── warehouse.py # warehouse_factory 核心实现
|
||||
│ └── bioyond/
|
||||
│ ├── YB_warehouses.py # ⭐ 仓库函数定义
|
||||
│ ├── decks.py # ⭐ Deck 布局配置
|
||||
│ └── README_WAREHOUSE.md # 📖 本文档
|
||||
└── devices/
|
||||
└── workstation/
|
||||
└── bioyond_studio/
|
||||
├── config.py # ⭐ UUID 映射配置
|
||||
└── bioyond_cell/
|
||||
└── bioyond_cell_workstation.py # 业务逻辑
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本历史
|
||||
|
||||
- **v1.1** (2026-01-08): 补充实际配置案例
|
||||
- 添加"手动传递窗右"和"手动传递窗左"的完整配置示例
|
||||
- 展示 UUID 复用的实际应用
|
||||
- 说明三个仓库共享物理堆栈的配置方法
|
||||
|
||||
- **v1.0** (2026-01-07): 初始版本
|
||||
- 新增 `row_offset` 参数支持
|
||||
- 创建 `bioyond_warehouse_5x3x1` 和 `bioyond_warehouse_2x2x1`
|
||||
- 修复多个仓库的上下颠倒问题
|
||||
|
||||
---
|
||||
|
||||
## 相关资源
|
||||
|
||||
- [warehouse.py](../warehouse.py) - 核心工厂函数实现
|
||||
- [YB_warehouses.py](YB_warehouses.py) - 所有仓库定义
|
||||
- [decks.py](decks.py) - Deck 布局配置
|
||||
- [config.py](../../devices/workstation/bioyond_studio/config.py) - UUID 映射
|
||||
|
||||
---
|
||||
|
||||
**维护者:** Uni-Lab-OS 开发团队
|
||||
**最后更新:** 2026-01-07
|
||||
@@ -1,13 +1,79 @@
|
||||
from unilabos.resources.warehouse import WareHouse, YB_warehouse_factory
|
||||
from unilabos.resources.warehouse import WareHouse, warehouse_factory
|
||||
|
||||
# ================ 反应站相关堆栈 ================
|
||||
|
||||
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""创建BioYond 4x4x1仓库 (左侧堆栈: A01~D04)
|
||||
|
||||
使用行优先排序,前端展示为:
|
||||
A01 | A02 | A03 | A04
|
||||
B01 | B02 | B03 | B04
|
||||
C01 | C02 | C03 | C04
|
||||
D01 | D02 | D03 | D04
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
num_items_x=4, # 4列
|
||||
num_items_y=4, # 4行
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
category="warehouse",
|
||||
col_offset=0, # 从01开始: A01, A02, A03, A04
|
||||
layout="row-major", # ⭐ 改为行优先排序
|
||||
)
|
||||
|
||||
def bioyond_warehouse_1x4x4_right(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x4x1仓库 (右侧堆栈: A05~D08)"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4,
|
||||
num_items_y=4,
|
||||
num_items_z=4,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
category="warehouse",
|
||||
col_offset=4, # 从05开始: A05, A06, A07, A08
|
||||
layout="row-major", # ⭐ 改为行优先排序
|
||||
)
|
||||
|
||||
def bioyond_warehouse_density_vial(name: str) -> WareHouse:
|
||||
"""创建测量小瓶仓库(测密度) A01~B03"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列(01-03)
|
||||
num_items_y=2, # 2行(A-B)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=40.0,
|
||||
item_dy=40.0,
|
||||
item_dz=50.0,
|
||||
# 用更小的 resource_size 来表现 "小点的孔位"
|
||||
resource_size_x=30.0,
|
||||
resource_size_y=30.0,
|
||||
resource_size_z=12.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
layout="row-major",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_reagent_storage(name: str) -> WareHouse:
|
||||
"""创建BioYond站内试剂存放堆栈(A01~A02, 1行×2列)"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2, # 2列(01-02)
|
||||
num_items_y=1, # 1行(A)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
@@ -17,10 +83,74 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_tipbox_storage(name: str) -> WareHouse:
|
||||
"""创建BioYond站内Tip盒堆栈(A01~B03),用于存放枪头盒"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列(01-03)
|
||||
num_items_y=2, # 2行(A-B)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
layout="row-major",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_liquid_preparation(name: str) -> WareHouse:
|
||||
"""已弃用,创建BioYond移液站内10%分装液体准备仓库(A01~B04)"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列(01-04)
|
||||
num_items_y=2, # 2行(A-B)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
layout="row-major",
|
||||
)
|
||||
|
||||
# ================ 配液站相关堆栈 ================
|
||||
|
||||
def bioyond_warehouse_reagent_stack(name: str) -> WareHouse:
|
||||
"""创建BioYond 试剂堆栈 2x4x1 (2行×4列: A01-A04, B01-B04)
|
||||
|
||||
使用行优先排序,前端展示为:
|
||||
A01 | A02 | A03 | A04
|
||||
B01 | B02 | B03 | B04
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列 (01-04)
|
||||
num_items_y=2, # 2行 (A-B)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
category="warehouse",
|
||||
col_offset=0, # 从01开始
|
||||
layout="row-major", # ⭐ 使用行优先排序: A01,A02,A03,A04, B01,B02,B03,B04
|
||||
)
|
||||
|
||||
# 定义bioyond的堆栈
|
||||
|
||||
# =================== Other ===================
|
||||
|
||||
def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x2仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""创建BioYond 4x2x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
num_items_y=4,
|
||||
@@ -34,42 +164,57 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||
category="warehouse",
|
||||
removed_positions=None
|
||||
)
|
||||
# 定义benyond的堆栈
|
||||
|
||||
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""创建BioYond 1x2x2仓库(1列×2行×2层)- 旧版本,已弃用
|
||||
|
||||
布局(2层):
|
||||
层1: A01
|
||||
B01
|
||||
层2: A02
|
||||
B02
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_x=1,
|
||||
num_items_y=2,
|
||||
num_items_z=1,
|
||||
num_items_z=2,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="YB_warehouse",
|
||||
category="warehouse",
|
||||
layout="row-major", # 使用行优先避免上下颠倒
|
||||
)
|
||||
|
||||
def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 2x2x1仓库(自动堆栈)"""
|
||||
return YB_warehouse_factory(
|
||||
"""创建BioYond 2x2x1仓库(2行×2列×1层)
|
||||
|
||||
布局:
|
||||
A01 | A02
|
||||
B01 | B02
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
num_items_z=1,
|
||||
num_items_x=2, # 2列
|
||||
num_items_y=2, # 2行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="YB_warehouse",
|
||||
category="warehouse",
|
||||
layout="row-major", # 使用行优先避免上下颠倒
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""创建BioYond 10x1x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=10,
|
||||
num_items_y=1,
|
||||
@@ -82,9 +227,10 @@ def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""创建BioYond 1x3x3仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
num_items_y=3,
|
||||
@@ -93,13 +239,64 @@ def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0, # 增大Y方向间距以避免重叠
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建BioYond 5x3x1仓库(5行×3列×1层)
|
||||
|
||||
标准布局(row_offset=0):
|
||||
A01 | A02 | A03
|
||||
B01 | B02 | B03
|
||||
C01 | C02 | C03
|
||||
D01 | D02 | D03
|
||||
E01 | E02 | E03
|
||||
|
||||
带偏移布局(row_offset=5):
|
||||
F01 | F02 | F03
|
||||
G01 | G02 | G03
|
||||
H01 | H02 | H03
|
||||
I01 | I02 | I03
|
||||
J01 | J02 | J03
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
row_offset=row_offset, # 支持行偏移
|
||||
layout="row-major", # 使用行优先避免颠倒
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 3x3x1仓库"""
|
||||
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_2x1x3(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""创建BioYond 2x1x3仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_y=1,
|
||||
@@ -114,8 +311,8 @@ def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
|
||||
)
|
||||
|
||||
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""创建BioYond 3x3x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=3,
|
||||
@@ -128,9 +325,10 @@ def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""已弃用:创建BioYond 5x1x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=5,
|
||||
num_items_y=1,
|
||||
@@ -143,9 +341,10 @@ def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x4仓库"""
|
||||
return YB_warehouse_factory(
|
||||
"""已弃用:创建BioYond 3x3x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=3,
|
||||
@@ -161,7 +360,7 @@ def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
|
||||
|
||||
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
||||
"""创建BioYond开关盖加液模块台面"""
|
||||
return YB_warehouse_factory(
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_y=5,
|
||||
@@ -176,34 +375,18 @@ def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
||||
removed_positions=None
|
||||
)
|
||||
|
||||
def bioyond_warehouse_3x5x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 3x5x1仓库(手动堆栈)"""
|
||||
return YB_warehouse_factory(
|
||||
def bioyond_warehouse_1x8x4(name: str) -> WareHouse:
|
||||
"""创建BioYond 8x4x1反应站堆栈(A01~D08)"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=5,
|
||||
num_items_z=1,
|
||||
num_items_x=8, # 8列(01-08)
|
||||
num_items_y=4, # 4行(A-D)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_20x1x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 20x1x1仓库(粉末加样头堆栈)"""
|
||||
return YB_warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=20,
|
||||
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",
|
||||
)
|
||||
@@ -1,14 +1,16 @@
|
||||
from os import name
|
||||
from pylabrobot.resources import Deck, Coordinate, Rotation
|
||||
|
||||
from unilabos.resources.bioyond.warehouses import (
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_1x4x4,
|
||||
bioyond_warehouse_1x4x4_right, # 新增:右侧仓库 (A05~D08)
|
||||
bioyond_warehouse_1x4x2,
|
||||
bioyond_warehouse_reagent_stack, # 新增:试剂堆栈 (A1-B4)
|
||||
bioyond_warehouse_liquid_and_lid_handling,
|
||||
bioyond_warehouse_1x2x2,
|
||||
bioyond_warehouse_2x2x1, # 新增:321和43窗口 (2行×2列)
|
||||
bioyond_warehouse_1x3x3,
|
||||
bioyond_warehouse_5x3x1, # 新增:手动传递窗仓库 (5行×3列)
|
||||
bioyond_warehouse_10x1x1,
|
||||
bioyond_warehouse_3x3x1,
|
||||
bioyond_warehouse_3x3x1_2,
|
||||
@@ -115,10 +117,10 @@ class BIOYOND_YB_Deck(Deck):
|
||||
def setup(self) -> None:
|
||||
# 添加仓库
|
||||
self.warehouses = {
|
||||
"321窗口": bioyond_warehouse_1x2x2("321窗口"),
|
||||
"43窗口": bioyond_warehouse_1x2x2("43窗口"),
|
||||
"手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"),
|
||||
"手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"),
|
||||
"321窗口": bioyond_warehouse_2x2x1("321窗口"), # 2行×2列
|
||||
"43窗口": bioyond_warehouse_2x2x1("43窗口"), # 2行×2列
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
|
||||
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
|
||||
"加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"),
|
||||
"加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"),
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ def warehouse_factory(
|
||||
category: str = "warehouse",
|
||||
model: Optional[str] = None,
|
||||
col_offset: int = 0, # 列起始偏移量,用于生成A05-D08等命名
|
||||
row_offset: int = 0, # 行起始偏移量,用于生成F01-J03等命名
|
||||
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
|
||||
):
|
||||
# 创建位置坐标
|
||||
@@ -65,10 +66,10 @@ def warehouse_factory(
|
||||
if layout == "row-major":
|
||||
# 行优先顺序: A01,A02,A03,A04, B01,B02,B03,B04
|
||||
# locations[0] 对应 row=0, y最大(前端顶部)→ 应该是 A01
|
||||
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
|
||||
else:
|
||||
# 列优先顺序: A01,B01,C01,D01, A02,B02,C02,D02
|
||||
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
|
||||
|
||||
sites = {i: site for i, site in zip(keys, _sites.values())}
|
||||
|
||||
|
||||
@@ -404,6 +404,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
|
||||
return result_future.result
|
||||
|
||||
"""还没有改过的部分"""
|
||||
|
||||
def _setup_hardware_proxy(
|
||||
self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method
|
||||
|
||||
34
unilabos/test/experiments/chinwe.json
Normal file
34
unilabos/test/experiments/chinwe.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "ChinWeStation",
|
||||
"name": "分液工作站",
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "separator.chinwe",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "192.168.31.13:8899",
|
||||
"baudrate": 9600,
|
||||
"pump_ids": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"motor_ids": [
|
||||
4,
|
||||
5
|
||||
],
|
||||
"sensor_id": 6,
|
||||
"sensor_threshold": 300
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
Reference in New Issue
Block a user