Compare commits

..

14 Commits

Author SHA1 Message Date
Andy6M
f355722281 feat: migrate to pymodbus 3.11.4 and update bioyond configs
PyModbus 3.x Migration:
- Copied modbus.py and client.py from dev branch for compatibility
- Rewrote FLOAT32 decoding using struct module in coin_cell_assembly.py
- Fixed STRING decoding for QR codes (battery and electrolyte barcodes)
- Tested successfully on hardware with correct data decoding

Bioyond Studio Updates:
- Updated bioyond_studio config.py
- Modified bioyond_cell_workstation.py
- Enhanced warehouse.py and decks.py
- Added README_WAREHOUSE.md documentation

Parameter Enhancements:
- Enhanced coin_cell_workstation.yaml parameter descriptions
- Added matrix position ranges and indexing rules

Breaking changes:
- Requires pymodbus >= 3.9.0
- Removed deprecated BinaryPayloadDecoder/BinaryPayloadBuilder
- Updated to use client.convert_from/to_registers() methods
2026-01-10 17:01:40 +08:00
Andy6M
936834f8c3 Update workstation code for YB4 0107 2026-01-07 11:59:32 +08:00
Calvin Cao
915a6a04c3 Merge pull request #201 from sun7151887/push-sync-20251222
合并dev分支
2025-12-22 11:12:40 +08:00
dijkstra402
48b51c3a4a Merge dptech/workstation_dev_YB4: resolve conflicts by keeping local changes (ours) 2025-12-22 11:09:17 +08:00
dijkstra402
acef0b8ca2 Sync local workspace → push to yb4-fix 2025-12-22 10:57:29 +08:00
ZiWei
97788b4e07 feat: introduce wait_time command and configurable device communication timeout. 2025-12-19 18:02:38 +08:00
ZiWei
39cc280c91 feat: Add SyringePump (SY-03B) driver with unified serial/TCP transport for chinwe device, including registry and test configurations. 2025-12-19 03:05:11 +08:00
Calvin Cao
d2a30fe33b Merge pull request #177 from sun7151887/yb4-fix
Yb4默认仿真机
2025-11-27 18:49:41 +08:00
dijkstra402
096875e910 默认仿真机 2025-11-27 18:22:46 +08:00
dijkstra402
ee4ed26846 Merge branch 'workstation_dev_YB4' of https://github.com/dptech-corp/Uni-Lab-OS into workstation_dev_YB4 2025-11-11 09:54:22 +08:00
dijkstra402
19dffcb5db 更新YB_Deck堆栈坐标位置,根据图片像素坐标映射到实际尺寸 2025-11-10 18:57:10 +08:00
dijkstra402
b441362cd2 Merge branch 'workstation_dev_YB4' of https://github.com/dptech-corp/Uni-Lab-OS into workstation_dev_YB4 2025-11-10 18:35:41 +08:00
dijkstra402
ed53ef2f64 Update bioyond_cell and YAML configurations: modified default Excel paths and added new bottle carrier resources. Removed unused fields and updated descriptions for clarity. 2025-11-10 18:35:37 +08:00
dijkstra402
0c9f26e8fc Update Excel files: modified bioyond_cell and material_template with new data 2025-11-10 18:35:21 +08:00
56 changed files with 5117 additions and 36397 deletions

32
fix_datatype.py Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
filepath = r'd:\UniLab\Uni-Lab-OS\unilabos\device_comms\modbus_plc\modbus.py'
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Replace the DataType placeholder with actual enum
find_pattern = r'# DataType will be accessed via client instance.*?DataType = None # Placeholder.*?\n'
replacement = '''# Define DataType enum for pymodbus 2.5.3 compatibility
class DataType(Enum):
INT16 = "int16"
UINT16 = "uint16"
INT32 = "int32"
UINT32 = "uint32"
INT64 = "int64"
UINT64 = "uint64"
FLOAT32 = "float32"
FLOAT64 = "float64"
STRING = "string"
BOOL = "bool"
'''
new_content = re.sub(find_pattern, replacement, content, flags=re.DOTALL)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(new_content)
print('File updated successfully!')

View File

@@ -91,11 +91,8 @@
}, },
"data": {} "data": {}
} }
] ],
} "links": []
}

View 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": {}
}
]
}

View File

@@ -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:

View 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": []
}

View File

@@ -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-58列对应chlid 1-8 # 第1盘5行8列网格 (A1-E8) - 5行对应subdevid 1-58列对应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:
""" """
获取设备级别的摘要统计设备动作 获取设备级别的摘要统计设备动作

View File

@@ -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",
]

View File

@@ -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 Timestamp Battery_Count Assembly_Time Open_Circuit_Voltage Pole_Weight Assembly_Pressure Battery_Code Electrolyte_Code 集流体质量 活性物质含量 克容量mah/g 电池体系 设备号 排号 通道号
2 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
3 2025/10/30 17:49 2 5 0 13.109999895095825 4094 YS101224 NoRead88 5.2 0.92 190 SiGr_Li 2 1 1

View File

@@ -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": []
}

View File

@@ -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)

View File

@@ -1,282 +1,649 @@
import sys # -*- coding: utf-8 -*-
import threading """
Contains drivers for:
1. SyringePump: Runze Fluid SY-03B (ASCII)
2. EmmMotor: Emm V5.0 Closed-loop Stepper (Modbus-RTU variant)
3. XKCSensor: XKC Non-contact Level Sensor (Modbus-RTU)
"""
import socket
import serial import serial
import serial.tools.list_ports
import re
import time import time
from typing import Optional, List, Dict, Tuple import threading
import struct
import re
import traceback
import queue
from typing import Optional, Dict, List, Any
class ChinweDevice: try:
""" from unilabos.device_comms.universal_driver import UniversalDriver
ChinWe设备控制类 except ImportError:
提供串口通信、电机控制、传感器数据读取等功能 import logging
""" class UniversalDriver:
def __init__(self):
self.logger = logging.getLogger(self.__class__.__name__)
def __init__(self, port: str, baudrate: int = 115200, debug: bool = False): def execute_command_from_outer(self, command: str):
""" pass
初始化ChinWe设备
Args: # ==============================================================================
port: 串口名称如果为None则自动检测 # 1. Transport Layer (通信层)
baudrate: 波特率默认115200 # ==============================================================================
class TransportManager:
""" """
self.debug = debug 统一通信管理类。
自动识别 串口 (Serial) 或 网络 (TCP) 连接。
"""
def __init__(self, port: str, baudrate: int = 9600, timeout: float = 3.0, logger=None):
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
# 默认配置
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() 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 # --- 对外功能指令 (Actions) ---
def voltage(self) -> float:
"""获取电源电压值"""
return self._voltage
@property def pump_initialize(self, pump_id: int, drain_port=0, output_port=0, speed=10):
def ec_value(self) -> float: """指定泵初始化"""
"""获取电导率值 (ms/cm)""" pump_id = int(pump_id)
return self._ec_value if pump_id in self.pumps:
self.pumps[pump_id].initialize(drain_port, output_port, speed)
@property self.pumps[pump_id].wait_until_idle()
def ec_adc_value(self) -> int:
"""获取EC ADC原始值"""
return self._ec_adc_value
@property
def device_status(self) -> Dict[str, any]:
"""
获取设备状态信息
Returns:
包含设备状态的字典
"""
return {
"connected": self.is_connected,
"port": self.port,
"baudrate": self.baudrate,
"voltage": self.voltage,
"ec_value": self.ec_value,
"ec_adc_value": self.ec_adc_value
}
def connect(self, port: Optional[str] = None, baudrate: Optional[int] = None) -> bool:
"""
连接到串口设备
Args:
port: 串口名称如果为None则使用初始化时的port或自动检测
baudrate: 波特率如果为None则使用初始化时的baudrate
Returns:
连接是否成功
"""
if self.is_connected:
return True return True
target_port = port or self.port
target_baudrate = baudrate or self.baudrate
try:
self.serial_port = serial.Serial(target_port, target_baudrate, timeout=0.5)
self._is_connected = True
self.port = target_port
self.baudrate = target_baudrate
connect_allow_times = 5
while not self.serial_port.is_open and connect_allow_times > 0:
time.sleep(0.5)
connect_allow_times -= 1
print(f"尝试连接到 {target_port} @ {target_baudrate},剩余尝试次数: {connect_allow_times}", self.debug)
raise ValueError("串口未打开,请检查设备连接")
print(f"已连接到 {target_port} @ {target_baudrate}", self.debug)
threading.Thread(target=self._read_data, daemon=True).start()
return True
except Exception as e:
print(f"ChinweDevice连接失败: {e}")
self._is_connected = False
return False return False
def disconnect(self) -> bool: def pump_aspirate(self, pump_id: int, volume: int, valve_port: int):
""" """
断开串口连接 泵吸液 (阻塞)
: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. 切换阀门
pump.switch_valve(valve_port)
pump.wait_until_idle()
# 2. 吸液
pump.aspirate(volume)
pump.wait_until_idle()
return True return True
except Exception as e:
print(f"断开连接失败: {e}")
return False
return True
def _send_motor_command(self, command: str) -> bool:
"""
发送电机控制命令
Args:
command: 电机命令字符串,例如 "M 1 CW 1.5"
Returns:
发送是否成功
"""
if not self.is_connected:
print("设备未连接")
return False return False
try: def pump_dispense(self, pump_id: int, volume: int, valve_port: int):
self.serial_port.write((command + "\n").encode('utf-8')) """
print(f"发送命令: {command}") 泵排液 (阻塞)
:param valve_port: 阀门端口 (1-8)
"""
pump_id = int(pump_id)
valve_port = int(valve_port)
if pump_id in self.pumps:
pump = self.pumps[pump_id]
# 1. 切换阀门
pump.switch_valve(valve_port)
pump.wait_until_idle()
# 2. 排液
pump.dispense(volume)
pump.wait_until_idle()
return True return True
except Exception as e:
print(f"发送命令失败: {e}")
return False return False
def rotate_motor(self, motor_id: int, turns: float, clockwise: bool = True) -> bool: 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 "逆时针"
Args:
motor_id: 电机ID1, 2, 3...
turns: 转动圈数,支持小数
clockwise: True为顺时针False为逆时针
Returns:
命令发送是否成功
""" """
if clockwise: motor_id = int(motor_id)
command = f"M {motor_id} CW {turns}" if motor_id not in self.motors: return False
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: dir_val = 0 if direction == "顺时针" else 1
self.motors[motor_id].run_speed(speed, dir_val)
return True
def motor_rotate_quarter(self, motor_id: int, speed: int = 60, direction: str = "顺时针"):
""" """
设置电机转速(如果设备支持) 电机旋转1/4圈 (阻塞)
假设电机设置为 3200 脉冲/圈1/4圈 = 800脉冲
Args:
motor_id: 电机ID1, 2, 3...
speed: 转速值
Returns:
命令发送是否成功
""" """
command = f"M {motor_id} SPEED {speed}" motor_id = int(motor_id)
return self._send_motor_command(command) if motor_id not in self.motors: return False
def _read_data(self) -> List[str]: pulses = 800
dir_val = 0 if direction == "顺时针" else 1
self.motors[motor_id].run_position(pulses, speed, dir_val, absolute=False)
# 预估时间阻塞 (单位: 分钟 -> 秒)
# Time(s) = revs / (RPM/60). revs = 0.25. time = 15 / RPM.
estimated_time = 15.0 / max(1, speed)
time.sleep(estimated_time + 0.5)
return True
def motor_stop(self, motor_id: int):
"""电机停止"""
motor_id = int(motor_id)
if motor_id in self.motors:
self.motors[motor_id].stop()
return True
return False
def wait_sensor_level(self, target_state: str = "有液", timeout: int = 30) -> bool:
""" """
读取串口数据并解析 等待传感器达到指定电平
:param target_state: "有液" or "无液"
Returns:
读取到的数据行列表
""" """
print("开始读取串口数据...") target_bool = True if target_state == "有液" else False
if not self.is_connected:
return []
data_lines = [] self.logger.info(f"Wait sensor: {target_state} ({target_bool}), timeout: {timeout}")
try: start = time.time()
while self.serial_port.in_waiting: while time.time() - start < timeout:
time.sleep(0.1) # 等待数据稳定 if self.sensor_level == target_bool:
try: return True
line = self.serial_port.readline().decode('utf-8', errors='ignore').strip() time.sleep(0.1)
if line: self.logger.warning("Wait sensor level timeout")
data_lines.append(line) return False
self._parse_sensor_data(line)
except Exception as ex:
print(f"解码数据错误: {ex}")
except Exception as e:
print(f"读取串口数据错误: {e}")
return data_lines def wait_time(self, duration: int) -> bool:
def _parse_sensor_data(self, line: str) -> None:
""" """
解析传感器数据 等待指定时间 (秒)
:param duration: 秒
Args:
line: 接收到的数据行
""" """
# 解析电源电压 self.logger.info(f"Waiting for {duration} seconds...")
if "电源电压" in line: time.sleep(duration)
try: return True
val = float(line.split("")[1].replace("V", "").strip())
self._voltage = val
if self.debug:
print(f"电源电压更新: {val}V")
except Exception:
pass
# 解析电导率和ADC原始值支持两种格式 def execute_command_from_outer(self, command_dict: Dict[str, Any]) -> bool:
if "电导率" in line and "ADC原始值" in line: """支持标准 JSON 指令调用"""
try: return super().execute_command_from_outer(command_dict)
# 支持格式如电导率2.50ms/cm, ADC原始值2052
ec_match = re.search(r"电导率[:]\s*([\d\.]+)", line)
adc_match = re.search(r"ADC原始值[:]\s*(\d+)", line)
if ec_match:
ec_val = float(ec_match.group(1))
self._ec_value = ec_val
if self.debug:
print(f"电导率更新: {ec_val:.2f} ms/cm")
if adc_match:
adc_val = int(adc_match.group(1))
self._ec_adc_value = adc_val
if self.debug:
print(f"EC ADC原始值更新: {adc_val}")
except Exception:
pass
# 仅电导率无ADC原始值
elif "电导率" in line:
try:
val = float(line.split("")[1].replace("ms/cm", "").strip())
self._ec_value = val
if self.debug:
print(f"电导率更新: {val:.2f} ms/cm")
except Exception:
pass
# 仅ADC原始值如有分开回传场景
elif "ADC原始值" in line:
try:
adc_val = int(line.split("")[1].strip())
self._ec_adc_value = adc_val
if self.debug:
print(f"EC ADC原始值更新: {adc_val}")
except Exception:
pass
def spin_when_ec_ge_0():
pass
def 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()

View File

