2025-01-18 09:10:52 +08:00

382 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import struct
import logging
import binascii
from datetime import datetime
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class Command2324:
def __init__(self):
self.command_23 = 0x23 # 最新充电订单命令
self.command_24 = 0x24 # 平台回复最新充电订单命令
def parse_23h_latest_charging_order(self, data):
"""
解析23H最新充电订单命令
:param data: 完整的23H命令报文
:return: 解析后的字典或None
"""
try:
# 验证基本帧格式
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command_23:
logging.warning(f"23H命令帧格式不正确原始报文: {binascii.hexlify(data)}")
return None
# 打印完整的原始报文以便调试
print(f"完整原始报文: {binascii.hexlify(data)}")
# 提取桩号
pile_id_bytes = data[3:11]
# 提取时间标识
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}"
current_index = 20
# 解析记录索引号
record_index = struct.unpack("<I", data[current_index:current_index + 4])[0]
current_index += 4
# 解析充电订单号
charging_order_number = data[current_index:current_index + 32].decode('ascii').rstrip('\x00')
current_index += 32
# 解析用户ID
user_id = data[current_index:current_index + 32].decode('ascii').rstrip('\x00')
current_index += 32
# 解析用户类型
user_type = struct.unpack("<H", data[current_index:current_index + 2])[0]
current_index += 2
# 解析组织机构代码
org_code = data[current_index:current_index + 9].decode('ascii').rstrip('\x00')
current_index += 9
# 解析充电卡余额(用于离线卡)
card_balance = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01元
current_index += 4
# 解析VIN
vin = data[current_index:current_index + 17].decode('ascii').rstrip('\x00')
current_index += 17
# 解析开始充电时间
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
# 解析结束充电时间
end_charging_time_bytes = data[current_index:current_index + 6]
end_charging_time = datetime(
end_charging_time_bytes[0] + 2000,
end_charging_time_bytes[1],
end_charging_time_bytes[2],
end_charging_time_bytes[3],
end_charging_time_bytes[4],
end_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
# 解析结束充电电量
end_charging_amount = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01kWh
current_index += 4
# 解析开始SOC
start_soc = data[current_index]
current_index += 1
# 解析结束SOC
end_soc = data[current_index]
current_index += 1
# 解析控制方式
control_mode = data[current_index]
current_index += 1
# 解析控制参数
control_param = struct.unpack("<I", data[current_index:current_index + 4])[0]
current_index += 4
# 解析启动类型
start_type = data[current_index]
current_index += 1
# 如果启动类型为定时启动,解析定时启动时间
start_timing_time = None
if start_type == 2:
start_timing_time_bytes = data[current_index:current_index + 6]
start_timing_time = datetime(
start_timing_time_bytes[0] + 2000,
start_timing_time_bytes[1],
start_timing_time_bytes[2],
start_timing_time_bytes[3],
start_timing_time_bytes[4],
start_timing_time_bytes[5]
)
current_index += 6
# 解析充电模式
charging_mode = data[current_index]
current_index += 1
# 解析停止原因
stop_reason = struct.unpack("<H", data[current_index:current_index + 2])[0]
current_index += 2
# 打印解析结果
print("\n23H最新充电订单命令解析结果:")
print(f"桩号: {pile_id_bytes.hex()}")
print(f"时间标识: {timestamp}")
print(f"记录索引号: {record_index}")
print(f"充电订单号: {charging_order_number}")
print(f"用户ID: {user_id}")
print(f"用户类型: {self.get_user_type_text(user_type)}")
print(f"组织机构代码: {org_code}")
print(f"充电卡余额: {card_balance}")
print(f"VIN: {vin}")
print(f"开始充电时间: {start_charging_time}")
print(f"结束充电时间: {end_charging_time}")
print(f"开始充电电量: {start_charging_amount}kWh")
print(f"结束充电电量: {end_charging_amount}kWh")
print(f"开始SOC: {start_soc}%")
print(f"结束SOC: {end_soc}%")
print(f"控制方式: {self.get_control_mode_text(control_mode)}")
print(f"控制参数: {control_param}")
print(f"启动类型: {self.get_start_type_text(start_type)}")
if start_timing_time:
print(f"定时启动时间: {start_timing_time}")
print(f"充电模式: {self.get_charging_mode_text(charging_mode)}")
print(f"停止原因: {self.get_stop_reason_text(stop_reason)}")
return {
"pile_id": pile_id_bytes.hex(),
"timestamp": timestamp,
"record_index": record_index,
"charging_order_number": charging_order_number,
"user_id": user_id,
"user_type": self.get_user_type_text(user_type),
"org_code": org_code,
"card_balance": card_balance,
"vin": vin,
"start_charging_time": start_charging_time,
"end_charging_time": end_charging_time,
"start_charging_amount": start_charging_amount,
"end_charging_amount": end_charging_amount,
"start_soc": start_soc,
"end_soc": end_soc,
"control_mode": self.get_control_mode_text(control_mode),
"control_param": control_param,
"start_type": self.get_start_type_text(start_type),
"start_timing_time": start_timing_time,
"charging_mode": self.get_charging_mode_text(charging_mode),
"stop_reason": self.get_stop_reason_text(stop_reason)
}
except Exception as e:
logging.error(f"解析23H命令失败: {str(e)}")
logging.error(f"原始报文: {binascii.hexlify(data)}")
return None
def generate_24h_charging_order_response(self, pile_id_bytes, record_index):
"""
生成24H平台回复最新充电订单命令
:param pile_id_bytes: 充电桩桩号字节
:param record_index: 记录索引号
:return: 24H响应报文
"""
try:
# 构建帧
frame = bytearray()
frame.extend(b'JX') # 帧起始标志
frame.append(self.command_24) # 命令码
frame.extend(pile_id_bytes) # 桩号
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))
# 记录索引号
data.extend(struct.pack("<I", record_index))
# 数据域长度
frame.extend(struct.pack("<H", len(data)))
# 加入数据域
frame.extend(data)
# 计算校验码(从命令码开始到数据域结束的所有字节异或)
check = 0
for b in frame[2:]:
check ^= b
frame.append(check)
print("24H最新充电订单响应数据构建成功:")
print(f"数据内容: {frame.hex()}")
print(f"数据长度: {len(frame)}字节")
return bytes(frame)
except Exception as e:
logging.error(f"生成24H最新充电订单响应出错: {str(e)}")
return None
def process_23h_latest_charging_order(self, data):
"""
处理23H最新充电订单命令
:param data: 完整的23H命令报文
:return: 是否成功处理
"""
try:
parsed_data = self.parse_23h_latest_charging_order(data)
if parsed_data is None:
logging.warning("23H命令解析失败")
return False
# 记录最新充电订单信息日志
logging.info(
f"收到桩号 {parsed_data['pile_id']} 的最新充电订单: "
f"订单号 {parsed_data['charging_order_number']}, "
f"充电时间 {parsed_data['start_charging_time']} - {parsed_data['end_charging_time']}, "
f"充电电量 {parsed_data['start_charging_amount']} - {parsed_data['end_charging_amount']}kWh"
)
return True
except Exception as e:
logging.error(f"处理23H命令出错: {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 get_charging_mode_text(self, mode):
"""解析充电模式"""
mode_map = {
1: "普通充电",
2: "轮充",
3: "大功率",
4: "超级充",
5: "电池维护",
6: "柔性充"
}
return mode_map.get(mode, f"未知模式 (0x{mode:02X})")
def get_start_type_text(self, start_type):
"""解析启动类型"""
type_map = {
1: "立即启动",
2: "定时启动"
}
return type_map.get(start_type, f"未知类型 (0x{start_type:02X})")
def get_stop_reason_text(self, reason):
"""解析停止原因"""
reason_map = {
3: "强制拔枪",
5: "电子锁故障",
7: "启动绝缘电压失败",
8: "绝缘低故障",
9: "绝缘检测故障",
10: "绝缘泄放电压异常",
11: "电池外侧电压大于10V",
12: "BRM报文超时",
13: "BCP报文超时",
14: "BRO_00超时",
15: "BRO超时",
16: "BCL超时",
17: "BCS超时",
18: "电池电压不匹配",
20: "启动预充电压失败",
21: "电池单体电压过高",
22: "电池单体电压过低",
23: "SOC过高",
24: "SOC过低",
26: "过温",
31: "输出电压过高",
32: "充电过流",
51: "到达设定的SOC",
52: "到达设定的电压",
53: "到达设定的单体电压",
54: "充电机主动停止",
61: "绝缘故障",
62: "电池输出连接器异常",
63: "输出连接器过温"
}
return reason_map.get(reason, f"未知原因 (0x{reason:04X})")
# 测试用例
if __name__ == "__main__":
# 创建解析器实例
parser = Command2324()
# 23H命令测试报文
# 注意:确保测试报文长度和格式与解析逻辑一致
test_23_data = bytes.fromhex(
"4A5823031766561136063701A3001901090B251E01C20A0000313837373139373830313631353535363631300000000000000000003840343300000000000000000000000000000000000000000000000000000000160000000000000000004C5A474A4C4D34443550583131343533371901090B242D1901090B251BBE6C6801C66C6801494903F4010000041901090B242F01F903011B000300000003000000000000010308002F"
)
# 24H命令测试报文
test_24_data = bytes.fromhex(
"4A58240317665611360637010B001901090B252001C20A0000CA"
)
# 测试解析23H命令
parsed_data = parser.parse_23h_latest_charging_order(test_23_data)
# 检查解析是否成功
if parsed_data:
# 测试生成24H响应
pile_id_bytes = bytes.fromhex("0317665611360637") # 从测试报文中提取的桩号
record_index = parsed_data['record_index'] # 使用解析得到的记录索引号
response = parser.generate_24h_charging_order_response(pile_id_bytes, record_index)
print("\n24H最新充电订单响应:")
print(response.hex())
else:
logging.error("23H命令解析失败无法生成24H响应。")