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

293 lines
11 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
class Command2122:
def __init__(self):
self.command_21 = 0x21 # 充电启动结果命令
self.command_22 = 0x22 # 平台回复启动充电结果命令
def parse_21h_charging_start_result(self, data):
"""
解析21H充电启动结果命令
:param data: 完整的21H命令报文
:return: 解析后的字典或None
"""
try:
# 验证基本帧格式
if len(data) < 14 or data[0:2] != b'JX' or data[2] != 0x21:
logging.warning(f"21H命令帧格式不正确原始报文: {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 = 22
# 解析充电订单号
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
# 解析车牌号
vehicle_number = data[current_index:current_index + 9].decode('ascii').rstrip('\x00')
current_index += 9
# 解析控制方式
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(),
"timestamp": timestamp,
"charging_order_number": charging_order_number,
"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
}
except Exception as e:
logging.error(f"解析21H命令失败: {str(e)}")
logging.error(f"原始报文: {binascii.hexlify(data)}")
return None
def generate_22h_charging_start_response(self, pile_id_bytes):
"""
生成22H平台回复启动充电结果命令
:param pile_id_bytes: 充电桩桩号字节
:return: 22H响应报文
"""
try:
# 构建帧
frame = bytearray()
frame.extend(b'JX') # 帧起始标志
frame.append(self.command_22) # 命令码
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))
# 数据域长度
frame.extend(struct.pack("<H", len(data)))
# 加入数据域
frame.extend(data)
# 计算校验码
check = 0
for b in frame[2:]:
check ^= b
frame.append(check)
print("22H充电启动结果响应数据构建成功:")
print(f"数据内容: {frame.hex()}")
print(f"数据长度: {len(frame)}字节")
return bytes(frame)
except Exception as e:
logging.error(f"生成22H充电启动结果响应出错: {str(e)}")
return None
def process_21h_charging_start_result(self, data):
"""
处理21H充电启动结果命令
:param data: 完整的21H命令报文
:return: 是否成功处理
"""
try:
parsed_data = self.parse_21h_charging_start_result(data)
if parsed_data is None:
logging.warning("21H命令解析失败")
return False
# 记录充电启动结果信息日志
logging.info(
f"收到桩号 {parsed_data['pile_id']} 的充电启动结果: "
f"订单号 {parsed_data['charging_order_number']}, "
f"启动结果 {parsed_data['start_result']}"
)
return True
except Exception as e:
logging.error(f"处理21H命令出错: {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_pile_type_text(self, pile_type):
"""解析充电桩类型"""
type_map = {
1: "交流",
2: "直流"
}
return type_map.get(pile_type, f"未知类型 (0x{pile_type:02X})")
def get_start_result_text(self, result):
"""解析启动结果"""
result_map = {
1: "成功",
2: "失败"
}
return result_map.get(result, f"未知结果 (0x{result:02X})")
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})")
# 测试用例
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())