@@ -0,0 +1,197 @@
{
"token": "",
"request_time": "2025-12-24T15:32:09.2148671+08:00",
"data": {
"orderId": "3a1e614d-a082-c44a-60be-68647a35e6f1",
"orderCode": "BSO2025122400024",
"orderName": "DP20251224001",
"startTime": "2025-12-24T14:51:50.549848",
"endTime": "2025-12-24T15:32:09.000765",
"status": "30",
"workflowStatus": "completed",
"completionTime": "2025-12-24T15:32:09.000765",
"usedMaterials": [
{
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
"typemode": "1",
"usedQuantity": 2,
"realQuantity": 2
},
{
"materialId": "3a1e614b-4da7-cf62-3a40-7e5879255c0c",
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
"typemode": "1",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614b-53a7-2850-42c8-a7a2de8ff4bf",
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
"typemode": "1",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
"typemode": "1",
"usedQuantity": 2,
"realQuantity": 2
},
{
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
"locationId": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c",
"typemode": "0",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
"typemode": "2",
"usedQuantity": 3.51,
"realQuantity": 3.5155000000000000000000000000
},
{
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
"typemode": "2",
"usedQuantity": 0.33,
"realQuantity": 0.3336000000000000000000000000
}
]
}
}
{
"token": "",
"request_time": "2025-12-24T15:32:09.9999039+08:00",
"data": {
"orderId": "3a1e614d-a0a2-f7a9-9360-610021c9479d",
"orderCode": "BSO2025122400025",
"orderName": "DP20251224002",
"startTime": "2025-12-24T14:53:03.44259",
"endTime": "2025-12-24T15:32:09.828261",
"status": "30",
"workflowStatus": "completed",
"completionTime": "2025-12-24T15:32:09.828261",
"usedMaterials": [
{
"materialId": "3a1e614b-4da7-6527-9f1c-b39e3de8ff2b",
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
"typemode": "1",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
"typemode": "1",
"usedQuantity": 2,
"realQuantity": 2
},
{
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
"typemode": "1",
"usedQuantity": 2,
"realQuantity": 2
},
{
"materialId": "3a1e614b-53a8-8474-cac8-0fd7d349e4b2",
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
"typemode": "1",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
"locationId": null,
"typemode": "0",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
"typemode": "2",
"usedQuantity": 0.7,
"realQuantity": 0
},
{
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
"typemode": "2",
"usedQuantity": 1.15,
"realQuantity": 1.1627000000000000000000000000
}
]
}
}
{
"token": "",
"request_time": "2025-12-24T15:34:00.4139986+08:00",
"data": {
"orderId": "3a1e614d-a0cd-81ca-9f7f-2f4e93af01cd",
"orderCode": "BSO2025122400026",
"orderName": "DP20251224003",
"startTime": "2025-12-24T14:54:24.443344",
"endTime": "2025-12-24T15:34:00.26321",
"status": "30",
"workflowStatus": "completed",
"completionTime": "2025-12-24T15:34:00.26321",
"usedMaterials": [
{
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
"locationId": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
"typemode": "1",
"usedQuantity": 2,
"realQuantity": 2
},
{
"materialId": "3a1e614b-4da8-b678-f204-207076f09c83",
"locationId": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
"typemode": "1",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
"typemode": "1",
"usedQuantity": 2,
"realQuantity": 2
},
{
"materialId": "3a1e614b-53a8-e3f2-dee0-fa97b600b652",
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
"typemode": "1",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
"locationId": null,
"typemode": "0",
"usedQuantity": 1,
"realQuantity": 1
},
{
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
"typemode": "2",
"usedQuantity": 2.0,
"realQuantity": 2.0075000000000000000000000000
},
{
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
"typemode": "2",
"usedQuantity": 1.2,
"realQuantity": 1.2126000000000000000000000000
}
]
}
}

View File

@@ -0,0 +1,12 @@
import pubchempy as pcp
cas = "21324-40-3" # 示例
comps = pcp.get_compounds(cas, namespace="name")
if not comps:
raise ValueError("No hit")
c = comps[0]
print("Canonical SMILES:", c.canonical_smiles)
print("Isomeric SMILES:", c.isomeric_smiles)
print("MW:", c.molecular_weight)

View File

@@ -0,0 +1,113 @@
# Bioyond Cell 工作站 - 多订单返回示例
本文档说明了 `create_orders` 函数如何收集并返回所有订单的完成报文。
## 问题描述
之前的实现只会等待并返回第一个订单的完成报文,如果有多个订单(例如从 Excel 解析出 3 个订单),只能得到第一个订单的推送信息。
## 解决方案
修改后的 `create_orders` 函数现在会:
1. **提取所有 orderCode**:从 LIMS 接口返回的 `data` 列表中提取所有订单编号
2. **逐个等待完成**:遍历所有 orderCode调用 `wait_for_order_finish` 等待每个订单完成
3. **收集所有报文**:将每个订单的完成报文存入 `all_reports` 列表
4. **统一返回**:返回包含所有订单报文的 JSON 格式数据
## 返回格式
```json
{
"status": "all_completed",
"total_orders": 3,
"reports": [
{
"token": "",
"request_time": "2025-12-24T15:32:09.2148671+08:00",
"data": {
"orderId": "3a1e614d-a082-c44a-60be-68647a35e6f1",
"orderCode": "BSO2025122400024",
"orderName": "DP20251224001",
"status": "30",
"workflowStatus": "completed",
"usedMaterials": [...]
}
},
{
"token": "",
"request_time": "2025-12-24T15:32:09.9999039+08:00",
"data": {
"orderId": "3a1e614d-a0a2-f7a9-9360-610021c9479d",
"orderCode": "BSO2025122400025",
"orderName": "DP20251224002",
"status": "30",
"workflowStatus": "completed",
"usedMaterials": [...]
}
},
{
"token": "",
"request_time": "2025-12-24T15:34:00.4139986+08:00",
"data": {
"orderId": "3a1e614d-a0cd-81ca-9f7f-2f4e93af01cd",
"orderCode": "BSO2025122400026",
"orderName": "DP20251224003",
"status": "30",
"workflowStatus": "completed",
"usedMaterials": [...]
}
}
],
"original_response": {...}
}
```
## 使用示例
```python
# 调用 create_orders
result = workstation.create_orders("20251224.xlsx")
# 访问返回数据
print(f"总订单数: {result['total_orders']}")
print(f"状态: {result['status']}")
# 遍历所有订单的报文
for i, report in enumerate(result['reports'], 1):
order_data = report.get('data', {})
print(f"\n订单 {i}:")
print(f" orderCode: {order_data.get('orderCode')}")
print(f" orderName: {order_data.get('orderName')}")
print(f" status: {order_data.get('status')}")
print(f" 使用物料数: {len(order_data.get('usedMaterials', []))}")
```
## 控制台输出示例
```
[create_orders] 即将提交订单数量: 3
[create_orders] 接口返回: {...}
[create_orders] 等待 3 个订单完成: ['BSO2025122400024', 'BSO2025122400025', 'BSO2025122400026']
[create_orders] 正在等待第 1/3 个订单: BSO2025122400024
[create_orders] ✓ 订单 BSO2025122400024 完成
[create_orders] 正在等待第 2/3 个订单: BSO2025122400025
[create_orders] ✓ 订单 BSO2025122400025 完成
[create_orders] 正在等待第 3/3 个订单: BSO2025122400026
[create_orders] ✓ 订单 BSO2025122400026 完成
[create_orders] 所有订单已完成,共收集 3 个报文
实验记录本========================create_orders========================
返回报文数量: 3
报文 1: orderCode=BSO2025122400024, status=30
报文 2: orderCode=BSO2025122400025, status=30
报文 3: orderCode=BSO2025122400026, status=30
========================
```
## 关键改进
1.**等待所有订单**:不再只等待第一个订单,而是遍历所有 orderCode
2.**收集完整报文**:每个订单的完整推送报文都被保存在 `reports` 数组中
3.**详细日志**:清晰显示正在等待哪个订单,以及完成情况
4.**错误处理**:即使某个订单失败,也会记录其状态信息
5.**统一格式**:返回的 JSON 格式便于后续处理和分析

View File

@@ -8,8 +8,10 @@ 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.219:44388"),# 仿真机 #"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.118:44389"),
# 仿真机
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
@@ -17,7 +19,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.11.206"), # 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,# 调试模式
} }
@@ -131,6 +133,46 @@ WAREHOUSE_MAPPING = {
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9" "J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
} }
}, },
"手动传递窗右": {
"uuid": "",
"site_uuids": {
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
}
},
"手动传递窗左": {
"uuid": "",
"site_uuids": {
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
}
},
"4号手套箱内部堆栈": { "4号手套箱内部堆栈": {
"uuid": "", "uuid": "",
"site_uuids": { "site_uuids": {

View File

@@ -0,0 +1,84 @@
# Modbus CSV 地址映射说明
本文档说明 `coin_cell_assembly_a.csv` 文件如何将命名节点映射到实际的 Modbus 地址,以及如何在代码中使用它们。
## 1. CSV 文件结构
地址表文件位于同级目录下:`coin_cell_assembly_a.csv`
每一行定义了一个 Modbus 节点,包含以下关键列:
| 列名 | 说明 | 示例 |
|------|------|------|
| **Name** | **节点名称** (代码中引用的 Key) | `COIL_ALUMINUM_FOIL` |
| **DataType** | 数据类型 (BOOL, INT16, FLOAT32, STRING) | `BOOL` |
| **Comment** | 注释说明 | `使用铝箔垫` |
| **Attribute** | 属性 (通常留空或用于额外标记) | |
| **DeviceType** | Modbus 寄存器类型 (`coil`, `hold_register`) | `coil` |
| **Address** | **Modbus 地址** (十进制) | `8340` |
### 示例行 (铝箔垫片)
```csv
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,8340,
```
- **名称**: `COIL_ALUMINUM_FOIL`
- **类型**: `coil` (线圈,读写单个位)
- **地址**: `8340`
---
## 2. 加载与注册流程
`coin_cell_assembly.py` 的初始化代码中:
1. **加载 CSV**: `BaseClient.load_csv()` 读取 CSV 并解析每行定义。
2. **注册节点**: `modbus_client.register_node_list()` 将解析后的节点注册到 Modbus 客户端实例中。
```python
# 代码位置: coin_cell_assembly.py (L174-175)
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv'))
self.client = modbus_client.register_node_list(self.nodes)
```
---
## 3. 代码中的使用方式
注册后,通过 `self.client.use_node('节点名称')` 即可获取该节点对象并进行读写操作,无需关心具体地址。
### 控制铝箔垫片 (COIL_ALUMINUM_FOIL)
```python
# 代码位置: qiming_coin_cell_code 函数 (L1048)
self.client.use_node('COIL_ALUMINUM_FOIL').write(not lvbodian)
```
- **写入 True**: 对应 Modbus 功能码 05 (Write Single Coil),向地址 `8340` 写入 `1` (ON)。
- **写入 False**: 向地址 `8340` 写入 `0` (OFF)。
> **注意**: 代码中使用了 `not lvbodian`,这意味着逻辑是反转的。如果 `lvbodian` 参数为 `True` (默认),写入的是 `False` (不使用铝箔垫)。
---
## 4. 地址转换注意事项 (Modbus vs PLC)
CSV 中的 `Address` 列(如 `8340`)是 **Modbus 协议地址**
如果使用 InoProShop (汇川 PLC 编程软件),看到的可能是 **PLC 内部地址** (如 `%QX...``%MW...`)。这两者之间通常需要转换。
### 常见的转换规则 (示例)
- **Coil (线圈) %QX**:
- `Modbus地址 = 字节地址 * 8 + 位偏移`
- *例子*: `%QX834.0` -> `834 * 8 + 0` = `6672`
- *注意*: 如果 CSV 中配置的是 `8340`,这可能是一个自定义映射,或者是基于不同规则(如直接对应 Word 地址的某种映射,或者可能就是地址写错了/使用了非标准映射)。
- **Register (寄存器) %MW**:
- 通常直接对应,或者有偏移量 (如 Modbus 40001 = PLC MW0)。
### 验证方法
由于 `test_unilab_interact.py` 中发现 `8450` (CSV风格) 不工作,而 `6760` (%QX845.0 计算值) 工作正常,**建议对 CSV 中的其他地址也进行核实**,特别是像 `8340` 这样以 0 结尾看起来像是 "字节地址+0" 的数值,可能实际上应该是 `%QX834.0` 对应的 `6672`
如果发现设备控制无反应,请尝试按照标准的 Modbus 计算方式转换 PLC 地址。

View File

@@ -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)

View File

@@ -1,64 +0,0 @@
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
COIL_SYS_START_CMD,BOOL,,,,coil,9010,
COIL_SYS_STOP_CMD,BOOL,,,,coil,9020,
COIL_SYS_RESET_CMD,BOOL,,,,coil,9030,
COIL_SYS_HAND_CMD,BOOL,,,,coil,9040,
COIL_SYS_AUTO_CMD,BOOL,,,,coil,9050,
COIL_SYS_INIT_CMD,BOOL,,,,coil,9060,
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,9700,
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,9710,unilab_rec_msg_succ_cmd
COIL_SYS_START_STATUS,BOOL,,,,coil,9210,
COIL_SYS_STOP_STATUS,BOOL,,,,coil,9220,
COIL_SYS_RESET_STATUS,BOOL,,,,coil,9230,
COIL_SYS_HAND_STATUS,BOOL,,,,coil,9240,
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,9250,
COIL_SYS_INIT_STATUS,BOOL,,,,coil,9260,
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,9500,
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,9510,request_send_msg_status
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,17000,
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,17002,unilab_send_msg_electrolyte_num
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,17004,unilab_send_msg_electrolyte_vol
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,17006,unilab_send_msg_assembly_type
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,17008,unilab_send_msg_assembly_pressure
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,16000,data_assembly_coin_cell_num
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,16002,data_open_circuit_voltage
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,16004,
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,16006,
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,16008,
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,16010,data_pole_weight
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,16012,data_assembly_time
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,16014,data_assembly_pressure
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,16016,data_electrolyte_volume
REG_DATA_COIN_NUM,INT16,,,,hold_register,16018,data_coin_num
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,16020,data_electrolyte_code()
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,16030,data_coin_cell_code()
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,18004,data_stack_vision_code()
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,16050,data_glove_box_pressure
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,16052,data_glove_box_water_content
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,16054,data_glove_box_o2_content
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9720,
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9520,
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,17496,
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,16000,
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,9730,
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,9530,
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,16018,ASSEMBLY_TYPE7or8
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,9340,
REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,17440,
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,17450,
REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,17480,
REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,17443,
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,17453,
REG_MSG_PRESS_MODE,BOOL,,压制模式false:压力检测模式True:距离模式),,coil,9360,电池压制模式
,,,,,,,
,BOOL,,视觉对位false:使用true:忽略),,coil,9300,视觉对位
,BOOL,,复检false:使用true:忽略),,coil,9310,视觉复检
,BOOL,,手套箱_左仓false:使用true:忽略),,coil,9320,手套箱左仓
,BOOL,,手套箱_右仓false:使用true:忽略),,coil,9420,手套箱右仓
,BOOL,,真空检知false:使用true:忽略),,coil,9350,真空检知
,BOOL,,电解液添加模式false:单次滴液true:二次滴液),,coil,9370,滴液模式
,BOOL,,正极片称重false:使用true:忽略),,coil,9380,正极片称重
,BOOL,,正负极片组装方式false:正装true:倒装),,coil,9390,正负极反装
,BOOL,,压制清洁false:使用true:忽略),,coil,9400,压制清洁
,BOOL,,物料盘摆盘方式false:水平摆盘true:堆叠摆盘),,coil,9410,负极片摆盘方式
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁false:使用true:忽略),,coil,9460,
1 Name DataType InitValue Comment Attribute DeviceType Address
2 COIL_SYS_START_CMD BOOL coil 9010
3 COIL_SYS_STOP_CMD BOOL coil 9020
4 COIL_SYS_RESET_CMD BOOL coil 9030
5 COIL_SYS_HAND_CMD BOOL coil 9040
6 COIL_SYS_AUTO_CMD BOOL coil 9050
7 COIL_SYS_INIT_CMD BOOL coil 9060
8 COIL_UNILAB_SEND_MSG_SUCC_CMD BOOL coil 9700
9 COIL_UNILAB_REC_MSG_SUCC_CMD BOOL coil 9710 unilab_rec_msg_succ_cmd
10 COIL_SYS_START_STATUS BOOL coil 9210
11 COIL_SYS_STOP_STATUS BOOL coil 9220
12 COIL_SYS_RESET_STATUS BOOL coil 9230
13 COIL_SYS_HAND_STATUS BOOL coil 9240
14 COIL_SYS_AUTO_STATUS BOOL coil 9250
15 COIL_SYS_INIT_STATUS BOOL coil 9260
16 COIL_REQUEST_REC_MSG_STATUS BOOL coil 9500
17 COIL_REQUEST_SEND_MSG_STATUS BOOL coil 9510 request_send_msg_status
18 REG_MSG_ELECTROLYTE_USE_NUM INT16 hold_register 17000
19 REG_MSG_ELECTROLYTE_NUM INT16 hold_register 17002 unilab_send_msg_electrolyte_num
20 REG_MSG_ELECTROLYTE_VOLUME INT16 hold_register 17004 unilab_send_msg_electrolyte_vol
21 REG_MSG_ASSEMBLY_TYPE INT16 hold_register 17006 unilab_send_msg_assembly_type
22 REG_MSG_ASSEMBLY_PRESSURE INT16 hold_register 17008 unilab_send_msg_assembly_pressure
23 REG_DATA_ASSEMBLY_COIN_CELL_NUM INT16 hold_register 16000 data_assembly_coin_cell_num
24 REG_DATA_OPEN_CIRCUIT_VOLTAGE FLOAT32 hold_register 16002 data_open_circuit_voltage
25 REG_DATA_AXIS_X_POS FLOAT32 hold_register 16004
26 REG_DATA_AXIS_Y_POS FLOAT32 hold_register 16006
27 REG_DATA_AXIS_Z_POS FLOAT32 hold_register 16008
28 REG_DATA_POLE_WEIGHT FLOAT32 hold_register 16010 data_pole_weight
29 REG_DATA_ASSEMBLY_PER_TIME FLOAT32 hold_register 16012 data_assembly_time
30 REG_DATA_ASSEMBLY_PRESSURE INT16 hold_register 16014 data_assembly_pressure
31 REG_DATA_ELECTROLYTE_VOLUME INT16 hold_register 16016 data_electrolyte_volume
32 REG_DATA_COIN_NUM INT16 hold_register 16018 data_coin_num
33 REG_DATA_ELECTROLYTE_CODE STRING hold_register 16020 data_electrolyte_code()
34 REG_DATA_COIN_CELL_CODE STRING hold_register 16030 data_coin_cell_code()
35 REG_DATA_STACK_VISON_CODE STRING hold_register 18004 data_stack_vision_code()
36 REG_DATA_GLOVE_BOX_PRESSURE FLOAT32 hold_register 16050 data_glove_box_pressure
37 REG_DATA_GLOVE_BOX_WATER_CONTENT FLOAT32 hold_register 16052 data_glove_box_water_content
38 REG_DATA_GLOVE_BOX_O2_CONTENT FLOAT32 hold_register 16054 data_glove_box_o2_content
39 UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM BOOL coil 9720
40 UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM BOOL coil 9520
41 REG_MSG_ELECTROLYTE_NUM_USED INT16 hold_register 17496
42 REG_DATA_ELECTROLYTE_USE_NUM INT16 hold_register 16000
43 UNILAB_SEND_FINISHED_CMD BOOL coil 9730
44 UNILAB_RECE_FINISHED_CMD BOOL coil 9530
45 REG_DATA_ASSEMBLY_TYPE INT16 hold_register 16018 ASSEMBLY_TYPE7or8
46 COIL_ALUMINUM_FOIL BOOL 使用铝箔垫 coil 9340
47 REG_MSG_NE_PLATE_MATRIX INT16 负极片矩阵点位 hold_register 17440
48 REG_MSG_SEPARATOR_PLATE_MATRIX INT16 隔膜矩阵点位 hold_register 17450
49 REG_MSG_TIP_BOX_MATRIX INT16 移液枪头矩阵点位 hold_register 17480
50 REG_MSG_NE_PLATE_NUM INT16 负极片盘数 hold_register 17443
51 REG_MSG_SEPARATOR_PLATE_NUM INT16 隔膜盘数 hold_register 17453
52 REG_MSG_PRESS_MODE BOOL 压制模式(false:压力检测模式,True:距离模式) coil 9360 电池压制模式
53
54 BOOL 视觉对位(false:使用,true:忽略) coil 9300 视觉对位
55 BOOL 复检(false:使用,true:忽略) coil 9310 视觉复检
56 BOOL 手套箱_左仓(false:使用,true:忽略) coil 9320 手套箱左仓
57 BOOL 手套箱_右仓(false:使用,true:忽略) coil 9420 手套箱右仓
58 BOOL 真空检知(false:使用,true:忽略) coil 9350 真空检知
59 BOOL 电解液添加模式(false:单次滴液,true:二次滴液) coil 9370 滴液模式
60 BOOL 正极片称重(false:使用,true:忽略) coil 9380 正极片称重
61 BOOL 正负极片组装方式(false:正装,true:倒装) coil 9390 正负极反装
62 BOOL 压制清洁(false:使用,true:忽略) coil 9400 压制清洁
63 BOOL 物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘) coil 9410 负极片摆盘方式
64 REG_MSG_BATTERY_CLEAN_IGNORE BOOL 忽略电池清洁(false:使用,true:忽略) coil 9460

