Compare commits

..

20 Commits

Author SHA1 Message Date
Calvin Cao
af7443e74b Merge pull request #199 from sun7151887/yb_fix5
合并dev分支
2025-12-19 10:27:04 +08:00
dijkstra402
8d16536973 Update .gitignore to exclude nested Uni-Lab-OS directory 2025-12-18 17:59:42 +08:00
Calvin Cao
015fee4171 Merge pull request #198 from sun7151887/yb_fix5
Yb fix5
2025-12-18 17:53:45 +08:00
dijkstra402
9f8b5eaf71 Merge dev branch: Add battery resources, bioyond_cell device registry, and fix file path resolution 2025-12-18 11:11:59 +08:00
dijkstra402
143104e9e3 Add battery resources, bioyond_cell device registry, and fix file path resolution 2025-12-18 11:11:13 +08:00
Calvin Cao
6413828c59 Merge pull request #183 from sun7151887/yb_fix5
添加新威电池测试系统设备节点到配置文件
2025-12-02 17:06:07 +08:00
dijkstra402
5072f00836 添加新威电池测试系统设备节点到配置文件
- 在 new_cellconfig3c.json 中新增 NewareTester 设备
- 配置 IP:127.0.0.1, Port:502, Machine_ID:1
- 修复之前的 JSON 格式错误(重复对象和数组语法错误)
- 设备位置设置为 (1500, 0, 0),避免与其他设备重叠
- 包含功能说明: 720通道监控、2盘电池物料管理、CSV批量提交
2025-12-02 17:01:02 +08:00
Calvin Cao
9dfbe3246e Merge pull request #182 from sun7151887/yb_fix5
解决前端物料显示问题
2025-12-02 16:07:55 +08:00
dijkstra402
bef69db3b6 解决前端物料显示问题 2025-12-02 15:42:07 +08:00
Calvin Cao
a061bc2942 Merge pull request #181 from sun7151887/yb_fix5
修复遇到的参数错误和物料转换问题
2025-12-02 11:26:54 +08:00
dijkstra402
8c9e11c04f chore: 更新 Excel 模板文件
- 更新 2025092701.xlsx 配方文件
- 更新 material_template.xlsx 物料模板
2025-12-02 11:08:48 +08:00
dijkstra402
e4e3ec805a feat: 添加三阶段工作流函数和别名映射
- 在 BioyondCellWorkstation 添加 run_feeding_stage, run_liquid_preparation_stage, run_transfer_stage 三个阶段函数
- 在 host_node.py 添加 JSON_COMMAND_ALIASES 映射表,支持 run_feeding_stage -> auto_feeding4to3 别名
- 修复 create_orders 中 transfer_resource_to_another 参数名错误
- 简化 run_transfer_stage,注释掉物料转换逻辑,只保留核心转运功能
2025-12-02 11:05:36 +08:00
dijkstra402
d634316bce feat: enhance BioyondCellWorkstation and CoinCellAssembly workflows
- Added support for transferring resources between workstations with detailed logging.
- Introduced new methods for material conversion and resource registration.
- Updated YAML configurations to reflect new parameters and structures for workflows.
- Enhanced error handling and logging for better debugging and operational clarity.
2025-11-27 10:46:40 +08:00
Calvin Cao
f5446c6480 Merge pull request #174 from sun7151887/yb_fix5
奔曜实现物料流
2025-11-25 18:39:56 +08:00
dijkstra402
a98d25c16d feat: expose workflow material outputs 2025-11-25 18:27:34 +08:00
Calvin Cao
80b9589973 Merge pull request #173 from sun7151887/yb_fix5
fix: 修复 BioyondCellWorkstation 和 CoinCellAssembly 工作流程
2025-11-25 18:26:26 +08:00
dijkstra402
4d4bbcbae8 fix: 修复 BioyondCellWorkstation 和 CoinCellAssembly 工作流程
- 修复 run 方法的函数参数语法错误(冒号改为等号)
- 将 BioyondCellWorkstation 的 run 函数移入类内部
- 添加 run_bioyond_cell_workflow 方法支持可选的 1to2 步骤
- 更新相关 YAML 配置文件
2025-11-25 15:39:07 +08:00
Calvin Cao
fa9b2a08f2 Merge pull request #171 from Andy6M/feat/merge-neware-battery-systems
feat: Merge Neware monitoring and submission systems into unified driver
2025-11-24 15:24:02 +08:00
Xie Qiming
929d50f954 feat: Merge Neware monitoring and submission systems into unified driver 2025-11-21 20:13:51 +08:00
calvincao
e60bf29a7f feat(workstation): 实现奔曜与扣电池装配工作流统一配置执行接口
- 新增 `run_bioyond_cell_workflow` 函数以支持通过配置驱动奔曜配液与转运流程
- 新增 `run_coin_cell_packaging_workflow` 函数以支持通过配置驱动扣电池装配流程
- 两个函数均接受字典配置参数,实现初始化、操作调用及日志记录等功能的灵活控制- 提供 keep_alive机制用于持续运行场景
- 更新主程序入口逻辑,使用新工作流函数替代原有手动调用方式
- 支持从配置中读取实验样本、调度器设置以及各项操作开关和日志选项- 添加对 Excel 订单创建路径的配置化支持- 引入路径对象处理文件输入,提升跨平台兼容性- 增强错误提示信息,确保必要字段如 create_orders 的 excel_path 存在
- 封装所有设备动作至标准化函数调用结构,便于维护和扩展
2025-11-19 09:51:24 +08:00
56 changed files with 30564 additions and 5829 deletions

1
.gitignore vendored
View File

@@ -249,3 +249,4 @@ ros-humble-unilabos-msgs-0.9.13-h6403a04_5.tar.bz2
test_config.py
Uni-Lab-OS/

View File

@@ -1,32 +0,0 @@
#!/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

@@ -1,98 +1,101 @@
{
"nodes": [
{
"id": "bioyond_cell_workstation",
"name": "配液分液工站",
"parent": null,
"children": [
"YB_Bioyond_Deck"
],
"type": "device",
"class": "bioyond_cell",
"config": {
"deck": {
"data": {
"_resource_child_name": "YB_Bioyond_Deck",
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
}
"nodes": [
{
"id": "bioyond_cell_workstation",
"name": "配液分液工站",
"parent": null,
"children": [
"YB_Bioyond_Deck"
],
"type": "device",
"class": "bioyond_cell",
"config": {
"deck": {
"data": {
"_resource_child_name": "YB_Bioyond_Deck",
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
}
},
"protocol_type": []
},
"protocol_type": []
"data": {}
},
"data": {}
},
{
"id": "YB_Bioyond_Deck",
"name": "YB_Bioyond_Deck",
"children": [],
"parent": "bioyond_cell_workstation",
"type": "deck",
"class": "BIOYOND_YB_Deck",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "BIOYOND_YB_Deck",
"setup": true,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
}
},
"data": {}
},
{
"id": "BatteryStation",
"name": "扣电工作站",
"parent": null,
"children": [
"coin_cell_deck"
],
"type": "device",
"class":"coincellassemblyworkstation_device",
"config": {
"deck": {
"data": {
"_resource_child_name": "YB_YH_Deck",
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
}
},
"protocol_type": []
},
"position": {
"size": {"height": 1450, "width": 1450, "depth": 2100},
{
"id": "YB_Bioyond_Deck",
"name": "YB_Bioyond_Deck",
"children": [],
"parent": "bioyond_cell_workstation",
"type": "deck",
"class": "BIOYOND_YB_Deck",
"position": {
"x": -1500,
"x": 0,
"y": 0,
"z": 0
}
}
},
{
"id": "YB_YH_Deck",
"name": "YB_YH_Deck",
"children": [],
"parent": "BatteryStation",
"type": "deck",
"class": "CoincellDeck",
"config": {
"type": "CoincellDeck",
"setup": true,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"config": {
"type": "BIOYOND_YB_Deck",
"setup": true,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
}
},
"data": {}
},
{
"id": "BatteryStation",
"name": "扣电工作站",
"parent": null,
"children": [
"coin_cell_deck"
],
"type": "device",
"class":"coincellassemblyworkstation_device",
"config": {
"deck": {
"data": {
"_resource_child_name": "YB_YH_Deck",
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
}
},
"protocol_type": []
},
"position": {
"size": {"height": 1450, "width": 1450, "depth": 2100},
"position": {
"x": -1500,
"y": 0,
"z": 0
}
}
},
"data": {}
}
],
"links": []
}
{
"id": "YB_YH_Deck",
"name": "YB_YH_Deck",
"children": [],
"parent": "BatteryStation",
"type": "deck",
"class": "CoincellDeck",
"config": {
"type": "CoincellDeck",
"setup": true,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
}
},
"data": {}
}
]
}

View File

@@ -1,72 +0,0 @@
{
"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,10 +367,37 @@ def main():
graph, resource_tree_set, resource_links = read_node_link_json(request_startup_json)
else:
if not os.path.isfile(file_path):
# 尝试从 main.py 向上两级目录查找
temp_file_path = os.path.abspath(str(os.path.join(__file__, "..", "..", file_path)))
if os.path.isfile(temp_file_path):
print_status(f"使用相对路径{temp_file_path}", "info")
file_path = temp_file_path
else:
# 尝试在 working_dir 中查找
working_dir_file_path = os.path.join(working_dir, file_path)
if os.path.isfile(working_dir_file_path):
print_status(f"在工作目录中找到文件: {working_dir_file_path}", "info")
file_path = working_dir_file_path
else:
# 尝试使用文件名在 working_dir 中查找
file_name = os.path.basename(file_path)
working_dir_file_path = os.path.join(working_dir, file_name)
if os.path.isfile(working_dir_file_path):
print_status(f"在工作目录中找到文件: {working_dir_file_path}", "info")
file_path = working_dir_file_path
# 最终检查文件是否存在
if not os.path.isfile(file_path):
print_status(
f"无法找到设备加载文件: {file_path}\n"
f"已尝试在以下位置查找:\n"
f" 1. 原始路径: {args_dict.get('graph', BasicConfig.startup_json_path)}\n"
f" 2. 相对路径: {os.path.abspath(str(os.path.join(__file__, '..', '..', args_dict.get('graph', BasicConfig.startup_json_path) or '')))}\n"
f" 3. 工作目录: {os.path.join(working_dir, args_dict.get('graph', BasicConfig.startup_json_path) or '')}\n"
f" 4. 工作目录(仅文件名): {os.path.join(working_dir, os.path.basename(args_dict.get('graph', BasicConfig.startup_json_path) or ''))}\n"
f"请使用 -g 参数指定正确的文件路径,或在工作目录 {working_dir} 中放置文件",
"error"
)
os._exit(1)
if file_path.endswith(".json"):
graph, resource_tree_set, resource_links = read_node_link_json(file_path)
else:

View File

@@ -4,7 +4,8 @@ import traceback
from typing import Any, Union, List, Dict, Callable, Optional, Tuple
from pydantic import BaseModel
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient
from pymodbus.client import ModbusSerialClient, ModbusTcpClient
from pymodbus.framer import FramerType
from typing import TypedDict
from unilabos.device_comms.modbus_plc.modbus import DeviceType, HoldRegister, Coil, InputRegister, DiscreteInputs, DataType, WorderOrder
@@ -402,7 +403,7 @@ class TCPClient(BaseClient):
class RTUClient(BaseClient):
def __init__(self, port: str, baudrate: int, timeout: int):
super().__init__()
self._set_client(ModbusSerialClient(method='rtu', port=port, baudrate=baudrate, timeout=timeout))
self._set_client(ModbusSerialClient(framer=FramerType.RTU, port=port, baudrate=baudrate, timeout=timeout))
self._connect()
if __name__ == '__main__':

View File

@@ -1,26 +1,12 @@
# coding=utf-8
from enum import Enum
from abc import ABC, abstractmethod
from typing import Tuple, Union, Optional, TYPE_CHECKING
from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
from pymodbus.constants import Endian
if TYPE_CHECKING:
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient
# 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"
from pymodbus.client import ModbusBaseSyncClient
from pymodbus.client.mixin import ModbusClientMixin
from typing import Tuple, Union, Optional
DataType = ModbusClientMixin.DATATYPE
class WorderOrder(Enum):
BIG = "big"
@@ -33,96 +19,8 @@ class DeviceType(Enum):
INPUT_REGISTER = 'input_register'
def _convert_from_registers(registers, data_type: DataType, word_order: str = 'big'):
"""Convert registers to a value using BinaryPayloadDecoder.
Args:
registers: List of register values
data_type: DataType enum specifying the target data type
word_order: 'big' or 'little' endian
Returns:
Converted value
"""
# Determine byte and word order based on word_order parameter
if word_order == 'little':
byte_order = Endian.Little
word_order_enum = Endian.Little
else:
byte_order = Endian.Big
word_order_enum = Endian.Big
decoder = BinaryPayloadDecoder.fromRegisters(registers, byteorder=byte_order, wordorder=word_order_enum)
if data_type == DataType.INT16:
return decoder.decode_16bit_int()
elif data_type == DataType.UINT16:
return decoder.decode_16bit_uint()
elif data_type == DataType.INT32:
return decoder.decode_32bit_int()
elif data_type == DataType.UINT32:
return decoder.decode_32bit_uint()
elif data_type == DataType.INT64:
return decoder.decode_64bit_int()
elif data_type == DataType.UINT64:
return decoder.decode_64bit_uint()
elif data_type == DataType.FLOAT32:
return decoder.decode_32bit_float()
elif data_type == DataType.FLOAT64:
return decoder.decode_64bit_float()
elif data_type == DataType.STRING:
return decoder.decode_string(len(registers) * 2)
else:
raise ValueError(f"Unsupported data type: {data_type}")
def _convert_to_registers(value, data_type: DataType, word_order: str = 'little'):
"""Convert a value to registers using BinaryPayloadBuilder.
Args:
value: Value to convert
data_type: DataType enum specifying the source data type
word_order: 'big' or 'little' endian
Returns:
List of register values
"""
# Determine byte and word order based on word_order parameter
if word_order == 'little':
byte_order = Endian.Little
word_order_enum = Endian.Little
else:
byte_order = Endian.Big
word_order_enum = Endian.Big
builder = BinaryPayloadBuilder(byteorder=byte_order, wordorder=word_order_enum)
if data_type == DataType.INT16:
builder.add_16bit_int(value)
elif data_type == DataType.UINT16:
builder.add_16bit_uint(value)
elif data_type == DataType.INT32:
builder.add_32bit_int(value)
elif data_type == DataType.UINT32:
builder.add_32bit_uint(value)
elif data_type == DataType.INT64:
builder.add_64bit_int(value)
elif data_type == DataType.UINT64:
builder.add_64bit_uint(value)
elif data_type == DataType.FLOAT32:
builder.add_32bit_float(value)
elif data_type == DataType.FLOAT64:
builder.add_64bit_float(value)
elif data_type == DataType.STRING:
builder.add_string(value)
else:
raise ValueError(f"Unsupported data type: {data_type}")
return builder.to_registers()
class Base(ABC):
def __init__(self, client, name: str, address: int, typ: DeviceType, data_type):
def __init__(self, client: ModbusBaseSyncClient, name: str, address: int, typ: DeviceType, data_type: DataType):
self._address: int = address
self._client = client
self._name = name
@@ -160,11 +58,7 @@ class Coil(Base):
count = value,
slave = slave)
# 检查是否读取出错
if resp.isError():
return [], True
return resp.bits, False
return resp.bits, resp.isError()
def write(self,value: Union[int, float, bool, str, list[bool], list[int], list[float]], data_type: Optional[DataType ]= None, word_order: WorderOrder = WorderOrder.LITTLE, slave = 1) -> bool:
if isinstance(value, list):
@@ -197,18 +91,8 @@ class DiscreteInputs(Base):
count = value,
slave = slave)
# 检查是否读取出错
if resp.isError():
# 根据数据类型返回默认值
if data_type in [DataType.FLOAT32, DataType.FLOAT64]:
return 0.0, True
elif data_type == DataType.STRING:
return "", True
else:
return 0, True
# noinspection PyTypeChecker
return _convert_from_registers(resp.registers, data_type, word_order=word_order.value), False
return self._client.convert_from_registers(resp.registers, data_type, word_order=word_order.value), resp.isError()
def write(self,value: Union[int, float, bool, str, list[bool], list[int], list[float]], data_type: Optional[DataType ]= None, word_order: WorderOrder = WorderOrder.LITTLE, slave = 1) -> bool:
raise ValueError('discrete inputs only support read')
@@ -228,19 +112,8 @@ class HoldRegister(Base):
address = self.address,
count = value,
slave = slave)
# 检查是否读取出错
if resp.isError():
# 根据数据类型返回默认值
if data_type in [DataType.FLOAT32, DataType.FLOAT64]:
return 0.0, True
elif data_type == DataType.STRING:
return "", True
else:
return 0, True
# noinspection PyTypeChecker
return _convert_from_registers(resp.registers, data_type, word_order=word_order.value), False
return self._client.convert_from_registers(resp.registers, data_type, word_order=word_order.value), resp.isError()
def write(self,value: Union[int, float, bool, str, list[bool], list[int], list[float]], data_type: Optional[DataType ]= None, word_order: WorderOrder = WorderOrder.LITTLE, slave = 1) -> bool:
@@ -259,7 +132,7 @@ class HoldRegister(Base):
return self._client.write_register(self.address, value, slave= slave).isError()
else:
# noinspection PyTypeChecker
encoder_resp = _convert_to_registers(value, data_type=data_type, word_order=word_order.value)
encoder_resp = self._client.convert_to_registers(value, data_type=data_type, word_order=word_order.value)
return self._client.write_registers(self.address, encoder_resp, slave=slave).isError()
@@ -280,19 +153,8 @@ class InputRegister(Base):
address = self.address,
count = value,
slave = slave)
# 检查是否读取出错
if resp.isError():
# 根据数据类型返回默认值
if data_type in [DataType.FLOAT32, DataType.FLOAT64]:
return 0.0, True
elif data_type == DataType.STRING:
return "", True
else:
return 0, True
# noinspection PyTypeChecker
return _convert_from_registers(resp.registers, data_type, word_order=word_order.value), False
return self._client.convert_from_registers(resp.registers, data_type, word_order=word_order.value), resp.isError()
def write(self,value: Union[int, float, bool, str, list[bool], list[int], list[float]], data_type: Optional[DataType ]= None, word_order: WorderOrder = WorderOrder.LITTLE, slave = 1) -> bool:
raise ValueError('input register only support read')

View File

@@ -1,29 +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.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

@@ -0,0 +1,8 @@
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

@@ -0,0 +1,3 @@
Timestamp,Battery_Count,Assembly_Time,Open_Circuit_Voltage,Pole_Weight,Assembly_Pressure,Battery_Code,Electrolyte_Code,集流体质量,活性物质含量,克容量mah/g,电池体系,设备号,排号,通道号
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

