382 lines
14 KiB
Python
Raw Normal View History

2025-01-18 09:10:52 +08:00
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响应。")