View File

@@ -1,64 +0,0 @@
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
COIL_SYS_START_CMD,BOOL,,,,coil,8010,
COIL_SYS_STOP_CMD,BOOL,,,,coil,8020,
COIL_SYS_RESET_CMD,BOOL,,,,coil,8030,
COIL_SYS_HAND_CMD,BOOL,,,,coil,8040,
COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050,
COIL_SYS_INIT_CMD,BOOL,,,,coil,8060,
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700,
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd
COIL_SYS_START_STATUS,BOOL,,,,coil,8210,
COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220,
COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230,
COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240,
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250,
COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260,
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500,
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000,
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004,
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006,
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008,
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume
REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code()
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code()
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code()
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720,
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520,
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496,
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000,
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730,
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530,
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,8340,
REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,440,
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,450,
REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,480,
REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,443,
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,453,
REG_MSG_PRESS_MODE,BOOL,,压制模式false:压力检测模式True:距离模式),,coil,8360,电池压制模式
,,,,,,,
,BOOL,,视觉对位false:使用true:忽略),,coil,8300,视觉对位
,BOOL,,复检false:使用true:忽略),,coil,8310,视觉复检
,BOOL,,手套箱_左仓false:使用true:忽略),,coil,8320,手套箱左仓
,BOOL,,手套箱_右仓false:使用true:忽略),,coil,8420,手套箱右仓
,BOOL,,真空检知false:使用true:忽略),,coil,8350,真空检知
,BOOL,,电解液添加模式false:单次滴液true:二次滴液),,coil,8370,滴液模式
,BOOL,,正极片称重false:使用true:忽略),,coil,8380,正极片称重
,BOOL,,正负极片组装方式false:正装true:倒装),,coil,8390,正负极反装
,BOOL,,压制清洁false:使用true:忽略),,coil,8400,压制清洁
,BOOL,,物料盘摆盘方式false:水平摆盘true:堆叠摆盘),,coil,8410,负极片摆盘方式
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁false:使用true:忽略),,coil,8460,
1 Name DataType InitValue Comment Attribute DeviceType Address
2 COIL_SYS_START_CMD BOOL coil 8010
3 COIL_SYS_STOP_CMD BOOL coil 8020
4 COIL_SYS_RESET_CMD BOOL coil 8030
5 COIL_SYS_HAND_CMD BOOL coil 8040
6 COIL_SYS_AUTO_CMD BOOL coil 8050
7 COIL_SYS_INIT_CMD BOOL coil 8060
8 COIL_UNILAB_SEND_MSG_SUCC_CMD BOOL coil 8700
9 COIL_UNILAB_REC_MSG_SUCC_CMD BOOL coil 8710 unilab_rec_msg_succ_cmd
10 COIL_SYS_START_STATUS BOOL coil 8210
11 COIL_SYS_STOP_STATUS BOOL coil 8220
12 COIL_SYS_RESET_STATUS BOOL coil 8230
13 COIL_SYS_HAND_STATUS BOOL coil 8240
14 COIL_SYS_AUTO_STATUS BOOL coil 8250
15 COIL_SYS_INIT_STATUS BOOL coil 8260
16 COIL_REQUEST_REC_MSG_STATUS BOOL coil 8500
17 COIL_REQUEST_SEND_MSG_STATUS BOOL coil 8510 request_send_msg_status
18 REG_MSG_ELECTROLYTE_USE_NUM INT16 hold_register 11000
19 REG_MSG_ELECTROLYTE_NUM INT16 hold_register 11002 unilab_send_msg_electrolyte_num
20 REG_MSG_ELECTROLYTE_VOLUME INT16 hold_register 11004 unilab_send_msg_electrolyte_vol
21 REG_MSG_ASSEMBLY_TYPE INT16 hold_register 11006 unilab_send_msg_assembly_type
22 REG_MSG_ASSEMBLY_PRESSURE INT16 hold_register 11008 unilab_send_msg_assembly_pressure
23 REG_DATA_ASSEMBLY_COIN_CELL_NUM INT16 hold_register 10000 data_assembly_coin_cell_num
24 REG_DATA_OPEN_CIRCUIT_VOLTAGE FLOAT32 hold_register 10002 data_open_circuit_voltage
25 REG_DATA_AXIS_X_POS FLOAT32 hold_register 10004
26 REG_DATA_AXIS_Y_POS FLOAT32 hold_register 10006
27 REG_DATA_AXIS_Z_POS FLOAT32 hold_register 10008
28 REG_DATA_POLE_WEIGHT FLOAT32 hold_register 10010 data_pole_weight
29 REG_DATA_ASSEMBLY_PER_TIME FLOAT32 hold_register 10012 data_assembly_time
30 REG_DATA_ASSEMBLY_PRESSURE INT16 hold_register 10014 data_assembly_pressure
31 REG_DATA_ELECTROLYTE_VOLUME INT16 hold_register 10016 data_electrolyte_volume
32 REG_DATA_COIN_NUM INT16 hold_register 10018 data_coin_num
33 REG_DATA_ELECTROLYTE_CODE STRING hold_register 10020 data_electrolyte_code()
34 REG_DATA_COIN_CELL_CODE STRING hold_register 10030 data_coin_cell_code()
35 REG_DATA_STACK_VISON_CODE STRING hold_register 12004 data_stack_vision_code()
36 REG_DATA_GLOVE_BOX_PRESSURE FLOAT32 hold_register 10050 data_glove_box_pressure
37 REG_DATA_GLOVE_BOX_WATER_CONTENT FLOAT32 hold_register 10052 data_glove_box_water_content
38 REG_DATA_GLOVE_BOX_O2_CONTENT FLOAT32 hold_register 10054 data_glove_box_o2_content
39 UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM BOOL coil 8720
40 UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM BOOL coil 8520
41 REG_MSG_ELECTROLYTE_NUM_USED INT16 hold_register 496
42 REG_DATA_ELECTROLYTE_USE_NUM INT16 hold_register 10000
43 UNILAB_SEND_FINISHED_CMD BOOL coil 8730
44 UNILAB_RECE_FINISHED_CMD BOOL coil 8530
45 REG_DATA_ASSEMBLY_TYPE INT16 hold_register 10018 ASSEMBLY_TYPE7or8
46 COIL_ALUMINUM_FOIL BOOL 使用铝箔垫 coil 8340
47 REG_MSG_NE_PLATE_MATRIX INT16 负极片矩阵点位 hold_register 440
48 REG_MSG_SEPARATOR_PLATE_MATRIX INT16 隔膜矩阵点位 hold_register 450
49 REG_MSG_TIP_BOX_MATRIX INT16 移液枪头矩阵点位 hold_register 480
50 REG_MSG_NE_PLATE_NUM INT16 负极片盘数 hold_register 443
51 REG_MSG_SEPARATOR_PLATE_NUM INT16 隔膜盘数 hold_register 453
52 REG_MSG_PRESS_MODE BOOL 压制模式(false:压力检测模式,True:距离模式) coil 8360 电池压制模式
53
54 BOOL 视觉对位(false:使用,true:忽略) coil 8300 视觉对位
55 BOOL 复检(false:使用,true:忽略) coil 8310 视觉复检
56 BOOL 手套箱_左仓(false:使用,true:忽略) coil 8320 手套箱左仓
57 BOOL 手套箱_右仓(false:使用,true:忽略) coil 8420 手套箱右仓
58 BOOL 真空检知(false:使用,true:忽略) coil 8350 真空检知
59 BOOL 电解液添加模式(false:单次滴液,true:二次滴液) coil 8370 滴液模式
60 BOOL 正极片称重(false:使用,true:忽略) coil 8380 正极片称重
61 BOOL 正负极片组装方式(false:正装,true:倒装) coil 8390 正负极反装
62 BOOL 压制清洁(false:使用,true:忽略) coil 8400 压制清洁
63 BOOL 物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘) coil 8410 负极片摆盘方式
64 REG_MSG_BATTERY_CLEAN_IGNORE BOOL 忽略电池清洁(false:使用,true:忽略) coil 8460

View File

