Update neware battery test system driver and registry

- Expand neware_battery_test_system.py with new actions and logic
- Update generate_xml_content.py with additional XML generation support
- Extend neware_battery_test_system.yaml registry with new action schemas
- Update OSS upload READMEs and device.json
- Add electrode_sheet.py resource fields

Made-with: Cursor
This commit is contained in:
Xie Qiming
2026-04-21 17:30:56 +08:00
parent 7efccbc688
commit 52b460466d
7 changed files with 1677 additions and 50 deletions

View File

@@ -219,10 +219,10 @@ device = NewareBatteryTestSystem(
#### 步骤 2提交测试任务 #### 步骤 2提交测试任务
使用 `submit_from_csv` 提交测试任务: 使用 `submit_from_csv_export_ndax` 提交测试任务:
```python ```python
result = device.submit_from_csv( result = device.submit_from_csv_export_ndax(
csv_path="test_data.csv", csv_path="test_data.csv",
output_dir="D:/neware_output" output_dir="D:/neware_output"
) )
@@ -489,7 +489,7 @@ A: 重新获取新的 Token 并更新环境变量 `UNI_LAB_AUTH_TOKEN`。
**Q: 可以自定义上传路径吗?** **Q: 可以自定义上传路径吗?**
A: 当前版本路径由统一 API 自动分配,`oss_prefix` 参数暂不使用(保留接口兼容性)。 A: 当前版本路径由统一 API 自动分配,`oss_prefix` 参数暂不使用(保留接口兼容性)。
**Q: 为什么不在 `submit_from_csv` 中自动上传?** **Q: 为什么不在 `submit_from_csv_export_ndax` 中自动上传?**
A: 因为备份文件在测试进行中逐步生成,方法返回时可能文件尚未完全生成,因此提供独立的上传方法更灵活。 A: 因为备份文件在测试进行中逐步生成,方法返回时可能文件尚未完全生成,因此提供独立的上传方法更灵活。
**Q: 上传后如何访问文件?** **Q: 上传后如何访问文件?**

View File

@@ -230,10 +230,10 @@ device = NewareBatteryTestSystem(
#### Step 2: Submit Test Tasks #### Step 2: Submit Test Tasks
Use `submit_from_csv` to submit test tasks: Use `submit_from_csv_export_ndax` to submit test tasks:
```python ```python
result = device.submit_from_csv( result = device.submit_from_csv_export_ndax(
csv_path="test_data.csv", csv_path="test_data.csv",
output_dir="D:/neware_output" output_dir="D:/neware_output"
) )
@@ -500,7 +500,7 @@ A: Obtain a new API Key and update the `UNI_LAB_AUTH_TOKEN` environment variable
**Q: Can I customize upload paths?** **Q: Can I customize upload paths?**
A: Current version has paths automatically assigned by unified API. `oss_prefix` parameter is currently unused (retained for interface compatibility). A: Current version has paths automatically assigned by unified API. `oss_prefix` parameter is currently unused (retained for interface compatibility).
**Q: Why not auto-upload in `submit_from_csv`?** **Q: Why not auto-upload in `submit_from_csv_export_ndax`?**
A: Because backup files are generated progressively during testing, they may not be fully generated when the method returns. A separate upload method provides more flexibility. A: Because backup files are generated progressively during testing, they may not be fully generated when the method returns. A separate upload method provides more flexibility.
**Q: How to access files after upload?** **Q: How to access files after upload?**

View File

@@ -26,7 +26,7 @@
"data": { "data": {
"功能说明": "新威电池测试系统提供720通道监控和CSV批量提交功能", "功能说明": "新威电池测试系统提供720通道监控和CSV批量提交功能",
"监控功能": "支持720个通道的实时状态监控、2盘电池物料管理、状态导出等", "监控功能": "支持720个通道的实时状态监控、2盘电池物料管理、状态导出等",
"提交功能": "通过submit_from_csv action从CSV文件批量提交测试任务。CSV必须包含: Battery_Code, Pole_Weight, 集流体质量, 活性物质含量, 克容量mah/g, 电池体系, 设备号, 排号, 通道号" "提交功能": "通过submit_from_csv action从CSV文件批量提交测试任务NDA备份或通过submit_from_csv_export_excel action提交并备份为Excel格式。CSV必须包含: Battery_Code, Pole_Weight, 集流体质量, 活性物质含量, 克容量mah/g, 电池体系, 设备号, 排号, 通道号"
}, },
"children": [] "children": []
} }

View File

