diff --git a/charging_pile_proxy/commands/__init__.py b/charging_pile_proxy/commands/__init__.py index 232848b..e7e222d 100644 --- a/charging_pile_proxy/commands/__init__.py +++ b/charging_pile_proxy/commands/__init__.py @@ -10,9 +10,10 @@ from commands.command_0A import Command0A from commands.command_25 import Command25 from commands.command_30 import Command30 from commands.command_19_1A import Command191A +from commands.command_1F_20 import Command1F20 from commands.command_21_22 import Command2122 from commands.command_23_24 import Command2324 -#from commands.command_26_27 import Command2627 +from commands.command_26_27 import Command2627 __all__ = ['Command02', 'Command03', 'CommandHeartbeat','Command07','Command08','Command09', - 'Command0A','Command25','Command30','Command191A','Command2122','Command2324'] \ No newline at end of file + 'Command0A','Command25','Command30','Command191A','Command2122','Command2324','Command1F20'] \ No newline at end of file diff --git a/charging_pile_proxy/commands/__pycache__/__init__.cpython-311.pyc b/charging_pile_proxy/commands/__pycache__/__init__.cpython-311.pyc index 1c28149..900d997 100644 Binary files a/charging_pile_proxy/commands/__pycache__/__init__.cpython-311.pyc and b/charging_pile_proxy/commands/__pycache__/__init__.cpython-311.pyc differ diff --git a/charging_pile_proxy/commands/__pycache__/command_1F_20.cpython-311.pyc b/charging_pile_proxy/commands/__pycache__/command_1F_20.cpython-311.pyc new file mode 100644 index 0000000..06aa984 Binary files /dev/null and b/charging_pile_proxy/commands/__pycache__/command_1F_20.cpython-311.pyc differ diff --git a/charging_pile_proxy/commands/__pycache__/command_23_24.cpython-311.pyc b/charging_pile_proxy/commands/__pycache__/command_23_24.cpython-311.pyc index dec89ff..3f7d13f 100644 Binary files a/charging_pile_proxy/commands/__pycache__/command_23_24.cpython-311.pyc and b/charging_pile_proxy/commands/__pycache__/command_23_24.cpython-311.pyc differ diff --git a/charging_pile_proxy/commands/__pycache__/command_25.cpython-311.pyc b/charging_pile_proxy/commands/__pycache__/command_25.cpython-311.pyc index 1d17fb4..5304155 100644 Binary files a/charging_pile_proxy/commands/__pycache__/command_25.cpython-311.pyc and b/charging_pile_proxy/commands/__pycache__/command_25.cpython-311.pyc differ diff --git a/charging_pile_proxy/commands/command_02.py b/charging_pile_proxy/commands/command_02.py index 0cd9b2f..7c2c85b 100644 --- a/charging_pile_proxy/commands/command_02.py +++ b/charging_pile_proxy/commands/command_02.py @@ -46,111 +46,122 @@ class Command02: def validate_frame(self, data): """验证帧格式""" try: - print(f"\n验证帧格式:") - print(f"数据内容: {data.hex()}") + print("\n开始验证帧格式:") + print(f"数据内容: {data.hex().upper()}") print(f"数据长度: {len(data)}字节") - # 1. 基本长度检查 + # 1. 基本格式检查 if len(data) < 14: - print("数据长度不足14字节,无效") + print("数据长度不足14字节") return False - # 2. 检查帧起始标志 if data[0:2] != b'JX': - print("帧起始标志不是'JX',无效") + print("帧起始标志不是'JX'") return False - # 3. 获取并检查数据域长度 + # 2. 获取数据域长度 data_len = struct.unpack(" expected_total_len: + print(f"截断多余数据") + data = data[:expected_total_len] + else: + print("数据不完整") + return False - if len(data) != expected_len: - print("数据总长度不匹配") - return False + # 3. 显示数据结构 + print(f"\n数据结构分析:") + print(f"起始标识: {data[0:2].hex().upper()}") + print(f"命令字: {data[2]:02X}") + print(f"桩号: {data[3:11].hex().upper()}") + print(f"加密方式: {data[11]:02X}") + print(f"数据长度: {data_len}") + print(f"数据域: {data[14:14 + data_len].hex().upper()}") + print(f"校验码: {data[-1]:02X}") - # 5. 验证校验码 - check_data = data[2:-1] # 从命令字节到校验码前的数据 + # 4. 校验码验证 + check_data = data[2:-1] calculated_check = 0 for b in check_data: calculated_check ^= b - received_check = data[-1] - print(f"计算得到的校验码: {calculated_check:02X}") - print(f"接收到的校验码: {received_check:02X}") - - if calculated_check != received_check: - print("校验码不匹配") + if calculated_check != data[-1]: + print(f"校验码不匹配: 计算值={calculated_check:02X}, 接收值={data[-1]:02X}") return False - print("帧格式验证通过") return True except Exception as e: - print(f"帧格式验证出错: {str(e)}") + print(f"验证帧格式异常: {str(e)}") return False + def debug_print_fields(self, data): + """打印数据包的详细字段""" + print("\n===== 数据包解析 =====") + print("1. 固定头部:") + print(f" 起始标识 (2字节): {data[0:2].hex().upper()}") + print(f" 命令码 (1字节): {data[2]:02X}") + print(f" 桩号 (8字节): {data[3:11].hex().upper()}") + print(f" 加密方式 (1字节): {data[11]:02X}") + print(f" 数据域长度 (2字节): {struct.unpack(' expected_len: + print(f"\n警告: 发现多余字节:") + print(f"多余字节内容: {data[expected_len:].hex().upper()}") + def parse_01h(self, data): """解析01H命令数据""" try: print("\n开始解析01H命令...") - if not self.validate_frame(data): raise ValueError("帧格式验证失败") + # 提取基本信息 command = data[2] pile_id = data[3:11] encrypt_mode = data[11] - data_len = struct.unpack(" 600: # 超过10分钟 + logging.warning(f"时间差异过大: {time_diff}秒") + response = self.build_02h_response(pile_id, False, 6) # 拒绝原因6-时差过大 + else: + # 这里可以添加更多的验证逻辑 + response = self.build_02h_response(pile_id, True, 0) + + if response and hasattr(sock, 'send'): sock.send(response) + logging.info(f"成功发送02H响应, 长度: {len(response)}字节") + return True - return True + return False except Exception as e: - logging.error(f"处理和响应失败: {str(e)}") - print(f"处理失败: {str(e)}") + logging.error(f"处理01H命令失败: {str(e)}") return False @@ -243,29 +254,31 @@ def test_command(): """测试函数""" print("开始测试01H/02H命令处理...") - # 配置日志 - # logging.basicConfig( - # filename='command_response_02h.log', - # level=logging.INFO, - # format='%(asctime)s - %(levelname)s - %(message)s', - # encoding='utf-8' - # ) - # 创建响应处理器 handler = Command02() - # 测试数据 - 使用实际收到的数据 - test_data = bytes.fromhex("4A5801031767631136065701100019010909371501000000000000000000004D") + # 原始测试数据 + hex_string = "4A5801031767631136065701100019010909371501000000000000000000004D" + + # 解析数据长度字段以确定正确的数据包长度 + expected_len = 14 + 16 + 1 # 头部(14字节) + 数据域(16字节) + 校验码(1字节) + + # 确保只取需要的字节 + test_data = bytes.fromhex(hex_string[:expected_len * 2]) # *2是因为hex字符串中一个字节用两个字符表示 print("\n测试数据:") - print(f"十六进制: {test_data.hex()}") - print(f"长度: {len(test_data)}字节") + print(f"原始数据: {hex_string}") + print(f"处理后数据: {test_data.hex().upper()}") + print(f"数据长度: {len(test_data)}字节") + + # 打印详细字段解析 + handler.debug_print_fields(test_data) # 创建模拟socket class MockSocket: def send(self, data): print(f"\n模拟发送响应数据:") - print(f"数据内容: {data.hex()}") + print(f"数据内容: {data.hex().upper()}") print(f"数据长度: {len(data)}字节") mock_sock = MockSocket() diff --git a/charging_pile_proxy/core/__pycache__/proxy_server.cpython-311.pyc b/charging_pile_proxy/core/__pycache__/proxy_server.cpython-311.pyc index 95f007d..6d14b28 100644 Binary files a/charging_pile_proxy/core/__pycache__/proxy_server.cpython-311.pyc and b/charging_pile_proxy/core/__pycache__/proxy_server.cpython-311.pyc differ diff --git a/charging_pile_proxy/core/proxy_server.py b/charging_pile_proxy/core/proxy_server.py index 7e17c0f..017d244 100644 --- a/charging_pile_proxy/core/proxy_server.py +++ b/charging_pile_proxy/core/proxy_server.py @@ -1,6 +1,7 @@ import socket import logging import threading +import time from .utils import ProxyUtils from .mqtt_client import MQTTClient from commands.command_heartbeat import CommandHeartbeat @@ -61,8 +62,61 @@ class ChargingPileProxyServer: # 存储心跳信息的字典 self.heartbeat_info = {} + self.heartbeat_thread = threading.Thread(target=self.check_heartbeat_timeout) + self.heartbeat_thread.daemon = True + self.heartbeat_thread.start() + def check_heartbeat_timeout(self): + """心跳超时检测""" + while self.running: + try: + current_time = time.time() + disconnected_piles = [] + for pile_id, last_heartbeat in self.heartbeat_info.items(): + time_diff = current_time - last_heartbeat + + # 如果超过60秒没收到心跳 + if time_diff > 60: + msg = f"充电桩 {pile_id.hex().upper()} 心跳超时 {time_diff:.1f}秒" + logging.warning(msg) + print(msg) + self.mqtt_client.publish_message(msg) + disconnected_piles.append(pile_id) + + # 从心跳信息中移除断开的充电桩 + for pile_id in disconnected_piles: + del self.heartbeat_info[pile_id] + self.handle_pile_disconnect(pile_id) + + # 每5秒检查一次 + time.sleep(5) + + except Exception as e: + logging.error(f"心跳检测错误: {str(e)}") + time.sleep(5) + + def update_heartbeat(self, pile_id): + """更新心跳时间戳""" + try: + self.heartbeat_info[pile_id] = time.time() + logging.debug(f"更新充电桩 {pile_id.hex().upper()} 心跳时间") + except Exception as e: + logging.error(f"更新心跳时间戳失败: {str(e)}") + + def handle_pile_disconnect(self, pile_id): + """处理充电桩断开连接""" + try: + msg = f"充电桩 {pile_id.hex().upper()} 断开连接" + logging.info(msg) + print(msg) + self.mqtt_client.publish_message(msg) + + # 可以在这里添加充电桩断开后的清理逻辑 + # 例如:中断正在进行的充电等 + + except Exception as e: + logging.error(f"处理充电桩断开连接失败: {str(e)}") def start(self): """启动代理服务器""" @@ -139,16 +193,21 @@ class ChargingPileProxyServer: # 根据命令字节处理不同命令 if command == 0x01: # 01H命令 + logging.info(f"收到01H连接请求命令: {data.hex().upper()}") if self.command_02_handler.process_and_respond(data, destination_socket): - continue - + logging.info("01H命令处理完成") + continue # 跳过后续转发,避免重复转发 elif command == 0x03: # 03H命令 - + logging.info(f"收到03H登录命令: {data.hex().upper()}") if self.command_03_handler.process_03h(data): + logging.info("03H命令处理完成") continue elif command == 0x0C: # 0CH桩心跳命令 + # 提取桩号并更新心跳 + pile_id = data[3:11] + self.update_heartbeat(pile_id) logging.info(f"收到0CH心跳命令: {data.hex().upper()}") if self.heartbeat_handler.process_and_respond(data, destination_socket): logging.info("0CH心跳命令处理完成") @@ -160,16 +219,27 @@ class ChargingPileProxyServer: logging.info("19H卡鉴权命令处理完成") continue # 跳过后续转发 + + elif command == 0x1F: # 1FH启动充电命令 + + logging.info(f"收到1FH启动充电命令: {data.hex()}") + + if self.start_charge_handler.process_and_respond(data, destination_socket): + logging.info("1FH命令处理完成") + + continue + + elif command == 0x20: # 20H启动充电回复 + logging.info(f"收到20H启动充电回复: {data.hex()}") + self.start_charge_handler.parse_20h(data) - elif command == 0x21: # 21H启动充电结果命令 - logging.info(f"收到21H启动充电结果命令: {data.hex()}") - if self.charge_result_handler.process_and_respond(data, destination_socket): + logging.info("21H命令处理完成") continue # 跳过后续转发 elif command == 0x23: # 23H充电订单命令 @@ -179,6 +249,7 @@ class ChargingPileProxyServer: continue + elif command == 0x25: # 25H充电信息命令 logging.info(f"收到25H充电信息命令: {data.hex()}") @@ -186,13 +257,19 @@ class ChargingPileProxyServer: if self.charge_info_handler.process_25h(data): logging.info("25H命令处理完成") + elif command == 0x26: # 26H停止充电命令 + logging.info(f"收到26H停止充电命令: {data.hex()}") + if self.stop_charge_handler.build_26h_command(data, destination_socket): + logging.info("26H命令处理完成") + continue + elif command == 0x27: # 27H停止充电回复 logging.info(f"收到27H停止充电回复: {data.hex()}") self.stop_charge_handler.parse_27h(data) elif command == 0x30: # 30H BMS信息命令 logging.info(f"收到30H BMS信息命令: {data.hex().upper()}") - if self.bms_handler.process_30h(data): + if self.bms_handler.parse_30h_bms_status(data): logging.info("30H BMS信息命令处理完成") else: diff --git a/charging_pile_proxy/test.log b/charging_pile_proxy/test.log index 400bb1d..88c8f52 100644 --- a/charging_pile_proxy/test.log +++ b/charging_pile_proxy/test.log @@ -4,3 +4,4 @@ 2025-01-17 14:41:09,050 - ERROR - 代理服务器错误: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。 2025-01-17 14:58:36,033 - ERROR - 代理服务器错误: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。 2025-01-17 17:12:27,913 - ERROR - 代理服务器错误: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。 +2025-01-20 09:45:44,376 - INFO - 代理服务器已启动,监听地址: 0.0.0.0:52461