@@ -0,0 +1,130 @@
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
COIL_SYS_START_CMD,BOOL,,,,coil,8010,
COIL_SYS_STOP_CMD,BOOL,,,,coil,8020,
COIL_SYS_RESET_CMD,BOOL,,,,coil,8030,
COIL_SYS_HAND_CMD,BOOL,,,,coil,8040,
COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050,
COIL_SYS_INIT_CMD,BOOL,,,,coil,8060,
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700,
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd
COIL_SYS_START_STATUS,BOOL,,,,coil,8210,
COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220,
COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230,
COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240,
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250,
COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260,
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500,
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000,
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004,
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006,
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008,
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume
REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code()
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code()
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code()
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720,
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520,
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496,
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000,
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730,
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530,
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8
REG_UNILAB_INTERACT,BOOL,,,,coil,8450,
,,,,,coil,8320,
COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340,
REG_MSG_NE_PLATE_MATRIX,INT16,,,,hold_register,440,
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,,,hold_register,450,
REG_MSG_TIP_BOX_MATRIX,INT16,,,,hold_register,480,
REG_MSG_NE_PLATE_NUM,INT16,,,,hold_register,443,
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,,,hold_register,453,
REG_MSG_PRESS_MODE,BOOL,,,,coil,8360,
,BOOL,,,,coil,8300,
,BOOL,,,,coil,8310,
COIL_GB_L_IGNORE_CMD,BOOL,,,,coil,8320,
COIL_GB_R_IGNORE_CMD,BOOL,,,,coil,8420,
,BOOL,,,,coil,8350,
COIL_ELECTROLYTE_DUAL_DROP_MODE,BOOL,,,,coil,8370,
,BOOL,,,,coil,8380,
,BOOL,,,,coil,8390,
,BOOL,,,,coil,8400,
,BOOL,,,,coil,8410,
REG_MSG_DUAL_DROP_FIRST_VOLUME,INT16,,,,hold_register,4001,
COIL_DUAL_DROP_SUCTION_TIMING,BOOL,,,,coil,8430,
COIL_DUAL_DROP_START_TIMING,BOOL,,,,coil,8470,
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,,,coil,8460,
COIL_ALARM_100_SYSTEM_ERROR,BOOL,,,,coil,1000,异常100-系统异常
COIL_ALARM_101_EMERGENCY_STOP,BOOL,,,,coil,1010,异常101-急停
COIL_ALARM_111_GLOVEBOX_EMERGENCY_STOP,BOOL,,,,coil,1110,异常111-手套箱急停
COIL_ALARM_112_GLOVEBOX_GRATING_BLOCKED,BOOL,,,,coil,1120,异常112-手套箱内光栅遮挡
COIL_ALARM_160_PIPETTE_TIP_SHORTAGE,BOOL,,,,coil,1600,异常160-移液枪头缺料
COIL_ALARM_161_POSITIVE_SHELL_SHORTAGE,BOOL,,,,coil,1610,异常161-正极壳缺料
COIL_ALARM_162_ALUMINUM_FOIL_SHORTAGE,BOOL,,,,coil,1620,异常162-铝箔垫缺料
COIL_ALARM_163_POSITIVE_PLATE_SHORTAGE,BOOL,,,,coil,1630,异常163-正极片缺料
COIL_ALARM_164_SEPARATOR_SHORTAGE,BOOL,,,,coil,1640,异常164-隔膜缺料
COIL_ALARM_165_NEGATIVE_PLATE_SHORTAGE,BOOL,,,,coil,1650,异常165-负极片缺料
COIL_ALARM_166_FLAT_WASHER_SHORTAGE,BOOL,,,,coil,1660,异常166-平垫缺料
COIL_ALARM_167_SPRING_WASHER_SHORTAGE,BOOL,,,,coil,1670,异常167-弹垫缺料
COIL_ALARM_168_NEGATIVE_SHELL_SHORTAGE,BOOL,,,,coil,1680,异常168-负极壳缺料
COIL_ALARM_169_FINISHED_BATTERY_FULL,BOOL,,,,coil,1690,异常169-成品电池满料
COIL_ALARM_201_SERVO_AXIS_01_ERROR,BOOL,,,,coil,2010,异常201-伺服轴01异常
COIL_ALARM_202_SERVO_AXIS_02_ERROR,BOOL,,,,coil,2020,异常202-伺服轴02异常
COIL_ALARM_203_SERVO_AXIS_03_ERROR,BOOL,,,,coil,2030,异常203-伺服轴03异常
COIL_ALARM_204_SERVO_AXIS_04_ERROR,BOOL,,,,coil,2040,异常204-伺服轴04异常
COIL_ALARM_205_SERVO_AXIS_05_ERROR,BOOL,,,,coil,2050,异常205-伺服轴05异常
COIL_ALARM_206_SERVO_AXIS_06_ERROR,BOOL,,,,coil,2060,异常206-伺服轴06异常
COIL_ALARM_207_SERVO_AXIS_07_ERROR,BOOL,,,,coil,2070,异常207-伺服轴07异常
COIL_ALARM_208_SERVO_AXIS_08_ERROR,BOOL,,,,coil,2080,异常208-伺服轴08异常
COIL_ALARM_209_SERVO_AXIS_09_ERROR,BOOL,,,,coil,2090,异常209-伺服轴09异常
COIL_ALARM_210_SERVO_AXIS_10_ERROR,BOOL,,,,coil,2100,异常210-伺服轴10异常
COIL_ALARM_211_SERVO_AXIS_11_ERROR,BOOL,,,,coil,2110,异常211-伺服轴11异常
COIL_ALARM_212_SERVO_AXIS_12_ERROR,BOOL,,,,coil,2120,异常212-伺服轴12异常
COIL_ALARM_213_SERVO_AXIS_13_ERROR,BOOL,,,,coil,2130,异常213-伺服轴13异常
COIL_ALARM_214_SERVO_AXIS_14_ERROR,BOOL,,,,coil,2140,异常214-伺服轴14异常
COIL_ALARM_250_OTHER_COMPONENT_ERROR,BOOL,,,,coil,2500,异常250-其他元件异常
COIL_ALARM_251_PIPETTE_COMM_ERROR,BOOL,,,,coil,2510,异常251-移液枪通讯异常
COIL_ALARM_252_PIPETTE_ALARM,BOOL,,,,coil,2520,异常252-移液枪报警
COIL_ALARM_256_ELECTRIC_GRIPPER_ERROR,BOOL,,,,coil,2560,异常256-电爪异常
COIL_ALARM_262_RB_UNKNOWN_POSITION_ERROR,BOOL,,,,coil,2620,异常262-RB报警未知点位错误
COIL_ALARM_263_RB_XYZ_PARAM_LIMIT_ERROR,BOOL,,,,coil,2630,异常263-RB报警X、Y、Z参数超限制
COIL_ALARM_264_RB_VISION_PARAM_ERROR,BOOL,,,,coil,2640,异常264-RB报警视觉参数误差过大
COIL_ALARM_265_RB_NOZZLE_1_PICK_FAIL,BOOL,,,,coil,2650,异常265-RB报警1#吸嘴取料失败
COIL_ALARM_266_RB_NOZZLE_2_PICK_FAIL,BOOL,,,,coil,2660,异常266-RB报警2#吸嘴取料失败
COIL_ALARM_267_RB_NOZZLE_3_PICK_FAIL,BOOL,,,,coil,2670,异常267-RB报警3#吸嘴取料失败
COIL_ALARM_268_RB_NOZZLE_4_PICK_FAIL,BOOL,,,,coil,2680,异常268-RB报警4#吸嘴取料失败
COIL_ALARM_269_RB_TRAY_PICK_FAIL,BOOL,,,,coil,2690,异常269-RB报警取物料盘失败
COIL_ALARM_280_RB_COLLISION_ERROR,BOOL,,,,coil,2800,异常280-RB碰撞异常
COIL_ALARM_290_VISION_SYSTEM_COMM_ERROR,BOOL,,,,coil,2900,异常290-视觉系统通讯异常
COIL_ALARM_291_VISION_ALIGNMENT_NG,BOOL,,,,coil,2910,异常291-视觉对位NG异常
COIL_ALARM_292_BARCODE_SCANNER_COMM_ERROR,BOOL,,,,coil,2920,异常292-扫码枪通讯异常
COIL_ALARM_310_OCV_TRANSFER_NOZZLE_SUCTION_ERROR,BOOL,,,,coil,3100,异常310-开电移载吸嘴吸真空异常
COIL_ALARM_311_OCV_TRANSFER_NOZZLE_BREAK_ERROR,BOOL,,,,coil,3110,异常311-开电移载吸嘴破真空异常
COIL_ALARM_312_WEIGHT_TRANSFER_NOZZLE_SUCTION_ERROR,BOOL,,,,coil,3120,异常312-称重移载吸嘴吸真空异常
COIL_ALARM_313_WEIGHT_TRANSFER_NOZZLE_BREAK_ERROR,BOOL,,,,coil,3130,异常313-称重移载吸嘴破真空异常
COIL_ALARM_340_OCV_NOZZLE_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3400,异常340-开路电压吸嘴移载气缸异常
COIL_ALARM_342_OCV_NOZZLE_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3420,异常342-开路电压吸嘴升降气缸异常
COIL_ALARM_344_OCV_CRIMPING_CYLINDER_ERROR,BOOL,,,,coil,3440,异常344-开路电压旋压气缸异常
COIL_ALARM_350_WEIGHT_NOZZLE_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3500,异常350-称重吸嘴移载气缸异常
COIL_ALARM_352_WEIGHT_NOZZLE_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3520,异常352-称重吸嘴升降气缸异常
COIL_ALARM_354_CLEANING_CLOTH_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3540,异常354-清洗无尘布移载气缸异常
COIL_ALARM_356_CLEANING_CLOTH_PRESS_CYLINDER_ERROR,BOOL,,,,coil,3560,异常356-清洗无尘布压紧气缸异常
COIL_ALARM_360_ELECTROLYTE_BOTTLE_POSITION_CYLINDER_ERROR,BOOL,,,,coil,3600,异常360-电解液瓶定位气缸异常
COIL_ALARM_362_PIPETTE_TIP_BOX_POSITION_CYLINDER_ERROR,BOOL,,,,coil,3620,异常362-移液枪头盒定位气缸异常
COIL_ALARM_364_REAGENT_BOTTLE_GRIPPER_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3640,异常364-试剂瓶夹爪升降气缸异常
COIL_ALARM_366_REAGENT_BOTTLE_GRIPPER_CYLINDER_ERROR,BOOL,,,,coil,3660,异常366-试剂瓶夹爪气缸异常
COIL_ALARM_370_PRESS_MODULE_BLOW_CYLINDER_ERROR,BOOL,,,,coil,3700,异常370-压制模块吹气气缸异常
COIL_ALARM_151_ELECTROLYTE_BOTTLE_POSITION_ERROR,BOOL,,,,coil,1510,异常151-电解液瓶定位在籍异常
COIL_ALARM_152_ELECTROLYTE_BOTTLE_CAP_ERROR,BOOL,,,,coil,1520,异常152-电解液瓶盖在籍异常
1 Name DataType InitValue Comment Attribute DeviceType Address
2 COIL_SYS_START_CMD BOOL coil 8010
3 COIL_SYS_STOP_CMD BOOL coil 8020
4 COIL_SYS_RESET_CMD BOOL coil 8030
5 COIL_SYS_HAND_CMD BOOL coil 8040
6 COIL_SYS_AUTO_CMD BOOL coil 8050
7 COIL_SYS_INIT_CMD BOOL coil 8060
8 COIL_UNILAB_SEND_MSG_SUCC_CMD BOOL coil 8700
9 COIL_UNILAB_REC_MSG_SUCC_CMD BOOL coil 8710 unilab_rec_msg_succ_cmd
10 COIL_SYS_START_STATUS BOOL coil 8210
11 COIL_SYS_STOP_STATUS BOOL coil 8220
12 COIL_SYS_RESET_STATUS BOOL coil 8230
13 COIL_SYS_HAND_STATUS BOOL coil 8240
14 COIL_SYS_AUTO_STATUS BOOL coil 8250
15 COIL_SYS_INIT_STATUS BOOL coil 8260
16 COIL_REQUEST_REC_MSG_STATUS BOOL coil 8500
17 COIL_REQUEST_SEND_MSG_STATUS BOOL coil 8510 request_send_msg_status
18 REG_MSG_ELECTROLYTE_USE_NUM INT16 hold_register 11000
19 REG_MSG_ELECTROLYTE_NUM INT16 hold_register 11002 unilab_send_msg_electrolyte_num
20 REG_MSG_ELECTROLYTE_VOLUME INT16 hold_register 11004 unilab_send_msg_electrolyte_vol
21 REG_MSG_ASSEMBLY_TYPE INT16 hold_register 11006 unilab_send_msg_assembly_type
22 REG_MSG_ASSEMBLY_PRESSURE INT16 hold_register 11008 unilab_send_msg_assembly_pressure
23 REG_DATA_ASSEMBLY_COIN_CELL_NUM INT16 hold_register 10000 data_assembly_coin_cell_num
24 REG_DATA_OPEN_CIRCUIT_VOLTAGE FLOAT32 hold_register 10002 data_open_circuit_voltage
25 REG_DATA_AXIS_X_POS FLOAT32 hold_register 10004
26 REG_DATA_AXIS_Y_POS FLOAT32 hold_register 10006
27 REG_DATA_AXIS_Z_POS FLOAT32 hold_register 10008
28 REG_DATA_POLE_WEIGHT FLOAT32 hold_register 10010 data_pole_weight
29 REG_DATA_ASSEMBLY_PER_TIME FLOAT32 hold_register 10012 data_assembly_time
30 REG_DATA_ASSEMBLY_PRESSURE INT16 hold_register 10014 data_assembly_pressure
31 REG_DATA_ELECTROLYTE_VOLUME INT16 hold_register 10016 data_electrolyte_volume
32 REG_DATA_COIN_NUM INT16 hold_register 10018 data_coin_num
33 REG_DATA_ELECTROLYTE_CODE STRING hold_register 10020 data_electrolyte_code()
34 REG_DATA_COIN_CELL_CODE STRING hold_register 10030 data_coin_cell_code()
35 REG_DATA_STACK_VISON_CODE STRING hold_register 12004 data_stack_vision_code()
36 REG_DATA_GLOVE_BOX_PRESSURE FLOAT32 hold_register 10050 data_glove_box_pressure
37 REG_DATA_GLOVE_BOX_WATER_CONTENT FLOAT32 hold_register 10052 data_glove_box_water_content
38 REG_DATA_GLOVE_BOX_O2_CONTENT FLOAT32 hold_register 10054 data_glove_box_o2_content
39 UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM BOOL coil 8720
40 UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM BOOL coil 8520
41 REG_MSG_ELECTROLYTE_NUM_USED INT16 hold_register 496
42 REG_DATA_ELECTROLYTE_USE_NUM INT16 hold_register 10000
43 UNILAB_SEND_FINISHED_CMD BOOL coil 8730
44 UNILAB_RECE_FINISHED_CMD BOOL coil 8530
45 REG_DATA_ASSEMBLY_TYPE INT16 hold_register 10018 ASSEMBLY_TYPE7or8
46 REG_UNILAB_INTERACT BOOL coil 8450
47 coil 8320
48 COIL_ALUMINUM_FOIL BOOL coil 8340
49 REG_MSG_NE_PLATE_MATRIX INT16 hold_register 440
50 REG_MSG_SEPARATOR_PLATE_MATRIX INT16 hold_register 450
51 REG_MSG_TIP_BOX_MATRIX INT16 hold_register 480
52 REG_MSG_NE_PLATE_NUM INT16 hold_register 443
53 REG_MSG_SEPARATOR_PLATE_NUM INT16 hold_register 453
54 REG_MSG_PRESS_MODE BOOL coil 8360
55 BOOL coil 8300
56 BOOL coil 8310
57 COIL_GB_L_IGNORE_CMD BOOL coil 8320
58 COIL_GB_R_IGNORE_CMD BOOL coil 8420
59 BOOL coil 8350
60 COIL_ELECTROLYTE_DUAL_DROP_MODE BOOL coil 8370
61 BOOL coil 8380
62 BOOL coil 8390
63 BOOL coil 8400
64 BOOL coil 8410
65 REG_MSG_DUAL_DROP_FIRST_VOLUME INT16 hold_register 4001
66 COIL_DUAL_DROP_SUCTION_TIMING BOOL coil 8430
67 COIL_DUAL_DROP_START_TIMING BOOL coil 8470
68 REG_MSG_BATTERY_CLEAN_IGNORE BOOL coil 8460
69 COIL_ALARM_100_SYSTEM_ERROR BOOL coil 1000 异常100-系统异常
70 COIL_ALARM_101_EMERGENCY_STOP BOOL coil 1010 异常101-急停
71 COIL_ALARM_111_GLOVEBOX_EMERGENCY_STOP BOOL coil 1110 异常111-手套箱急停
72 COIL_ALARM_112_GLOVEBOX_GRATING_BLOCKED BOOL coil 1120 异常112-手套箱内光栅遮挡
73 COIL_ALARM_160_PIPETTE_TIP_SHORTAGE BOOL coil 1600 异常160-移液枪头缺料
74 COIL_ALARM_161_POSITIVE_SHELL_SHORTAGE BOOL coil 1610 异常161-正极壳缺料
75 COIL_ALARM_162_ALUMINUM_FOIL_SHORTAGE BOOL coil 1620 异常162-铝箔垫缺料
76 COIL_ALARM_163_POSITIVE_PLATE_SHORTAGE BOOL coil 1630 异常163-正极片缺料
77 COIL_ALARM_164_SEPARATOR_SHORTAGE BOOL coil 1640 异常164-隔膜缺料
78 COIL_ALARM_165_NEGATIVE_PLATE_SHORTAGE BOOL coil 1650 异常165-负极片缺料
79 COIL_ALARM_166_FLAT_WASHER_SHORTAGE BOOL coil 1660 异常166-平垫缺料
80 COIL_ALARM_167_SPRING_WASHER_SHORTAGE BOOL coil 1670 异常167-弹垫缺料
81 COIL_ALARM_168_NEGATIVE_SHELL_SHORTAGE BOOL coil 1680 异常168-负极壳缺料
82 COIL_ALARM_169_FINISHED_BATTERY_FULL BOOL coil 1690 异常169-成品电池满料
83 COIL_ALARM_201_SERVO_AXIS_01_ERROR BOOL coil 2010 异常201-伺服轴01异常
84 COIL_ALARM_202_SERVO_AXIS_02_ERROR BOOL coil 2020 异常202-伺服轴02异常
85 COIL_ALARM_203_SERVO_AXIS_03_ERROR BOOL coil 2030 异常203-伺服轴03异常
86 COIL_ALARM_204_SERVO_AXIS_04_ERROR BOOL coil 2040 异常204-伺服轴04异常
87 COIL_ALARM_205_SERVO_AXIS_05_ERROR BOOL coil 2050 异常205-伺服轴05异常
88 COIL_ALARM_206_SERVO_AXIS_06_ERROR BOOL coil 2060 异常206-伺服轴06异常
89 COIL_ALARM_207_SERVO_AXIS_07_ERROR BOOL coil 2070 异常207-伺服轴07异常
90 COIL_ALARM_208_SERVO_AXIS_08_ERROR BOOL coil 2080 异常208-伺服轴08异常
91 COIL_ALARM_209_SERVO_AXIS_09_ERROR BOOL coil 2090 异常209-伺服轴09异常
92 COIL_ALARM_210_SERVO_AXIS_10_ERROR BOOL coil 2100 异常210-伺服轴10异常
93 COIL_ALARM_211_SERVO_AXIS_11_ERROR BOOL coil 2110 异常211-伺服轴11异常
94 COIL_ALARM_212_SERVO_AXIS_12_ERROR BOOL coil 2120 异常212-伺服轴12异常
95 COIL_ALARM_213_SERVO_AXIS_13_ERROR BOOL coil 2130 异常213-伺服轴13异常
96 COIL_ALARM_214_SERVO_AXIS_14_ERROR BOOL coil 2140 异常214-伺服轴14异常
97 COIL_ALARM_250_OTHER_COMPONENT_ERROR BOOL coil 2500 异常250-其他元件异常
98 COIL_ALARM_251_PIPETTE_COMM_ERROR BOOL coil 2510 异常251-移液枪通讯异常
99 COIL_ALARM_252_PIPETTE_ALARM BOOL coil 2520 异常252-移液枪报警
100 COIL_ALARM_256_ELECTRIC_GRIPPER_ERROR BOOL coil 2560 异常256-电爪异常
101 COIL_ALARM_262_RB_UNKNOWN_POSITION_ERROR BOOL coil 2620 异常262-RB报警:未知点位错误
102 COIL_ALARM_263_RB_XYZ_PARAM_LIMIT_ERROR BOOL coil 2630 异常263-RB报警:X、Y、Z参数超限制
103 COIL_ALARM_264_RB_VISION_PARAM_ERROR BOOL coil 2640 异常264-RB报警:视觉参数误差过大
104 COIL_ALARM_265_RB_NOZZLE_1_PICK_FAIL BOOL coil 2650 异常265-RB报警:1#吸嘴取料失败
105 COIL_ALARM_266_RB_NOZZLE_2_PICK_FAIL BOOL coil 2660 异常266-RB报警:2#吸嘴取料失败
106 COIL_ALARM_267_RB_NOZZLE_3_PICK_FAIL BOOL coil 2670 异常267-RB报警:3#吸嘴取料失败
107 COIL_ALARM_268_RB_NOZZLE_4_PICK_FAIL BOOL coil 2680 异常268-RB报警:4#吸嘴取料失败
108 COIL_ALARM_269_RB_TRAY_PICK_FAIL BOOL coil 2690 异常269-RB报警:取物料盘失败
109 COIL_ALARM_280_RB_COLLISION_ERROR BOOL coil 2800 异常280-RB碰撞异常
110 COIL_ALARM_290_VISION_SYSTEM_COMM_ERROR BOOL coil 2900 异常290-视觉系统通讯异常
111 COIL_ALARM_291_VISION_ALIGNMENT_NG BOOL coil 2910 异常291-视觉对位NG异常
112 COIL_ALARM_292_BARCODE_SCANNER_COMM_ERROR BOOL coil 2920 异常292-扫码枪通讯异常
113 COIL_ALARM_310_OCV_TRANSFER_NOZZLE_SUCTION_ERROR BOOL coil 3100 异常310-开电移载吸嘴吸真空异常
114 COIL_ALARM_311_OCV_TRANSFER_NOZZLE_BREAK_ERROR BOOL coil 3110 异常311-开电移载吸嘴破真空异常
115 COIL_ALARM_312_WEIGHT_TRANSFER_NOZZLE_SUCTION_ERROR BOOL coil 3120 异常312-称重移载吸嘴吸真空异常
116 COIL_ALARM_313_WEIGHT_TRANSFER_NOZZLE_BREAK_ERROR BOOL coil 3130 异常313-称重移载吸嘴破真空异常
117 COIL_ALARM_340_OCV_NOZZLE_TRANSFER_CYLINDER_ERROR BOOL coil 3400 异常340-开路电压吸嘴移载气缸异常
118 COIL_ALARM_342_OCV_NOZZLE_LIFT_CYLINDER_ERROR BOOL coil 3420 异常342-开路电压吸嘴升降气缸异常
119 COIL_ALARM_344_OCV_CRIMPING_CYLINDER_ERROR BOOL coil 3440 异常344-开路电压旋压气缸异常
120 COIL_ALARM_350_WEIGHT_NOZZLE_TRANSFER_CYLINDER_ERROR BOOL coil 3500 异常350-称重吸嘴移载气缸异常
121 COIL_ALARM_352_WEIGHT_NOZZLE_LIFT_CYLINDER_ERROR BOOL coil 3520 异常352-称重吸嘴升降气缸异常
122 COIL_ALARM_354_CLEANING_CLOTH_TRANSFER_CYLINDER_ERROR BOOL coil 3540 异常354-清洗无尘布移载气缸异常
123 COIL_ALARM_356_CLEANING_CLOTH_PRESS_CYLINDER_ERROR BOOL coil 3560 异常356-清洗无尘布压紧气缸异常
124 COIL_ALARM_360_ELECTROLYTE_BOTTLE_POSITION_CYLINDER_ERROR BOOL coil 3600 异常360-电解液瓶定位气缸异常
125 COIL_ALARM_362_PIPETTE_TIP_BOX_POSITION_CYLINDER_ERROR BOOL coil 3620 异常362-移液枪头盒定位气缸异常
126 COIL_ALARM_364_REAGENT_BOTTLE_GRIPPER_LIFT_CYLINDER_ERROR BOOL coil 3640 异常364-试剂瓶夹爪升降气缸异常
127 COIL_ALARM_366_REAGENT_BOTTLE_GRIPPER_CYLINDER_ERROR BOOL coil 3660 异常366-试剂瓶夹爪气缸异常
128 COIL_ALARM_370_PRESS_MODULE_BLOW_CYLINDER_ERROR BOOL coil 3700 异常370-压制模块吹气气缸异常
129 COIL_ALARM_151_ELECTROLYTE_BOTTLE_POSITION_ERROR BOOL coil 1510 异常151-电解液瓶定位在籍异常
130 COIL_ALARM_152_ELECTROLYTE_BOTTLE_CAP_ERROR BOOL coil 1520 异常152-电解液瓶盖在籍异常