@@ -1359,3 +1359,286 @@ def xml_ZQXNLRMO(act_mass, Cap_mAh):
</root> </root>
""" """
return xml_data return xml_data
def xml_811_Li_JY(act_mass=None, Cap_mAh=None):
"""
生成XML内容
参数:
act_mass: 可选,未使用
Cap_mAh: 可选,未使用
"""
xml_data = f"""<?xml version="1.0" encoding="utf-8"?>
<root>
<config type="Step File" version="18" client_version="BTS Client 8.0.1.492(2025.01.23)(R3)" date="20251210133911" Guid="8a47521b-79f9-40e7-baaa-3e462f26979a">
<Head_Info>
<Operate Value="66" />
<Scale Value="1" />
<Start_Step Value="1" Hide_Ctrl_Step="0" />
<PN Value="2025-08-05 19-42-25" />
<RateType Value="103" />
</Head_Info>
<Whole_Prt>
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
</Whole_Prt>
<Step_Info Num="13">
<Step1 Step_ID="1" Step_Type="4">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Time Value="43200000" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step1>
<Step2 Step_ID="2" Step_Type="1">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="0.206" />
<Stop_Volt Value="43000" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step2>
<Step3 Step_ID="3" Step_Type="3">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="0.206" />
<Volt Value="43000" />
<Stop_Curr Value="0.05" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step3>
<Step4 Step_ID="4" Step_Type="2">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="0.206" />
<Stop_Volt Value="27500" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step4>
<Step5 Step_ID="5" Step_Type="1">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="0.206" />
<Stop_Volt Value="43000" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step5>
<Step6 Step_ID="6" Step_Type="2">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="0.206" />
<Stop_Volt Value="27500" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step6>
<Step7 Step_ID="7" Step_Type="1">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="1.03" />
<Stop_Volt Value="43000" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step7>
<Step8 Step_ID="8" Step_Type="2">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="1.03" />
<Stop_Volt Value="27500" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step8>
<Step9 Step_ID="9" Step_Type="5">
<Limit>
<Other>
<Start_Step Value="7" />
<Cycle_Count Value="5" />
</Other>
</Limit>
</Step9>
<Step10 Step_ID="10" Step_Type="1">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="2.06" />
<Stop_Volt Value="43000" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step10>
<Step11 Step_ID="11" Step_Type="2">
<Record>
<Main>
<Time Value="30000" />
</Main>
</Record>
<Limit>
<Main>
<Curr Value="2.06" />
<Stop_Volt Value="27500" />
</Main>
</Limit>
<Protect>
<Main>
<Volt>
<Upper Value="50000" />
</Volt>
<EndVolt>
<Lower Value="-50000" />
</EndVolt>
</Main>
</Protect>
</Step11>
<Step12 Step_ID="12" Step_Type="5">
<Limit>
<Other>
<Start_Step Value="10" />
<Cycle_Count Value="500" />
</Other>
</Limit>
</Step12>
<Step13 Step_ID="13" Step_Type="6">
</Step13>
</Step_Info>
<SMBUS>
<SMBUS_Info Num="0" AdjacentInterval="0" />
</SMBUS>
</config>
</root>
"""
return xml_data

View File

