no message
This commit is contained in:
parent
d097e79f71
commit
b6130bebcc
Binary file not shown.
Binary file not shown.
201
charging_pile_proxy/commands/command_1F_20.py
Normal file
201
charging_pile_proxy/commands/command_1F_20.py
Normal 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()
|
@ -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()
|
@ -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()}")
|
||||
|
@ -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()
|
Binary file not shown.
@ -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
19
玖行原始报文/1F.txt
Normal 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
21
玖行原始报文/20.txt
Normal 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
|
Loading…
x
Reference in New Issue
Block a user