View File

@@ -0,0 +1,6 @@
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
20260110_153356,0.0,23.889999389648438,17.0,3609,40,7,deaoR,
20260110_153640,3.430999994277954,23.719999313354492,162.0,3560,40,7,deaoR,
20260110_162905,0.0,23.920000076293945,772.0,3625,30,7,LG600001,YS103130
20260110_163526,3.430999994277954,24.010000228881836,234.0,3690,30,7,LG600001,YS102964
20260110_164530,0.0,23.589998245239258,219.0,3690,30,7,LG600001,YS102857
1 Time open_circuit_voltage pole_weight assembly_time assembly_pressure electrolyte_volume coin_num electrolyte_code coin_cell_code
2 20260110_153356 0.0 23.889999389648438 17.0 3609 40 7 deaoR 
3 20260110_153640 3.430999994277954 23.719999313354492 162.0 3560 40 7 deaoR 
4 20260110_162905 0.0 23.920000076293945 772.0 3625 30 7 LG600001 YS103130
5 20260110_163526 3.430999994277954 24.010000228881836 234.0 3690 30 7 LG600001 YS102964
6 20260110_164530 0.0 23.589998245239258 219.0 3690 30 7 LG600001 YS102857

View File

@@ -0,0 +1,107 @@
# 电池组装资源冲突问题修复说明
## 问题描述
在运行 `func_allpack_cmd` 函数时,遇到以下错误:
```
ValueError: Resource 'battery_0' already assigned to deck
```
**错误位置**`coin_cell_assembly.py` 第 849 行
```python
liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None)
```
## 原因分析
1. **资源名称冲突**
- 每次创建电池资源使用固定格式 `battery_{coin_num_N}`
- 如果程序重启或断点恢复,`coin_num_N` 可能重置为 0
- Deck 上可能已存在 `battery_0` 等同名资源
2. **缺少冲突处理**
- 在分配资源前没有检查目标位置是否已有资源
- 没有清理机制来移除旧资源
## 解决方案
### 1. 使用时间戳确保资源名称唯一
```python
# 之前
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", ...)
# 修复后
timestamp_suffix = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
battery_name = f"battery_{self.coin_num_N}_{timestamp_suffix}"
battery = ElectrodeSheet(name=battery_name, ...)
```
### 2. 添加资源冲突检查和清理
```python
# 检查目标位置是否已有资源
target_slot = liaopan3.children[self.coin_num_N]
if target_slot.children:
logger.warning(f"位置 {self.coin_num_N} 已有资源,将先卸载旧资源")
try:
# 卸载所有现有子资源
for child in list(target_slot.children):
target_slot.unassign_child_resource(child)
logger.info(f"已卸载旧资源: {child.name}")
except Exception as e:
logger.error(f"卸载旧资源时出错: {e}")
```
### 3. 增强错误处理
```python
# 分配新资源到目标位置
try:
target_slot.assign_child_resource(battery, location=None)
logger.info(f"成功分配电池 {battery_name} 到位置 {self.coin_num_N}")
except Exception as e:
logger.error(f"分配电池资源失败: {e}")
raise
```
## 修复效果
**不再出现重复资源名称错误**
- 每个电池资源都有唯一的时间戳后缀
- 即使 `coin_num_N` 相同,资源名称也不会冲突
**自动清理旧资源**
- 在分配新资源前检查目标位置
- 自动卸载已存在的旧资源
**增强日志记录**
- 记录资源卸载操作
- 记录资源分配成功/失败
- 便于调试和问题追踪
## 测试建议
1. **正常运行测试**
```python
workstation.func_allpack_cmd(
elec_num=1,
elec_use_num=1,
elec_vol=20,
file_path="..."
)
```
2. **断点恢复测试**
- 运行一次后中断
- 再次运行相同参数
- 验证不会出现资源冲突错误
3. **连续运行测试**
- 连续多次运行
- 验证每次都能正常分配资源
## 相关文件
- `coin_cell_assembly.py` - 第 838-875 行(`func_pack_get_msg_cmd` 函数)

View File

@@ -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

View File