@@ -19,10 +19,13 @@ import socket
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import json import json
import time import time
import inspect
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, Dict, List, Optional, TypedDict
from pylabrobot.resources import ResourceHolder, Coordinate, create_ordered_items_2d, Deck, Plate from pylabrobot.resources import ResourceHolder, Coordinate, create_ordered_items_2d, Deck, Plate
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
from unilabos.resources.resource_tracker import ResourceTreeSet
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
@@ -256,12 +259,27 @@ class BatteryTestPosition(ResourceHolder):
super().load_state(state) super().load_state(state)
self._unilabos_state = state self._unilabos_state = state
def serialize(self) -> dict:
d = super().serialize()
channel_name = self._unilabos_state.get("Channel_Name")
if channel_name:
d["name"] = channel_name
return d
def serialize_state(self) -> Dict[str, Dict[str, Any]]: def serialize_state(self) -> Dict[str, Dict[str, Any]]:
"""格式不变""" """格式不变"""
data = super().serialize_state() data = super().serialize_state()
data.update(self._unilabos_state) data.update(self._unilabos_state)
return data return data
def serialize_all_state(self) -> Dict[str, Dict[str, Any]]:
states = {}
channel_name = self._unilabos_state.get("Channel_Name", self.name)
states[channel_name] = self.serialize_state()
for child in self.children:
states.update(child.serialize_all_state())
return states
class NewareBatteryTestSystem: class NewareBatteryTestSystem:
""" """
@@ -292,13 +310,13 @@ class NewareBatteryTestSystem:
# ======================== # ========================
STATUS_SET = {"working", "stop", "finish", "protect", "pause", "false"} STATUS_SET = {"working", "stop", "finish", "protect", "pause", "false"}
STATUS_COLOR = { STATUS_COLOR = {
"working": "#22c55e", # 绿 "working": "#15803d", # 绿
"stop": "#6b7280", # 灰 "stop": "#4b5563", #
"finish": "#3b82f6", # 蓝 "finish": "#1d4ed8", #
"protect": "#ef4444", # 红 "protect": "#b91c1c", #
"pause": "#f59e0b", # 橙 "pause": "#b45309", #
"false": "#9ca3af", # 不存在/无效 "false": "#6b7280", #
"unknown": "#a855f7", # 未知 "unknown": "#7c3aed", # 深紫
} }
# 字母常量 # 字母常量
@@ -409,10 +427,10 @@ class NewareBatteryTestSystem:
"""设置物料管理系统""" """设置物料管理系统"""
deck_main = Deck( deck_main = Deck(
name="ADeckName", name="ADeckName",
size_x=2200, size_x=1200,
size_y=2800, size_y=2800,
size_z=100, size_z=100,
origin=Coordinate(2000, 2000, 0) origin=Coordinate(-5500, 0, 0)
) )
self.station_resources = {} self.station_resources = {}
self.station_resources_by_plate = {} self.station_resources_by_plate = {}
@@ -432,19 +450,34 @@ class NewareBatteryTestSystem:
plate_name = self._plate_name(devid, plate_num) plate_name = self._plate_name(devid, plate_num)
plate = Plate( plate = Plate(
name=plate_name, name=plate_name,
size_x=400, size_x=540,
size_y=300, size_y=350,
size_z=50, size_z=50,
ordered_items=plate_resources ordered_items=plate_resources
) )
location_x = 0 if plate_num == 1 else 450 location_x = 0 if plate_num == 1 else 590
location_y = row_idx * 350 location_y = row_idx * 400
deck_main.assign_child_resource(plate, location=Coordinate(location_x, location_y, 0)) deck_main.assign_child_resource(plate, location=Coordinate(location_x, location_y, 0))
plate_key = (devid, plate_num) plate_key = (devid, plate_num)
subdev_start = 1 if plate_num == 1 else 6
self.station_resources_by_plate[plate_key] = {} self.station_resources_by_plate[plate_key] = {}
for name, resource in plate_resources.items(): for name, resource in plate_resources.items():
new_name = f"{plate_name}_{name}" new_name = f"{plate_name}_{name}"
# 从名称解析 col/row 索引,设置初始 Channel_Name
parts = name.rsplit("_", 2)
if len(parts) >= 3:
col_idx, row_idx = int(parts[-2]), int(parts[-1])
chl_id = col_idx + 1
subdev_id = subdev_start + row_idx
resource.load_state({
"status": "unknown",
"color": self.STATUS_COLOR["unknown"],
"voltage": 0.0,
"current": 0.0,
"time": 0.0,
"Channel_Name": f"{devid}-{subdev_id}-{chl_id}",
})
self.station_resources_by_plate[plate_key][new_name] = resource self.station_resources_by_plate[plate_key][new_name] = resource
self.station_resources[new_name] = resource self.station_resources[new_name] = resource
@@ -874,6 +907,28 @@ class NewareBatteryTestSystem:
"""规范化电池体系名称""" """规范化电池体系名称"""
return str(bs).strip().replace('-', '_').upper() return str(bs).strip().replace('-', '_').upper()
def _get_builder_required_positional_count(self, builder) -> int:
"""返回XML生成函数必填位置参数个数仅统计无默认值的positional参数"""
sig = inspect.signature(builder)
required = 0
for p in sig.parameters.values():
if p.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD):
if p.default is inspect.Parameter.empty:
required += 1
return required
def _is_csv_value_empty(self, value) -> bool:
"""判断CSV单元格是否为空兼容NaN/None/空串/null"""
if value is None:
return True
if isinstance(value, str):
return value.strip().lower() in ("", "nan", "none", "null")
try:
# NaN 与自身不相等
return value != value
except Exception:
return False
def _compute_values(self, row): def _compute_values(self, row):
""" """
计算活性物质质量和容量 计算活性物质质量和容量
@@ -884,7 +939,7 @@ class NewareBatteryTestSystem:
Returns: Returns:
tuple: (活性物质质量mg, 容量mAh) tuple: (活性物质质量mg, 容量mAh)
""" """
pw = float(row['Pole_Weight']) pw = float(row['pole_weight'])
cm = float(row['集流体质量']) cm = float(row['集流体质量'])
am = row['活性物质含量'] am = row['活性物质含量']
if isinstance(am, str) and am.endswith('%'): if isinstance(am, str) and am.endswith('%'):
@@ -918,6 +973,7 @@ class NewareBatteryTestSystem:
'SIGR_LI': gen_mod.xml_SiGr_Li_Step, 'SIGR_LI': gen_mod.xml_SiGr_Li_Step,
'811_SIGR': gen_mod.xml_811_SiGr, '811_SIGR': gen_mod.xml_811_SiGr,
'811_CU_AGING': gen_mod.xml_811_Cu_aging, '811_CU_AGING': gen_mod.xml_811_Cu_aging,
'811_LI_JY': gen_mod.xml_811_Li_JY,
'ZQXNLRMO':gen_mod.xml_ZQXNLRMO, 'ZQXNLRMO':gen_mod.xml_ZQXNLRMO,
} }
if key not in fmap: if key not in fmap:
@@ -935,7 +991,7 @@ class NewareBatteryTestSystem:
with open(path, 'w', encoding='utf-8') as f: with open(path, 'w', encoding='utf-8') as f:
f.write(xml) f.write(xml)
def submit_from_csv(self, csv_path: str, output_dir: str = ".") -> dict: def submit_from_csv_export_ndax(self, csv_path: str, output_dir: str = ".") -> dict:
""" """
从CSV文件批量提交Neware测试任务设备动作 从CSV文件批量提交Neware测试任务设备动作
@@ -967,8 +1023,7 @@ class NewareBatteryTestSystem:
# 验证必需列 # 验证必需列
required = [ required = [
'Battery_Code', 'Electrolyte_Code', 'Pole_Weight', '集流体质量', '活性物质含量', 'coin_cell_code', 'electrolyte_code', '电池体系', '设备号', '排号', '通道号'
'克容量mah/g', '电池体系', '设备号', '排号', '通道号'
] ]
missing = [c for c in required if c not in df.columns] missing = [c for c in required if c not in df.columns]
if missing: if missing:
@@ -997,27 +1052,47 @@ class NewareBatteryTestSystem:
for idx, row in df.iterrows(): for idx, row in df.iterrows():
try: try:
coin_id = f"{row['Battery_Code']}-{row['Electrolyte_Code']}" coin_id = f"{row['coin_cell_code']}-{row['electrolyte_code']}"
# 计算活性物质质量和容量
act_mass, cap_mAh = self._compute_values(row)
if cap_mAh < 0:
error_msg = (
f"容量为负数: Battery_Code={coin_id}, "
f"活性物质质量mg={act_mass}, 容量mah={cap_mAh}"
)
if self._ros_node:
self._ros_node.lab_logger().warning(error_msg)
results.append(f"{idx+1} 失败: {error_msg}")
continue
# 获取电池体系对应的XML生成函数 # 获取电池体系对应的XML生成函数
key = self._canon(row['电池体系']) key = self._canon(row['电池体系'])
builder = self._get_xml_builder(gen_mod, key) builder = self._get_xml_builder(gen_mod, key)
builder_required_args = self._get_builder_required_positional_count(builder)
# 生成XML内容 # 生成XML内容:仅当工步模板需要时才校验并计算 act_mass/cap_mAh
xml_content = builder(act_mass, cap_mAh) if builder_required_args == 0:
xml_content = builder()
elif builder_required_args == 2:
calc_cols = ['pole_weight', '集流体质量', '活性物质含量', '克容量mah/g']
missing_calc = [
c for c in calc_cols
if c not in df.columns or self._is_csv_value_empty(row[c])
]
if missing_calc:
error_msg = (
f"电池体系 {key} 需要 act_mass/Cap_mAh以下列缺失或为空: {missing_calc}, "
f"CoinID={coin_id}"
)
if self._ros_node:
self._ros_node.lab_logger().warning(error_msg)
results.append(f"{idx+1} 失败: {error_msg}")
continue
act_mass, cap_mAh = self._compute_values(row)
if cap_mAh < 0:
error_msg = (
f"容量为负数: Battery_Code={coin_id}, "
f"活性物质质量mg={act_mass}, 容量mah={cap_mAh}"
)
if self._ros_node:
self._ros_node.lab_logger().warning(error_msg)
results.append(f"{idx+1} 失败: {error_msg}")
continue
xml_content = builder(act_mass, cap_mAh)
else:
raise ValueError(
f"XML生成函数参数不支持: {builder.__name__} 需要 {builder_required_args} 个必填位置参数"
)
# 获取设备信息 # 获取设备信息
devid = int(row['设备号']) devid = int(row['设备号'])
@@ -1040,7 +1115,8 @@ class NewareBatteryTestSystem:
chlid=chlid, chlid=chlid,
CoinID=coin_id, CoinID=coin_id,
recipe_path=recipe_path, recipe_path=recipe_path,
backup_dir=backup_dir backup_dir=backup_dir,
filetype=0
) )
submitted_count += 1 submitted_count += 1
@@ -1048,7 +1124,7 @@ class NewareBatteryTestSystem:
if self._ros_node: if self._ros_node:
self._ros_node.lab_logger().info( self._ros_node.lab_logger().info(
f"已提交 {coin_id} (设备{devid}-{subdevid}-{chlid}): {resp}" f"已提交 {coin_id} (设备{devid}-{subdevid}-{chlid}, NDAX备份): {resp}"
) )
except Exception as e: except Exception as e:
@@ -1088,6 +1164,168 @@ class NewareBatteryTestSystem:
} }
def submit_from_csv_export_excel(self, csv_path: str, output_dir: str = ".") -> dict:
"""
从CSV文件批量提交Neware测试任务备份格式为Excel设备动作
与 submit_from_csv_export_ndax 逻辑一致,唯一区别是 BTS 备份文件格式为 Excel 而非 NDA。
Args:
csv_path (str): 输入CSV文件路径
output_dir (str): 输出目录用于存储XML文件和备份默认当前目录
Returns:
dict: 执行结果 {"return_info": str, "success": bool, "submitted_count": int}
"""
try:
self._ensure_local_import_path()
import pandas as pd
import generate_xml_content as gen_mod
from neware_driver import start_test
if self._ros_node:
self._ros_node.lab_logger().info(f"开始从CSV文件提交任务(Excel备份): {csv_path}")
if not os.path.exists(csv_path):
error_msg = f"CSV文件不存在: {csv_path}"
if self._ros_node:
self._ros_node.lab_logger().error(error_msg)
return {"return_info": error_msg, "success": False, "submitted_count": 0, "total_count": 0}
df = pd.read_csv(csv_path, encoding='gbk')
required = [
'coin_cell_code', 'electrolyte_code', '电池体系', '设备号', '排号', '通道号'
]
missing = [c for c in required if c not in df.columns]
if missing:
error_msg = f"CSV缺少必需列: {missing}"
if self._ros_node:
self._ros_node.lab_logger().error(error_msg)
return {"return_info": error_msg, "success": False, "submitted_count": 0, "total_count": 0}
xml_dir = os.path.join(output_dir, 'xml_dir')
backup_dir = os.path.join(output_dir, 'backup_dir')
os.makedirs(xml_dir, exist_ok=True)
os.makedirs(backup_dir, exist_ok=True)
self._last_backup_dir = backup_dir
if self._ros_node:
self._ros_node.lab_logger().info(
f"输出目录: XML={xml_dir}, 备份(Excel)={backup_dir}"
)
submitted_count = 0
results = []
for idx, row in df.iterrows():
try:
coin_id = f"{row['coin_cell_code']}-{row['electrolyte_code']}"
key = self._canon(row['电池体系'])
builder = self._get_xml_builder(gen_mod, key)
builder_required_args = self._get_builder_required_positional_count(builder)
if builder_required_args == 0:
xml_content = builder()
elif builder_required_args == 2:
calc_cols = ['pole_weight', '集流体质量', '活性物质含量', '克容量mah/g']
missing_calc = [
c for c in calc_cols
if c not in df.columns or self._is_csv_value_empty(row[c])
]
if missing_calc:
error_msg = (
f"电池体系 {key} 需要 act_mass/Cap_mAh以下列缺失或为空: {missing_calc}, "
f"CoinID={coin_id}"
)
if self._ros_node:
self._ros_node.lab_logger().warning(error_msg)
results.append(f"{idx+1} 失败: {error_msg}")
continue
act_mass, cap_mAh = self._compute_values(row)
if cap_mAh < 0:
error_msg = (
f"容量为负数: Battery_Code={coin_id}, "
f"活性物质质量mg={act_mass}, 容量mah={cap_mAh}"
)
if self._ros_node:
self._ros_node.lab_logger().warning(error_msg)
results.append(f"{idx+1} 失败: {error_msg}")
continue
xml_content = builder(act_mass, cap_mAh)
else:
raise ValueError(
f"XML生成函数参数不支持: {builder.__name__} 需要 {builder_required_args} 个必填位置参数"
)
devid = int(row['设备号'])
subdevid = int(row['排号'])
chlid = int(row['通道号'])
recipe_path = os.path.join(
xml_dir,
f"{coin_id}_{devid}_{subdevid}_{chlid}.xml"
)
self._save_xml(xml_content, recipe_path)
resp = start_test(
ip=self.ip,
port=self.port,
devid=devid,
subdevid=subdevid,
chlid=chlid,
CoinID=coin_id,
recipe_path=recipe_path,
backup_dir=backup_dir,
filetype=1
)
submitted_count += 1
results.append(f"{idx+1} {coin_id}: {resp}")
if self._ros_node:
self._ros_node.lab_logger().info(
f"已提交 {coin_id} (设备{devid}-{subdevid}-{chlid}, Excel备份): {resp}"
)
except Exception as e:
error_msg = f"{idx+1} 处理失败: {str(e)}"
results.append(error_msg)
if self._ros_node:
self._ros_node.lab_logger().error(error_msg)
success_msg = (
f"批量提交完成(Excel备份): 成功{submitted_count}个,共{len(df)}行。"
f"\n详细结果:\n" + "\n".join(results)
)
if self._ros_node:
self._ros_node.lab_logger().info(
f"批量提交完成(Excel备份): 成功{submitted_count}/{len(df)}"
)
return {
"return_info": success_msg,
"success": True,
"submitted_count": submitted_count,
"total_count": len(df),
"results": results
}
except Exception as e:
error_msg = f"批量提交失败(Excel备份): {str(e)}"
if self._ros_node:
self._ros_node.lab_logger().error(error_msg)
return {
"return_info": error_msg,
"success": False,
"submitted_count": 0,
"total_count": 0
}
def get_device_summary(self) -> dict: def get_device_summary(self) -> dict:
""" """
获取设备级别的摘要统计(设备动作) 获取设备级别的摘要统计(设备动作)
@@ -1164,7 +1402,7 @@ class NewareBatteryTestSystem:
上传备份目录中的文件到 OSSROS2 动作) 上传备份目录中的文件到 OSSROS2 动作)
Args: Args:
backup_dir: 备份目录路径,默认使用最近一次 submit_from_csv 的 backup_dir backup_dir: 备份目录路径,默认使用最近一次提交任务的 backup_dir
file_pattern: 文件通配符模式,默认 "*" 上传所有文件(例如 "*.csv" 仅上传 CSV 文件) file_pattern: 文件通配符模式,默认 "*" 上传所有文件(例如 "*.csv" 仅上传 CSV 文件)
oss_prefix: OSS 对象前缀,默认使用类初始化时的配置 oss_prefix: OSS 对象前缀,默认使用类初始化时的配置
@@ -1694,6 +1932,235 @@ class NewareBatteryTestSystem:
return result return result
def manual_confirm(
self,
resource: List[ResourceSlot],
target_device: DeviceSlot,
mount_resource: List[ResourceSlot],
collector_mass: List[float],
active_material: List[float],
capacity: List[float],
battery_system: List[str],
timeout_seconds: int,
assignee_user_ids: list[str],
**kwargs
) -> dict:
"""
timeout_seconds: 超时时间默认3600秒
collector_mass: 极流体质量
active_material: 活性物质含量
capacity: 克容量mAh/g
battery_system: 电池体系
修改的结果无效,是只读的
"""
resource = ResourceTreeSet.from_plr_resources(resource).dump()
mount_resource = ResourceTreeSet.from_plr_resources(mount_resource).dump()
kwargs.update(locals())
kwargs.pop("kwargs")
kwargs.pop("self")
return kwargs
async def transfer(self, resource: List[ResourceSlot], target_device: DeviceSlot, mount_resource: List[ResourceSlot]):
future = ROS2DeviceNode.run_async_func(self._ros_node.transfer_resource_to_another, True,
**{
"plr_resources": resource,
"target_device_id": target_device,
"target_resources": mount_resource,
"sites": [None] * len(mount_resource),
})
result = await future
return result
# ──────────────────────────────────────────────
# test() 辅助方法
# ──────────────────────────────────────────────
@staticmethod
def _extract_channel_name(res) -> Optional[str]:
"""从 BatteryTestPosition 或通用 Resource 中提取 Channel_Name (devid-subdevid-chlid)"""
# 情况1: ResourceSlot 对象 —— 直接读 _unilabos_state
state = getattr(res, "_unilabos_state", None)
if isinstance(state, dict):
ch = state.get("Channel_Name")
if ch:
return str(ch)
# 情况2: serialize_state()
if hasattr(res, "serialize_state"):
try:
ss = res.serialize_state()
if isinstance(ss, dict):
ch = ss.get("Channel_Name")
if ch:
return str(ch)
except Exception:
pass
# 情况3: 来自 ResourceTreeSet.dump() 的 dict
if isinstance(res, dict):
data = res.get("data", {})
if isinstance(data, dict):
ch = data.get("Channel_Name")
if ch:
return str(ch)
ch = res.get("name") or res.get("id")
if ch and len(str(ch).split("-")) == 3:
return str(ch)
# 情况4: name 本身就是 "devid-subdevid-chlid"
name = getattr(res, "name", "")
if name and len(name.split("-")) == 3:
return name
return None
@staticmethod
def _extract_pole_weight(res) -> float:
"""从电池资源 state 中提取极片称重 (mg)"""
state = getattr(res, "_unilabos_state", None)
if isinstance(state, dict) and "pole_weight" in state:
return float(state["pole_weight"])
if hasattr(res, "serialize_state"):
try:
ss = res.serialize_state()
if isinstance(ss, dict) and "pole_weight" in ss:
return float(ss["pole_weight"])
except Exception:
pass
if isinstance(res, dict):
data = res.get("data", {})
if isinstance(data, dict) and "pole_weight" in data:
return float(data["pole_weight"])
return 0.0
@staticmethod
def _parse_active_material(val) -> float:
"""解析活性物质含量,支持 0.97 或 '97%' 两种格式"""
if isinstance(val, str):
val = val.strip()
if val.endswith("%"):
return float(val[:-1]) / 100.0
return float(val)
return float(val)
# ──────────────────────────────────────────────
# test 动作:下发测试
# ──────────────────────────────────────────────
async def test(
self,
resource: List[ResourceSlot],
mount_resource: List[ResourceSlot],
collector_mass: List[float],
active_material: List[float],
capacity: List[float],
battery_system: List[str],
) -> dict:
"""
对每颗电池计算测试参数、生成 XML 工步文件并通过 TCP 下发给新威测试仪。
Args:
resource: 成品电池资源列表(含 pole_weight 状态)
mount_resource: 目标通道资源列表(含 Channel_Name = devid-subdevid-chlid
collector_mass: 各电池集流体质量 (mg)
active_material: 各电池活性物质比例0.97 或 "97%"
capacity: 各电池克容量 (mAh/g)
battery_system: 各电池体系名称(如 "811_LI_002"
"""
import importlib
gen_mod = importlib.import_module(
"unilabos.devices.neware_battery_test_system.generate_xml_content"
)
from .neware_driver import start_test as _start_test
n = len(resource)
results = []
submitted = 0
xml_dir = os.path.join(os.path.dirname(__file__), "xml_recipes")
os.makedirs(xml_dir, exist_ok=True)
backup_dir = self._last_backup_dir or os.path.join(os.path.dirname(__file__), "backup")
os.makedirs(backup_dir, exist_ok=True)
for i in range(n):
try:
# 1. 解析通道地址
ch_name = self._extract_channel_name(mount_resource[i])
if not ch_name:
raise ValueError(f"无法从 mount_resource[{i}] 提取 Channel_Name")
parts = ch_name.split("-")
if len(parts) != 3:
raise ValueError(f"Channel_Name 格式错误,期望 devid-subdevid-chlid实际: {ch_name}")
devid, subdevid, chlid = int(parts[0]), int(parts[1]), int(parts[2])
# 2. 获取电池标识与极片重量
res = resource[i]
coin_id = (
getattr(res, "name", None)
or (res.get("name") if isinstance(res, dict) else None)
or f"battery_{i}"
)
pw = self._extract_pole_weight(res)
# 3. 计算活性物质质量与容量
cm = float(collector_mass[i])
amv = self._parse_active_material(active_material[i])
sc = float(capacity[i])
act_mass = round((pw - cm) * amv, 4)
if act_mass <= 0:
raise ValueError(
f"活性物质质量异常: pole_weight={pw}mg, collector_mass={cm}mg, "
f"active_material={amv}, act_mass={act_mass}"
)
cap_mAh = round(act_mass * sc / 1000.0, 4)
if cap_mAh <= 0:
raise ValueError(f"容量计算异常: act_mass={act_mass}mg, capacity={sc}mAh/g, cap_mAh={cap_mAh}")
# 4. 生成 XML 工步文件
key = self._canon(battery_system[i])
builder = self._get_xml_builder(gen_mod, key)
req_args = self._get_builder_required_positional_count(builder)
xml_content = builder(act_mass, cap_mAh) if req_args >= 2 else builder()
recipe_path = os.path.join(xml_dir, f"{coin_id}_{devid}_{subdevid}_{chlid}.xml")
self._save_xml(xml_content, recipe_path)
# 5. TCP 下发测试
resp = _start_test(
ip=self.ip,
port=int(self.port),
devid=devid,
subdevid=subdevid,
chlid=chlid,
CoinID=coin_id,
recipe_path=recipe_path,
backup_dir=backup_dir,
filetype=0,
)
submitted += 1
results.append({
"index": i,
"coin_id": coin_id,
"channel": ch_name,
"act_mass_mg": act_mass,
"cap_mAh": cap_mAh,
"success": True,
"response": str(resp)[:300],
})
if self._ros_node:
self._ros_node.lab_logger().info(
f"[test] 已下发 {coin_id}{ch_name} "
f"act_mass={act_mass}mg cap={cap_mAh}mAh"
)
except Exception as e:
if self._ros_node:
self._ros_node.lab_logger().error(f"[test] 电池[{i}] 下发失败: {e}")
results.append({"index": i, "success": False, "error": str(e)})
summary = f"{n} 颗电池,成功下发 {submitted}"
return {
"return_info": summary,
"success": submitted > 0,
"submitted_count": submitted,
"total_count": n,
"results": results,
}
# ======================== # ========================
# 示例和测试代码 # 示例和测试代码

