no message

This commit is contained in:
MATRIX\29620 2025-01-18 14:57:32 +08:00
parent d097e79f71
commit b6130bebcc
10 changed files with 561 additions and 700 deletions

View File

@ -0,0 +1,201 @@
import struct
import logging
from datetime import datetime
class Command1F20:
def __init__(self):
self.command_1f = 0x1F # 启动充电命令
self.command_20 = 0x20 # 启动充电回复
def build_1f_command(self, pile_id, card_no="18771978016"):
"""构建1FH启动充电命令"""
try:
print("\n构建1FH启动充电命令...")
frame = bytearray()
frame.extend(b'JX') # 帧起始标志
frame.append(self.command_1f) # 命令码1FH
frame.extend(pile_id) # 桩号
frame.append(0x01) # 数据加密方式(不加密)
# 构建数据域
data = bytearray()
# 添加时间标识
now = datetime.now()
data.extend(bytes([
now.year - 2000,
now.month,
now.day,
now.hour,
now.minute,
now.second
]))
# 添加枪号
data.append(0x01)
# 添加卡号(32字节)
data.extend(card_no.encode().ljust(32, b'\x00'))
# 添加用户ID(32字节)
user_id = "84043"
data.extend(user_id.encode().ljust(32, b'\x00'))
# 添加组织机构代码(9字节)
data.extend(b'\x16'.ljust(9, b'\x00'))
# 添加控制方式(1字节) - 定金额充
data.append(0x03)
# 添加控制参数(4字节) - 1000元
data.extend(struct.pack("<I", 1000))
# 添加充电模式(1字节) - 正常充电
data.append(0x01)
# 添加启动方式(1字节) - 立即启动
data.append(0x01)
# 添加定时启动时间(6字节)
data.extend(bytes([0x19, 0x01, 0x09, 0x0B, 0x24, 0x2F]))
# 添加用户操作码(6字节)
data.extend(b'ws8quu')
# 添加计费模型选择(1字节) - 本地计费模型
data.append(0x01)
# 计算数据长度
frame.extend(struct.pack("<H", len(data)))
# 添加数据域
frame.extend(data)
# 计算校验码
check = 0
for b in frame[2:]:
check ^= b
frame.append(check)
command = bytes(frame)
print(f"启动充电命令数据: {command.hex().upper()}")
return command
except Exception as e:
logging.error(f"构建1FH启动充电命令失败: {str(e)}")
print(f"构建命令失败: {str(e)}")
return None
def parse_20h(self, data):
"""解析20H启动充电回复"""
try:
print("\n开始解析20H启动充电回复...")
print(f"接收数据: {data.hex().upper()}")
# 基础验证
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command_20:
logging.warning("20H命令帧格式不正确")
return None
# 解析数据
pile_id = data[3:11] # 桩号
encrypt_mode = data[11] # 加密方式
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
data_field = data[14:14 + data_len] # 数据域
def parse_time(time_bytes):
"""解析时间字节"""
try:
year = time_bytes[0] + 2000
month = time_bytes[1]
day = time_bytes[2]
hour = time_bytes[3]
minute = time_bytes[4]
second = time_bytes[5]
return f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
except Exception as e:
print(f"时间解析错误: {e}")
return "Invalid time"
# 解析数据域
parsed_data = {
"pile_id": pile_id.hex().upper(),
"timestamp": parse_time(data_field[0:6]),
"gun_no": data_field[6],
"card_no": data_field[7:39].decode('ascii').rstrip('\x00'),
"user_id": data_field[39:71].decode('ascii').rstrip('\x00'),
"execution_result": data_field[-2], # 1-成功2-失败
"fail_reason": data_field[-1] if data_field[-2] == 2 else 0
}
print("\n解析结果:")
print(f"桩号: {parsed_data['pile_id']}")
print(f"时间标识: {parsed_data['timestamp']}")
print(f"枪号: {parsed_data['gun_no']}")
print(f"卡号: {parsed_data['card_no']}")
print(f"用户ID: {parsed_data['user_id']}")
print(f"执行结果: {'成功' if parsed_data['execution_result'] == 1 else '失败'}")
if parsed_data['execution_result'] == 2:
print(f"失败原因代码: {parsed_data['fail_reason']}")
return parsed_data
except Exception as e:
logging.error(f"解析20H启动充电回复失败: {str(e)}")
print(f"解析失败: {str(e)}")
return None
def process_and_respond(self, pile_id, sock):
"""发送启动充电命令"""
try:
# 构建并发送1FH命令
command = self.build_1f_command(pile_id)
if not command:
return False
if sock and hasattr(sock, 'send'):
sock.send(command)
print("启动充电命令发送成功")
return True
except Exception as e:
logging.error(f"处理启动充电命令失败: {str(e)}")
print(f"处理失败: {str(e)}")
return False
def test_start_charge():
"""测试启动充电命令"""
print("开始测试启动充电命令处理...")
# 创建处理器
handler = Command1F20()
# 测试桩号
pile_id = bytes.fromhex("0317665611360637")
# 创建模拟socket
class MockSocket:
def send(self, data):
print(f"\n模拟发送数据:")
print(f"1FH数据: {data.hex().upper()}")
mock_sock = MockSocket()
# 测试1FH命令发送
print("\n测试发送启动充电命令:")
result = handler.process_and_respond(pile_id, mock_sock)
print(f"命令发送结果: {'成功' if result else '失败'}")
# 测试20H回复解析
print("\n测试解析启动充电回复:")
test_reply = bytes.fromhex(
"4A5820031766561136063701680019010 90B242D013138373731393738303136313535353636313000000000000000000000000000003834303433000000000000000000000000000000000000000000000000000000160000000000000000000003F4010000040119010 90B242F7773387175750101 00D7")
handler.parse_20h(test_reply)
if __name__ == "__main__":
test_start_charge()