@@ -32,112 +32,7 @@ bioyond_cell:
feedback: {} feedback: {}
goal: {} goal: {}
goal_default: goal_default:
WH3_x1_y1_z3_1_materialId: '' xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
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
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -147,323 +42,8 @@ bioyond_cell:
feedback: {} feedback: {}
goal: goal:
properties: properties:
WH3_x1_y1_z3_1_materialId:
default: ''
type: string
WH3_x1_y1_z3_1_materialType:
default: ''
type: string
WH3_x1_y1_z3_1_quantity:
default: 0
type: number
WH3_x1_y2_z3_4_materialId:
default: ''
type: string
WH3_x1_y2_z3_4_materialType:
default: ''
type: string
WH3_x1_y2_z3_4_quantity:
default: 0
type: number
WH3_x1_y3_z3_7_materialId:
default: ''
type: string
WH3_x1_y3_z3_7_materialType:
default: ''
type: string
WH3_x1_y3_z3_7_quantity:
default: 0
type: number
WH3_x1_y4_z3_10_materialId:
default: ''
type: string
WH3_x1_y4_z3_10_materialType:
default: ''
type: string
WH3_x1_y4_z3_10_quantity:
default: 0
type: number
WH3_x1_y5_z3_13_materialId:
default: ''
type: string
WH3_x1_y5_z3_13_materialType:
default: ''
type: string
WH3_x1_y5_z3_13_quantity:
default: 0
type: number
WH3_x2_y1_z3_2_materialId:
default: ''
type: string
WH3_x2_y1_z3_2_materialType:
default: ''
type: string
WH3_x2_y1_z3_2_quantity:
default: 0
type: number
WH3_x2_y2_z3_5_materialId:
default: ''
type: string
WH3_x2_y2_z3_5_materialType:
default: ''
type: string
WH3_x2_y2_z3_5_quantity:
default: 0
type: number
WH3_x2_y3_z3_8_materialId:
default: ''
type: string
WH3_x2_y3_z3_8_materialType:
default: ''
type: string
WH3_x2_y3_z3_8_quantity:
default: 0
type: number
WH3_x2_y4_z3_11_materialId:
default: ''
type: string
WH3_x2_y4_z3_11_materialType:
default: ''
type: string
WH3_x2_y4_z3_11_quantity:
default: 0
type: number
WH3_x2_y5_z3_14_materialId:
default: ''
type: string
WH3_x2_y5_z3_14_materialType:
default: ''
type: string
WH3_x2_y5_z3_14_quantity:
default: 0
type: number
WH3_x3_y1_z3_3_materialId:
default: ''
type: string
WH3_x3_y1_z3_3_materialType:
default: ''
type: string
WH3_x3_y1_z3_3_quantity:
default: 0
type: number
WH3_x3_y2_z3_6_materialId:
default: ''
type: string
WH3_x3_y2_z3_6_materialType:
default: ''
type: string
WH3_x3_y2_z3_6_quantity:
default: 0
type: number
WH3_x3_y3_z3_9_materialId:
default: ''
type: string
WH3_x3_y3_z3_9_materialType:
default: ''
type: string
WH3_x3_y3_z3_9_quantity:
default: 0
type: number
WH3_x3_y4_z3_12_materialId:
default: ''
type: string
WH3_x3_y4_z3_12_materialType:
default: ''
type: string
WH3_x3_y4_z3_12_quantity:
default: 0
type: number
WH3_x3_y5_z3_15_materialId:
default: ''
type: string
WH3_x3_y5_z3_15_materialType:
default: ''
type: string
WH3_x3_y5_z3_15_quantity:
default: 0
type: number
WH4_x1_y1_z1_1_materialName:
default: ''
type: string
WH4_x1_y1_z1_1_quantity:
default: 0.0
type: number
WH4_x1_y1_z2_1_materialName:
default: ''
type: string
WH4_x1_y1_z2_1_materialType:
default: ''
type: string
WH4_x1_y1_z2_1_quantity:
default: 0.0
type: number
WH4_x1_y1_z2_1_targetWH:
default: ''
type: string
WH4_x1_y2_z1_6_materialName:
default: ''
type: string
WH4_x1_y2_z1_6_quantity:
default: 0.0
type: number
WH4_x1_y2_z2_4_materialName:
default: ''
type: string
WH4_x1_y2_z2_4_materialType:
default: ''
type: string
WH4_x1_y2_z2_4_quantity:
default: 0.0
type: number
WH4_x1_y2_z2_4_targetWH:
default: ''
type: string
WH4_x1_y3_z1_11_materialName:
default: ''
type: string
WH4_x1_y3_z1_11_quantity:
default: 0.0
type: number
WH4_x1_y3_z2_7_materialName:
default: ''
type: string
WH4_x1_y3_z2_7_materialType:
default: ''
type: string
WH4_x1_y3_z2_7_quantity:
default: 0.0
type: number
WH4_x1_y3_z2_7_targetWH:
default: ''
type: string
WH4_x2_y1_z1_2_materialName:
default: ''
type: string
WH4_x2_y1_z1_2_quantity:
default: 0.0
type: number
WH4_x2_y1_z2_2_materialName:
default: ''
type: string
WH4_x2_y1_z2_2_materialType:
default: ''
type: string
WH4_x2_y1_z2_2_quantity:
default: 0.0
type: number
WH4_x2_y1_z2_2_targetWH:
default: ''
type: string
WH4_x2_y2_z1_7_materialName:
default: ''
type: string
WH4_x2_y2_z1_7_quantity:
default: 0.0
type: number
WH4_x2_y2_z2_5_materialName:
default: ''
type: string
WH4_x2_y2_z2_5_materialType:
default: ''
type: string
WH4_x2_y2_z2_5_quantity:
default: 0.0
type: number
WH4_x2_y2_z2_5_targetWH:
default: ''
type: string
WH4_x2_y3_z1_12_materialName:
default: ''
type: string
WH4_x2_y3_z1_12_quantity:
default: 0.0
type: number
WH4_x2_y3_z2_8_materialName:
default: ''
type: string
WH4_x2_y3_z2_8_materialType:
default: ''
type: string
WH4_x2_y3_z2_8_quantity:
default: 0.0
type: number
WH4_x2_y3_z2_8_targetWH:
default: ''
type: string
WH4_x3_y1_z1_3_materialName:
default: ''
type: string
WH4_x3_y1_z1_3_quantity:
default: 0.0
type: number
WH4_x3_y1_z2_3_materialName:
default: ''
type: string
WH4_x3_y1_z2_3_materialType:
default: ''
type: string
WH4_x3_y1_z2_3_quantity:
default: 0.0
type: number
WH4_x3_y1_z2_3_targetWH:
default: ''
type: string
WH4_x3_y2_z1_8_materialName:
default: ''
type: string
WH4_x3_y2_z1_8_quantity:
default: 0.0
type: number
WH4_x3_y2_z2_6_materialName:
default: ''
type: string
WH4_x3_y2_z2_6_materialType:
default: ''
type: string
WH4_x3_y2_z2_6_quantity:
default: 0.0
type: number
WH4_x3_y2_z2_6_targetWH:
default: ''
type: string
WH4_x3_y3_z2_9_materialName:
default: ''
type: string
WH4_x3_y3_z2_9_materialType:
default: ''
type: string
WH4_x3_y3_z2_9_quantity:
default: 0.0
type: number
WH4_x3_y3_z2_9_targetWH:
default: ''
type: string
WH4_x4_y1_z1_4_materialName:
default: ''
type: string
WH4_x4_y1_z1_4_quantity:
default: 0.0
type: number
WH4_x4_y2_z1_9_materialName:
default: ''
type: string
WH4_x4_y2_z1_9_quantity:
default: 0.0
type: number
WH4_x5_y1_z1_5_materialName:
default: ''
type: string
WH4_x5_y1_z1_5_quantity:
default: 0.0
type: number
WH4_x5_y2_z1_10_materialName:
default: ''
type: string
WH4_x5_y2_z1_10_quantity:
default: 0.0
type: number
xlsx_path: xlsx_path:
default: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025122301.xlsx
type: string type: string
required: [] required: []
type: object type: object
@@ -571,7 +151,14 @@ bioyond_cell:
goal: {} goal: {}
goal_default: goal_default:
xlsx_path: null xlsx_path: null
handles: {} handles:
output:
- data_key: total_orders
data_source: executor
data_type: integer
handler_key: bottle_count
io_type: sink
label: 配液瓶数
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
schema: schema:
@@ -591,6 +178,38 @@ bioyond_cell:
title: create_orders参数 title: create_orders参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-create_orders_v2:
feedback: {}
goal: {}
goal_default:
xlsx_path: null
handles:
output:
- data_key: total_orders
data_source: executor
data_type: integer
handler_key: bottle_count
io_type: sink
label: 配液瓶数
placeholder_keys: {}
result: {}
schema:
description: 从Excel解析并创建实验V2版本
properties:
feedback: {}
goal:
properties:
xlsx_path:
type: string
required:
- xlsx_path
type: object
result: {}
required:
- goal
title: create_orders_v2参数
type: object
type: UniLabJsonCommand
auto-create_sample: auto-create_sample:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -821,154 +440,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: {}
@@ -1032,6 +503,31 @@ bioyond_cell:
title: scheduler_start参数 title: scheduler_start参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-scheduler_start_and_auto_feeding:
feedback: {}
goal: {}
goal_default:
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 组合函数:先启动调度,然后执行自动化上料
properties:
feedback: {}
goal:
properties:
xlsx_path:
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
type: string
required: []
type: object
result: {}
required:
- goal
title: scheduler_start_and_auto_feeding参数
type: object
type: UniLabJsonCommand
auto-scheduler_stop: auto-scheduler_stop:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -1130,6 +626,47 @@ bioyond_cell:
title: transfer_1_to_2参数 title: transfer_1_to_2参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-transfer_3_to_2:
feedback: {}
goal: {}
goal_default:
source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b
source_x: 1
source_y: 1
source_z: 1
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 3-2 物料转运从3号位置转运到2号位置
properties:
feedback: {}
goal:
properties:
source_wh_id:
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
description: 来源仓库ID
type: string
source_x:
default: 1
description: 来源位置X坐标
type: integer
source_y:
default: 1
description: 来源位置Y坐标
type: integer
source_z:
default: 1
description: 来源位置Z坐标
type: integer
required: []
type: object
result: {}
required:
- goal
title: transfer_3_to_2参数
type: object
type: UniLabJsonCommand
auto-transfer_3_to_2_to_1: auto-transfer_3_to_2_to_1:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -1260,7 +797,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:

View 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

View File

@@ -115,6 +115,117 @@ coincellassemblyworkstation_device:
title: func_allpack_cmd参数 title: func_allpack_cmd参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-func_allpack_cmd_simp:
feedback: {}
goal: {}
goal_default:
assembly_pressure: 4200
assembly_type: 7
battery_clean_ignore: false
battery_pressure_mode: true
dual_drop_first_volume: 25
dual_drop_mode: false
dual_drop_start_timing: false
dual_drop_suction_timing: false
elec_num: null
elec_use_num: null
elec_vol: 50
file_path: /Users/sml/work
fujipian_juzhendianwei: 0
fujipian_panshu: 0
gemo_juzhendianwei: 0
gemopanshu: 0
lvbodian: true
qiangtou_juzhendianwei: 0
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 简化版电池组装函数,整合了参数设置和双滴模式
properties:
feedback: {}
goal:
properties:
assembly_pressure:
default: 4200
description: 电池压制力(N)
type: integer
assembly_type:
default: 7
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
type: integer
battery_clean_ignore:
default: false
description: 是否忽略电池清洁步骤
type: boolean
battery_pressure_mode:
default: true
description: 是否启用压力模式
type: boolean
dual_drop_first_volume:
default: 25
description: 二次滴液第一次排液体积(μL)
type: integer
dual_drop_mode:
default: false
description: 电解液添加模式(false=单次滴液, true=二次滴液)
type: boolean
dual_drop_start_timing:
default: false
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
type: boolean
dual_drop_suction_timing:
default: false
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
type: boolean
elec_num:
description: 电解液瓶数
type: string
elec_use_num:
description: 每瓶电解液组装电池数
type: string
elec_vol:
default: 50
description: 电解液吸液量(μL)
type: integer
file_path:
default: /Users/sml/work
description: 实验记录保存路径
type: string
fujipian_juzhendianwei:
default: 0
description: 负极片矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
fujipian_panshu:
default: 0
description: 负极片盘数
type: integer
gemo_juzhendianwei:
default: 0
description: 隔膜矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
gemopanshu:
default: 0
description: 隔膜盘数
type: integer
lvbodian:
default: true
description: 是否使用铝箔垫片
type: boolean
qiangtou_juzhendianwei:
default: 0
description: 枪头盒矩阵点位。盘位置从1开始计数有效范围1-32, 64-96 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
required:
- elec_num
- elec_use_num
type: object
result: {}
required:
- goal
title: func_allpack_cmd_simp参数
type: object
type: UniLabJsonCommand
auto-func_get_csv_export_status: auto-func_get_csv_export_status:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -178,6 +289,27 @@ coincellassemblyworkstation_device:
title: func_pack_device_init参数 title: func_pack_device_init参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-func_pack_device_init_auto_start_combined:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 组合函数:设备初始化 + 切换自动模式 + 启动
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: func_pack_device_init_auto_start_combined参数
type: object
type: UniLabJsonCommand
auto-func_pack_device_start: auto-func_pack_device_start:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -250,7 +382,15 @@ coincellassemblyworkstation_device:
goal: {} goal: {}
goal_default: goal_default:
bottle_num: null bottle_num: null
handles: {} handles:
input:
- data_key: bottle_num
data_source: workflow
data_type: integer
handler_key: bottle_count
io_type: source
label: 配液瓶数
required: true
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
schema: schema:
@@ -260,7 +400,7 @@ coincellassemblyworkstation_device:
goal: goal:
properties: properties:
bottle_num: bottle_num:
type: string type: integer
required: required:
- bottle_num - bottle_num
type: object type: object
@@ -353,6 +493,125 @@ coincellassemblyworkstation_device:
title: func_read_data_and_output参数 title: func_read_data_and_output参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-func_sendbottle_allpack_multi:
feedback: {}
goal: {}
goal_default:
assembly_pressure: 4200
assembly_type: 7
battery_clean_ignore: false
battery_pressure_mode: true
dual_drop_first_volume: 25
dual_drop_mode: false
dual_drop_start_timing: false
dual_drop_suction_timing: false
elec_num: null
elec_use_num: null
elec_vol: 50
file_path: /Users/sml/work
fujipian_juzhendianwei: 0
fujipian_panshu: 0
gemo_juzhendianwei: 0
gemopanshu: 0
lvbodian: true
qiangtou_juzhendianwei: 0
handles:
input:
- data_key: elec_num
data_source: workflow
data_type: integer
handler_key: bottle_count
io_type: source
label: 配液瓶数
required: true
placeholder_keys: {}
result: {}
schema:
description: 发送瓶数+简化组装函数(适用于第二批次及后续批次),合并了发送瓶数和简化组装流程
properties:
feedback: {}
goal:
properties:
assembly_pressure:
default: 4200
description: 电池压制力(N)
type: integer
assembly_type:
default: 7
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
type: integer
battery_clean_ignore:
default: false
description: 是否忽略电池清洁步骤
type: boolean
battery_pressure_mode:
default: true
description: 是否启用压力模式
type: boolean
dual_drop_first_volume:
default: 25
description: 二次滴液第一次排液体积(μL)
type: integer
dual_drop_mode:
default: false
description: 电解液添加模式(false=单次滴液, true=二次滴液)
type: boolean
dual_drop_start_timing:
default: false
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
type: boolean
dual_drop_suction_timing:
default: false
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
type: boolean
elec_num:
description: 电解液瓶数如果在workflow中已通过handles连接上游(create_orders的bottle_count输出),则此参数会自动从上游获取,无需手动填写;如果单独使用此函数(没有上游连接),则必须手动填写电解液瓶数
type: string
elec_use_num:
description: 每瓶电解液组装电池数
type: string
elec_vol:
default: 50
description: 电解液吸液量(μL)
type: integer
file_path:
default: /Users/sml/work
description: 实验记录保存路径
type: string
fujipian_juzhendianwei:
default: 0
description: 负极片矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
fujipian_panshu:
default: 0
description: 负极片盘数
type: integer
gemo_juzhendianwei:
default: 0
description: 隔膜矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
gemopanshu:
default: 0
description: 隔膜盘数
type: integer
lvbodian:
default: true
description: 是否使用铝箔垫片
type: boolean
qiangtou_juzhendianwei:
default: 0
description: 枪头盒矩阵点位。盘位置从1开始计数有效范围1-32, 64-96 (写入值比实际位置少1例如写0取盘位1写1取盘位2)
type: integer
required:
- elec_num
- elec_use_num
type: object
result: {}
required:
- goal
title: func_sendbottle_allpack_multi参数
type: object
type: UniLabJsonCommand
auto-func_stop_read_data: auto-func_stop_read_data:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -477,171 +736,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 +759,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:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -1,4 +0,0 @@
"""Battery-related resource classes for coin cell assembly"""