@@ -0,0 +1,33 @@
{
"nodes": [
{
"id": "NEWARE_BATTERY_TEST_SYSTEM",
"name": "Neware Battery Test System",
"parent": null,
"type": "device",
"class": "neware_battery_test_system",
"position": {
"x": 620.0,
"y": 200.0,
"z": 0
},
"config": {
"ip": "127.0.0.1",
"port": 502,
"machine_id": 1,
"devtype": "27",
"timeout": 20,
"size_x": 500.0,
"size_y": 500.0,
"size_z": 2000.0
},
"data": {
"功能说明": "新威电池测试系统提供720通道监控和CSV批量提交功能",
"监控功能": "支持720个通道的实时状态监控、2盘电池物料管理、状态导出等",
"提交功能": "通过submit_from_csv action从CSV文件批量提交测试任务。CSV必须包含: Battery_Code, Pole_Weight, 集流体质量, 活性物质含量, 克容量mah/g, 电池体系, 设备号, 排号, 通道号"
},
"children": []
}
],
"links": []
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,8 @@
- 状态类型: working/stop/finish/protect/pause/false/unknown
"""
import os
import sys
import socket
import xml.etree.ElementTree as ET
import json
@@ -21,7 +23,6 @@ from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TypedDict
from pylabrobot.resources import ResourceHolder, Coordinate, create_ordered_items_2d, Deck, Plate
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
@@ -56,13 +57,6 @@ class BatteryTestPositionState(TypedDict):
status: str # 通道状态
color: str # 状态对应颜色
# 额外的inquire协议字段
relativetime: float # 相对时间 (s)
open_or_close: int # 0=关闭, 1=打开
step_type: str # 步骤类型
cycle_id: int # 循环ID
step_id: int # 步骤ID
log_code: str # 日志代码
class BatteryTestPosition(ResourceHolder):
@@ -142,9 +136,9 @@ class NewareBatteryTestSystem:
devtype: str = None,
timeout: int = None,
size_x: float = 500.0,
size_y: float = 500.0,
size_z: float = 2000.0,
size_x: float = 50,
size_y: float = 50,
size_z: float = 20,
):
"""
初始化新威电池测试系统
@@ -162,6 +156,12 @@ class NewareBatteryTestSystem:
self.machine_id = machine_id
self.devtype = devtype or self.DEVTYPE
self.timeout = timeout or self.TIMEOUT
# 存储设备物理尺寸
self.size_x = size_x
self.size_y = size_y
self.size_z = size_z
self._last_status_update = None
self._cached_status = {}
self._ros_node: Optional[ROS2WorkstationNode] = None # ROS节点引用由框架设置
@@ -192,8 +192,9 @@ class NewareBatteryTestSystem:
def _setup_material_management(self):
"""设置物料管理系统"""
# 第1盘5行8列网格 (A1-E8) - 5行对应subdevid 1-58列对应chlid 1-8
# 先给物料设置一个最大的Deck
deck_main = Deck("ADeckName", 200, 200, 200)
# 先给物料设置一个最大的Deck,并设置其在空间中的位置
deck_main = Deck("ADeckName", 2000, 1800, 100, origin=Coordinate(2000,2000,0))
plate1_resources: Dict[str, BatteryTestPosition] = create_ordered_items_2d(
BatteryTestPosition,
@@ -202,8 +203,8 @@ class NewareBatteryTestSystem:
dx=10,
dy=10,
dz=0,
item_dx=45,
item_dy=45
item_dx=65,
item_dy=65
)
plate1 = Plate("P1", 400, 300, 50, ordered_items=plate1_resources)
deck_main.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
@@ -232,11 +233,15 @@ class NewareBatteryTestSystem:
num_items_y=5, # 5行对应subdevid 6-10即A-E
dx=10,
dy=10,
dz=100, # Z轴偏移100mm
dz=0,
item_dx=65,
item_dy=65
)
plate2 = Plate("P2", 400, 300, 50, ordered_items=plate2_resources)
deck_main.assign_child_resource(plate2, location=Coordinate(0, 350, 0))
# 为第2盘资源添加P2_前缀
self.station_resources_plate2 = {}
for name, resource in plate2_resources.items():
@@ -306,55 +311,132 @@ class NewareBatteryTestSystem:
def _update_plate_resources(self, subunits: Dict):
"""更新两盘电池资源的状态"""
# 第1盘subdevid 1-5 映射到 P1_A1-P1_E8 (5行8列)
# 第1盘subdevid 1-5 映射到 8列5行网格 (列0-7, 行0-4)
for subdev_id in range(1, 6): # subdevid 1-5
status_row = subunits.get(subdev_id, {})
for chl_id in range(1, 9): # chlid 1-8
try:
# 计算在5×8网格中的位置
row_idx = (subdev_id - 1) # 0-4 (对应A-E)
col_idx = (chl_id - 1) # 0-7 (对应1-8)
resource_name = f"P1_{self.LETTERS[row_idx]}{col_idx + 1}"
# 根据用户描述:第一个是(0,0),最后一个是(7,4)
# 说明是8列5行列从0开始行从0开始
col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 列0-7)
row_idx = (subdev_id - 1) # 0-4 (subdevid 1-5 -> 行0-4)
# 尝试多种可能的资源命名格式
possible_names = [
f"P1_batterytestposition_{col_idx}_{row_idx}", # 用户提到的格式
f"P1_{self.LETTERS[row_idx]}{col_idx + 1}", # 原有的A1-E8格式
f"P1_{self.LETTERS[row_idx].lower()}{col_idx + 1}", # 小写字母格式
]
r = None
resource_name = None
for name in possible_names:
if name in self.station_resources:
r = self.station_resources[name]
resource_name = name
break
r = self.station_resources.get(resource_name)
if r:
status_channel = status_row.get(chl_id, {})
metrics = status_channel.get("metrics", {})
# 构建BatteryTestPosition状态数据移除capacity和energy
channel_state = {
# 基本测量数据
"voltage": metrics.get("voltage_V", 0.0),
"current": metrics.get("current_A", 0.0),
"time": metrics.get("totaltime_s", 0.0),
# 状态信息
"status": status_channel.get("state", "unknown"),
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
"voltage": status_channel.get("voltage_V", 0.0),
"current": status_channel.get("current_A", 0.0),
"time": status_channel.get("totaltime_s", 0.0),
# 通道名称标识
"Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
}
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
# 第2盘subdevid 6-10 映射到 P2_A1-P2_E8 (5行8列)
# 第2盘subdevid 6-10 映射到 8列5行网格 (列0-7, 行0-4)
for subdev_id in range(6, 11): # subdevid 6-10
status_row = subunits.get(subdev_id, {})
for chl_id in range(1, 9): # chlid 1-8
try:
# 计算在5×8网格中的位置
row_idx = (subdev_id - 6) # 0-4 (subdevid 6->0, 7->1, ..., 10->4) (对应A-E)
col_idx = (chl_id - 1) # 0-7 (对应1-8)
resource_name = f"P2_{self.LETTERS[row_idx]}{col_idx + 1}"
col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 列0-7)
row_idx = (subdev_id - 6) # 0-4 (subdevid 6-10 -> 行0-4)
# 尝试多种可能的资源命名格式
possible_names = [
f"P2_batterytestposition_{col_idx}_{row_idx}", # 用户提到的格式
f"P2_{self.LETTERS[row_idx]}{col_idx + 1}", # 原有的A1-E8格式
f"P2_{self.LETTERS[row_idx].lower()}{col_idx + 1}", # 小写字母格式
]
r = None
resource_name = None
for name in possible_names:
if name in self.station_resources:
r = self.station_resources[name]
resource_name = name
break
r = self.station_resources.get(resource_name)
if r:
status_channel = status_row.get(chl_id, {})
metrics = status_channel.get("metrics", {})
# 构建BatteryTestPosition状态数据移除capacity和energy
channel_state = {
# 基本测量数据
"voltage": metrics.get("voltage_V", 0.0),
"current": metrics.get("current_A", 0.0),
"time": metrics.get("totaltime_s", 0.0),
# 状态信息
"status": status_channel.get("state", "unknown"),
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
"voltage": status_channel.get("voltage_V", 0.0),
"current": status_channel.get("current_A", 0.0),
"time": status_channel.get("totaltime_s", 0.0),
# 通道名称标识
"Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
}
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
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
"resources": list(self.station_resources.values())
})
@property
def connection_info(self) -> Dict[str, str]:
@@ -490,6 +572,45 @@ 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}
# ========================
# 辅助方法
# ========================
@@ -538,6 +659,228 @@ class NewareBatteryTestSystem:
except Exception as e:
print(f" 获取状态失败: {e}")
# ========================
# CSV批量提交功能新增
# ========================
def _ensure_local_import_path(self):
"""确保本地模块导入路径"""
base_dir = os.path.dirname(__file__)
if base_dir not in sys.path:
sys.path.insert(0, base_dir)
def _canon(self, bs: str) -> str:
"""规范化电池体系名称"""
return str(bs).strip().replace('-', '_').upper()
def _compute_values(self, row):
"""
计算活性物质质量和容量
Args:
row: DataFrame行数据
Returns:
tuple: (活性物质质量mg, 容量mAh)
"""
pw = float(row['Pole_Weight'])
cm = float(row['集流体质量'])
am = row['活性物质含量']
if isinstance(am, str) and am.endswith('%'):
amv = float(am.rstrip('%')) / 100.0
else:
amv = float(am)
act_mass = (pw - cm) * amv
sc = float(row['克容量mah/g'])
cap = act_mass * sc / 1000.0
return round(act_mass, 2), round(cap, 3)
def _get_xml_builder(self, gen_mod, key: str):
"""
获取对应电池体系的XML生成函数
Args:
gen_mod: generate_xml_content模块
key: 电池体系标识
Returns:
callable: XML生成函数
"""
fmap = {
'LB6': gen_mod.xml_LB6,
'GR_LI': gen_mod.xml_Gr_Li,
'LFP_LI': gen_mod.xml_LFP_Li,
'LFP_GR': gen_mod.xml_LFP_Gr,
'811_LI_002': gen_mod.xml_811_Li_002,
'811_LI_005': gen_mod.xml_811_Li_005,
'SIGR_LI_STEP': gen_mod.xml_SiGr_Li_Step,
'SIGR_LI': gen_mod.xml_SiGr_Li_Step,
'811_SIGR': gen_mod.xml_811_SiGr,
}
if key not in fmap:
raise ValueError(f"未定义电池体系映射: {key}")
return fmap[key]
def _save_xml(self, xml: str, path: str):
"""
保存XML文件
Args:
xml: XML内容
path: 文件路径
"""
with open(path, 'w', encoding='utf-8') as f:
f.write(xml)
def submit_from_csv(self, csv_path: str, output_dir: str = ".") -> dict:
"""
从CSV文件批量提交Neware测试任务设备动作
Args:
csv_path (str): 输入CSV文件路径
output_dir (str): 输出目录用于存储XML文件和备份默认当前目录
Returns:
dict: 执行结果 {"return_info": str, "success": bool, "submitted_count": int}
"""
try:
# 确保可以导入本地模块
self._ensure_local_import_path()
import pandas as pd
import generate_xml_content as gen_mod
from neware_driver import start_test
if self._ros_node:
self._ros_node.lab_logger().info(f"开始从CSV文件提交任务: {csv_path}")
# 读取CSV文件
if not os.path.exists(csv_path):
error_msg = f"CSV文件不存在: {csv_path}"
if self._ros_node:
self._ros_node.lab_logger().error(error_msg)
return {"return_info": error_msg, "success": False, "submitted_count": 0}
df = pd.read_csv(csv_path, encoding='gbk')
# 验证必需列
required = [
'Battery_Code', 'Pole_Weight', '集流体质量', '活性物质含量',
'克容量mah/g', '电池体系', '设备号', '排号', '通道号'
]
missing = [c for c in required if c not in df.columns]
if missing:
error_msg = f"CSV缺少必需列: {missing}"
if self._ros_node:
self._ros_node.lab_logger().error(error_msg)
return {"return_info": error_msg, "success": False, "submitted_count": 0}
# 创建输出目录
xml_dir = os.path.join(output_dir, 'xml_dir')
backup_dir = os.path.join(output_dir, 'backup_dir')
os.makedirs(xml_dir, exist_ok=True)
os.makedirs(backup_dir, exist_ok=True)
if self._ros_node:
self._ros_node.lab_logger().info(
f"输出目录: XML={xml_dir}, 备份={backup_dir}"
)
# 逐行处理CSV数据
submitted_count = 0
results = []
for idx, row in df.iterrows():
try:
coin_id = str(row['Battery_Code'])
# 计算活性物质质量和容量
act_mass, cap_mAh = self._compute_values(row)
if cap_mAh < 0:
error_msg = (
f"容量为负数: Battery_Code={coin_id}, "
f"活性物质质量mg={act_mass}, 容量mah={cap_mAh}"
)
if self._ros_node:
self._ros_node.lab_logger().warning(error_msg)
results.append(f"{idx+1} 失败: {error_msg}")
continue
# 获取电池体系对应的XML生成函数
key = self._canon(row['电池体系'])
builder = self._get_xml_builder(gen_mod, key)
# 生成XML内容
xml_content = builder(act_mass, cap_mAh)
# 获取设备信息
devid = int(row['设备号'])
subdevid = int(row['排号'])
chlid = int(row['通道号'])
# 保存XML文件
recipe_path = os.path.join(
xml_dir,
f"{coin_id}_{devid}_{subdevid}_{chlid}.xml"
)
self._save_xml(xml_content, recipe_path)
# 提交测试任务
resp = start_test(
ip=self.ip,
port=self.port,
devid=devid,
subdevid=subdevid,
chlid=chlid,
CoinID=coin_id,
recipe_path=recipe_path,
backup_dir=backup_dir
)
submitted_count += 1
results.append(f"{idx+1} {coin_id}: {resp}")
if self._ros_node:
self._ros_node.lab_logger().info(
f"已提交 {coin_id} (设备{devid}-{subdevid}-{chlid}): {resp}"
)
except Exception as e:
error_msg = f"{idx+1} 处理失败: {str(e)}"
results.append(error_msg)
if self._ros_node:
self._ros_node.lab_logger().error(error_msg)
# 汇总结果
success_msg = (
f"批量提交完成: 成功{submitted_count}个,共{len(df)}行。"
f"\n详细结果:\n" + "\n".join(results)
)
if self._ros_node:
self._ros_node.lab_logger().info(
f"批量提交完成: 成功{submitted_count}/{len(df)}"
)
return {
"return_info": success_msg,
"success": True,
"submitted_count": submitted_count,
"total_count": len(df),
"results": results
}
except Exception as e:
error_msg = f"批量提交失败: {str(e)}"
if self._ros_node:
self._ros_node.lab_logger().error(error_msg)
return {
"return_info": error_msg,
"success": False,
"submitted_count": 0
}
def get_device_summary(self) -> dict:
"""
获取设备级别的摘要统计设备动作

View File

@@ -0,0 +1,49 @@
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,649 +1,282 @@
# -*- coding: utf-8 -*-
"""
Contains drivers for:
1. SyringePump: Runze Fluid SY-03B (ASCII)
2. EmmMotor: Emm V5.0 Closed-loop Stepper (Modbus-RTU variant)
3. XKCSensor: XKC Non-contact Level Sensor (Modbus-RTU)
"""
import socket
import serial
import time
import sys
import threading
import struct
import serial
import serial.tools.list_ports
import re
import traceback
import queue
from typing import Optional, Dict, List, Any
import time
from typing import Optional, List, Dict, Tuple
try:
from unilabos.device_comms.universal_driver import UniversalDriver
except ImportError:
import logging
class UniversalDriver:
def __init__(self):
self.logger = logging.getLogger(self.__class__.__name__)
def execute_command_from_outer(self, command: str):
pass
# ==============================================================================
# 1. Transport Layer (通信层)
# ==============================================================================
class TransportManager:
class ChinweDevice:
"""
统一通信管理类。
自动识别 串口 (Serial) 或 网络 (TCP) 连接。
ChinWe设备控制类
提供串口通信、电机控制、传感器数据读取等功能
"""
def __init__(self, port: str, baudrate: int = 9600, timeout: float = 3.0, logger=None):
def __init__(self, port: str, baudrate: int = 115200, debug: bool = False):
"""
初始化ChinWe设备
Args:
port: 串口名称如果为None则自动检测
baudrate: 波特率默认115200
"""
self.debug = debug
self.port = port
self.baudrate = baudrate
self.timeout = timeout
self.logger = logger
self.lock = threading.RLock() # 线程锁,确保多设备共用一个连接时不冲突
self.is_tcp = False
self.serial = None
self.socket = None
# 简单判断: 如果包含 ':' (如 192.168.1.1:8899) 或者看起来像 IP则认为是 TCP
if ':' in self.port or (self.port.count('.') == 3 and not self.port.startswith('/')):
self.is_tcp = True
self._connect_tcp()
else:
self._connect_serial()
def _log(self, msg):
if self.logger:
pass
# self.logger.debug(f"[Transport] {msg}")
def _connect_tcp(self):
try:
if ':' in self.port:
host, p = self.port.split(':')
self.tcp_host = host
self.tcp_port = int(p)
else:
self.tcp_host = self.port
self.tcp_port = 8899 # 默认端口
# if self.logger: self.logger.info(f"Connecting TCP {self.tcp_host}:{self.tcp_port} ...")
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.settimeout(self.timeout)
self.socket.connect((self.tcp_host, self.tcp_port))
except Exception as e:
raise ConnectionError(f"TCP connection failed: {e}")
def _connect_serial(self):
try:
# if self.logger: self.logger.info(f"Opening Serial {self.port} (Baud: {self.baudrate}) ...")
self.serial = serial.Serial(
port=self.port,
baudrate=self.baudrate,
timeout=self.timeout
)
except Exception as e:
raise ConnectionError(f"Serial open failed: {e}")
def close(self):
"""关闭连接"""
if self.is_tcp and self.socket:
try: self.socket.close()
except: pass
elif not self.is_tcp and self.serial and self.serial.is_open:
self.serial.close()
def clear_buffer(self):
"""清空缓冲区 (Thread-safe)"""
with self.lock:
if self.is_tcp:
self.socket.setblocking(False)
try:
while True:
if not self.socket.recv(1024): break
except: pass
finally: self.socket.settimeout(self.timeout)
else:
self.serial.reset_input_buffer()
def write(self, data: bytes):
"""发送原始字节"""
with self.lock:
if self.is_tcp:
self.socket.sendall(data)
else:
self.serial.write(data)
def read(self, size: int) -> bytes:
"""读取指定长度字节"""
if self.is_tcp:
data = b''
start = time.time()
while len(data) < size:
if time.time() - start > self.timeout: break
try:
chunk = self.socket.recv(size - len(data))
if not chunk: break
data += chunk
except socket.timeout: break
return data
else:
return self.serial.read(size)
def send_ascii_command(self, command: str) -> str:
"""
发送 ASCII 字符串命令 (如注射泵指令),读取直到 '\r'
"""
with self.lock:
data = command.encode('ascii') if isinstance(command, str) else command
self.clear_buffer()
self.write(data)
# Read until \r
if self.is_tcp:
resp = b''
start = time.time()
while True:
if time.time() - start > self.timeout: break
try:
char = self.socket.recv(1)
if not char: break
resp += char
if char == b'\r': break
except: break
return resp.decode('ascii', errors='ignore').strip()
else:
return self.serial.read_until(b'\r').decode('ascii', errors='ignore').strip()
# ==============================================================================
# 2. Syringe Pump Driver (注射泵)
# ==============================================================================
class SyringePump:
"""SY-03B 注射泵驱动 (ASCII协议)"""
CMD_INITIALIZE = "Z{speed},{drain_port},{output_port}R"
CMD_SWITCH_VALVE = "I{port}R"
CMD_ASPIRATE = "P{vol}R"
CMD_DISPENSE = "D{vol}R"
CMD_DISPENSE_ALL = "A0R"
CMD_STOP = "TR"
CMD_QUERY_STATUS = "Q"
CMD_QUERY_PLUNGER = "?0"
def __init__(self, device_id: int, transport: TransportManager):
if not 1 <= device_id <= 15:
pass # Allow all IDs for now
self.id = str(device_id)
self.transport = transport
def _send(self, template: str, **kwargs) -> str:
cmd = f"/{self.id}" + template.format(**kwargs) + "\r"
return self.transport.send_ascii_command(cmd)
def is_busy(self) -> bool:
"""查询繁忙状态"""
resp = self._send(self.CMD_QUERY_STATUS)
# 响应如 /0` (Ready, 0x60) 或 /0@ (Busy, 0x40)
if len(resp) >= 3:
status_byte = ord(resp[2])
# Bit 5: 1=Ready, 0=Busy
return (status_byte & 0x20) == 0
return False
def wait_until_idle(self, timeout=30):
"""阻塞等待直到空闲"""
start = time.time()
while time.time() - start < timeout:
if not self.is_busy(): return
time.sleep(0.5)
# raise TimeoutError(f"Pump {self.id} wait idle timeout")
pass
def initialize(self, drain_port=0, output_port=0, speed=10):
"""初始化"""
self._send(self.CMD_INITIALIZE, speed=speed, drain_port=drain_port, output_port=output_port)
def switch_valve(self, port: int):
"""切换阀门 (1-8)"""
self._send(self.CMD_SWITCH_VALVE, port=port)
def aspirate(self, steps: int):
"""吸液 (相对步数)"""
self._send(self.CMD_ASPIRATE, vol=steps)
def dispense(self, steps: int):
"""排液 (相对步数)"""
self._send(self.CMD_DISPENSE, vol=steps)
def stop(self):
"""停止"""
self._send(self.CMD_STOP)
def get_position(self) -> int:
"""获取柱塞位置 (步数)"""
resp = self._send(self.CMD_QUERY_PLUNGER)
m = re.search(r'\d+', resp)
return int(m.group()) if m else -1
# ==============================================================================
# 3. Stepper Motor Driver (步进电机)
# ==============================================================================
class EmmMotor:
"""Emm V5.0 闭环步进电机驱动"""
def __init__(self, device_id: int, transport: TransportManager):
self.id = device_id
self.transport = transport
def _send(self, func_code: int, payload: list) -> bytes:
with self.transport.lock:
self.transport.clear_buffer()
# 格式: [ID] [Func] [Data...] [Check=0x6B]
body = [self.id, func_code] + payload
body.append(0x6B) # Checksum
self.transport.write(bytes(body))
# 根据指令不同,读取不同长度响应
read_len = 10 if func_code in [0x31, 0x32, 0x35, 0x24, 0x27] else 4
return self.transport.read(read_len)
def enable(self, on=True):
"""使能 (True=锁轴, False=松轴)"""
state = 1 if on else 0
self._send(0xF3, [0xAB, state, 0])
def run_speed(self, speed_rpm: int, direction=0, acc=10):
"""速度模式运行"""
sp = struct.pack('>H', int(speed_rpm))
self._send(0xF6, [direction, sp[0], sp[1], acc, 0])
def run_position(self, pulses: int, speed_rpm: int, direction=0, acc=10, absolute=False):
"""位置模式运行"""
sp = struct.pack('>H', int(speed_rpm))
pl = struct.pack('>I', int(pulses))
is_abs = 1 if absolute else 0
self._send(0xFD, [direction, sp[0], sp[1], acc, pl[0], pl[1], pl[2], pl[3], is_abs, 0])
def stop(self):
"""停止"""
self._send(0xFE, [0x98, 0])
def set_zero(self):
"""清零位置"""
self._send(0x0A, [])
def get_position(self) -> int:
"""获取当前脉冲位置"""
resp = self._send(0x32, [])
if len(resp) >= 8:
sign = resp[2]
val = struct.unpack('>I', resp[3:7])[0]
return -val if sign == 1 else val
return 0
# ==============================================================================
# 4. Liquid Sensor Driver (液位传感器)
# ==============================================================================
class XKCSensor:
"""XKC RS485 液位传感器 (Modbus RTU)"""
def __init__(self, device_id: int, transport: TransportManager, threshold: int = 300):
self.id = device_id
self.transport = transport
self.threshold = threshold
def _crc(self, data: bytes) -> bytes:
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001: crc = (crc >> 1) ^ 0xA001
else: crc >>= 1
return struct.pack('<H', crc)
def read_level(self) -> Optional[Dict[str, Any]]:
"""
读取液位。
返回: {'level': bool, 'rssi': int}
"""
with self.transport.lock:
self.transport.clear_buffer()
# Modbus Read Registers: 01 03 00 01 00 02 CRC
payload = struct.pack('>HH', 0x0001, 0x0002)
msg = struct.pack('BB', self.id, 0x03) + payload
msg += self._crc(msg)
self.transport.write(msg)
# Read header
h = self.transport.read(3) # Addr, Func, Len
if len(h) < 3: return None
length = h[2]
# Read body + CRC
body = self.transport.read(length + 2)
if len(body) < length + 2:
# Firmware bug fix specific to some modules
if len(body) == 4 and length == 4:
pass
else:
return None
data = body[:-2]
if len(data) == 2:
rssi = data[1]
elif len(data) >= 4:
rssi = (data[2] << 8) | data[3]
else:
return None
return {
'level': rssi > self.threshold,
'rssi': rssi
}
# ==============================================================================
# 5. Main Device Class (ChinweDevice)
# ==============================================================================
class ChinweDevice(UniversalDriver):
"""
ChinWe 工作站主驱动
继承自 UniversalDriver管理所有子设备泵、电机、传感器
"""
def __init__(self, port: str = "192.168.1.200:8899", baudrate: int = 9600,
pump_ids: List[int] = None, motor_ids: List[int] = None,
sensor_id: int = 6, sensor_threshold: int = 300,
timeout: float = 10.0):
"""
初始化 ChinWe 工作站
:param port: 串口号 或 IP:Port
:param baudrate: 串口波特率
:param pump_ids: 注射泵 ID列表 (默认 [1, 2, 3])
:param motor_ids: 步进电机 ID列表 (默认 [4, 5])
:param sensor_id: 液位传感器 ID (默认 6)
:param sensor_threshold: 传感器液位判定阈值
:param timeout: 通信超时时间 (默认 10秒)
"""
super().__init__()
self.port = port
self.baudrate = baudrate
self.timeout = timeout
self.mgr = None
self.serial_port: Optional[serial.Serial] = None
self._voltage: float = 0.0
self._ec_value: float = 0.0
self._ec_adc_value: int = 0
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()
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"]
self.connect()
@property
def is_connected(self) -> bool:
return self._is_connected
"""获取连接状态"""
return self._is_connected and self.serial_port and self.serial_port.is_open
@property
def voltage(self) -> float:
"""获取电源电压值"""
return self._voltage
@property
def ec_value(self) -> float:
"""获取电导率值 (ms/cm)"""
return self._ec_value
# --- 对外功能指令 (Actions) ---
@property
def ec_adc_value(self) -> int:
"""获取EC ADC原始值"""
return self._ec_adc_value
def pump_initialize(self, pump_id: int, drain_port=0, output_port=0, speed=10):
"""指定泵初始化"""
pump_id = int(pump_id)
if pump_id in self.pumps:
self.pumps[pump_id].initialize(drain_port, output_port, speed)
self.pumps[pump_id].wait_until_idle()
@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 False
def pump_aspirate(self, pump_id: int, volume: int, valve_port: int):
"""
泵吸液 (阻塞)
:param valve_port: 阀门端口 (1-8)
"""
pump_id = int(pump_id)
valve_port = int(valve_port)
if pump_id in self.pumps:
pump = self.pumps[pump_id]
# 1. 切换阀门
pump.switch_valve(valve_port)
pump.wait_until_idle()
# 2. 吸液
pump.aspirate(volume)
pump.wait_until_idle()
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
return False
def pump_dispense(self, pump_id: int, volume: int, valve_port: int):
except Exception as e:
print(f"ChinweDevice连接失败: {e}")
self._is_connected = False
return False
def disconnect(self) -> bool:
"""
泵排液 (阻塞)
:param valve_port: 阀门端口 (1-8)
断开串口连接
Returns:
断开是否成功
"""
pump_id = int(pump_id)
valve_port = int(valve_port)
if pump_id in self.pumps:
pump = self.pumps[pump_id]
# 1. 切换阀门
pump.switch_valve(valve_port)
pump.wait_until_idle()
# 2. 排液
pump.dispense(volume)
pump.wait_until_idle()
return True
return False
def pump_valve(self, pump_id: int, port: int):
"""泵切换阀门 (阻塞)"""
pump_id = int(pump_id)
port = int(port)
if pump_id in self.pumps:
pump = self.pumps[pump_id]
pump.switch_valve(port)
pump.wait_until_idle()
return True
return False
def motor_run_continuous(self, motor_id: int, speed: int, direction: str = "顺时针"):
"""
电机一直旋转 (速度模式)
:param direction: "顺时针" or "逆时针"
"""
motor_id = int(motor_id)
if motor_id not in self.motors: return False
dir_val = 0 if direction == "顺时针" else 1
self.motors[motor_id].run_speed(speed, dir_val)
return True
def motor_rotate_quarter(self, motor_id: int, speed: int = 60, direction: str = "顺时针"):
"""
电机旋转1/4圈 (阻塞)
假设电机设置为 3200 脉冲/圈1/4圈 = 800脉冲
"""
motor_id = int(motor_id)
if motor_id not in self.motors: return False
pulses = 800
dir_val = 0 if direction == "顺时针" else 1
self.motors[motor_id].run_position(pulses, speed, dir_val, absolute=False)
# 预估时间阻塞 (单位: 分钟 -> 秒)
# Time(s) = revs / (RPM/60). revs = 0.25. time = 15 / RPM.
estimated_time = 15.0 / max(1, speed)
time.sleep(estimated_time + 0.5)
return True
def motor_stop(self, motor_id: int):
"""电机停止"""
motor_id = int(motor_id)
if motor_id in self.motors:
self.motors[motor_id].stop()
return True
return False
def wait_sensor_level(self, target_state: str = "有液", timeout: int = 30) -> bool:
"""
等待传感器达到指定电平
:param target_state: "有液" or "无液"
"""
target_bool = True if target_state == "有液" else False
self.logger.info(f"Wait sensor: {target_state} ({target_bool}), timeout: {timeout}")
start = time.time()
while time.time() - start < timeout:
if self.sensor_level == target_bool:
if self.serial_port and self.serial_port.is_open:
try:
self.serial_port.close()
self._is_connected = False
print("已断开串口连接")
return True
time.sleep(0.1)
self.logger.warning("Wait sensor level timeout")
return False
def wait_time(self, duration: int) -> bool:
"""
等待指定时间 (秒)
:param duration: 秒
"""
self.logger.info(f"Waiting for {duration} seconds...")
time.sleep(duration)
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
try:
self.serial_port.write((command + "\n").encode('utf-8'))
print(f"发送命令: {command}")
return True
except Exception as e:
print(f"发送命令失败: {e}")
return False
def rotate_motor(self, motor_id: int, turns: float, clockwise: bool = True) -> bool:
"""
使电机转动指定圈数
Args:
motor_id: 电机ID1, 2, 3...
turns: 转动圈数,支持小数
clockwise: True为顺时针False为逆时针
Returns:
命令发送是否成功
"""
if clockwise:
command = f"M {motor_id} CW {turns}"
else:
command = f"M {motor_id} CCW {turns}"
return self._send_motor_command(command)
def execute_command_from_outer(self, command_dict: Dict[str, Any]) -> bool:
"""支持标准 JSON 指令调用"""
return super().execute_command_from_outer(command_dict)
def set_motor_speed(self, motor_id: int, speed: float) -> bool:
"""
设置电机转速(如果设备支持)
Args:
motor_id: 电机ID1, 2, 3...
speed: 转速值
Returns:
命令发送是否成功
"""
command = f"M {motor_id} SPEED {speed}"
return self._send_motor_command(command)
if __name__ == "__main__":
# Test
logging.basicConfig(level=logging.INFO)
dev = ChinweDevice(port="192.168.31.201:8899")
def _read_data(self) -> List[str]:
"""
读取串口数据并解析
Returns:
读取到的数据行列表
"""
print("开始读取串口数据...")
if not self.is_connected:
return []
data_lines = []
try:
while self.serial_port.in_waiting:
time.sleep(0.1) # 等待数据稳定
try:
line = self.serial_port.readline().decode('utf-8', errors='ignore').strip()
if line:
data_lines.append(line)
self._parse_sensor_data(line)
except Exception as ex:
print(f"解码数据错误: {ex}")
except Exception as e:
print(f"读取串口数据错误: {e}")
return data_lines
def _parse_sensor_data(self, line: str) -> None:
"""
解析传感器数据
Args:
line: 接收到的数据行
"""
# 解析电源电压
if "电源电压" in line:
try:
val = float(line.split("")[1].replace("V", "").strip())
self._voltage = val
if self.debug:
print(f"电源电压更新: {val}V")
except Exception:
pass
# 解析电导率和ADC原始值支持两种格式
if "电导率" in line and "ADC原始值" in line:
try:
# 支持格式如电导率2.50ms/cm, ADC原始值2052
ec_match = re.search(r"电导率[:]\s*([\d\.]+)", line)
adc_match = re.search(r"ADC原始值[:]\s*(\d+)", line)
if ec_match:
ec_val = float(ec_match.group(1))
self._ec_value = ec_val
if self.debug:
print(f"电导率更新: {ec_val:.2f} ms/cm")
if adc_match:
adc_val = int(adc_match.group(1))
self._ec_adc_value = adc_val
if self.debug:
print(f"EC ADC原始值更新: {adc_val}")
except Exception:
pass
# 仅电导率无ADC原始值
elif "电导率" in line:
try:
val = float(line.split("")[1].replace("ms/cm", "").strip())
self._ec_value = val
if self.debug:
print(f"电导率更新: {val:.2f} ms/cm")
except Exception:
pass
# 仅ADC原始值如有分开回传场景
elif "ADC原始值" in line:
try:
adc_val = int(line.split("")[1].strip())
self._ec_adc_value = adc_val
if self.debug:
print(f"EC ADC原始值更新: {adc_val}")
except Exception:
pass
def spin_when_ec_ge_0():
pass
def main():
"""测试函数"""
print("=== ChinWe设备测试 ===")
# 创建设备实例
device = ChinweDevice("/dev/tty.usbserial-A5069RR4", debug=True)
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)
# 测试5: 发送电机命令
print("\n5. 发送电机命令测试:")
print(" 5.3 使用通用函数控制电机20顺时针转2圈:")
device.rotate_motor(2, 20.0, clockwise=True)
time.sleep(0.5)
finally:
dev.disconnect()
time.sleep(10)
# 测试7: 断开连接
print("\n7. 断开连接:")
device.disconnect()
if __name__ == "__main__":
main()

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from cgi import print_arguments
from doctest import debug
from typing import Dict, Any, List, Optional
from typing import Dict, Any, List, Optional, Tuple
import requests
from pylabrobot.resources.resource import Resource as ResourcePLR
from pathlib import Path
@@ -19,8 +19,22 @@ from unilabos.devices.workstation.bioyond_studio.config import (
)
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
from unilabos.resources.bioyond.decks import BIOYOND_YB_Deck
from unilabos.resources.graphio import resource_bioyond_to_plr
from unilabos.utils.log import logger
from unilabos.registry.registry import lab_registry
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
class device(BIOYOND_YB_Deck):
@classmethod
def deserialize(cls, data, allow_marshal=False): # type: ignore[override]
patched = dict(data)
if patched.get("type") == "device":
patched["type"] = "Deck"
if patched.get("category") == "device":
patched["category"] = "deck"
return super().deserialize(patched, allow_marshal=allow_marshal)
def _iso_local_now_ms() -> str:
# 文档要求:到毫秒 + Z例如 2025-08-15T05:43:22.814Z
@@ -40,12 +54,14 @@ class BioyondCellWorkstation(BioyondWorkstation):
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
# 使用统一配置,支持自定义覆盖, 从 config.py 加载完整配置
self.bioyond_config ={
self.bioyond_config = {
**API_CONFIG,
"material_type_mappings": MATERIAL_TYPE_MAPPINGS,
"warehouse_mapping": WAREHOUSE_MAPPING,
"debug_mode": False
}
"debug_mode": False,
}
if config:
self.bioyond_config.update(config)
# "material_type_mappings": MATERIAL_TYPE_MAPPINGS
# "warehouse_mapping": WAREHOUSE_MAPPING
@@ -56,6 +72,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
self.http_service_started = self.debug_mode
self._device_id = "bioyond_cell_workstation" # 默认值后续会从_ros_node获取
super().__init__(bioyond_config=config, deck=deck)
self.transfer_target_device_id = self.bioyond_config.get("transfer_target_device_id", "BatteryStation")
self.transfer_target_parent = self.bioyond_config.get("transfer_target_parent", "YB_YH_Deck")
self.transfer_timeout = float(self.bioyond_config.get("transfer_timeout", 180.0))
self.coin_cell_workflow_config = self.bioyond_config.get("coin_cell_workflow_config", {})
self.pending_transfer_materials: List[Dict[str, Any]] = []
self.pending_transfer_plr: List[ResourcePLR] = []
self.update_push_ip() #直接修改奔耀端的报送ip地址
logger.info("已更新奔耀端推送 IP 地址")
@@ -257,7 +279,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
def auto_feeding4to3(
self,
# ★ 修改点:默认模板路径
xlsx_path: Optional[str] = "D:\\UniLab\\Uni-Lab-OS\\unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\material_template.xlsx",
xlsx_path: Optional[str] = "/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx",
# ---------------- WH4 - 加样头面 (Z=1, 12个点位) ----------------
WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0,
WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0,
@@ -324,6 +346,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
"materialName": str(row[5]).strip(),
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
"temperature": 0,
})
# 四号手套箱原液瓶面
for _, row in df.iloc[14:23, 2:9].iterrows():
@@ -335,6 +358,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
"materialType": str(row[7]).strip() if pd.notna(row[7]) else "",
"targetWH": str(row[8]).strip() if pd.notna(row[8]) else "",
"temperature": 0,
})
# 三号手套箱人工堆栈
for _, row in df.iloc[25:40, 2:7].iterrows():
@@ -344,11 +368,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
"materialType": str(row[5]).strip() if pd.notna(row[5]) else "",
"materialId": str(row[6]).strip() if pd.notna(row[6]) else "",
"quantity": 1
"quantity": 1,
"temperature": 0,
})
else:
logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。")
# TODO: 温度下面手动模式没改,上面的改了
# ---------- 模式 2: 手动填写 ----------
if not items:
params = locals()
@@ -391,16 +416,16 @@ class BioyondCellWorkstation(BioyondWorkstation):
order_code = response.get("data", {}).get("orderCode")
if not order_code:
logger.error("上料任务未返回有效 orderCode")
return response
# 等待完成报送
return {"api_response": response, "order_finish": None}
# 等待完成报送
result = self.wait_for_order_finish(order_code)
print("\n" + "="*60)
print("实验记录本结果auto_feeding4to3")
print("="*60)
print(json.dumps(result, indent=2, ensure_ascii=False))
print("="*60 + "\n")
return result
return {
"api_response": response,
"order_finish": result,
"items": items,
}
def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
"""
3.31 自动化下料Excel -> JSON -> POST /api/lims/storage/auto-batch-out-bound
@@ -469,7 +494,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
return response
# 2.14 新建实验
def create_orders(self, xlsx_path: str) -> Dict[str, Any]:
def create_orders(self, xlsx_path: str, *, material_filter: Optional[str] = None) -> Dict[str, Any]:
"""
从 Excel 解析并创建实验2.14
约定:
@@ -478,7 +503,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
- totalMass 自动计算为所有物料质量之和
- createTime 缺失或为空时自动填充为当前日期YYYY/M/D
"""
default_path = Path("D:\\UniLab\\Uni-Lab-OS\\unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025122301.xlsx")
default_path = Path("/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx")
path = Path(xlsx_path) if xlsx_path else default_path
print(f"[create_orders] 使用 Excel 路径: {path}")
if path != default_path:
@@ -614,63 +639,46 @@ class BioyondCellWorkstation(BioyondWorkstation):
print(f"[create_orders] 即将提交订单数量: {len(orders)}")
response = self._post_lims("/api/lims/order/orders", orders)
print(f"[create_orders] 接口返回: {response}")
# 提取所有返回的 orderCode
# 等待任务报送成功
data_list = response.get("data", [])
if not data_list:
logger.error("创建订单未返回有效数据!")
if data_list:
order_code = data_list[0].get("orderCode")
else:
order_code = None
if not order_code:
logger.error("上料任务未返回有效 orderCode")
return response
# 收集所有 orderCode
order_codes = []
for order_item in data_list:
code = order_item.get("orderCode")
if code:
order_codes.append(code)
if not order_codes:
logger.error("未找到任何有效的 orderCode")
return response
print(f"[create_orders] 等待 {len(order_codes)} 个订单完成: {order_codes}")
# 等待所有订单完成并收集报文
all_reports = []
for idx, order_code in enumerate(order_codes, 1):
print(f"[create_orders] 正在等待第 {idx}/{len(order_codes)} 个订单: {order_code}")
result = self.wait_for_order_finish(order_code)
# 提取报文数据
if result.get("status") == "success":
report = result.get("report", {})
all_reports.append(report)
print(f"[create_orders] ✓ 订单 {order_code} 完成")
else:
logger.warning(f"订单 {order_code} 状态异常: {result.get('status')}")
# 即使订单失败,也记录下这个结果
all_reports.append({
"orderCode": order_code,
"status": result.get("status"),
"error": result.get("message", "未知错误")
})
print(f"[create_orders] 所有订单已完成,共收集 {len(all_reports)} 个报文")
print("实验记录本========================create_orders========================")
# 返回所有订单的完成报文
final_result = {
"status": "all_completed",
"total_orders": len(order_codes),
"reports": all_reports,
"original_response": response
# 等待完成报送
result = self.wait_for_order_finish(order_code)
report_data = result.get("report") if isinstance(result, dict) else None
materials_from_report = (
report_data.get("usedMaterials") if isinstance(report_data, dict) else None
)
if materials_from_report:
materials = materials_from_report
logger.info(
"[create_orders] 使用订单完成报送中的物料信息: "
f"{len(materials)}"
)
else:
materials = self._fetch_bioyond_materials(filter_keyword=material_filter)
logger.info(
"[create_orders] 未收到订单报送物料信息,回退到实时查询"
)
print("materials_from_report:", materials_from_report)
# TODO: 需要将 materials 字典转换为 ResourceSlot 对象后才能转运
# self.transfer_resource_to_another(
# resource=[materials],
# mount_resource=["YB_YH_Deck"],
# sites=[None],
# mount_device_id="BatteryStation"
# )
return {
"api_response": response,
"order_finish": result,
"materials": materials,
}
print(f"返回报文数量: {len(all_reports)}")
for i, report in enumerate(all_reports, 1):
print(f"报文 {i}: orderCode={report.get('orderCode', 'N/A')}, status={report.get('status', 'N/A')}")
print("========================")
return final_result
# 2.7 启动调度
def scheduler_start(self) -> Dict[str, Any]:
@@ -698,146 +706,6 @@ class BioyondCellWorkstation(BioyondWorkstation):
"""
return self._post_lims("/api/lims/scheduler/reset")
def scheduler_start_and_auto_feeding(
self,
# ★ Excel路径参数
xlsx_path: Optional[str] = "D:\\UniLab\\Uni-Lab-OS\\unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\material_template.xlsx",
# ---------------- WH4 - 加样头面 (Z=1, 12个点位) ----------------
WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0,
WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0,
WH4_x3_y1_z1_3_materialName: str = "", WH4_x3_y1_z1_3_quantity: float = 0.0,
WH4_x4_y1_z1_4_materialName: str = "", WH4_x4_y1_z1_4_quantity: float = 0.0,
WH4_x5_y1_z1_5_materialName: str = "", WH4_x5_y1_z1_5_quantity: float = 0.0,
WH4_x1_y2_z1_6_materialName: str = "", WH4_x1_y2_z1_6_quantity: float = 0.0,
WH4_x2_y2_z1_7_materialName: str = "", WH4_x2_y2_z1_7_quantity: float = 0.0,
WH4_x3_y2_z1_8_materialName: str = "", WH4_x3_y2_z1_8_quantity: float = 0.0,
WH4_x4_y2_z1_9_materialName: str = "", WH4_x4_y2_z1_9_quantity: float = 0.0,
WH4_x5_y2_z1_10_materialName: str = "", WH4_x5_y2_z1_10_quantity: float = 0.0,
WH4_x1_y3_z1_11_materialName: str = "", WH4_x1_y3_z1_11_quantity: float = 0.0,
WH4_x2_y3_z1_12_materialName: str = "", WH4_x2_y3_z1_12_quantity: float = 0.0,
# ---------------- WH4 - 原液瓶面 (Z=2, 9个点位) ----------------
WH4_x1_y1_z2_1_materialName: str = "", WH4_x1_y1_z2_1_quantity: float = 0.0, WH4_x1_y1_z2_1_materialType: str = "", WH4_x1_y1_z2_1_targetWH: str = "",
WH4_x2_y1_z2_2_materialName: str = "", WH4_x2_y1_z2_2_quantity: float = 0.0, WH4_x2_y1_z2_2_materialType: str = "", WH4_x2_y1_z2_2_targetWH: str = "",
WH4_x3_y1_z2_3_materialName: str = "", WH4_x3_y1_z2_3_quantity: float = 0.0, WH4_x3_y1_z2_3_materialType: str = "", WH4_x3_y1_z2_3_targetWH: str = "",
WH4_x1_y2_z2_4_materialName: str = "", WH4_x1_y2_z2_4_quantity: float = 0.0, WH4_x1_y2_z2_4_materialType: str = "", WH4_x1_y2_z2_4_targetWH: str = "",
WH4_x2_y2_z2_5_materialName: str = "", WH4_x2_y2_z2_5_quantity: float = 0.0, WH4_x2_y2_z2_5_materialType: str = "", WH4_x2_y2_z2_5_targetWH: str = "",
WH4_x3_y2_z2_6_materialName: str = "", WH4_x3_y2_z2_6_quantity: float = 0.0, WH4_x3_y2_z2_6_materialType: str = "", WH4_x3_y2_z2_6_targetWH: str = "",
WH4_x1_y3_z2_7_materialName: str = "", WH4_x1_y3_z2_7_quantity: float = 0.0, WH4_x1_y3_z2_7_materialType: str = "", WH4_x1_y3_z2_7_targetWH: str = "",
WH4_x2_y3_z2_8_materialName: str = "", WH4_x2_y3_z2_8_quantity: float = 0.0, WH4_x2_y3_z2_8_materialType: str = "", WH4_x2_y3_z2_8_targetWH: str = "",
WH4_x3_y3_z2_9_materialName: str = "", WH4_x3_y3_z2_9_quantity: float = 0.0, WH4_x3_y3_z2_9_materialType: str = "", WH4_x3_y3_z2_9_targetWH: str = "",
# ---------------- WH3 - 人工堆栈 (Z=3, 15个点位) ----------------
WH3_x1_y1_z3_1_materialType: str = "", WH3_x1_y1_z3_1_materialId: str = "", WH3_x1_y1_z3_1_quantity: float = 0,
WH3_x2_y1_z3_2_materialType: str = "", WH3_x2_y1_z3_2_materialId: str = "", WH3_x2_y1_z3_2_quantity: float = 0,
WH3_x3_y1_z3_3_materialType: str = "", WH3_x3_y1_z3_3_materialId: str = "", WH3_x3_y1_z3_3_quantity: float = 0,
WH3_x1_y2_z3_4_materialType: str = "", WH3_x1_y2_z3_4_materialId: str = "", WH3_x1_y2_z3_4_quantity: float = 0,
WH3_x2_y2_z3_5_materialType: str = "", WH3_x2_y2_z3_5_materialId: str = "", WH3_x2_y2_z3_5_quantity: float = 0,
WH3_x3_y2_z3_6_materialType: str = "", WH3_x3_y2_z3_6_materialId: str = "", WH3_x3_y2_z3_6_quantity: float = 0,
WH3_x1_y3_z3_7_materialType: str = "", WH3_x1_y3_z3_7_materialId: str = "", WH3_x1_y3_z3_7_quantity: float = 0,
WH3_x2_y3_z3_8_materialType: str = "", WH3_x2_y3_z3_8_materialId: str = "", WH3_x2_y3_z3_8_quantity: float = 0,
WH3_x3_y3_z3_9_materialType: str = "", WH3_x3_y3_z3_9_materialId: str = "", WH3_x3_y3_z3_9_quantity: float = 0,
WH3_x1_y4_z3_10_materialType: str = "", WH3_x1_y4_z3_10_materialId: str = "", WH3_x1_y4_z3_10_quantity: float = 0,
WH3_x2_y4_z3_11_materialType: str = "", WH3_x2_y4_z3_11_materialId: str = "", WH3_x2_y4_z3_11_quantity: float = 0,
WH3_x3_y4_z3_12_materialType: str = "", WH3_x3_y4_z3_12_materialId: str = "", WH3_x3_y4_z3_12_quantity: float = 0,
WH3_x1_y5_z3_13_materialType: str = "", WH3_x1_y5_z3_13_materialId: str = "", WH3_x1_y5_z3_13_quantity: float = 0,
WH3_x2_y5_z3_14_materialType: str = "", WH3_x2_y5_z3_14_materialId: str = "", WH3_x2_y5_z3_14_quantity: float = 0,
WH3_x3_y5_z3_15_materialType: str = "", WH3_x3_y5_z3_15_materialId: str = "", WH3_x3_y5_z3_15_quantity: float = 0,
) -> Dict[str, Any]:
"""
组合函数:先启动调度,然后执行自动化上料
此函数简化了工作流操作,将两个有顺序依赖的操作组合在一起:
1. 启动调度scheduler_start
2. 自动化上料auto_feeding4to3
参数与 auto_feeding4to3 完全相同,支持 Excel 和手动参数两种模式
Returns:
包含调度启动结果和上料结果的字典
"""
logger.info("=" * 60)
logger.info("开始执行组合操作:启动调度 + 自动化上料")
logger.info("=" * 60)
# 步骤1: 启动调度
logger.info("【步骤 1/2】启动调度...")
scheduler_result = self.scheduler_start()
logger.info(f"调度启动结果: {scheduler_result}")
# 检查调度是否启动成功
if scheduler_result.get("code") != 1:
logger.error(f"调度启动失败: {scheduler_result}")
return {
"success": False,
"step": "scheduler_start",
"scheduler_result": scheduler_result,
"error": "调度启动失败"
}
logger.info("✓ 调度启动成功")
# 步骤2: 执行自动化上料
logger.info("【步骤 2/2】执行自动化上料...")
feeding_result = self.auto_feeding4to3(
xlsx_path=xlsx_path,
WH4_x1_y1_z1_1_materialName=WH4_x1_y1_z1_1_materialName, WH4_x1_y1_z1_1_quantity=WH4_x1_y1_z1_1_quantity,
WH4_x2_y1_z1_2_materialName=WH4_x2_y1_z1_2_materialName, WH4_x2_y1_z1_2_quantity=WH4_x2_y1_z1_2_quantity,
WH4_x3_y1_z1_3_materialName=WH4_x3_y1_z1_3_materialName, WH4_x3_y1_z1_3_quantity=WH4_x3_y1_z1_3_quantity,
WH4_x4_y1_z1_4_materialName=WH4_x4_y1_z1_4_materialName, WH4_x4_y1_z1_4_quantity=WH4_x4_y1_z1_4_quantity,
WH4_x5_y1_z1_5_materialName=WH4_x5_y1_z1_5_materialName, WH4_x5_y1_z1_5_quantity=WH4_x5_y1_z1_5_quantity,
WH4_x1_y2_z1_6_materialName=WH4_x1_y2_z1_6_materialName, WH4_x1_y2_z1_6_quantity=WH4_x1_y2_z1_6_quantity,
WH4_x2_y2_z1_7_materialName=WH4_x2_y2_z1_7_materialName, WH4_x2_y2_z1_7_quantity=WH4_x2_y2_z1_7_quantity,
WH4_x3_y2_z1_8_materialName=WH4_x3_y2_z1_8_materialName, WH4_x3_y2_z1_8_quantity=WH4_x3_y2_z1_8_quantity,
WH4_x4_y2_z1_9_materialName=WH4_x4_y2_z1_9_materialName, WH4_x4_y2_z1_9_quantity=WH4_x4_y2_z1_9_quantity,
WH4_x5_y2_z1_10_materialName=WH4_x5_y2_z1_10_materialName, WH4_x5_y2_z1_10_quantity=WH4_x5_y2_z1_10_quantity,
WH4_x1_y3_z1_11_materialName=WH4_x1_y3_z1_11_materialName, WH4_x1_y3_z1_11_quantity=WH4_x1_y3_z1_11_quantity,
WH4_x2_y3_z1_12_materialName=WH4_x2_y3_z1_12_materialName, WH4_x2_y3_z1_12_quantity=WH4_x2_y3_z1_12_quantity,
WH4_x1_y1_z2_1_materialName=WH4_x1_y1_z2_1_materialName, WH4_x1_y1_z2_1_quantity=WH4_x1_y1_z2_1_quantity,
WH4_x1_y1_z2_1_materialType=WH4_x1_y1_z2_1_materialType, WH4_x1_y1_z2_1_targetWH=WH4_x1_y1_z2_1_targetWH,
WH4_x2_y1_z2_2_materialName=WH4_x2_y1_z2_2_materialName, WH4_x2_y1_z2_2_quantity=WH4_x2_y1_z2_2_quantity,
WH4_x2_y1_z2_2_materialType=WH4_x2_y1_z2_2_materialType, WH4_x2_y1_z2_2_targetWH=WH4_x2_y1_z2_2_targetWH,
WH4_x3_y1_z2_3_materialName=WH4_x3_y1_z2_3_materialName, WH4_x3_y1_z2_3_quantity=WH4_x3_y1_z2_3_quantity,
WH4_x3_y1_z2_3_materialType=WH4_x3_y1_z2_3_materialType, WH4_x3_y1_z2_3_targetWH=WH4_x3_y1_z2_3_targetWH,
WH4_x1_y2_z2_4_materialName=WH4_x1_y2_z2_4_materialName, WH4_x1_y2_z2_4_quantity=WH4_x1_y2_z2_4_quantity,
WH4_x1_y2_z2_4_materialType=WH4_x1_y2_z2_4_materialType, WH4_x1_y2_z2_4_targetWH=WH4_x1_y2_z2_4_targetWH,
WH4_x2_y2_z2_5_materialName=WH4_x2_y2_z2_5_materialName, WH4_x2_y2_z2_5_quantity=WH4_x2_y2_z2_5_quantity,
WH4_x2_y2_z2_5_materialType=WH4_x2_y2_z2_5_materialType, WH4_x2_y2_z2_5_targetWH=WH4_x2_y2_z2_5_targetWH,
WH4_x3_y2_z2_6_materialName=WH4_x3_y2_z2_6_materialName, WH4_x3_y2_z2_6_quantity=WH4_x3_y2_z2_6_quantity,
WH4_x3_y2_z2_6_materialType=WH4_x3_y2_z2_6_materialType, WH4_x3_y2_z2_6_targetWH=WH4_x3_y2_z2_6_targetWH,
WH4_x1_y3_z2_7_materialName=WH4_x1_y3_z2_7_materialName, WH4_x1_y3_z2_7_quantity=WH4_x1_y3_z2_7_quantity,
WH4_x1_y3_z2_7_materialType=WH4_x1_y3_z2_7_materialType, WH4_x1_y3_z2_7_targetWH=WH4_x1_y3_z2_7_targetWH,
WH4_x2_y3_z2_8_materialName=WH4_x2_y3_z2_8_materialName, WH4_x2_y3_z2_8_quantity=WH4_x2_y3_z2_8_quantity,
WH4_x2_y3_z2_8_materialType=WH4_x2_y3_z2_8_materialType, WH4_x2_y3_z2_8_targetWH=WH4_x2_y3_z2_8_targetWH,
WH4_x3_y3_z2_9_materialName=WH4_x3_y3_z2_9_materialName, WH4_x3_y3_z2_9_quantity=WH4_x3_y3_z2_9_quantity,
WH4_x3_y3_z2_9_materialType=WH4_x3_y3_z2_9_materialType, WH4_x3_y3_z2_9_targetWH=WH4_x3_y3_z2_9_targetWH,
WH3_x1_y1_z3_1_materialType=WH3_x1_y1_z3_1_materialType, WH3_x1_y1_z3_1_materialId=WH3_x1_y1_z3_1_materialId, WH3_x1_y1_z3_1_quantity=WH3_x1_y1_z3_1_quantity,
WH3_x2_y1_z3_2_materialType=WH3_x2_y1_z3_2_materialType, WH3_x2_y1_z3_2_materialId=WH3_x2_y1_z3_2_materialId, WH3_x2_y1_z3_2_quantity=WH3_x2_y1_z3_2_quantity,
WH3_x3_y1_z3_3_materialType=WH3_x3_y1_z3_3_materialType, WH3_x3_y1_z3_3_materialId=WH3_x3_y1_z3_3_materialId, WH3_x3_y1_z3_3_quantity=WH3_x3_y1_z3_3_quantity,
WH3_x1_y2_z3_4_materialType=WH3_x1_y2_z3_4_materialType, WH3_x1_y2_z3_4_materialId=WH3_x1_y2_z3_4_materialId, WH3_x1_y2_z3_4_quantity=WH3_x1_y2_z3_4_quantity,
WH3_x2_y2_z3_5_materialType=WH3_x2_y2_z3_5_materialType, WH3_x2_y2_z3_5_materialId=WH3_x2_y2_z3_5_materialId, WH3_x2_y2_z3_5_quantity=WH3_x2_y2_z3_5_quantity,
WH3_x3_y2_z3_6_materialType=WH3_x3_y2_z3_6_materialType, WH3_x3_y2_z3_6_materialId=WH3_x3_y2_z3_6_materialId, WH3_x3_y2_z3_6_quantity=WH3_x3_y2_z3_6_quantity,
WH3_x1_y3_z3_7_materialType=WH3_x1_y3_z3_7_materialType, WH3_x1_y3_z3_7_materialId=WH3_x1_y3_z3_7_materialId, WH3_x1_y3_z3_7_quantity=WH3_x1_y3_z3_7_quantity,
WH3_x2_y3_z3_8_materialType=WH3_x2_y3_z3_8_materialType, WH3_x2_y3_z3_8_materialId=WH3_x2_y3_z3_8_materialId, WH3_x2_y3_z3_8_quantity=WH3_x2_y3_z3_8_quantity,
WH3_x3_y3_z3_9_materialType=WH3_x3_y3_z3_9_materialType, WH3_x3_y3_z3_9_materialId=WH3_x3_y3_z3_9_materialId, WH3_x3_y3_z3_9_quantity=WH3_x3_y3_z3_9_quantity,
WH3_x1_y4_z3_10_materialType=WH3_x1_y4_z3_10_materialType, WH3_x1_y4_z3_10_materialId=WH3_x1_y4_z3_10_materialId, WH3_x1_y4_z3_10_quantity=WH3_x1_y4_z3_10_quantity,
WH3_x2_y4_z3_11_materialType=WH3_x2_y4_z3_11_materialType, WH3_x2_y4_z3_11_materialId=WH3_x2_y4_z3_11_materialId, WH3_x2_y4_z3_11_quantity=WH3_x2_y4_z3_11_quantity,
WH3_x3_y4_z3_12_materialType=WH3_x3_y4_z3_12_materialType, WH3_x3_y4_z3_12_materialId=WH3_x3_y4_z3_12_materialId, WH3_x3_y4_z3_12_quantity=WH3_x3_y4_z3_12_quantity,
WH3_x1_y5_z3_13_materialType=WH3_x1_y5_z3_13_materialType, WH3_x1_y5_z3_13_materialId=WH3_x1_y5_z3_13_materialId, WH3_x1_y5_z3_13_quantity=WH3_x1_y5_z3_13_quantity,
WH3_x2_y5_z3_14_materialType=WH3_x2_y5_z3_14_materialType, WH3_x2_y5_z3_14_materialId=WH3_x2_y5_z3_14_materialId, WH3_x2_y5_z3_14_quantity=WH3_x2_y5_z3_14_quantity,
WH3_x3_y5_z3_15_materialType=WH3_x3_y5_z3_15_materialType, WH3_x3_y5_z3_15_materialId=WH3_x3_y5_z3_15_materialId, WH3_x3_y5_z3_15_quantity=WH3_x3_y5_z3_15_quantity,
)
logger.info("=" * 60)
logger.info("组合操作完成")
logger.info("=" * 60)
return {
"success": True,
"scheduler_result": scheduler_result,
"feeding_result": feeding_result
}
# 2.24 物料变更推送
def report_material_change(self, material_obj: Dict[str, Any]) -> Dict[str, Any]:
@@ -882,6 +750,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
return response
# 等待完成报送
result = self.wait_for_order_finish(order_code)
return result
# 2.5 批量查询实验报告(post过滤关键字查询)
@@ -1350,28 +1219,221 @@ class BioyondCellWorkstation(BioyondWorkstation):
})
return final_result
def _fetch_bioyond_materials(
self,
*,
filter_keyword: Optional[str] = None,
type_mode: int = 2,
) -> List[Dict[str, Any]]:
query: Dict[str, Any] = {
"typeMode": type_mode,
"includeDetail": True,
}
if filter_keyword:
query["filter"] = filter_keyword
response = self._post_lims("/api/lims/storage/stock-material", query)
raw_materials = response.get("data")
if not isinstance(raw_materials, list):
raw_materials = []
try:
resource_bioyond_to_plr(
raw_materials,
type_mapping=self.bioyond_config.get("material_type_mappings", MATERIAL_TYPE_MAPPINGS),
deck=self.deck,
)
except Exception as exc:
logger.warning(f"转换奔曜物料到 PLR 失败: {exc}", exc_info=True)
return raw_materials
def _convert_materials_to_plr(self, materials: List[Dict[str, Any]]) -> List[ResourcePLR]:
try:
return resource_bioyond_to_plr(
deepcopy(materials),
type_mapping=self.bioyond_config.get("material_type_mappings", MATERIAL_TYPE_MAPPINGS),
deck=self.deck,
)
except Exception as exc:
logger.error(f"物料转换为 PLR 失败: {exc}", exc_info=True)
return []
def _wait_for_future(self, future, stage: str, timeout: Optional[float] = None):
if future is None:
return None
timeout = timeout or self.transfer_timeout
start = time.time()
while not future.done():
if (time.time() - start) > timeout:
raise TimeoutError(f"{stage} 超时 {timeout}s")
time.sleep(0.05)
return future.result()
def _register_plr_resources(self, resources: List[ResourcePLR]) -> None:
if not resources or not hasattr(self, "_ros_node") or self._ros_node is None:
return
future = ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, resources=resources)
self._wait_for_future(future, "update_resource")
def _get_target_resource(self, name: str) -> ResourcePLR:
if not hasattr(self, "_ros_node") or self._ros_node is None:
raise RuntimeError("ROS 节点未初始化,无法获取资源")
resource = self._ros_node.resource_tracker.figure_resource({"name": name}, try_mode=False) # type: ignore
if resource is None:
raise ValueError(f"未找到目标资源: {name}")
return resource
def _allocate_sites(self, parent_resource: ResourcePLR, count: int) -> List[str]:
if not hasattr(parent_resource, "get_free_sites"):
raise ValueError(f"资源 {parent_resource} 不支持自动分配站位")
free_indices = list(parent_resource.get_free_sites())
if len(free_indices) < count:
raise ValueError(f"{parent_resource.name} 可用站位不足 (need {count}, have {len(free_indices)})")
ordering = list(getattr(parent_resource, "_ordering", {}).keys())
sites: List[str] = []
for idx in free_indices[:count]:
if ordering and idx < len(ordering):
sites.append(ordering[idx])
else:
sites.append(str(idx))
return sites
def _invoke_coin_cell_workflow(self, material_payload: List[Dict[str, Any]]) -> Any:
timeout = float(self.bioyond_config.get("coin_cell_workflow_timeout", 300.0))
workflow_payload: Dict[str, Any] = {}
if isinstance(self.coin_cell_workflow_config, dict):
workflow_payload.update(deepcopy(self.coin_cell_workflow_config))
workflow_payload["materials"] = deepcopy(material_payload)
return self._call_remote_device_method(
self.transfer_target_device_id,
"run_coin_cell_assembly_workflow",
timeout=timeout,
workflow_config=workflow_payload,
)
def _call_remote_device_method(
self,
device_id: str,
method: str,
*,
timeout: Optional[float] = None,
**kwargs,
) -> Any:
if not hasattr(self, "_ros_node") or self._ros_node is None:
raise RuntimeError("ROS 节点未初始化,无法调用远程设备")
if not device_id:
raise ValueError("device_id 不能为空")
if not method:
raise ValueError("method 不能为空")
timeout = timeout or self.transfer_timeout
payload = json.dumps(
{
"function_name": method,
"function_args": kwargs,
},
ensure_ascii=False,
)
future = ROS2DeviceNode.run_async_func(
self._ros_node.execute_single_action,
True,
device_id=device_id,
action_name="_execute_driver_command_async",
action_kwargs={"string": payload},
)
result = self._wait_for_future(future, f"{device_id}.{method}", timeout)
if hasattr(result, "return_info"):
try:
return json.loads(result.return_info)
except Exception:
return result.return_info
return result
def run_feeding_stage(self) -> Dict[str, Any]:
self.create_sample(
board_type="配液瓶(小)板",
bottle_type="配液瓶(小)",
location_code="B01",
name="配液瓶",
warehouse_name="手动堆栈"
)
self.create_sample(
board_type="5ml分液瓶板",
bottle_type="5ml分液瓶",
location_code="B02",
name="分液瓶",
warehouse_name="手动堆栈"
)
self.scheduler_start()
feeding_task = self.auto_feeding4to3(
xlsx_path="/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx"
)
feeding_materials = self._fetch_bioyond_materials()
return {
"feeding_materials": feeding_materials,
"feeding_items": feeding_task.get("items", []),
"feeding_task": feeding_task,
}
def run_liquid_preparation_stage(
self,
feeding_materials: Optional[List[Dict[str, Any]]] = None,
) -> Dict[str, List[Dict[str, Any]]]:
result = self.create_orders(
xlsx_path="/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx"
)
filter_keyword = self.bioyond_config.get("mixing_material_filter") or None
materials = result.get("materials")
if materials is None:
materials = self._fetch_bioyond_materials(filter_keyword=filter_keyword)
return {
"feeding_materials": feeding_materials or [],
"liquid_materials": materials,
}
def run_transfer_stage(
self,
liquid_materials: Optional[List[Dict[str, Any]]] = None,
source_wh_id: Optional[str] = '3a19debc-84b4-0359-e2d4-b3beea49348b',
source_x: int = 1,
source_y: int = 1,
source_z: int = 1
) -> Dict[str, Any]:
"""转运阶段调用transfer_3_to_2_to_1执行3到2到1转运"""
logger.info("开始执行转运阶段 (run_transfer_stage)")
# 暂时注释掉物料转换和跨工站转运逻辑
# transfer_summary: Dict[str, Any] = {}
# try:
# source_materials = liquid_materials or self._fetch_bioyond_materials()
# transfer_plr = self._convert_materials_to_plr(source_materials)
# transfer_summary["plr_count"] = len(transfer_plr)
# ...
# except Exception as exc:
# transfer_summary["error"] = str(exc)
# logger.error(f"跨工站转运失败: {exc}", exc_info=True)
# 只执行核心的3到2到1转运
transfer_result = self.transfer_3_to_2_to_1(
source_wh_id=source_wh_id,
source_x=source_x,
source_y=source_y,
source_z=source_z
)
logger.info("转运阶段执行完成")
return {
"success": True,
"stage": "transfer",
"transfer_result": transfer_result
}
if __name__ == "__main__":
lab_registry.setup()
deck = BIOYOND_YB_Deck(setup=True)
ws = BioyondCellWorkstation(deck=deck)
# ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01")
# logger.info(ws.scheduler_stop())
# logger.info(ws.scheduler_start())
# 继续后续流程
logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱
# # # 使用正斜杠或 Path 对象来指定文件路径
# excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx")
# logger.info(ws.create_orders(excel_path))
# logger.info(ws.transfer_3_to_2_to_1())
# logger.info(ws.transfer_1_to_2())
# logger.info(ws.scheduler_start())
w = BioyondCellWorkstation(deck=deck, address="172.16.28.102", port="502", debug_mode=False)
feeding = w.run_feeding_stage()
liquid = w.run_liquid_preparation_stage(feeding.get("feeding_materials"))
transfer = w.run_transfer_stage(liquid.get("liquid_materials"))
while True:
time.sleep(1)
# re=ws.scheduler_stop()

File diff suppressed because it is too large Load Diff

View File

@@ -1,197 +0,0 @@
{
"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

@@ -1,113 +0,0 @@
# 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,10 +8,8 @@ import os
# BioyondCellWorkstation 默认配置(包含所有必需参数)
API_CONFIG = {
# API 连接配置
# 实机
#"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.118:44389"),
# 仿真机
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),
# "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),# 仿真机
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
@@ -19,7 +17,7 @@ API_CONFIG = {
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
# HTTP 服务配置
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.206"), # HTTP服务监听地址监听计算机飞连ip地址
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.6"), # HTTP服务监听地址监听计算机飞连ip地址
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
"debug_mode": False,# 调试模式
}

View File

@@ -1,84 +0,0 @@
# 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,12 +634,6 @@ class CoincellDeck(Deck):
self.assign_child_resource(waste_tip_box, Coordinate(x=778.0, y=622.0, z=0))
def YH_Deck(name=""):
cd = CoincellDeck(name=name)
cd.setup()
return cd
if __name__ == "__main__":
deck = create_coin_cell_deck()
print(deck)

View File

@@ -1,159 +0,0 @@
Name,DataType,Comment,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_SYS_STOP_STATUS,BOOL,设备暂停中,coil,8220,,
,,,,,,
,BOOL,UNILAB发送电解液瓶数完毕,coil,8720,,
,BOOL,设备请求接受电解液瓶数,coil,8520,,
REG_MSG_ELECTROLYTE_NUM,WORD,电解液使用瓶数,hold_register,496,,
,WORD,负极片盘矩阵点位初始位0,hold_register,440,,
,WORD,隔膜盘矩阵点位初始位0,hold_register,450,,
,WORD,电解液瓶盘_缓存上料矩阵点位初始位0,hold_register,460,,
,WORD,电解液瓶盘_缓存回收矩阵点位初始位0,hold_register,430,,
,WORD,g_电解液瓶盘_手套箱无杆缸矩阵点位初始位0,hold_register,470,,
,WORD,移液枪头矩阵点位初始位0,hold_register,480,,
,WORD,设置负极片盘数,hold_register,443,,
,WORD,设置隔膜盘数,hold_register,453,,
,,,,,,
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,UNILAB发送配方完毕,coil,8700,,
COIL_REQUEST_REC_MSG_STATUS,BOOL,设备请求接受配方,coil,8500,,
REG_MSG_ELECTROLYTE_USE_NUM,INT16,单瓶电解液使用次数,hold_register,11000,,
REG_MSG_ELECTROLYTE_VOLUME,INT16,电解液吸取量,hold_register,11004,,
REG_MSG_ASSEMBLY_PRESSURE,INT16,电池组装压制力,hold_register,11008,,
REG_DATA_ELECTROLYTE_CODE,STRING,电解液二维码序列号,hold_register,10020,,
,BOOL,视觉对位false:使用true:忽略),coil,8300,,
,BOOL,复检false:使用true:忽略),coil,8310,,
,BOOL,手套箱_左仓false:使用true:忽略),coil,8320,,
,BOOL,手套箱_右仓false:使用true:忽略),coil,8420,,
,BOOL,机械手搬送料盘false:使用true:忽略),coil,8330,,
,BOOL,铝箔物料false:使用true:忽略),coil,8340,,
,BOOL,真空检知false:使用true:忽略),coil,8350,,
,BOOL,压制模式false:压力检测模式True:距离模式),coil,8360,,
,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,,
COIL_SYS_UNILAB_INTERACT ,BOOL,忽略Unilab交互false:使用true:忽略),coil,8450,,
,BOOL,忽略电池清洁false:使用true:忽略),colil,8460,,
,,,,,,
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,UNILAB发送配方完毕,coil,8510,,
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,UNILAB接受测试数据完毕,coil,8710,,
REG_DATA_POLE_WEIGHT,FLOAT32,当前电池正极片称重数据,hold_register,10010,,
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,当前单颗电池组装时间,hold_register,10012,,
REG_DATA_ASSEMBLY_PRESSURE,INT16,当前电池组装压制力,hold_register,10014,,
REG_DATA_ELECTROLYTE_VOLUME,INT16,当前电解液加注量,hold_register,10016,,
REG_DATA_ASSEMBLY_TYPE,INT16,组装参数:极片堆叠方式(7/8),hold_register,10018,,
REG_DATA_ELECTROLYTE_CODE,STRING,电解液二维码序列号,hold_register,10020,,
REG_DATA_COIN_CELL_CODE,STRING,电池二维码序列号,hold_register,10030,,
REG_DATA_STACK_VISON_CODE,STRING,物料堆叠复检图片编码,hold_register,10040,,
REG_DATA_ELECTROLYTE_USE_NUM,INT16,当前电极液组装电池数量R,hold_register,10000,,
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,当前电池电压数据,hold_register,10002,,
,INT,机械手吸取物料寄存器1-正极壳、2-铝垫、3-正极片、4-隔膜、5-负极片、6-平垫、7-弹垫、8-负极壳),hold_register,10060,,
,,,,,,
,INT,当前负极片剩余盘数量R,hold_register,10062,PLC地址,1223-新增
,INT,当前隔膜神域盘数量R,hold_register,10064,,
,INT,电解液状态码R,hold_register,10066,,
,REAL,开路电压OK下限值R,hold_register,10068,,
,REAL,开路电压OK上限值R,hold_register,10070,,
,INT,当前进行组装电池数量R,hold_register,10072,,
,INT,当前完成组装电池数量R,hold_register,10074,,
,REAL,10mm正极片剩余物料数量R,hold_register,520,HMI地址,
,REAL,12mm正极片剩余物料数量R,hold_register,522,,
,REAL,16mm正极片剩余物料数量R,hold_register,524,,
,REAL,铝箔剩余物料数量R,hold_register,526,,
,REAL,正极壳剩余物料数量R,hold_register,528,,
,REAL,平垫剩余物料数量R,hold_register,530,,
,REAL,负极壳剩余物料数量R,hold_register,532,,
,REAL,弹垫剩余物料数量R,hold_register,534,,
,REAL,成品电池剩余可容纳数量R,hold_register,536,,
,REAL,成品电池NG槽剩余可容纳数量R,hold_register,538,,
,,,,,,
,REAL,设置10mm正极片厚度W,hold_register,540,,
,REAL,设置12mm正极片厚度W,hold_register,542,,
,REAL,设置16mm正极片厚度W,hold_register,544,,
,REAL,设置铝箔厚度W,hold_register,546,,
,REAL,设置正极壳厚度W,hold_register,548,,
,REAL,设置平垫厚度W,hold_register,550,,
,REAL,设置负极壳厚度W,hold_register,552,,
,REAL,设置弹垫厚度W,hold_register,554,,
,REAL,设置成品电池厚度W,hold_register,556,,
,,,,,,
,,,,,,
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,手套箱压力,hold_register,10050,,
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,手套箱水含量,hold_register,10052,,
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,手套箱氧含量,hold_register,10054,,
,,,,,,
,BOOL,异常100-系统异常,coil,1000,,
,BOOL,异常101-急停,coil,1010,,
,BOOL,异常111-手套箱急停,coil,1110,,
,BOOL,异常112-手套箱内光栅遮挡,coil,1120,,
,BOOL,异常160-移液枪头缺料,coil,1600,,
,BOOL,异常161-正极壳缺料,coil,1610,,
,BOOL,异常162-铝箔垫缺料,coil,1620,,
,BOOL,异常163-正极片缺料,coil,1630,,
,BOOL,异常164-隔膜缺料,coil,1640,,
,BOOL,异常165-负极片缺料,coil,1650,,
,BOOL,异常166-平垫缺料,coil,1660,,
,BOOL,异常167-弹垫缺料,coil,1670,,
,BOOL,异常168-负极壳缺料,coil,1680,,
,BOOL,异常169-成品电池满料,coil,1690,,
,BOOL,异常201-伺服轴01异常,coil,2010,,
,BOOL,异常202-伺服轴02异常,coil,2020,,
,BOOL,异常203-伺服轴03异常,coil,2030,,
,BOOL,异常204-伺服轴04异常,coil,2040,,
,BOOL,异常205-伺服轴05异常,coil,2050,,
,BOOL,异常206-伺服轴06异常,coil,2060,,
,BOOL,异常207-伺服轴07异常,coil,2070,,
,BOOL,异常208-伺服轴08异常,coil,2080,,
,BOOL,异常209-伺服轴09异常,coil,2090,,
,BOOL,异常210-伺服轴10异常,coil,2100,,
,BOOL,异常211-伺服轴11异常,coil,2110,,
,BOOL,异常212-伺服轴12异常,coil,2120,,
,BOOL,异常213-伺服轴13异常,coil,2130,,
,BOOL,异常214-伺服轴14异常,coil,2140,,
,BOOL,异常250-其他元件异常,coil,2500,,
,BOOL,异常251-移液枪通讯异常,coil,2510,,
,BOOL,异常252-移液枪报警,coil,2520,,
,BOOL,异常256-电爪异常,coil,2560,,
,BOOL,异常262-RB报警未知点位错误,coil,2620,,
,BOOL,异常263-RB报警X、Y、Z参数超限制,coil,2630,,
,BOOL,异常264-RB报警视觉参数误差过大,coil,2640,,
,BOOL,异常265-RB报警1#吸嘴取料失败,coil,2650,,
,BOOL,异常266-RB报警2#吸嘴取料失败,coil,2660,,
,BOOL,异常267-RB报警3#吸嘴取料失败,coil,2670,,
,BOOL,异常268-RB报警4#吸嘴取料失败,coil,2680,,
,BOOL,异常269-RB报警取物料盘失败,coil,2690,,
,BOOL,异常280-RB碰撞异常,coil,2800,,
,BOOL,异常290-视觉系统通讯异常,coil,2900,,
,BOOL,异常291-视觉对位NG异常,coil,2910,,
,BOOL,异常292-扫码枪通讯异常,coil,2920,,
,BOOL,异常310-开电移载吸嘴吸真空异常,coil,3100,,
,BOOL,异常311-开电移载吸嘴破真空异常,coil,3110,,
,BOOL,异常312-称重移载吸嘴吸真空异常,coil,3120,,
,BOOL,异常313-称重移载吸嘴破真空异常,coil,3130,,
,BOOL,异常340-开路电压吸嘴移载气缸异常,coil,3400,,
,BOOL,异常342-开路电压吸嘴升降气缸异常,coil,3420,,
,BOOL,异常344-开路电压旋压气缸异常,coil,3440,,
,BOOL,异常350-称重吸嘴移载气缸异常,coil,3500,,
,BOOL,异常352-称重吸嘴升降气缸异常,coil,3520,,
,BOOL,异常354-清洗无尘布移载气缸异常,coil,3540,,
,BOOL,异常356-清洗无尘布压紧气缸异常,coil,3560,,
,BOOL,异常360-电解液瓶定位气缸异常,coil,3600,,
,BOOL,异常362-移液枪头盒定位气缸异常,coil,3620,,
COIL ALARM_364_SERVO_DRIVE_ERROR,BOOL,异常364-试剂瓶夹爪升降气缸异常,coil,3640,,
COIL ALARM_367_SERVO_DRIVER_ERROR,BOOL,异常366-试剂瓶夹爪气缸异常,coil,3660,,
COIL ALARM_370_SERVO_MODULE_ERROR,BOOL,异常370-压制模块吹气气缸异常,coil,3700,,
,,,,,,
,,,,,,
,,,,,,
,,,,,,
,,,,,,
,,,,,,
,,,,,,
COIL + 功能模块/类型 + 具体描述(大写+下划线分隔)--针对bool值,,,,,,
REG + 功能模块/类型 + 具体描述(大写+下划线分隔)--针对寄存器,,,,,,
1 Name DataType Comment 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_SYS_STOP_STATUS BOOL 设备暂停中 coil 8220
9
10 BOOL UNILAB发送电解液瓶数完毕 coil 8720
11 BOOL 设备请求接受电解液瓶数 coil 8520
12 REG_MSG_ELECTROLYTE_NUM WORD 电解液使用瓶数 hold_register 496
13 WORD 负极片盘矩阵点位(初始位0) hold_register 440
14 WORD 隔膜盘矩阵点位(初始位0) hold_register 450
15 WORD 电解液瓶盘_缓存上料矩阵点位(初始位0) hold_register 460
16 WORD 电解液瓶盘_缓存回收矩阵点位(初始位0) hold_register 430
17 WORD g_电解液瓶盘_手套箱无杆缸矩阵点位(初始位0) hold_register 470
18 WORD 移液枪头矩阵点位(初始位0) hold_register 480
19 WORD 设置负极片盘数 hold_register 443
20 WORD 设置隔膜盘数 hold_register 453
21
22 COIL_UNILAB_SEND_MSG_SUCC_CMD BOOL UNILAB发送配方完毕 coil 8700
23 COIL_REQUEST_REC_MSG_STATUS BOOL 设备请求接受配方 coil 8500
24 REG_MSG_ELECTROLYTE_USE_NUM INT16 单瓶电解液使用次数 hold_register 11000
25 REG_MSG_ELECTROLYTE_VOLUME INT16 电解液吸取量 hold_register 11004
26 REG_MSG_ASSEMBLY_PRESSURE INT16 电池组装压制力 hold_register 11008
27 REG_DATA_ELECTROLYTE_CODE STRING 电解液二维码序列号 hold_register 10020
28 BOOL 视觉对位(false:使用,true:忽略) coil 8300
29 BOOL 复检(false:使用,true:忽略) coil 8310
30 BOOL 手套箱_左仓(false:使用,true:忽略) coil 8320
31 BOOL 手套箱_右仓(false:使用,true:忽略) coil 8420
32 BOOL 机械手搬送料盘(false:使用,true:忽略) coil 8330
33 BOOL 铝箔物料(false:使用,true:忽略) coil 8340
34 BOOL 真空检知(false:使用,true:忽略) coil 8350
35 BOOL 压制模式(false:压力检测模式,True:距离模式) coil 8360
36 BOOL 电解液添加模式(false:单次滴液,true:二次滴液) coil 8370
37 BOOL 正极片称重(false:使用,true:忽略) coil 8380
38 BOOL 正负极片组装方式(false:正装,true:倒装) coil 8390
39 BOOL 压制清洁(false:使用,true:忽略) coil 8400
40 BOOL 物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘) coil 8410
41 COIL_SYS_UNILAB_INTERACT BOOL 忽略Unilab交互(false:使用,true:忽略) coil 8450
42 BOOL 忽略电池清洁(false:使用,true:忽略) colil 8460
43
44 COIL_UNILAB_SEND_MSG_SUCC_CMD BOOL UNILAB发送配方完毕 coil 8510
45 COIL_UNILAB_REC_MSG_SUCC_CMD BOOL UNILAB接受测试数据完毕 coil 8710
46 REG_DATA_POLE_WEIGHT FLOAT32 当前电池正极片称重数据 hold_register 10010
47 REG_DATA_ASSEMBLY_PER_TIME FLOAT32 当前单颗电池组装时间 hold_register 10012
48 REG_DATA_ASSEMBLY_PRESSURE INT16 当前电池组装压制力 hold_register 10014
49 REG_DATA_ELECTROLYTE_VOLUME INT16 当前电解液加注量 hold_register 10016
50 REG_DATA_ASSEMBLY_TYPE INT16 组装参数:极片堆叠方式(7/8) hold_register 10018
51 REG_DATA_ELECTROLYTE_CODE STRING 电解液二维码序列号 hold_register 10020
52 REG_DATA_COIN_CELL_CODE STRING 电池二维码序列号 hold_register 10030
53 REG_DATA_STACK_VISON_CODE STRING 物料堆叠复检图片编码 hold_register 10040
54 REG_DATA_ELECTROLYTE_USE_NUM INT16 当前电极液组装电池数量(R) hold_register 10000
55 REG_DATA_OPEN_CIRCUIT_VOLTAGE FLOAT32 当前电池电压数据 hold_register 10002
56 INT 机械手吸取物料寄存器(1-正极壳、2-铝垫、3-正极片、4-隔膜、5-负极片、6-平垫、7-弹垫、8-负极壳) hold_register 10060
57
58 INT 当前负极片剩余盘数量(R) hold_register 10062 PLC地址 1223-新增
59 INT 当前隔膜神域盘数量(R) hold_register 10064
60 INT 电解液状态码(R) hold_register 10066
61 REAL 开路电压OK下限值(R) hold_register 10068
62 REAL 开路电压OK上限值(R) hold_register 10070
63 INT 当前进行组装电池数量(R) hold_register 10072
64 INT 当前完成组装电池数量(R) hold_register 10074
65 REAL 10mm正极片剩余物料数量(R) hold_register 520 HMI地址
66 REAL 12mm正极片剩余物料数量(R) hold_register 522
67 REAL 16mm正极片剩余物料数量(R) hold_register 524
68 REAL 铝箔剩余物料数量(R) hold_register 526
69 REAL 正极壳剩余物料数量(R) hold_register 528
70 REAL 平垫剩余物料数量(R) hold_register 530
71 REAL 负极壳剩余物料数量(R) hold_register 532
72 REAL 弹垫剩余物料数量(R) hold_register 534
73 REAL 成品电池剩余可容纳数量(R) hold_register 536
74 REAL 成品电池NG槽剩余可容纳数量(R) hold_register 538
75
76 REAL 设置10mm正极片厚度(W) hold_register 540
77 REAL 设置12mm正极片厚度(W) hold_register 542
78 REAL 设置16mm正极片厚度(W) hold_register 544
79 REAL 设置铝箔厚度(W) hold_register 546
80 REAL 设置正极壳厚度(W) hold_register 548
81 REAL 设置平垫厚度(W) hold_register 550
82 REAL 设置负极壳厚度(W) hold_register 552
83 REAL 设置弹垫厚度(W) hold_register 554
84 REAL 设置成品电池厚度(W) hold_register 556
85
86
87 REG_DATA_GLOVE_BOX_PRESSURE FLOAT32 手套箱压力 hold_register 10050
88 REG_DATA_GLOVE_BOX_WATER_CONTENT FLOAT32 手套箱水含量 hold_register 10052
89 REG_DATA_GLOVE_BOX_O2_CONTENT FLOAT32 手套箱氧含量 hold_register 10054
90
91 BOOL 异常100-系统异常 coil 1000
92 BOOL 异常101-急停 coil 1010
93 BOOL 异常111-手套箱急停 coil 1110
94 BOOL 异常112-手套箱内光栅遮挡 coil 1120
95 BOOL 异常160-移液枪头缺料 coil 1600
96 BOOL 异常161-正极壳缺料 coil 1610
97 BOOL 异常162-铝箔垫缺料 coil 1620
98 BOOL 异常163-正极片缺料 coil 1630
99 BOOL 异常164-隔膜缺料 coil 1640
100 BOOL 异常165-负极片缺料 coil 1650
101 BOOL 异常166-平垫缺料 coil 1660
102 BOOL 异常167-弹垫缺料 coil 1670
103 BOOL 异常168-负极壳缺料 coil 1680
104 BOOL 异常169-成品电池满料 coil 1690
105 BOOL 异常201-伺服轴01异常 coil 2010
106 BOOL 异常202-伺服轴02异常 coil 2020
107 BOOL 异常203-伺服轴03异常 coil 2030
108 BOOL 异常204-伺服轴04异常 coil 2040
109 BOOL 异常205-伺服轴05异常 coil 2050
110 BOOL 异常206-伺服轴06异常 coil 2060
111 BOOL 异常207-伺服轴07异常 coil 2070
112 BOOL 异常208-伺服轴08异常 coil 2080
113 BOOL 异常209-伺服轴09异常 coil 2090
114 BOOL 异常210-伺服轴10异常 coil 2100
115 BOOL 异常211-伺服轴11异常 coil 2110
116 BOOL 异常212-伺服轴12异常 coil 2120
117 BOOL 异常213-伺服轴13异常 coil 2130
118 BOOL 异常214-伺服轴14异常 coil 2140
119 BOOL 异常250-其他元件异常 coil 2500
120 BOOL 异常251-移液枪通讯异常 coil 2510
121 BOOL 异常252-移液枪报警 coil 2520
122 BOOL 异常256-电爪异常 coil 2560
123 BOOL 异常262-RB报警:未知点位错误 coil 2620
124 BOOL 异常263-RB报警:X、Y、Z参数超限制 coil 2630
125 BOOL 异常264-RB报警:视觉参数误差过大 coil 2640
126 BOOL 异常265-RB报警:1#吸嘴取料失败 coil 2650
127 BOOL 异常266-RB报警:2#吸嘴取料失败 coil 2660
128 BOOL 异常267-RB报警:3#吸嘴取料失败 coil 2670
129 BOOL 异常268-RB报警:4#吸嘴取料失败 coil 2680
130 BOOL 异常269-RB报警:取物料盘失败 coil 2690
131 BOOL 异常280-RB碰撞异常 coil 2800
132 BOOL 异常290-视觉系统通讯异常 coil 2900
133 BOOL 异常291-视觉对位NG异常 coil 2910
134 BOOL 异常292-扫码枪通讯异常 coil 2920
135 BOOL 异常310-开电移载吸嘴吸真空异常 coil 3100
136 BOOL 异常311-开电移载吸嘴破真空异常 coil 3110
137 BOOL 异常312-称重移载吸嘴吸真空异常 coil 3120
138 BOOL 异常313-称重移载吸嘴破真空异常 coil 3130
139 BOOL 异常340-开路电压吸嘴移载气缸异常 coil 3400
140 BOOL 异常342-开路电压吸嘴升降气缸异常 coil 3420
141 BOOL 异常344-开路电压旋压气缸异常 coil 3440
142 BOOL 异常350-称重吸嘴移载气缸异常 coil 3500
143 BOOL 异常352-称重吸嘴升降气缸异常 coil 3520
144 BOOL 异常354-清洗无尘布移载气缸异常 coil 3540
145 BOOL 异常356-清洗无尘布压紧气缸异常 coil 3560
146 BOOL 异常360-电解液瓶定位气缸异常 coil 3600
147 BOOL 异常362-移液枪头盒定位气缸异常 coil 3620
148 COIL ALARM_364_SERVO_DRIVE_ERROR BOOL 异常364-试剂瓶夹爪升降气缸异常 coil 3640
149 COIL ALARM_367_SERVO_DRIVER_ERROR BOOL 异常366-试剂瓶夹爪气缸异常 coil 3660
150 COIL ALARM_370_SERVO_MODULE_ERROR BOOL 异常370-压制模块吹气气缸异常 coil 3700
151
152
153
154
155
156
157
158 COIL + 功能模块/类型 + 具体描述(大写+下划线分隔)--针对bool值
159 REG + 功能模块/类型 + 具体描述(大写+下划线分隔)--针对寄存器

View File

@@ -0,0 +1,64 @@
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

@@ -1,130 +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
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

@@ -1,2 +0,0 @@
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
20251224_172304,-5.537573695435827e-37,-48.45097351074219,1.372190511464448e+16,3820,30,7,b'\x00\x00d\x00eaoR',b'\x00\x00\x01\x00\x00\x00\r\n'
1 Time open_circuit_voltage pole_weight assembly_time assembly_pressure electrolyte_volume coin_num electrolyte_code coin_cell_code
2 20251224_172304 -5.537573695435827e-37 -48.45097351074219 1.372190511464448e+16 3820 30 7 b'\x00\x00d\x00eaoR' b'\x00\x00\x01\x00\x00\x00\r\n'

View File

@@ -1,2 +0,0 @@
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
20251225_105600,5.566961054206384e-37,-53149746331648.0,3271557120.0,3658,10,7,b'\x00\x00d\x00eaoR',b'\x00\x00\x01\x00\x00\x00\r\n'
1 Time open_circuit_voltage pole_weight assembly_time assembly_pressure electrolyte_volume coin_num electrolyte_code coin_cell_code
2 20251225_105600 5.566961054206384e-37 -53149746331648.0 3271557120.0 3658 10 7 b'\x00\x00d\x00eaoR' b'\x00\x00\x01\x00\x00\x00\r\n'

View File

@@ -1,2 +0,0 @@
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
20251229_161836,-5.537573695435827e-37,8.919000478163591e+20,-3.806253867691382e-29,3544,20,7,b'\x00\x00d\x00eaoR',b'\x00\x00\x01\x00\x00\x00\r\n'
1 Time open_circuit_voltage pole_weight assembly_time assembly_pressure electrolyte_volume coin_num electrolyte_code coin_cell_code
2 20251229_161836 -5.537573695435827e-37 8.919000478163591e+20 -3.806253867691382e-29 3544 20 7 b'\x00\x00d\x00eaoR' b'\x00\x00\x01\x00\x00\x00\r\n'

View File

@@ -1,9 +0,0 @@
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
20251230_182319,0.01600000075995922,13.899999618530273,175.0,3836,20,7,b'\x00\x00d\x00eaoR',b'\x00\x00\x01\x00\x00\x00\r\n'
20251230_185306,0.01600000075995922,13.639999389648438,625.0,3819,20,7,deaoR,
20251230_192124,0.0,8.949999809265137,414.0,3803,20,8,deaoR,
20251230_195621,3.8359999656677246,10.069999694824219,205.0,3350,20,8,LG600001,19311909
20251230_200830,0.7929999828338623,9.34999942779541,18.0,3318,20,8,LG600001,19533419
20251230_201123,0.0,9.169999122619629,17.0,3269,20,8,LG600001,20054389
20251230_201410,0.0,9.569999694824219,18.0,3237,20,8,LG600001,YS102704
20251230_201659,0.0,9.699999809265137,169.0,3318,20,8,LG600001,20112754
1 Time open_circuit_voltage pole_weight assembly_time assembly_pressure electrolyte_volume coin_num electrolyte_code coin_cell_code
2 20251230_182319 0.01600000075995922 13.899999618530273 175.0 3836 20 7 b'\x00\x00d\x00eaoR' b'\x00\x00\x01\x00\x00\x00\r\n'
3 20251230_185306 0.01600000075995922 13.639999389648438 625.0 3819 20 7 deaoR 
4 20251230_192124 0.0 8.949999809265137 414.0 3803 20 8 deaoR 
5 20251230_195621 3.8359999656677246 10.069999694824219 205.0 3350 20 8 LG600001 19311909
6 20251230_200830 0.7929999828338623 9.34999942779541 18.0 3318 20 8 LG600001 19533419
7 20251230_201123 0.0 9.169999122619629 17.0 3269 20 8 LG600001 20054389
8 20251230_201410 0.0 9.569999694824219 18.0 3237 20 8 LG600001 YS102704
9 20251230_201659 0.0 9.699999809265137 169.0 3318 20 8 LG600001 20112754

View File

@@ -1,3 +0,0 @@
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
20260106_221708,0.03200000151991844,26.26999855041504,18.0,3803,30,7,NoRead88,22000063
20260106_221957,0.11299999803304672,26.26999855041504,170.0,3787,30,7,LG600001,22124813
1 Time open_circuit_voltage pole_weight assembly_time assembly_pressure electrolyte_volume coin_num electrolyte_code coin_cell_code
2 20260106_221708 0.03200000151991844 26.26999855041504 18.0 3803 30 7 NoRead88 22000063
3 20260106_221957 0.11299999803304672 26.26999855041504 170.0 3787 30 7 LG600001 22124813

View File

@@ -1,536 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""扣式电池组装系统 - 交互式CSV导出演示脚本增强版
此脚本专为交互式使用优化,提供清洁的命令行界面,
禁用了所有调试信息输出,确保用户可以顺畅地输入命令。
主要功能:
1. 手动导出设备数据到CSV文件包含6个关键数据字段
2. 查看CSV文件内容和导出状态
3. 兼容原有的电池组装完成状态自动导出功能
4. 实时查看设备数据和电池数量
数据字段:
- timestamp: 时间戳
- assembly_time: 单颗电池组装时间(秒)
- open_circuit_voltage: 开路电压值V
- pole_weight: 正极片称重数据g
- battery_qr_code: 电池二维码序列号
- electrolyte_qr_code: 电解液二维码序列号
使用方法:
1. 确保设备已连接并可正常通信
2. 运行此脚本: python interactive_battery_export_demo.py
3. 使用交互式命令控制导出功能
"""
import time
import os
import sys
import logging
import csv
from datetime import datetime
from pathlib import Path
# 完全禁用所有调试和信息级别的日志输出
logging.getLogger().setLevel(logging.CRITICAL)
logging.getLogger('pymodbus').setLevel(logging.CRITICAL)
logging.getLogger('unilabos').setLevel(logging.CRITICAL)
logging.getLogger('pymodbus.logging').setLevel(logging.CRITICAL)
logging.getLogger('pymodbus.logging.tcp').setLevel(logging.CRITICAL)
logging.getLogger('pymodbus.logging.base').setLevel(logging.CRITICAL)
logging.getLogger('pymodbus.logging.decoders').setLevel(logging.CRITICAL)
# 添加当前目录到Python路径以便正确导入模块
current_dir = Path(__file__).parent
sys.path.insert(0, str(current_dir.parent.parent.parent)) # 添加unilabos根目录
sys.path.insert(0, str(current_dir)) # 添加当前目录
# 导入扣式电池组装系统
try:
from unilabos.devices.coin_cell_assembly.coin_cell_assembly_system import Coin_Cell_Assembly
except ImportError:
# 如果上述导入失败,尝试直接导入
try:
from coin_cell_assembly_system import Coin_Cell_Assembly
except ImportError as e:
print(f"导入错误: {e}")
print("请确保在正确的目录下运行此脚本或者将unilabos添加到Python路径中")
sys.exit(1)
def clear_screen():
"""清屏函数"""
os.system('cls' if os.name == 'nt' else 'clear')
def print_header():
"""打印程序头部信息"""
print("="*60)
print(" 扣式电池组装系统 - 交互式CSV导出控制台")
print("="*60)
print()
def print_commands():
"""打印可用命令"""
print("可用命令:")
print(" start - 启动电池组装完成状态导出")
print(" stop - 停止导出")
print(" status - 查看导出状态")
print(" data - 查看当前设备数据")
print(" count - 查看当前电池数量")
print(" export - 手动导出当前数据到CSV")
print(" setpath - 设置自定义CSV文件路径")
print(" view - 查看CSV文件内容")
print(" force - 强制继续CSV导出(即使设备停止)")
print(" detail - 显示详细设备状态")
print(" clear - 清屏")
print(" help - 显示帮助信息")
print(" quit - 退出程序")
print("-"*60)
def print_status_info(device, csv_file_path):
"""打印状态信息"""
try:
status = device.get_csv_export_status()
is_running = status.get('running', False)
export_file = status.get('file_path', None)
thread_alive = status.get('thread_alive', False)
device_status = status.get('device_status', 'N/A')
battery_count = status.get('battery_count', 'N/A')
print(f"导出状态: {'运行中' if is_running else '已停止'}")
print(f"导出文件: {export_file if export_file else 'N/A'}")
print(f"线程状态: {'活跃' if thread_alive else '非活跃'}")
print(f"设备状态: {device_status}")
print(f"电池计数: {battery_count}")
# 检查手动导出的CSV文件
if os.path.exists(csv_file_path):
file_size = os.path.getsize(csv_file_path)
print(f"手动导出文件: {csv_file_path} ({file_size} 字节)")
else:
print(f"手动导出文件: {csv_file_path} (不存在)")
# 显示设备运行状态
try:
print("\n=== 设备运行状态 ===")
print(f"系统启动状态: {device.sys_start_status}")
print(f"系统停止状态: {device.sys_stop_status}")
print(f"自动模式状态: {device.sys_auto_status}")
print(f"手动模式状态: {device.sys_hand_status}")
except Exception as e:
print(f"获取设备运行状态失败: {e}")
except Exception as e:
print(f"获取状态失败: {e}")
def collect_device_data(device):
"""收集设备的六个关键数据"""
try:
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 读取各项数据,添加错误处理和重试机制
try:
assembly_time = device.data_assembly_time # 单颗电池组装时间(秒)
# 确保返回的是数值类型
if isinstance(assembly_time, (list, tuple)) and len(assembly_time) > 0:
assembly_time = float(assembly_time[0])
else:
assembly_time = float(assembly_time)
except Exception as e:
print(f"读取组装时间失败: {e}")
assembly_time = 0.0
try:
open_circuit_voltage = device.data_open_circuit_voltage # 开路电压值(V)
# 确保返回的是数值类型
if isinstance(open_circuit_voltage, (list, tuple)) and len(open_circuit_voltage) > 0:
open_circuit_voltage = float(open_circuit_voltage[0])
else:
open_circuit_voltage = float(open_circuit_voltage)
except Exception as e:
print(f"读取开路电压失败: {e}")
open_circuit_voltage = 0.0
try:
pole_weight = device.data_pole_weight # 正极片称重数据(g)
# 确保返回的是数值类型
if isinstance(pole_weight, (list, tuple)) and len(pole_weight) > 0:
pole_weight = float(pole_weight[0])
else:
pole_weight = float(pole_weight)
except Exception as e:
print(f"读取正极片重量失败: {e}")
pole_weight = 0.0
try:
assembly_pressure = device.data_assembly_pressure # 电池压制力(N)
# 确保返回的是数值类型
if isinstance(assembly_pressure, (list, tuple)) and len(assembly_pressure) > 0:
assembly_pressure = int(assembly_pressure[0])
else:
assembly_pressure = int(assembly_pressure)
except Exception as e:
print(f"读取压制力失败: {e}")
assembly_pressure = 0
try:
battery_qr_code = device.data_coin_cell_code # 电池二维码序列号
# 处理字符串类型数据
if isinstance(battery_qr_code, str):
battery_qr_code = battery_qr_code.strip()
else:
battery_qr_code = str(battery_qr_code)
except Exception as e:
print(f"读取电池二维码失败: {e}")
battery_qr_code = "N/A"
try:
electrolyte_qr_code = device.data_electrolyte_code # 电解液二维码序列号
# 处理字符串类型数据
if isinstance(electrolyte_qr_code, str):
electrolyte_qr_code = electrolyte_qr_code.strip()
else:
electrolyte_qr_code = str(electrolyte_qr_code)
except Exception as e:
print(f"读取电解液二维码失败: {e}")
electrolyte_qr_code = "N/A"
# 获取电池数量
try:
battery_count = device.data_assembly_coin_cell_num
# 确保返回的是数值类型
if isinstance(battery_count, (list, tuple)) and len(battery_count) > 0:
battery_count = int(battery_count[0])
else:
battery_count = int(battery_count)
except Exception as e:
print(f"读取电池数量失败: {e}")
battery_count = 0
return {
'Timestamp': timestamp,
'Battery_Count': battery_count,
'Assembly_Time': assembly_time,
'Open_Circuit_Voltage': open_circuit_voltage,
'Pole_Weight': pole_weight,
'Assembly_Pressure': assembly_pressure,
'Battery_Code': battery_qr_code,
'Electrolyte_Code': electrolyte_qr_code
}
except Exception as e:
print(f"收集数据时出错: {e}")
return None
def export_to_csv(data, csv_file_path):
"""将数据导出到CSV文件"""
try:
# 检查文件是否存在,如果不存在则创建并写入表头
file_exists = os.path.exists(csv_file_path)
# 确保目录存在
csv_dir = os.path.dirname(csv_file_path)
if csv_dir:
os.makedirs(csv_dir, exist_ok=True)
# 确保数值字段为正确的数值类型,避免前导单引号问题
processed_data = data.copy()
# 处理数值字段,确保它们是数值类型而不是字符串,增强错误处理
numeric_fields = ['Battery_Count', 'Assembly_Time', 'Open_Circuit_Voltage', 'Pole_Weight', 'Assembly_Pressure']
for field in numeric_fields:
if field in processed_data:
try:
value = processed_data[field]
# 处理可能的列表或元组类型
if isinstance(value, (list, tuple)) and len(value) > 0:
value = value[0]
if field == 'Battery_Count' or field == 'Assembly_Pressure':
processed_data[field] = int(float(value)) # 先转float再转int处理字符串数字
else:
processed_data[field] = float(value)
except (ValueError, TypeError, IndexError) as e:
print(f"字段 {field} 类型转换失败: {e}, 使用默认值")
processed_data[field] = 0 if field == 'Battery_Count' else 0.0
# 处理字符串字段
for field in ['Battery_Code', 'Electrolyte_Code']:
if field in processed_data:
try:
value = processed_data[field]
if isinstance(value, (list, tuple)) and len(value) > 0:
value = value[0]
processed_data[field] = str(value).strip()
except Exception as e:
print(f"字段 {field} 处理失败: {e}, 使用默认值")
processed_data[field] = "N/A"
with open(csv_file_path, 'a', newline='', encoding='utf-8') as csvfile:
fieldnames = ['Timestamp', 'Battery_Count', 'Assembly_Time', 'Open_Circuit_Voltage',
'Pole_Weight', 'Assembly_Pressure', 'Battery_QR_Code', 'Electrolyte_QR_Code']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, quoting=csv.QUOTE_MINIMAL)
# 如果文件不存在,写入表头
if not file_exists:
writer.writeheader()
print(f"创建新的CSV文件: {csv_file_path}")
# 写入数据
writer.writerow(processed_data)
print(f"数据已导出到: {csv_file_path}")
return True
except Exception as e:
print(f"导出CSV时出错: {e}")
return False
def view_csv_content(csv_file_path, lines=10):
"""查看CSV文件内容"""
try:
if not os.path.exists(csv_file_path):
print("CSV文件不存在")
return
with open(csv_file_path, 'r', encoding='utf-8') as csvfile:
content = csvfile.readlines()
if not content:
print("CSV文件为空")
return
print(f"CSV文件内容 (显示最后{min(lines, len(content))}行):")
print("-" * 80)
# 显示表头
if len(content) > 0:
print(content[0].strip())
print("-" * 80)
# 显示最后几行数据
start_line = max(1, len(content) - lines + 1)
for i in range(start_line, len(content)):
print(content[i].strip())
print("-" * 80)
print(f"总共 {len(content)-1} 条数据记录")
except Exception as e:
print(f"读取CSV文件时出错: {e}")
def interactive_demo():
"""
交互式演示模式(优化版)
"""
clear_screen()
print_header()
print("正在初始化设备连接...")
print("设备地址: 192.168.1.20:502")
print("正在尝试连接...")
try:
device = Coin_Cell_Assembly(address="192.168.1.20", port="502")
print("✓ 设备连接成功")
# 测试设备数据读取
print("正在测试设备数据读取...")
try:
test_count = device.data_assembly_coin_cell_num
print(f"✓ 当前电池数量: {test_count}")
except Exception as e:
print(f"⚠ 数据读取测试失败: {e}")
print("设备连接正常,但数据读取可能存在问题")
except Exception as e:
print(f"✗ 设备连接失败: {e}")
print("请检查以下项目:")
print("1. 设备是否已开机并正常运行")
print("2. 网络连接是否正常")
print("3. 设备IP地址是否为192.168.1.20")
print("4. Modbus服务是否在端口502上运行")
input("按回车键退出...")
return
csv_file_path = "battery_data_export.csv"
print(f"CSV文件路径: {os.path.abspath(csv_file_path)}")
print()
print("功能说明:")
print("- 支持手动导出当前设备数据到CSV文件")
print("- 包含六个关键数据: 组装时间、开路电压、正极片重量、电池码、电解液码")
print("- 电池码和电解液码可能显示为N/A当二维码读取失败时")
print("- 支持查看CSV文件内容和导出状态")
print("- 兼容原有的电池组装完成状态自动导出功能")
print()
print_commands()
while True:
try:
command = input("\n请输入命令 > ").strip().lower()
if command == "start":
print("启动电池组装完成状态导出...")
try:
success, message = device.start_battery_completion_export(csv_file_path)
if success:
print(f"{message}")
print("系统正在监控电池组装完成状态...")
else:
print(f"{message}")
except Exception as e:
print(f"启动导出时出错: {e}")
elif command == "stop":
print("停止导出...")
try:
success, message = device.stop_csv_export()
if success:
print(f"{message}")
else:
print(f"{message}")
except Exception as e:
print(f"停止导出时出错: {e}")
elif command == "force":
print("强制继续CSV导出...")
try:
success, message = device.force_continue_csv_export()
if success:
print(f"{message}")
print("注意: CSV导出将继续监控数据变化即使设备处于停止状态")
else:
print(f"{message}")
except AttributeError:
print("✗ 当前版本不支持强制继续功能")
except Exception as e:
print(f"✗ 强制继续失败: {e}")
elif command == "detail":
print("=== 详细设备状态 ===")
print_status_info(device, csv_file_path)
elif command == "status":
print_status_info(device, csv_file_path)
elif command == "data":
print("读取当前设备数据...")
try:
data = collect_device_data(device)
if data:
print("\n=== 当前设备数据 ===")
print(f"时间戳: {data['Timestamp']}")
print(f"电池数量: {data['Battery_Count']}")
print(f"单颗电池组装时间: {data['Assembly_Time']:.2f}")
print(f"开路电压值: {data['Open_Circuit_Voltage']:.4f} V")
print(f"正极片称重数据: {data['Pole_Weight']:.4f} g")
print(f"电池压制力: {data['Assembly_Pressure']} N")
print(f"电池二维码序列号: {data['Battery_Code']}")
print(f"电解液二维码序列号: {data['Electrolyte_Code']}")
print("===================")
else:
print("无法获取设备数据")
except Exception as e:
print(f"读取数据时出错: {e}")
elif command == "count":
print("读取当前电池数量...")
try:
count = device.data_assembly_coin_cell_num
print(f"当前已完成电池数量: {count}")
except Exception as e:
print(f"读取电池数量时出错: {e}")
elif command == "export":
print("正在收集设备数据并导出到CSV...")
data = collect_device_data(device)
if data:
print(f"收集到数据: 电池数量={data.get('Battery_Count', 'N/A')}, 组装时间={data.get('Assembly_Time', 'N/A')}s")
if export_to_csv(data, csv_file_path):
print("✓ 数据已成功导出到CSV文件")
print(f"导出数据: 时间={data['Timestamp']}, 电池数量={data['Battery_Count']}, 组装时间={data['Assembly_Time']}秒, "
f"电压={data['Open_Circuit_Voltage']}V, 重量={data['Pole_Weight']}g, 压制力={data['Assembly_Pressure']}N")
print(f"电池码={data['Battery_Code']}, 电解液码={data['Electrolyte_Code']}")
else:
print("✗ 导出失败")
else:
print("✗ 数据收集失败,无法导出!请检查设备连接状态。")
# 尝试重新连接设备
try:
if hasattr(device, 'connect'):
device.connect()
print("尝试重新连接设备...")
except Exception as e:
print(f"重新连接失败: {e}")
elif command == "setpath":
print("设置自定义CSV文件路径")
print(f"当前CSV文件路径: {csv_file_path}")
new_path = input("请输入新的CSV文件路径包含文件名如: D:/data/my_battery_data.csv: ").strip()
if new_path:
try:
# 确保目录存在
new_dir = os.path.dirname(new_path)
if new_dir and not os.path.exists(new_dir):
os.makedirs(new_dir, exist_ok=True)
print(f"✓ 已创建目录: {new_dir}")
csv_file_path = new_path
print(f"✓ CSV文件路径已更新为: {os.path.abspath(csv_file_path)}")
# 检查文件是否存在
if os.path.exists(csv_file_path):
file_size = os.path.getsize(csv_file_path)
print(f"文件已存在,大小: {file_size} 字节")
else:
print("文件不存在,将在首次导出时创建")
except Exception as e:
print(f"✗ 设置路径失败: {e}")
else:
print("路径不能为空")
elif command == "view":
print("查看CSV文件内容...")
view_csv_content(csv_file_path)
elif command == "clear":
clear_screen()
print_header()
print_commands()
elif command == "help":
print_commands()
elif command == "quit" or command == "exit":
print("正在退出...")
# 停止导出
try:
device.stop_csv_export()
print("✓ 导出已停止")
except:
pass
print("程序已退出")
break
elif command == "":
# 空命令,不做任何操作
continue
else:
print(f"未知命令: {command}")
print("输入 'help' 查看可用命令")
except KeyboardInterrupt:
print("\n\n检测到 Ctrl+C正在退出...")
try:
device.stop_csv_export()
print("✓ 导出已停止")
except:
pass
print("程序已退出")
break
except Exception as e:
print(f"执行命令时出错: {e}")
if __name__ == '__main__':
interactive_demo()

