293 lines
11 KiB
Python
Raw Normal View History

2025-01-18 09:10:52 +08:00
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())