View File

@@ -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)
carrier_size_x = 120.0
carrier_size_y = 250.0
carrier_size_z = 50.0
Args: # 瓶位尺寸
name: 瓶架名称 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
Returns:
ItemizedCarrier: 包含12个瓶位的瓶架
"""
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( 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, name=name,
size_x=300.0, size_x=carrier_size_x,
size_y=100.0, size_y=carrier_size_y,
size_z=80.0, size_z=carrier_size_z,
num_items_x=4,
num_items_y=3,
sites=sites, sites=sites,
category="bottle_carrier", 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

View File

@@ -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:
"""获取电极类型"""
return self._electrode_type
# TODO: 这个还要不要给self._unilabos_state赋值的
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进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
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进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data

View File

@@ -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: Args:
name: 弹夹名称 name: 洞位名称
direction: 堆叠方向
Returns: resources: 资源列表
ItemizedCarrier: 包含4个槽位的弹夹架 max_sheets: 最大极片数量
""" """
sites = create_ordered_items_2d( super().__init__(
klass=ResourceHolder,
num_items_x=2,
num_items_y=2,
dx=10.0,
dy=10.0,
dz=0.0,
item_dx=50.0,
item_dy=30.0,
size_x=40.0,
size_y=25.0,
size_z=40.0,
)
return ItemizedCarrier(
name=name, name=name,
size_x=120.0, direction=direction,
size_y=80.0, resources=resources,
size_z=50.0,
num_items_x=2,
num_items_y=2,
sites=sites,
category="magazine_holder",
) )
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,
}
def MagazineHolder_6_Cathode(name: str) -> ItemizedCarrier: class MagazineHolder(ItemizedResource):
"""正极壳&平垫片弹夹 - 6个洞位 (2x3布局) """弹夹 - 有多个洞位,每个洞位放多个极片"""
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: Args:
name: 弹夹名称 name: 弹夹名称
size_x: 长度 (mm)
Returns: size_y: 宽度 (mm)
ItemizedCarrier: 包含6个槽位的弹夹架 size_z: 高度 (mm)
hole_diameter: 洞直径 (mm)
hole_depth: 洞深度 (mm)
max_sheets_per_hole: 每个洞位最大极片数量
category: 类别
model: 型号
""" """
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=3,
num_items_y=2,
dx=10.0,
dy=10.0,
dz=0.0,
item_dx=40.0,
item_dy=30.0,
size_x=35.0,
size_y=25.0,
size_z=40.0,
)
return ItemizedCarrier( super().__init__(
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, ordered_items=ordered_items,
num_items_y=2, ordering=ordering,
sites=sites, category=category,
category="magazine_holder", 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 MagazineHolder_6_Anode(name: str) -> ItemizedCarrier: def serialize(self) -> dict:
"""负极壳&弹垫片弹夹 - 6个洞位 (2x3布局) 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: 包含6个槽位的弹夹架 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=3, 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=40.0, locations=locations,
item_dy=30.0, resource_size_x=hole_diameter,
size_x=35.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=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, 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_Battery(name: str) -> ItemizedCarrier:
"""成品弹夹 - 6个洞位 (3x2布局)
Args: def MagazineHolder_6_Cathode(
name: 弹夹名称 name: str,
size_x: float = 80.0,
size_y: float = 80.0,
size_z: float = 40.0,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
hole_spacing: float = 20.0,
max_sheets_per_hole: int = 100,
) -> MagazineHolder:
"""创建6孔子弹夹 - 六边形排布"""
center_x = size_x / 2
center_y = size_y / 2
Returns: locations = []
ItemizedCarrier: 包含6个槽位的弹夹架
"""
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=3,
num_items_y=2,
dx=10.0,
dy=10.0,
dz=0.0,
item_dx=33.0,
item_dy=40.0,
size_x=30.0,
size_y=35.0,
size_z=40.0,
)
return ItemizedCarrier( # 周围6个孔按六边形排布
for i in range(6):
angle = i * 60 * math.pi / 180 # 每60度一个孔
x = center_x + hole_spacing * math.cos(angle)
y = center_y + hole_spacing * math.sin(angle)
locations.append(Coordinate(x, y, size_z - hole_depth))
return magazine_factory(
name=name, 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=[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,
size_x: float = 80.0,
size_y: float = 80.0,
size_z: float = 40.0,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
hole_spacing: float = 20.0,
max_sheets_per_hole: int = 100,
) -> MagazineHolder:
"""创建6孔子弹夹 - 六边形排布"""
center_x = size_x / 2
center_y = size_y / 2
locations = []
# 周围6个孔按六边形排布
for i in range(6):
angle = i * 60 * math.pi / 180 # 每60度一个孔
x = center_x + hole_spacing * math.cos(angle)
y = center_y + hole_spacing * math.sin(angle)
locations.append(Coordinate(x, y, size_z - hole_depth))
return magazine_factory(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
locations=locations,
klasses=[SpringWasher, NegativeCan, NegativeCan, SpringWasher, NegativeCan, NegativeCan],
hole_diameter=hole_diameter,
hole_depth=hole_depth,
max_sheets_per_hole=max_sheets_per_hole,
category="magazine_holder",
model="MagazineHolder_6_Anode",
)
def MagazineHolder_6_Battery(
name: str,
size_x: float = 80.0,
size_y: float = 80.0,
size_z: float = 40.0,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
hole_spacing: float = 20.0,
max_sheets_per_hole: int = 100,
) -> MagazineHolder:
"""创建6孔子弹夹 - 六边形排布"""
center_x = size_x / 2
center_y = size_y / 2
locations = []
# 周围6个孔按六边形排布
for i in range(6):
angle = i * 60 * math.pi / 180 # 每60度一个孔
x = center_x + hole_spacing * math.cos(angle)
y = center_y + hole_spacing * math.sin(angle)
locations.append(Coordinate(x, y, size_z - hole_depth))
return magazine_factory(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
locations=locations,
klasses=None, # 初始化时,不放入装好的电池
hole_diameter=hole_diameter,
hole_depth=hole_depth,
max_sheets_per_hole=max_sheets_per_hole,
category="magazine_holder",
model="MagazineHolder_6_Battery",
)
def MagazineHolder_4_Cathode(
name: str,
) -> MagazineHolder:
"""创建4孔子弹夹 - 正方形四角排布"""
size_x: float = 80.0
size_y: float = 80.0
size_z: float = 10.0
hole_diameter: float = 14.0
hole_depth: float = 10.0
hole_spacing: float = 25.0
max_sheets_per_hole: int = 100
# 计算4个洞位的坐标正方形四角排布
center_x = size_x / 2
center_y = size_y / 2
offset = hole_spacing / 2
locations = [
Coordinate(center_x - offset, center_y - offset, size_z - hole_depth), # 左下
Coordinate(center_x + offset, center_y - offset, size_z - hole_depth), # 右下
Coordinate(center_x - offset, center_y + offset, size_z - hole_depth), # 左上
Coordinate(center_x + offset, center_y + offset, size_z - hole_depth), # 右上
]
return magazine_factory(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
locations=locations,
klasses=[AluminumFoil, PositiveElectrode, PositiveElectrode, PositiveElectrode],
hole_diameter=hole_diameter,
hole_depth=hole_depth,
max_sheets_per_hole=max_sheets_per_hole,
category="magazine_holder",
model="MagazineHolder_4_Cathode",
)

View File

@@ -0,0 +1,548 @@
# Bioyond 仓库系统开发指南
本文档详细说明 Bioyond 仓库Warehouse系统的架构、配置和使用方法帮助开发者快速理解和维护仓库相关代码。
## 📚 目录
- [系统架构](#系统架构)
- [核心概念](#核心概念)
- [三层映射关系](#三层映射关系)
- [warehouse_factory 详解](#warehouse_factory-详解)
- [创建新仓库](#创建新仓库)
- [常见问题](#常见问题)
- [调试技巧](#调试技巧)
---
## 系统架构
Bioyond 仓库系统采用**三层架构**,实现从前端显示到后端 API 的完整映射:
```
┌─────────────────────────────────────────────────────────┐
│ 前端显示层 (YB_warehouses.py) │
│ - warehouse_factory 自动生成库位网格 │
│ - 生成库位名称A01, B02, C03... │
│ - 存储在 WareHouse.sites 字典中 │
└────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Deck 布局层 (decks.py) │
│ - 定义仓库在 Deck 上的物理位置 │
│ - 组织多个仓库形成完整布局 │
└────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ UUID 映射层 (config.py) │
│ - 将库位名称映射到 Bioyond 系统 UUID │
│ - 用于 API 调用时的物料入库操作 │
└─────────────────────────────────────────────────────────┘
```
---
## 核心概念
### 仓库Warehouse
仓库是一个**三维网格**,用于存放物料。由以下参数定义:
- **num_items_x**: 列数X 轴)
- **num_items_y**: 行数Y 轴)
- **num_items_z**: 层数Z 轴)
例如:`5行×3列×1层` = 5×3×1 = 15个库位
### 库位Site
库位是仓库中的单个存储位置,由**字母行+数字列**命名:
- **字母行**A, B, C, D, E, F...(对应 Y 轴)
- **数字列**01, 02, 03, 04...(对应 X 轴或 Z 轴)
示例:`A01`, `B02`, `C03`
### 布局模式Layout
控制库位的排序和 Y 坐标计算:
| 模式 | 说明 | 生成顺序 | Y 坐标计算 | 显示效果 |
|------|------|----------|-----------|---------|
| `col-major` | 列优先(默认) | A01, B01, C01, A02... | `dy + (num_y - row - 1) * item_dy` | A 可能在下 |
| `row-major` | 行优先 | A01, A02, A03, B01... | `dy + row * item_dy` | **A 在上** ✓ |
**重要:** 使用 `row-major` 可以避免上下颠倒问题!
---
## 三层映射关系
### 示例手动传递窗右A01-E03
#### 1⃣ 前端显示层 - [`YB_warehouses.py`](YB_warehouses.py)
```python
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
"""创建 5行×3列×1层 仓库"""
return warehouse_factory(
name=name,
num_items_x=3, # 3列
num_items_y=5, # 5行
num_items_z=1, # 1层
row_offset=row_offset,
layout="row-major",
)
```
**自动生成的库位:** A01, A02, A03, B01, B02, B03, ..., E01, E02, E03
#### 2⃣ Deck 布局层 - [`decks.py`](decks.py)
```python
self.warehouses = {
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0),
}
self.warehouse_locations = {
"手动传递窗右": Coordinate(4160.0, 877.0, 0.0),
}
```
**作用:**
- 创建仓库实例
- 设置在 Deck 上的物理坐标
#### 3⃣ UUID 映射层 - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
```python
WAREHOUSE_MAPPING = {
"手动传递窗右": {
"uuid": "",
"site_uuids": {
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
# ... 其他库位
}
}
}
```
**作用:**
- 用户拖拽物料到"手动传递窗右"的"A01"位置时
- 系统查找 `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]`
- 获取 UUID `"3a19deae-2c7a-36f5-5e41-02c5b66feaea"`
- 调用 Bioyond API 将物料入库到该 UUID 位置
---
## 实际配置案例
### 案例:手动传递窗左/右的完整配置
本案例展示如何为"手动传递窗右"和"手动传递窗左"建立完整的三层映射。
#### 背景需求
- **手动传递窗右**: 需要 A01-E035行×3列=15个库位
- **手动传递窗左**: 需要 F01-J035行×3列=15个库位
- 这两个仓库共享同一个物理堆栈的 UUID"手动堆栈"
#### 实施步骤
**1⃣ 修复前端布局** - [`YB_warehouses.py`](YB_warehouses.py)
```python
# 创建新的 5×3×1 仓库函数(之前是错误的 1×3×3
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
"""创建5行×3列×1层仓库支持行偏移生成不同字母行"""
return warehouse_factory(
name=name,
num_items_x=3, # 3列
num_items_y=5, # 5行 ← 修正
num_items_z=1, # 1层 ← 修正
row_offset=row_offset, # ← 支持 F-J 行
layout="row-major", # ← 避免上下颠倒
)
```
**2⃣ 更新 Deck 配置** - [`decks.py`](decks.py)
```python
from unilabos.resources.bioyond.YB_warehouses import (
bioyond_warehouse_5x3x1, # 新增导入
)
class BIOYOND_YB_Deck(Deck):
def setup(self) -> None:
self.warehouses = {
# 修改前: bioyond_warehouse_1x3x3 (错误尺寸)
# 修改后: bioyond_warehouse_5x3x1 (正确尺寸)
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
}
```
**3⃣ 添加 UUID 映射** - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
```python
WAREHOUSE_MAPPING = {
# 保持原有的"手动堆栈"配置不变A01-J03共30个库位
"手动堆栈": {
"uuid": "",
"site_uuids": {
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
# ... A02-E03 共15个
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
# ... F02-J03 共15个
}
},
# [新增] 手动传递窗右 - 复用"手动堆栈"的 A01-E03 UUID
"手动传递窗右": {
"uuid": "",
"site_uuids": {
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea", # ← 与手动堆栈A01相同
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
}
},
# [新增] 手动传递窗左 - 复用"手动堆栈"的 F01-J03 UUID
"手动传递窗左": {
"uuid": "",
"site_uuids": {
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a", # ← 与手动堆栈F01相同
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
}
},
}
```
#### 关键要点
1. **UUID 可以复用**: 三个仓库(手动堆栈、手动传递窗右、手动传递窗左)可以共享相同的物理库位 UUID
2. **库位名称必须匹配**: 前端生成的库位名称(如 F01必须与 config.py 中的键名完全一致
3. **row_offset 的妙用**:
- `row_offset=0` → 生成 A-E 行
- `row_offset=5` → 生成 F-J 行跳过前5个字母
#### 验证结果
配置完成后,拖拽测试:
| 拖拽位置 | 前端库位 | 查找路径 | UUID | 结果 |
|---------|---------|---------|------|------|
| 手动传递窗右/A01 | A01 | `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 正确入库 |
| 手动传递窗左/F01 | F01 | `WAREHOUSE_MAPPING["手动传递窗左"]["site_uuids"]["F01"]` | `3a19...c4a` | ✅ 正确入库 |
| 手动堆栈/A01 | A01 | `WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 仍然正常 |
---
## warehouse_factory 详解
### 函数签名
```python
def warehouse_factory(
name: str,
num_items_x: int = 1, # 列数
num_items_y: int = 4, # 行数
num_items_z: int = 4, # 层数
dx: float = 137.0, # X 起始偏移
dy: float = 96.0, # Y 起始偏移
dz: float = 120.0, # Z 起始偏移
item_dx: float = 10.0, # X 间距
item_dy: float = 10.0, # Y 间距
item_dz: float = 10.0, # Z 间距
col_offset: int = 0, # 列偏移(影响数字)
row_offset: int = 0, # 行偏移(影响字母)
layout: str = "col-major", # 布局模式
) -> WareHouse:
```
### 参数说明
#### 尺寸参数
- **num_items_x, y, z**: 定义仓库的网格尺寸
- **注意**: 当 `num_items_z > 1`Z 轴会被映射为数字列
#### 位置参数
- **dx, dy, dz**: 第一个库位的起始坐标
- **item_dx, dy, dz**: 库位之间的间距
#### 偏移参数
- **col_offset**: 列起始偏移,用于生成 A05-D08 等命名
```python
col_offset=4 # 生成 A05, A06, A07, A08
```
- **row_offset**: 行起始偏移,用于生成 F01-J03 等命名
```python
row_offset=5 # 生成 F01, F02, F03跳过 A-E
```
#### 布局参数
- **layout**:
- `"col-major"`: 列优先(默认),可能导致上下颠倒
- `"row-major"`: 行优先,**推荐使用**A 显示在上
### 库位生成逻辑
```python
# row-major 模式(推荐)
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}"
for j in range(num_y)
for i in range(num_x)]
# 示例num_y=2, num_x=3, row_offset=0, col_offset=0
# 生成A01, A02, A03, B01, B02, B03
```
### Y 坐标计算
```python
if layout == "row-major":
# A 在上Y 较小)
y = dy + row * item_dy
else:
# A 在下Y 较大)- 不推荐
y = dy + (num_items_y - row - 1) * item_dy
```
---
## 创建新仓库
### 步骤 1: 在 YB_warehouses.py 中创建函数
```python
def bioyond_warehouse_3x4x1(name: str) -> WareHouse:
"""创建 3行×4列×1层 仓库
布局:
A01 | A02 | A03 | A04
B01 | B02 | B03 | B04
C01 | C02 | C03 | C04
"""
return warehouse_factory(
name=name,
num_items_x=4, # 4列
num_items_y=3, # 3行
num_items_z=1, # 1层
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=120.0,
item_dz=120.0,
category="warehouse",
layout="row-major", # ⭐ 推荐使用
)
```
### 步骤 2: 在 decks.py 中使用
```python
# 1. 导入函数
from unilabos.resources.bioyond.YB_warehouses import (
bioyond_warehouse_3x4x1, # 新增
)
# 2. 在 setup() 中添加
self.warehouses = {
"我的新仓库": bioyond_warehouse_3x4x1("我的新仓库"),
}
self.warehouse_locations = {
"我的新仓库": Coordinate(100.0, 200.0, 0.0),
}
```
### 步骤 3: 在 config.py 中配置 UUID可选
```python
WAREHOUSE_MAPPING = {
"我的新仓库": {
"uuid": "",
"site_uuids": {
"A01": "从 Bioyond 系统获取的 UUID",
"A02": "从 Bioyond 系统获取的 UUID",
# ... 其他 11 个库位
}
}
}
```
**注意:** 如果不需要拖拽入库功能,可跳过此步骤。
---
## 常见问题
### Q1: 为什么库位显示上下颠倒C 在上A 在下)?
**原因:** 使用了默认的 `col-major` 布局。
**解决:** 在 `warehouse_factory` 中添加 `layout="row-major"`
```python
return warehouse_factory(
...
layout="row-major", # ← 添加这行
)
```
### Q2: 我需要 1×3×3 还是 3×3×1
**判断方法:**
- **1×3×3**: 1列×3行×3**层**(垂直堆叠,有高度)
- **3×3×1**: 3行×3列×1**层**(平面网格)
**推荐:** 大多数情况使用 `X×Y×1`(平面网格)更直观。
### Q3: 如何生成 F01-J03 而非 A01-E03
**方法:** 使用 `row_offset` 参数
```python
bioyond_warehouse_5x3x1("仓库名", row_offset=5)
# row_offset=5 跳过 A-E从 F 开始
```
### Q4: 拖拽物料后找不到 UUID 怎么办?
**检查清单:**
1. `config.py` 中是否有该仓库的配置?
2. 仓库名称是否完全匹配?
3. 库位名称(如 A01是否在 `site_uuids` 中?
**示例错误:**
```python
# decks.py
"手动传递窗右": bioyond_warehouse_5x3x1(...)
# config.py - ❌ 名称不匹配
"手动传递窗": { ... } # 缺少"右"字
```
### Q5: 库位重叠怎么办?
**原因:** 间距(`item_dx/dy/dz`)太小。
**解决:** 增大间距参数
```python
item_dx=150.0, # 增大 X 间距
item_dy=130.0, # 增大 Y 间距
```
---
## 调试技巧
### 1. 查看生成的库位
```python
warehouse = bioyond_warehouse_5x3x1("测试仓库")
print(list(warehouse.sites.keys()))
# 输出:['A01', 'A02', 'A03', 'B01', 'B02', ...]
```
### 2. 检查库位坐标
```python
for name, site in warehouse.sites.items():
print(f"{name}: {site.location}")
# 输出:
# A01: Coordinate(x=10.0, y=10.0, z=120.0)
# A02: Coordinate(x=147.0, y=10.0, z=120.0)
# ...
```
### 3. 验证 UUID 映射
```python
from unilabos.devices.workstation.bioyond_studio.config import WAREHOUSE_MAPPING
warehouse_name = "手动传递窗右"
location_code = "A01"
if warehouse_name in WAREHOUSE_MAPPING:
uuid = WAREHOUSE_MAPPING[warehouse_name]["site_uuids"].get(location_code)
print(f"{warehouse_name}/{location_code} → {uuid}")
else:
print(f"❌ 未找到仓库: {warehouse_name}")
```
---
## 文件关系图
```
unilabos/
├── resources/
│ ├── warehouse.py # warehouse_factory 核心实现
│ └── bioyond/
│ ├── YB_warehouses.py # ⭐ 仓库函数定义
│ ├── decks.py # ⭐ Deck 布局配置
│ └── README_WAREHOUSE.md # 📖 本文档
└── devices/
└── workstation/
└── bioyond_studio/
├── config.py # ⭐ UUID 映射配置
└── bioyond_cell/
└── bioyond_cell_workstation.py # 业务逻辑
```
---
## 版本历史
- **v1.1** (2026-01-08): 补充实际配置案例
- 添加"手动传递窗右"和"手动传递窗左"的完整配置示例
- 展示 UUID 复用的实际应用
- 说明三个仓库共享物理堆栈的配置方法
- **v1.0** (2026-01-07): 初始版本
- 新增 `row_offset` 参数支持
- 创建 `bioyond_warehouse_5x3x1` 和 `bioyond_warehouse_2x2x1`
- 修复多个仓库的上下颠倒问题
---
## 相关资源
- [warehouse.py](../warehouse.py) - 核心工厂函数实现
- [YB_warehouses.py](YB_warehouses.py) - 所有仓库定义
- [decks.py](decks.py) - Deck 布局配置
- [config.py](../../devices/workstation/bioyond_studio/config.py) - UUID 映射
---
**维护者:** Uni-Lab-OS 开发团队
**最后更新:** 2026-01-07

View File

@@ -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仓库 (左侧堆栈: A01D04)
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仓库 (右侧堆栈: A05D08)"""
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:
"""创建测量小瓶仓库(测密度) A01B03"""
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站内试剂存放堆栈A01A02, 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盒堆栈A01B03用于存放枪头盒"""
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%分装液体准备仓库A01B04"""
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,57 @@ 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: def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库""" """创建BioYond 1x2x2仓库1列×2行×2层- 旧版本,已弃用
return YB_warehouse_factory(
布局2层:
层1: A01
B01
层2: A02
B02
"""
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",
layout="row-major", # 使用行优先避免上下颠倒
) )
def bioyond_warehouse_2x2x1(name: str) -> WareHouse: def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
"""创建BioYond 2x2x1仓库自动堆栈)""" """创建BioYond 2x2x1仓库2行×2列×1层
return YB_warehouse_factory(
布局:
A01 | A02
B01 | B02
"""
return warehouse_factory(
name=name, name=name,
num_items_x=2, num_items_x=2, # 2列
num_items_y=2, num_items_y=2, # 2行
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=137.0,
item_dy=96.0, item_dy=96.0,
item_dz=120.0, item_dz=120.0,
category="YB_warehouse", category="warehouse",
layout="row-major", # 使用行优先避免上下颠倒
) )
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 +227,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,
@@ -93,13 +239,64 @@ def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
dy=10.0, dy=10.0,
dz=10.0, dz=10.0,
item_dx=137.0, item_dx=137.0,
item_dy=120.0, # 增大Y方向间距以避免重叠
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
"""创建BioYond 5x3x1仓库5行×3列×1层
标准布局row_offset=0:
A01 | A02 | A03
B01 | B02 | B03
C01 | C02 | C03
D01 | D02 | D03
E01 | E02 | E03
带偏移布局row_offset=5:
F01 | F02 | F03
G01 | G02 | G03
H01 | H02 | H03
I01 | I02 | I03
J01 | J02 | J03
"""
return warehouse_factory(
name=name,
num_items_x=3, # 3列
num_items_y=5, # 5行
num_items_z=1, # 1层
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=120.0,
item_dz=120.0,
category="warehouse",
col_offset=0,
row_offset=row_offset, # 支持行偏移
layout="row-major", # 使用行优先避免颠倒
)
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
"""创建BioYond 3x3x1仓库"""
return warehouse_factory(
name=name,
num_items_x=3,
num_items_y=3,
num_items_z=1,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0, item_dy=96.0,
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 +311,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 +325,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 +341,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 +360,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 +375,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反应站堆栈A01D08"""
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",
)
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", category="warehouse",
) )

