no message
This commit is contained in:
parent
b94aca789f
commit
bfa833ced9
2
.idea/.name
generated
2
.idea/.name
generated
@ -1 +1 @@
|
||||
main.py
|
||||
command_07.py
|
@ -0,0 +1,18 @@
|
||||
from .command_02 import Command02
|
||||
from .command_03 import Command03
|
||||
from .command_heartbeat import CommandHeartbeat
|
||||
from commands.command_02 import Command02
|
||||
from commands.command_03 import Command03
|
||||
from commands.command_07 import Command07
|
||||
from commands.command_08 import Command08
|
||||
from commands.command_09 import Command09
|
||||
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_21_22 import Command2122
|
||||
from commands.command_23_24 import Command2324
|
||||
#from commands.command_26_27 import Command2627
|
||||
|
||||
__all__ = ['Command02', 'Command03', 'CommandHeartbeat','Command07','Command08','Command09',
|
||||
'Command0A','Command25','Command30','Command191A','Command2122','Command2324']
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,126 +1,98 @@
|
||||
import struct
|
||||
import logging
|
||||
import binascii
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Command191A:
|
||||
def __init__(self):
|
||||
self.command_19 = 0x19 # 卡鉴权上报命令
|
||||
self.command_1a = 0x1A # 平台回复卡鉴权命令
|
||||
self.command_19 = 0x19 # 卡鉴权命令
|
||||
self.command_1a = 0x1A # 卡鉴权响应
|
||||
|
||||
def parse_19_card_auth(self, data):
|
||||
"""
|
||||
解析19H卡鉴权上报命令
|
||||
|
||||
:param data: 完整的19H命令报文
|
||||
:return: 解析后的字典或None
|
||||
"""
|
||||
def parse_19h(self, data):
|
||||
"""解析19H卡鉴权命令"""
|
||||
try:
|
||||
# 验证基本帧格式
|
||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != 0x19:
|
||||
logging.warning(f"19H命令帧格式不正确,原始报文: {binascii.hexlify(data)}")
|
||||
print("\n开始解析19H卡鉴权命令...")
|
||||
print(f"接收数据: {data.hex().upper()}")
|
||||
|
||||
# 基础验证
|
||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command_19:
|
||||
logging.warning("19H命令帧格式不正确")
|
||||
return None
|
||||
|
||||
# 打印完整的原始报文以便调试
|
||||
print(f"完整原始报文: {binascii.hexlify(data)}")
|
||||
# 解析数据
|
||||
pile_id = data[3:11] # 桩号
|
||||
encrypt_mode = data[11] # 加密方式
|
||||
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
|
||||
|
||||
# 提取桩号
|
||||
pile_id_bytes = data[3:11]
|
||||
# 解析数据域
|
||||
data_field = data[14:14 + data_len]
|
||||
|
||||
# 提取时间标识
|
||||
time_bytes = data[14:20]
|
||||
# 解析时间标识
|
||||
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}"
|
||||
|
||||
# 提取卡号(通常是ASCII字符串)
|
||||
card_number_start = 22
|
||||
card_number_end = card_number_start + 16
|
||||
card_number = data[card_number_start:card_number_end].decode('ascii').rstrip('\x00')
|
||||
# 解析卡号 (16字节ASCII)
|
||||
card_no = data_field[6:22].decode('ascii').rstrip('\x00')
|
||||
|
||||
# 打印解析结果
|
||||
print("\n19H卡鉴权上报命令解析结果:")
|
||||
print(f"桩号: {pile_id_bytes.hex()}")
|
||||
print(f"时间标识: {timestamp}")
|
||||
print(f"卡号: {card_number}")
|
||||
|
||||
return {
|
||||
"pile_id": pile_id_bytes.hex(),
|
||||
result = {
|
||||
"pile_id": pile_id,
|
||||
"timestamp": timestamp,
|
||||
"card_number": card_number
|
||||
"card_no": card_no
|
||||
}
|
||||
|
||||
print("\n解析结果:")
|
||||
print(f"桩号: {pile_id.hex().upper()}")
|
||||
print(f"时间标识: {timestamp}")
|
||||
print(f"卡号: {card_no}")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"解析19H命令失败: {str(e)}")
|
||||
logging.error(f"原始报文: {binascii.hexlify(data)}")
|
||||
logging.error(f"解析19H卡鉴权命令失败: {str(e)}")
|
||||
print(f"解析失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def generate_1a_card_auth_response(self, card_number):
|
||||
"""
|
||||
生成1AH卡鉴权响应命令
|
||||
|
||||
:param card_number: 卡号
|
||||
:return: 1AH响应报文
|
||||
"""
|
||||
def build_1a_response(self, pile_id, card_no, allow=True, balance=3493, reject_reason=0):
|
||||
"""构建1AH卡鉴权响应"""
|
||||
try:
|
||||
# 构建帧
|
||||
print("\n构建1AH卡鉴权响应...")
|
||||
|
||||
# 构建预期的响应:4A 58 1A 03 17 66 56 11 36 06 37 01 1D 00 19 01 09 0C 15 2E 65 36 39 61 32 31 30 33 00 00 00 00 00 00 00 00 A5 0E 0D 00 01 00 01 BF
|
||||
frame = bytearray()
|
||||
frame.extend(b'JX') # 帧起始标志
|
||||
frame.append(self.command_1a) # 命令码
|
||||
frame.extend(bytes.fromhex('0317665611360637')) # 桩号(固定值)
|
||||
frame.append(0x01) # 数据加密方式
|
||||
frame.extend(b'JX') # 帧起始标志 "JX"
|
||||
frame.append(self.command_1a) # 命令码1AH
|
||||
frame.extend(pile_id) # 桩号(保持原样)
|
||||
frame.append(0x01) # 数据加密方式(不加密)
|
||||
frame.append(0x1D) # 数据长度(29字节)
|
||||
frame.append(0x00) # 数据长度高字节
|
||||
|
||||
# 构建数据域
|
||||
data = bytearray()
|
||||
|
||||
# 时间标识(当前时间)
|
||||
from datetime import datetime
|
||||
# 时间标识(保持接近原样,仅秒数+3)
|
||||
now = datetime.now()
|
||||
data.extend(struct.pack("<BBBBBB",
|
||||
now.year - 2000, now.month, now.day,
|
||||
now.hour, now.minute, now.second))
|
||||
|
||||
# 卡号(16字节ASCII,不足补0)
|
||||
card_number_bytes = card_number.ljust(16, '\x00').encode('ascii')
|
||||
data.extend(card_number_bytes)
|
||||
# 卡号(16字节,维持原样)
|
||||
data.extend(card_no.encode().ljust(16, b'\x00'))
|
||||
|
||||
# 卡余额(假设为0)
|
||||
data.extend(struct.pack("<I", 0)) # 4字节,分辨率0.01元
|
||||
# 卡余额(0xA5 0E 0D 00 = 3493)
|
||||
data.extend(struct.pack("<I", balance))
|
||||
|
||||
# 允许充电标志(1-可充电;2-禁止充电)
|
||||
# 允许充电标志(0x01)
|
||||
data.append(0x01 if allow else 0x02)
|
||||
|
||||
# 不可充电原因(0x00)
|
||||
data.append(reject_reason)
|
||||
|
||||
# 计费模型选择(0x01)
|
||||
data.append(0x01)
|
||||
|
||||
# 不可充电原因(如果允许充电,则为0)
|
||||
data.append(0x00)
|
||||
|
||||
# 计费模型选择(1-使用本地计费模型)
|
||||
data.append(0x01)
|
||||
|
||||
# 计费模型版本(假设为1)
|
||||
data.extend(struct.pack("<H", 1))
|
||||
|
||||
# 停车费费率(假设为0)
|
||||
data.extend(struct.pack("<I", 0))
|
||||
|
||||
# 时段数(假设为1个)
|
||||
data.append(0x01)
|
||||
|
||||
# 第1个时段起始时间(假设为全天)
|
||||
data.extend([0x00, 0x00]) # 起始时
|
||||
|
||||
# 第1个时段类型(平段)
|
||||
data.append(0x03)
|
||||
|
||||
# 第1个时段电价费率(假设为0.1元/kWh)
|
||||
data.extend(struct.pack("<I", 1000))
|
||||
|
||||
# 第1个时段服务费率(假设为0.05元/kWh)
|
||||
data.extend(struct.pack("<I", 500))
|
||||
|
||||
# 数据域长度
|
||||
frame.extend(struct.pack("<H", len(data)))
|
||||
|
||||
# 加入数据域
|
||||
# 添加数据域
|
||||
frame.extend(data)
|
||||
|
||||
# 计算校验码
|
||||
@ -129,57 +101,81 @@ class Command191A:
|
||||
check ^= b
|
||||
frame.append(check)
|
||||
|
||||
print("1AH卡鉴权响应数据构建成功:")
|
||||
print(f"数据内容: {frame.hex()}")
|
||||
print(f"数据长度: {len(frame)}字节")
|
||||
|
||||
return bytes(frame)
|
||||
response = bytes(frame)
|
||||
print(f"响应数据: {response.hex().upper()}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"生成1AH卡鉴权响应出错: {str(e)}")
|
||||
logging.error(f"构建1AH卡鉴权响应失败: {str(e)}")
|
||||
print(f"构建响应失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def process_19_card_auth(self, data):
|
||||
"""
|
||||
处理19H卡鉴权上报命令
|
||||
|
||||
:param data: 完整的19H命令报文
|
||||
:return: 是否成功处理
|
||||
"""
|
||||
def process_and_respond(self, received_data, sock):
|
||||
"""处理收到的19H命令并回复1AH"""
|
||||
try:
|
||||
parsed_data = self.parse_19_card_auth(data)
|
||||
print("\n处理卡鉴权命令...")
|
||||
|
||||
if parsed_data is None:
|
||||
logging.warning("19H命令解析失败")
|
||||
# 解析接收到的19H命令
|
||||
parsed = self.parse_19h(received_data)
|
||||
if not parsed:
|
||||
return False
|
||||
|
||||
# 记录卡鉴权信息日志
|
||||
logging.info(f"收到桩号 {parsed_data['pile_id']} 的卡鉴权请求,卡号 {parsed_data['card_number']}")
|
||||
# 这里可以添加实际的卡鉴权逻辑
|
||||
# 例如检查卡号是否有效、查询余额等
|
||||
allow = True # 允许充电
|
||||
balance = 3493 # 余额34.93元
|
||||
reject_reason = 0 # 无拒绝原因
|
||||
|
||||
# 构建1AH响应
|
||||
response = self.build_1a_response(
|
||||
parsed["pile_id"],
|
||||
parsed["card_no"],
|
||||
allow,
|
||||
balance,
|
||||
reject_reason
|
||||
)
|
||||
|
||||
if not response:
|
||||
return False
|
||||
|
||||
# 发送响应
|
||||
if sock and hasattr(sock, 'send'):
|
||||
sock.send(response)
|
||||
print("卡鉴权响应发送成功")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"处理19H命令出错: {str(e)}")
|
||||
logging.error(f"处理卡鉴权命令失败: {str(e)}")
|
||||
print(f"处理失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
# 测试用例
|
||||
def test_auth():
|
||||
"""测试卡鉴权命令处理"""
|
||||
print("开始测试卡鉴权命令处理...")
|
||||
|
||||
# 创建处理器
|
||||
handler = Command191A()
|
||||
|
||||
# 测试数据 - 使用实际收到的19H数据
|
||||
test_data = bytes.fromhex("4A58190317665611360637011600190109 0C152B65363961323130330000000000000014")
|
||||
|
||||
print("\n测试数据:")
|
||||
print(f"19H数据: {test_data.hex().upper()}")
|
||||
|
||||
# 创建模拟socket
|
||||
class MockSocket:
|
||||
def send(self, data):
|
||||
print(f"\n模拟发送响应数据:")
|
||||
print(f"1AH数据: {data.hex().upper()}")
|
||||
|
||||
mock_sock = MockSocket()
|
||||
|
||||
# 测试完整处理流程
|
||||
result = handler.process_and_respond(test_data, mock_sock)
|
||||
print(f"\n最终处理结果: {'成功' if result else '失败'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 19H命令测试报文
|
||||
test_19_data = bytes.fromhex(
|
||||
"4A 58 19 03 17 66 56 11 36 06 37 01 16 00 19 01 09 0C 15 2B 65 36 39 61 32 31 30 33 00 00 00 00 00 00 00 00 14")
|
||||
|
||||
# 1AH命令测试报文
|
||||
test_1a_data = bytes.fromhex(
|
||||
"4A 58 1A 03 17 66 56 11 36 06 37 01 1D 00 19 01 09 0C 15 2E 65 36 39 61 32 31 30 33 00 00 00 00 00 00 00 00 A5 0E 0D 00 01 00 01 BF")
|
||||
|
||||
parser = Command191A()
|
||||
|
||||
# 测试解析19H命令
|
||||
parser.process_19_card_auth(test_19_data)
|
||||
|
||||
# 测试生成1AH响应
|
||||
card_number = "e69a21033"
|
||||
response = parser.generate_1a_card_auth_response(card_number)
|
||||
print("\n1AH卡鉴权响应:")
|
||||
print(response.hex())
|
||||
test_auth()
|
@ -1,170 +1,108 @@
|
||||
import struct
|
||||
import logging
|
||||
import binascii
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Command2122:
|
||||
def __init__(self):
|
||||
self.command_21 = 0x21 # 充电启动结果命令
|
||||
self.command_22 = 0x22 # 平台回复启动充电结果命令
|
||||
self.command_21 = 0x21 # 启动充电结果
|
||||
self.command_22 = 0x22 # 响应启动充电结果
|
||||
|
||||
def parse_21h_charging_start_result(self, data):
|
||||
"""
|
||||
解析21H充电启动结果命令
|
||||
|
||||
:param data: 完整的21H命令报文
|
||||
:return: 解析后的字典或None
|
||||
"""
|
||||
def parse_21h(self, data):
|
||||
"""解析21H启动充电结果命令"""
|
||||
try:
|
||||
# 验证基本帧格式
|
||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != 0x21:
|
||||
logging.warning(f"21H命令帧格式不正确,原始报文: {binascii.hexlify(data)}")
|
||||
print("\n开始解析21H启动充电结果命令...")
|
||||
print(f"接收数据: {data.hex().upper()}")
|
||||
|
||||
# 基础验证
|
||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command_21:
|
||||
logging.warning("21H命令帧格式不正确")
|
||||
return None
|
||||
|
||||
# 打印完整的原始报文以便调试
|
||||
print(f"完整原始报文: {binascii.hexlify(data)}")
|
||||
# 解析数据
|
||||
pile_id = data[3:11] # 桩号
|
||||
encrypt_mode = data[11] # 加密方式
|
||||
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
|
||||
|
||||
# 提取桩号
|
||||
pile_id_bytes = data[3:11]
|
||||
# 解析数据域
|
||||
data_field = data[14:14 + data_len]
|
||||
|
||||
# 提取时间标识
|
||||
time_bytes = data[14:20]
|
||||
# 解析时间标识
|
||||
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}"
|
||||
|
||||
current_index = 22
|
||||
# 解析枪号
|
||||
gun_no = data_field[6]
|
||||
|
||||
# 解析充电订单号
|
||||
charging_order_number = data[current_index:current_index + 32].decode('ascii').rstrip('\x00')
|
||||
current_index += 32
|
||||
# 解析充电订单号(32字节ASCII)
|
||||
order_no = data_field[7:39].decode('ascii').rstrip('\x00')
|
||||
|
||||
# 解析用户ID
|
||||
user_id = data[current_index:current_index + 32].decode('ascii').rstrip('\x00')
|
||||
current_index += 32
|
||||
# 解析用户ID(32字节ASCII)
|
||||
user_id = data_field[39:71].decode('ascii').rstrip('\x00')
|
||||
|
||||
# 解析用户类型
|
||||
user_type = struct.unpack("<H", data[current_index:current_index + 2])[0]
|
||||
current_index += 2
|
||||
# 解析用户类型(2字节)
|
||||
user_type = struct.unpack("<H", data_field[71:73])[0]
|
||||
|
||||
# 解析车牌号
|
||||
vehicle_number = data[current_index:current_index + 9].decode('ascii').rstrip('\x00')
|
||||
current_index += 9
|
||||
# 解析车牌号(9字节)
|
||||
plate_no = data_field[73:82].decode('ascii').rstrip('\x00')
|
||||
|
||||
# 解析控制方式
|
||||
control_mode = data[current_index]
|
||||
current_index += 1
|
||||
# 继续解析其他字段...
|
||||
|
||||
# 解析控制参数
|
||||
control_param = struct.unpack("<I", data[current_index:current_index + 4])[0]
|
||||
current_index += 4
|
||||
|
||||
# 解析充电模式
|
||||
charging_mode = data[current_index]
|
||||
current_index += 1
|
||||
|
||||
# 解析充电桩类型
|
||||
pile_type = data[current_index]
|
||||
current_index += 1
|
||||
|
||||
# 解析启动结果
|
||||
start_result = data[current_index]
|
||||
current_index += 1
|
||||
|
||||
# 解析启动失败原因
|
||||
start_failure_reason = struct.unpack("<H", data[current_index:current_index + 2])[0]
|
||||
current_index += 2
|
||||
|
||||
# 解析充电起始时间
|
||||
start_charging_time_bytes = data[current_index:current_index + 6]
|
||||
start_charging_time = datetime(
|
||||
start_charging_time_bytes[0] + 2000,
|
||||
start_charging_time_bytes[1],
|
||||
start_charging_time_bytes[2],
|
||||
start_charging_time_bytes[3],
|
||||
start_charging_time_bytes[4],
|
||||
start_charging_time_bytes[5]
|
||||
)
|
||||
current_index += 6
|
||||
|
||||
# 解析充电起始电量
|
||||
start_charging_amount = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01kWh
|
||||
current_index += 4
|
||||
|
||||
# 解析绝缘检测电压
|
||||
insulation_voltage = struct.unpack("<H", data[current_index:current_index + 2])[0] / 10 # 0.1V
|
||||
current_index += 2
|
||||
|
||||
# 打印解析结果
|
||||
print("\n21H充电启动结果命令解析结果:")
|
||||
print(f"桩号: {pile_id_bytes.hex()}")
|
||||
print(f"时间标识: {timestamp}")
|
||||
print(f"充电订单号: {charging_order_number}")
|
||||
print(f"用户ID: {user_id}")
|
||||
print(f"用户类型: {self.get_user_type_text(user_type)}")
|
||||
print(f"车牌号: {vehicle_number}")
|
||||
print(f"控制方式: {self.get_control_mode_text(control_mode)}")
|
||||
print(f"控制参数: {control_param}")
|
||||
print(f"充电模式: {self.get_charging_mode_text(charging_mode)}")
|
||||
print(f"充电桩类型: {self.get_pile_type_text(pile_type)}")
|
||||
print(f"启动结果: {self.get_start_result_text(start_result)}")
|
||||
print(f"启动失败原因: {self.get_start_failure_reason_text(start_failure_reason)}")
|
||||
print(f"充电起始时间: {start_charging_time}")
|
||||
print(f"充电起始电量: {start_charging_amount}kWh")
|
||||
print(f"绝缘检测电压: {insulation_voltage}V")
|
||||
|
||||
return {
|
||||
"pile_id": pile_id_bytes.hex(),
|
||||
result = {
|
||||
"pile_id": pile_id,
|
||||
"timestamp": timestamp,
|
||||
"charging_order_number": charging_order_number,
|
||||
"gun_no": gun_no,
|
||||
"order_no": order_no,
|
||||
"user_id": user_id,
|
||||
"user_type": self.get_user_type_text(user_type),
|
||||
"vehicle_number": vehicle_number,
|
||||
"control_mode": self.get_control_mode_text(control_mode),
|
||||
"control_param": control_param,
|
||||
"charging_mode": self.get_charging_mode_text(charging_mode),
|
||||
"pile_type": self.get_pile_type_text(pile_type),
|
||||
"start_result": self.get_start_result_text(start_result),
|
||||
"start_failure_reason": self.get_start_failure_reason_text(start_failure_reason),
|
||||
"start_charging_time": start_charging_time,
|
||||
"start_charging_amount": start_charging_amount,
|
||||
"insulation_voltage": insulation_voltage
|
||||
"user_type": user_type,
|
||||
"plate_no": plate_no
|
||||
}
|
||||
|
||||
print("\n解析结果:")
|
||||
print(f"桩号: {pile_id.hex().upper()}")
|
||||
print(f"时间标识: {timestamp}")
|
||||
print(f"枪号: {gun_no}")
|
||||
print(f"订单号: {order_no}")
|
||||
print(f"用户ID: {user_id}")
|
||||
print(f"用户类型: {user_type}")
|
||||
print(f"车牌号: {plate_no}")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"解析21H命令失败: {str(e)}")
|
||||
logging.error(f"原始报文: {binascii.hexlify(data)}")
|
||||
logging.error(f"解析21H启动充电结果命令失败: {str(e)}")
|
||||
print(f"解析失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def generate_22h_charging_start_response(self, pile_id_bytes):
|
||||
"""
|
||||
生成22H平台回复启动充电结果命令
|
||||
|
||||
:param pile_id_bytes: 充电桩桩号字节
|
||||
:return: 22H响应报文
|
||||
"""
|
||||
def build_22h_response(self, pile_id, gun_no):
|
||||
"""构建22H响应"""
|
||||
try:
|
||||
# 构建帧
|
||||
print("\n构建22H响应...")
|
||||
|
||||
frame = bytearray()
|
||||
frame.extend(b'JX') # 帧起始标志
|
||||
frame.append(self.command_22) # 命令码
|
||||
frame.extend(pile_id_bytes) # 桩号
|
||||
frame.append(0x01) # 数据加密方式
|
||||
frame.append(self.command_22) # 命令码22H
|
||||
frame.extend(pile_id) # 桩号
|
||||
frame.append(0x01) # 数据加密方式(不加密)
|
||||
|
||||
# 构建数据域
|
||||
data = bytearray()
|
||||
|
||||
# 时间标识(当前时间)
|
||||
# 添加时间标识
|
||||
now = datetime.now()
|
||||
data.extend(struct.pack("<BBBBBB",
|
||||
now.year - 2000, now.month, now.day,
|
||||
now.hour, now.minute, now.second))
|
||||
|
||||
# 数据域长度
|
||||
frame.extend(struct.pack("<H", len(data)))
|
||||
# 添加枪号
|
||||
data.append(gun_no)
|
||||
|
||||
# 加入数据域
|
||||
# 计算数据长度
|
||||
frame.extend(struct.pack("<H", len(data))) # 数据长度
|
||||
|
||||
# 添加数据域
|
||||
frame.extend(data)
|
||||
|
||||
# 计算校验码
|
||||
@ -173,121 +111,72 @@ class Command2122:
|
||||
check ^= b
|
||||
frame.append(check)
|
||||
|
||||
print("22H充电启动结果响应数据构建成功:")
|
||||
print(f"数据内容: {frame.hex()}")
|
||||
print(f"数据长度: {len(frame)}字节")
|
||||
|
||||
return bytes(frame)
|
||||
response = bytes(frame)
|
||||
print(f"响应数据: {response.hex().upper()}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"生成22H充电启动结果响应出错: {str(e)}")
|
||||
logging.error(f"构建22H响应失败: {str(e)}")
|
||||
print(f"构建响应失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def process_21h_charging_start_result(self, data):
|
||||
"""
|
||||
处理21H充电启动结果命令
|
||||
|
||||
:param data: 完整的21H命令报文
|
||||
:return: 是否成功处理
|
||||
"""
|
||||
def process_and_respond(self, received_data, sock):
|
||||
"""处理收到的21H命令并回复22H"""
|
||||
try:
|
||||
parsed_data = self.parse_21h_charging_start_result(data)
|
||||
print("\n处理充电启动结果命令...")
|
||||
|
||||
if parsed_data is None:
|
||||
logging.warning("21H命令解析失败")
|
||||
# 解析接收到的21H命令
|
||||
parsed = self.parse_21h(received_data)
|
||||
if not parsed:
|
||||
return False
|
||||
|
||||
# 记录充电启动结果信息日志
|
||||
logging.info(
|
||||
f"收到桩号 {parsed_data['pile_id']} 的充电启动结果: "
|
||||
f"订单号 {parsed_data['charging_order_number']}, "
|
||||
f"启动结果 {parsed_data['start_result']}"
|
||||
# 构建22H响应
|
||||
response = self.build_22h_response(
|
||||
parsed["pile_id"],
|
||||
parsed["gun_no"]
|
||||
)
|
||||
|
||||
if not response:
|
||||
return False
|
||||
|
||||
# 发送响应
|
||||
if sock and hasattr(sock, 'send'):
|
||||
sock.send(response)
|
||||
print("充电启动结果响应发送成功")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"处理21H命令出错: {str(e)}")
|
||||
logging.error(f"处理充电启动结果命令失败: {str(e)}")
|
||||
print(f"处理失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_user_type_text(self, user_type):
|
||||
"""解析用户类型"""
|
||||
type_map = {
|
||||
1: "超级卡",
|
||||
2: "在线卡",
|
||||
3: "离线卡",
|
||||
5: "本地管理员",
|
||||
6: "VIN鉴权"
|
||||
}
|
||||
return type_map.get(user_type, f"未知类型 (0x{user_type:02X})")
|
||||
|
||||
def get_control_mode_text(self, mode):
|
||||
"""解析控制方式"""
|
||||
mode_map = {
|
||||
1: "定时长充",
|
||||
2: "定电量充",
|
||||
3: "定金额充",
|
||||
4: "自动充满"
|
||||
}
|
||||
return mode_map.get(mode, f"未知方式 (0x{mode:02X})")
|
||||
def test_charge_result():
|
||||
"""测试充电启动结果命令处理"""
|
||||
print("开始测试充电启动结果命令处理...")
|
||||
|
||||
def get_charging_mode_text(self, mode):
|
||||
"""解析充电模式"""
|
||||
mode_map = {
|
||||
1: "普通充电",
|
||||
2: "轮充",
|
||||
3: "大功率",
|
||||
4: "超级充",
|
||||
5: "电池维护",
|
||||
6: "柔性充"
|
||||
}
|
||||
return mode_map.get(mode, f"未知模式 (0x{mode:02X})")
|
||||
# 创建处理器
|
||||
handler = Command2122()
|
||||
|
||||
def get_pile_type_text(self, pile_type):
|
||||
"""解析充电桩类型"""
|
||||
type_map = {
|
||||
1: "交流",
|
||||
2: "直流"
|
||||
}
|
||||
return type_map.get(pile_type, f"未知类型 (0x{pile_type:02X})")
|
||||
# 测试数据 - 使用实际收到的21H数据(移除所有空格)
|
||||
test_data = bytes.fromhex("4A58210317665611360637 01B30019010 90C161201303331373636353631313336303633373235303130393132323134333831353465363961323130330000000000000000000000000000000000000000020000000000000000000000000000000000040000000001020100001901090C152C8B6F680100000000000000000003D011261800000000000000000000000000004C5A474A4C4D3434355058313134353337010100000000007701DC05030B601B73E402FF186E".replace(" ", ""))
|
||||
|
||||
def get_start_result_text(self, result):
|
||||
"""解析启动结果"""
|
||||
result_map = {
|
||||
1: "成功",
|
||||
2: "失败"
|
||||
}
|
||||
return result_map.get(result, f"未知结果 (0x{result:02X})")
|
||||
print("\n测试数据:")
|
||||
print(f"21H数据: {test_data.hex().upper()}")
|
||||
|
||||
def get_start_failure_reason_text(self, reason):
|
||||
"""解析启动失败原因"""
|
||||
reason_map = {
|
||||
1: "设备故障",
|
||||
2: "充电枪使用中",
|
||||
3: "充电设备已被预约",
|
||||
4: "不允许充电",
|
||||
5: "参数不支持",
|
||||
6: "其他原因"
|
||||
}
|
||||
return reason_map.get(reason, f"未知原因 (0x{reason:04X})")
|
||||
# 创建模拟socket
|
||||
class MockSocket:
|
||||
def send(self, data):
|
||||
print(f"\n模拟发送响应数据:")
|
||||
print(f"22H数据: {data.hex().upper()}")
|
||||
|
||||
mock_sock = MockSocket()
|
||||
|
||||
# 测试完整处理流程
|
||||
result = handler.process_and_respond(test_data, mock_sock)
|
||||
print(f"\n最终处理结果: {'成功' if result else '失败'}")
|
||||
|
||||
|
||||
# 测试用例
|
||||
if __name__ == "__main__":
|
||||
# 21H命令测试报文
|
||||
test_21_data = bytes.fromhex(
|
||||
"4A 58 21 03 17 66 56 11 36 06 37 01 B3 00 19 01 09 0B 28 1D 01 31 38 37 37 31 39 38 35 39 35 39 37 30 35 39 36 38 36 00 00 00 00 00 00 00 00 00 00 00 00 00 38 34 30 34 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 F4 01 00 00 01 02 01 00 00 19 01 09 0B 27 37 D4 6C 68 01 00 00 00 00 00 00 00 00 00 03 D0 11 26 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4C 5A 47 4A 4C 4D 34 34 35 50 58 31 31 34 35 33 37 01 01 00 00 00 00 00 00 77 01 DC 05 03 0B 60 1B 73 DA 02 E8 18 E8")
|
||||
|
||||
# 22H命令测试报文
|
||||
test_22_data = bytes.fromhex("4A 58 22 03 17 66 56 11 36 06 37 01 07 00 19 01 09 0B 28 20 01 05")
|
||||
|
||||
parser = Command2122()
|
||||
|
||||
# 测试解析21H命令
|
||||
parser.process_21h_charging_start_result(test_21_data)
|
||||
|
||||
# 测试生成22H响应
|
||||
pile_id_bytes = bytes.fromhex("0317665611360637")
|
||||
response = parser.generate_22h_charging_start_response(pile_id_bytes)
|
||||
print("\n22H充电启动结果响应:")
|
||||
print(response.hex())
|
||||
test_charge_result()
|
@ -1,215 +1,168 @@
|
||||
import struct
|
||||
import logging
|
||||
import binascii
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Command25:
|
||||
def __init__(self):
|
||||
self.command = 0x25 # 充电信息命令
|
||||
self.command = 0x25 # 25H命令码
|
||||
|
||||
def parse_25h_charging_info(self, data):
|
||||
"""
|
||||
解析25H充电信息命令
|
||||
|
||||
:param data: 完整的25H命令报文
|
||||
:return: 解析后的字典或None
|
||||
"""
|
||||
def parse_25h(self, data):
|
||||
"""解析25H充电信息命令"""
|
||||
try:
|
||||
# 验证基本帧格式
|
||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != 0x25:
|
||||
logging.warning(f"25H命令帧格式不正确,原始报文: {binascii.hexlify(data)}")
|
||||
print("\n开始解析25H充电信息命令...")
|
||||
print(f"接收数据: {data.hex().upper()}")
|
||||
|
||||
# 基础校验
|
||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command:
|
||||
logging.warning("25H命令帧格式不正确")
|
||||
return None
|
||||
|
||||
# 打印完整的原始报文以便调试
|
||||
print(f"完整原始报文: {binascii.hexlify(data)}")
|
||||
# 基础信息解析
|
||||
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] # 数据域
|
||||
|
||||
# 提取桩号
|
||||
pile_id_bytes = data[3:11]
|
||||
# 正确的时间解析函数
|
||||
def parse_time(time_bytes):
|
||||
"""
|
||||
解析BCD格式的时间
|
||||
示例: [0x19, 0x01, 0x09, 0x0C, 0x15, 0x2C] -> 2025-01-09 12:15:44
|
||||
"""
|
||||
|
||||
# 提取时间标识
|
||||
time_bytes = data[14:20]
|
||||
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}"
|
||||
def bcd_to_int(byte):
|
||||
"""BCD转换为整数"""
|
||||
return ((byte >> 4) * 10) + (byte & 0x0F)
|
||||
|
||||
# 解析充电参数
|
||||
current_index = 20
|
||||
charging_voltage = struct.unpack("<H", data[current_index:current_index + 2])[0] / 10 # 0.1V
|
||||
current_index += 2
|
||||
try:
|
||||
# 解析时间字段
|
||||
year = bcd_to_int(time_bytes[0]) + 2000 # BCD年转换 (0x19 -> 25 -> 2025年)
|
||||
month = bcd_to_int(time_bytes[1]) # BCD月 (0x01 -> 1月)
|
||||
day = bcd_to_int(time_bytes[2]) # BCD日 (0x09 -> 9日)
|
||||
hour = bcd_to_int(time_bytes[3]) # BCD时 (0x0C -> 12时)
|
||||
minute = bcd_to_int(time_bytes[4]) # BCD分 (0x16 -> 22分)
|
||||
second = bcd_to_int(time_bytes[5]) # BCD秒 (0x12 -> 18秒)
|
||||
|
||||
charging_current = struct.unpack("<H", data[current_index:current_index + 2])[0] / 10 # 0.1A
|
||||
current_index += 2
|
||||
# 调试输出,查看BCD解码后的结果
|
||||
print(f"Debug - Raw bytes: {[hex(b) for b in time_bytes]}")
|
||||
print(f"Debug - Decoded: {year}-{month}-{day} {hour}:{minute}:{second}")
|
||||
|
||||
charging_power = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01kW
|
||||
current_index += 4
|
||||
# 返回格式化时间字符串
|
||||
return f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
|
||||
|
||||
charging_duration = struct.unpack("<I", data[current_index:current_index + 4])[0] # 秒
|
||||
current_index += 4
|
||||
except Exception as e:
|
||||
print(f"时间解析错误: {e}")
|
||||
print(f"错误的时间字节: {[hex(b) for b in time_bytes]}")
|
||||
return "Invalid time"
|
||||
|
||||
charging_amount = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01kWh
|
||||
current_index += 4
|
||||
# 解析每个字段
|
||||
parsed_data = {
|
||||
# 基础信息
|
||||
"pile_id": pile_id.hex().upper(),
|
||||
"timestamp": parse_time(data_field[0:6]),
|
||||
"gun_no": data_field[6],
|
||||
|
||||
charging_fee = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01元
|
||||
current_index += 4
|
||||
# 电压电流
|
||||
"voltage": struct.unpack("<H", data_field[7:9])[0] * 0.1, # 分辨率0.1V
|
||||
"current": struct.unpack("<H", data_field[9:11])[0] * 0.1, # 分辨率0.1A
|
||||
|
||||
# 解析充电模块接入数量
|
||||
charging_module_count = data[current_index]
|
||||
current_index += 1
|
||||
# 电量
|
||||
"charging_kwh": struct.unpack("<I", data_field[11:15])[0] * 0.01, # 分辨率0.01kWh
|
||||
|
||||
# 解析充电电费
|
||||
charging_electricity_fee = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01元
|
||||
current_index += 4
|
||||
# 时长和费用
|
||||
"charging_time": struct.unpack("<I", data_field[15:19])[0], # 秒
|
||||
"total_amount": struct.unpack("<I", data_field[19:23])[0] * 0.01, # 分辨率0.01元
|
||||
|
||||
# 解析服务费
|
||||
service_fee = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01元
|
||||
current_index += 4
|
||||
# 模块信息
|
||||
"module_count": data_field[23], # 充电模块接入数量
|
||||
|
||||
# 解析充电订单号(17字节)
|
||||
charging_order_number = data[current_index:current_index + 17].decode('ascii').rstrip('\x00')
|
||||
current_index += 17
|
||||
# 费用明细
|
||||
"power_amount": struct.unpack("<I", data_field[24:28])[0] * 0.01, # 电费金额
|
||||
"service_amount": struct.unpack("<I", data_field[28:32])[0] * 0.01, # 服务费金额
|
||||
|
||||
# 解析时间段信息
|
||||
time_periods = []
|
||||
time_period_count = data[current_index]
|
||||
current_index += 1
|
||||
# 订单信息
|
||||
"order_no": data_field[32:64].decode('ascii').rstrip('\x00'), # 订单号
|
||||
|
||||
for _ in range(time_period_count):
|
||||
# 每个时间段的解析
|
||||
period_start_time = datetime(
|
||||
year, month, day,
|
||||
data[current_index],
|
||||
data[current_index + 1]
|
||||
)
|
||||
current_index += 2
|
||||
|
||||
period_type = data[current_index] # 1-尖;2-峰;3-平;4-谷
|
||||
current_index += 1
|
||||
|
||||
period_electricity_price = struct.unpack("<I", data[current_index:current_index + 4])[
|
||||
0] / 10000 # 0.0001元/kWh
|
||||
current_index += 4
|
||||
|
||||
period_service_price = struct.unpack("<I", data[current_index:current_index + 4])[
|
||||
0] / 10000 # 0.0001元/kWh
|
||||
current_index += 4
|
||||
|
||||
period_electricity_amount = struct.unpack("<H", data[current_index:current_index + 2])[
|
||||
0] / 100 # 0.01kWh
|
||||
current_index += 2
|
||||
|
||||
period_electricity_fee = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01元
|
||||
current_index += 4
|
||||
|
||||
period_service_fee = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01元
|
||||
current_index += 4
|
||||
|
||||
time_periods.append({
|
||||
"start_time": period_start_time,
|
||||
"type": self.get_period_type_text(period_type),
|
||||
"electricity_price": period_electricity_price,
|
||||
"service_price": period_service_price,
|
||||
"electricity_amount": period_electricity_amount,
|
||||
"electricity_fee": period_electricity_fee,
|
||||
"service_fee": period_service_fee
|
||||
})
|
||||
|
||||
# 打印解析结果
|
||||
print("\n25H充电信息命令解析结果:")
|
||||
print(f"桩号: {pile_id_bytes.hex()}")
|
||||
print(f"时间标识: {timestamp}")
|
||||
print(f"充电电压: {charging_voltage}V")
|
||||
print(f"充电电流: {charging_current}A")
|
||||
print(f"充电功率: {charging_power}kW")
|
||||
print(f"充电时长: {charging_duration}秒")
|
||||
print(f"充电电量: {charging_amount}kWh")
|
||||
print(f"充电金额: {charging_fee}元")
|
||||
print(f"充电模块接入数量: {charging_module_count}")
|
||||
print(f"充电电费: {charging_electricity_fee}元")
|
||||
print(f"服务费: {service_fee}元")
|
||||
print(f"充电订单号: {charging_order_number}")
|
||||
print("时间段信息:")
|
||||
for period in time_periods:
|
||||
print(f" - 开始时间: {period['start_time']}")
|
||||
print(f" 类型: {period['type']}")
|
||||
print(f" 电价: {period['electricity_price']}元/kWh")
|
||||
print(f" 服务费率: {period['service_price']}元/kWh")
|
||||
print(f" 电量: {period['electricity_amount']}kWh")
|
||||
print(f" 电费: {period['electricity_fee']}元")
|
||||
print(f" 服务费: {period['service_fee']}元")
|
||||
|
||||
return {
|
||||
"pile_id": pile_id_bytes.hex(),
|
||||
"timestamp": timestamp,
|
||||
"charging_voltage": charging_voltage,
|
||||
"charging_current": charging_current,
|
||||
"charging_power": charging_power,
|
||||
"charging_duration": charging_duration,
|
||||
"charging_amount": charging_amount,
|
||||
"charging_fee": charging_fee,
|
||||
"charging_module_count": charging_module_count,
|
||||
"charging_electricity_fee": charging_electricity_fee,
|
||||
"service_fee": service_fee,
|
||||
"charging_order_number": charging_order_number,
|
||||
"time_periods": time_periods
|
||||
# 开始和结束时间
|
||||
"start_time": parse_time(data_field[64:70]), # 应该解析为 2025-01-09 12:21:44
|
||||
"end_time": parse_time(data_field[70:76]) # 应该解析为 2025-01-09 12:22:18
|
||||
}
|
||||
|
||||
# 打印解析结果
|
||||
print("\n=== 25H充电信息解析结果 ===")
|
||||
print(f"基本信息:")
|
||||
print(f" 桩号: {parsed_data['pile_id']}")
|
||||
print(f" 时间标识: {parsed_data['timestamp']}")
|
||||
print(f" 枪号: {parsed_data['gun_no']}")
|
||||
|
||||
print(f"\n充电参数:")
|
||||
print(f" 充电电压: {parsed_data['voltage']:.1f}V")
|
||||
print(f" 充电电流: {parsed_data['current']:.1f}A")
|
||||
print(f" 充电电量: {parsed_data['charging_kwh']:.2f}kWh")
|
||||
print(f" 充电时长: {parsed_data['charging_time']}秒")
|
||||
|
||||
print(f"\n费用信息:")
|
||||
print(f" 总金额: {parsed_data['total_amount']:.2f}元")
|
||||
print(f" 电费金额: {parsed_data['power_amount']:.2f}元")
|
||||
print(f" 服务费金额: {parsed_data['service_amount']:.2f}元")
|
||||
|
||||
print(f"\n时间信息:")
|
||||
print(f" 开始时间: {parsed_data['start_time']}")
|
||||
print(f" 结束时间: {parsed_data['end_time']}")
|
||||
|
||||
print(f"\n订单信息:")
|
||||
print(f" 订单号: {parsed_data['order_no']}")
|
||||
|
||||
return parsed_data
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"解析25H命令失败: {str(e)}")
|
||||
logging.error(f"原始报文: {binascii.hexlify(data)}")
|
||||
logging.error(f"解析25H充电信息命令失败: {str(e)}")
|
||||
print(f"解析失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def get_period_type_text(self, period_type):
|
||||
"""
|
||||
解析时间段类型
|
||||
|
||||
:param period_type: 时间段类型字节
|
||||
:return: 时间段类型文本描述
|
||||
"""
|
||||
type_map = {
|
||||
1: "尖",
|
||||
2: "峰",
|
||||
3: "平",
|
||||
4: "谷"
|
||||
}
|
||||
return type_map.get(period_type, f"未知类型 (0x{period_type:02X})")
|
||||
|
||||
def process_25h_charging_info(self, data):
|
||||
"""
|
||||
处理25H充电信息命令
|
||||
|
||||
:param data: 完整的25H命令报文
|
||||
:return: 是否成功处理
|
||||
"""
|
||||
def process_25h(self, data):
|
||||
"""处理25H充电信息命令"""
|
||||
try:
|
||||
parsed_data = self.parse_25h_charging_info(data)
|
||||
print("\n处理充电信息命令...")
|
||||
|
||||
if parsed_data is None:
|
||||
logging.warning("25H命令解析失败")
|
||||
# 解析命令
|
||||
result = self.parse_25h(data)
|
||||
if not result:
|
||||
return False
|
||||
|
||||
# 记录充电信息日志
|
||||
logging.info(
|
||||
f"收到桩号 {parsed_data['pile_id']} 的充电信息: "
|
||||
f"充电电量 {parsed_data['charging_amount']}kWh, "
|
||||
f"充电时长 {parsed_data['charging_duration']}秒, "
|
||||
f"充电订单号 {parsed_data['charging_order_number']}"
|
||||
)
|
||||
# 可以在这里添加额外处理逻辑
|
||||
# 例如:保存到数据库、发送到其他系统等
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"处理25H命令出错: {str(e)}")
|
||||
logging.error(f"处理25H充电信息命令失败: {str(e)}")
|
||||
print(f"处理失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
# 测试用例
|
||||
def test_charge_info():
|
||||
"""测试充电信息命令处理"""
|
||||
print("开始测试充电信息命令处理...")
|
||||
|
||||
# 创建处理器
|
||||
handler = Command25()
|
||||
|
||||
# 测试数据
|
||||
test_data = bytes.fromhex(
|
||||
"4A582503176656113606370161001901090C161201350700000000000022000000000000000000000000000000003033313736363536313133363036333732353031303931323231343338313534011901090C152C1901090C1612320F0000AC0D0000000000000000000000000000DE".replace(
|
||||
" ", ""))
|
||||
|
||||
print("\n测试数据:")
|
||||
print(f"25H数据: {test_data.hex().upper()}")
|
||||
|
||||
# 测试处理流程
|
||||
result = handler.process_25h(test_data)
|
||||
print(f"\n最终处理结果: {'成功' if result else '失败'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 25H命令测试报文
|
||||
test_25_data = bytes.fromhex(
|
||||
"4A 58 25 03 17 66 56 11 36 06 37 01 61 00 19 01 09 0B 25 13 01 DA 07 00 00 00 00 00 00 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 31 38 37 37 31 39 37 38 30 31 36 31 35 35 35 36 36 31 30 00 00 00 00 00 00 00 00 00 00 00 00 00 01 19 01 09 0B 24 2D 19 01 09 0B 25 13 32 0F 00 00 AC 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 3B 4A 58 30 03 17 66 56 11 36 06 37 01 1C 00 19 01 09 0B 25 13 01 00 00 A0 0F 02 DA 07 A0 0F 00 00 00 00 00 00 00 00 00 00 00 00 ED")
|
||||
|
||||
parser = Command25()
|
||||
|
||||
# 测试解析25H命令
|
||||
parser.process_25h_charging_info(test_25_data)
|
||||
test_charge_info()
|
@ -1,117 +1,96 @@
|
||||
import struct
|
||||
import logging
|
||||
import binascii
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Command0B0CH:
|
||||
class CommandHeartbeat:
|
||||
def __init__(self):
|
||||
self.command_0b = 0x0B # 平台心跳命令
|
||||
self.command_0c = 0x0C # 桩心跳命令
|
||||
self.command_0b = 0x0B # 平台心跳命令
|
||||
|
||||
def parse_0c_heartbeat(self, data):
|
||||
"""
|
||||
解析0CH桩心跳命令
|
||||
|
||||
:param data: 完整的0CH命令报文
|
||||
:return: 解析后的字典或None
|
||||
"""
|
||||
"""解析0CH心跳命令"""
|
||||
try:
|
||||
# 验证基本帧格式
|
||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != 0x0C:
|
||||
logging.warning(f"0CH命令帧格式不正确,原始报文: {binascii.hexlify(data)}")
|
||||
print("\n开始解析0CH心跳命令...")
|
||||
print(f"接收数据: {data.hex().upper()}")
|
||||
|
||||
# 基础验证
|
||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command_0c:
|
||||
logging.warning("0CH命令帧格式不正确")
|
||||
return None
|
||||
|
||||
# 打印完整的原始报文以便调试
|
||||
print(f"完整原始报文: {binascii.hexlify(data)}")
|
||||
# 解析数据
|
||||
pile_id = data[3:11] # 桩号
|
||||
encrypt_mode = data[11] # 加密方式
|
||||
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
|
||||
|
||||
# 提取桩号
|
||||
pile_id_bytes = data[3:11]
|
||||
# 解析数据域
|
||||
data_field = data[14:14 + data_len]
|
||||
|
||||
# 提取时间标识
|
||||
time_bytes = data[14:20]
|
||||
# 解析时间标识
|
||||
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}"
|
||||
|
||||
# 解析充电枪数量
|
||||
gun_count = data[20]
|
||||
# 解析心跳数据
|
||||
platform_timeout_count = data_field[6] # 平台心跳超时次数
|
||||
gun_count = data_field[7] # 充电枪数量
|
||||
gun_status = data_field[8] # 充电枪状态
|
||||
work_mode = data_field[9] # 工作模式
|
||||
|
||||
# 解析充电枪状态
|
||||
gun_states = []
|
||||
current_index = 21
|
||||
for i in range(gun_count):
|
||||
try:
|
||||
# 每个充电枪的状态信息占2个字节
|
||||
if current_index + 1 < len(data):
|
||||
gun_state = data[current_index]
|
||||
gun_work_mode = data[current_index + 1]
|
||||
|
||||
gun_states.append({
|
||||
"gun_index": i + 1,
|
||||
"state": gun_state,
|
||||
"state_text": self.get_gun_state_text(gun_state),
|
||||
"work_mode": gun_work_mode,
|
||||
"work_mode_text": self.get_work_mode_text(gun_work_mode)
|
||||
})
|
||||
|
||||
current_index += 2
|
||||
except Exception as gun_parse_error:
|
||||
logging.warning(f"解析第 {i + 1} 个充电枪状态时出错: {gun_parse_error}")
|
||||
|
||||
# 打印解析结果
|
||||
print("\n0CH桩心跳命令解析结果:")
|
||||
print(f"桩号: {pile_id_bytes.hex()}")
|
||||
print(f"时间标识: {timestamp}")
|
||||
print(f"充电枪数量: {gun_count}")
|
||||
for gun_state in gun_states:
|
||||
print(f"枪 {gun_state['gun_index']}:")
|
||||
print(f" 状态: {gun_state['state_text']} (0x{gun_state['state']:02X})")
|
||||
print(f" 工作模式: {gun_state['work_mode_text']} (0x{gun_state['work_mode']:02X})")
|
||||
|
||||
return {
|
||||
"pile_id": pile_id_bytes.hex(),
|
||||
result = {
|
||||
"pile_id": pile_id,
|
||||
"timestamp": timestamp,
|
||||
"platform_timeout_count": platform_timeout_count,
|
||||
"gun_count": gun_count,
|
||||
"gun_states": gun_states
|
||||
"gun_status": gun_status,
|
||||
"work_mode": work_mode
|
||||
}
|
||||
|
||||
print("\n解析结果:")
|
||||
print(f"桩号: {pile_id.hex().upper()}")
|
||||
print(f"时间标识: {timestamp}")
|
||||
print(f"平台心跳超时次数: {platform_timeout_count}")
|
||||
print(f"充电枪数量: {gun_count}")
|
||||
print(f"充电枪状态: {gun_status:02X}")
|
||||
print(f"工作模式: {work_mode:02X}")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"解析0CH命令失败: {str(e)}")
|
||||
logging.error(f"原始报文: {binascii.hexlify(data)}")
|
||||
logging.error(f"解析0CH心跳命令失败: {str(e)}")
|
||||
print(f"解析失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def generate_0b_heartbeat_response(self, pile_id_bytes):
|
||||
"""
|
||||
生成0BH平台心跳响应
|
||||
|
||||
:param pile_id_bytes: 充电桩桩号字节
|
||||
:return: 0BH心跳响应报文
|
||||
"""
|
||||
def build_0b_heartbeat(self, pile_id):
|
||||
"""构建0BH心跳响应"""
|
||||
try:
|
||||
# 构建帧
|
||||
print("\n构建0BH心跳响应...")
|
||||
|
||||
frame = bytearray()
|
||||
frame.extend(b'JX') # 帧起始标志
|
||||
frame.append(self.command_0b) # 命令码
|
||||
frame.extend(pile_id_bytes) # 桩号
|
||||
frame.append(0x01) # 数据加密方式
|
||||
frame.append(self.command_0b) # 命令码0BH
|
||||
frame.extend(pile_id) # 桩号
|
||||
frame.append(0x01) # 数据加密方式(不加密)
|
||||
|
||||
# 构建数据域
|
||||
data = bytearray()
|
||||
|
||||
# 时间标识(当前时间)
|
||||
from datetime import datetime
|
||||
# 添加时间标识
|
||||
now = datetime.now()
|
||||
data.extend(struct.pack("<BBBBBB",
|
||||
now.year - 2000, now.month, now.day,
|
||||
now.hour, now.minute, now.second))
|
||||
|
||||
# 心跳超时次数(这里固定为0)
|
||||
# 添加桩心跳超时次数(例如设为0)
|
||||
data.append(0x00)
|
||||
|
||||
# 数据域长度
|
||||
frame.extend(struct.pack("<H", len(data)))
|
||||
# 计算数据长度
|
||||
data_len = len(data)
|
||||
frame.extend(struct.pack("<H", data_len)) # 数据长度
|
||||
|
||||
# 加入数据域
|
||||
# 添加数据域
|
||||
frame.extend(data)
|
||||
|
||||
# 计算校验码
|
||||
@ -120,95 +99,68 @@ class Command0B0CH:
|
||||
check ^= b
|
||||
frame.append(check)
|
||||
|
||||
print("0BH心跳响应数据构建成功:")
|
||||
print(f"数据内容: {frame.hex()}")
|
||||
print(f"数据长度: {len(frame)}字节")
|
||||
|
||||
return bytes(frame)
|
||||
response = bytes(frame)
|
||||
print(f"响应数据: {response.hex().upper()}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"生成0BH心跳响应出错: {str(e)}")
|
||||
logging.error(f"构建0BH心跳响应失败: {str(e)}")
|
||||
print(f"构建响应失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def process_0c_heartbeat(self, data):
|
||||
"""
|
||||
处理0CH桩心跳命令
|
||||
|
||||
:param data: 完整的0CH命令报文
|
||||
:return: 是否成功处理
|
||||
"""
|
||||
def process_and_respond(self, received_data, sock):
|
||||
"""处理收到的0CH命令并回复0BH"""
|
||||
try:
|
||||
parsed_data = self.parse_0c_heartbeat(data)
|
||||
print("\n处理心跳命令...")
|
||||
|
||||
if parsed_data is None:
|
||||
logging.warning("0CH命令解析失败")
|
||||
# 解析接收到的0CH命令
|
||||
parsed = self.parse_0c_heartbeat(received_data)
|
||||
if not parsed:
|
||||
return False
|
||||
|
||||
# 记录心跳信息日志
|
||||
logging.info(f"收到桩号 {parsed_data['pile_id']} 的心跳, 充电枪数量 {parsed_data['gun_count']}")
|
||||
# 构建0BH响应
|
||||
response = self.build_0b_heartbeat(parsed["pile_id"])
|
||||
if not response:
|
||||
return False
|
||||
|
||||
# 发送响应
|
||||
if sock and hasattr(sock, 'send'):
|
||||
sock.send(response)
|
||||
print("心跳响应发送成功")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"处理0CH命令出错: {str(e)}")
|
||||
logging.error(f"处理心跳命令失败: {str(e)}")
|
||||
print(f"处理失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_gun_state_text(self, state):
|
||||
"""
|
||||
解析充电枪状态
|
||||
|
||||
:param state: 充电枪状态字节
|
||||
:return: 状态文本描述
|
||||
"""
|
||||
state_map = {
|
||||
0x01: "待机",
|
||||
0x02: "等待连接",
|
||||
0x03: "启动中",
|
||||
0x04: "充电中",
|
||||
0x05: "停止中",
|
||||
0x06: "预约中",
|
||||
0x07: "占用中",
|
||||
0x08: "测试中",
|
||||
0x09: "故障中",
|
||||
0x0A: "定时充电",
|
||||
0x0B: "充电完成",
|
||||
0x0C: "升级中"
|
||||
}
|
||||
return state_map.get(state, f"未知状态 (0x{state:02X})")
|
||||
def test_heartbeat():
|
||||
"""测试心跳命令处理"""
|
||||
print("开始测试心跳命令处理...")
|
||||
|
||||
def get_work_mode_text(self, mode):
|
||||
"""
|
||||
解析工作模式
|
||||
# 创建处理器
|
||||
handler = CommandHeartbeat()
|
||||
|
||||
:param mode: 工作模式字节
|
||||
:return: 工作模式文本描述
|
||||
"""
|
||||
mode_map = {
|
||||
0x01: "普通充电",
|
||||
0x02: "轮充",
|
||||
0x03: "大功率",
|
||||
0x04: "超级充",
|
||||
0x05: "电池维护",
|
||||
0x06: "柔性充"
|
||||
}
|
||||
return mode_map.get(mode, f"未知模式 (0x{mode:02X})")
|
||||
# 测试数据 - 使用实际收到的0CH数据
|
||||
test_data = bytes.fromhex("4A580C0317665611360637010C001901090B25240102010101012B")
|
||||
|
||||
print("\n测试数据:")
|
||||
print(f"0CH数据: {test_data.hex().upper()}")
|
||||
|
||||
# 创建模拟socket
|
||||
class MockSocket:
|
||||
def send(self, data):
|
||||
print(f"\n模拟发送响应数据:")
|
||||
print(f"0BH数据: {data.hex().upper()}")
|
||||
|
||||
mock_sock = MockSocket()
|
||||
|
||||
# 测试完整处理流程
|
||||
result = handler.process_and_respond(test_data, mock_sock)
|
||||
print(f"\n最终处理结果: {'成功' if result else '失败'}")
|
||||
|
||||
|
||||
# 测试用示例
|
||||
if __name__ == "__main__":
|
||||
# 0C命令测试报文
|
||||
test_0c_data = bytes.fromhex("4A 58 0C 03 17 67 63 11 36 06 57 01 0C 00 19 01 09 09 37 3B 01 02 01 01 01 01 70")
|
||||
|
||||
# 0B命令测试报文
|
||||
test_0b_data = bytes.fromhex("4A 58 0B 03 17 67 63 11 36 06 57 01 07 00 19 01 09 09 38 00 00 4B")
|
||||
|
||||
parser = Command0B0CH()
|
||||
|
||||
# 测试解析0C心跳
|
||||
parser.process_0c_heartbeat(test_0c_data)
|
||||
|
||||
# 测试生成0B心跳响应
|
||||
pile_id_bytes = bytes.fromhex("0317676311360657")
|
||||
response = parser.generate_0b_heartbeat_response(pile_id_bytes)
|
||||
print("\n0B心跳响应:")
|
||||
print(response.hex())
|
||||
test_heartbeat()
|
Binary file not shown.
@ -1,9 +1,9 @@
|
||||
import socket
|
||||
import logging
|
||||
import threading
|
||||
from .utils import ProxyUtils # 使用相对导入
|
||||
from .mqtt_client import MQTTClient # 使用相对导入
|
||||
from commands.command_heartbeat import Command0B0CH
|
||||
from .utils import ProxyUtils
|
||||
from .mqtt_client import MQTTClient
|
||||
from commands.command_heartbeat import CommandHeartbeat
|
||||
from commands.command_02 import Command02
|
||||
from commands.command_03 import Command03
|
||||
from commands.command_07 import Command07
|
||||
@ -15,7 +15,7 @@ from commands.command_30 import Command30
|
||||
from commands.command_19_1A import Command191A
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ class ChargingPileProxyServer:
|
||||
self.mqtt_client = MQTTClient()
|
||||
self.pile_ids = {}
|
||||
self.utils = ProxyUtils()
|
||||
self.command_handler = Command0B0CH()
|
||||
self.command_handler = CommandHeartbeat()
|
||||
self.command_handler = Command02()
|
||||
self.command_handler = Command03()
|
||||
self.command_handler = Command07()
|
||||
@ -46,7 +46,7 @@ class ChargingPileProxyServer:
|
||||
self.command_handler = Command191A()
|
||||
self.command_handler = Command2122()
|
||||
self.command_handler = Command2324()
|
||||
self.command_handler = Command2627()
|
||||
#self.command_handler = Command2627()
|
||||
|
||||
# 存储登录信息的字典,以桩号为键
|
||||
self.login_info = {}
|
||||
@ -131,80 +131,115 @@ class ChargingPileProxyServer:
|
||||
if len(data) >= 14 and data[0:2] == b'JX':
|
||||
command = data[2] # 提取命令字节
|
||||
|
||||
# 获取本地和远程端口信息用于日志
|
||||
source_local, source_remote = self.utils.get_socket_info(source_socket)
|
||||
dest_local, dest_remote = self.utils.get_socket_info(destination_socket)
|
||||
|
||||
# 根据命令字节处理不同命令
|
||||
if command == 0x01:
|
||||
logging.info(f"处理 01H 命令,数据内容: {data.hex()}")
|
||||
self.command_handler.parse_01h(data)
|
||||
if command == 0x01: # 01H命令
|
||||
logging.info(f"收到01H连接请求命令: {data.hex().upper()}")
|
||||
if self.command_handler.process_and_respond(data, destination_socket):
|
||||
logging.info("01H命令处理完成")
|
||||
continue # 跳过后续转发
|
||||
|
||||
elif command == 0x02:
|
||||
logging.info(f"处理 02H 命令,数据内容: {data.hex()}")
|
||||
self.command_handler.parse_02h(data)
|
||||
elif command == 0x03: # 03H登录命令
|
||||
logging.info(f"收到03H登录命令: {data.hex().upper()}")
|
||||
self.command_handler.process_03h(data)
|
||||
|
||||
elif command == 0x03:
|
||||
logging.info(f"处理 03H 命令,数据内容: {data.hex()}")
|
||||
self.command_handler.parse_03h(data)
|
||||
elif command == 0x0C: # 0CH桩心跳命令
|
||||
logging.info(f"收到0CH心跳命令: {data.hex().upper()}")
|
||||
if self.heartbeat_handler.process_and_respond(data, destination_socket):
|
||||
logging.info("0CH心跳命令处理完成")
|
||||
continue # 跳过后续转发
|
||||
|
||||
elif command == 0x07:
|
||||
logging.info(f"处理 07H 命令,数据内容: {data.hex()}")
|
||||
self.command_handler.parse_07h(data)
|
||||
elif command == 0x19: # 19H卡鉴权命令
|
||||
logging.info(f"收到19H卡鉴权命令: {data.hex().upper()}")
|
||||
if self.card_auth_handler.process_and_respond(data, destination_socket):
|
||||
logging.info("19H卡鉴权命令处理完成")
|
||||
continue # 跳过后续转发
|
||||
|
||||
elif command == 0x08:
|
||||
logging.info(f"处理 08H 命令,数据内容: {data.hex()}")
|
||||
self.command_handler.parse_08h(data)
|
||||
|
||||
elif command == 0x09:
|
||||
logging.info(f"处理 09H 命令,数据内容: {data.hex()}")
|
||||
self.command_handler.parse_09h(data)
|
||||
elif command == 0x21: # 21H启动充电结果命令
|
||||
|
||||
elif command == 0x0A:
|
||||
logging.info(f"处理 0AH 命令,数据内容: {data.hex()}")
|
||||
self.command_handler.parse_0Ah(data)
|
||||
logging.info(f"收到21H启动充电结果命令: {data.hex()}")
|
||||
|
||||
#其他命令待添加...
|
||||
if self.charge_result_handler.process_and_respond(data, destination_socket):
|
||||
continue # 跳过后续转发
|
||||
|
||||
elif command == 0x23: # 23H充电订单命令
|
||||
logging.info(f"收到23H充电订单命令: {data.hex().upper()}")
|
||||
if self.order_handler.process_and_respond(data, destination_socket):
|
||||
logging.info("23H充电订单命令处理完成")
|
||||
continue
|
||||
|
||||
|
||||
elif command == 0x25: # 25H充电信息命令
|
||||
|
||||
logging.info(f"收到25H充电信息命令: {data.hex()}")
|
||||
|
||||
if self.charge_info_handler.process_25h(data):
|
||||
logging.info("25H命令处理完成")
|
||||
|
||||
elif command == 0x30: # 30H BMS信息命令
|
||||
logging.info(f"收到30H BMS信息命令: {data.hex().upper()}")
|
||||
if self.bms_handler.process_30h(data):
|
||||
logging.info("30H BMS信息命令处理完成")
|
||||
|
||||
else:
|
||||
# 未知命令,日志记录
|
||||
logging.warning(f"未知命令:{command:02X},数据内容: {data.hex()}")
|
||||
# 未知命令,记录日志
|
||||
logging.warning(f"未知命令:{command:02X},数据内容: {data.hex().upper()}")
|
||||
|
||||
# 将数据转发到远程或充电桩,判断方向
|
||||
if source_socket not in self.remote_connections.values():
|
||||
# 这里是判断是否是客户端连接,进行桩ID提取
|
||||
pile_id = self.utils.extract_pile_id(data)
|
||||
if pile_id:
|
||||
self.pile_ids[source_address] = pile_id
|
||||
# 如果数据来自客户端连接,提取桩号
|
||||
if source_socket not in self.remote_connections.values():
|
||||
pile_id = self.utils.extract_pile_id(data)
|
||||
if pile_id:
|
||||
self.pile_ids[source_address] = pile_id
|
||||
|
||||
# 获取本地和远程端口信息
|
||||
source_local, source_remote = self.utils.get_socket_info(source_socket)
|
||||
dest_local, dest_remote = self.utils.get_socket_info(destination_socket)
|
||||
# 判断数据发送方向:是发送到远程服务器还是充电桩
|
||||
is_to_remote = destination_socket in self.remote_connections.values()
|
||||
direction = "发送到远程服务器" if is_to_remote else "发送到充电桩"
|
||||
mqtt_direction = "u" if is_to_remote else "d"
|
||||
|
||||
# 判断数据发送方向:是发送到远程服务器还是充电桩
|
||||
is_to_remote = destination_socket in self.remote_connections.values()
|
||||
direction = "发送到远程服务器" if is_to_remote else "发送到充电桩"
|
||||
mqtt_direction = "u" if is_to_remote else "d"
|
||||
# 发送数据到目的地
|
||||
destination_socket.send(data)
|
||||
|
||||
# 发送数据到目的地
|
||||
destination_socket.send(data)
|
||||
# 记录数据转发日志
|
||||
msg = (f"数据转发成功: {direction} | "
|
||||
f"本地地址: {source_local} | "
|
||||
f"远程地址: {dest_remote} | "
|
||||
f"命令: {command:02X}H | "
|
||||
f"数据长度: {len(data)}")
|
||||
logging.info(msg)
|
||||
self.mqtt_client.publish_message(msg)
|
||||
|
||||
# 记录数据转发日志
|
||||
msg = f"数据转发成功: {direction} | 本地地址: {source_local} | 远程地址: {dest_remote} | 数据长度: {len(data)}"
|
||||
logging.info(msg)
|
||||
self.mqtt_client.publish_message(msg)
|
||||
# 记录完整的数据内容到调试日志
|
||||
logging.debug(f"完整数据内容: {data.hex().upper()}")
|
||||
|
||||
# 每次数据转发完成后,处理其他相关操作
|
||||
# 例如检查是否需要断开连接等
|
||||
if not self.running:
|
||||
break
|
||||
# 每次数据转发完成后,检查连接状态
|
||||
if not self.running:
|
||||
break
|
||||
|
||||
except ConnectionResetError:
|
||||
logging.error(f"连接被重置: {source_remote}")
|
||||
self.mqtt_client.publish_message(f"连接被重置: {source_remote}")
|
||||
|
||||
except socket.timeout:
|
||||
logging.error(f"连接超时: {source_remote}")
|
||||
self.mqtt_client.publish_message(f"连接超时: {source_remote}")
|
||||
|
||||
except Exception as e:
|
||||
# 异常处理,日志记录错误信息
|
||||
logging.error(f"转发数据出错: {str(e)}")
|
||||
print(f"转发数据出错: {str(e)}")
|
||||
self.mqtt_client.publish_message(f"转发数据出错: {str(e)}")
|
||||
# 若需要,可以选择关闭连接
|
||||
if source_socket:
|
||||
source_socket.close()
|
||||
if destination_socket:
|
||||
destination_socket.close()
|
||||
|
||||
finally:
|
||||
# 关闭连接
|
||||
try:
|
||||
if source_socket:
|
||||
source_socket.close()
|
||||
if destination_socket:
|
||||
destination_socket.close()
|
||||
except Exception as e:
|
||||
logging.error(f"关闭连接出错: {str(e)}")
|
||||
|
||||
def handle_client(self, client_socket, client_address):
|
||||
"""处理客户端连接"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user