diff --git a/unilabos/devices/battery/battery.json b/unilabos/devices/battery/battery.json
deleted file mode 100644
index 105ba5bd..00000000
--- a/unilabos/devices/battery/battery.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "nodes": [
- {
- "id": "NEWARE_BATTERY_TEST_SYSTEM",
- "name": "Neware Battery Test System",
- "parent": null,
- "type": "device",
- "class": "neware_battery_test_system",
- "position": {
- "x": 620.6111111111111,
- "y": 171,
- "z": 0
- },
- "config": {
- "ip": "127.0.0.1",
- "port": 502,
- "machine_id": 1,
- "devtype": "27",
- "timeout": 20,
- "size_x": 500.0,
- "size_y": 500.0,
- "size_z": 2000.0
- },
- "data": {},
- "children": []
- }
- ],
- "links": []
-}
\ No newline at end of file
diff --git a/unilabos/devices/neware_battery_test_system/__init__.py b/unilabos/devices/neware_battery_test_system/__init__.py
new file mode 100644
index 00000000..b1a170b8
--- /dev/null
+++ b/unilabos/devices/neware_battery_test_system/__init__.py
@@ -0,0 +1,8 @@
+from .neware_battery_test_system import NewareBatteryTestSystem
+from .neware_driver import build_start_command, start_test
+
+__all__ = [
+ "NewareBatteryTestSystem",
+ "build_start_command",
+ "start_test",
+]
diff --git a/unilabos/devices/neware_battery_test_system/demo.csv b/unilabos/devices/neware_battery_test_system/demo.csv
new file mode 100644
index 00000000..0ecf064f
--- /dev/null
+++ b/unilabos/devices/neware_battery_test_system/demo.csv
@@ -0,0 +1,3 @@
+Timestamp,Battery_Count,Assembly_Time,Open_Circuit_Voltage,Pole_Weight,Assembly_Pressure,Battery_Code,Electrolyte_Code,集流体质量,活性物质含量,克容量mah/g,电池体系,设备号,排号,通道号
+2025/10/29 17:32,7,5,0.11299999803304672,18.049999237060547,3593,Li000595,Si-Gr001,9.2,0.954,469,SiGr_Li,1,1,2
+2025/10/30 17:49,2,5,0,13.109999895095825,4094,YS101224,NoRead88,5.2,0.92,190,SiGr_Li,2,1,1
diff --git a/unilabos/devices/neware_battery_test_system/device.json b/unilabos/devices/neware_battery_test_system/device.json
new file mode 100644
index 00000000..ad3b4513
--- /dev/null
+++ b/unilabos/devices/neware_battery_test_system/device.json
@@ -0,0 +1,33 @@
+{
+ "nodes": [
+ {
+ "id": "NEWARE_BATTERY_TEST_SYSTEM",
+ "name": "Neware Battery Test System",
+ "parent": null,
+ "type": "device",
+ "class": "neware_battery_test_system",
+ "position": {
+ "x": 620.0,
+ "y": 200.0,
+ "z": 0
+ },
+ "config": {
+ "ip": "127.0.0.1",
+ "port": 502,
+ "machine_id": 1,
+ "devtype": "27",
+ "timeout": 20,
+ "size_x": 500.0,
+ "size_y": 500.0,
+ "size_z": 2000.0
+ },
+ "data": {
+ "鍔熻兘璇存槑": "鏂板▉鐢垫睜娴嬭瘯绯荤粺锛屾彁渚720閫氶亾鐩戞帶鍜孋SV鎵归噺鎻愪氦鍔熻兘",
+ "鐩戞帶鍔熻兘": "鏀寔720涓氶亾鐨勫疄鏃剁姸鎬佺洃鎺с2鐩樼數姹犵墿鏂欑鐞嗐佺姸鎬佸鍑虹瓑",
+ "鎻愪氦鍔熻兘": "閫氳繃submit_from_csv action浠嶤SV鏂囦欢鎵归噺鎻愪氦娴嬭瘯浠诲姟銆侰SV蹇呴』鍖呭惈: Battery_Code, Pole_Weight, 闆嗘祦浣撹川閲, 娲绘х墿璐ㄥ惈閲, 鍏嬪閲弇ah/g, 鐢垫睜浣撶郴, 璁惧鍙, 鎺掑彿, 閫氶亾鍙"
+ },
+ "children": []
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/unilabos/devices/neware_battery_test_system/generate_xml_content.py b/unilabos/devices/neware_battery_test_system/generate_xml_content.py
new file mode 100644
index 00000000..e4ac2e44
--- /dev/null
+++ b/unilabos/devices/neware_battery_test_system/generate_xml_content.py
@@ -0,0 +1,1100 @@
+def xml_811_Li_002(act_mass, Cap_mAh):
+ """
+ 鐢熸垚XML鍐呭
+
+ 鍙傛暟:
+ act_mass: 姝f瀬璐ㄩ噺(mg)
+ Cap_mAh: 姝f瀬杞介噺(mAh)
+ devid: 璁惧鍙
+ subdevid: 鎺掑彿
+ chlid: 閫氶亾鍙
+ """
+ xml_data = f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ return xml_data
+
+def xml_811_Li_005(act_mass, Cap_mAh):
+ """
+ 鐢熸垚XML鍐呭
+
+ 鍙傛暟:
+ act_mass: 姝f瀬璐ㄩ噺(mg)
+ Cap_mAh: 姝f瀬杞介噺(mAh)
+ devid: 璁惧鍙
+ subdevid: 鎺掑彿
+ chlid: 閫氶亾鍙
+ """
+ xml_data = f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ return xml_data
+
+def xml_LFP_Li(act_mass, Cap_mAh):
+ """
+ 鐢熸垚XML鍐呭
+
+ 鍙傛暟:
+ act_mass: 姝f瀬璐ㄩ噺(mg)
+ Cap_mAh: 姝f瀬杞介噺(mAh)
+ devid: 璁惧鍙
+ subdevid: 鎺掑彿
+ chlid: 閫氶亾鍙
+ """
+ xml_data = f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ return xml_data
+
+def xml_LFP_Gr(act_mass, Cap_mAh):
+ """
+ 鐢熸垚XML鍐呭
+
+ 鍙傛暟:
+ act_mass: 姝f瀬璐ㄩ噺(mg)
+ Cap_mAh: 姝f瀬杞介噺(mAh)
+ devid: 璁惧鍙
+ subdevid: 鎺掑彿
+ chlid: 閫氶亾鍙
+ """
+ xml_data = f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ return xml_data
+
+def xml_Gr_Li(act_mass, Cap_mAh):
+ """
+ 鐢熸垚XML鍐呭
+
+ 鍙傛暟:
+ act_mass: 姝f瀬璐ㄩ噺(mg)
+ Cap_mAh: 姝f瀬杞介噺(mAh)
+ devid: 璁惧鍙
+ subdevid: 鎺掑彿
+ chlid: 閫氶亾鍙
+ """
+ xml_data = f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ return xml_data
+
+def xml_LB6(act_mass, Cap_mAh):
+ """
+ 鐢熸垚XML鍐呭
+
+ 鍙傛暟:
+ act_mass: 姝f瀬璐ㄩ噺(mg)
+ Cap_mAh: 姝f瀬杞介噺(mAh)
+ devid: 璁惧鍙
+ subdevid: 鎺掑彿
+ chlid: 閫氶亾鍙
+ """
+ xml_data = f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ return xml_data
+
+
+def xml_SiGr_Li_Step(act_mass, Cap_mAh):
+ """
+ 鐢熸垚XML鍐呭
+
+ 鍙傛暟:
+ act_mass: 姝f瀬璐ㄩ噺(mg)
+ Cap_mAh: 姝f瀬杞介噺(mAh)
+ devid: 璁惧鍙
+ subdevid: 鎺掑彿
+ chlid: 閫氶亾鍙
+ """
+ xml_data= f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ return xml_data
+
+def xml_811_SiGr(act_mass, Cap_mAh):
+ """
+ 鐢熸垚XML鍐呭
+
+ 鍙傛暟:
+ act_mass: 姝f瀬璐ㄩ噺(mg)
+ Cap_mAh: 姝f瀬杞介噺(mAh)
+ devid: 璁惧鍙
+ subdevid: 鎺掑彿
+ chlid: 閫氶亾鍙
+ """
+ xml_data= f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ return xml_data
\ No newline at end of file
diff --git a/unilabos/devices/battery/neware_battery_test_system.py b/unilabos/devices/neware_battery_test_system/neware_battery_test_system.py
similarity index 69%
rename from unilabos/devices/battery/neware_battery_test_system.py
rename to unilabos/devices/neware_battery_test_system/neware_battery_test_system.py
index 317e8fe5..b51139ec 100644
--- a/unilabos/devices/battery/neware_battery_test_system.py
+++ b/unilabos/devices/neware_battery_test_system/neware_battery_test_system.py
@@ -13,6 +13,8 @@
- 鐘舵佺被鍨: working/stop/finish/protect/pause/false/unknown
"""
+import os
+import sys
import socket
import xml.etree.ElementTree as ET
import json
@@ -21,7 +23,6 @@ from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TypedDict
from pylabrobot.resources import ResourceHolder, Coordinate, create_ordered_items_2d, Deck, Plate
-
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
@@ -56,13 +57,6 @@ class BatteryTestPositionState(TypedDict):
status: str # 閫氶亾鐘舵
color: str # 鐘舵佸搴旈鑹
- # 棰濆鐨刬nquire鍗忚瀛楁
- relativetime: float # 鐩稿鏃堕棿 (s)
- open_or_close: int # 0=鍏抽棴, 1=鎵撳紑
- step_type: str # 姝ラ绫诲瀷
- cycle_id: int # 寰幆ID
- step_id: int # 姝ラID
- log_code: str # 鏃ュ織浠g爜
class BatteryTestPosition(ResourceHolder):
@@ -142,9 +136,9 @@ class NewareBatteryTestSystem:
devtype: str = None,
timeout: int = None,
- size_x: float = 500.0,
- size_y: float = 500.0,
- size_z: float = 2000.0,
+ size_x: float = 50,
+ size_y: float = 50,
+ size_z: float = 20,
):
"""
鍒濆鍖栨柊濞佺數姹犳祴璇曠郴缁
@@ -162,6 +156,12 @@ class NewareBatteryTestSystem:
self.machine_id = machine_id
self.devtype = devtype or self.DEVTYPE
self.timeout = timeout or self.TIMEOUT
+
+ # 瀛樺偍璁惧鐗╃悊灏哄
+ self.size_x = size_x
+ self.size_y = size_y
+ self.size_z = size_z
+
self._last_status_update = None
self._cached_status = {}
self._ros_node: Optional[ROS2WorkstationNode] = None # ROS鑺傜偣寮曠敤锛岀敱妗嗘灦璁剧疆
@@ -192,8 +192,9 @@ class NewareBatteryTestSystem:
def _setup_material_management(self):
"""璁剧疆鐗╂枡绠$悊绯荤粺"""
# 绗1鐩橈細5琛8鍒楃綉鏍 (A1-E8) - 5琛屽搴攕ubdevid 1-5锛8鍒楀搴攃hlid 1-8
- # 鍏堢粰鐗╂枡璁剧疆涓涓渶澶х殑Deck
- deck_main = Deck("ADeckName", 200, 200, 200)
+ # 鍏堢粰鐗╂枡璁剧疆涓涓渶澶х殑Deck锛屽苟璁剧疆鍏跺湪绌洪棿涓殑浣嶇疆
+
+ deck_main = Deck("ADeckName", 2000, 1800, 100, origin=Coordinate(2000,2000,0))
plate1_resources: Dict[str, BatteryTestPosition] = create_ordered_items_2d(
BatteryTestPosition,
@@ -202,8 +203,8 @@ class NewareBatteryTestSystem:
dx=10,
dy=10,
dz=0,
- item_dx=45,
- item_dy=45
+ item_dx=65,
+ item_dy=65
)
plate1 = Plate("P1", 400, 300, 50, ordered_items=plate1_resources)
deck_main.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
@@ -232,11 +233,15 @@ class NewareBatteryTestSystem:
num_items_y=5, # 5琛岋紙瀵瑰簲subdevid 6-10锛屽嵆A-E锛
dx=10,
dy=10,
- dz=100, # Z杞村亸绉100mm
+ dz=0,
item_dx=65,
item_dy=65
)
+ plate2 = Plate("P2", 400, 300, 50, ordered_items=plate2_resources)
+ deck_main.assign_child_resource(plate2, location=Coordinate(0, 350, 0))
+
+
# 涓虹2鐩樿祫婧愭坊鍔燩2_鍓嶇紑
self.station_resources_plate2 = {}
for name, resource in plate2_resources.items():
@@ -306,55 +311,132 @@ class NewareBatteryTestSystem:
def _update_plate_resources(self, subunits: Dict):
"""鏇存柊涓ょ洏鐢垫睜璧勬簮鐨勭姸鎬"""
- # 绗1鐩橈細subdevid 1-5 鏄犲皠鍒 P1_A1-P1_E8 (5琛8鍒)
+ # 绗1鐩橈細subdevid 1-5 鏄犲皠鍒 8鍒5琛岀綉鏍 (鍒0-7, 琛0-4)
for subdev_id in range(1, 6): # subdevid 1-5
status_row = subunits.get(subdev_id, {})
for chl_id in range(1, 9): # chlid 1-8
try:
- # 璁$畻鍦5脳8缃戞牸涓殑浣嶇疆
- row_idx = (subdev_id - 1) # 0-4 (瀵瑰簲A-E)
- col_idx = (chl_id - 1) # 0-7 (瀵瑰簲1-8)
- resource_name = f"P1_{self.LETTERS[row_idx]}{col_idx + 1}"
+ # 鏍规嵁鐢ㄦ埛鎻忚堪锛氱涓涓槸(0,0)锛屾渶鍚庝竴涓槸(7,4)
+ # 璇存槑鏄8鍒5琛岋紝鍒椾粠0寮濮嬶紝琛屼粠0寮濮
+ col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 鍒0-7)
+ row_idx = (subdev_id - 1) # 0-4 (subdevid 1-5 -> 琛0-4)
+
+ # 灏濊瘯澶氱鍙兘鐨勮祫婧愬懡鍚嶆牸寮
+ possible_names = [
+ f"P1_batterytestposition_{col_idx}_{row_idx}", # 鐢ㄦ埛鎻愬埌鐨勬牸寮
+ f"P1_{self.LETTERS[row_idx]}{col_idx + 1}", # 鍘熸湁鐨凙1-E8鏍煎紡
+ f"P1_{self.LETTERS[row_idx].lower()}{col_idx + 1}", # 灏忓啓瀛楁瘝鏍煎紡
+ ]
+
+ r = None
+ resource_name = None
+ for name in possible_names:
+ if name in self.station_resources:
+ r = self.station_resources[name]
+ resource_name = name
+ break
- r = self.station_resources.get(resource_name)
if r:
status_channel = status_row.get(chl_id, {})
+ metrics = status_channel.get("metrics", {})
+ # 鏋勫缓BatteryTestPosition鐘舵佹暟鎹紙绉婚櫎capacity鍜宔nergy锛
channel_state = {
+ # 鍩烘湰娴嬮噺鏁版嵁
+ "voltage": metrics.get("voltage_V", 0.0),
+ "current": metrics.get("current_A", 0.0),
+ "time": metrics.get("totaltime_s", 0.0),
+
+ # 鐘舵佷俊鎭
"status": status_channel.get("state", "unknown"),
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
- "voltage": status_channel.get("voltage_V", 0.0),
- "current": status_channel.get("current_A", 0.0),
- "time": status_channel.get("totaltime_s", 0.0),
+
+ # 閫氶亾鍚嶇О鏍囪瘑
+ "Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
+
}
r.load_state(channel_state)
- except (KeyError, IndexError):
+
+ # 璋冭瘯淇℃伅
+ if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
+ self._ros_node.lab_logger().debug(
+ f"鏇存柊P1璧勬簮鐘舵: {resource_name} <- subdev{subdev_id}/chl{chl_id} "
+ f"鐘舵:{channel_state['status']}"
+ )
+ else:
+ # 濡傛灉鎵句笉鍒拌祫婧愶紝璁板綍璋冭瘯淇℃伅
+ if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
+ self._ros_node.lab_logger().debug(
+ f"P1鏈壘鍒拌祫婧: subdev{subdev_id}/chl{chl_id} -> 灏濊瘯鐨勫悕绉: {possible_names}"
+ )
+ except (KeyError, IndexError) as e:
+ if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
+ self._ros_node.lab_logger().debug(f"P1鏄犲皠閿欒: subdev{subdev_id}/chl{chl_id} - {e}")
continue
- # 绗2鐩橈細subdevid 6-10 鏄犲皠鍒 P2_A1-P2_E8 (5琛8鍒)
+ # 绗2鐩橈細subdevid 6-10 鏄犲皠鍒 8鍒5琛岀綉鏍 (鍒0-7, 琛0-4)
for subdev_id in range(6, 11): # subdevid 6-10
status_row = subunits.get(subdev_id, {})
for chl_id in range(1, 9): # chlid 1-8
try:
- # 璁$畻鍦5脳8缃戞牸涓殑浣嶇疆
- row_idx = (subdev_id - 6) # 0-4 (subdevid 6->0, 7->1, ..., 10->4) (瀵瑰簲A-E)
- col_idx = (chl_id - 1) # 0-7 (瀵瑰簲1-8)
- resource_name = f"P2_{self.LETTERS[row_idx]}{col_idx + 1}"
+ col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 鍒0-7)
+ row_idx = (subdev_id - 6) # 0-4 (subdevid 6-10 -> 琛0-4)
+
+ # 灏濊瘯澶氱鍙兘鐨勮祫婧愬懡鍚嶆牸寮
+ possible_names = [
+ f"P2_batterytestposition_{col_idx}_{row_idx}", # 鐢ㄦ埛鎻愬埌鐨勬牸寮
+ f"P2_{self.LETTERS[row_idx]}{col_idx + 1}", # 鍘熸湁鐨凙1-E8鏍煎紡
+ f"P2_{self.LETTERS[row_idx].lower()}{col_idx + 1}", # 灏忓啓瀛楁瘝鏍煎紡
+ ]
+
+ r = None
+ resource_name = None
+ for name in possible_names:
+ if name in self.station_resources:
+ r = self.station_resources[name]
+ resource_name = name
+ break
- r = self.station_resources.get(resource_name)
if r:
status_channel = status_row.get(chl_id, {})
+ metrics = status_channel.get("metrics", {})
+ # 鏋勫缓BatteryTestPosition鐘舵佹暟鎹紙绉婚櫎capacity鍜宔nergy锛
channel_state = {
+ # 鍩烘湰娴嬮噺鏁版嵁
+ "voltage": metrics.get("voltage_V", 0.0),
+ "current": metrics.get("current_A", 0.0),
+ "time": metrics.get("totaltime_s", 0.0),
+
+ # 鐘舵佷俊鎭
"status": status_channel.get("state", "unknown"),
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
- "voltage": status_channel.get("voltage_V", 0.0),
- "current": status_channel.get("current_A", 0.0),
- "time": status_channel.get("totaltime_s", 0.0),
+
+ # 閫氶亾鍚嶇О鏍囪瘑
+ "Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
+
}
r.load_state(channel_state)
- except (KeyError, IndexError):
+
+ # 璋冭瘯淇℃伅
+ if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
+ self._ros_node.lab_logger().debug(
+ f"鏇存柊P2璧勬簮鐘舵: {resource_name} <- subdev{subdev_id}/chl{chl_id} "
+ f"鐘舵:{channel_state['status']}"
+ )
+ else:
+ # 濡傛灉鎵句笉鍒拌祫婧愶紝璁板綍璋冭瘯淇℃伅
+ if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
+ self._ros_node.lab_logger().debug(
+ f"P2鏈壘鍒拌祫婧: subdev{subdev_id}/chl{chl_id} -> 灏濊瘯鐨勫悕绉: {possible_names}"
+ )
+ except (KeyError, IndexError) as e:
+ if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
+ self._ros_node.lab_logger().debug(f"P2鏄犲皠閿欒: subdev{subdev_id}/chl{chl_id} - {e}")
continue
+ ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
+ "resources": list(self.station_resources.values())
+ })
@property
def connection_info(self) -> Dict[str, str]:
@@ -490,6 +572,45 @@ class NewareBatteryTestSystem:
+ def debug_resource_names(self) -> dict:
+ """
+ 璋冭瘯鏂规硶锛氭樉绀烘墍鏈夎祫婧愮殑瀹為檯鍚嶇О锛圧OS2鍔ㄤ綔锛
+
+ Returns:
+ dict: ROS2鍔ㄤ綔缁撴灉鏍煎紡锛屽寘鍚墍鏈夎祫婧愬悕绉颁俊鎭
+ """
+ try:
+ debug_info = {
+ "total_resources": len(self.station_resources),
+ "plate1_resources": len(self.station_resources_plate1),
+ "plate2_resources": len(self.station_resources_plate2),
+ "plate1_names": list(self.station_resources_plate1.keys())[:10], # 鏄剧ず鍓10涓
+ "plate2_names": list(self.station_resources_plate2.keys())[:10], # 鏄剧ず鍓10涓
+ "all_resource_names": list(self.station_resources.keys())[:20], # 鏄剧ず鍓20涓
+ }
+
+ # 妫鏌ユ槸鍚︽湁鐢ㄦ埛鎻愬埌鐨勫懡鍚嶆牸寮
+ batterytestposition_names = [name for name in self.station_resources.keys()
+ if "batterytestposition" in name]
+ debug_info["batterytestposition_names"] = batterytestposition_names[:10]
+
+ success_msg = f"璧勬簮璋冭瘯淇℃伅鑾峰彇鎴愬姛锛屽叡{debug_info['total_resources']}涓祫婧"
+ if self._ros_node:
+ self._ros_node.lab_logger().info(success_msg)
+ self._ros_node.lab_logger().info(f"璋冭瘯淇℃伅: {debug_info}")
+
+ return {
+ "return_info": success_msg,
+ "success": True,
+ "debug_data": debug_info
+ }
+
+ except Exception as e:
+ error_msg = f"鑾峰彇璧勬簮璋冭瘯淇℃伅澶辫触: {str(e)}"
+ if self._ros_node:
+ self._ros_node.lab_logger().error(error_msg)
+ return {"return_info": error_msg, "success": False}
+
# ========================
# 杈呭姪鏂规硶
# ========================
@@ -538,6 +659,228 @@ class NewareBatteryTestSystem:
except Exception as e:
print(f" 鑾峰彇鐘舵佸け璐: {e}")
+ # ========================
+ # CSV鎵归噺鎻愪氦鍔熻兘锛堟柊澧烇級
+ # ========================
+
+ def _ensure_local_import_path(self):
+ """纭繚鏈湴妯″潡瀵煎叆璺緞"""
+ base_dir = os.path.dirname(__file__)
+ if base_dir not in sys.path:
+ sys.path.insert(0, base_dir)
+
+ def _canon(self, bs: str) -> str:
+ """瑙勮寖鍖栫數姹犱綋绯诲悕绉"""
+ return str(bs).strip().replace('-', '_').upper()
+
+ def _compute_values(self, row):
+ """
+ 璁$畻娲绘х墿璐ㄨ川閲忓拰瀹归噺
+
+ Args:
+ row: DataFrame琛屾暟鎹
+
+ Returns:
+ tuple: (娲绘х墿璐ㄨ川閲弇g, 瀹归噺mAh)
+ """
+ pw = float(row['Pole_Weight'])
+ cm = float(row['闆嗘祦浣撹川閲'])
+ am = row['娲绘х墿璐ㄥ惈閲']
+ if isinstance(am, str) and am.endswith('%'):
+ amv = float(am.rstrip('%')) / 100.0
+ else:
+ amv = float(am)
+ act_mass = (pw - cm) * amv
+ sc = float(row['鍏嬪閲弇ah/g'])
+ cap = act_mass * sc / 1000.0
+ return round(act_mass, 2), round(cap, 3)
+
+ def _get_xml_builder(self, gen_mod, key: str):
+ """
+ 鑾峰彇瀵瑰簲鐢垫睜浣撶郴鐨刋ML鐢熸垚鍑芥暟
+
+ Args:
+ gen_mod: generate_xml_content妯″潡
+ key: 鐢垫睜浣撶郴鏍囪瘑
+
+ Returns:
+ callable: XML鐢熸垚鍑芥暟
+ """
+ fmap = {
+ 'LB6': gen_mod.xml_LB6,
+ 'GR_LI': gen_mod.xml_Gr_Li,
+ 'LFP_LI': gen_mod.xml_LFP_Li,
+ 'LFP_GR': gen_mod.xml_LFP_Gr,
+ '811_LI_002': gen_mod.xml_811_Li_002,
+ '811_LI_005': gen_mod.xml_811_Li_005,
+ 'SIGR_LI_STEP': gen_mod.xml_SiGr_Li_Step,
+ 'SIGR_LI': gen_mod.xml_SiGr_Li_Step,
+ '811_SIGR': gen_mod.xml_811_SiGr,
+ }
+ if key not in fmap:
+ raise ValueError(f"鏈畾涔夌數姹犱綋绯绘槧灏: {key}")
+ return fmap[key]
+
+ def _save_xml(self, xml: str, path: str):
+ """
+ 淇濆瓨XML鏂囦欢
+
+ Args:
+ xml: XML鍐呭
+ path: 鏂囦欢璺緞
+ """
+ with open(path, 'w', encoding='utf-8') as f:
+ f.write(xml)
+
+ def submit_from_csv(self, csv_path: str, output_dir: str = ".") -> dict:
+ """
+ 浠嶤SV鏂囦欢鎵归噺鎻愪氦Neware娴嬭瘯浠诲姟锛堣澶囧姩浣滐級
+
+ Args:
+ csv_path (str): 杈撳叆CSV鏂囦欢璺緞
+ output_dir (str): 杈撳嚭鐩綍锛岀敤浜庡瓨鍌╔ML鏂囦欢鍜屽浠斤紝榛樿褰撳墠鐩綍
+
+ Returns:
+ dict: 鎵ц缁撴灉 {"return_info": str, "success": bool, "submitted_count": int}
+ """
+ try:
+ # 纭繚鍙互瀵煎叆鏈湴妯″潡
+ self._ensure_local_import_path()
+ import pandas as pd
+ import generate_xml_content as gen_mod
+ from neware_driver import start_test
+
+ if self._ros_node:
+ self._ros_node.lab_logger().info(f"寮濮嬩粠CSV鏂囦欢鎻愪氦浠诲姟: {csv_path}")
+
+ # 璇诲彇CSV鏂囦欢
+ if not os.path.exists(csv_path):
+ error_msg = f"CSV鏂囦欢涓嶅瓨鍦: {csv_path}"
+ if self._ros_node:
+ self._ros_node.lab_logger().error(error_msg)
+ return {"return_info": error_msg, "success": False, "submitted_count": 0}
+
+ df = pd.read_csv(csv_path, encoding='gbk')
+
+ # 楠岃瘉蹇呴渶鍒
+ required = [
+ 'Battery_Code', 'Pole_Weight', '闆嗘祦浣撹川閲', '娲绘х墿璐ㄥ惈閲',
+ '鍏嬪閲弇ah/g', '鐢垫睜浣撶郴', '璁惧鍙', '鎺掑彿', '閫氶亾鍙'
+ ]
+ missing = [c for c in required if c not in df.columns]
+ if missing:
+ error_msg = f"CSV缂哄皯蹇呴渶鍒: {missing}"
+ if self._ros_node:
+ self._ros_node.lab_logger().error(error_msg)
+ return {"return_info": error_msg, "success": False, "submitted_count": 0}
+
+ # 鍒涘缓杈撳嚭鐩綍
+ xml_dir = os.path.join(output_dir, 'xml_dir')
+ backup_dir = os.path.join(output_dir, 'backup_dir')
+ os.makedirs(xml_dir, exist_ok=True)
+ os.makedirs(backup_dir, exist_ok=True)
+
+ if self._ros_node:
+ self._ros_node.lab_logger().info(
+ f"杈撳嚭鐩綍: XML={xml_dir}, 澶囦唤={backup_dir}"
+ )
+
+ # 閫愯澶勭悊CSV鏁版嵁
+ submitted_count = 0
+ results = []
+
+ for idx, row in df.iterrows():
+ try:
+ coin_id = str(row['Battery_Code'])
+
+ # 璁$畻娲绘х墿璐ㄨ川閲忓拰瀹归噺
+ act_mass, cap_mAh = self._compute_values(row)
+
+ if cap_mAh < 0:
+ error_msg = (
+ f"瀹归噺涓鸿礋鏁: Battery_Code={coin_id}, "
+ f"娲绘х墿璐ㄨ川閲弇g={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
+
+ # 鑾峰彇鐢垫睜浣撶郴瀵瑰簲鐨刋ML鐢熸垚鍑芥暟
+ key = self._canon(row['鐢垫睜浣撶郴'])
+ builder = self._get_xml_builder(gen_mod, key)
+
+ # 鐢熸垚XML鍐呭
+ xml_content = builder(act_mass, cap_mAh)
+
+ # 鑾峰彇璁惧淇℃伅
+ devid = int(row['璁惧鍙'])
+ subdevid = int(row['鎺掑彿'])
+ chlid = int(row['閫氶亾鍙'])
+
+ # 淇濆瓨XML鏂囦欢
+ recipe_path = os.path.join(
+ xml_dir,
+ f"{coin_id}_{devid}_{subdevid}_{chlid}.xml"
+ )
+ self._save_xml(xml_content, recipe_path)
+
+ # 鎻愪氦娴嬭瘯浠诲姟
+ resp = start_test(
+ ip=self.ip,
+ port=self.port,
+ devid=devid,
+ subdevid=subdevid,
+ chlid=chlid,
+ CoinID=coin_id,
+ recipe_path=recipe_path,
+ backup_dir=backup_dir
+ )
+
+ submitted_count += 1
+ results.append(f"琛寋idx+1} {coin_id}: {resp}")
+
+ if self._ros_node:
+ self._ros_node.lab_logger().info(
+ f"宸叉彁浜 {coin_id} (璁惧{devid}-{subdevid}-{chlid}): {resp}"
+ )
+
+ except Exception as e:
+ error_msg = f"琛寋idx+1} 澶勭悊澶辫触: {str(e)}"
+ results.append(error_msg)
+ if self._ros_node:
+ self._ros_node.lab_logger().error(error_msg)
+
+ # 姹囨荤粨鏋
+ success_msg = (
+ f"鎵归噺鎻愪氦瀹屾垚: 鎴愬姛{submitted_count}涓紝鍏眥len(df)}琛屻"
+ f"\n璇︾粏缁撴灉:\n" + "\n".join(results)
+ )
+
+ if self._ros_node:
+ self._ros_node.lab_logger().info(
+ f"鎵归噺鎻愪氦瀹屾垚: 鎴愬姛{submitted_count}/{len(df)}"
+ )
+
+ return {
+ "return_info": success_msg,
+ "success": True,
+ "submitted_count": submitted_count,
+ "total_count": len(df),
+ "results": results
+ }
+
+ except Exception as e:
+ error_msg = f"鎵归噺鎻愪氦澶辫触: {str(e)}"
+ if self._ros_node:
+ self._ros_node.lab_logger().error(error_msg)
+ return {
+ "return_info": error_msg,
+ "success": False,
+ "submitted_count": 0
+ }
+
+
def get_device_summary(self) -> dict:
"""
鑾峰彇璁惧绾у埆鐨勬憳瑕佺粺璁★紙璁惧鍔ㄤ綔锛
diff --git a/unilabos/devices/neware_battery_test_system/neware_driver.py b/unilabos/devices/neware_battery_test_system/neware_driver.py
new file mode 100644
index 00000000..5393892b
--- /dev/null
+++ b/unilabos/devices/neware_battery_test_system/neware_driver.py
@@ -0,0 +1,49 @@
+import socket
+END_MARKS = [b"\r\n#\r\n", b""] # 璇诲埌浠讳竴鏍囧織鍗冲彲鍒ゅ畾瀹屾暣鍝嶅簲
+
+def build_start_command(devid, subdevid, chlid, CoinID,
+ ip_in_xml="127.0.0.1",
+ devtype:int=27,
+ recipe_path:str=f"D:\\HHM_test\\A001.xml",
+ backup_dir:str=f"D:\\HHM_test\\backup") -> str:
+ lines = [
+ '',
+ '',
+ ' start',
+ ' ',
+ f' {recipe_path}',
+ f' ',
+ '
',
+ '',
+ ]
+ # TCP 妯″紡锛氳姹傚繀椤讳互 #\r\n 缁撴潫锛堝崗璁姹傦級
+ return "\r\n".join(lines) + "\r\n#\r\n"
+
+def recv_until_marks(sock: socket.socket, timeout=60):
+ sock.settimeout(timeout) # 涓婇檺缁欒冻锛屽崗璁厑璁稿埌 30s:contentReference[oaicite:2]{index=2}
+ buf = bytearray()
+ while True:
+ chunk = sock.recv(8192)
+ if not chunk:
+ break
+ buf += chunk
+ # 璇诲埌缁撴潫鏍囧織灏卞仠锛岄伩鍏嶇瓑瀵圭鏂紑
+ for m in END_MARKS:
+ if m in buf:
+ return bytes(buf)
+ # 淇濋櫓锛氳鍒板畬鏁 XML 缁撴潫鏍囩涔熷仠
+ if b"" in buf:
+ return bytes(buf)
+ return bytes(buf)
+
+def start_test(ip="127.0.0.1", port=502, devid=3, subdevid=2, chlid=1, CoinID="A001", recipe_path=f"D:\\HHM_test\\A001.xml", backup_dir=f"D:\\HHM_test\\backup"):
+ xml_cmd = build_start_command(devid=devid, subdevid=subdevid, chlid=chlid, CoinID=CoinID, recipe_path=recipe_path, backup_dir=backup_dir)
+ #print(xml_cmd)
+ with socket.create_connection((ip, port), timeout=60) as s:
+ s.sendall(xml_cmd.encode("utf-8"))
+ data = recv_until_marks(s, timeout=60)
+ return data.decode("utf-8", errors="replace")
+
+if __name__ == "__main__":
+ resp = start_test(ip="127.0.0.1", port=502, devid=4, subdevid=10, chlid=1, CoinID="A001", recipe_path=f"D:\\HHM_test\\A001.xml", backup_dir=f"D:\\HHM_test\\backup")
+ print(resp)
diff --git a/unilabos/registry/devices/neware_battery_test_system.yaml b/unilabos/registry/devices/neware_battery_test_system.yaml
index bfcbc43d..2ef8d591 100644
--- a/unilabos/registry/devices/neware_battery_test_system.yaml
+++ b/unilabos/registry/devices/neware_battery_test_system.yaml
@@ -1,73 +1,40 @@
neware_battery_test_system:
category:
- neware_battery_test_system
+ - neware
+ - battery_test
class:
action_value_mappings:
- auto-post_init:
+ debug_resource_names:
feedback: {}
goal: {}
- goal_default:
- ros_node: null
+ goal_default: {}
handles: {}
- placeholder_keys: {}
- result: {}
+ result:
+ return_info: return_info
+ success: success
schema:
- description: ''
+ description: 璋冭瘯鏂规硶锛氭樉绀烘墍鏈夎祫婧愮殑瀹為檯鍚嶇О
properties:
feedback: {}
goal:
+ properties: {}
+ required: []
+ type: object
+ result:
properties:
- ros_node:
+ return_info:
+ description: 璧勬簮璋冭瘯淇℃伅
type: string
+ success:
+ description: 鏄惁鎴愬姛
+ type: boolean
required:
- - ros_node
+ - return_info
+ - success
type: object
- result: {}
required:
- goal
- title: post_init鍙傛暟
- type: object
- type: UniLabJsonCommand
- auto-print_status_summary:
- feedback: {}
- goal: {}
- goal_default: {}
- handles: {}
- placeholder_keys: {}
- result: {}
- schema:
- description: ''
- properties:
- feedback: {}
- goal:
- properties: {}
- required: []
- type: object
- result: {}
- required:
- - goal
- title: print_status_summary鍙傛暟
- type: object
- type: UniLabJsonCommand
- auto-test_connection:
- feedback: {}
- goal: {}
- goal_default: {}
- handles: {}
- placeholder_keys: {}
- result: {}
- schema:
- description: ''
- properties:
- feedback: {}
- goal:
- properties: {}
- required: []
- type: object
- result: {}
- required:
- - goal
- title: test_connection鍙傛暟
type: object
type: UniLabJsonCommand
export_status_json:
@@ -219,7 +186,9 @@ neware_battery_test_system:
goal_default:
string: ''
handles: {}
- result: {}
+ result:
+ return_info: return_info
+ success: success
schema:
description: ''
properties:
@@ -252,6 +221,56 @@ neware_battery_test_system:
title: StrSingleInput
type: object
type: StrSingleInput
+ submit_from_csv:
+ feedback: {}
+ goal:
+ csv_path: string
+ output_dir: string
+ goal_default:
+ csv_path: ''
+ output_dir: .
+ handles: {}
+ result:
+ return_info: return_info
+ submitted_count: submitted_count
+ success: success
+ schema:
+ description: 浠嶤SV鏂囦欢鎵归噺鎻愪氦Neware娴嬭瘯浠诲姟
+ properties:
+ feedback: {}
+ goal:
+ properties:
+ csv_path:
+ description: 杈撳叆CSV鏂囦欢鐨勭粷瀵硅矾寰
+ type: string
+ output_dir:
+ description: 杈撳嚭鐩綍锛堢敤浜庡瓨鍌╔ML鍜屽浠芥枃浠讹級锛岄粯璁ゅ綋鍓嶇洰褰
+ type: string
+ required:
+ - csv_path
+ type: object
+ result:
+ properties:
+ return_info:
+ description: 鎵ц缁撴灉璇︾粏淇℃伅
+ type: string
+ submitted_count:
+ description: 鎴愬姛鎻愪氦鐨勪换鍔℃暟閲
+ type: integer
+ success:
+ description: 鏄惁鎴愬姛
+ type: boolean
+ total_count:
+ description: CSV鏂囦欢涓殑鎬昏鏁
+ type: integer
+ required:
+ - return_info
+ - success
+ type: object
+ required:
+ - goal
+ type: object
+ type: UniLabJsonCommand
test_connection_action:
feedback: {}
goal: {}
@@ -284,7 +303,7 @@ neware_battery_test_system:
- goal
type: object
type: UniLabJsonCommand
- module: unilabos.devices.battery.neware_battery_test_system:NewareBatteryTestSystem
+ module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
status_types:
channel_status: dict
connection_info: dict
@@ -294,31 +313,35 @@ neware_battery_test_system:
total_channels: int
type: python
config_info: []
- description: 鏂板▉鐢垫睜娴嬭瘯绯荤粺椹卞姩锛屾敮鎸720涓氶亾鐨勭數姹犳祴璇曠姸鎬佺洃鎺у拰鏁版嵁瀵煎嚭銆傞氳繃TCP閫氫俊瀹炵幇杩滅▼鎺у埗锛屽寘鍚畬鏁寸殑鐗╂枡绠$悊绯荤粺锛屾敮鎸2鐩樼數姹犵殑鐘舵佹槧灏勫拰鐩戞帶銆
+ description: 鏂板▉鐢垫睜娴嬭瘯绯荤粺椹卞姩锛屾彁渚720涓氶亾鐨勭數姹犳祴璇曠姸鎬佺洃鎺с佺墿鏂欑鐞嗗拰CSV鎵归噺鎻愪氦鍔熻兘銆傛敮鎸乀CP閫氫俊瀹炵幇杩滅▼鎺у埗锛屽寘鍚畬鏁寸殑鐗╂枡绠$悊绯荤粺锛2鐩樼數姹犵姸鎬佹槧灏勶級锛屼互鍙婁粠CSV鏂囦欢鎵归噺鎻愪氦娴嬭瘯浠诲姟鐨勮兘鍔涖
handles: []
icon: ''
init_param_schema:
config:
properties:
devtype:
+ default: '27'
type: string
ip:
+ default: 127.0.0.1
type: string
machine_id:
default: 1
type: integer
port:
+ default: 502
type: integer
size_x:
- default: 500.0
+ default: 50.0
type: number
size_y:
- default: 500.0
+ default: 50.0
type: number
size_z:
- default: 2000.0
+ default: 20.0
type: number
timeout:
+ default: 20
type: integer
required: []
type: object