mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 21:35:09 +00:00
Compare commits
16 Commits
workstatio
...
a64ccfdb50
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a64ccfdb50 | ||
|
|
9bd6e83a8c | ||
|
|
3b16210b45 | ||
|
|
907a40b9d7 | ||
|
|
915a6a04c3 | ||
|
|
48b51c3a4a | ||
|
|
acef0b8ca2 | ||
|
|
97788b4e07 | ||
|
|
39cc280c91 | ||
|
|
d2a30fe33b | ||
|
|
096875e910 | ||
|
|
ee4ed26846 | ||
|
|
19dffcb5db | ||
|
|
b441362cd2 | ||
|
|
ed53ef2f64 | ||
|
|
0c9f26e8fc |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -249,4 +249,3 @@ ros-humble-unilabos-msgs-0.9.13-h6403a04_5.tar.bz2
|
|||||||
test_config.py
|
test_config.py
|
||||||
|
|
||||||
|
|
||||||
Uni-Lab-OS/
|
|
||||||
|
|||||||
@@ -1,101 +1,95 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "bioyond_cell_workstation",
|
"id": "bioyond_cell_workstation",
|
||||||
"name": "配液分液工站",
|
"name": "配液分液工站",
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"children": [
|
"children": [
|
||||||
"YB_Bioyond_Deck"
|
"YB_Bioyond_Deck"
|
||||||
],
|
],
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "bioyond_cell",
|
"class": "bioyond_cell",
|
||||||
"config": {
|
"config": {
|
||||||
"deck": {
|
"deck": {
|
||||||
"data": {
|
"data": {
|
||||||
"_resource_child_name": "YB_Bioyond_Deck",
|
"_resource_child_name": "YB_Bioyond_Deck",
|
||||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
|
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"protocol_type": []
|
|
||||||
},
|
},
|
||||||
"data": {}
|
"protocol_type": []
|
||||||
},
|
},
|
||||||
{
|
"data": {}
|
||||||
"id": "YB_Bioyond_Deck",
|
},
|
||||||
"name": "YB_Bioyond_Deck",
|
{
|
||||||
"children": [],
|
"id": "YB_Bioyond_Deck",
|
||||||
"parent": "bioyond_cell_workstation",
|
"name": "YB_Bioyond_Deck",
|
||||||
"type": "deck",
|
"children": [],
|
||||||
"class": "BIOYOND_YB_Deck",
|
"parent": "bioyond_cell_workstation",
|
||||||
"position": {
|
"type": "deck",
|
||||||
|
"class": "BIOYOND_YB_Deck",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "BIOYOND_YB_Deck",
|
||||||
|
"setup": true,
|
||||||
|
"rotation": {
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"z": 0
|
"z": 0,
|
||||||
},
|
"type": "Rotation"
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
"data": {}
|
||||||
"id": "YB_YH_Deck",
|
},
|
||||||
"name": "YB_YH_Deck",
|
{
|
||||||
"children": [],
|
"id": "BatteryStation",
|
||||||
"parent": "BatteryStation",
|
"name": "扣电工作站",
|
||||||
"type": "deck",
|
"parent": null,
|
||||||
"class": "CoincellDeck",
|
"children": [
|
||||||
"config": {
|
"coin_cell_deck"
|
||||||
"type": "CoincellDeck",
|
],
|
||||||
"setup": true,
|
"type": "device",
|
||||||
"rotation": {
|
"class":"coincellassemblyworkstation_device",
|
||||||
"x": 0,
|
"config": {
|
||||||
"y": 0,
|
"deck": {
|
||||||
"z": 0,
|
"data": {
|
||||||
"type": "Rotation"
|
"_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)
|
graph, resource_tree_set, resource_links = read_node_link_json(request_startup_json)
|
||||||
else:
|
else:
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
# 尝试从 main.py 向上两级目录查找
|
|
||||||
temp_file_path = os.path.abspath(str(os.path.join(__file__, "..", "..", file_path)))
|
temp_file_path = os.path.abspath(str(os.path.join(__file__, "..", "..", file_path)))
|
||||||
if os.path.isfile(temp_file_path):
|
if os.path.isfile(temp_file_path):
|
||||||
print_status(f"使用相对路径{temp_file_path}", "info")
|
print_status(f"使用相对路径{temp_file_path}", "info")
|
||||||
file_path = temp_file_path
|
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"):
|
if file_path.endswith(".json"):
|
||||||
graph, resource_tree_set, resource_links = read_node_link_json(file_path)
|
graph, resource_tree_set, resource_links = read_node_link_json(file_path)
|
||||||
else:
|
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
|
- 状态类型: working/stop/finish/protect/pause/false/unknown
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import socket
|
import socket
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import json
|
import json
|
||||||
@@ -23,6 +21,7 @@ from dataclasses import dataclass
|
|||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, Dict, List, Optional, TypedDict
|
||||||
|
|
||||||
from pylabrobot.resources import ResourceHolder, Coordinate, create_ordered_items_2d, Deck, Plate
|
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.base_device_node import ROS2DeviceNode
|
||||||
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
||||||
|
|
||||||
@@ -57,6 +56,13 @@ class BatteryTestPositionState(TypedDict):
|
|||||||
status: str # 通道状态
|
status: str # 通道状态
|
||||||
color: 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):
|
class BatteryTestPosition(ResourceHolder):
|
||||||
@@ -136,9 +142,9 @@ class NewareBatteryTestSystem:
|
|||||||
devtype: str = None,
|
devtype: str = None,
|
||||||
timeout: int = None,
|
timeout: int = None,
|
||||||
|
|
||||||
size_x: float = 50,
|
size_x: float = 500.0,
|
||||||
size_y: float = 50,
|
size_y: float = 500.0,
|
||||||
size_z: float = 20,
|
size_z: float = 2000.0,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
初始化新威电池测试系统
|
初始化新威电池测试系统
|
||||||
@@ -156,12 +162,6 @@ class NewareBatteryTestSystem:
|
|||||||
self.machine_id = machine_id
|
self.machine_id = machine_id
|
||||||
self.devtype = devtype or self.DEVTYPE
|
self.devtype = devtype or self.DEVTYPE
|
||||||
self.timeout = timeout or self.TIMEOUT
|
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._last_status_update = None
|
||||||
self._cached_status = {}
|
self._cached_status = {}
|
||||||
self._ros_node: Optional[ROS2WorkstationNode] = None # ROS节点引用,由框架设置
|
self._ros_node: Optional[ROS2WorkstationNode] = None # ROS节点引用,由框架设置
|
||||||
@@ -192,9 +192,8 @@ class NewareBatteryTestSystem:
|
|||||||
def _setup_material_management(self):
|
def _setup_material_management(self):
|
||||||
"""设置物料管理系统"""
|
"""设置物料管理系统"""
|
||||||
# 第1盘:5行8列网格 (A1-E8) - 5行对应subdevid 1-5,8列对应chlid 1-8
|
# 第1盘:5行8列网格 (A1-E8) - 5行对应subdevid 1-5,8列对应chlid 1-8
|
||||||
# 先给物料设置一个最大的Deck,并设置其在空间中的位置
|
# 先给物料设置一个最大的Deck
|
||||||
|
deck_main = Deck("ADeckName", 200, 200, 200)
|
||||||
deck_main = Deck("ADeckName", 2000, 1800, 100, origin=Coordinate(2000,2000,0))
|
|
||||||
|
|
||||||
plate1_resources: Dict[str, BatteryTestPosition] = create_ordered_items_2d(
|
plate1_resources: Dict[str, BatteryTestPosition] = create_ordered_items_2d(
|
||||||
BatteryTestPosition,
|
BatteryTestPosition,
|
||||||
@@ -203,8 +202,8 @@ class NewareBatteryTestSystem:
|
|||||||
dx=10,
|
dx=10,
|
||||||
dy=10,
|
dy=10,
|
||||||
dz=0,
|
dz=0,
|
||||||
item_dx=65,
|
item_dx=45,
|
||||||
item_dy=65
|
item_dy=45
|
||||||
)
|
)
|
||||||
plate1 = Plate("P1", 400, 300, 50, ordered_items=plate1_resources)
|
plate1 = Plate("P1", 400, 300, 50, ordered_items=plate1_resources)
|
||||||
deck_main.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
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)
|
num_items_y=5, # 5行(对应subdevid 6-10,即A-E)
|
||||||
dx=10,
|
dx=10,
|
||||||
dy=10,
|
dy=10,
|
||||||
dz=0,
|
dz=100, # Z轴偏移100mm
|
||||||
item_dx=65,
|
item_dx=65,
|
||||||
item_dy=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_前缀
|
# 为第2盘资源添加P2_前缀
|
||||||
self.station_resources_plate2 = {}
|
self.station_resources_plate2 = {}
|
||||||
for name, resource in plate2_resources.items():
|
for name, resource in plate2_resources.items():
|
||||||
@@ -311,132 +306,55 @@ class NewareBatteryTestSystem:
|
|||||||
|
|
||||||
def _update_plate_resources(self, subunits: Dict):
|
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
|
for subdev_id in range(1, 6): # subdevid 1-5
|
||||||
status_row = subunits.get(subdev_id, {})
|
status_row = subunits.get(subdev_id, {})
|
||||||
|
|
||||||
for chl_id in range(1, 9): # chlid 1-8
|
for chl_id in range(1, 9): # chlid 1-8
|
||||||
try:
|
try:
|
||||||
# 根据用户描述:第一个是(0,0),最后一个是(7,4)
|
# 计算在5×8网格中的位置
|
||||||
# 说明是8列5行,列从0开始,行从0开始
|
row_idx = (subdev_id - 1) # 0-4 (对应A-E)
|
||||||
col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 列0-7)
|
col_idx = (chl_id - 1) # 0-7 (对应1-8)
|
||||||
row_idx = (subdev_id - 1) # 0-4 (subdevid 1-5 -> 行0-4)
|
resource_name = f"P1_{self.LETTERS[row_idx]}{col_idx + 1}"
|
||||||
|
|
||||||
# 尝试多种可能的资源命名格式
|
|
||||||
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
|
|
||||||
|
|
||||||
|
r = self.station_resources.get(resource_name)
|
||||||
if r:
|
if r:
|
||||||
status_channel = status_row.get(chl_id, {})
|
status_channel = status_row.get(chl_id, {})
|
||||||
metrics = status_channel.get("metrics", {})
|
|
||||||
# 构建BatteryTestPosition状态数据(移除capacity和energy)
|
|
||||||
channel_state = {
|
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"),
|
"status": status_channel.get("state", "unknown"),
|
||||||
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
||||||
|
"voltage": status_channel.get("voltage_V", 0.0),
|
||||||
# 通道名称标识
|
"current": status_channel.get("current_A", 0.0),
|
||||||
"Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
|
"time": status_channel.get("totaltime_s", 0.0),
|
||||||
|
|
||||||
}
|
}
|
||||||
r.load_state(channel_state)
|
r.load_state(channel_state)
|
||||||
|
except (KeyError, IndexError):
|
||||||
# 调试信息
|
|
||||||
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}")
|
|
||||||
continue
|
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
|
for subdev_id in range(6, 11): # subdevid 6-10
|
||||||
status_row = subunits.get(subdev_id, {})
|
status_row = subunits.get(subdev_id, {})
|
||||||
|
|
||||||
for chl_id in range(1, 9): # chlid 1-8
|
for chl_id in range(1, 9): # chlid 1-8
|
||||||
try:
|
try:
|
||||||
col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 列0-7)
|
# 计算在5×8网格中的位置
|
||||||
row_idx = (subdev_id - 6) # 0-4 (subdevid 6-10 -> 行0-4)
|
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}"
|
||||||
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
|
|
||||||
|
|
||||||
|
r = self.station_resources.get(resource_name)
|
||||||
if r:
|
if r:
|
||||||
status_channel = status_row.get(chl_id, {})
|
status_channel = status_row.get(chl_id, {})
|
||||||
metrics = status_channel.get("metrics", {})
|
|
||||||
# 构建BatteryTestPosition状态数据(移除capacity和energy)
|
|
||||||
channel_state = {
|
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"),
|
"status": status_channel.get("state", "unknown"),
|
||||||
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
||||||
|
"voltage": status_channel.get("voltage_V", 0.0),
|
||||||
# 通道名称标识
|
"current": status_channel.get("current_A", 0.0),
|
||||||
"Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
|
"time": status_channel.get("totaltime_s", 0.0),
|
||||||
|
|
||||||
}
|
}
|
||||||
r.load_state(channel_state)
|
r.load_state(channel_state)
|
||||||
|
except (KeyError, IndexError):
|
||||||
# 调试信息
|
|
||||||
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}")
|
|
||||||
continue
|
continue
|
||||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
|
||||||
"resources": list(self.station_resources.values())
|
|
||||||
})
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connection_info(self) -> Dict[str, str]:
|
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:
|
except Exception as e:
|
||||||
print(f" 获取状态失败: {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:
|
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
|
# -*- coding: utf-8 -*-
|
||||||
import threading
|
"""
|
||||||
import serial
|
Contains drivers for:
|
||||||
import serial.tools.list_ports
|
1. SyringePump: Runze Fluid SY-03B (ASCII)
|
||||||
import re
|
2. EmmMotor: Emm V5.0 Closed-loop Stepper (Modbus-RTU variant)
|
||||||
import time
|
3. XKCSensor: XKC Non-contact Level Sensor (Modbus-RTU)
|
||||||
from typing import Optional, List, Dict, Tuple
|
"""
|
||||||
|
|
||||||
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 = 9600, timeout: float = 3.0, logger=None):
|
||||||
def __init__(self, port: str, baudrate: int = 115200, debug: bool = False):
|
|
||||||
"""
|
|
||||||
初始化ChinWe设备
|
|
||||||
|
|
||||||
Args:
|
|
||||||
port: 串口名称,如果为None则自动检测
|
|
||||||
baudrate: 波特率,默认115200
|
|
||||||
"""
|
|
||||||
self.debug = debug
|
|
||||||
self.port = port
|
self.port = port
|
||||||
self.baudrate = baudrate
|
self.baudrate = baudrate
|
||||||
self.serial_port: Optional[serial.Serial] = None
|
self.timeout = timeout
|
||||||
self._voltage: float = 0.0
|
self.logger = logger
|
||||||
self._ec_value: float = 0.0
|
self.lock = threading.RLock() # 线程锁,确保多设备共用一个连接时不冲突
|
||||||
self._ec_adc_value: int = 0
|
|
||||||
|
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._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
|
@property
|
||||||
def is_connected(self) -> bool:
|
def is_connected(self) -> bool:
|
||||||
"""获取连接状态"""
|
return self._is_connected
|
||||||
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
|
|
||||||
|
|
||||||
@property
|
# --- 对外功能指令 (Actions) ---
|
||||||
def ec_adc_value(self) -> int:
|
|
||||||
"""获取EC ADC原始值"""
|
|
||||||
return self._ec_adc_value
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
def pump_initialize(self, pump_id: int, drain_port=0, output_port=0, speed=10):
|
||||||
def device_status(self) -> Dict[str, any]:
|
"""指定泵初始化"""
|
||||||
"""
|
pump_id = int(pump_id)
|
||||||
获取设备状态信息
|
if pump_id in self.pumps:
|
||||||
|
self.pumps[pump_id].initialize(drain_port, output_port, speed)
|
||||||
Returns:
|
self.pumps[pump_id].wait_until_idle()
|
||||||
包含设备状态的字典
|
|
||||||
"""
|
|
||||||
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:
|
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
target_port = port or self.port
|
|
||||||
target_baudrate = baudrate or self.baudrate
|
def pump_aspirate(self, pump_id: int, volume: int, valve_port: int):
|
||||||
|
"""
|
||||||
try:
|
泵吸液 (阻塞)
|
||||||
self.serial_port = serial.Serial(target_port, target_baudrate, timeout=0.5)
|
:param valve_port: 阀门端口 (1-8)
|
||||||
self._is_connected = True
|
"""
|
||||||
self.port = target_port
|
pump_id = int(pump_id)
|
||||||
self.baudrate = target_baudrate
|
valve_port = int(valve_port)
|
||||||
connect_allow_times = 5
|
if pump_id in self.pumps:
|
||||||
while not self.serial_port.is_open and connect_allow_times > 0:
|
pump = self.pumps[pump_id]
|
||||||
time.sleep(0.5)
|
# 1. 切换阀门
|
||||||
connect_allow_times -= 1
|
pump.switch_valve(valve_port)
|
||||||
print(f"尝试连接到 {target_port} @ {target_baudrate},剩余尝试次数: {connect_allow_times}", self.debug)
|
pump.wait_until_idle()
|
||||||
raise ValueError("串口未打开,请检查设备连接")
|
# 2. 吸液
|
||||||
print(f"已连接到 {target_port} @ {target_baudrate}", self.debug)
|
pump.aspirate(volume)
|
||||||
threading.Thread(target=self._read_data, daemon=True).start()
|
pump.wait_until_idle()
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
return False
|
||||||
print(f"ChinweDevice连接失败: {e}")
|
|
||||||
self._is_connected = False
|
def pump_dispense(self, pump_id: int, volume: int, valve_port: int):
|
||||||
return False
|
|
||||||
|
|
||||||
def disconnect(self) -> bool:
|
|
||||||
"""
|
"""
|
||||||
断开串口连接
|
泵排液 (阻塞)
|
||||||
|
:param valve_port: 阀门端口 (1-8)
|
||||||
Returns:
|
|
||||||
断开是否成功
|
|
||||||
"""
|
"""
|
||||||
if self.serial_port and self.serial_port.is_open:
|
pump_id = int(pump_id)
|
||||||
try:
|
valve_port = int(valve_port)
|
||||||
self.serial_port.close()
|
if pump_id in self.pumps:
|
||||||
self._is_connected = False
|
pump = self.pumps[pump_id]
|
||||||
print("已断开串口连接")
|
# 1. 切换阀门
|
||||||
return True
|
pump.switch_valve(valve_port)
|
||||||
except Exception as e:
|
pump.wait_until_idle()
|
||||||
print(f"断开连接失败: {e}")
|
# 2. 排液
|
||||||
return False
|
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
|
return True
|
||||||
|
|
||||||
def _send_motor_command(self, command: str) -> bool:
|
def motor_rotate_quarter(self, motor_id: int, speed: int = 60, direction: str = "顺时针"):
|
||||||
"""
|
"""
|
||||||
发送电机控制命令
|
电机旋转1/4圈 (阻塞)
|
||||||
|
假设电机设置为 3200 脉冲/圈,1/4圈 = 800脉冲
|
||||||
Args:
|
|
||||||
command: 电机命令字符串,例如 "M 1 CW 1.5"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
发送是否成功
|
|
||||||
"""
|
"""
|
||||||
if not self.is_connected:
|
motor_id = int(motor_id)
|
||||||
print("设备未连接")
|
if motor_id not in self.motors: return False
|
||||||
return False
|
|
||||||
|
pulses = 800
|
||||||
try:
|
dir_val = 0 if direction == "顺时针" else 1
|
||||||
self.serial_port.write((command + "\n").encode('utf-8'))
|
|
||||||
print(f"发送命令: {command}")
|
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
|
return True
|
||||||
except Exception as e:
|
return False
|
||||||
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)
|
|
||||||
|
|
||||||
def set_motor_speed(self, motor_id: int, speed: float) -> bool:
|
def wait_sensor_level(self, target_state: str = "有液", timeout: int = 30) -> bool:
|
||||||
"""
|
"""
|
||||||
设置电机转速(如果设备支持)
|
等待传感器达到指定电平
|
||||||
|
:param target_state: "有液" or "无液"
|
||||||
Args:
|
|
||||||
motor_id: 电机ID(1, 2, 3...)
|
|
||||||
speed: 转速值
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
命令发送是否成功
|
|
||||||
"""
|
"""
|
||||||
command = f"M {motor_id} SPEED {speed}"
|
target_bool = True if target_state == "有液" else False
|
||||||
return self._send_motor_command(command)
|
|
||||||
|
|
||||||
def _read_data(self) -> List[str]:
|
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:
|
||||||
Returns:
|
return True
|
||||||
读取到的数据行列表
|
time.sleep(0.1)
|
||||||
"""
|
self.logger.warning("Wait sensor level timeout")
|
||||||
print("开始读取串口数据...")
|
return False
|
||||||
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
|
|
||||||
|
|
||||||
# 解析电导率和ADC原始值(支持两种格式)
|
def wait_time(self, duration: int) -> bool:
|
||||||
if "电导率" in line and "ADC原始值" in line:
|
"""
|
||||||
try:
|
等待指定时间 (秒)
|
||||||
# 支持格式如:电导率:2.50ms/cm, ADC原始值:2052
|
:param duration: 秒
|
||||||
ec_match = re.search(r"电导率[::]\s*([\d\.]+)", line)
|
"""
|
||||||
adc_match = re.search(r"ADC原始值[::]\s*(\d+)", line)
|
self.logger.info(f"Waiting for {duration} seconds...")
|
||||||
if ec_match:
|
time.sleep(duration)
|
||||||
ec_val = float(ec_match.group(1))
|
return True
|
||||||
self._ec_value = ec_val
|
|
||||||
if self.debug:
|
def execute_command_from_outer(self, command_dict: Dict[str, Any]) -> bool:
|
||||||
print(f"电导率更新: {ec_val:.2f} ms/cm")
|
"""支持标准 JSON 指令调用"""
|
||||||
if adc_match:
|
return super().execute_command_from_outer(command_dict)
|
||||||
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 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__":
|
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.
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from cgi import print_arguments
|
from cgi import print_arguments
|
||||||
from doctest import debug
|
from doctest import debug
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional
|
||||||
import requests
|
import requests
|
||||||
from pylabrobot.resources.resource import Resource as ResourcePLR
|
from pylabrobot.resources.resource import Resource as ResourcePLR
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -19,22 +19,8 @@ from unilabos.devices.workstation.bioyond_studio.config import (
|
|||||||
)
|
)
|
||||||
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
||||||
from unilabos.resources.bioyond.decks import BIOYOND_YB_Deck
|
from unilabos.resources.bioyond.decks import BIOYOND_YB_Deck
|
||||||
from unilabos.resources.graphio import resource_bioyond_to_plr
|
|
||||||
from unilabos.utils.log import logger
|
from unilabos.utils.log import logger
|
||||||
from unilabos.registry.registry import lab_registry
|
from unilabos.registry.registry import lab_registry
|
||||||
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class device(BIOYOND_YB_Deck):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def deserialize(cls, data, allow_marshal=False): # type: ignore[override]
|
|
||||||
patched = dict(data)
|
|
||||||
if patched.get("type") == "device":
|
|
||||||
patched["type"] = "Deck"
|
|
||||||
if patched.get("category") == "device":
|
|
||||||
patched["category"] = "deck"
|
|
||||||
return super().deserialize(patched, allow_marshal=allow_marshal)
|
|
||||||
|
|
||||||
def _iso_local_now_ms() -> str:
|
def _iso_local_now_ms() -> str:
|
||||||
# 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z
|
# 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z
|
||||||
@@ -54,14 +40,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
|
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
|
||||||
|
|
||||||
# 使用统一配置,支持自定义覆盖, 从 config.py 加载完整配置
|
# 使用统一配置,支持自定义覆盖, 从 config.py 加载完整配置
|
||||||
self.bioyond_config = {
|
self.bioyond_config ={
|
||||||
**API_CONFIG,
|
**API_CONFIG,
|
||||||
"material_type_mappings": MATERIAL_TYPE_MAPPINGS,
|
"material_type_mappings": MATERIAL_TYPE_MAPPINGS,
|
||||||
"warehouse_mapping": WAREHOUSE_MAPPING,
|
"warehouse_mapping": WAREHOUSE_MAPPING,
|
||||||
"debug_mode": False,
|
"debug_mode": False
|
||||||
}
|
}
|
||||||
if config:
|
|
||||||
self.bioyond_config.update(config)
|
|
||||||
|
|
||||||
# "material_type_mappings": MATERIAL_TYPE_MAPPINGS
|
# "material_type_mappings": MATERIAL_TYPE_MAPPINGS
|
||||||
# "warehouse_mapping": WAREHOUSE_MAPPING
|
# "warehouse_mapping": WAREHOUSE_MAPPING
|
||||||
@@ -72,12 +56,6 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
self.http_service_started = self.debug_mode
|
self.http_service_started = self.debug_mode
|
||||||
self._device_id = "bioyond_cell_workstation" # 默认值,后续会从_ros_node获取
|
self._device_id = "bioyond_cell_workstation" # 默认值,后续会从_ros_node获取
|
||||||
super().__init__(bioyond_config=config, deck=deck)
|
super().__init__(bioyond_config=config, deck=deck)
|
||||||
self.transfer_target_device_id = self.bioyond_config.get("transfer_target_device_id", "BatteryStation")
|
|
||||||
self.transfer_target_parent = self.bioyond_config.get("transfer_target_parent", "YB_YH_Deck")
|
|
||||||
self.transfer_timeout = float(self.bioyond_config.get("transfer_timeout", 180.0))
|
|
||||||
self.coin_cell_workflow_config = self.bioyond_config.get("coin_cell_workflow_config", {})
|
|
||||||
self.pending_transfer_materials: List[Dict[str, Any]] = []
|
|
||||||
self.pending_transfer_plr: List[ResourcePLR] = []
|
|
||||||
self.update_push_ip() #直接修改奔耀端的报送ip地址
|
self.update_push_ip() #直接修改奔耀端的报送ip地址
|
||||||
logger.info("已更新奔耀端推送 IP 地址")
|
logger.info("已更新奔耀端推送 IP 地址")
|
||||||
|
|
||||||
@@ -279,7 +257,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
def auto_feeding4to3(
|
def auto_feeding4to3(
|
||||||
self,
|
self,
|
||||||
# ★ 修改点:默认模板路径
|
# ★ 修改点:默认模板路径
|
||||||
xlsx_path: Optional[str] = "/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx",
|
xlsx_path: Optional[str] = "/Users/sml/work/Unilab/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx",
|
||||||
# ---------------- WH4 - 加样头面 (Z=1, 12个点位) ----------------
|
# ---------------- WH4 - 加样头面 (Z=1, 12个点位) ----------------
|
||||||
WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0,
|
WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0,
|
||||||
WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0,
|
WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0,
|
||||||
@@ -346,7 +324,6 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
|
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
|
||||||
"materialName": str(row[5]).strip(),
|
"materialName": str(row[5]).strip(),
|
||||||
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
||||||
"temperature": 0,
|
|
||||||
})
|
})
|
||||||
# 四号手套箱原液瓶面
|
# 四号手套箱原液瓶面
|
||||||
for _, row in df.iloc[14:23, 2:9].iterrows():
|
for _, row in df.iloc[14:23, 2:9].iterrows():
|
||||||
@@ -358,7 +335,6 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
||||||
"materialType": str(row[7]).strip() if pd.notna(row[7]) else "",
|
"materialType": str(row[7]).strip() if pd.notna(row[7]) else "",
|
||||||
"targetWH": str(row[8]).strip() if pd.notna(row[8]) else "",
|
"targetWH": str(row[8]).strip() if pd.notna(row[8]) else "",
|
||||||
"temperature": 0,
|
|
||||||
})
|
})
|
||||||
# 三号手套箱人工堆栈
|
# 三号手套箱人工堆栈
|
||||||
for _, row in df.iloc[25:40, 2:7].iterrows():
|
for _, row in df.iloc[25:40, 2:7].iterrows():
|
||||||
@@ -368,12 +344,11 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
|
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
|
||||||
"materialType": str(row[5]).strip() if pd.notna(row[5]) else "",
|
"materialType": str(row[5]).strip() if pd.notna(row[5]) else "",
|
||||||
"materialId": str(row[6]).strip() if pd.notna(row[6]) else "",
|
"materialId": str(row[6]).strip() if pd.notna(row[6]) else "",
|
||||||
"quantity": 1,
|
"quantity": 1
|
||||||
"temperature": 0,
|
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。")
|
logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。")
|
||||||
# TODO: 温度下面手动模式没改,上面的改了
|
|
||||||
# ---------- 模式 2: 手动填写 ----------
|
# ---------- 模式 2: 手动填写 ----------
|
||||||
if not items:
|
if not items:
|
||||||
params = locals()
|
params = locals()
|
||||||
@@ -416,16 +391,16 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
order_code = response.get("data", {}).get("orderCode")
|
order_code = response.get("data", {}).get("orderCode")
|
||||||
if not order_code:
|
if not order_code:
|
||||||
logger.error("上料任务未返回有效 orderCode!")
|
logger.error("上料任务未返回有效 orderCode!")
|
||||||
return {"api_response": response, "order_finish": None}
|
return response
|
||||||
# 等待完成报送
|
# 等待完成报送
|
||||||
result = self.wait_for_order_finish(order_code)
|
result = self.wait_for_order_finish(order_code)
|
||||||
return {
|
print("\n" + "="*60)
|
||||||
"api_response": response,
|
print("实验记录本结果auto_feeding4to3")
|
||||||
"order_finish": result,
|
print("="*60)
|
||||||
"items": items,
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||||
}
|
print("="*60 + "\n")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
|
def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
3.31 自动化下料(Excel -> JSON -> POST /api/lims/storage/auto-batch-out-bound)
|
3.31 自动化下料(Excel -> JSON -> POST /api/lims/storage/auto-batch-out-bound)
|
||||||
@@ -494,7 +469,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
# 2.14 新建实验
|
# 2.14 新建实验
|
||||||
def create_orders(self, xlsx_path: str, *, material_filter: Optional[str] = None) -> Dict[str, Any]:
|
def create_orders(self, xlsx_path: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
从 Excel 解析并创建实验(2.14)
|
从 Excel 解析并创建实验(2.14)
|
||||||
约定:
|
约定:
|
||||||
@@ -503,7 +478,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
- totalMass 自动计算为所有物料质量之和
|
- totalMass 自动计算为所有物料质量之和
|
||||||
- createTime 缺失或为空时自动填充为当前日期(YYYY/M/D)
|
- createTime 缺失或为空时自动填充为当前日期(YYYY/M/D)
|
||||||
"""
|
"""
|
||||||
default_path = Path("/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx")
|
default_path = Path("/Users/sml/work/Unilab/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx")
|
||||||
path = Path(xlsx_path) if xlsx_path else default_path
|
path = Path(xlsx_path) if xlsx_path else default_path
|
||||||
print(f"[create_orders] 使用 Excel 路径: {path}")
|
print(f"[create_orders] 使用 Excel 路径: {path}")
|
||||||
if path != default_path:
|
if path != default_path:
|
||||||
@@ -649,36 +624,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
if not order_code:
|
if not order_code:
|
||||||
logger.error("上料任务未返回有效 orderCode!")
|
logger.error("上料任务未返回有效 orderCode!")
|
||||||
return response
|
return response
|
||||||
# 等待完成报送
|
# 等待完成报送
|
||||||
result = self.wait_for_order_finish(order_code)
|
result = self.wait_for_order_finish(order_code)
|
||||||
report_data = result.get("report") if isinstance(result, dict) else None
|
print("实验记录本========================create_orders========================")
|
||||||
materials_from_report = (
|
print(result)
|
||||||
report_data.get("usedMaterials") if isinstance(report_data, dict) else None
|
print("========================")
|
||||||
)
|
return result
|
||||||
if materials_from_report:
|
|
||||||
materials = materials_from_report
|
|
||||||
logger.info(
|
|
||||||
"[create_orders] 使用订单完成报送中的物料信息: "
|
|
||||||
f"{len(materials)} 条"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
materials = self._fetch_bioyond_materials(filter_keyword=material_filter)
|
|
||||||
logger.info(
|
|
||||||
"[create_orders] 未收到订单报送物料信息,回退到实时查询"
|
|
||||||
)
|
|
||||||
print("materials_from_report:", materials_from_report)
|
|
||||||
# TODO: 需要将 materials 字典转换为 ResourceSlot 对象后才能转运
|
|
||||||
# self.transfer_resource_to_another(
|
|
||||||
# resource=[materials],
|
|
||||||
# mount_resource=["YB_YH_Deck"],
|
|
||||||
# sites=[None],
|
|
||||||
# mount_device_id="BatteryStation"
|
|
||||||
# )
|
|
||||||
return {
|
|
||||||
"api_response": response,
|
|
||||||
"order_finish": result,
|
|
||||||
"materials": materials,
|
|
||||||
}
|
|
||||||
|
|
||||||
# 2.7 启动调度
|
# 2.7 启动调度
|
||||||
def scheduler_start(self) -> Dict[str, Any]:
|
def scheduler_start(self) -> Dict[str, Any]:
|
||||||
@@ -750,7 +701,6 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
return response
|
return response
|
||||||
# 等待完成报送
|
# 等待完成报送
|
||||||
result = self.wait_for_order_finish(order_code)
|
result = self.wait_for_order_finish(order_code)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 2.5 批量查询实验报告(post过滤关键字查询)
|
# 2.5 批量查询实验报告(post过滤关键字查询)
|
||||||
@@ -1219,221 +1169,28 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
})
|
})
|
||||||
return final_result
|
return final_result
|
||||||
|
|
||||||
def _fetch_bioyond_materials(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
filter_keyword: Optional[str] = None,
|
|
||||||
type_mode: int = 2,
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
query: Dict[str, Any] = {
|
|
||||||
"typeMode": type_mode,
|
|
||||||
"includeDetail": True,
|
|
||||||
}
|
|
||||||
if filter_keyword:
|
|
||||||
query["filter"] = filter_keyword
|
|
||||||
|
|
||||||
response = self._post_lims("/api/lims/storage/stock-material", query)
|
|
||||||
raw_materials = response.get("data")
|
|
||||||
if not isinstance(raw_materials, list):
|
|
||||||
raw_materials = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
resource_bioyond_to_plr(
|
|
||||||
raw_materials,
|
|
||||||
type_mapping=self.bioyond_config.get("material_type_mappings", MATERIAL_TYPE_MAPPINGS),
|
|
||||||
deck=self.deck,
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(f"转换奔曜物料到 PLR 失败: {exc}", exc_info=True)
|
|
||||||
|
|
||||||
return raw_materials
|
|
||||||
|
|
||||||
def _convert_materials_to_plr(self, materials: List[Dict[str, Any]]) -> List[ResourcePLR]:
|
|
||||||
try:
|
|
||||||
return resource_bioyond_to_plr(
|
|
||||||
deepcopy(materials),
|
|
||||||
type_mapping=self.bioyond_config.get("material_type_mappings", MATERIAL_TYPE_MAPPINGS),
|
|
||||||
deck=self.deck,
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(f"物料转换为 PLR 失败: {exc}", exc_info=True)
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _wait_for_future(self, future, stage: str, timeout: Optional[float] = None):
|
|
||||||
if future is None:
|
|
||||||
return None
|
|
||||||
timeout = timeout or self.transfer_timeout
|
|
||||||
start = time.time()
|
|
||||||
while not future.done():
|
|
||||||
if (time.time() - start) > timeout:
|
|
||||||
raise TimeoutError(f"{stage} 超时 {timeout}s")
|
|
||||||
time.sleep(0.05)
|
|
||||||
return future.result()
|
|
||||||
|
|
||||||
def _register_plr_resources(self, resources: List[ResourcePLR]) -> None:
|
|
||||||
if not resources or not hasattr(self, "_ros_node") or self._ros_node is None:
|
|
||||||
return
|
|
||||||
future = ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, resources=resources)
|
|
||||||
self._wait_for_future(future, "update_resource")
|
|
||||||
|
|
||||||
def _get_target_resource(self, name: str) -> ResourcePLR:
|
|
||||||
if not hasattr(self, "_ros_node") or self._ros_node is None:
|
|
||||||
raise RuntimeError("ROS 节点未初始化,无法获取资源")
|
|
||||||
resource = self._ros_node.resource_tracker.figure_resource({"name": name}, try_mode=False) # type: ignore
|
|
||||||
if resource is None:
|
|
||||||
raise ValueError(f"未找到目标资源: {name}")
|
|
||||||
return resource
|
|
||||||
|
|
||||||
def _allocate_sites(self, parent_resource: ResourcePLR, count: int) -> List[str]:
|
|
||||||
if not hasattr(parent_resource, "get_free_sites"):
|
|
||||||
raise ValueError(f"资源 {parent_resource} 不支持自动分配站位")
|
|
||||||
free_indices = list(parent_resource.get_free_sites())
|
|
||||||
if len(free_indices) < count:
|
|
||||||
raise ValueError(f"{parent_resource.name} 可用站位不足 (need {count}, have {len(free_indices)})")
|
|
||||||
ordering = list(getattr(parent_resource, "_ordering", {}).keys())
|
|
||||||
sites: List[str] = []
|
|
||||||
for idx in free_indices[:count]:
|
|
||||||
if ordering and idx < len(ordering):
|
|
||||||
sites.append(ordering[idx])
|
|
||||||
else:
|
|
||||||
sites.append(str(idx))
|
|
||||||
return sites
|
|
||||||
|
|
||||||
def _invoke_coin_cell_workflow(self, material_payload: List[Dict[str, Any]]) -> Any:
|
|
||||||
timeout = float(self.bioyond_config.get("coin_cell_workflow_timeout", 300.0))
|
|
||||||
workflow_payload: Dict[str, Any] = {}
|
|
||||||
if isinstance(self.coin_cell_workflow_config, dict):
|
|
||||||
workflow_payload.update(deepcopy(self.coin_cell_workflow_config))
|
|
||||||
workflow_payload["materials"] = deepcopy(material_payload)
|
|
||||||
return self._call_remote_device_method(
|
|
||||||
self.transfer_target_device_id,
|
|
||||||
"run_coin_cell_assembly_workflow",
|
|
||||||
timeout=timeout,
|
|
||||||
workflow_config=workflow_payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _call_remote_device_method(
|
|
||||||
self,
|
|
||||||
device_id: str,
|
|
||||||
method: str,
|
|
||||||
*,
|
|
||||||
timeout: Optional[float] = None,
|
|
||||||
**kwargs,
|
|
||||||
) -> Any:
|
|
||||||
if not hasattr(self, "_ros_node") or self._ros_node is None:
|
|
||||||
raise RuntimeError("ROS 节点未初始化,无法调用远程设备")
|
|
||||||
if not device_id:
|
|
||||||
raise ValueError("device_id 不能为空")
|
|
||||||
if not method:
|
|
||||||
raise ValueError("method 不能为空")
|
|
||||||
|
|
||||||
timeout = timeout or self.transfer_timeout
|
|
||||||
payload = json.dumps(
|
|
||||||
{
|
|
||||||
"function_name": method,
|
|
||||||
"function_args": kwargs,
|
|
||||||
},
|
|
||||||
ensure_ascii=False,
|
|
||||||
)
|
|
||||||
future = ROS2DeviceNode.run_async_func(
|
|
||||||
self._ros_node.execute_single_action,
|
|
||||||
True,
|
|
||||||
device_id=device_id,
|
|
||||||
action_name="_execute_driver_command_async",
|
|
||||||
action_kwargs={"string": payload},
|
|
||||||
)
|
|
||||||
result = self._wait_for_future(future, f"{device_id}.{method}", timeout)
|
|
||||||
if hasattr(result, "return_info"):
|
|
||||||
try:
|
|
||||||
return json.loads(result.return_info)
|
|
||||||
except Exception:
|
|
||||||
return result.return_info
|
|
||||||
return result
|
|
||||||
|
|
||||||
def run_feeding_stage(self) -> Dict[str, Any]:
|
|
||||||
self.create_sample(
|
|
||||||
board_type="配液瓶(小)板",
|
|
||||||
bottle_type="配液瓶(小)",
|
|
||||||
location_code="B01",
|
|
||||||
name="配液瓶",
|
|
||||||
warehouse_name="手动堆栈"
|
|
||||||
)
|
|
||||||
self.create_sample(
|
|
||||||
board_type="5ml分液瓶板",
|
|
||||||
bottle_type="5ml分液瓶",
|
|
||||||
location_code="B02",
|
|
||||||
name="分液瓶",
|
|
||||||
warehouse_name="手动堆栈"
|
|
||||||
)
|
|
||||||
self.scheduler_start()
|
|
||||||
feeding_task = self.auto_feeding4to3(
|
|
||||||
xlsx_path="/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx"
|
|
||||||
)
|
|
||||||
feeding_materials = self._fetch_bioyond_materials()
|
|
||||||
return {
|
|
||||||
"feeding_materials": feeding_materials,
|
|
||||||
"feeding_items": feeding_task.get("items", []),
|
|
||||||
"feeding_task": feeding_task,
|
|
||||||
}
|
|
||||||
|
|
||||||
def run_liquid_preparation_stage(
|
|
||||||
self,
|
|
||||||
feeding_materials: Optional[List[Dict[str, Any]]] = None,
|
|
||||||
) -> Dict[str, List[Dict[str, Any]]]:
|
|
||||||
result = self.create_orders(
|
|
||||||
xlsx_path="/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx"
|
|
||||||
)
|
|
||||||
filter_keyword = self.bioyond_config.get("mixing_material_filter") or None
|
|
||||||
materials = result.get("materials")
|
|
||||||
if materials is None:
|
|
||||||
materials = self._fetch_bioyond_materials(filter_keyword=filter_keyword)
|
|
||||||
return {
|
|
||||||
"feeding_materials": feeding_materials or [],
|
|
||||||
"liquid_materials": materials,
|
|
||||||
}
|
|
||||||
|
|
||||||
def run_transfer_stage(
|
|
||||||
self,
|
|
||||||
liquid_materials: Optional[List[Dict[str, Any]]] = None,
|
|
||||||
source_wh_id: Optional[str] = '3a19debc-84b4-0359-e2d4-b3beea49348b',
|
|
||||||
source_x: int = 1,
|
|
||||||
source_y: int = 1,
|
|
||||||
source_z: int = 1
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""转运阶段:调用transfer_3_to_2_to_1执行3到2到1转运"""
|
|
||||||
logger.info("开始执行转运阶段 (run_transfer_stage)")
|
|
||||||
|
|
||||||
# 暂时注释掉物料转换和跨工站转运逻辑
|
|
||||||
# transfer_summary: Dict[str, Any] = {}
|
|
||||||
# try:
|
|
||||||
# source_materials = liquid_materials or self._fetch_bioyond_materials()
|
|
||||||
# transfer_plr = self._convert_materials_to_plr(source_materials)
|
|
||||||
# transfer_summary["plr_count"] = len(transfer_plr)
|
|
||||||
# ...
|
|
||||||
# except Exception as exc:
|
|
||||||
# transfer_summary["error"] = str(exc)
|
|
||||||
# logger.error(f"跨工站转运失败: {exc}", exc_info=True)
|
|
||||||
|
|
||||||
# 只执行核心的3到2到1转运
|
|
||||||
transfer_result = self.transfer_3_to_2_to_1(
|
|
||||||
source_wh_id=source_wh_id,
|
|
||||||
source_x=source_x,
|
|
||||||
source_y=source_y,
|
|
||||||
source_z=source_z
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("转运阶段执行完成")
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"stage": "transfer",
|
|
||||||
"transfer_result": transfer_result
|
|
||||||
}
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
lab_registry.setup()
|
||||||
deck = BIOYOND_YB_Deck(setup=True)
|
deck = BIOYOND_YB_Deck(setup=True)
|
||||||
w = BioyondCellWorkstation(deck=deck, address="172.16.28.102", port="502", debug_mode=False)
|
ws = BioyondCellWorkstation(deck=deck)
|
||||||
feeding = w.run_feeding_stage()
|
# ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01")
|
||||||
liquid = w.run_liquid_preparation_stage(feeding.get("feeding_materials"))
|
# logger.info(ws.scheduler_stop())
|
||||||
transfer = w.run_transfer_stage(liquid.get("liquid_materials"))
|
# logger.info(ws.scheduler_start())
|
||||||
|
|
||||||
|
# 继续后续流程
|
||||||
|
logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱
|
||||||
|
# # # 使用正斜杠或 Path 对象来指定文件路径
|
||||||
|
# excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx")
|
||||||
|
# logger.info(ws.create_orders(excel_path))
|
||||||
|
# logger.info(ws.transfer_3_to_2_to_1())
|
||||||
|
|
||||||
|
# logger.info(ws.transfer_1_to_2())
|
||||||
|
# logger.info(ws.scheduler_start())
|
||||||
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
# re=ws.scheduler_stop()
|
# re=ws.scheduler_stop()
|
||||||
|
|||||||
Binary file not shown.
@@ -8,7 +8,7 @@ import os
|
|||||||
# BioyondCellWorkstation 默认配置(包含所有必需参数)
|
# BioyondCellWorkstation 默认配置(包含所有必需参数)
|
||||||
API_CONFIG = {
|
API_CONFIG = {
|
||||||
# API 连接配置
|
# 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.118: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.219:44388"),# 仿真机
|
||||||
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
|
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
|
||||||
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
|
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
|
||||||
@@ -17,7 +17,7 @@ API_CONFIG = {
|
|||||||
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
||||||
|
|
||||||
# HTTP 服务配置
|
# HTTP 服务配置
|
||||||
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.6"), # HTTP服务监听地址,监听计算机飞连ip地址
|
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.10.148"), # HTTP服务监听地址,监听计算机飞连ip地址
|
||||||
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
||||||
"debug_mode": False,# 调试模式
|
"debug_mode": False,# 调试模式
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -634,6 +634,12 @@ class CoincellDeck(Deck):
|
|||||||
self.assign_child_resource(waste_tip_box, Coordinate(x=778.0, y=622.0, z=0))
|
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__":
|
if __name__ == "__main__":
|
||||||
deck = create_coin_cell_deck()
|
deck = create_coin_cell_deck()
|
||||||
print(deck)
|
print(deck)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import csv
|
import csv
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
@@ -143,6 +144,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
else:
|
else:
|
||||||
print("测试模式,跳过连接")
|
print("测试模式,跳过连接")
|
||||||
self.nodes, self.client = None, None
|
self.nodes, self.client = None, None
|
||||||
|
|
||||||
""" 工站的配置 """
|
""" 工站的配置 """
|
||||||
|
|
||||||
self.success = False
|
self.success = False
|
||||||
@@ -159,27 +161,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
"resources": [self.deck]
|
"resources": [self.deck]
|
||||||
})
|
})
|
||||||
|
|
||||||
def sync_transfer_resources(self) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
供跨工站转运完成后调用,强制将当前台面资源同步到云端/前端。
|
|
||||||
"""
|
|
||||||
if not hasattr(self, "_ros_node") or self._ros_node is None:
|
|
||||||
return {"status": "failed", "error": "ros_node_not_ready"}
|
|
||||||
if self.deck is None:
|
|
||||||
return {"status": "failed", "error": "deck_not_initialized"}
|
|
||||||
try:
|
|
||||||
future = ROS2DeviceNode.run_async_func(
|
|
||||||
self._ros_node.update_resource,
|
|
||||||
True,
|
|
||||||
resources=[self.deck],
|
|
||||||
)
|
|
||||||
if future:
|
|
||||||
future.result()
|
|
||||||
return {"status": "success"}
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(f"同步转运资源失败: {exc}", exc_info=True)
|
|
||||||
return {"status": "failed", "error": str(exc)}
|
|
||||||
|
|
||||||
# 批量操作在这里写
|
# 批量操作在这里写
|
||||||
async def change_hole_sheet_to_2(self, hole: MaterialHole):
|
async def change_hole_sheet_to_2(self, hole: MaterialHole):
|
||||||
hole._unilabos_state["max_sheets"] = 2
|
hole._unilabos_state["max_sheets"] = 2
|
||||||
@@ -1005,31 +986,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
#self.success = True
|
#self.success = True
|
||||||
#return self.success
|
#return self.success
|
||||||
|
|
||||||
def run_packaging_workflow(self, workflow_config: Dict[str, Any]) -> "CoinCellAssemblyWorkstation":
|
|
||||||
config = workflow_config or {}
|
|
||||||
|
|
||||||
qiming_params = config.get("qiming") or {}
|
|
||||||
if qiming_params:
|
|
||||||
self.qiming_coin_cell_code(**qiming_params)
|
|
||||||
|
|
||||||
if config.get("init", True):
|
|
||||||
self.func_pack_device_init()
|
|
||||||
if config.get("auto", True):
|
|
||||||
self.func_pack_device_auto()
|
|
||||||
if config.get("start", True):
|
|
||||||
self.func_pack_device_start()
|
|
||||||
|
|
||||||
packaging_config = config.get("packaging") or {}
|
|
||||||
bottle_num = packaging_config.get("bottle_num")
|
|
||||||
if bottle_num is not None:
|
|
||||||
self.func_pack_send_bottle_num(bottle_num)
|
|
||||||
|
|
||||||
allpack_params = packaging_config.get("command") or {}
|
|
||||||
if allpack_params:
|
|
||||||
self.func_allpack_cmd(**allpack_params)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def fun_wuliao_test(self) -> bool:
|
def fun_wuliao_test(self) -> bool:
|
||||||
#找到data_init中构建的2个物料盘
|
#找到data_init中构建的2个物料盘
|
||||||
liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8")
|
liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8")
|
||||||
@@ -1243,95 +1199,20 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
"""移液枪头库存 (数量, INT16)"""
|
"""移液枪头库存 (数量, INT16)"""
|
||||||
inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1)
|
inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1)
|
||||||
return inventory
|
return inventory
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def run_coin_cell_assembly_workflow(
|
|
||||||
self,
|
|
||||||
workflow_config: Optional[Dict[str, Any]] = None,
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
config: Dict[str, Any]
|
|
||||||
if workflow_config is None:
|
|
||||||
config = {}
|
|
||||||
elif isinstance(workflow_config, list):
|
|
||||||
config = {"materials": workflow_config}
|
|
||||||
else:
|
|
||||||
config = workflow_config
|
|
||||||
qiming_defaults = {
|
|
||||||
"fujipian_panshu": 1,
|
|
||||||
"fujipian_juzhendianwei": 0,
|
|
||||||
"gemopanshu": 1,
|
|
||||||
"gemo_juzhendianwei": 0,
|
|
||||||
"lvbodian": True,
|
|
||||||
"battery_pressure_mode": True,
|
|
||||||
"battery_pressure": 4200,
|
|
||||||
"battery_clean_ignore": False,
|
|
||||||
}
|
|
||||||
qiming_params = {**qiming_defaults, **(config.get("qiming") or {})}
|
|
||||||
qiming_success = self.qiming_coin_cell_code(**qiming_params)
|
|
||||||
|
|
||||||
step_results: Dict[str, Any] = {}
|
|
||||||
try:
|
|
||||||
self.func_pack_device_init()
|
|
||||||
step_results["init"] = True
|
|
||||||
except Exception as exc:
|
|
||||||
step_results["init"] = f"error: {exc}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.func_pack_device_auto()
|
|
||||||
step_results["auto"] = True
|
|
||||||
except Exception as exc:
|
|
||||||
step_results["auto"] = f"error: {exc}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.func_pack_device_start()
|
|
||||||
step_results["start"] = True
|
|
||||||
except Exception as exc:
|
|
||||||
step_results["start"] = f"error: {exc}"
|
|
||||||
|
|
||||||
packaging_cfg = config.get("packaging") or {}
|
|
||||||
bottle_num = packaging_cfg.get("bottle_num", 1)
|
|
||||||
try:
|
|
||||||
self.func_pack_send_bottle_num(bottle_num)
|
|
||||||
step_results["send_bottle_num"] = True
|
|
||||||
except Exception as exc:
|
|
||||||
step_results["send_bottle_num"] = f"error: {exc}"
|
|
||||||
|
|
||||||
command_defaults = {
|
|
||||||
"elec_num": 1,
|
|
||||||
"elec_use_num": 1,
|
|
||||||
"elec_vol": 50,
|
|
||||||
"assembly_type": 7,
|
|
||||||
"assembly_pressure": 4200,
|
|
||||||
"file_path": "/Users/sml/work",
|
|
||||||
}
|
|
||||||
command_params = {**command_defaults, **(packaging_cfg.get("command") or {})}
|
|
||||||
packaging_result = self.func_allpack_cmd(**command_params)
|
|
||||||
|
|
||||||
finished_result = self.func_pack_send_finished_cmd()
|
|
||||||
stop_result = self.func_pack_device_stop()
|
|
||||||
|
|
||||||
return {
|
|
||||||
"qiming": {
|
|
||||||
"params": qiming_params,
|
|
||||||
"success": qiming_success,
|
|
||||||
},
|
|
||||||
"workflow_steps": step_results,
|
|
||||||
"packaging": {
|
|
||||||
"bottle_num": bottle_num,
|
|
||||||
"command": command_params,
|
|
||||||
"result": packaging_result,
|
|
||||||
},
|
|
||||||
"finish": {
|
|
||||||
"send_finished": finished_result,
|
|
||||||
"stop": stop_result,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
deck = CoincellDeck(setup=True, name="coin_cell_deck")
|
# 简单测试
|
||||||
w = CoinCellAssemblyWorkstation(deck=deck, address="172.16.28.102", port="502", debug_mode=False)
|
workstation = CoinCellAssemblyWorkstation(deck=CoincellDeck(setup=True, name="coin_cell_deck"))
|
||||||
w.run_coin_cell_assembly_workflow()
|
# workstation.qiming_coin_cell_code(fujipian_panshu=1, fujipian_juzhendianwei=2, gemopanshu=3, gemo_juzhendianwei=4, lvbodian=False, battery_pressure_mode=False, battery_pressure=4200, battery_clean_ignore=False)
|
||||||
|
# print(f"工作站创建成功: {workstation.deck.name}")
|
||||||
|
# print(f"料盘数量: {len(workstation.deck.children)}")
|
||||||
|
workstation.func_pack_device_init()
|
||||||
|
workstation.func_pack_device_auto()
|
||||||
|
workstation.func_pack_device_start()
|
||||||
|
workstation.func_pack_send_bottle_num(16)
|
||||||
|
workstation.func_allpack_cmd(elec_num=16, elec_use_num=16, elec_vol=50, assembly_type=7, assembly_pressure=4200, file_path="/Users/calvincao/Desktop/work/Uni-Lab-OS-hhm")
|
||||||
|
|
||||||
@@ -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,111 +32,6 @@ bioyond_cell:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
goal_default:
|
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: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
@@ -821,154 +716,6 @@ bioyond_cell:
|
|||||||
title: resource_tree_transfer参数
|
title: resource_tree_transfer参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
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:
|
auto-scheduler_continue:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -1260,7 +1007,7 @@ bioyond_cell:
|
|||||||
device_id: String
|
device_id: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: 配液工站
|
description: ''
|
||||||
handles: []
|
handles: []
|
||||||
icon: benyao2.webp
|
icon: benyao2.webp
|
||||||
init_param_schema:
|
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
|
||||||
@@ -477,171 +477,6 @@ coincellassemblyworkstation_device:
|
|||||||
title: qiming_coin_cell_code参数
|
title: qiming_coin_cell_code参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
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
|
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
||||||
status_types:
|
status_types:
|
||||||
data_assembly_coin_cell_num: int
|
data_assembly_coin_cell_num: int
|
||||||
@@ -665,7 +500,7 @@ coincellassemblyworkstation_device:
|
|||||||
sys_status: str
|
sys_status: str
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: 扣电工站
|
description: ''
|
||||||
handles: []
|
handles: []
|
||||||
icon: koudian.webp
|
icon: koudian.webp
|
||||||
init_param_schema:
|
init_param_schema:
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
neware_battery_test_system:
|
neware_battery_test_system:
|
||||||
category:
|
category:
|
||||||
- neware_battery_test_system
|
- neware_battery_test_system
|
||||||
- neware
|
|
||||||
- battery_test
|
|
||||||
class:
|
class:
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
auto-post_init:
|
auto-post_init:
|
||||||
@@ -72,38 +70,6 @@ neware_battery_test_system:
|
|||||||
title: test_connection参数
|
title: test_connection参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
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:
|
export_status_json:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -253,9 +219,7 @@ neware_battery_test_system:
|
|||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
result:
|
result: {}
|
||||||
return_info: return_info
|
|
||||||
success: success
|
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
@@ -288,56 +252,6 @@ neware_battery_test_system:
|
|||||||
title: StrSingleInput
|
title: StrSingleInput
|
||||||
type: object
|
type: object
|
||||||
type: StrSingleInput
|
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:
|
test_connection_action:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -370,7 +284,7 @@ neware_battery_test_system:
|
|||||||
- goal
|
- goal
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
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:
|
status_types:
|
||||||
channel_status: dict
|
channel_status: dict
|
||||||
connection_info: dict
|
connection_info: dict
|
||||||
@@ -380,7 +294,7 @@ neware_battery_test_system:
|
|||||||
total_channels: int
|
total_channels: int
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: 新威电池测试系统驱动,提供720个通道的电池测试状态监控、物料管理和CSV批量提交功能。支持TCP通信实现远程控制,包含完整的物料管理系统(2盘电池状态映射),以及从CSV文件批量提交测试任务的能力。
|
description: 新威电池测试系统驱动,支持720个通道的电池测试状态监控和数据导出。通过TCP通信实现远程控制,包含完整的物料管理系统,支持2盘电池的状态映射和监控。
|
||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema:
|
init_param_schema:
|
||||||
@@ -396,13 +310,13 @@ neware_battery_test_system:
|
|||||||
port:
|
port:
|
||||||
type: integer
|
type: integer
|
||||||
size_x:
|
size_x:
|
||||||
default: 50
|
default: 500.0
|
||||||
type: number
|
type: number
|
||||||
size_y:
|
size_y:
|
||||||
default: 50
|
default: 500.0
|
||||||
type: number
|
type: number
|
||||||
size_z:
|
size_z:
|
||||||
default: 20
|
default: 2000.0
|
||||||
type: number
|
type: number
|
||||||
timeout:
|
timeout:
|
||||||
type: integer
|
type: integer
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ BIOYOND_PolymerReactionStation_Deck:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_Deck11:
|
BIOYOND_YB_Deck:
|
||||||
category:
|
category:
|
||||||
- deck
|
- deck
|
||||||
class:
|
class:
|
||||||
@@ -34,3 +34,15 @@ YB_Deck11:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
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 @@
|
|||||||
"""
|
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
||||||
瓶架类定义 - 用于纽扣电池组装工作站
|
|
||||||
Bottle Carrier Resource Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||||
from pylabrobot.resources import ResourceHolder
|
from unilabos.resources.bioyond.YB_bottles import (
|
||||||
from pylabrobot.resources.utils import create_ordered_items_2d
|
YB_pei_ye_xiao_Bottle,
|
||||||
from unilabos.resources.itemized_carrier import ItemizedCarrier
|
)
|
||||||
|
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
||||||
|
|
||||||
|
|
||||||
def YIHUA_Electrolyte_12VialCarrier(name: str) -> ItemizedCarrier:
|
def YIHUA_Electrolyte_12VialCarrier(name: str) -> BottleCarrier:
|
||||||
"""依华电解液12瓶架 - 3x4布局
|
"""12瓶载架 - 2x6布局"""
|
||||||
|
# 载架尺寸 (mm)
|
||||||
Args:
|
carrier_size_x = 120.0
|
||||||
name: 瓶架名称
|
carrier_size_y = 250.0
|
||||||
|
carrier_size_z = 50.0
|
||||||
Returns:
|
|
||||||
ItemizedCarrier: 包含12个瓶位的瓶架
|
# 瓶位尺寸
|
||||||
"""
|
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(
|
sites = create_ordered_items_2d(
|
||||||
klass=ResourceHolder,
|
klass=ResourceHolder,
|
||||||
num_items_x=4,
|
num_items_x=2,
|
||||||
num_items_y=3,
|
num_items_y=6,
|
||||||
dx=10.0,
|
dx=start_x,
|
||||||
dy=10.0,
|
dy=start_y,
|
||||||
dz=5.0,
|
dz=5.0,
|
||||||
item_dx=70.0,
|
item_dx=bottle_spacing_x,
|
||||||
item_dy=26.67,
|
item_dy=bottle_spacing_y,
|
||||||
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",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
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 @@
|
|||||||
"""
|
from typing import Any, Dict, Optional, TypedDict
|
||||||
电极片类定义
|
|
||||||
Electrode Sheet Resource Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from pylabrobot.resources import Resource as ResourcePLR
|
||||||
from typing import Any, Dict, Optional
|
from pylabrobot.resources import Container
|
||||||
from pylabrobot.resources.resource import Resource
|
|
||||||
|
|
||||||
|
|
||||||
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str = "极片",
|
||||||
size_x: float = 12.0,
|
size_x: float = 10,
|
||||||
size_y: float = 12.0,
|
size_y: float = 10,
|
||||||
size_z: float = 0.1,
|
size_z: float = 10,
|
||||||
category: str = "electrode_sheet",
|
category: str = "electrode_sheet",
|
||||||
electrode_type: str = "anode", # "anode" 负极, "cathode" 正极, "separator" 隔膜
|
model: Optional[str] = None,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
"""初始化电极片
|
"""初始化极片
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: 电极片名称
|
name: 极片名称
|
||||||
size_x: X方向尺寸 (mm)
|
size_x: 长度 (mm)
|
||||||
size_y: Y方向尺寸 (mm)
|
size_y: 宽度 (mm)
|
||||||
size_z: Z方向尺寸/厚度 (mm)
|
size_z: 高度 (mm)
|
||||||
category: 类别
|
category: 类别
|
||||||
electrode_type: 电极类型
|
model: 型号
|
||||||
|
**kwargs: 其他参数传递给父类
|
||||||
"""
|
"""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -37,31 +54,142 @@ class ElectrodeSheet(Resource):
|
|||||||
size_y=size_y,
|
size_y=size_y,
|
||||||
size_z=size_z,
|
size_z=size_z,
|
||||||
category=category,
|
category=category,
|
||||||
|
model=model,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
self._electrode_type = electrode_type
|
self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(
|
||||||
self._unilabos_state: Dict[str, Any] = {
|
diameter=14,
|
||||||
"electrode_type": electrode_type,
|
thickness=0.1,
|
||||||
"material": "",
|
mass=0.5,
|
||||||
"thickness": size_z,
|
material_type="copper",
|
||||||
}
|
color="#8b4513",
|
||||||
|
info=None
|
||||||
@property
|
)
|
||||||
def electrode_type(self) -> str:
|
|
||||||
"""获取电极类型"""
|
# TODO: 这个还要不要?给self._unilabos_state赋值的?
|
||||||
return self._electrode_type
|
|
||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]) -> None:
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
"""加载状态"""
|
"""格式不变"""
|
||||||
super().load_state(state)
|
super().load_state(state)
|
||||||
if isinstance(state, dict):
|
self._unilabos_state = state
|
||||||
self._unilabos_state.update(state)
|
#序列化
|
||||||
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
def serialize_state(self) -> Dict[str, Any]:
|
"""格式不变"""
|
||||||
"""序列化状态"""
|
|
||||||
data = super().serialize_state()
|
data = super().serialize_state()
|
||||||
data.update(self._unilabos_state)
|
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||||
return data
|
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 @@
|
|||||||
"""
|
from typing import Dict, List, Optional, OrderedDict, Union, Callable
|
||||||
弹夹架类定义 - 用于纽扣电池组装工作站
|
import math
|
||||||
Magazine Holder Resource Classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
from typing import List, Optional
|
|
||||||
from pylabrobot.resources.coordinate import Coordinate
|
from pylabrobot.resources.coordinate import Coordinate
|
||||||
from pylabrobot.resources import ResourceHolder
|
from pylabrobot.resources import Resource, ResourceStack, ItemizedResource
|
||||||
from pylabrobot.resources.utils import create_ordered_items_2d
|
from pylabrobot.resources.carrier import create_homogeneous_resources
|
||||||
from unilabos.resources.itemized_carrier import ItemizedCarrier
|
|
||||||
|
from unilabos.resources.battery.electrode_sheet import (
|
||||||
|
PositiveCan, PositiveElectrode,
|
||||||
|
NegativeCan, NegativeElectrode,
|
||||||
|
SpringWasher, FlatWasher,
|
||||||
|
AluminumFoil,
|
||||||
|
Battery
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def MagazineHolder_4_Cathode(name: str) -> ItemizedCarrier:
|
class Magazine(ResourceStack):
|
||||||
"""正极&铝箔弹夹 - 4个洞位 (2x2布局)
|
"""子弹夹洞位类"""
|
||||||
|
|
||||||
|
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:
|
Args:
|
||||||
name: 弹夹名称
|
name: 子弹夹名称
|
||||||
|
size_x: 长度 (mm)
|
||||||
Returns:
|
size_y: 宽度 (mm)
|
||||||
ItemizedCarrier: 包含4个槽位的弹夹架
|
size_z: 高度 (mm)
|
||||||
|
locations: 洞位坐标列表
|
||||||
|
klasses: 每个洞位中极片的类列表
|
||||||
|
hole_diameter: 洞直径 (mm)
|
||||||
|
hole_depth: 洞深度 (mm)
|
||||||
|
max_sheets_per_hole: 每个洞位最大极片数量
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
"""
|
"""
|
||||||
sites = create_ordered_items_2d(
|
for loc in locations:
|
||||||
klass=ResourceHolder,
|
loc.x -= hole_diameter / 2
|
||||||
num_items_x=2,
|
loc.y -= hole_diameter / 2
|
||||||
num_items_y=2,
|
|
||||||
dx=10.0,
|
# 创建洞位
|
||||||
dy=10.0,
|
_sites = create_homogeneous_resources(
|
||||||
dz=0.0,
|
klass=Magazine,
|
||||||
item_dx=50.0,
|
locations=locations,
|
||||||
item_dy=30.0,
|
resource_size_x=hole_diameter,
|
||||||
size_x=40.0,
|
resource_size_y=hole_diameter,
|
||||||
size_y=25.0,
|
name_prefix=name,
|
||||||
size_z=40.0,
|
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,
|
name=name,
|
||||||
size_x=120.0,
|
size_x=size_x,
|
||||||
size_y=80.0,
|
size_y=size_y,
|
||||||
size_z=50.0,
|
size_z=size_z,
|
||||||
num_items_x=2,
|
ordered_items=sites,
|
||||||
num_items_y=2,
|
hole_diameter=hole_diameter,
|
||||||
sites=sites,
|
hole_depth=hole_depth,
|
||||||
category="magazine_holder",
|
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布局)
|
def MagazineHolder_6_Cathode(
|
||||||
|
name: str,
|
||||||
Args:
|
size_x: float = 80.0,
|
||||||
name: 弹夹名称
|
size_y: float = 80.0,
|
||||||
|
size_z: float = 40.0,
|
||||||
Returns:
|
hole_diameter: float = 14.0,
|
||||||
ItemizedCarrier: 包含6个槽位的弹夹架
|
hole_depth: float = 10.0,
|
||||||
"""
|
hole_spacing: float = 20.0,
|
||||||
sites = create_ordered_items_2d(
|
max_sheets_per_hole: int = 100,
|
||||||
klass=ResourceHolder,
|
) -> MagazineHolder:
|
||||||
num_items_x=3,
|
"""创建6孔子弹夹 - 六边形排布"""
|
||||||
num_items_y=2,
|
center_x = size_x / 2
|
||||||
dx=10.0,
|
center_y = size_y / 2
|
||||||
dy=10.0,
|
|
||||||
dz=0.0,
|
locations = []
|
||||||
item_dx=40.0,
|
|
||||||
item_dy=30.0,
|
# 周围6个孔,按六边形排布
|
||||||
size_x=35.0,
|
for i in range(6):
|
||||||
size_y=25.0,
|
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||||
size_z=40.0,
|
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 ItemizedCarrier(
|
|
||||||
|
return magazine_factory(
|
||||||
name=name,
|
name=name,
|
||||||
size_x=150.0,
|
size_x=size_x,
|
||||||
size_y=80.0,
|
size_y=size_y,
|
||||||
size_z=50.0,
|
size_z=size_z,
|
||||||
num_items_x=3,
|
locations=locations,
|
||||||
num_items_y=2,
|
klasses=[FlatWasher, PositiveCan, PositiveCan, FlatWasher, PositiveCan, PositiveCan],
|
||||||
sites=sites,
|
hole_diameter=hole_diameter,
|
||||||
|
hole_depth=hole_depth,
|
||||||
|
max_sheets_per_hole=max_sheets_per_hole,
|
||||||
category="magazine_holder",
|
category="magazine_holder",
|
||||||
|
model="MagazineHolder_6_Cathode",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def MagazineHolder_6_Anode(name: str) -> ItemizedCarrier:
|
def MagazineHolder_6_Anode(
|
||||||
"""负极壳&弹垫片弹夹 - 6个洞位 (2x3布局)
|
name: str,
|
||||||
|
size_x: float = 80.0,
|
||||||
Args:
|
size_y: float = 80.0,
|
||||||
name: 弹夹名称
|
size_z: float = 40.0,
|
||||||
|
hole_diameter: float = 14.0,
|
||||||
Returns:
|
hole_depth: float = 10.0,
|
||||||
ItemizedCarrier: 包含6个槽位的弹夹架
|
hole_spacing: float = 20.0,
|
||||||
"""
|
max_sheets_per_hole: int = 100,
|
||||||
sites = create_ordered_items_2d(
|
) -> MagazineHolder:
|
||||||
klass=ResourceHolder,
|
"""创建6孔子弹夹 - 六边形排布"""
|
||||||
num_items_x=3,
|
center_x = size_x / 2
|
||||||
num_items_y=2,
|
center_y = size_y / 2
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
locations = []
|
||||||
dz=0.0,
|
|
||||||
item_dx=40.0,
|
# 周围6个孔,按六边形排布
|
||||||
item_dy=30.0,
|
for i in range(6):
|
||||||
size_x=35.0,
|
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||||
size_y=25.0,
|
x = center_x + hole_spacing * math.cos(angle)
|
||||||
size_z=40.0,
|
y = center_y + hole_spacing * math.sin(angle)
|
||||||
)
|
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||||
|
|
||||||
return ItemizedCarrier(
|
return magazine_factory(
|
||||||
name=name,
|
name=name,
|
||||||
size_x=150.0,
|
size_x=size_x,
|
||||||
size_y=80.0,
|
size_y=size_y,
|
||||||
size_z=50.0,
|
size_z=size_z,
|
||||||
num_items_x=3,
|
locations=locations,
|
||||||
num_items_y=2,
|
klasses=[SpringWasher, NegativeCan, NegativeCan, SpringWasher, NegativeCan, NegativeCan],
|
||||||
sites=sites,
|
hole_diameter=hole_diameter,
|
||||||
|
hole_depth=hole_depth,
|
||||||
|
max_sheets_per_hole=max_sheets_per_hole,
|
||||||
category="magazine_holder",
|
category="magazine_holder",
|
||||||
|
model="MagazineHolder_6_Anode",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def MagazineHolder_6_Battery(name: str) -> ItemizedCarrier:
|
def MagazineHolder_6_Battery(
|
||||||
"""成品弹夹 - 6个洞位 (3x2布局)
|
name: str,
|
||||||
|
size_x: float = 80.0,
|
||||||
Args:
|
size_y: float = 80.0,
|
||||||
name: 弹夹名称
|
size_z: float = 40.0,
|
||||||
|
hole_diameter: float = 14.0,
|
||||||
Returns:
|
hole_depth: float = 10.0,
|
||||||
ItemizedCarrier: 包含6个槽位的弹夹架
|
hole_spacing: float = 20.0,
|
||||||
"""
|
max_sheets_per_hole: int = 100,
|
||||||
sites = create_ordered_items_2d(
|
) -> MagazineHolder:
|
||||||
klass=ResourceHolder,
|
"""创建6孔子弹夹 - 六边形排布"""
|
||||||
num_items_x=3,
|
center_x = size_x / 2
|
||||||
num_items_y=2,
|
center_y = size_y / 2
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
locations = []
|
||||||
dz=0.0,
|
|
||||||
item_dx=33.0,
|
# 周围6个孔,按六边形排布
|
||||||
item_dy=40.0,
|
for i in range(6):
|
||||||
size_x=30.0,
|
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||||
size_y=35.0,
|
x = center_x + hole_spacing * math.cos(angle)
|
||||||
size_z=40.0,
|
y = center_y + hole_spacing * math.sin(angle)
|
||||||
)
|
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||||
|
|
||||||
return ItemizedCarrier(
|
return magazine_factory(
|
||||||
name=name,
|
name=name,
|
||||||
size_x=120.0,
|
size_x=size_x,
|
||||||
size_y=100.0,
|
size_y=size_y,
|
||||||
size_z=50.0,
|
size_z=size_z,
|
||||||
num_items_x=3,
|
locations=locations,
|
||||||
num_items_y=2,
|
klasses=None, # 初始化时,不放入装好的电池
|
||||||
sites=sites,
|
hole_diameter=hole_diameter,
|
||||||
|
hole_depth=hole_depth,
|
||||||
|
max_sheets_per_hole=max_sheets_per_hole,
|
||||||
category="magazine_holder",
|
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",
|
||||||
|
)
|
||||||
|
|||||||
@@ -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:
|
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||||
"""创建BioYond 4x1x4仓库"""
|
"""创建BioYond 4x4x1仓库 (左侧堆栈: A01~D04)
|
||||||
return YB_warehouse_factory(
|
|
||||||
|
使用行优先排序,前端展示为:
|
||||||
|
A01 | A02 | A03 | A04
|
||||||
|
B01 | B02 | B03 | B04
|
||||||
|
C01 | C02 | C03 | C04
|
||||||
|
D01 | D02 | D03 | D04
|
||||||
|
"""
|
||||||
|
return warehouse_factory(
|
||||||
name=name,
|
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_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,
|
dx=10.0,
|
||||||
dy=10.0,
|
dy=10.0,
|
||||||
dz=10.0,
|
dz=10.0,
|
||||||
@@ -17,10 +83,74 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
|||||||
category="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:
|
def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||||
"""创建BioYond 4x1x2仓库"""
|
"""创建BioYond 4x2x1仓库"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=1,
|
num_items_x=1,
|
||||||
num_items_y=4,
|
num_items_y=4,
|
||||||
@@ -34,42 +164,26 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
|||||||
category="warehouse",
|
category="warehouse",
|
||||||
removed_positions=None
|
removed_positions=None
|
||||||
)
|
)
|
||||||
# 定义benyond的堆栈
|
|
||||||
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=2,
|
|
||||||
num_items_y=2,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="YB_warehouse",
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
|
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
||||||
"""创建BioYond 2x2x1仓库(自动堆栈)"""
|
"""创建BioYond 1x2x2仓库"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=2,
|
num_items_x=1,
|
||||||
num_items_y=2,
|
num_items_y=2,
|
||||||
num_items_z=1,
|
num_items_z=2,
|
||||||
dx=10.0,
|
dx=10.0,
|
||||||
dy=10.0,
|
dy=10.0,
|
||||||
dz=10.0,
|
dz=10.0,
|
||||||
item_dx=137.0,
|
item_dx=137.0,
|
||||||
item_dy=96.0,
|
item_dy=96.0,
|
||||||
item_dz=120.0,
|
item_dz=120.0,
|
||||||
category="YB_warehouse",
|
category="warehouse",
|
||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
||||||
"""创建BioYond 4x1x4仓库"""
|
"""创建BioYond 10x1x1仓库"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=10,
|
num_items_x=10,
|
||||||
num_items_y=1,
|
num_items_y=1,
|
||||||
@@ -82,9 +196,10 @@ def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
|||||||
item_dz=120.0,
|
item_dz=120.0,
|
||||||
category="warehouse",
|
category="warehouse",
|
||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
||||||
"""创建BioYond 4x1x4仓库"""
|
"""创建BioYond 1x3x3仓库"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=1,
|
num_items_x=1,
|
||||||
num_items_y=3,
|
num_items_y=3,
|
||||||
@@ -97,9 +212,10 @@ def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
|||||||
item_dz=120.0,
|
item_dz=120.0,
|
||||||
category="warehouse",
|
category="warehouse",
|
||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
|
def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
|
||||||
"""创建BioYond 4x1x4仓库"""
|
"""创建BioYond 2x1x3仓库"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=2,
|
num_items_x=2,
|
||||||
num_items_y=1,
|
num_items_y=1,
|
||||||
@@ -114,8 +230,8 @@ def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
||||||
"""创建BioYond 4x1x4仓库"""
|
"""创建BioYond 3x3x1仓库"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=3,
|
num_items_x=3,
|
||||||
num_items_y=3,
|
num_items_y=3,
|
||||||
@@ -128,9 +244,10 @@ def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
|||||||
item_dz=120.0,
|
item_dz=120.0,
|
||||||
category="warehouse",
|
category="warehouse",
|
||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
|
def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
|
||||||
"""创建BioYond 4x1x4仓库"""
|
"""已弃用:创建BioYond 5x1x1仓库"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=5,
|
num_items_x=5,
|
||||||
num_items_y=1,
|
num_items_y=1,
|
||||||
@@ -143,9 +260,10 @@ def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
|
|||||||
item_dz=120.0,
|
item_dz=120.0,
|
||||||
category="warehouse",
|
category="warehouse",
|
||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
|
def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
|
||||||
"""创建BioYond 4x1x4仓库"""
|
"""已弃用:创建BioYond 3x3x1仓库"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=3,
|
num_items_x=3,
|
||||||
num_items_y=3,
|
num_items_y=3,
|
||||||
@@ -161,7 +279,7 @@ def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
|
|||||||
|
|
||||||
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
||||||
"""创建BioYond开关盖加液模块台面"""
|
"""创建BioYond开关盖加液模块台面"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=2,
|
num_items_x=2,
|
||||||
num_items_y=5,
|
num_items_y=5,
|
||||||
@@ -176,34 +294,18 @@ def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
|||||||
removed_positions=None
|
removed_positions=None
|
||||||
)
|
)
|
||||||
|
|
||||||
def bioyond_warehouse_3x5x1(name: str) -> WareHouse:
|
def bioyond_warehouse_1x8x4(name: str) -> WareHouse:
|
||||||
"""创建BioYond 3x5x1仓库(手动堆栈)"""
|
"""创建BioYond 8x4x1反应站堆栈(A01~D08)"""
|
||||||
return YB_warehouse_factory(
|
return warehouse_factory(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=3,
|
num_items_x=8, # 8列(01-08)
|
||||||
num_items_y=5,
|
num_items_y=4, # 4行(A-D)
|
||||||
num_items_z=1,
|
num_items_z=1, # 1层
|
||||||
dx=10.0,
|
dx=10.0,
|
||||||
dy=10.0,
|
dy=10.0,
|
||||||
dz=10.0,
|
dz=10.0,
|
||||||
item_dx=137.0,
|
item_dx=147.0,
|
||||||
item_dy=96.0,
|
item_dy=106.0,
|
||||||
item_dz=120.0,
|
item_dz=130.0,
|
||||||
category="warehouse",
|
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,7 +1,7 @@
|
|||||||
from os import name
|
from os import name
|
||||||
from pylabrobot.resources import Deck, Coordinate, Rotation
|
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,
|
||||||
bioyond_warehouse_1x4x4_right, # 新增:右侧仓库 (A05~D08)
|
bioyond_warehouse_1x4x4_right, # 新增:右侧仓库 (A05~D08)
|
||||||
bioyond_warehouse_1x4x2,
|
bioyond_warehouse_1x4x2,
|
||||||
|
|||||||
@@ -404,6 +404,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
|
|
||||||
return result_future.result
|
return result_future.result
|
||||||
|
|
||||||
|
"""还没有改过的部分"""
|
||||||
|
|
||||||
def _setup_hardware_proxy(
|
def _setup_hardware_proxy(
|
||||||
self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method
|
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