no message
This commit is contained in:
parent
4304015dd6
commit
e8d33efc42
@ -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']
|
||||
'Command0A','Command25','Command30','Command191A','Command2122','Command2324','Command1F20']
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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("<H", data[12:14])[0]
|
||||
print(f"数据域长度字段值: {data_len}")
|
||||
expected_total_len = 14 + data_len + 1 # 头部(14) + 数据域 + 校验码(1)
|
||||
|
||||
# 4. 计算并检查总长度
|
||||
expected_len = 16 + data_len # 固定部分(14) + 数据域 + 校验码(1)
|
||||
print(f"期望总长度: {expected_len}")
|
||||
print(f"实际长度: {len(data)}")
|
||||
# 如果数据长度不匹配,尝试修正数据
|
||||
if len(data) != expected_total_len:
|
||||
print(f"数据长度不匹配,尝试修正...")
|
||||
if len(data) > 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('<H', data[12:14])[0]}")
|
||||
|
||||
data_len = struct.unpack("<H", data[12:14])[0]
|
||||
print("\n2. 数据域:")
|
||||
print(f" 时间标识 (6字节): {data[14:20].hex().upper()}")
|
||||
print(f" 密钥版本 (2字节): {data[20:22].hex().upper()}")
|
||||
print(f" 校验密文 (8字节): {data[22:30].hex().upper()}")
|
||||
|
||||
print(f"\n3. 校验码 (1字节): {data[-1]:02X}")
|
||||
|
||||
# 检查是否有多余字节
|
||||
expected_len = 14 + data_len + 1
|
||||
if len(data) > 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("<H", data[12:14])[0]
|
||||
data_field = data[14:14 + data_len]
|
||||
data_field = data[14:-1] # 从数据域开始到校验码之前
|
||||
|
||||
# 解析时间标识
|
||||
# 解析数据域内容
|
||||
time_bytes = data_field[0:6]
|
||||
year = time_bytes[0] + 2000
|
||||
month, day, hour, minute, second = time_bytes[1:6]
|
||||
timestamp = f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
|
||||
|
||||
# 解析密钥版本和校验密文
|
||||
key_version = struct.unpack("<H", data_field[6:8])[0]
|
||||
check_text = data_field[8:16]
|
||||
|
||||
# 解析桩号
|
||||
pile_info = self.parse_pile_id(pile_id)
|
||||
|
||||
result = {
|
||||
"command": command,
|
||||
"pile_id": pile_id,
|
||||
"pile_info": pile_info,
|
||||
"encrypt_mode": encrypt_mode,
|
||||
"timestamp": timestamp,
|
||||
"key_version": key_version,
|
||||
"check_text": check_text
|
||||
}
|
||||
|
||||
print("\n解析结果:")
|
||||
print(f"命令码: {command:02X}")
|
||||
print(f"桩号信息: {pile_info}")
|
||||
print(f"加密方式: {encrypt_mode:02X}")
|
||||
print(f"时间标识: {timestamp}")
|
||||
print(f"密钥版本: {key_version}")
|
||||
print(f"校验密文: {check_text.hex()}")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"解析01H命令失败: {str(e)}")
|
||||
print(f"解析失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def build_02h_response(self, pile_id, allow=True, reject_reason=0):
|
||||
"""构建02H响应命令"""
|
||||
try:
|
||||
print("\n构建02H响应...")
|
||||
|
||||
frame = bytearray()
|
||||
frame.extend(b'JX') # 帧起始标志
|
||||
frame.append(self.command) # 命令
|
||||
@ -159,83 +170,83 @@ class Command02:
|
||||
|
||||
data = bytearray()
|
||||
|
||||
# 时间标识
|
||||
# 添加时间标识 (6字节)
|
||||
now = datetime.now()
|
||||
data.extend(struct.pack("<BBBBBB",
|
||||
now.year - 2000, now.month, now.day,
|
||||
now.hour, now.minute, now.second))
|
||||
|
||||
# 请求结果
|
||||
# 添加请求结果和拒绝原因
|
||||
data.append(0x01 if allow else 0x02)
|
||||
|
||||
# 拒绝原因
|
||||
data.append(reject_reason)
|
||||
|
||||
if allow:
|
||||
# 二维码固定段
|
||||
data.extend(self.qr_fixed.ljust(100, '\x00').encode())
|
||||
# 添加二维码固定段 (100字节)
|
||||
fixed_part = self.qr_fixed.encode()
|
||||
data.extend(fixed_part.ljust(100, b'\x00'))
|
||||
|
||||
# 二维码枪号段数量
|
||||
data.append(0x02)
|
||||
# 添加二维码枪号段数量
|
||||
data.append(0x02) # 2个充电枪
|
||||
|
||||
# 二维码枪号段1和2
|
||||
# 添加枪号段 (每个20字节)
|
||||
pile_id_str = ''.join([f"{b:02X}" for b in pile_id])
|
||||
gun1 = f"{pile_id_str}001"
|
||||
gun2 = f"{pile_id_str}002"
|
||||
data.extend(gun1.encode().ljust(20, b'\x00'))
|
||||
data.extend(gun2.encode().ljust(20, b'\x00'))
|
||||
|
||||
data.extend(gun1.ljust(20, '\x00').encode())
|
||||
data.extend(gun2.ljust(20, '\x00').encode())
|
||||
|
||||
# 数据域长度
|
||||
# 添加数据域长度
|
||||
frame.extend(struct.pack("<H", len(data)))
|
||||
|
||||
# 数据域
|
||||
# 添加数据域
|
||||
frame.extend(data)
|
||||
|
||||
# 计算校验码
|
||||
# 计算并添加校验码
|
||||
check = 0
|
||||
for b in frame[2:]:
|
||||
for b in frame[2:]: # 从命令字节开始异或
|
||||
check ^= b
|
||||
frame.append(check)
|
||||
|
||||
print("响应数据构建成功:")
|
||||
print(f"数据内容: {frame.hex()}")
|
||||
print(f"数据长度: {len(frame)}字节")
|
||||
|
||||
return bytes(frame)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"构建02H响应失败: {str(e)}")
|
||||
print(f"构建响应失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def process_and_respond(self, received_data, sock):
|
||||
"""处理收到的01H命令并回复02H"""
|
||||
try:
|
||||
print("\n处理01H命令并生成响应...")
|
||||
|
||||
# 解析收到的01H命令
|
||||
parsed = self.parse_01h(received_data)
|
||||
if not parsed:
|
||||
# 基础验证
|
||||
if not self.validate_frame(received_data):
|
||||
logging.error("01H命令帧格式验证失败")
|
||||
return False
|
||||
|
||||
# 构建02H响应
|
||||
allow = True # 这里可以根据业务逻辑判断是否允许连接
|
||||
reject_reason = 0
|
||||
# 提取必要信息
|
||||
pile_id = received_data[3:11] # 桩号
|
||||
time_bytes = received_data[14:20] # 时间标识
|
||||
key_version = struct.unpack("<H", received_data[20:22])[0] # 密钥版本
|
||||
|
||||
response = self.build_02h_response(parsed["pile_id"], allow, reject_reason)
|
||||
if not response:
|
||||
return False
|
||||
# 检查时间是否在合理范围内
|
||||
cmd_time = datetime(2000 + time_bytes[0], time_bytes[1], time_bytes[2],
|
||||
time_bytes[3], time_bytes[4], time_bytes[5])
|
||||
time_diff = abs((datetime.now() - cmd_time).total_seconds())
|
||||
|
||||
# 发送响应
|
||||
if hasattr(sock, 'send'):
|
||||
if time_diff > 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()
|
||||
|
Binary file not shown.
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user