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