View File

@@ -1,14 +1,16 @@
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, # 新增:右侧仓库 (A05D08) bioyond_warehouse_1x4x4_right, # 新增:右侧仓库 (A05D08)
bioyond_warehouse_1x4x2, bioyond_warehouse_1x4x2,
bioyond_warehouse_reagent_stack, # 新增:试剂堆栈 (A1-B4) bioyond_warehouse_reagent_stack, # 新增:试剂堆栈 (A1-B4)
bioyond_warehouse_liquid_and_lid_handling, bioyond_warehouse_liquid_and_lid_handling,
bioyond_warehouse_1x2x2, bioyond_warehouse_1x2x2,
bioyond_warehouse_2x2x1, # 新增321和43窗口 (2行×2列)
bioyond_warehouse_1x3x3, bioyond_warehouse_1x3x3,
bioyond_warehouse_5x3x1, # 新增:手动传递窗仓库 (5行×3列)
bioyond_warehouse_10x1x1, bioyond_warehouse_10x1x1,
bioyond_warehouse_3x3x1, bioyond_warehouse_3x3x1,
bioyond_warehouse_3x3x1_2, bioyond_warehouse_3x3x1_2,
@@ -115,10 +117,10 @@ class BIOYOND_YB_Deck(Deck):
def setup(self) -> None: def setup(self) -> None:
# 添加仓库 # 添加仓库
self.warehouses = { self.warehouses = {
"321窗口": bioyond_warehouse_1x2x2("321窗口"), "321窗口": bioyond_warehouse_2x2x1("321窗口"), # 2行×2列
"43窗口": bioyond_warehouse_1x2x2("43窗口"), "43窗口": bioyond_warehouse_2x2x1("43窗口"), # 2行×2列
"手动传递窗": bioyond_warehouse_1x3x3("手动传递窗"), "手动传递窗": bioyond_warehouse_5x3x1("手动传递窗", row_offset=0), # A01-E03
"手动传递窗": bioyond_warehouse_1x3x3("手动传递窗"), "手动传递窗": bioyond_warehouse_5x3x1("手动传递窗", row_offset=5), # F01-J03
"加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"), "加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"),
"加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"), "加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"),

View File

@@ -27,6 +27,7 @@ def warehouse_factory(
category: str = "warehouse", category: str = "warehouse",
model: Optional[str] = None, model: Optional[str] = None,
col_offset: int = 0, # 列起始偏移量用于生成A05-D08等命名 col_offset: int = 0, # 列起始偏移量用于生成A05-D08等命名
row_offset: int = 0, # 行起始偏移量用于生成F01-J03等命名
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先 layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
): ):
# 创建位置坐标 # 创建位置坐标
@@ -65,10 +66,10 @@ def warehouse_factory(
if layout == "row-major": if layout == "row-major":
# 行优先顺序: A01,A02,A03,A04, B01,B02,B03,B04 # 行优先顺序: A01,A02,A03,A04, B01,B02,B03,B04
# locations[0] 对应 row=0, y最大前端顶部→ 应该是 A01 # locations[0] 对应 row=0, y最大前端顶部→ 应该是 A01
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)] keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
else: else:
# 列优先顺序: A01,B01,C01,D01, A02,B02,C02,D02 # 列优先顺序: A01,B01,C01,D01, A02,B02,C02,D02
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)] keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
sites = {i: site for i, site in zip(keys, _sites.values())} sites = {i: site for i, site in zip(keys, _sites.values())}

View File

@@ -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

View 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": []
}