mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 05:15:10 +00:00
feat: migrate to pymodbus 3.11.4 and update bioyond configs
PyModbus 3.x Migration: - Copied modbus.py and client.py from dev branch for compatibility - Rewrote FLOAT32 decoding using struct module in coin_cell_assembly.py - Fixed STRING decoding for QR codes (battery and electrolyte barcodes) - Tested successfully on hardware with correct data decoding Bioyond Studio Updates: - Updated bioyond_studio config.py - Modified bioyond_cell_workstation.py - Enhanced warehouse.py and decks.py - Added README_WAREHOUSE.md documentation Parameter Enhancements: - Enhanced coin_cell_workstation.yaml parameter descriptions - Added matrix position ranges and indexing rules Breaking changes: - Requires pymodbus >= 3.9.0 - Removed deprecated BinaryPayloadDecoder/BinaryPayloadBuilder - Updated to use client.convert_from/to_registers() methods
This commit is contained in:
@@ -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__':
|
||||
|
||||
@@ -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')
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -161,6 +161,95 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
logger.warning(f"任务未知状态 ({status}) (orderCode={order_code})")
|
||||
return {"status": f"unknown_{status}", "report": report}
|
||||
|
||||
def get_material_info(self, material_id: str) -> Dict[str, Any]:
|
||||
"""查询物料详细信息(物料详情接口)
|
||||
|
||||
Args:
|
||||
material_id: 物料 ID (GUID)
|
||||
|
||||
Returns:
|
||||
物料详情,包含 name, typeName, locations 等
|
||||
"""
|
||||
result = self._post_lims("/api/lims/storage/material-info", material_id)
|
||||
return result.get("data", {})
|
||||
|
||||
def _process_order_reagents(self, report: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""处理订单完成报文中的试剂数据,计算质量比
|
||||
|
||||
Args:
|
||||
report: 订单完成推送的 report 数据
|
||||
|
||||
Returns:
|
||||
{
|
||||
"real_mass_ratio": {"试剂A": 0.6, "试剂B": 0.4},
|
||||
"target_mass_ratio": {"试剂A": 0.6, "试剂B": 0.4},
|
||||
"reagent_details": [...] # 详细数据
|
||||
}
|
||||
"""
|
||||
used_materials = report.get("usedMaterials", [])
|
||||
|
||||
# 1. 筛选试剂(typemode="2",注意是小写且是字符串)
|
||||
reagents = [m for m in used_materials if str(m.get("typemode")) == "2"]
|
||||
|
||||
if not reagents:
|
||||
logger.warning("订单完成报文中没有试剂(typeMode=2)")
|
||||
return {
|
||||
"real_mass_ratio": {},
|
||||
"target_mass_ratio": {},
|
||||
"reagent_details": []
|
||||
}
|
||||
|
||||
# 2. 查询试剂名称
|
||||
reagent_data = []
|
||||
for reagent in reagents:
|
||||
material_id = reagent.get("materialId")
|
||||
if not material_id:
|
||||
continue
|
||||
|
||||
try:
|
||||
info = self.get_material_info(material_id)
|
||||
name = info.get("name", f"Unknown_{material_id[:8]}")
|
||||
real_qty = float(reagent.get("realQuantity", 0.0))
|
||||
used_qty = float(reagent.get("usedQuantity", 0.0))
|
||||
|
||||
reagent_data.append({
|
||||
"name": name,
|
||||
"material_id": material_id,
|
||||
"real_quantity": real_qty,
|
||||
"used_quantity": used_qty
|
||||
})
|
||||
logger.info(f"试剂: {name}, 目标={used_qty}g, 实际={real_qty}g")
|
||||
except Exception as e:
|
||||
logger.error(f"查询物料信息失败: {material_id}, {e}")
|
||||
continue
|
||||
|
||||
if not reagent_data:
|
||||
return {
|
||||
"real_mass_ratio": {},
|
||||
"target_mass_ratio": {},
|
||||
"reagent_details": []
|
||||
}
|
||||
|
||||
# 3. 计算质量比
|
||||
def calculate_mass_ratio(items: List[Dict], key: str) -> Dict[str, float]:
|
||||
total = sum(item[key] for item in items)
|
||||
if total == 0:
|
||||
logger.warning(f"总质量为0,无法计算{key}质量比")
|
||||
return {item["name"]: 0.0 for item in items}
|
||||
return {item["name"]: round(item[key] / total, 4) for item in items}
|
||||
|
||||
real_mass_ratio = calculate_mass_ratio(reagent_data, "real_quantity")
|
||||
target_mass_ratio = calculate_mass_ratio(reagent_data, "used_quantity")
|
||||
|
||||
logger.info(f"真实质量比: {real_mass_ratio}")
|
||||
logger.info(f"目标质量比: {target_mass_ratio}")
|
||||
|
||||
return {
|
||||
"real_mass_ratio": real_mass_ratio,
|
||||
"target_mass_ratio": target_mass_ratio,
|
||||
"reagent_details": reagent_data
|
||||
}
|
||||
|
||||
|
||||
# -------------------- 基础HTTP封装 --------------------
|
||||
def _url(self, path: str) -> str:
|
||||
@@ -643,6 +732,21 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
# 提取报文数据
|
||||
if result.get("status") == "success":
|
||||
report = result.get("report", {})
|
||||
|
||||
# [新增] 处理试剂数据,计算质量比
|
||||
try:
|
||||
mass_ratios = self._process_order_reagents(report)
|
||||
report["mass_ratios"] = mass_ratios # 添加到报文中
|
||||
logger.info(f"已计算订单 {order_code} 的试剂质量比")
|
||||
except Exception as e:
|
||||
logger.error(f"计算试剂质量比失败: {e}")
|
||||
report["mass_ratios"] = {
|
||||
"real_mass_ratio": {},
|
||||
"target_mass_ratio": {},
|
||||
"reagent_details": [],
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
all_reports.append(report)
|
||||
print(f"[create_orders] ✓ 订单 {order_code} 完成")
|
||||
else:
|
||||
@@ -672,6 +776,252 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
|
||||
return final_result
|
||||
|
||||
def create_orders_v2(self, xlsx_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
从 Excel 解析并创建实验(2.14)- V2版本
|
||||
约定:
|
||||
- batchId = Excel 文件名(不含扩展名)
|
||||
- 物料列:所有以 "(g)" 结尾(不再读取"总质量(g)"列)
|
||||
- totalMass 自动计算为所有物料质量之和
|
||||
- createTime 缺失或为空时自动填充为当前日期(YYYY/M/D)
|
||||
"""
|
||||
default_path = Path("D:\\UniLab\\Uni-Lab-OS\\unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025122301.xlsx")
|
||||
path = Path(xlsx_path) if xlsx_path else default_path
|
||||
print(f"[create_orders_v2] 使用 Excel 路径: {path}")
|
||||
if path != default_path:
|
||||
print("[create_orders_v2] 来源: 调用方传入自定义路径")
|
||||
else:
|
||||
print("[create_orders_v2] 来源: 使用默认模板路径")
|
||||
|
||||
if not path.exists():
|
||||
print(f"[create_orders_v2] ⚠️ Excel 文件不存在: {path}")
|
||||
raise FileNotFoundError(f"未找到 Excel 文件:{path}")
|
||||
|
||||
try:
|
||||
df = pd.read_excel(path, sheet_name=0, engine="openpyxl")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"读取 Excel 失败:{e}")
|
||||
print(f"[create_orders_v2] Excel 读取成功,行数: {len(df)}, 列: {list(df.columns)}")
|
||||
|
||||
# 列名容错:返回可选列名,找不到则返回 None
|
||||
def _pick(col_names: List[str]) -> Optional[str]:
|
||||
for c in col_names:
|
||||
if c in df.columns:
|
||||
return c
|
||||
return None
|
||||
|
||||
col_order_name = _pick(["配方ID", "orderName", "订单编号"])
|
||||
col_create_time = _pick(["创建日期", "createTime"])
|
||||
col_bottle_type = _pick(["配液瓶类型", "bottleType"])
|
||||
col_mix_time = _pick(["混匀时间(s)", "mixTime"])
|
||||
col_load = _pick(["扣电组装分液体积", "loadSheddingInfo"])
|
||||
col_pouch = _pick(["软包组装分液体积", "pouchCellInfo"])
|
||||
col_cond = _pick(["电导测试分液体积", "conductivityInfo"])
|
||||
col_cond_cnt = _pick(["电导测试分液瓶数", "conductivityBottleCount"])
|
||||
print("[create_orders_v2] 列匹配结果:", {
|
||||
"order_name": col_order_name,
|
||||
"create_time": col_create_time,
|
||||
"bottle_type": col_bottle_type,
|
||||
"mix_time": col_mix_time,
|
||||
"load": col_load,
|
||||
"pouch": col_pouch,
|
||||
"conductivity": col_cond,
|
||||
"conductivity_bottle_count": col_cond_cnt,
|
||||
})
|
||||
|
||||
# 物料列:所有以 (g) 结尾
|
||||
material_cols = [c for c in df.columns if isinstance(c, str) and c.endswith("(g)")]
|
||||
print(f"[create_orders_v2] 识别到的物料列: {material_cols}")
|
||||
if not material_cols:
|
||||
raise KeyError("未发现任何以“(g)”结尾的物料列,请检查表头。")
|
||||
|
||||
batch_id = path.stem
|
||||
|
||||
def _to_ymd_slash(v) -> str:
|
||||
# 统一为 "YYYY/M/D";为空或解析失败则用当前日期
|
||||
if v is None or (isinstance(v, float) and pd.isna(v)) or str(v).strip() == "":
|
||||
ts = datetime.now()
|
||||
else:
|
||||
try:
|
||||
ts = pd.to_datetime(v)
|
||||
except Exception:
|
||||
ts = datetime.now()
|
||||
return f"{ts.year}/{ts.month}/{ts.day}"
|
||||
|
||||
def _as_int(val, default=0) -> int:
|
||||
try:
|
||||
if pd.isna(val):
|
||||
return default
|
||||
return int(val)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def _as_float(val, default=0.0) -> float:
|
||||
try:
|
||||
if pd.isna(val):
|
||||
return default
|
||||
return float(val)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def _as_str(val, default="") -> str:
|
||||
if val is None or (isinstance(val, float) and pd.isna(val)):
|
||||
return default
|
||||
s = str(val).strip()
|
||||
return s if s else default
|
||||
|
||||
orders: List[Dict[str, Any]] = []
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
mats: List[Dict[str, Any]] = []
|
||||
total_mass = 0.0
|
||||
|
||||
for mcol in material_cols:
|
||||
val = row.get(mcol, None)
|
||||
if val is None or (isinstance(val, float) and pd.isna(val)):
|
||||
continue
|
||||
try:
|
||||
mass = float(val)
|
||||
except Exception:
|
||||
continue
|
||||
if mass > 0:
|
||||
mats.append({"name": mcol.replace("(g)", ""), "mass": mass})
|
||||
total_mass += mass
|
||||
else:
|
||||
if mass < 0:
|
||||
print(f"[create_orders_v2] 第 {idx+1} 行物料 {mcol} 数值为负数: {mass}")
|
||||
|
||||
order_data = {
|
||||
"batchId": batch_id,
|
||||
"orderName": _as_str(row[col_order_name], default=f"{batch_id}_order_{idx+1}") if col_order_name else f"{batch_id}_order_{idx+1}",
|
||||
"createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None),
|
||||
"bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶",
|
||||
"mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0,
|
||||
"loadSheddingInfo": _as_float(row[col_load]) if col_load else 0.0,
|
||||
"pouchCellInfo": _as_float(row[col_pouch]) if col_pouch else 0,
|
||||
"conductivityInfo": _as_float(row[col_cond]) if col_cond else 0,
|
||||
"conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0,
|
||||
"materialInfos": mats,
|
||||
"totalMass": round(total_mass, 4) # 自动汇总
|
||||
}
|
||||
print(f"[create_orders_v2] 第 {idx+1} 行解析结果: orderName={order_data['orderName']}, "
|
||||
f"loadShedding={order_data['loadSheddingInfo']}, pouchCell={order_data['pouchCellInfo']}, "
|
||||
f"conductivity={order_data['conductivityInfo']}, totalMass={order_data['totalMass']}, "
|
||||
f"material_count={len(mats)}")
|
||||
|
||||
if order_data["totalMass"] <= 0:
|
||||
print(f"[create_orders_v2] ⚠️ 第 {idx+1} 行总质量 <= 0,可能导致 LIMS 校验失败")
|
||||
if not mats:
|
||||
print(f"[create_orders_v2] ⚠️ 第 {idx+1} 行未找到有效物料")
|
||||
|
||||
orders.append(order_data)
|
||||
print("================================================")
|
||||
print("orders:", orders)
|
||||
|
||||
print(f"[create_orders_v2] 即将提交订单数量: {len(orders)}")
|
||||
response = self._post_lims("/api/lims/order/orders", orders)
|
||||
print(f"[create_orders_v2] 接口返回: {response}")
|
||||
|
||||
# 提取所有返回的 orderCode
|
||||
data_list = response.get("data", [])
|
||||
if not data_list:
|
||||
logger.error("创建订单未返回有效数据!")
|
||||
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_v2] 等待 {len(order_codes)} 个订单完成: {order_codes}")
|
||||
|
||||
# ========== 步骤1: 等待所有订单完成并收集报文(不计算质量比)==========
|
||||
all_reports = []
|
||||
for idx, order_code in enumerate(order_codes, 1):
|
||||
print(f"[create_orders_v2] 正在等待第 {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_v2] ✓ 订单 {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_v2] 所有订单已完成,共收集 {len(all_reports)} 个报文")
|
||||
|
||||
# ========== 步骤2: 统一计算所有订单的质量比 ==========
|
||||
print(f"[create_orders_v2] 开始统一计算 {len(all_reports)} 个订单的质量比...")
|
||||
all_mass_ratios = [] # 存储所有订单的质量比,与reports顺序一致
|
||||
|
||||
for idx, report in enumerate(all_reports, 1):
|
||||
order_code = report.get("orderCode", "N/A")
|
||||
print(f"[create_orders_v2] 计算第 {idx}/{len(all_reports)} 个订单 {order_code} 的质量比...")
|
||||
|
||||
# 只为成功完成的订单计算质量比
|
||||
if "error" not in report:
|
||||
try:
|
||||
mass_ratios = self._process_order_reagents(report)
|
||||
# 精简输出,只保留核心质量比信息
|
||||
all_mass_ratios.append({
|
||||
"orderCode": order_code,
|
||||
"orderName": report.get("orderName", "N/A"),
|
||||
"real_mass_ratio": mass_ratios.get("real_mass_ratio", {}),
|
||||
"target_mass_ratio": mass_ratios.get("target_mass_ratio", {})
|
||||
})
|
||||
logger.info(f"✓ 已计算订单 {order_code} 的试剂质量比")
|
||||
except Exception as e:
|
||||
logger.error(f"计算订单 {order_code} 质量比失败: {e}")
|
||||
all_mass_ratios.append({
|
||||
"orderCode": order_code,
|
||||
"orderName": report.get("orderName", "N/A"),
|
||||
"real_mass_ratio": {},
|
||||
"target_mass_ratio": {},
|
||||
"error": str(e)
|
||||
})
|
||||
else:
|
||||
# 失败的订单不计算质量比
|
||||
all_mass_ratios.append({
|
||||
"orderCode": order_code,
|
||||
"orderName": report.get("orderName", "N/A"),
|
||||
"real_mass_ratio": {},
|
||||
"target_mass_ratio": {},
|
||||
"error": "订单未成功完成"
|
||||
})
|
||||
|
||||
print(f"[create_orders_v2] 质量比计算完成")
|
||||
print("实验记录本========================create_orders_v2========================")
|
||||
|
||||
# 返回所有订单的完成报文
|
||||
final_result = {
|
||||
"status": "all_completed",
|
||||
"total_orders": len(order_codes),
|
||||
"bottle_count": len(order_codes), # 明确标注瓶数,用于下游check
|
||||
"reports": all_reports, # 原始订单报文(不含质量比)
|
||||
"mass_ratios": all_mass_ratios, # 所有质量比统一放在这里
|
||||
"original_response": response
|
||||
}
|
||||
|
||||
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]:
|
||||
return self._post_lims("/api/lims/scheduler/start")
|
||||
@@ -683,7 +1033,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
请求体只包含 apiKey 和 requestTime
|
||||
"""
|
||||
return self._post_lims("/api/lims/scheduler/stop")
|
||||
# 2.9 继续调度
|
||||
|
||||
# 2.9 继续调度
|
||||
def scheduler_continue(self) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -867,6 +1217,48 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
result = self.wait_for_order_finish(order_code)
|
||||
return result
|
||||
|
||||
def transfer_3_to_2(self,
|
||||
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]:
|
||||
"""
|
||||
2.34 3-2 物料转运接口
|
||||
|
||||
新建从 3 -> 2 的搬运任务
|
||||
|
||||
Args:
|
||||
source_wh_id: 来源仓库 Id (默认为3号仓库)
|
||||
source_x: 来源位置 X 坐标
|
||||
source_y: 来源位置 Y 坐标
|
||||
source_z: 来源位置 Z 坐标
|
||||
|
||||
Returns:
|
||||
dict: 包含任务 orderId 和 orderCode 的响应
|
||||
"""
|
||||
payload: Dict[str, Any] = {
|
||||
"sourcePosX": source_x,
|
||||
"sourcePosY": source_y,
|
||||
"sourcePosZ": source_z
|
||||
}
|
||||
if source_wh_id:
|
||||
payload["sourceWHID"] = source_wh_id
|
||||
|
||||
logger.info(f"[transfer_3_to_2] 开始转运: 仓库={source_wh_id}, 位置=({source_x}, {source_y}, {source_z})")
|
||||
response = self._post_lims("/api/lims/order/transfer-task3To2", payload)
|
||||
|
||||
# 等待任务报送成功
|
||||
order_code = response.get("data", {}).get("orderCode")
|
||||
if not order_code:
|
||||
logger.error("[transfer_3_to_2] 转运任务未返回有效 orderCode!")
|
||||
return response
|
||||
|
||||
logger.info(f"[transfer_3_to_2] 转运任务已创建: {order_code}")
|
||||
# 等待完成报送
|
||||
result = self.wait_for_order_finish(order_code)
|
||||
logger.info(f"[transfer_3_to_2] 转运任务完成: {order_code}")
|
||||
return result
|
||||
|
||||
# 3.35 1→2 物料转运
|
||||
def transfer_1_to_2(self) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -874,14 +1266,28 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
URL: /api/lims/order/transfer-task1To2
|
||||
只需要 apiKey 和 requestTime
|
||||
"""
|
||||
logger.info("[transfer_1_to_2] 开始 1→2 物料转运")
|
||||
response = self._post_lims("/api/lims/order/transfer-task1To2")
|
||||
# 等待任务报送成功
|
||||
order_code = response.get("data", {}).get("orderCode")
|
||||
logger.info(f"[transfer_1_to_2] API Response: {response}")
|
||||
|
||||
# 等待任务报送成功 - 处理不同的响应格式
|
||||
order_code = None
|
||||
data_field = response.get("data")
|
||||
|
||||
if isinstance(data_field, dict):
|
||||
order_code = data_field.get("orderCode")
|
||||
elif isinstance(data_field, str):
|
||||
# 某些接口可能直接返回 orderCode 字符串
|
||||
order_code = data_field
|
||||
|
||||
if not order_code:
|
||||
logger.error("上料任务未返回有效 orderCode!")
|
||||
logger.error(f"[transfer_1_to_2] 转运任务未返回有效 orderCode!响应: {response}")
|
||||
return response
|
||||
|
||||
logger.info(f"[transfer_1_to_2] 转运任务已创建: {order_code}")
|
||||
# 等待完成报送
|
||||
result = self.wait_for_order_finish(order_code)
|
||||
logger.info(f"[transfer_1_to_2] 转运任务完成: {order_code}")
|
||||
return result
|
||||
|
||||
# 2.5 批量查询实验报告(post过滤关键字查询)
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
import pubchempy as pcp
|
||||
|
||||
cas = "21324-40-3" # 示例
|
||||
comps = pcp.get_compounds(cas, namespace="name")
|
||||
if not comps:
|
||||
raise ValueError("No hit")
|
||||
|
||||
c = comps[0]
|
||||
|
||||
print("Canonical SMILES:", c.canonical_smiles)
|
||||
print("Isomeric SMILES:", c.isomeric_smiles)
|
||||
print("MW:", c.molecular_weight)
|
||||
@@ -133,6 +133,46 @@ WAREHOUSE_MAPPING = {
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||
}
|
||||
},
|
||||
"手动传递窗左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
"4号手套箱内部堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
|
||||
@@ -20,8 +20,7 @@ from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
||||
from unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials import CoincellDeck
|
||||
from unilabos.resources.graphio import convert_resources_to_type
|
||||
from unilabos.utils.log import logger
|
||||
from pymodbus.payload import BinaryPayloadDecoder
|
||||
from pymodbus.constants import Endian
|
||||
import struct
|
||||
|
||||
|
||||
def _decode_float32_correct(registers):
|
||||
@@ -43,13 +42,19 @@ def _decode_float32_correct(registers):
|
||||
return 0.0
|
||||
|
||||
try:
|
||||
# 使用正确的字节序配置
|
||||
decoder = BinaryPayloadDecoder.fromRegisters(
|
||||
registers,
|
||||
byteorder=Endian.Big, # 字节序始终为Big
|
||||
wordorder=Endian.Little # 字序为Little (根据PLC配置)
|
||||
)
|
||||
return decoder.decode_32bit_float()
|
||||
# Word Order: Little - 交换两个寄存器的顺序
|
||||
# Byte Order: Big - 每个寄存器内部使用大端字节序
|
||||
low_word = registers[0]
|
||||
high_word = registers[1]
|
||||
|
||||
# 将两个16位寄存器组合成一个32位值 (Little Word Order)
|
||||
# 使用大端字节序 ('>') 将每个寄存器转换为字节
|
||||
byte_string = struct.pack('>HH', high_word, low_word)
|
||||
|
||||
# 将字节字符串解码为浮点数 (大端)
|
||||
value = struct.unpack('>f', byte_string)[0]
|
||||
|
||||
return value
|
||||
except Exception as e:
|
||||
logger.error(f"解码FLOAT32失败: {e}, registers: {registers}")
|
||||
return 0.0
|
||||
@@ -632,23 +637,22 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
# 读取 STRING 类型数据
|
||||
code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE)
|
||||
|
||||
# 处理 bytes 或 string 类型
|
||||
if isinstance(code_little, bytes):
|
||||
code_str = code_little.decode('utf-8', errors='ignore')
|
||||
elif isinstance(code_little, str):
|
||||
code_str = code_little
|
||||
else:
|
||||
logger.warning(f"电池二维码返回的类型不支持: {type(code_little)}")
|
||||
# PyModbus 3.x 返回 string 类型
|
||||
if not isinstance(code_little, str):
|
||||
logger.warning(f"电池二维码返回的类型不支持: {type(code_little)}, 值: {repr(code_little)}")
|
||||
return "N/A"
|
||||
|
||||
# 取前8个字符
|
||||
raw_code = code_str[:8]
|
||||
# 从字符串末尾查找连续的字母数字字符(反转字符串)
|
||||
import re
|
||||
reversed_str = code_little[::-1]
|
||||
match = re.match(r'^([A-Za-z0-9]+)', reversed_str)
|
||||
|
||||
# LITTLE字节序需要每2个字符交换位置
|
||||
clean_code = ''.join([raw_code[i+1] + raw_code[i] for i in range(0, len(raw_code), 2)])
|
||||
if not match:
|
||||
logger.warning(f"未找到有效的电池二维码数据,原始字符串: {repr(code_little)}")
|
||||
return "N/A"
|
||||
|
||||
# 去除空字符和空格
|
||||
decoded = clean_code.replace('\x00', '').replace('\r', '').replace('\n', '').strip()
|
||||
# 提取匹配到的字符串(已经是正确顺序)
|
||||
decoded = match.group(1)[:8] # 只取前8个字符
|
||||
|
||||
return decoded if decoded else "N/A"
|
||||
except Exception as e:
|
||||
@@ -663,23 +667,22 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
# 读取 STRING 类型数据
|
||||
code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE)
|
||||
|
||||
# 处理 bytes 或 string 类型
|
||||
if isinstance(code_little, bytes):
|
||||
code_str = code_little.decode('utf-8', errors='ignore')
|
||||
elif isinstance(code_little, str):
|
||||
code_str = code_little
|
||||
else:
|
||||
logger.warning(f"电解液二维码返回的类型不支持: {type(code_little)}")
|
||||
# PyModbus 3.x 返回 string 类型
|
||||
if not isinstance(code_little, str):
|
||||
logger.warning(f"电解液二维码返回的类型不支持: {type(code_little)}, 值: {repr(code_little)}")
|
||||
return "N/A"
|
||||
|
||||
# 取前8个字符
|
||||
raw_code = code_str[:8]
|
||||
# 从字符串末尾查找连续的字母数字字符(反转字符串)
|
||||
import re
|
||||
reversed_str = code_little[::-1]
|
||||
match = re.match(r'^([A-Za-z0-9]+)', reversed_str)
|
||||
|
||||
# LITTLE字节序需要每2个字符交换位置
|
||||
clean_code = ''.join([raw_code[i+1] + raw_code[i] for i in range(0, len(raw_code), 2)])
|
||||
if not match:
|
||||
logger.warning(f"未找到有效的电解液二维码数据,原始字符串: {repr(code_little)}")
|
||||
return "N/A"
|
||||
|
||||
# 去除空字符和空格
|
||||
decoded = clean_code.replace('\x00', '').replace('\r', '').replace('\n', '').strip()
|
||||
# 提取匹配到的字符串(已经是正确顺序)
|
||||
decoded = match.group(1)[:8] # 只取前8个字符
|
||||
|
||||
return decoded if decoded else "N/A"
|
||||
except Exception as e:
|
||||
@@ -940,6 +943,111 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
time.sleep(1)
|
||||
#自动按钮置False
|
||||
|
||||
def func_sendbottle_allpack_multi(
|
||||
self,
|
||||
elec_num,
|
||||
elec_use_num,
|
||||
elec_vol: int = 50,
|
||||
# 电解液双滴模式参数
|
||||
dual_drop_mode: bool = False,
|
||||
dual_drop_first_volume: int = 25,
|
||||
dual_drop_suction_timing: bool = False,
|
||||
dual_drop_start_timing: bool = False,
|
||||
assembly_type: int = 7,
|
||||
assembly_pressure: int = 4200,
|
||||
# 来自原 qiming_coin_cell_code 的参数
|
||||
fujipian_panshu: int = 0,
|
||||
fujipian_juzhendianwei: int = 0,
|
||||
gemopanshu: int = 0,
|
||||
gemo_juzhendianwei: int = 0,
|
||||
qiangtou_juzhendianwei: int = 0,
|
||||
lvbodian: bool = True,
|
||||
battery_pressure_mode: bool = True,
|
||||
battery_clean_ignore: bool = False,
|
||||
file_path: str = "/Users/sml/work"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
发送瓶数+简化组装函数(适用于第二批次及后续批次)
|
||||
|
||||
合并了发送瓶数和简化组装流程,用于连续批次生产。
|
||||
适用场景:设备已完成初始化和启动,仍在自动模式下运行。
|
||||
|
||||
Args:
|
||||
elec_num: 电解液瓶数
|
||||
elec_use_num: 每瓶电解液组装的电池数
|
||||
elec_vol: 电解液吸液量 (μL)
|
||||
dual_drop_mode: 电解液添加模式 (False=单次滴液, True=二次滴液)
|
||||
dual_drop_first_volume: 二次滴液第一次排液体积 (μL)
|
||||
dual_drop_suction_timing: 二次滴液吸液时机 (False=正常吸液, True=先吸液)
|
||||
dual_drop_start_timing: 二次滴液开始滴液时机 (False=正极片前, True=正极片后)
|
||||
assembly_type: 组装类型 (7=不用铝箔垫, 8=使用铝箔垫)
|
||||
assembly_pressure: 电池压制力 (N)
|
||||
fujipian_panshu: 负极片盘数
|
||||
fujipian_juzhendianwei: 负极片矩阵点位
|
||||
gemopanshu: 隔膜盘数
|
||||
gemo_juzhendianwei: 隔膜矩阵点位
|
||||
qiangtou_juzhendianwei: 枪头盒矩阵点位
|
||||
lvbodian: 是否使用铝箔垫片
|
||||
battery_pressure_mode: 是否启用压力模式
|
||||
battery_clean_ignore: 是否忽略电池清洁
|
||||
file_path: 实验记录保存路径
|
||||
|
||||
Returns:
|
||||
dict: 包含组装结果的字典
|
||||
|
||||
注意:
|
||||
- 第一次启动需先调用 func_pack_device_init_auto_start_combined()
|
||||
- 后续批次直接调用此函数即可
|
||||
"""
|
||||
logger.info("=" * 60)
|
||||
logger.info("开始发送瓶数+简化组装流程...")
|
||||
logger.info(f"电解液瓶数: {elec_num}, 每瓶电池数: {elec_use_num}")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# 步骤1: 发送电解液瓶数(触发物料搬运)
|
||||
logger.info("步骤1/2: 发送电解液瓶数,触发物料搬运...")
|
||||
try:
|
||||
self.func_pack_send_bottle_num(elec_num)
|
||||
logger.info("✓ 瓶数发送完成,物料搬运中...")
|
||||
except Exception as e:
|
||||
logger.error(f"发送瓶数失败: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"发送瓶数失败: {e}",
|
||||
"total_batteries": 0,
|
||||
"batteries": []
|
||||
}
|
||||
|
||||
# 步骤2: 执行简化组装流程
|
||||
logger.info("步骤2/2: 开始简化组装流程...")
|
||||
result = self.func_allpack_cmd_simp(
|
||||
elec_num=elec_num,
|
||||
elec_use_num=elec_use_num,
|
||||
elec_vol=elec_vol,
|
||||
dual_drop_mode=dual_drop_mode,
|
||||
dual_drop_first_volume=dual_drop_first_volume,
|
||||
dual_drop_suction_timing=dual_drop_suction_timing,
|
||||
dual_drop_start_timing=dual_drop_start_timing,
|
||||
assembly_type=assembly_type,
|
||||
assembly_pressure=assembly_pressure,
|
||||
fujipian_panshu=fujipian_panshu,
|
||||
fujipian_juzhendianwei=fujipian_juzhendianwei,
|
||||
gemopanshu=gemopanshu,
|
||||
gemo_juzhendianwei=gemo_juzhendianwei,
|
||||
qiangtou_juzhendianwei=qiangtou_juzhendianwei,
|
||||
lvbodian=lvbodian,
|
||||
battery_pressure_mode=battery_pressure_mode,
|
||||
battery_clean_ignore=battery_clean_ignore,
|
||||
file_path=file_path
|
||||
)
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("发送瓶数+简化组装流程完成")
|
||||
logger.info(f"总组装电池数: {result.get('total_batteries', 0)}")
|
||||
logger.info("=" * 60)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# 下发参数
|
||||
#def func_pack_send_msg_cmd(self, elec_num: int, elec_use_num: int, elec_vol: float, assembly_type: int, assembly_pressure: int) -> bool:
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
|
||||
COIL_SYS_START_CMD,BOOL,,,,coil,9010,
|
||||
COIL_SYS_STOP_CMD,BOOL,,,,coil,9020,
|
||||
COIL_SYS_RESET_CMD,BOOL,,,,coil,9030,
|
||||
COIL_SYS_HAND_CMD,BOOL,,,,coil,9040,
|
||||
COIL_SYS_AUTO_CMD,BOOL,,,,coil,9050,
|
||||
COIL_SYS_INIT_CMD,BOOL,,,,coil,9060,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,9700,
|
||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,9710,unilab_rec_msg_succ_cmd
|
||||
COIL_SYS_START_STATUS,BOOL,,,,coil,9210,
|
||||
COIL_SYS_STOP_STATUS,BOOL,,,,coil,9220,
|
||||
COIL_SYS_RESET_STATUS,BOOL,,,,coil,9230,
|
||||
COIL_SYS_HAND_STATUS,BOOL,,,,coil,9240,
|
||||
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,9250,
|
||||
COIL_SYS_INIT_STATUS,BOOL,,,,coil,9260,
|
||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,9500,
|
||||
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,9510,request_send_msg_status
|
||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,17000,
|
||||
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,17002,unilab_send_msg_electrolyte_num
|
||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,17004,unilab_send_msg_electrolyte_vol
|
||||
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,17006,unilab_send_msg_assembly_type
|
||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,17008,unilab_send_msg_assembly_pressure
|
||||
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,16000,data_assembly_coin_cell_num
|
||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,16002,data_open_circuit_voltage
|
||||
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,16004,
|
||||
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,16006,
|
||||
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,16008,
|
||||
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,16010,data_pole_weight
|
||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,16012,data_assembly_time
|
||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,16014,data_assembly_pressure
|
||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,16016,data_electrolyte_volume
|
||||
REG_DATA_COIN_NUM,INT16,,,,hold_register,16018,data_coin_num
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,16020,data_electrolyte_code()
|
||||
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,16030,data_coin_cell_code()
|
||||
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,18004,data_stack_vision_code()
|
||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,16050,data_glove_box_pressure
|
||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,16052,data_glove_box_water_content
|
||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,16054,data_glove_box_o2_content
|
||||
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9720,
|
||||
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9520,
|
||||
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,17496,
|
||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,16000,
|
||||
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,9730,
|
||||
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,9530,
|
||||
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,16018,ASSEMBLY_TYPE7or8
|
||||
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,9340,
|
||||
REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,17440,
|
||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,17450,
|
||||
REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,17480,
|
||||
REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,17443,
|
||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,17453,
|
||||
REG_MSG_PRESS_MODE,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,9360,电池压制模式
|
||||
,,,,,,,
|
||||
,BOOL,,视觉对位(false:使用,true:忽略),,coil,9300,视觉对位
|
||||
,BOOL,,复检(false:使用,true:忽略),,coil,9310,视觉复检
|
||||
,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,9320,手套箱左仓
|
||||
,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,9420,手套箱右仓
|
||||
,BOOL,,真空检知(false:使用,true:忽略),,coil,9350,真空检知
|
||||
,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,9370,滴液模式
|
||||
,BOOL,,正极片称重(false:使用,true:忽略),,coil,9380,正极片称重
|
||||
,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,9390,正负极反装
|
||||
,BOOL,,压制清洁(false:使用,true:忽略),,coil,9400,压制清洁
|
||||
,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,9410,负极片摆盘方式
|
||||
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁(false:使用,true:忽略),,coil,9460,
|
||||
|
@@ -1,159 +0,0 @@
|
||||
Name,DataType,Comment,DeviceType,Address,,
|
||||
COIL_SYS_START_CMD,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8010,,
|
||||
COIL_SYS_STOP_CMD,BOOL,<EFBFBD>豸ֹͣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8020,,
|
||||
COIL_SYS_RESET_CMD,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8030,,
|
||||
COIL_SYS_HAND_CMD,BOOL,<EFBFBD>豸<EFBFBD>ֶ<EFBFBD>ģʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8040,,
|
||||
COIL_SYS_AUTO_CMD,BOOL,<EFBFBD>豸<EFBFBD>Զ<EFBFBD>ģʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8050,,
|
||||
COIL_SYS_INIT_CMD,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8060,,
|
||||
COIL_SYS_STOP_STATUS,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD>ͣ<EFBFBD><EFBFBD>,coil,8220,,
|
||||
,,,,,,
|
||||
,BOOL,UNILAB<EFBFBD><EFBFBD><EFBFBD>͵<EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8720,,
|
||||
,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܵ<EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD>,coil,8520,,
|
||||
REG_MSG_ELECTROLYTE_NUM,WORD,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һʹ<EFBFBD><EFBFBD>ƿ<EFBFBD><EFBFBD>,hold_register,496,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD>̾<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,440,,
|
||||
,WORD,<EFBFBD><EFBFBD>Ĥ<EFBFBD>̾<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,450,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,460,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>վ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,430,,
|
||||
,WORD,g_<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,470,,
|
||||
,WORD,<EFBFBD><EFBFBD>Һǹͷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,480,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD>ø<EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,443,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD>ø<EFBFBD>Ĥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,453,,
|
||||
,,,,,,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,UNILAB<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䷽<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8700,,
|
||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䷽,coil,8500,,
|
||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,<EFBFBD><EFBFBD>ƿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һʹ<EFBFBD>ô<EFBFBD><EFBFBD><EFBFBD>,hold_register,11000,,
|
||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>,hold_register,11004,,
|
||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,11008,,
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD>ά<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>к<EFBFBD>,hold_register,10020,,
|
||||
,BOOL,<EFBFBD>Ӿ<EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8300,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD>죨false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8310,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD>֣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8320,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>Ҳ֣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8420,,
|
||||
,BOOL,<EFBFBD><EFBFBD>е<EFBFBD>ְ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8330,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8340,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD>֪<EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8350,,
|
||||
,BOOL,ѹ<EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>false:ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>True:<3A><><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>,coil,8360,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>false:<3A><><EFBFBD>ε<EFBFBD>Һ<EFBFBD><D2BA>true:<3A><><EFBFBD>ε<EFBFBD>Һ<EFBFBD><D2BA>,coil,8370,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8380,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD>false:<3A><>װ<EFBFBD><D7B0>true:<3A><>װ<EFBFBD><D7B0>,coil,8390,,
|
||||
,BOOL,ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ࣨfalse:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8400,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̰<EFBFBD><EFBFBD>̷<EFBFBD>ʽ<EFBFBD><EFBFBD>false:ˮƽ<CBAE><C6BD><EFBFBD>̣<EFBFBD>true:<3A>ѵ<EFBFBD><D1B5><EFBFBD><EFBFBD>̣<EFBFBD>,coil,8410,,
|
||||
COIL_SYS_UNILAB_INTERACT ,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Unilab<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8450,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ࣨfalse:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,colil,8460,,
|
||||
,,,,,,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,UNILAB<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䷽<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8510,,
|
||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,UNILAB<EFBFBD><EFBFBD><EFBFBD>ܲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8710,,
|
||||
REG_DATA_POLE_WEIGHT,FLOAT32,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10010,,
|
||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD>ŵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װʱ<EFBFBD><EFBFBD>,hold_register,10012,,
|
||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10014,,
|
||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD>,hold_register,10016,,
|
||||
REG_DATA_ASSEMBLY_TYPE,INT16,<EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD>ѵ<EFBFBD><EFBFBD><EFBFBD>ʽ(7/8),hold_register,10018,,
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD>ά<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>к<EFBFBD>,hold_register,10020,,
|
||||
REG_DATA_COIN_CELL_CODE,STRING,<EFBFBD><EFBFBD><EFBFBD>ض<EFBFBD>ά<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>к<EFBFBD>,hold_register,10030,,
|
||||
REG_DATA_STACK_VISON_CODE,STRING,<EFBFBD><EFBFBD><EFBFBD>϶ѵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼƬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10040,,
|
||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,<EFBFBD><EFBFBD>ǰ<EFBFBD>缫Һ<EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10000,,
|
||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10002,,
|
||||
,INT,<EFBFBD><EFBFBD>е<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD>ϼĴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>1-<2D><><EFBFBD><EFBFBD><EFBFBD>ǡ<EFBFBD>2-<2D><><EFBFBD>桢3-<2D><><EFBFBD><EFBFBD>Ƭ<EFBFBD><C6AC>4-<2D><>Ĥ<EFBFBD><C4A4>5-<2D><><EFBFBD><EFBFBD>Ƭ<EFBFBD><C6AC>6-ƽ<>桢7-<2D><><EFBFBD>桢8-<2D><><EFBFBD><EFBFBD><EFBFBD>ǣ<EFBFBD>,hold_register,10060,,
|
||||
,,,,,,
|
||||
,INT,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10062,PLC<EFBFBD><EFBFBD>ַ,1223-<2D><><EFBFBD><EFBFBD>
|
||||
,INT,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD>Ĥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10064,,
|
||||
,INT,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ״̬<EFBFBD>루R<EFBFBD><EFBFBD>,hold_register,10066,,
|
||||
,REAL,<EFBFBD><EFBFBD>·<EFBFBD><EFBFBD>ѹOK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10068,,
|
||||
,REAL,<EFBFBD><EFBFBD>·<EFBFBD><EFBFBD>ѹOK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10070,,
|
||||
,INT,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10072,,
|
||||
,INT,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10074,,
|
||||
,REAL,10mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,520,HMI<EFBFBD><EFBFBD>ַ,
|
||||
,REAL,12mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,522,,
|
||||
,REAL,16mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,524,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,526,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,528,,
|
||||
,REAL,ƽ<EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,530,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,532,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,534,,
|
||||
,REAL,<EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,536,,
|
||||
,REAL,<EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>NG<EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,538,,
|
||||
,,,,,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>10mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,540,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>12mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,542,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>16mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,544,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,546,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǻ<EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,548,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,550,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD>ø<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǻ<EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,552,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,554,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD>ó<EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD>غ<EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,556,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD>,hold_register,10050,,
|
||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ˮ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10052,,
|
||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10054,,
|
||||
,,,,,,
|
||||
,BOOL,<EFBFBD>쳣100-ϵͳ<CFB5>쳣,coil,1000,,
|
||||
,BOOL,<EFBFBD>쳣101-<2D><>ͣ,coil,1010,,
|
||||
,BOOL,<EFBFBD>쳣111-<2D><><EFBFBD><EFBFBD><EFBFBD>伱ͣ,coil,1110,,
|
||||
,BOOL,<EFBFBD>쳣112-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڹ<EFBFBD>դ<EFBFBD>ڵ<EFBFBD>,coil,1120,,
|
||||
,BOOL,<EFBFBD>쳣160-<2D><>Һǹͷȱ<CDB7><C8B1>,coil,1600,,
|
||||
,BOOL,<EFBFBD>쳣161-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD><C8B1>,coil,1610,,
|
||||
,BOOL,<EFBFBD>쳣162-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD><C8B1>,coil,1620,,
|
||||
,BOOL,<EFBFBD>쳣163-<2D><><EFBFBD><EFBFBD>Ƭȱ<C6AC><C8B1>,coil,1630,,
|
||||
,BOOL,<EFBFBD>쳣164-<2D><>Ĥȱ<C4A4><C8B1>,coil,1640,,
|
||||
,BOOL,<EFBFBD>쳣165-<2D><><EFBFBD><EFBFBD>Ƭȱ<C6AC><C8B1>,coil,1650,,
|
||||
,BOOL,<EFBFBD>쳣166-ƽ<><C6BD>ȱ<EFBFBD><C8B1>,coil,1660,,
|
||||
,BOOL,<EFBFBD>쳣167-<2D><><EFBFBD><EFBFBD>ȱ<EFBFBD><C8B1>,coil,1670,,
|
||||
,BOOL,<EFBFBD>쳣168-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD><C8B1>,coil,1680,,
|
||||
,BOOL,<EFBFBD>쳣169-<2D><>Ʒ<EFBFBD><C6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,1690,,
|
||||
,BOOL,<EFBFBD>쳣201-<2D>ŷ<EFBFBD><C5B7><EFBFBD>01<30>쳣,coil,2010,,
|
||||
,BOOL,<EFBFBD>쳣202-<2D>ŷ<EFBFBD><C5B7><EFBFBD>02<30>쳣,coil,2020,,
|
||||
,BOOL,<EFBFBD>쳣203-<2D>ŷ<EFBFBD><C5B7><EFBFBD>03<30>쳣,coil,2030,,
|
||||
,BOOL,<EFBFBD>쳣204-<2D>ŷ<EFBFBD><C5B7><EFBFBD>04<30>쳣,coil,2040,,
|
||||
,BOOL,<EFBFBD>쳣205-<2D>ŷ<EFBFBD><C5B7><EFBFBD>05<30>쳣,coil,2050,,
|
||||
,BOOL,<EFBFBD>쳣206-<2D>ŷ<EFBFBD><C5B7><EFBFBD>06<30>쳣,coil,2060,,
|
||||
,BOOL,<EFBFBD>쳣207-<2D>ŷ<EFBFBD><C5B7><EFBFBD>07<30>쳣,coil,2070,,
|
||||
,BOOL,<EFBFBD>쳣208-<2D>ŷ<EFBFBD><C5B7><EFBFBD>08<30>쳣,coil,2080,,
|
||||
,BOOL,<EFBFBD>쳣209-<2D>ŷ<EFBFBD><C5B7><EFBFBD>09<30>쳣,coil,2090,,
|
||||
,BOOL,<EFBFBD>쳣210-<2D>ŷ<EFBFBD><C5B7><EFBFBD>10<31>쳣,coil,2100,,
|
||||
,BOOL,<EFBFBD>쳣211-<2D>ŷ<EFBFBD><C5B7><EFBFBD>11<31>쳣,coil,2110,,
|
||||
,BOOL,<EFBFBD>쳣212-<2D>ŷ<EFBFBD><C5B7><EFBFBD>12<31>쳣,coil,2120,,
|
||||
,BOOL,<EFBFBD>쳣213-<2D>ŷ<EFBFBD><C5B7><EFBFBD>13<31>쳣,coil,2130,,
|
||||
,BOOL,<EFBFBD>쳣214-<2D>ŷ<EFBFBD><C5B7><EFBFBD>14<31>쳣,coil,2140,,
|
||||
,BOOL,<EFBFBD>쳣250-<2D><><EFBFBD><EFBFBD>Ԫ<EFBFBD><D4AA><EFBFBD>쳣,coil,2500,,
|
||||
,BOOL,<EFBFBD>쳣251-<2D><>ҺǹͨѶ<CDA8>쳣,coil,2510,,
|
||||
,BOOL,<EFBFBD>쳣252-<2D><>Һǹ<D2BA><C7B9><EFBFBD><EFBFBD>,coil,2520,,
|
||||
,BOOL,<EFBFBD>쳣256-<2D><>צ<EFBFBD>쳣,coil,2560,,
|
||||
,BOOL,<EFBFBD>쳣262-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ֪<CEB4><D6AA>λ<EFBFBD><CEBB><EFBFBD><EFBFBD>,coil,2620,,
|
||||
,BOOL,<EFBFBD>쳣263-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>X<EFBFBD><58>Y<EFBFBD><59>Z<EFBFBD><5A><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,2630,,
|
||||
,BOOL,<EFBFBD>쳣264-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӿ<EFBFBD><D3BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,2640,,
|
||||
,BOOL,<EFBFBD>쳣265-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>1#<23><><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>ʧ<EFBFBD><CAA7>,coil,2650,,
|
||||
,BOOL,<EFBFBD>쳣266-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2#<23><><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>ʧ<EFBFBD><CAA7>,coil,2660,,
|
||||
,BOOL,<EFBFBD>쳣267-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>3#<23><><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>ʧ<EFBFBD><CAA7>,coil,2670,,
|
||||
,BOOL,<EFBFBD>쳣268-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4#<23><><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>ʧ<EFBFBD><CAA7>,coil,2680,,
|
||||
,BOOL,<EFBFBD>쳣269-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>,coil,2690,,
|
||||
,BOOL,<EFBFBD>쳣280-RB<52><42>ײ<EFBFBD>쳣,coil,2800,,
|
||||
,BOOL,<EFBFBD>쳣290-<2D>Ӿ<EFBFBD>ϵͳͨѶ<CDA8>쳣,coil,2900,,
|
||||
,BOOL,<EFBFBD>쳣291-<2D>Ӿ<EFBFBD><D3BE><EFBFBD>λNG<4E>쳣,coil,2910,,
|
||||
,BOOL,<EFBFBD>쳣292-ɨ<><C9A8>ǹͨѶ<CDA8>쳣,coil,2920,,
|
||||
,BOOL,<EFBFBD>쳣310-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3100,,
|
||||
,BOOL,<EFBFBD>쳣311-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3110,,
|
||||
,BOOL,<EFBFBD>쳣312-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3120,,
|
||||
,BOOL,<EFBFBD>쳣313-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3130,,
|
||||
,BOOL,<EFBFBD>쳣340-<2D><>·<EFBFBD><C2B7>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3400,,
|
||||
,BOOL,<EFBFBD>쳣342-<2D><>·<EFBFBD><C2B7>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3420,,
|
||||
,BOOL,<EFBFBD>쳣344-<2D><>·<EFBFBD><C2B7>ѹ<EFBFBD><D1B9>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3440,,
|
||||
,BOOL,<EFBFBD>쳣350-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3500,,
|
||||
,BOOL,<EFBFBD>쳣352-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3520,,
|
||||
,BOOL,<EFBFBD>쳣354-<2D><>ϴ<EFBFBD><EFBFBD><DEB3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3540,,
|
||||
,BOOL,<EFBFBD>쳣356-<2D><>ϴ<EFBFBD><EFBFBD><DEB3><EFBFBD>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3560,,
|
||||
,BOOL,<EFBFBD>쳣360-<2D><><EFBFBD><EFBFBD>Һƿ<D2BA><C6BF>λ<EFBFBD><CEBB><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3600,,
|
||||
,BOOL,<EFBFBD>쳣362-<2D><>Һǹͷ<C7B9>ж<EFBFBD>λ<EFBFBD><CEBB><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3620,,
|
||||
COIL ALARM_364_SERVO_DRIVE_ERROR,BOOL,<EFBFBD>쳣364-<2D>Լ<EFBFBD>ƿ<EFBFBD><C6BF>צ<EFBFBD><D7A6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3640,,
|
||||
COIL ALARM_367_SERVO_DRIVER_ERROR,BOOL,<EFBFBD>쳣366-<2D>Լ<EFBFBD>ƿ<EFBFBD><C6BF>צ<EFBFBD><D7A6><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3660,,
|
||||
COIL ALARM_370_SERVO_MODULE_ERROR,BOOL,<EFBFBD>쳣370-ѹ<><D1B9>ģ<EFBFBD>鴵<EFBFBD><E9B4B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3700,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
COIL + <20><><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>/<2F><><EFBFBD><EFBFBD> + <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д+<2B>»<EFBFBD><C2BB>߷ָ<DFB7><D6B8><EFBFBD>--<2D><><EFBFBD><EFBFBD>boolֵ,,,,,,
|
||||
REG + <20><><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>/<2F><><EFBFBD><EFBFBD> + <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д+<2B>»<EFBFBD><C2BB>߷ָ<DFB7><D6B8><EFBFBD>--<2D><><EFBFBD>ԼĴ<D4BC><C4B4><EFBFBD>,,,,,,
|
||||
|
@@ -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,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,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,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,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
|
||||
|
@@ -0,0 +1,6 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||
20260110_153356,0.0,23.889999389648438,17.0,3609,40,7,deaoR,
|
||||
20260110_153640,3.430999994277954,23.719999313354492,162.0,3560,40,7,deaoR,
|
||||
20260110_162905,0.0,23.920000076293945,772.0,3625,30,7,LG600001,YS103130
|
||||
20260110_163526,3.430999994277954,24.010000228881836,234.0,3690,30,7,LG600001,YS102964
|
||||
20260110_164530,0.0,23.589998245239258,219.0,3690,30,7,LG600001,YS102857
|
||||
|
@@ -1,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()
|
||||
@@ -178,6 +178,38 @@ bioyond_cell:
|
||||
title: create_orders参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_orders_v2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles:
|
||||
output:
|
||||
- data_key: total_orders
|
||||
data_source: executor
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: sink
|
||||
label: 配液瓶数
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 从Excel解析并创建实验(V2版本)
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
type: string
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_orders_v2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_sample:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -594,6 +626,47 @@ bioyond_cell:
|
||||
title: transfer_1_to_2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_3_to_2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
source_x: 1
|
||||
source_y: 1
|
||||
source_z: 1
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 3-2 物料转运,从3号位置转运到2号位置
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
source_wh_id:
|
||||
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
description: 来源仓库ID
|
||||
type: string
|
||||
source_x:
|
||||
default: 1
|
||||
description: 来源位置X坐标
|
||||
type: integer
|
||||
source_y:
|
||||
default: 1
|
||||
description: 来源位置Y坐标
|
||||
type: integer
|
||||
source_z:
|
||||
default: 1
|
||||
description: 来源位置Z坐标
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_3_to_2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_3_to_2_to_1:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
|
||||
@@ -194,7 +194,7 @@ coincellassemblyworkstation_device:
|
||||
type: string
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
description: 负极片矩阵点位
|
||||
description: 负极片矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
@@ -202,7 +202,7 @@ coincellassemblyworkstation_device:
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位
|
||||
description: 隔膜矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
@@ -214,7 +214,7 @@ coincellassemblyworkstation_device:
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位
|
||||
description: 枪头盒矩阵点位。盘位置从1开始计数,有效范围:1-32, 64-96 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
required:
|
||||
- elec_num
|
||||
@@ -493,6 +493,125 @@ coincellassemblyworkstation_device:
|
||||
title: func_read_data_and_output参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_sendbottle_allpack_multi:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
assembly_pressure: 4200
|
||||
assembly_type: 7
|
||||
battery_clean_ignore: false
|
||||
battery_pressure_mode: true
|
||||
dual_drop_first_volume: 25
|
||||
dual_drop_mode: false
|
||||
dual_drop_start_timing: false
|
||||
dual_drop_suction_timing: false
|
||||
elec_num: null
|
||||
elec_use_num: null
|
||||
elec_vol: 50
|
||||
file_path: /Users/sml/work
|
||||
fujipian_juzhendianwei: 0
|
||||
fujipian_panshu: 0
|
||||
gemo_juzhendianwei: 0
|
||||
gemopanshu: 0
|
||||
lvbodian: true
|
||||
qiangtou_juzhendianwei: 0
|
||||
handles:
|
||||
input:
|
||||
- data_key: elec_num
|
||||
data_source: workflow
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: source
|
||||
label: 配液瓶数
|
||||
required: true
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 发送瓶数+简化组装函数(适用于第二批次及后续批次),合并了发送瓶数和简化组装流程
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
default: 4200
|
||||
description: 电池压制力(N)
|
||||
type: integer
|
||||
assembly_type:
|
||||
default: 7
|
||||
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
|
||||
type: integer
|
||||
battery_clean_ignore:
|
||||
default: false
|
||||
description: 是否忽略电池清洁步骤
|
||||
type: boolean
|
||||
battery_pressure_mode:
|
||||
default: true
|
||||
description: 是否启用压力模式
|
||||
type: boolean
|
||||
dual_drop_first_volume:
|
||||
default: 25
|
||||
description: 二次滴液第一次排液体积(μL)
|
||||
type: integer
|
||||
dual_drop_mode:
|
||||
default: false
|
||||
description: 电解液添加模式(false=单次滴液, true=二次滴液)
|
||||
type: boolean
|
||||
dual_drop_start_timing:
|
||||
default: false
|
||||
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
|
||||
type: boolean
|
||||
dual_drop_suction_timing:
|
||||
default: false
|
||||
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
|
||||
type: boolean
|
||||
elec_num:
|
||||
description: 电解液瓶数,如果在workflow中已通过handles连接上游(create_orders的bottle_count输出),则此参数会自动从上游获取,无需手动填写;如果单独使用此函数(没有上游连接),则必须手动填写电解液瓶数
|
||||
type: string
|
||||
elec_use_num:
|
||||
description: 每瓶电解液组装电池数
|
||||
type: string
|
||||
elec_vol:
|
||||
default: 50
|
||||
description: 电解液吸液量(μL)
|
||||
type: integer
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
description: 实验记录保存路径
|
||||
type: string
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
description: 负极片矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
description: 负极片盘数
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
description: 隔膜盘数
|
||||
type: integer
|
||||
lvbodian:
|
||||
default: true
|
||||
description: 是否使用铝箔垫片
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位。盘位置从1开始计数,有效范围:1-32, 64-96 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
required:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_sendbottle_allpack_multi参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_stop_read_data:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
548
unilabos/resources/bioyond/README_WAREHOUSE.md
Normal file
548
unilabos/resources/bioyond/README_WAREHOUSE.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# Bioyond 仓库系统开发指南
|
||||
|
||||
本文档详细说明 Bioyond 仓库(Warehouse)系统的架构、配置和使用方法,帮助开发者快速理解和维护仓库相关代码。
|
||||
|
||||
## 📚 目录
|
||||
|
||||
- [系统架构](#系统架构)
|
||||
- [核心概念](#核心概念)
|
||||
- [三层映射关系](#三层映射关系)
|
||||
- [warehouse_factory 详解](#warehouse_factory-详解)
|
||||
- [创建新仓库](#创建新仓库)
|
||||
- [常见问题](#常见问题)
|
||||
- [调试技巧](#调试技巧)
|
||||
|
||||
---
|
||||
|
||||
## 系统架构
|
||||
|
||||
Bioyond 仓库系统采用**三层架构**,实现从前端显示到后端 API 的完整映射:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 前端显示层 (YB_warehouses.py) │
|
||||
│ - warehouse_factory 自动生成库位网格 │
|
||||
│ - 生成库位名称:A01, B02, C03... │
|
||||
│ - 存储在 WareHouse.sites 字典中 │
|
||||
└────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Deck 布局层 (decks.py) │
|
||||
│ - 定义仓库在 Deck 上的物理位置 │
|
||||
│ - 组织多个仓库形成完整布局 │
|
||||
└────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ UUID 映射层 (config.py) │
|
||||
│ - 将库位名称映射到 Bioyond 系统 UUID │
|
||||
│ - 用于 API 调用时的物料入库操作 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 仓库(Warehouse)
|
||||
|
||||
仓库是一个**三维网格**,用于存放物料。由以下参数定义:
|
||||
|
||||
- **num_items_x**: 列数(X 轴)
|
||||
- **num_items_y**: 行数(Y 轴)
|
||||
- **num_items_z**: 层数(Z 轴)
|
||||
|
||||
例如:`5行×3列×1层` = 5×3×1 = 15个库位
|
||||
|
||||
### 库位(Site)
|
||||
|
||||
库位是仓库中的单个存储位置,由**字母行+数字列**命名:
|
||||
|
||||
- **字母行**:A, B, C, D, E, F...(对应 Y 轴)
|
||||
- **数字列**:01, 02, 03, 04...(对应 X 轴或 Z 轴)
|
||||
|
||||
示例:`A01`, `B02`, `C03`
|
||||
|
||||
### 布局模式(Layout)
|
||||
|
||||
控制库位的排序和 Y 坐标计算:
|
||||
|
||||
| 模式 | 说明 | 生成顺序 | Y 坐标计算 | 显示效果 |
|
||||
|------|------|----------|-----------|---------|
|
||||
| `col-major` | 列优先(默认) | A01, B01, C01, A02... | `dy + (num_y - row - 1) * item_dy` | A 可能在下 |
|
||||
| `row-major` | 行优先 | A01, A02, A03, B01... | `dy + row * item_dy` | **A 在上** ✓ |
|
||||
|
||||
**重要:** 使用 `row-major` 可以避免上下颠倒问题!
|
||||
|
||||
---
|
||||
|
||||
## 三层映射关系
|
||||
|
||||
### 示例:手动传递窗右(A01-E03)
|
||||
|
||||
#### 1️⃣ 前端显示层 - [`YB_warehouses.py`](YB_warehouses.py)
|
||||
|
||||
```python
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建 5行×3列×1层 仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行
|
||||
num_items_z=1, # 1层
|
||||
row_offset=row_offset,
|
||||
layout="row-major",
|
||||
)
|
||||
```
|
||||
|
||||
**自动生成的库位:** A01, A02, A03, B01, B02, B03, ..., E01, E02, E03
|
||||
|
||||
#### 2️⃣ Deck 布局层 - [`decks.py`](decks.py)
|
||||
|
||||
```python
|
||||
self.warehouses = {
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0),
|
||||
}
|
||||
self.warehouse_locations = {
|
||||
"手动传递窗右": Coordinate(4160.0, 877.0, 0.0),
|
||||
}
|
||||
```
|
||||
|
||||
**作用:**
|
||||
- 创建仓库实例
|
||||
- 设置在 Deck 上的物理坐标
|
||||
|
||||
#### 3️⃣ UUID 映射层 - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
# ... 其他库位
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**作用:**
|
||||
- 用户拖拽物料到"手动传递窗右"的"A01"位置时
|
||||
- 系统查找 `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]`
|
||||
- 获取 UUID `"3a19deae-2c7a-36f5-5e41-02c5b66feaea"`
|
||||
- 调用 Bioyond API 将物料入库到该 UUID 位置
|
||||
|
||||
---
|
||||
|
||||
## 实际配置案例
|
||||
|
||||
### 案例:手动传递窗左/右的完整配置
|
||||
|
||||
本案例展示如何为"手动传递窗右"和"手动传递窗左"建立完整的三层映射。
|
||||
|
||||
#### 背景需求
|
||||
- **手动传递窗右**: 需要 A01-E03(5行×3列=15个库位)
|
||||
- **手动传递窗左**: 需要 F01-J03(5行×3列=15个库位)
|
||||
- 这两个仓库共享同一个物理堆栈的 UUID("手动堆栈")
|
||||
|
||||
#### 实施步骤
|
||||
|
||||
**1️⃣ 修复前端布局** - [`YB_warehouses.py`](YB_warehouses.py)
|
||||
|
||||
```python
|
||||
# 创建新的 5×3×1 仓库函数(之前是错误的 1×3×3)
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建5行×3列×1层仓库,支持行偏移生成不同字母行"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行 ← 修正
|
||||
num_items_z=1, # 1层 ← 修正
|
||||
row_offset=row_offset, # ← 支持 F-J 行
|
||||
layout="row-major", # ← 避免上下颠倒
|
||||
)
|
||||
```
|
||||
|
||||
**2️⃣ 更新 Deck 配置** - [`decks.py`](decks.py)
|
||||
|
||||
```python
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_5x3x1, # 新增导入
|
||||
)
|
||||
|
||||
class BIOYOND_YB_Deck(Deck):
|
||||
def setup(self) -> None:
|
||||
self.warehouses = {
|
||||
# 修改前: bioyond_warehouse_1x3x3 (错误尺寸)
|
||||
# 修改后: bioyond_warehouse_5x3x1 (正确尺寸)
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
|
||||
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
|
||||
}
|
||||
```
|
||||
|
||||
**3️⃣ 添加 UUID 映射** - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
# 保持原有的"手动堆栈"配置不变(A01-J03共30个库位)
|
||||
"手动堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
# ... A02-E03 共15个
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||
# ... F02-J03 共15个
|
||||
}
|
||||
},
|
||||
|
||||
# [新增] 手动传递窗右 - 复用"手动堆栈"的 A01-E03 UUID
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea", # ← 与手动堆栈A01相同
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||
}
|
||||
},
|
||||
|
||||
# [新增] 手动传递窗左 - 复用"手动堆栈"的 F01-J03 UUID
|
||||
"手动传递窗左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a", # ← 与手动堆栈F01相同
|
||||
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 关键要点
|
||||
|
||||
1. **UUID 可以复用**: 三个仓库(手动堆栈、手动传递窗右、手动传递窗左)可以共享相同的物理库位 UUID
|
||||
2. **库位名称必须匹配**: 前端生成的库位名称(如 F01)必须与 config.py 中的键名完全一致
|
||||
3. **row_offset 的妙用**:
|
||||
- `row_offset=0` → 生成 A-E 行
|
||||
- `row_offset=5` → 生成 F-J 行(跳过前5个字母)
|
||||
|
||||
#### 验证结果
|
||||
|
||||
配置完成后,拖拽测试:
|
||||
|
||||
| 拖拽位置 | 前端库位 | 查找路径 | UUID | 结果 |
|
||||
|---------|---------|---------|------|------|
|
||||
| 手动传递窗右/A01 | A01 | `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 正确入库 |
|
||||
| 手动传递窗左/F01 | F01 | `WAREHOUSE_MAPPING["手动传递窗左"]["site_uuids"]["F01"]` | `3a19...c4a` | ✅ 正确入库 |
|
||||
| 手动堆栈/A01 | A01 | `WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 仍然正常 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## warehouse_factory 详解
|
||||
|
||||
### 函数签名
|
||||
|
||||
```python
|
||||
def warehouse_factory(
|
||||
name: str,
|
||||
num_items_x: int = 1, # 列数
|
||||
num_items_y: int = 4, # 行数
|
||||
num_items_z: int = 4, # 层数
|
||||
dx: float = 137.0, # X 起始偏移
|
||||
dy: float = 96.0, # Y 起始偏移
|
||||
dz: float = 120.0, # Z 起始偏移
|
||||
item_dx: float = 10.0, # X 间距
|
||||
item_dy: float = 10.0, # Y 间距
|
||||
item_dz: float = 10.0, # Z 间距
|
||||
col_offset: int = 0, # 列偏移(影响数字)
|
||||
row_offset: int = 0, # 行偏移(影响字母)
|
||||
layout: str = "col-major", # 布局模式
|
||||
) -> WareHouse:
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
#### 尺寸参数
|
||||
- **num_items_x, y, z**: 定义仓库的网格尺寸
|
||||
- **注意**: 当 `num_items_z > 1` 时,Z 轴会被映射为数字列
|
||||
|
||||
#### 位置参数
|
||||
- **dx, dy, dz**: 第一个库位的起始坐标
|
||||
- **item_dx, dy, dz**: 库位之间的间距
|
||||
|
||||
#### 偏移参数
|
||||
- **col_offset**: 列起始偏移,用于生成 A05-D08 等命名
|
||||
```python
|
||||
col_offset=4 # 生成 A05, A06, A07, A08
|
||||
```
|
||||
|
||||
- **row_offset**: 行起始偏移,用于生成 F01-J03 等命名
|
||||
```python
|
||||
row_offset=5 # 生成 F01, F02, F03(跳过 A-E)
|
||||
```
|
||||
|
||||
#### 布局参数
|
||||
- **layout**:
|
||||
- `"col-major"`: 列优先(默认),可能导致上下颠倒
|
||||
- `"row-major"`: 行优先,**推荐使用**,A 显示在上
|
||||
|
||||
### 库位生成逻辑
|
||||
|
||||
```python
|
||||
# row-major 模式(推荐)
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}"
|
||||
for j in range(num_y)
|
||||
for i in range(num_x)]
|
||||
|
||||
# 示例:num_y=2, num_x=3, row_offset=0, col_offset=0
|
||||
# 生成:A01, A02, A03, B01, B02, B03
|
||||
```
|
||||
|
||||
### Y 坐标计算
|
||||
|
||||
```python
|
||||
if layout == "row-major":
|
||||
# A 在上(Y 较小)
|
||||
y = dy + row * item_dy
|
||||
else:
|
||||
# A 在下(Y 较大)- 不推荐
|
||||
y = dy + (num_items_y - row - 1) * item_dy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 创建新仓库
|
||||
|
||||
### 步骤 1: 在 YB_warehouses.py 中创建函数
|
||||
|
||||
```python
|
||||
def bioyond_warehouse_3x4x1(name: str) -> WareHouse:
|
||||
"""创建 3行×4列×1层 仓库
|
||||
|
||||
布局:
|
||||
A01 | A02 | A03 | A04
|
||||
B01 | B02 | B03 | B04
|
||||
C01 | C02 | C03 | C04
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列
|
||||
num_items_y=3, # 3行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # ⭐ 推荐使用
|
||||
)
|
||||
```
|
||||
|
||||
### 步骤 2: 在 decks.py 中使用
|
||||
|
||||
```python
|
||||
# 1. 导入函数
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_3x4x1, # 新增
|
||||
)
|
||||
|
||||
# 2. 在 setup() 中添加
|
||||
self.warehouses = {
|
||||
"我的新仓库": bioyond_warehouse_3x4x1("我的新仓库"),
|
||||
}
|
||||
self.warehouse_locations = {
|
||||
"我的新仓库": Coordinate(100.0, 200.0, 0.0),
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 3: 在 config.py 中配置 UUID(可选)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
"我的新仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "从 Bioyond 系统获取的 UUID",
|
||||
"A02": "从 Bioyond 系统获取的 UUID",
|
||||
# ... 其他 11 个库位
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 如果不需要拖拽入库功能,可跳过此步骤。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么库位显示上下颠倒(C 在上,A 在下)?
|
||||
|
||||
**原因:** 使用了默认的 `col-major` 布局。
|
||||
|
||||
**解决:** 在 `warehouse_factory` 中添加 `layout="row-major"`
|
||||
|
||||
```python
|
||||
return warehouse_factory(
|
||||
...
|
||||
layout="row-major", # ← 添加这行
|
||||
)
|
||||
```
|
||||
|
||||
### Q2: 我需要 1×3×3 还是 3×3×1?
|
||||
|
||||
**判断方法:**
|
||||
- **1×3×3**: 1列×3行×3**层**(垂直堆叠,有高度)
|
||||
- **3×3×1**: 3行×3列×1**层**(平面网格)
|
||||
|
||||
**推荐:** 大多数情况使用 `X×Y×1`(平面网格)更直观。
|
||||
|
||||
### Q3: 如何生成 F01-J03 而非 A01-E03?
|
||||
|
||||
**方法:** 使用 `row_offset` 参数
|
||||
|
||||
```python
|
||||
bioyond_warehouse_5x3x1("仓库名", row_offset=5)
|
||||
# row_offset=5 跳过 A-E,从 F 开始
|
||||
```
|
||||
|
||||
### Q4: 拖拽物料后找不到 UUID 怎么办?
|
||||
|
||||
**检查清单:**
|
||||
1. `config.py` 中是否有该仓库的配置?
|
||||
2. 仓库名称是否完全匹配?
|
||||
3. 库位名称(如 A01)是否在 `site_uuids` 中?
|
||||
|
||||
**示例错误:**
|
||||
```python
|
||||
# decks.py
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1(...)
|
||||
|
||||
# config.py - ❌ 名称不匹配
|
||||
"手动传递窗": { ... } # 缺少"右"字
|
||||
```
|
||||
|
||||
### Q5: 库位重叠怎么办?
|
||||
|
||||
**原因:** 间距(`item_dx/dy/dz`)太小。
|
||||
|
||||
**解决:** 增大间距参数
|
||||
|
||||
```python
|
||||
item_dx=150.0, # 增大 X 间距
|
||||
item_dy=130.0, # 增大 Y 间距
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 查看生成的库位
|
||||
|
||||
```python
|
||||
warehouse = bioyond_warehouse_5x3x1("测试仓库")
|
||||
print(list(warehouse.sites.keys()))
|
||||
# 输出:['A01', 'A02', 'A03', 'B01', 'B02', ...]
|
||||
```
|
||||
|
||||
### 2. 检查库位坐标
|
||||
|
||||
```python
|
||||
for name, site in warehouse.sites.items():
|
||||
print(f"{name}: {site.location}")
|
||||
# 输出:
|
||||
# A01: Coordinate(x=10.0, y=10.0, z=120.0)
|
||||
# A02: Coordinate(x=147.0, y=10.0, z=120.0)
|
||||
# ...
|
||||
```
|
||||
|
||||
### 3. 验证 UUID 映射
|
||||
|
||||
```python
|
||||
from unilabos.devices.workstation.bioyond_studio.config import WAREHOUSE_MAPPING
|
||||
|
||||
warehouse_name = "手动传递窗右"
|
||||
location_code = "A01"
|
||||
|
||||
if warehouse_name in WAREHOUSE_MAPPING:
|
||||
uuid = WAREHOUSE_MAPPING[warehouse_name]["site_uuids"].get(location_code)
|
||||
print(f"{warehouse_name}/{location_code} → {uuid}")
|
||||
else:
|
||||
print(f"❌ 未找到仓库: {warehouse_name}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件关系图
|
||||
|
||||
```
|
||||
unilabos/
|
||||
├── resources/
|
||||
│ ├── warehouse.py # warehouse_factory 核心实现
|
||||
│ └── bioyond/
|
||||
│ ├── YB_warehouses.py # ⭐ 仓库函数定义
|
||||
│ ├── decks.py # ⭐ Deck 布局配置
|
||||
│ └── README_WAREHOUSE.md # 📖 本文档
|
||||
└── devices/
|
||||
└── workstation/
|
||||
└── bioyond_studio/
|
||||
├── config.py # ⭐ UUID 映射配置
|
||||
└── bioyond_cell/
|
||||
└── bioyond_cell_workstation.py # 业务逻辑
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本历史
|
||||
|
||||
- **v1.1** (2026-01-08): 补充实际配置案例
|
||||
- 添加"手动传递窗右"和"手动传递窗左"的完整配置示例
|
||||
- 展示 UUID 复用的实际应用
|
||||
- 说明三个仓库共享物理堆栈的配置方法
|
||||
|
||||
- **v1.0** (2026-01-07): 初始版本
|
||||
- 新增 `row_offset` 参数支持
|
||||
- 创建 `bioyond_warehouse_5x3x1` 和 `bioyond_warehouse_2x2x1`
|
||||
- 修复多个仓库的上下颠倒问题
|
||||
|
||||
---
|
||||
|
||||
## 相关资源
|
||||
|
||||
- [warehouse.py](../warehouse.py) - 核心工厂函数实现
|
||||
- [YB_warehouses.py](YB_warehouses.py) - 所有仓库定义
|
||||
- [decks.py](decks.py) - Deck 布局配置
|
||||
- [config.py](../../devices/workstation/bioyond_studio/config.py) - UUID 映射
|
||||
|
||||
---
|
||||
|
||||
**维护者:** Uni-Lab-OS 开发团队
|
||||
**最后更新:** 2026-01-07
|
||||
@@ -166,7 +166,14 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||
)
|
||||
|
||||
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
||||
"""创建BioYond 1x2x2仓库"""
|
||||
"""创建BioYond 1x2x2仓库(1列×2行×2层)- 旧版本,已弃用
|
||||
|
||||
布局(2层):
|
||||
层1: A01
|
||||
B01
|
||||
层2: A02
|
||||
B02
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
@@ -179,8 +186,32 @@ def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # 使用行优先避免上下颠倒
|
||||
)
|
||||
|
||||
def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 2x2x1仓库(2行×2列×1层)
|
||||
|
||||
布局:
|
||||
A01 | A02
|
||||
B01 | B02
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2, # 2列
|
||||
num_items_y=2, # 2行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # 使用行优先避免上下颠倒
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 10x1x1仓库"""
|
||||
return warehouse_factory(
|
||||
@@ -208,11 +239,61 @@ def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dy=120.0, # 增大Y方向间距以避免重叠
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建BioYond 5x3x1仓库(5行×3列×1层)
|
||||
|
||||
标准布局(row_offset=0):
|
||||
A01 | A02 | A03
|
||||
B01 | B02 | B03
|
||||
C01 | C02 | C03
|
||||
D01 | D02 | D03
|
||||
E01 | E02 | E03
|
||||
|
||||
带偏移布局(row_offset=5):
|
||||
F01 | F02 | F03
|
||||
G01 | G02 | G03
|
||||
H01 | H02 | H03
|
||||
I01 | I02 | I03
|
||||
J01 | J02 | J03
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
row_offset=row_offset, # 支持行偏移
|
||||
layout="row-major", # 使用行优先避免颠倒
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 3x3x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=3,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
|
||||
"""创建BioYond 2x1x3仓库"""
|
||||
return warehouse_factory(
|
||||
|
||||
@@ -8,7 +8,9 @@ from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_reagent_stack, # 新增:试剂堆栈 (A1-B4)
|
||||
bioyond_warehouse_liquid_and_lid_handling,
|
||||
bioyond_warehouse_1x2x2,
|
||||
bioyond_warehouse_2x2x1, # 新增:321和43窗口 (2行×2列)
|
||||
bioyond_warehouse_1x3x3,
|
||||
bioyond_warehouse_5x3x1, # 新增:手动传递窗仓库 (5行×3列)
|
||||
bioyond_warehouse_10x1x1,
|
||||
bioyond_warehouse_3x3x1,
|
||||
bioyond_warehouse_3x3x1_2,
|
||||
@@ -115,10 +117,10 @@ class BIOYOND_YB_Deck(Deck):
|
||||
def setup(self) -> None:
|
||||
# 添加仓库
|
||||
self.warehouses = {
|
||||
"321窗口": bioyond_warehouse_1x2x2("321窗口"),
|
||||
"43窗口": bioyond_warehouse_1x2x2("43窗口"),
|
||||
"手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"),
|
||||
"手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"),
|
||||
"321窗口": bioyond_warehouse_2x2x1("321窗口"), # 2行×2列
|
||||
"43窗口": bioyond_warehouse_2x2x1("43窗口"), # 2行×2列
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
|
||||
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
|
||||
"加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"),
|
||||
"加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"),
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ def warehouse_factory(
|
||||
category: str = "warehouse",
|
||||
model: Optional[str] = None,
|
||||
col_offset: int = 0, # 列起始偏移量,用于生成A05-D08等命名
|
||||
row_offset: int = 0, # 行起始偏移量,用于生成F01-J03等命名
|
||||
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
|
||||
):
|
||||
# 创建位置坐标
|
||||
@@ -65,10 +66,10 @@ def warehouse_factory(
|
||||
if layout == "row-major":
|
||||
# 行优先顺序: A01,A02,A03,A04, B01,B02,B03,B04
|
||||
# locations[0] 对应 row=0, y最大(前端顶部)→ 应该是 A01
|
||||
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
|
||||
else:
|
||||
# 列优先顺序: A01,B01,C01,D01, A02,B02,C02,D02
|
||||
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
|
||||
|
||||
sites = {i: site for i, site in zip(keys, _sites.values())}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user