View File

@@ -219,7 +219,7 @@ neware_battery_test_system:
title: StrSingleInput title: StrSingleInput
type: object type: object
type: StrSingleInput type: StrSingleInput
submit_from_csv: submit_from_csv_export_ndax:
feedback: {} feedback: {}
goal: goal:
csv_path: string csv_path: string
@@ -231,7 +231,7 @@ neware_battery_test_system:
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
schema: schema:
description: 从CSV文件批量提交Neware测试任务 description: 从CSV文件批量提交Neware测试任务备份格式为NDA
properties: properties:
feedback: {} feedback: {}
goal: goal:
@@ -250,7 +250,41 @@ neware_battery_test_system:
type: object type: object
required: required:
- goal - goal
title: submit_from_csv参数 title: submit_from_csv_export_ndax参数
type: object
type: UniLabJsonCommand
submit_from_csv_export_excel:
feedback: {}
goal:
csv_path: string
output_dir: string
goal_default:
csv_path: null
output_dir: .
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 从CSV文件批量提交Neware测试任务备份格式为Excel
properties:
feedback: {}
goal:
properties:
csv_path:
description: 输入CSV文件的绝对路径
type: string
output_dir:
default: .
description: 输出目录用于存储XML和备份文件默认当前目录
type: string
required:
- csv_path
type: object
result:
type: object
required:
- goal
title: submit_from_csv_export_excel参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
test_connection_action: test_connection_action:
@@ -302,7 +336,7 @@ neware_battery_test_system:
goal: goal:
properties: properties:
backup_dir: backup_dir:
description: 备份目录路径(默认使用最近一次submit_from_csv的backup_dir description: 备份目录路径(默认使用最近一次提交任务的backup_dir
type: string type: string
file_pattern: file_pattern:
default: '*' default: '*'
@@ -320,6 +354,847 @@ neware_battery_test_system:
title: upload_backup_to_oss参数 title: upload_backup_to_oss参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
manual_confirm:
type: UniLabJsonCommand
goal:
resource: resource
target_device: target_device
mount_resource: mount_resource
collector_mass: collector_mass
active_material: active_material
capacity: capacity
battery_system: battery_system
timeout_seconds: timeout_seconds
assignee_user_ids: assignee_user_ids
feedback: {}
result:
resource: resource
target_device: target_device
mount_resource: mount_resource
collector_mass: collector_mass
active_material: active_material
capacity: capacity
battery_system: battery_system
schema:
title: manual_confirm参数
description: manual_confirm的参数schema
type: object
properties:
goal:
type: object
properties:
unilabos_device_id:
type: string
default: ''
description: UniLabOS设备ID用于指定执行动作的具体设备实例
resource:
items:
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
sample_id:
type: string
children:
type: array
items:
type: string
parent:
type: string
type:
type: string
category:
type: string
pose:
type: object
properties:
position:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
title: position
additionalProperties: false
orientation:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
w:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
- w
title: orientation
additionalProperties: false
required:
- position
- orientation
title: pose
additionalProperties: false
config:
type: string
data:
type: string
title: resource
type: array
target_device:
type: string
description: device reference
mount_resource:
items:
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
sample_id:
type: string
children:
type: array
items:
type: string
parent:
type: string
type:
type: string
category:
type: string
pose:
type: object
properties:
position:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
title: position
additionalProperties: false
orientation:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
w:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
- w
title: orientation
additionalProperties: false
required:
- position
- orientation
title: pose
additionalProperties: false
config:
type: string
data:
type: string
title: mount_resource
type: array
collector_mass:
type: array
items:
type: number
active_material:
type: array
items:
type: number
capacity:
type: array
items:
type: number
battery_system:
type: array
items:
type: string
timeout_seconds:
type: integer
assignee_user_ids:
type: array
items:
type: string
required:
- resource
- target_device
- mount_resource
- collector_mass
- active_material
- capacity
- battery_system
- timeout_seconds
- assignee_user_ids
_unilabos_placeholder_info:
resource: unilabos_resources
target_device: unilabos_devices
mount_resource: unilabos_resources
assignee_user_ids: unilabos_manual_confirm
feedback: {}
result:
type: object
required:
- goal
goal_default:
resource: []
target_device: ''
mount_resource: []
collector_mass: []
active_material: []
capacity: []
battery_system: []
timeout_seconds: 3600
assignee_user_ids: []
handles:
input:
- handler_key: target_device
data_type: device_id
label: 目标设备
data_key: target_device
data_source: handle
io_type: source
- handler_key: resource
data_type: resource
label: 待转移资源
data_key: resource
data_source: handle
io_type: source
- handler_key: mount_resource
data_type: resource
label: 目标孔位
data_key: mount_resource
data_source: handle
io_type: source
- handler_key: collector_mass
data_type: collector_mass
label: 极流体质量
data_key: collector_mass
data_source: handle
io_type: source
- handler_key: active_material
data_type: active_material
label: 活性物质含量
data_key: active_material
data_source: handle
io_type: source
- handler_key: capacity
data_type: capacity
label: 克容量
data_key: capacity
data_source: handle
io_type: source
- handler_key: battery_system
data_type: battery_system
label: 电池体系
data_key: battery_system
data_source: handle
io_type: source
output:
- handler_key: target_device
data_type: device_id
label: 目标设备
data_key: target_device
data_source: executor
- handler_key: resource
data_type: resource
label: 待转移资源
data_key: resource.@flatten
data_source: executor
- handler_key: mount_resource
data_type: resource
label: 目标孔位
data_key: mount_resource.@flatten
data_source: executor
- handler_key: collector_mass
data_type: collector_mass
label: 极流体质量
data_key: collector_mass
data_source: executor
- handler_key: active_material
data_type: active_material
label: 活性物质含量
data_key: active_material
data_source: executor
- handler_key: capacity
data_type: capacity
label: 克容量
data_key: capacity
data_source: executor
- handler_key: battery_system
data_type: battery_system
label: 电池体系
data_key: battery_system
data_source: executor
placeholder_keys:
resource: unilabos_resources
target_device: unilabos_devices
mount_resource: unilabos_resources
assignee_user_ids: unilabos_manual_confirm
always_free: true
feedback_interval: 300
node_type: manual_confirm
test:
type: UniLabJsonCommandAsync
goal:
resource: resource
mount_resource: mount_resource
collector_mass: collector_mass
active_material: active_material
capacity: capacity
battery_system: battery_system
feedback: {}
result:
return_info: return_info
success: success
submitted_count: submitted_count
total_count: total_count
results: results
schema:
title: test参数
description: test的参数schema
type: object
properties:
goal:
type: object
properties:
unilabos_device_id:
type: string
default: ''
description: UniLabOS设备ID用于指定执行动作的具体设备实例
resource:
items:
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
sample_id:
type: string
children:
type: array
items:
type: string
parent:
type: string
type:
type: string
category:
type: string
pose:
type: object
properties:
position:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
title: position
additionalProperties: false
orientation:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
w:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
- w
title: orientation
additionalProperties: false
required:
- position
- orientation
title: pose
additionalProperties: false
config:
type: string
data:
type: string
title: resource
type: array
mount_resource:
items:
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
sample_id:
type: string
children:
type: array
items:
type: string
parent:
type: string
type:
type: string
category:
type: string
pose:
type: object
properties:
position:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
title: position
additionalProperties: false
orientation:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
w:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
- w
title: orientation
additionalProperties: false
required:
- position
- orientation
title: pose
additionalProperties: false
config:
type: string
data:
type: string
title: mount_resource
type: array
collector_mass:
type: array
items:
type: number
active_material:
type: array
items:
type: number
capacity:
type: array
items:
type: number
battery_system:
type: array
items:
type: string
required:
- resource
- mount_resource
- collector_mass
- active_material
- capacity
- battery_system
_unilabos_placeholder_info:
resource: unilabos_resources
mount_resource: unilabos_resources
feedback: {}
result: {}
required:
- goal
goal_default:
resource: []
mount_resource: []
collector_mass: []
active_material: []
capacity: []
battery_system: []
handles:
input:
- handler_key: resource
data_type: resource
label: 待转移资源
data_key: resource
data_source: handle
io_type: source
- handler_key: mount_resource
data_type: resource
label: 目标孔位
data_key: mount_resource
data_source: handle
io_type: source
- handler_key: collector_mass
data_type: collector_mass
label: 极流体质量
data_key: collector_mass
data_source: handle
io_type: source
- handler_key: active_material
data_type: active_material
label: 活性物质含量
data_key: active_material
data_source: handle
io_type: source
- handler_key: capacity
data_type: capacity
label: 克容量
data_key: capacity
data_source: handle
io_type: source
- handler_key: battery_system
data_type: battery_system
label: 电池体系
data_key: battery_system
data_source: handle
io_type: source
output: []
placeholder_keys:
resource: unilabos_resources
mount_resource: unilabos_resources
feedback_interval: 1.0
transfer:
type: UniLabJsonCommandAsync
goal:
resource: resource
target_device: target_device
mount_resource: mount_resource
feedback: {}
result: {}
schema:
title: transfer参数
description: transfer的参数schema
type: object
properties:
goal:
type: object
properties:
unilabos_device_id:
type: string
default: ''
description: UniLabOS设备ID用于指定执行动作的具体设备实例
resource:
items:
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
sample_id:
type: string
children:
type: array
items:
type: string
parent:
type: string
type:
type: string
category:
type: string
pose:
type: object
properties:
position:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
title: position
additionalProperties: false
orientation:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
w:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
- w
title: orientation
additionalProperties: false
required:
- position
- orientation
title: pose
additionalProperties: false
config:
type: string
data:
type: string
title: resource
type: array
target_device:
type: string
description: device reference
mount_resource:
items:
type: object
additionalProperties: false
properties:
id:
type: string
name:
type: string
sample_id:
type: string
children:
type: array
items:
type: string
parent:
type: string
type:
type: string
category:
type: string
pose:
type: object
properties:
position:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
title: position
additionalProperties: false
orientation:
type: object
properties:
x:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
y:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
z:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
w:
type: number
minimum: -1.7976931348623157e+308
maximum: 1.7976931348623157e+308
required:
- x
- y
- z
- w
title: orientation
additionalProperties: false
required:
- position
- orientation
title: pose
additionalProperties: false
config:
type: string
data:
type: string
title: mount_resource
type: array
required:
- resource
- target_device
- mount_resource
_unilabos_placeholder_info:
resource: unilabos_resources
target_device: unilabos_devices
mount_resource: unilabos_resources
feedback: {}
result: {}
required:
- goal
goal_default:
resource: []
target_device: ''
mount_resource: []
handles:
input:
- handler_key: target_device
data_type: device_id
label: 目标设备
data_key: target_device
data_source: handle
io_type: source
- handler_key: resource
data_type: resource
label: 待转移资源
data_key: resource
data_source: handle
io_type: source
- handler_key: mount_resource
data_type: resource
label: 目标孔位
data_key: mount_resource
data_source: handle
io_type: source
output: []
placeholder_keys:
resource: unilabos_resources
target_device: unilabos_devices
mount_resource: unilabos_resources
feedback_interval: 1.0
module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
status_types: status_types:
channel_status: Dict[int, Dict] channel_status: Dict[int, Dict]

View File

@@ -135,6 +135,7 @@ class BatteryState(TypedDict):
open_circuit_voltage: float open_circuit_voltage: float
assembly_pressure: float assembly_pressure: float
electrolyte_volume: float electrolyte_volume: float
pole_weight: float # 极片称重 (mg)
info: Optional[str] # 附加信息 info: Optional[str] # 附加信息
@@ -179,6 +180,7 @@ class Battery(Container):
open_circuit_voltage=0.0, open_circuit_voltage=0.0,
assembly_pressure=0.0, assembly_pressure=0.0,
electrolyte_volume=0.0, electrolyte_volume=0.0,
pole_weight=0.0,
info=None info=None
) )