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响应。")
|