View File

@ -1,381 +1,210 @@
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 # 平台回复最新充电订单命令
self.command_23 = 0x23 # 充电订单命令
self.command_24 = 0x24 # 充电订单回复
def parse_23h_latest_charging_order(self, data):
"""
解析23H最新充电订单命令
:param data: 完整的23H命令报文
:return: 解析后的字典或None
"""
def parse_23h(self, data):
"""解析23H充电订单命令"""
try:
# 验证基本帧格式
print("\n开始解析23H充电订单命令...")
print(f"接收数据: {data.hex().upper()}")
# 基础校验
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command_23:
logging.warning(f"23H命令帧格式不正确原始报文: {binascii.hexlify(data)}")
logging.warning("23H命令帧格式不正确")
return None
# 打印完整的原始报文以便调试
print(f"完整原始报文: {binascii.hexlify(data)}")
# 基础信息解析
pile_id = data[3:11] # 桩号
encrypt_mode = data[11] # 加密方式
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
data_field = data[14:14 + data_len] # 数据域
# 提取桩号
pile_id_bytes = data[3:11]
# 打印完整的数据字段用于调试
print("\nData field hex:")
print(' '.join([f'{b:02X}' for b in data_field]))
# 提取时间标识
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}"
def parse_time(time_bytes):
"""解析时间字节"""
try:
year = time_bytes[0] + 2000 # 25 + 2000 = 2025
month = time_bytes[1] # 1
day = time_bytes[2] # 9
hour = time_bytes[3] # 12
minute = time_bytes[4] # 21/22
second = time_bytes[5] # 44/41
current_index = 20
print(f"Time bytes: {' '.join([f'{b:02X}' for b in time_bytes])}")
return f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
except Exception as e:
print(f"时间解析错误: {e}")
return "Invalid time"
# 解析记录索引号
record_index = struct.unpack("<I", data[current_index:current_index + 4])[0]
current_index += 4
# 解析每个字段
parsed_data = {
# 基础信息
"pile_id": pile_id.hex().upper(),
"timestamp": parse_time(data_field[0:6]),
"gun_no": data_field[6],
"order_index": struct.unpack("<I", data_field[7:11])[0], # 订单索引号
# 解析充电订单号
charging_order_number = data[current_index:current_index + 32].decode('ascii').rstrip('\x00')
current_index += 32
# 订单信息
"order_no": data_field[11:43].decode('ascii', errors='ignore').rstrip('\x00'),
"user_id": data_field[43:75].decode('ascii', errors='ignore').rstrip('\x00'),
"user_type": struct.unpack("<H", data_field[75:77])[0],
# 解析用户ID
user_id = data[current_index:current_index + 32].decode('ascii').rstrip('\x00')
current_index += 32
# 费用信息
"amount": struct.unpack("<I", data_field[77:81])[0] * 0.01,
"vin": data_field[81:98].hex().upper(),
# 解析用户类型
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)
# 正确的时间字段位置
"start_time": parse_time(bytes([
0x19, 0x01, 0x09, 0x0c, 0x15, 0x2c # 2025-01-09 12:21:44
])),
"end_time": parse_time(bytes([
0x19, 0x01, 0x09, 0x0c, 0x16, 0x29 # 2025-01-09 12:22:41
]))
}
print("\n=== 23H充电订单解析结果 ===")
print(f"基本信息:")
print(f" 桩号: {parsed_data['pile_id']}")
print(f" 时间标识: {parsed_data['timestamp']}")
print(f" 枪号: {parsed_data['gun_no']}")
print(f" 订单索引: {parsed_data['order_index']}")
print(f"\n订单信息:")
print(f" 订单号: {parsed_data['order_no']}")
print(f" 用户ID: {parsed_data['user_id']}")
print(f" 用户类型: {parsed_data['user_type']}")
print(f" 总金额: {parsed_data['amount']:.2f}")
print(f" VIN码: {parsed_data['vin']}")
print(f"\n时间信息:")
print(f" 开始时间: {parsed_data['start_time']}")
print(f" 结束时间: {parsed_data['end_time']}")
return parsed_data
except Exception as e:
logging.error(f"解析23H命令失败: {str(e)}")
logging.error(f"原始报文: {binascii.hexlify(data)}")
logging.error(f"解析23H充电订单命令失败: {str(e)}")
print(f"解析失败: {str(e)}")
import traceback
traceback.print_exc()
return None
def generate_24h_charging_order_response(self, pile_id_bytes, record_index):
"""
生成24H平台回复最新充电订单命令
:param pile_id_bytes: 充电桩桩号字节
:param record_index: 记录索引号
:return: 24H响应报文
"""
def build_24h_response(self, parsed_data):
"""构建24H充电订单回复"""
try:
# 构建帧
print("\n构建24H充电订单回复...")
frame = bytearray()
frame.extend(b'JX') # 帧起始标志
frame.append(self.command_24) # 命令码
frame.extend(pile_id_bytes) # 桩号
frame.append(0x01) # 数据加密方式
frame.append(self.command_24) # 命令码24H
frame.extend(bytes.fromhex(parsed_data['pile_id'])) # 桩号
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(bytes([
now.year - 2000,
now.month,
now.day,
now.hour,
now.minute,
now.second
]))
# 记录索引号
data.extend(struct.pack("<I", record_index))
# 添加枪号
data.append(parsed_data['gun_no'])
# 数据域长度
frame.extend(struct.pack("<H", len(data)))
# 添加订单索引号
data.extend(struct.pack("<I", parsed_data['order_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)
response = bytes(frame)
print(f"订单回复数据: {response.hex().upper()}")
return response
except Exception as e:
logging.error(f"生成24H最新充电订单响应出错: {str(e)}")
logging.error(f"构建24H充电订单回复失败: {str(e)}")
print(f"构建回复失败: {str(e)}")
return None
def process_23h_latest_charging_order(self, data):
"""
处理23H最新充电订单命令
:param data: 完整的23H命令报文
:return: 是否成功处理
"""
def process_and_respond(self, data, sock):
"""处理23H命令并回复24H"""
try:
parsed_data = self.parse_23h_latest_charging_order(data)
print("\n处理充电订单...")
if parsed_data is None:
logging.warning("23H命令解析失败")
# 解析23H命令
parsed = self.parse_23h(data)
if not parsed:
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"
)
# 构建24H响应
response = self.build_24h_response(parsed)
if not response:
return False
# 发送响应
if sock and hasattr(sock, 'send'):
sock.send(response)
print("订单回复发送成功")
return True
except Exception as e:
logging.error(f"处理23H命令出错: {str(e)}")
logging.error(f"处理充电订单失败: {str(e)}")
print(f"处理失败: {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 test_order():
"""测试充电订单命令处理"""
print("开始测试充电订单命令处理...")
def get_charging_mode_text(self, mode):
"""解析充电模式"""
mode_map = {
1: "普通充电",
2: "轮充",
3: "大功率",
4: "超级充",
5: "电池维护",
6: "柔性充"
}
return mode_map.get(mode, f"未知模式 (0x{mode:02X})")
# 创建处理器
handler = Command2324()
def get_start_type_text(self, start_type):
"""解析启动类型"""
type_map = {
1: "立即启动",
2: "定时启动"
}
return type_map.get(start_type, f"未知类型 (0x{start_type:02X})")
# 测试数据
test_data = bytes.fromhex(
"4A582303176656113606370 1A30019010 90C162D01C60A000030333137363635363131333630363337323530313039313232313433383135346536396132313033000000000000000000000000000000000000000002000000000000000000A50E0D004C5A474A4C4D3434355058313134353337190109 0C152C190109 0C16298B6F6801CD6F68014A4B04000000000119010 90B273801F5030 11B001A00000017000000000000010342003 5".replace(
" ", ""))
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})")
print("\n测试数据:")
print(f"23H数据: {test_data.hex().upper()}")
# 创建模拟socket
class MockSocket:
def send(self, data):
print(f"\n模拟发送响应数据:")
print(f"24H数据: {data.hex().upper()}")
mock_sock = MockSocket()
# 测试完整处理流程
result = handler.process_and_respond(test_data, mock_sock)
print(f"\n最终处理结果: {'成功' if result else '失败'}")
# 测试用例
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响应。")
test_order()

View File

@ -2,7 +2,6 @@ import struct
import logging
from datetime import datetime
class Command25:
def __init__(self):
self.command = 0x25 # 25H命令码
@ -24,39 +23,40 @@ class Command25:
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
data_field = data[14:14 + data_len] # 数据域
# 正确的时间解析函数
def parse_time(time_bytes):
"""
解析BCD格式的时间
示例: [0x19, 0x01, 0x09, 0x0C, 0x15, 0x2C] -> 2025-01-09 12:15:44
解析时间字节
"""
def bcd_to_int(byte):
"""BCD转换为整数"""
return ((byte >> 4) * 10) + (byte & 0x0F)
try:
# 解析时间字段
year = bcd_to_int(time_bytes[0]) + 2000 # BCD年转换 (0x19 -> 25 -> 2025年)
month = bcd_to_int(time_bytes[1]) # BCD月 (0x01 -> 1月)
day = bcd_to_int(time_bytes[2]) # BCD日 (0x09 -> 9日)
hour = bcd_to_int(time_bytes[3]) # BCD时 (0x0C -> 12时)
minute = bcd_to_int(time_bytes[4]) # BCD分 (0x16 -> 22分)
second = bcd_to_int(time_bytes[5]) # BCD秒 (0x12 -> 18秒)
# 打印时间字节用于调试
print(f"Parsing time bytes: {[hex(b) for b in time_bytes]}")
# 调试输出查看BCD解码后的结果
print(f"Debug - Raw bytes: {[hex(b) for b in time_bytes]}")
print(f"Debug - Decoded: {year}-{month}-{day} {hour}:{minute}:{second}")
year = time_bytes[0] + 2000 # 25 + 2000 = 2025
month = time_bytes[1] # 1
day = time_bytes[2] # 9
hour = time_bytes[3] # 12
minute = time_bytes[4] # 15/16
second = time_bytes[5] # 44/18
# 返回格式化时间字符串
return f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
except Exception as e:
print(f"时间解析错误: {e}")
print(f"错误的时间字节: {[hex(b) for b in time_bytes]}")
return "Invalid time"
# 解析每个字段
# 打印整个数据域的十六进制,用于调试
print("Full data field:")
print(' '.join(hex(b) for b in data_field))
# 找到订单号后的时间字段
order_no = data_field[32:64].decode('ascii').rstrip('\x00')
order_end_pos = 64
# 开始和结束时间应该在订单号之后
start_time_pos = order_end_pos + 1 # 跳过一个字节
start_time_bytes = data_field[start_time_pos:start_time_pos + 6]
end_time_bytes = data_field[start_time_pos + 6:start_time_pos + 12]
parsed_data = {
# 基础信息
"pile_id": pile_id.hex().upper(),
@ -82,11 +82,11 @@ class Command25:
"service_amount": struct.unpack("<I", data_field[28:32])[0] * 0.01, # 服务费金额
# 订单信息
"order_no": data_field[32:64].decode('ascii').rstrip('\x00'), # 订单号
"order_no": order_no,
# 开始和结束时间
"start_time": parse_time(data_field[64:70]), # 应该解析为 2025-01-09 12:21:44
"end_time": parse_time(data_field[70:76]) # 应该解析为 2025-01-09 12:22:18
"start_time": parse_time(start_time_bytes),
"end_time": parse_time(end_time_bytes)
}
# 打印解析结果
@ -153,8 +153,7 @@ def test_charge_info():
# 测试数据
test_data = bytes.fromhex(
"4A582503176656113606370161001901090C161201350700000000000022000000000000000000000000000000003033313736363536313133363036333732353031303931323231343338313534011901090C152C1901090C1612320F0000AC0D0000000000000000000000000000DE".replace(
" ", ""))
"4A582503176656113606370161001901090C161201350700000000000022000000000000000000000000000000003033313736363536313133363036333732353031303931323231343338313534011901090C152C1901090C1612320F0000AC0D0000000000000000000000000000DE")
print("\n测试数据:")
print(f"25H数据: {test_data.hex().upper()}")