View File

@@ -1,107 +0,0 @@
# 电池组装资源冲突问题修复说明
## 问题描述
在运行 `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

@@ -0,0 +1,589 @@
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,7 +32,112 @@ bioyond_cell:
feedback: {}
goal: {}
goal_default:
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
WH3_x1_y1_z3_1_materialId: ''
WH3_x1_y1_z3_1_materialType: ''
WH3_x1_y1_z3_1_quantity: 0
WH3_x1_y2_z3_4_materialId: ''
WH3_x1_y2_z3_4_materialType: ''
WH3_x1_y2_z3_4_quantity: 0
WH3_x1_y3_z3_7_materialId: ''
WH3_x1_y3_z3_7_materialType: ''
WH3_x1_y3_z3_7_quantity: 0
WH3_x1_y4_z3_10_materialId: ''
WH3_x1_y4_z3_10_materialType: ''
WH3_x1_y4_z3_10_quantity: 0
WH3_x1_y5_z3_13_materialId: ''
WH3_x1_y5_z3_13_materialType: ''
WH3_x1_y5_z3_13_quantity: 0
WH3_x2_y1_z3_2_materialId: ''
WH3_x2_y1_z3_2_materialType: ''
WH3_x2_y1_z3_2_quantity: 0
WH3_x2_y2_z3_5_materialId: ''
WH3_x2_y2_z3_5_materialType: ''
WH3_x2_y2_z3_5_quantity: 0
WH3_x2_y3_z3_8_materialId: ''
WH3_x2_y3_z3_8_materialType: ''
WH3_x2_y3_z3_8_quantity: 0
WH3_x2_y4_z3_11_materialId: ''
WH3_x2_y4_z3_11_materialType: ''
WH3_x2_y4_z3_11_quantity: 0
WH3_x2_y5_z3_14_materialId: ''
WH3_x2_y5_z3_14_materialType: ''
WH3_x2_y5_z3_14_quantity: 0
WH3_x3_y1_z3_3_materialId: ''
WH3_x3_y1_z3_3_materialType: ''
WH3_x3_y1_z3_3_quantity: 0
WH3_x3_y2_z3_6_materialId: ''
WH3_x3_y2_z3_6_materialType: ''
WH3_x3_y2_z3_6_quantity: 0
WH3_x3_y3_z3_9_materialId: ''
WH3_x3_y3_z3_9_materialType: ''
WH3_x3_y3_z3_9_quantity: 0
WH3_x3_y4_z3_12_materialId: ''
WH3_x3_y4_z3_12_materialType: ''
WH3_x3_y4_z3_12_quantity: 0
WH3_x3_y5_z3_15_materialId: ''
WH3_x3_y5_z3_15_materialType: ''
WH3_x3_y5_z3_15_quantity: 0
WH4_x1_y1_z1_1_materialName: ''
WH4_x1_y1_z1_1_quantity: 0.0
WH4_x1_y1_z2_1_materialName: ''
WH4_x1_y1_z2_1_materialType: ''
WH4_x1_y1_z2_1_quantity: 0.0
WH4_x1_y1_z2_1_targetWH: ''
WH4_x1_y2_z1_6_materialName: ''
WH4_x1_y2_z1_6_quantity: 0.0
WH4_x1_y2_z2_4_materialName: ''
WH4_x1_y2_z2_4_materialType: ''
WH4_x1_y2_z2_4_quantity: 0.0
WH4_x1_y2_z2_4_targetWH: ''
WH4_x1_y3_z1_11_materialName: ''
WH4_x1_y3_z1_11_quantity: 0.0
WH4_x1_y3_z2_7_materialName: ''
WH4_x1_y3_z2_7_materialType: ''
WH4_x1_y3_z2_7_quantity: 0.0
WH4_x1_y3_z2_7_targetWH: ''
WH4_x2_y1_z1_2_materialName: ''
WH4_x2_y1_z1_2_quantity: 0.0
WH4_x2_y1_z2_2_materialName: ''
WH4_x2_y1_z2_2_materialType: ''
WH4_x2_y1_z2_2_quantity: 0.0
WH4_x2_y1_z2_2_targetWH: ''
WH4_x2_y2_z1_7_materialName: ''
WH4_x2_y2_z1_7_quantity: 0.0
WH4_x2_y2_z2_5_materialName: ''
WH4_x2_y2_z2_5_materialType: ''
WH4_x2_y2_z2_5_quantity: 0.0
WH4_x2_y2_z2_5_targetWH: ''
WH4_x2_y3_z1_12_materialName: ''
WH4_x2_y3_z1_12_quantity: 0.0
WH4_x2_y3_z2_8_materialName: ''
WH4_x2_y3_z2_8_materialType: ''
WH4_x2_y3_z2_8_quantity: 0.0
WH4_x2_y3_z2_8_targetWH: ''
WH4_x3_y1_z1_3_materialName: ''
WH4_x3_y1_z1_3_quantity: 0.0
WH4_x3_y1_z2_3_materialName: ''
WH4_x3_y1_z2_3_materialType: ''
WH4_x3_y1_z2_3_quantity: 0.0
WH4_x3_y1_z2_3_targetWH: ''
WH4_x3_y2_z1_8_materialName: ''
WH4_x3_y2_z1_8_quantity: 0.0
WH4_x3_y2_z2_6_materialName: ''
WH4_x3_y2_z2_6_materialType: ''
WH4_x3_y2_z2_6_quantity: 0.0
WH4_x3_y2_z2_6_targetWH: ''
WH4_x3_y3_z2_9_materialName: ''
WH4_x3_y3_z2_9_materialType: ''
WH4_x3_y3_z2_9_quantity: 0.0
WH4_x3_y3_z2_9_targetWH: ''
WH4_x4_y1_z1_4_materialName: ''
WH4_x4_y1_z1_4_quantity: 0.0
WH4_x4_y2_z1_9_materialName: ''
WH4_x4_y2_z1_9_quantity: 0.0
WH4_x5_y1_z1_5_materialName: ''
WH4_x5_y1_z1_5_quantity: 0.0
WH4_x5_y2_z1_10_materialName: ''
WH4_x5_y2_z1_10_quantity: 0.0
xlsx_path: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
handles: {}
placeholder_keys: {}
result: {}
@@ -42,8 +147,323 @@ bioyond_cell:
feedback: {}
goal:
properties:
WH3_x1_y1_z3_1_materialId:
default: ''
type: string
WH3_x1_y1_z3_1_materialType:
default: ''
type: string
WH3_x1_y1_z3_1_quantity:
default: 0
type: number
WH3_x1_y2_z3_4_materialId:
default: ''
type: string
WH3_x1_y2_z3_4_materialType:
default: ''
type: string
WH3_x1_y2_z3_4_quantity:
default: 0
type: number
WH3_x1_y3_z3_7_materialId:
default: ''
type: string
WH3_x1_y3_z3_7_materialType:
default: ''
type: string
WH3_x1_y3_z3_7_quantity:
default: 0
type: number
WH3_x1_y4_z3_10_materialId:
default: ''
type: string
WH3_x1_y4_z3_10_materialType:
default: ''
type: string
WH3_x1_y4_z3_10_quantity:
default: 0
type: number
WH3_x1_y5_z3_13_materialId:
default: ''
type: string
WH3_x1_y5_z3_13_materialType:
default: ''
type: string
WH3_x1_y5_z3_13_quantity:
default: 0
type: number
WH3_x2_y1_z3_2_materialId:
default: ''
type: string
WH3_x2_y1_z3_2_materialType:
default: ''
type: string
WH3_x2_y1_z3_2_quantity:
default: 0
type: number
WH3_x2_y2_z3_5_materialId:
default: ''
type: string
WH3_x2_y2_z3_5_materialType:
default: ''
type: string
WH3_x2_y2_z3_5_quantity:
default: 0
type: number
WH3_x2_y3_z3_8_materialId:
default: ''
type: string
WH3_x2_y3_z3_8_materialType:
default: ''
type: string
WH3_x2_y3_z3_8_quantity:
default: 0
type: number
WH3_x2_y4_z3_11_materialId:
default: ''
type: string
WH3_x2_y4_z3_11_materialType:
default: ''
type: string
WH3_x2_y4_z3_11_quantity:
default: 0
type: number
WH3_x2_y5_z3_14_materialId:
default: ''
type: string
WH3_x2_y5_z3_14_materialType:
default: ''
type: string
WH3_x2_y5_z3_14_quantity:
default: 0
type: number
WH3_x3_y1_z3_3_materialId:
default: ''
type: string
WH3_x3_y1_z3_3_materialType:
default: ''
type: string
WH3_x3_y1_z3_3_quantity:
default: 0
type: number
WH3_x3_y2_z3_6_materialId:
default: ''
type: string
WH3_x3_y2_z3_6_materialType:
default: ''
type: string
WH3_x3_y2_z3_6_quantity:
default: 0
type: number
WH3_x3_y3_z3_9_materialId:
default: ''
type: string
WH3_x3_y3_z3_9_materialType:
default: ''
type: string
WH3_x3_y3_z3_9_quantity:
default: 0
type: number
WH3_x3_y4_z3_12_materialId:
default: ''
type: string
WH3_x3_y4_z3_12_materialType:
default: ''
type: string
WH3_x3_y4_z3_12_quantity:
default: 0
type: number
WH3_x3_y5_z3_15_materialId:
default: ''
type: string
WH3_x3_y5_z3_15_materialType:
default: ''
type: string
WH3_x3_y5_z3_15_quantity:
default: 0
type: number
WH4_x1_y1_z1_1_materialName:
default: ''
type: string
WH4_x1_y1_z1_1_quantity:
default: 0.0
type: number
WH4_x1_y1_z2_1_materialName:
default: ''
type: string
WH4_x1_y1_z2_1_materialType:
default: ''
type: string
WH4_x1_y1_z2_1_quantity:
default: 0.0
type: number
WH4_x1_y1_z2_1_targetWH:
default: ''
type: string
WH4_x1_y2_z1_6_materialName:
default: ''
type: string
WH4_x1_y2_z1_6_quantity:
default: 0.0
type: number
WH4_x1_y2_z2_4_materialName:
default: ''
type: string
WH4_x1_y2_z2_4_materialType:
default: ''
type: string
WH4_x1_y2_z2_4_quantity:
default: 0.0
type: number
WH4_x1_y2_z2_4_targetWH:
default: ''
type: string
WH4_x1_y3_z1_11_materialName:
default: ''
type: string
WH4_x1_y3_z1_11_quantity:
default: 0.0
type: number
WH4_x1_y3_z2_7_materialName:
default: ''
type: string
WH4_x1_y3_z2_7_materialType:
default: ''
type: string
WH4_x1_y3_z2_7_quantity:
default: 0.0
type: number
WH4_x1_y3_z2_7_targetWH:
default: ''
type: string
WH4_x2_y1_z1_2_materialName:
default: ''
type: string
WH4_x2_y1_z1_2_quantity:
default: 0.0
type: number
WH4_x2_y1_z2_2_materialName:
default: ''
type: string
WH4_x2_y1_z2_2_materialType:
default: ''
type: string
WH4_x2_y1_z2_2_quantity:
default: 0.0
type: number
WH4_x2_y1_z2_2_targetWH:
default: ''
type: string
WH4_x2_y2_z1_7_materialName:
default: ''
type: string
WH4_x2_y2_z1_7_quantity:
default: 0.0
type: number
WH4_x2_y2_z2_5_materialName:
default: ''
type: string
WH4_x2_y2_z2_5_materialType:
default: ''
type: string
WH4_x2_y2_z2_5_quantity:
default: 0.0
type: number
WH4_x2_y2_z2_5_targetWH:
default: ''
type: string
WH4_x2_y3_z1_12_materialName:
default: ''
type: string
WH4_x2_y3_z1_12_quantity:
default: 0.0
type: number
WH4_x2_y3_z2_8_materialName:
default: ''
type: string
WH4_x2_y3_z2_8_materialType:
default: ''
type: string
WH4_x2_y3_z2_8_quantity:
default: 0.0
type: number
WH4_x2_y3_z2_8_targetWH:
default: ''
type: string
WH4_x3_y1_z1_3_materialName:
default: ''
type: string
WH4_x3_y1_z1_3_quantity:
default: 0.0
type: number
WH4_x3_y1_z2_3_materialName:
default: ''
type: string
WH4_x3_y1_z2_3_materialType:
default: ''
type: string
WH4_x3_y1_z2_3_quantity:
default: 0.0
type: number
WH4_x3_y1_z2_3_targetWH:
default: ''
type: string
WH4_x3_y2_z1_8_materialName:
default: ''
type: string
WH4_x3_y2_z1_8_quantity:
default: 0.0
type: number
WH4_x3_y2_z2_6_materialName:
default: ''
type: string
WH4_x3_y2_z2_6_materialType:
default: ''
type: string
WH4_x3_y2_z2_6_quantity:
default: 0.0
type: number
WH4_x3_y2_z2_6_targetWH:
default: ''
type: string
WH4_x3_y3_z2_9_materialName:
default: ''
type: string
WH4_x3_y3_z2_9_materialType:
default: ''
type: string
WH4_x3_y3_z2_9_quantity:
default: 0.0
type: number
WH4_x3_y3_z2_9_targetWH:
default: ''
type: string
WH4_x4_y1_z1_4_materialName:
default: ''
type: string
WH4_x4_y1_z1_4_quantity:
default: 0.0
type: number
WH4_x4_y2_z1_9_materialName:
default: ''
type: string
WH4_x4_y2_z1_9_quantity:
default: 0.0
type: number
WH4_x5_y1_z1_5_materialName:
default: ''
type: string
WH4_x5_y1_z1_5_quantity:
default: 0.0
type: number
WH4_x5_y2_z1_10_materialName:
default: ''
type: string
WH4_x5_y2_z1_10_quantity:
default: 0.0
type: number
xlsx_path:
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025122301.xlsx
default: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
type: string
required: []
type: object
@@ -151,14 +571,7 @@ bioyond_cell:
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: 配液瓶数
handles: {}
placeholder_keys: {}
result: {}
schema:
@@ -408,6 +821,154 @@ bioyond_cell:
title: resource_tree_transfer参数
type: object
type: UniLabJsonCommand
auto-run_feeding_stage:
feedback: {}
goal: {}
goal_default: {}
handles:
input: []
output:
- data_key: feeding_materials
data_source: executor
data_type: resource
handler_key: feeding_materials
label: Feeding Materials
placeholder_keys: {}
result:
properties:
feeding_materials:
items:
type: object
type: array
required:
- feeding_materials
type: object
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: run_feeding_stage参数
type: object
type: UniLabJsonCommand
auto-run_liquid_preparation_stage:
feedback: {}
goal: {}
goal_default: {}
handles:
input:
- data_key: feeding_materials
data_source: handle
data_type: resource
handler_key: feeding_materials
label: Feeding Materials
output:
- data_key: liquid_materials
data_source: executor
data_type: resource
handler_key: liquid_materials
label: Liquid Materials
placeholder_keys: {}
result:
properties:
feeding_materials:
items:
type: object
type: array
liquid_materials:
items:
type: object
type: array
required:
- liquid_materials
type: object
schema:
description: ''
properties:
feedback: {}
goal:
properties:
feeding_materials:
items:
type: object
type: array
required: []
type: object
result: {}
required:
- goal
title: run_liquid_preparation_stage参数
type: object
type: UniLabJsonCommand
auto-run_transfer_stage:
feedback: {}
goal: {}
goal_default: {}
handles:
input:
- data_key: liquid_materials
data_source: handle
data_type: resource
handler_key: liquid_materials
label: Liquid Materials
output:
- data_key: transfer_materials
data_source: executor
data_type: resource
handler_key: transfer_materials
label: Transfer Materials
placeholder_keys: {}
result:
properties:
liquid_materials:
items:
type: object
type: array
transfer_materials:
items:
type: object
type: array
transfer_summary:
type: object
required:
- transfer_materials
type: object
schema:
description: ''
properties:
feedback: {}
goal:
properties:
liquid_materials:
items:
type: object
type: array
required: []
type: object
result:
properties:
liquid_materials:
items:
type: object
type: array
transfer_materials:
items:
type: object
type: array
transfer_summary:
type: object
type: object
required:
- goal
title: run_transfer_stage参数
type: object
type: UniLabJsonCommand
auto-scheduler_continue:
feedback: {}
goal: {}
@@ -471,31 +1032,6 @@ bioyond_cell:
title: scheduler_start参数
type: object
type: UniLabJsonCommand
auto-scheduler_start_and_auto_feeding:
feedback: {}
goal: {}
goal_default:
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 组合函数:先启动调度,然后执行自动化上料
properties:
feedback: {}
goal:
properties:
xlsx_path:
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
type: string
required: []
type: object
result: {}
required:
- goal
title: scheduler_start_and_auto_feeding参数
type: object
type: UniLabJsonCommand
auto-scheduler_stop:
feedback: {}
goal: {}
@@ -724,7 +1260,7 @@ bioyond_cell:
device_id: String
type: python
config_info: []
description: ''
description: 配液工站
handles: []
icon: benyao2.webp
init_param_schema:

View File

@@ -1,344 +0,0 @@
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,117 +115,6 @@ coincellassemblyworkstation_device:
title: func_allpack_cmd参数
type: object
type: UniLabJsonCommand
auto-func_allpack_cmd_simp:
feedback: {}
goal: {}
goal_default:
assembly_pressure: 4200
assembly_type: 7
battery_clean_ignore: false
battery_pressure_mode: true
dual_drop_first_volume: 25
dual_drop_mode: false
dual_drop_start_timing: false
dual_drop_suction_timing: false
elec_num: null
elec_use_num: null
elec_vol: 50
file_path: /Users/sml/work
fujipian_juzhendianwei: 0
fujipian_panshu: 0
gemo_juzhendianwei: 0
gemopanshu: 0
lvbodian: true
qiangtou_juzhendianwei: 0
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 简化版电池组装函数,整合了参数设置和双滴模式
properties:
feedback: {}
goal:
properties:
assembly_pressure:
default: 4200
description: 电池压制力(N)
type: integer
assembly_type:
default: 7
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
type: integer
battery_clean_ignore:
default: false
description: 是否忽略电池清洁步骤
type: boolean
battery_pressure_mode:
default: true
description: 是否启用压力模式
type: boolean
dual_drop_first_volume:
default: 25
description: 二次滴液第一次排液体积(μL)
type: integer
dual_drop_mode:
default: false
description: 电解液添加模式(false=单次滴液, true=二次滴液)
type: boolean
dual_drop_start_timing:
default: false
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
type: boolean
dual_drop_suction_timing:
default: false
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
type: boolean
elec_num:
description: 电解液瓶数
type: string
elec_use_num:
description: 每瓶电解液组装电池数
type: string
elec_vol:
default: 50
description: 电解液吸液量(μL)
type: integer
file_path:
default: /Users/sml/work
description: 实验记录保存路径
type: string
fujipian_juzhendianwei:
default: 0
description: 负极片矩阵点位
type: integer
fujipian_panshu:
default: 0
description: 负极片盘数
type: integer
gemo_juzhendianwei:
default: 0
description: 隔膜矩阵点位
type: integer
gemopanshu:
default: 0
description: 隔膜盘数
type: integer
lvbodian:
default: true
description: 是否使用铝箔垫片
type: boolean
qiangtou_juzhendianwei:
default: 0
description: 枪头盒矩阵点位
type: integer
required:
- elec_num
- elec_use_num
type: object
result: {}
required:
- goal
title: func_allpack_cmd_simp参数
type: object
type: UniLabJsonCommand
auto-func_get_csv_export_status:
feedback: {}
goal: {}
@@ -289,27 +178,6 @@ coincellassemblyworkstation_device:
title: func_pack_device_init参数
type: object
type: UniLabJsonCommand
auto-func_pack_device_init_auto_start_combined:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 组合函数:设备初始化 + 切换自动模式 + 启动
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: func_pack_device_init_auto_start_combined参数
type: object
type: UniLabJsonCommand
auto-func_pack_device_start:
feedback: {}
goal: {}
@@ -382,15 +250,7 @@ coincellassemblyworkstation_device:
goal: {}
goal_default:
bottle_num: null
handles:
input:
- data_key: bottle_num
data_source: workflow
data_type: integer
handler_key: bottle_count
io_type: source
label: 配液瓶数
required: true
handles: {}
placeholder_keys: {}
result: {}
schema:
@@ -400,7 +260,7 @@ coincellassemblyworkstation_device:
goal:
properties:
bottle_num:
type: integer
type: string
required:
- bottle_num
type: object
@@ -617,6 +477,171 @@ coincellassemblyworkstation_device:
title: qiming_coin_cell_code参数
type: object
type: UniLabJsonCommand
auto-run_coin_cell_assembly_workflow:
feedback: {}
goal:
properties:
workflow_config:
type: object
required: []
type: object
goal_default:
workflow_config: {}
handles:
input:
- data_key: workflow_config
data_source: handle
data_type: resource
handler_key: WorkflowConfig
label: Workflow Config
output:
- data_key: qiming
data_source: executor
data_type: resource
handler_key: QimingResult
label: Qiming Result
- data_key: workflow_steps
data_source: executor
data_type: resource
handler_key: WorkflowSteps
label: Workflow Steps
- data_key: packaging
data_source: executor
data_type: resource
handler_key: PackagingResult
label: Packaging Result
- data_key: finish
data_source: executor
data_type: resource
handler_key: FinishResult
label: Finish Result
placeholder_keys: {}
result:
properties:
finish:
properties:
send_finished:
type: object
stop:
type: object
required:
- send_finished
- stop
type: object
packaging:
properties:
bottle_num:
type: integer
command:
type: object
result:
type: object
required:
- bottle_num
- command
- result
type: object
qiming:
properties:
params:
type: object
success:
type: boolean
required:
- params
- success
type: object
workflow_steps:
type: object
required:
- qiming
- workflow_steps
- packaging
- finish
type: object
schema:
description: ''
properties:
feedback: {}
goal:
properties:
workflow_config:
type: object
required: []
type: object
result:
properties:
finish:
properties:
send_finished:
type: object
stop:
type: object
required:
- send_finished
- stop
type: object
packaging:
properties:
bottle_num:
type: integer
command:
type: object
result:
type: object
required:
- bottle_num
- command
- result
type: object
qiming:
properties:
params:
type: object
success:
type: boolean
required:
- params
- success
type: object
workflow_steps:
type: object
required:
- qiming
- workflow_steps
- packaging
- finish
type: object
required:
- goal
title: run_coin_cell_assembly_workflow参数
type: object
type: UniLabJsonCommand
auto-run_packaging_workflow:
feedback: {}
goal: {}
goal_default:
workflow_config: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
workflow_config:
type: object
required:
- workflow_config
type: object
result: {}
required:
- goal
title: run_packaging_workflow参数
type: object
type: UniLabJsonCommand
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
status_types:
data_assembly_coin_cell_num: int
@@ -640,7 +665,7 @@ coincellassemblyworkstation_device:
sys_status: str
type: python
config_info: []
description: ''
description: 扣电工站
handles: []
icon: koudian.webp
init_param_schema:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,8 @@
neware_battery_test_system:
category:
- neware_battery_test_system
- neware
- battery_test
class:
action_value_mappings:
auto-post_init:
@@ -70,6 +72,38 @@ neware_battery_test_system:
title: test_connection参数
type: object
type: UniLabJsonCommand
debug_resource_names:
feedback: {}
goal: {}
goal_default: {}
handles: {}
result:
return_info: return_info
success: success
schema:
description: 调试方法:显示所有资源的实际名称
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result:
properties:
return_info:
description: 资源调试信息
type: string
success:
description: 是否成功
type: boolean
required:
- return_info
- success
type: object
required:
- goal
type: object
type: UniLabJsonCommand
export_status_json:
feedback: {}
goal:
@@ -219,7 +253,9 @@ neware_battery_test_system:
goal_default:
string: ''
handles: {}
result: {}
result:
return_info: return_info
success: success
schema:
description: ''
properties:
@@ -252,6 +288,56 @@ neware_battery_test_system:
title: StrSingleInput
type: object
type: StrSingleInput
submit_from_csv:
feedback: {}
goal:
csv_path: string
output_dir: string
goal_default:
csv_path: ''
output_dir: .
handles: {}
result:
return_info: return_info
submitted_count: submitted_count
success: success
schema:
description: 从CSV文件批量提交Neware测试任务
properties:
feedback: {}
goal:
properties:
csv_path:
description: 输入CSV文件的绝对路径
type: string
output_dir:
description: 输出目录用于存储XML和备份文件默认当前目录
type: string
required:
- csv_path
type: object
result:
properties:
return_info:
description: 执行结果详细信息
type: string
submitted_count:
description: 成功提交的任务数量
type: integer
success:
description: 是否成功
type: boolean
total_count:
description: CSV文件中的总行数
type: integer
required:
- return_info
- success
type: object
required:
- goal
type: object
type: UniLabJsonCommand
test_connection_action:
feedback: {}
goal: {}
@@ -284,7 +370,7 @@ neware_battery_test_system:
- goal
type: object
type: UniLabJsonCommand
module: unilabos.devices.battery.neware_battery_test_system:NewareBatteryTestSystem
module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
status_types:
channel_status: dict
connection_info: dict
@@ -294,7 +380,7 @@ neware_battery_test_system:
total_channels: int
type: python
config_info: []
description: 新威电池测试系统驱动,支持720个通道的电池测试状态监控和数据导出。通过TCP通信实现远程控制包含完整的物料管理系统,支持2盘电池状态映射和监控
description: 新威电池测试系统驱动,提供720个通道的电池测试状态监控、物料管理和CSV批量提交功能。支持TCP通信实现远程控制包含完整的物料管理系统2盘电池状态映射以及从CSV文件批量提交测试任务的能力
handles: []
icon: ''
init_param_schema:
@@ -310,13 +396,13 @@ neware_battery_test_system:
port:
type: integer
size_x:
default: 500.0
default: 50
type: number
size_y:
default: 500.0
default: 50
type: number
size_z:
default: 2000.0
default: 20
type: number
timeout:
type: integer

View File

@@ -22,7 +22,7 @@ BIOYOND_PolymerReactionStation_Deck:
init_param_schema: {}
registry_type: resource
version: 1.0.0
BIOYOND_YB_Deck:
YB_Deck11:
category:
- deck
class:
@@ -34,15 +34,3 @@ BIOYOND_YB_Deck:
init_param_schema: {}
registry_type: resource
version: 1.0.0
CoincellDeck:
category:
- deck
class:
module: unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:YH_Deck
type: pylabrobot
description: BIOYOND PolymerReactionStation Deck
handles: []
icon: koudian.webp
init_param_schema: {}
registry_type: resource
version: 1.0.0

View File

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

View File

@@ -1,56 +1,45 @@
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
"""
瓶架类定义 - 用于纽扣电池组装工作站
Bottle Carrier Resource Classes
"""
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
from unilabos.resources.bioyond.YB_bottles import (
YB_pei_ye_xiao_Bottle,
)
# 命名约定:试剂瓶-Bottle烧杯-Beaker烧瓶-Flask小瓶-Vial
from __future__ import annotations
from pylabrobot.resources import ResourceHolder
from pylabrobot.resources.utils import create_ordered_items_2d
from unilabos.resources.itemized_carrier import ItemizedCarrier
def YIHUA_Electrolyte_12VialCarrier(name: str) -> BottleCarrier:
"""12瓶架 - 2x6布局"""
# 载架尺寸 (mm)
carrier_size_x = 120.0
carrier_size_y = 250.0
carrier_size_z = 50.0
# 瓶位尺寸
bottle_diameter = 35.0
bottle_spacing_x = 35.0 # X方向间距
bottle_spacing_y = 35.0 # Y方向间距
# 计算起始位置 (居中排列)
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
start_y = (carrier_size_y - (6 - 1) * bottle_spacing_y - bottle_diameter) / 2
def YIHUA_Electrolyte_12VialCarrier(name: str) -> ItemizedCarrier:
"""依华电解液12瓶架 - 3x4布局
Args:
name: 瓶架名称
Returns:
ItemizedCarrier: 包含12个瓶位的瓶架
"""
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=2,
num_items_y=6,
dx=start_x,
dy=start_y,
num_items_x=4,
num_items_y=3,
dx=10.0,
dy=10.0,
dz=5.0,
item_dx=bottle_spacing_x,
item_dy=bottle_spacing_y,
size_x=bottle_diameter,
size_y=bottle_diameter,
size_z=carrier_size_z,
item_dx=70.0,
item_dy=26.67,
size_x=60.0,
size_y=20.0,
size_z=70.0,
)
for k, v in sites.items():
v.name = f"{name}_{v.name}"
carrier = BottleCarrier(
return ItemizedCarrier(
name=name,
size_x=carrier_size_x,
size_y=carrier_size_y,
size_z=carrier_size_z,
size_x=300.0,
size_y=100.0,
size_z=80.0,
num_items_x=4,
num_items_y=3,
sites=sites,
model="Electrolyte_12VialCarrier",
category="bottle_carrier",
)
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,52 +1,35 @@
from typing import Any, Dict, Optional, TypedDict
"""
电极片类定义
Electrode Sheet Resource Classes
"""
from pylabrobot.resources import Resource as ResourcePLR
from pylabrobot.resources import Container
from __future__ import annotations
from typing import Any, Dict, Optional
from pylabrobot.resources.resource import 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):
"""极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料"""
class ElectrodeSheet(Resource):
"""电极片类 - 用于纽扣电池组装"""
def __init__(
self,
name: str = "极片",
size_x: float = 10,
size_y: float = 10,
size_z: float = 10,
name: str,
size_x: float = 12.0,
size_y: float = 12.0,
size_z: float = 0.1,
category: str = "electrode_sheet",
model: Optional[str] = None,
electrode_type: str = "anode", # "anode" 负极, "cathode" 正极, "separator" 隔膜
**kwargs
):
"""初始化极片
"""初始化极片
Args:
name: 极片名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 度 (mm)
name: 极片名称
size_x: X方向尺寸 (mm)
size_y: Y方向尺寸 (mm)
size_z: Z方向尺寸/厚度 (mm)
category: 类别
model: 型号
**kwargs: 其他参数传递给父类
electrode_type: 电极类型
"""
super().__init__(
name=name,
@@ -54,142 +37,31 @@ class ElectrodeSheet(ResourcePLR):
size_y=size_y,
size_z=size_z,
category=category,
model=model,
**kwargs
)
self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(
diameter=14,
thickness=0.1,
mass=0.5,
material_type="copper",
color="#8b4513",
info=None
)
# TODO: 这个还要不要给self._unilabos_state赋值的
self._electrode_type = electrode_type
self._unilabos_state: Dict[str, Any] = {
"electrode_type": electrode_type,
"material": "",
"thickness": size_z,
}
@property
def electrode_type(self) -> str:
"""获取电极类型"""
return self._electrode_type
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]]:
"""格式不变"""
if isinstance(state, dict):
self._unilabos_state.update(state)
def serialize_state(self) -> Dict[str, Any]:
"""序列化状态"""
data = super().serialize_state()
data.update(self._unilabos_state) # Container自身的信息云端物料将保存这一data本地也通过这里的data进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
data.update(self._unilabos_state)
return data
def PositiveCan(name: str) -> ElectrodeSheet:
"""创建正极壳"""
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=3.0, model="PositiveCan")
sheet.load_state({"diameter": 20.0, "thickness": 0.5, "mass": 0.5, "material_type": "aluminum", "color": electrode_colors["PositiveCan"], "info": None})
return sheet
def PositiveElectrode(name: str) -> ElectrodeSheet:
"""创建正极片"""
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="PositiveElectrode")
sheet.load_state({"material_type": "positive_electrode", "color": electrode_colors["PositiveElectrode"]})
return sheet
def NegativeCan(name: str) -> ElectrodeSheet:
"""创建负极壳"""
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=2.0, model="NegativeCan")
sheet.load_state({"material_type": "steel", "color": electrode_colors["NegativeCan"]})
return sheet
def NegativeElectrode(name: str) -> ElectrodeSheet:
"""创建负极片"""
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="NegativeElectrode")
sheet.load_state({"material_type": "negative_electrode", "color": electrode_colors["NegativeElectrode"]})
return sheet
def SpringWasher(name: str) -> ElectrodeSheet:
"""创建弹片"""
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.5, model="SpringWasher")
sheet.load_state({"material_type": "spring_steel", "color": electrode_colors["SpringWasher"]})
return sheet
def FlatWasher(name: str) -> ElectrodeSheet:
"""创建垫片"""
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.2, model="FlatWasher")
sheet.load_state({"material_type": "steel", "color": electrode_colors["FlatWasher"]})
return sheet
def AluminumFoil(name: str) -> ElectrodeSheet:
"""创建铝箔"""
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.05, model="AluminumFoil")
sheet.load_state({"material_type": "aluminum", "color": electrode_colors["AluminumFoil"]})
return sheet
class BatteryState(TypedDict):
color: str # 材料类型对应的颜色
electrolyte_name: str
data_electrolyte_code: str
open_circuit_voltage: float
assembly_pressure: float
electrolyte_volume: float
info: Optional[str] # 附加信息
class Battery(Container):
"""电池类 - 包含组装好的电池"""
def __init__(
self,
name: str = "电池",
size_x: float = 12,
size_y: float = 12,
size_z: float = 6,
category: str = "battery",
model: Optional[str] = None,
**kwargs
):
"""初始化电池
Args:
name: 电池名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 高度 (mm)
category: 类别
model: 型号
**kwargs: 其他参数传递给父类
"""
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
category=category,
model=model,
**kwargs
)
self._unilabos_state: BatteryState = BatteryState(
color=electrode_colors["Battery"],
electrolyte_name="",
data_electrolyte_code="",
open_circuit_voltage=0.0,
assembly_pressure=0.0,
electrolyte_volume=0.0,
info=None
)
def load_state(self, state: Dict[str, Any]) -> None:
"""格式不变"""
super().load_state(state)
self._unilabos_state = state
#序列化
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
"""格式不变"""
data = super().serialize_state()
data.update(self._unilabos_state) # Container自身的信息云端物料将保存这一data本地也通过这里的data进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data

View File

@@ -1,344 +1,152 @@
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 import Resource, ResourceStack, ItemizedResource
from pylabrobot.resources.carrier import create_homogeneous_resources
from unilabos.resources.battery.electrode_sheet import (
PositiveCan, PositiveElectrode,
NegativeCan, NegativeElectrode,
SpringWasher, FlatWasher,
AluminumFoil,
Battery
)
from pylabrobot.resources import ResourceHolder
from pylabrobot.resources.utils import create_ordered_items_2d
from unilabos.resources.itemized_carrier import ItemizedCarrier
class Magazine(ResourceStack):
"""子弹夹洞位类"""
def __init__(
self,
name: str,
direction: str = 'z',
resources: Optional[List[Resource]] = None,
max_sheets: int = 100,
**kwargs
):
"""初始化子弹夹洞位
Args:
name: 洞位名称
direction: 堆叠方向
resources: 资源列表
max_sheets: 最大极片数量
"""
super().__init__(
name=name,
direction=direction,
resources=resources,
)
self.max_sheets = max_sheets
@property
def size_x(self) -> float:
return self.get_size_x()
@property
def size_y(self) -> float:
return self.get_size_y()
@property
def size_z(self) -> float:
return self.get_size_z()
def serialize(self) -> dict:
return {
**super().serialize(),
"size_x": self.size_x or 10.0,
"size_y": self.size_y or 10.0,
"size_z": self.size_z or 10.0,
"max_sheets": self.max_sheets,
}
class MagazineHolder(ItemizedResource):
"""子弹夹类 - 有多个洞位,每个洞位放多个极片"""
def __init__(
self,
name: str,
size_x: float,
size_y: float,
size_z: float,
ordered_items: Optional[Dict[str, Magazine]] = None,
ordering: Optional[OrderedDict[str, str]] = None,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
max_sheets_per_hole: int = 100,
cross_section_type: str = "circle",
category: str = "magazine_holder",
model: Optional[str] = None,
):
"""初始化子弹夹
Args:
name: 子弹夹名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 高度 (mm)
hole_diameter: 洞直径 (mm)
hole_depth: 洞深度 (mm)
max_sheets_per_hole: 每个洞位最大极片数量
category: 类别
model: 型号
"""
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
ordered_items=ordered_items,
ordering=ordering,
category=category,
model=model,
)
# 保存洞位的直径和深度
self.hole_diameter = hole_diameter
self.hole_depth = hole_depth
self.max_sheets_per_hole = max_sheets_per_hole
self.cross_section_type = cross_section_type
def serialize(self) -> dict:
return {
**super().serialize(),
"hole_diameter": self.hole_diameter,
"hole_depth": self.hole_depth,
"max_sheets_per_hole": self.max_sheets_per_hole,
"cross_section_type": self.cross_section_type,
}
def magazine_factory(
name: str,
size_x: float,
size_y: float,
size_z: float,
locations: List[Coordinate],
klasses: Optional[List[Callable[[str], str]]] = None,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
max_sheets_per_hole: int = 100,
category: str = "magazine_holder",
model: Optional[str] = None,
) -> 'MagazineHolder':
"""工厂函数:创建子弹夹
def MagazineHolder_4_Cathode(name: str) -> ItemizedCarrier:
"""正极&铝箔弹夹 - 4个洞位 (2x2布局)
Args:
name: 弹夹名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 高度 (mm)
locations: 洞位坐标列表
klasses: 每个洞位中极片的类列表
hole_diameter: 洞直径 (mm)
hole_depth: 洞深度 (mm)
max_sheets_per_hole: 每个洞位最大极片数量
category: 类别
model: 型号
name: 弹夹名称
Returns:
ItemizedCarrier: 包含4个槽位的弹夹架
"""
for loc in locations:
loc.x -= hole_diameter / 2
loc.y -= hole_diameter / 2
# 创建洞位
_sites = create_homogeneous_resources(
klass=Magazine,
locations=locations,
resource_size_x=hole_diameter,
resource_size_y=hole_diameter,
name_prefix=name,
max_sheets=max_sheets_per_hole,
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=2,
num_items_y=2,
dx=10.0,
dy=10.0,
dz=0.0,
item_dx=50.0,
item_dy=30.0,
size_x=40.0,
size_y=25.0,
size_z=40.0,
)
# 生成编号键
keys = [f"A{i+1}" for i in range(len(locations))]
sites = dict(zip(keys, _sites.values()))
return ItemizedCarrier(
name=name,
size_x=120.0,
size_y=80.0,
size_z=50.0,
num_items_x=2,
num_items_y=2,
sites=sites,
category="magazine_holder",
)
def MagazineHolder_6_Cathode(name: str) -> ItemizedCarrier:
"""正极壳&平垫片弹夹 - 6个洞位 (2x3布局)
holder = MagazineHolder(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
ordered_items=sites,
hole_diameter=hole_diameter,
hole_depth=hole_depth,
max_sheets_per_hole=max_sheets_per_hole,
category=category,
model=model,
Args:
name: 弹夹名称
Returns:
ItemizedCarrier: 包含6个槽位的弹夹架
"""
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=3,
num_items_y=2,
dx=10.0,
dy=10.0,
dz=0.0,
item_dx=40.0,
item_dy=30.0,
size_x=35.0,
size_y=25.0,
size_z=40.0,
)
if klasses is not None:
for i, klass in enumerate(klasses):
hole_key = keys[i]
hole = holder.children[i]
for j in reversed(range(max_sheets_per_hole)):
item_name = f"{hole_key}_sheet{j+1}"
item = klass(name=item_name)
hole.assign_child_resource(item)
return holder
def MagazineHolder_6_Cathode(
name: str,
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=[FlatWasher, PositiveCan, PositiveCan, FlatWasher, PositiveCan, PositiveCan],
hole_diameter=hole_diameter,
hole_depth=hole_depth,
max_sheets_per_hole=max_sheets_per_hole,
category="magazine_holder",
model="MagazineHolder_6_Cathode",
)
def MagazineHolder_6_Anode(
name: str,
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(
return ItemizedCarrier(
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,
size_x=150.0,
size_y=80.0,
size_z=50.0,
num_items_x=3,
num_items_y=2,
sites=sites,
category="magazine_holder",
model="MagazineHolder_4_Cathode",
)
def MagazineHolder_6_Anode(name: str) -> ItemizedCarrier:
"""负极壳&弹垫片弹夹 - 6个洞位 (2x3布局)
Args:
name: 弹夹名称
Returns:
ItemizedCarrier: 包含6个槽位的弹夹架
"""
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=3,
num_items_y=2,
dx=10.0,
dy=10.0,
dz=0.0,
item_dx=40.0,
item_dy=30.0,
size_x=35.0,
size_y=25.0,
size_z=40.0,
)
return ItemizedCarrier(
name=name,
size_x=150.0,
size_y=80.0,
size_z=50.0,
num_items_x=3,
num_items_y=2,
sites=sites,
category="magazine_holder",
)
def MagazineHolder_6_Battery(name: str) -> ItemizedCarrier:
"""成品弹夹 - 6个洞位 (3x2布局)
Args:
name: 弹夹名称
Returns:
ItemizedCarrier: 包含6个槽位的弹夹架
"""
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=3,
num_items_y=2,
dx=10.0,
dy=10.0,
dz=0.0,
item_dx=33.0,
item_dy=40.0,
size_x=30.0,
size_y=35.0,
size_z=40.0,
)
return ItemizedCarrier(
name=name,
size_x=120.0,
size_y=100.0,
size_z=50.0,
num_items_x=3,
num_items_y=2,
sites=sites,
category="magazine_holder",
)

View File

@@ -1,79 +1,13 @@
from unilabos.resources.warehouse import WareHouse, warehouse_factory
from unilabos.resources.warehouse import WareHouse, YB_warehouse_factory
# ================ 反应站相关堆栈 ================
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
"""创建BioYond 4x4x1仓库 (左侧堆栈: A01D04)
使用行优先排序,前端展示为:
A01 | A02 | A03 | A04
B01 | B02 | B03 | B04
C01 | C02 | C03 | C04
D01 | D02 | D03 | D04
"""
return warehouse_factory(
"""创建BioYond 4x1x4仓库"""
return YB_warehouse_factory(
name=name,
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_x=1,
num_items_y=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层
num_items_z=4,
dx=10.0,
dy=10.0,
dz=10.0,
@@ -83,74 +17,10 @@ def bioyond_warehouse_reagent_storage(name: str) -> 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:
"""创建BioYond 4x2x1仓库"""
return warehouse_factory(
"""创建BioYond 4x1x2仓库"""
return YB_warehouse_factory(
name=name,
num_items_x=1,
num_items_y=4,
@@ -164,26 +34,42 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
category="warehouse",
removed_positions=None
)
# 定义benyond的堆栈
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
"""创建BioYond 1x2x2仓库"""
return warehouse_factory(
"""创建BioYond 4x1x4仓库"""
return YB_warehouse_factory(
name=name,
num_items_x=1,
num_items_x=2,
num_items_y=2,
num_items_z=2,
num_items_z=1,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="warehouse",
category="YB_warehouse",
)
def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
"""创建BioYond 2x2x1仓库自动堆栈"""
return YB_warehouse_factory(
name=name,
num_items_x=2,
num_items_y=2,
num_items_z=1,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="YB_warehouse",
)
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
"""创建BioYond 10x1x1仓库"""
return warehouse_factory(
"""创建BioYond 4x1x4仓库"""
return YB_warehouse_factory(
name=name,
num_items_x=10,
num_items_y=1,
@@ -196,10 +82,9 @@ def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
"""创建BioYond 1x3x3仓库"""
return warehouse_factory(
"""创建BioYond 4x1x4仓库"""
return YB_warehouse_factory(
name=name,
num_items_x=1,
num_items_y=3,
@@ -212,10 +97,9 @@ def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
"""创建BioYond 2x1x3仓库"""
return warehouse_factory(
"""创建BioYond 4x1x4仓库"""
return YB_warehouse_factory(
name=name,
num_items_x=2,
num_items_y=1,
@@ -230,8 +114,8 @@ def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
)
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
"""创建BioYond 3x3x1仓库"""
return warehouse_factory(
"""创建BioYond 4x1x4仓库"""
return YB_warehouse_factory(
name=name,
num_items_x=3,
num_items_y=3,
@@ -244,10 +128,9 @@ def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
"""已弃用:创建BioYond 5x1x1仓库"""
return warehouse_factory(
"""创建BioYond 4x1x4仓库"""
return YB_warehouse_factory(
name=name,
num_items_x=5,
num_items_y=1,
@@ -260,10 +143,9 @@ def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
"""已弃用:创建BioYond 3x3x1仓库"""
return warehouse_factory(
"""创建BioYond 4x1x4仓库"""
return YB_warehouse_factory(
name=name,
num_items_x=3,
num_items_y=3,
@@ -279,7 +161,7 @@ def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
"""创建BioYond开关盖加液模块台面"""
return warehouse_factory(
return YB_warehouse_factory(
name=name,
num_items_x=2,
num_items_y=5,
@@ -294,18 +176,34 @@ def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
removed_positions=None
)
def bioyond_warehouse_1x8x4(name: str) -> WareHouse:
"""创建BioYond 8x4x1反应站堆栈A01D08"""
return warehouse_factory(
def bioyond_warehouse_3x5x1(name: str) -> WareHouse:
"""创建BioYond 3x5x1仓库手动堆栈"""
return YB_warehouse_factory(
name=name,
num_items_x=8, # 8列01-08
num_items_y=4, # 4行A-D
num_items_z=1, # 1层
num_items_x=3,
num_items_y=5,
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,
item_dx=137.0,
item_dy=96.0,
item_dz=120.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",
)

View File

@@ -1,7 +1,7 @@
from os import name
from pylabrobot.resources import Deck, Coordinate, Rotation
from unilabos.resources.bioyond.YB_warehouses import (
from unilabos.resources.bioyond.warehouses import (
bioyond_warehouse_1x4x4,
bioyond_warehouse_1x4x4_right, # 新增:右侧仓库 (A05D08)
bioyond_warehouse_1x4x2,

View File

@@ -404,7 +404,6 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
return result_future.result
"""还没有改过的部分"""
def _setup_hardware_proxy(
self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method

View File

@@ -1,34 +0,0 @@
{
"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": []
}