382 lines
14 KiB
Python
382 lines
14 KiB
Python
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响应。")
|