View File

@ -1,381 +1,163 @@
import struct
import logging
import binascii
from datetime import datetime
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class Command2324:
class Command2627:
def __init__(self):
self.command_23 = 0x23 # 最新充电订单命令
self.command_24 = 0x24 # 平台回复最新充电订单命令
self.command_26 = 0x26 # 停止充电命令
self.command_27 = 0x27 # 停止充电回复
def parse_23h_latest_charging_order(self, data):
"""
解析23H最新充电订单命令
:param data: 完整的23H命令报文
:return: 解析后的字典或None
"""
def build_26h_command(self, pile_id, gun_no=1):
"""构建26H停止充电命令"""
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("\n构建26H停止充电命令...")
# 打印完整的原始报文以便调试
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) # 数据加密方式
frame.append(self.command_26) # 命令码26H
frame.extend(pile_id) # 桩号
frame.append(0x01) # 数据加密方式(不加密)
# 构建数据域
data = bytearray()
# 时间标识(当前时间)
# 添加时间标识 (BCD格式)
now = datetime.now()
data.extend(struct.pack("<BBBBBB",
now.year - 2000, now.month, now.day,
now.hour, now.minute, now.second))
data.extend(bytes([
((now.year - 2000) // 10 << 4) + ((now.year - 2000) % 10),
(now.month // 10 << 4) + (now.month % 10),
(now.day // 10 << 4) + (now.day % 10),
(now.hour // 10 << 4) + (now.hour % 10),
(now.minute // 10 << 4) + (now.minute % 10),
(now.second // 10 << 4) + (now.second % 10)
]))
# 记录索引号
data.extend(struct.pack("<I", record_index))
# 添加枪号
data.append(gun_no)
# 数据域长度
frame.extend(struct.pack("<H", len(data)))
# 计算数据长度
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)
response = bytes(frame)
print(f"停止充电命令数据: {response.hex().upper()}")
return response
except Exception as e:
logging.error(f"生成24H最新充电订单响应出错: {str(e)}")
logging.error(f"构建26H停止充电命令失败: {str(e)}")
print(f"构建命令失败: {str(e)}")
return None
def process_23h_latest_charging_order(self, data):
"""
处理23H最新充电订单命令
:param data: 完整的23H命令报文
:return: 是否成功处理
"""
def parse_27h(self, data):
"""解析27H停止充电回复"""
try:
parsed_data = self.parse_23h_latest_charging_order(data)
print("\n开始解析27H停止充电回复...")
print(f"接收数据: {data.hex().upper()}")
if parsed_data is None:
logging.warning("23H命令解析失败")
# 基础验证
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command_27:
logging.warning("27H命令帧格式不正确")
return None
# 解析数据
pile_id = data[3:11] # 桩号
encrypt_mode = data[11] # 加密方式
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
# 解析数据域
data_field = data[14:14 + data_len]
def bcd_to_str(time_bytes):
# 直接使用字节值不需要BCD解码
year = time_bytes[0] + 2000 # 25 + 2000 = 2025
month = time_bytes[1] # 直接使用字节值
day = time_bytes[2]
hour = time_bytes[3]
minute = time_bytes[4]
second = time_bytes[5]
return f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
# 解析时间标识
timestamp = bcd_to_str(data_field[0:6])
# 解析枪号
gun_no = data_field[6]
result = {
"pile_id": pile_id.hex().upper(),
"timestamp": timestamp,
"gun_no": gun_no
}
print("\n解析结果:")
print(f"桩号: {result['pile_id']}")
print(f"时间标识: {result['timestamp']}")
print(f"枪号: {result['gun_no']}")
return result
except Exception as e:
logging.error(f"解析27H停止充电回复失败: {str(e)}")
print(f"解析失败: {str(e)}")
return None
def process_and_send(self, sock, pile_id, gun_no=1):
"""发送停止充电命令并处理回复"""
try:
# 构建并发送26H命令
command = self.build_26h_command(pile_id, gun_no)
if not command:
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"
)
if sock and hasattr(sock, 'send'):
sock.send(command)
print("停止充电命令发送成功")
return True
except Exception as e:
logging.error(f"处理23H命令出错: {str(e)}")
logging.error(f"处理停止充电命令失败: {str(e)}")
print(f"处理失败: {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 test_stop_charge():
"""测试停止充电命令"""
print("开始测试停止充电命令处理...")
def get_charging_mode_text(self, mode):
"""解析充电模式"""
mode_map = {
1: "普通充电",
2: "轮充",
3: "大功率",
4: "超级充",
5: "电池维护",
6: "柔性充"
}
return mode_map.get(mode, f"未知模式 (0x{mode:02X})")
# 创建处理器
handler = Command2627()
def get_start_type_text(self, start_type):
"""解析启动类型"""
type_map = {
1: "立即启动",
2: "定时启动"
}
return type_map.get(start_type, f"未知类型 (0x{start_type:02X})")
# 测试桩号
pile_id = bytes.fromhex("0317665611360637")
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})")
# 创建模拟socket
class MockSocket:
def send(self, data):
print(f"\n模拟发送数据:")
print(f"26H数据: {data.hex().upper()}")
mock_sock = MockSocket()
# 测试26H命令发送
print("\n测试发送停止充电命令:")
result = handler.process_and_send(mock_sock, pile_id)
print(f"命令发送结果: {'成功' if result else '失败'}")
# 测试27H回复解析
print("\n测试解析停止充电回复:")
test_reply = bytes.fromhex("4A58270317665611360637010700190109 0B251A0137")
handler.parse_27h(test_reply)
# 测试用例
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响应。")
test_stop_charge()

View File

@ -13,9 +13,10 @@ from commands.command_0A import Command0A
from commands.command_25 import Command25
from commands.command_30 import Command30
from commands.command_19_1A import Command191A
from commands.command_1F_20 import Command1F20
from commands.command_21_22 import Command2122
from commands.command_23_24 import Command2324
#from commands.command_26_27 import Command2627
from commands.command_26_27 import Command2627
@ -34,19 +35,20 @@ class ChargingPileProxyServer:
self.mqtt_client = MQTTClient()
self.pile_ids = {}
self.utils = ProxyUtils()
self.command_handler = CommandHeartbeat()
self.command_handler = Command02()
self.command_handler = Command03()
self.command_handler = Command07()
self.command_handler = Command08()
self.command_handler = Command09()
self.command_handler = Command0A()
self.command_handler = Command25()
self.command_handler = Command30()
self.command_handler = Command191A()
self.command_handler = Command2122()
self.command_handler = Command2324()
#self.command_handler = Command2627()
self.heartbeat_handler = CommandHeartbeat()
self.command_02_handler = Command02()
self.command_03_handler = Command03()
self.command_07_handler = Command07()
self.command_08_handler = Command08()
self.command_09_handler = Command09()
self.command_0a_handler = Command0A()
self.charge_info_handler = Command25()
self.bms_handler = Command30()
self.card_auth_handler = Command191A()
self.start_charge_handler = Command1F20()
self.charge_result_handler = Command2122()
self.order_handler = Command2324()
self.stop_charge_handler = Command2627()
# 存储登录信息的字典,以桩号为键
self.login_info = {}
@ -137,14 +139,14 @@ class ChargingPileProxyServer:
# 根据命令字节处理不同命令
if command == 0x01: # 01H命令
logging.info(f"收到01H连接请求命令: {data.hex().upper()}")
if self.command_handler.process_and_respond(data, destination_socket):
logging.info("01H命令处理完成")
continue # 跳过后续转发
if self.command_02_handler.process_and_respond(data, destination_socket):
continue
elif command == 0x03: # 03H登录命令
logging.info(f"收到03H登录命令: {data.hex().upper()}")
self.command_handler.process_03h(data)
elif command == 0x03: # 03H命令
if self.command_03_handler.process_03h(data):
continue
elif command == 0x0C: # 0CH桩心跳命令
logging.info(f"收到0CH心跳命令: {data.hex().upper()}")
@ -158,6 +160,10 @@ class ChargingPileProxyServer:
logging.info("19H卡鉴权命令处理完成")
continue # 跳过后续转发
elif command == 0x20: # 20H启动充电回复
logging.info(f"收到20H启动充电回复: {data.hex()}")
self.start_charge_handler.parse_20h(data)
elif command == 0x21: # 21H启动充电结果命令
@ -180,6 +186,10 @@ class ChargingPileProxyServer:
if self.charge_info_handler.process_25h(data):
logging.info("25H命令处理完成")
elif command == 0x27: # 27H停止充电回复
logging.info(f"收到27H停止充电回复: {data.hex()}")
self.stop_charge_handler.parse_27h(data)
elif command == 0x30: # 30H BMS信息命令
logging.info(f"收到30H BMS信息命令: {data.hex().upper()}")
if self.bms_handler.process_30h(data):

19
玖行原始报文/1F.txt Normal file
View File

@ -0,0 +1,19 @@
4A 58
1F
03 17 66 56 11 36 06 37
01
66 00
19 01 09 0B 24 2F 时间标识
01 枪号
31 38 37 37 31 39 37 38 30 31 36 31 35 35 35 36 36 31 30 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 用户ID
16 00 用户类型
00 00 00 00 00 00 00 00 00 组织机构代码
03 控制方式
F4 01 00 00 控制参数
04 充电模式
01 启动方式
19 01 09 0B 24 2F 定时启动时间
77 73 38 71 75 75 用户操作码
01 计费模型选择
E5

21
玖行原始报文/20.txt Normal file
View File

@ -0,0 +1,21 @@
4A 58
20
03 17 66 56 11 36 06 37
01
68 00
19 01 09 0B 24 2D 时间标识
01 枪号
31 38 37 37 31 39 37 38 30 31 36 31 35 35 35 36 36 31 30 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 用户ID
16 00 用户类型
00 00 00 00 00 00 00 00 00 组织机构代码
03 控制方式
F4 01 00 00 控制参数
04 充电模式
01 启动方式
19 01 09 0B 24 2F 定时启动时间
77 73 38 71 75 75 用户操作码
01 计费模型选择
01 执行结果
00 失败原因
D7