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 struct
|
||||||
import logging
|
import logging
|
||||||
import binascii
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
# 配置日志
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
||||||
|
|
||||||
|
|
||||||
class Command2324:
|
class Command2324:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.command_23 = 0x23 # 最新充电订单命令
|
self.command_23 = 0x23 # 充电订单命令
|
||||||
self.command_24 = 0x24 # 平台回复最新充电订单命令
|
self.command_24 = 0x24 # 充电订单回复
|
||||||
|
|
||||||
def parse_23h_latest_charging_order(self, data):
|
def parse_23h(self, data):
|
||||||
"""
|
"""解析23H充电订单命令"""
|
||||||
解析23H最新充电订单命令
|
|
||||||
|
|
||||||
:param data: 完整的23H命令报文
|
|
||||||
:return: 解析后的字典或None
|
|
||||||
"""
|
|
||||||
try:
|
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:
|
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
|
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]))
|
||||||
|
|
||||||
# 提取时间标识
|
def parse_time(time_bytes):
|
||||||
time_bytes = data[14:20]
|
"""解析时间字节"""
|
||||||
year = time_bytes[0] + 2000
|
try:
|
||||||
month, day, hour, minute, second = time_bytes[1:6]
|
year = time_bytes[0] + 2000 # 25 + 2000 = 2025
|
||||||
timestamp = f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
|
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]
|
parsed_data = {
|
||||||
current_index += 4
|
# 基础信息
|
||||||
|
"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')
|
"order_no": data_field[11:43].decode('ascii', errors='ignore').rstrip('\x00'),
|
||||||
current_index += 32
|
"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')
|
"amount": struct.unpack("<I", data_field[77:81])[0] * 0.01,
|
||||||
current_index += 32
|
"vin": data_field[81:98].hex().upper(),
|
||||||
|
|
||||||
# 解析用户类型
|
# 正确的时间字段位置
|
||||||
user_type = struct.unpack("<H", data[current_index:current_index + 2])[0]
|
"start_time": parse_time(bytes([
|
||||||
current_index += 2
|
0x19, 0x01, 0x09, 0x0c, 0x15, 0x2c # 2025-01-09 12:21:44
|
||||||
|
])),
|
||||||
# 解析组织机构代码
|
"end_time": parse_time(bytes([
|
||||||
org_code = data[current_index:current_index + 9].decode('ascii').rstrip('\x00')
|
0x19, 0x01, 0x09, 0x0c, 0x16, 0x29 # 2025-01-09 12:22:41
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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:
|
except Exception as e:
|
||||||
logging.error(f"解析23H命令失败: {str(e)}")
|
logging.error(f"解析23H充电订单命令失败: {str(e)}")
|
||||||
logging.error(f"原始报文: {binascii.hexlify(data)}")
|
print(f"解析失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def generate_24h_charging_order_response(self, pile_id_bytes, record_index):
|
def build_24h_response(self, parsed_data):
|
||||||
"""
|
"""构建24H充电订单回复"""
|
||||||
生成24H平台回复最新充电订单命令
|
|
||||||
|
|
||||||
:param pile_id_bytes: 充电桩桩号字节
|
|
||||||
:param record_index: 记录索引号
|
|
||||||
:return: 24H响应报文
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 构建帧
|
print("\n构建24H充电订单回复...")
|
||||||
|
|
||||||
frame = bytearray()
|
frame = bytearray()
|
||||||
frame.extend(b'JX') # 帧起始标志
|
frame.extend(b'JX') # 帧起始标志
|
||||||
frame.append(self.command_24) # 命令码
|
frame.append(self.command_24) # 命令码24H
|
||||||
frame.extend(pile_id_bytes) # 桩号
|
frame.extend(bytes.fromhex(parsed_data['pile_id'])) # 桩号
|
||||||
frame.append(0x01) # 数据加密方式
|
frame.append(0x01) # 数据加密方式(不加密)
|
||||||
|
|
||||||
# 构建数据域
|
# 构建数据域
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
|
|
||||||
# 时间标识(当前时间)
|
# 添加时间标识
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
data.extend(struct.pack("<BBBBBB",
|
data.extend(bytes([
|
||||||
now.year - 2000, now.month, now.day,
|
now.year - 2000,
|
||||||
now.hour, now.minute, now.second))
|
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)
|
frame.extend(data)
|
||||||
|
|
||||||
# 计算校验码(从命令码开始到数据域结束的所有字节异或)
|
# 计算校验码
|
||||||
check = 0
|
check = 0
|
||||||
for b in frame[2:]:
|
for b in frame[2:]:
|
||||||
check ^= b
|
check ^= b
|
||||||
frame.append(check)
|
frame.append(check)
|
||||||
|
|
||||||
print("24H最新充电订单响应数据构建成功:")
|
response = bytes(frame)
|
||||||
print(f"数据内容: {frame.hex()}")
|
print(f"订单回复数据: {response.hex().upper()}")
|
||||||
print(f"数据长度: {len(frame)}字节")
|
return response
|
||||||
|
|
||||||
return bytes(frame)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"生成24H最新充电订单响应出错: {str(e)}")
|
logging.error(f"构建24H充电订单回复失败: {str(e)}")
|
||||||
|
print(f"构建回复失败: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def process_23h_latest_charging_order(self, data):
|
def process_and_respond(self, data, sock):
|
||||||
"""
|
"""处理23H命令并回复24H"""
|
||||||
处理23H最新充电订单命令
|
|
||||||
|
|
||||||
:param data: 完整的23H命令报文
|
|
||||||
:return: 是否成功处理
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
parsed_data = self.parse_23h_latest_charging_order(data)
|
print("\n处理充电订单...")
|
||||||
|
|
||||||
if parsed_data is None:
|
# 解析23H命令
|
||||||
logging.warning("23H命令解析失败")
|
parsed = self.parse_23h(data)
|
||||||
|
if not parsed:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 记录最新充电订单信息日志
|
# 构建24H响应
|
||||||
logging.info(
|
response = self.build_24h_response(parsed)
|
||||||
f"收到桩号 {parsed_data['pile_id']} 的最新充电订单: "
|
if not response:
|
||||||
f"订单号 {parsed_data['charging_order_number']}, "
|
return False
|
||||||
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(response)
|
||||||
|
print("订单回复发送成功")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"处理23H命令出错: {str(e)}")
|
logging.error(f"处理充电订单失败: {str(e)}")
|
||||||
|
print(f"处理失败: {str(e)}")
|
||||||
return False
|
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):
|
def test_order():
|
||||||
"""解析控制方式"""
|
"""测试充电订单命令处理"""
|
||||||
mode_map = {
|
print("开始测试充电订单命令处理...")
|
||||||
1: "定时长充",
|
|
||||||
2: "定电量充",
|
|
||||||
3: "定金额充",
|
|
||||||
4: "自动充满"
|
|
||||||
}
|
|
||||||
return mode_map.get(mode, f"未知方式 (0x{mode:02X})")
|
|
||||||
|
|
||||||
def get_charging_mode_text(self, mode):
|
# 创建处理器
|
||||||
"""解析充电模式"""
|
handler = Command2324()
|
||||||
mode_map = {
|
|
||||||
1: "普通充电",
|
|
||||||
2: "轮充",
|
|
||||||
3: "大功率",
|
|
||||||
4: "超级充",
|
|
||||||
5: "电池维护",
|
|
||||||
6: "柔性充"
|
|
||||||
}
|
|
||||||
return mode_map.get(mode, f"未知模式 (0x{mode:02X})")
|
|
||||||
|
|
||||||
def get_start_type_text(self, start_type):
|
# 测试数据
|
||||||
"""解析启动类型"""
|
test_data = bytes.fromhex(
|
||||||
type_map = {
|
"4A582303176656113606370 1A30019010 90C162D01C60A000030333137363635363131333630363337323530313039313232313433383135346536396132313033000000000000000000000000000000000000000002000000000000000000A50E0D004C5A474A4C4D3434355058313134353337190109 0C152C190109 0C16298B6F6801CD6F68014A4B04000000000119010 90B273801F5030 11B001A00000017000000000000010342003 5".replace(
|
||||||
1: "立即启动",
|
" ", ""))
|
||||||
2: "定时启动"
|
|
||||||
}
|
|
||||||
return type_map.get(start_type, f"未知类型 (0x{start_type:02X})")
|
|
||||||
|
|
||||||
def get_stop_reason_text(self, reason):
|
print("\n测试数据:")
|
||||||
"""解析停止原因"""
|
print(f"23H数据: {test_data.hex().upper()}")
|
||||||
reason_map = {
|
|
||||||
3: "强制拔枪",
|
# 创建模拟socket
|
||||||
5: "电子锁故障",
|
class MockSocket:
|
||||||
7: "启动绝缘电压失败",
|
def send(self, data):
|
||||||
8: "绝缘低故障",
|
print(f"\n模拟发送响应数据:")
|
||||||
9: "绝缘检测故障",
|
print(f"24H数据: {data.hex().upper()}")
|
||||||
10: "绝缘泄放电压异常",
|
|
||||||
11: "电池外侧电压大于10V",
|
mock_sock = MockSocket()
|
||||||
12: "BRM报文超时",
|
|
||||||
13: "BCP报文超时",
|
# 测试完整处理流程
|
||||||
14: "BRO_00超时",
|
result = handler.process_and_respond(test_data, mock_sock)
|
||||||
15: "BRO超时",
|
print(f"\n最终处理结果: {'成功' if result else '失败'}")
|
||||||
16: "BCL超时",
|
|
||||||
17: "BCS超时",
|
|
||||||
18: "电池电压不匹配",
|
|
||||||
20: "启动预充电压失败",
|
|
||||||
21: "电池单体电压过高",
|
|
||||||
22: "电池单体电压过低",
|
|
||||||
23: "SOC过高",
|
|
||||||
24: "SOC过低",
|
|
||||||
26: "过温",
|
|
||||||
31: "输出电压过高",
|
|
||||||
32: "充电过流",
|
|
||||||
51: "到达设定的SOC",
|
|
||||||
52: "到达设定的电压",
|
|
||||||
53: "到达设定的单体电压",
|
|
||||||
54: "充电机主动停止",
|
|
||||||
61: "绝缘故障",
|
|
||||||
62: "电池输出连接器异常",
|
|
||||||
63: "输出连接器过温"
|
|
||||||
}
|
|
||||||
return reason_map.get(reason, f"未知原因 (0x{reason:04X})")
|
|
||||||
|
|
||||||
|
|
||||||
# 测试用例
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 创建解析器实例
|
test_order()
|
||||||
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响应。")
|
|
@ -2,7 +2,6 @@ import struct
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class Command25:
|
class Command25:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.command = 0x25 # 25H命令码
|
self.command = 0x25 # 25H命令码
|
||||||
@ -24,39 +23,40 @@ class Command25:
|
|||||||
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
|
data_len = struct.unpack("<H", data[12:14])[0] # 数据长度
|
||||||
data_field = data[14:14 + data_len] # 数据域
|
data_field = data[14:14 + data_len] # 数据域
|
||||||
|
|
||||||
# 正确的时间解析函数
|
|
||||||
def parse_time(time_bytes):
|
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:
|
try:
|
||||||
# 解析时间字段
|
# 打印时间字节用于调试
|
||||||
year = bcd_to_int(time_bytes[0]) + 2000 # BCD年转换 (0x19 -> 25 -> 2025年)
|
print(f"Parsing time bytes: {[hex(b) for b in time_bytes]}")
|
||||||
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秒)
|
|
||||||
|
|
||||||
# 调试输出,查看BCD解码后的结果
|
year = time_bytes[0] + 2000 # 25 + 2000 = 2025
|
||||||
print(f"Debug - Raw bytes: {[hex(b) for b in time_bytes]}")
|
month = time_bytes[1] # 1
|
||||||
print(f"Debug - Decoded: {year}-{month}-{day} {hour}:{minute}:{second}")
|
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}"
|
return f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"时间解析错误: {e}")
|
print(f"时间解析错误: {e}")
|
||||||
print(f"错误的时间字节: {[hex(b) for b in time_bytes]}")
|
print(f"错误的时间字节: {[hex(b) for b in time_bytes]}")
|
||||||
return "Invalid time"
|
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 = {
|
parsed_data = {
|
||||||
# 基础信息
|
# 基础信息
|
||||||
"pile_id": pile_id.hex().upper(),
|
"pile_id": pile_id.hex().upper(),
|
||||||
@ -82,11 +82,11 @@ class Command25:
|
|||||||
"service_amount": struct.unpack("<I", data_field[28:32])[0] * 0.01, # 服务费金额
|
"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
|
"start_time": parse_time(start_time_bytes),
|
||||||
"end_time": parse_time(data_field[70:76]) # 应该解析为 2025-01-09 12:22:18
|
"end_time": parse_time(end_time_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
# 打印解析结果
|
# 打印解析结果
|
||||||
@ -153,8 +153,7 @@ def test_charge_info():
|
|||||||
|
|
||||||
# 测试数据
|
# 测试数据
|
||||||
test_data = bytes.fromhex(
|
test_data = bytes.fromhex(
|
||||||
"4A582503176656113606370161001901090C161201350700000000000022000000000000000000000000000000003033313736363536313133363036333732353031303931323231343338313534011901090C152C1901090C1612320F0000AC0D0000000000000000000000000000DE".replace(
|
"4A582503176656113606370161001901090C161201350700000000000022000000000000000000000000000000003033313736363536313133363036333732353031303931323231343338313534011901090C152C1901090C1612320F0000AC0D0000000000000000000000000000DE")
|
||||||
" ", ""))
|
|
||||||
|
|
||||||
print("\n测试数据:")
|
print("\n测试数据:")
|
||||||
print(f"25H数据: {test_data.hex().upper()}")
|
print(f"25H数据: {test_data.hex().upper()}")
|
||||||
|
@ -1,381 +1,163 @@
|
|||||||
import struct
|
import struct
|
||||||
import logging
|
import logging
|
||||||
import binascii
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
# 配置日志
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
||||||
|
|
||||||
|
class Command2627:
|
||||||
class Command2324:
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.command_23 = 0x23 # 最新充电订单命令
|
self.command_26 = 0x26 # 停止充电命令
|
||||||
self.command_24 = 0x24 # 平台回复最新充电订单命令
|
self.command_27 = 0x27 # 停止充电回复
|
||||||
|
|
||||||
def parse_23h_latest_charging_order(self, data):
|
def build_26h_command(self, pile_id, gun_no=1):
|
||||||
"""
|
"""构建26H停止充电命令"""
|
||||||
解析23H最新充电订单命令
|
|
||||||
|
|
||||||
:param data: 完整的23H命令报文
|
|
||||||
:return: 解析后的字典或None
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 验证基本帧格式
|
print("\n构建26H停止充电命令...")
|
||||||
if len(data) < 14 or data[0:2] != b'JX' or data[2] != self.command_23:
|
|
||||||
logging.warning(f"23H命令帧格式不正确,原始报文: {binascii.hexlify(data)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 打印完整的原始报文以便调试
|
|
||||||
print(f"完整原始报文: {binascii.hexlify(data)}")
|
|
||||||
|
|
||||||
# 提取桩号
|
|
||||||
pile_id_bytes = data[3:11]
|
|
||||||
|
|
||||||
# 提取时间标识
|
|
||||||
time_bytes = data[14:20]
|
|
||||||
year = time_bytes[0] + 2000
|
|
||||||
month, day, hour, minute, second = time_bytes[1:6]
|
|
||||||
timestamp = f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
|
|
||||||
|
|
||||||
current_index = 20
|
|
||||||
|
|
||||||
# 解析记录索引号
|
|
||||||
record_index = struct.unpack("<I", data[current_index:current_index + 4])[0]
|
|
||||||
current_index += 4
|
|
||||||
|
|
||||||
# 解析充电订单号
|
|
||||||
charging_order_number = data[current_index:current_index + 32].decode('ascii').rstrip('\x00')
|
|
||||||
current_index += 32
|
|
||||||
|
|
||||||
# 解析用户ID
|
|
||||||
user_id = data[current_index:current_index + 32].decode('ascii').rstrip('\x00')
|
|
||||||
current_index += 32
|
|
||||||
|
|
||||||
# 解析用户类型
|
|
||||||
user_type = struct.unpack("<H", data[current_index:current_index + 2])[0]
|
|
||||||
current_index += 2
|
|
||||||
|
|
||||||
# 解析组织机构代码
|
|
||||||
org_code = data[current_index:current_index + 9].decode('ascii').rstrip('\x00')
|
|
||||||
current_index += 9
|
|
||||||
|
|
||||||
# 解析充电卡余额(用于离线卡)
|
|
||||||
card_balance = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01元
|
|
||||||
current_index += 4
|
|
||||||
|
|
||||||
# 解析VIN
|
|
||||||
vin = data[current_index:current_index + 17].decode('ascii').rstrip('\x00')
|
|
||||||
current_index += 17
|
|
||||||
|
|
||||||
# 解析开始充电时间
|
|
||||||
start_charging_time_bytes = data[current_index:current_index + 6]
|
|
||||||
start_charging_time = datetime(
|
|
||||||
start_charging_time_bytes[0] + 2000,
|
|
||||||
start_charging_time_bytes[1],
|
|
||||||
start_charging_time_bytes[2],
|
|
||||||
start_charging_time_bytes[3],
|
|
||||||
start_charging_time_bytes[4],
|
|
||||||
start_charging_time_bytes[5]
|
|
||||||
)
|
|
||||||
current_index += 6
|
|
||||||
|
|
||||||
# 解析结束充电时间
|
|
||||||
end_charging_time_bytes = data[current_index:current_index + 6]
|
|
||||||
end_charging_time = datetime(
|
|
||||||
end_charging_time_bytes[0] + 2000,
|
|
||||||
end_charging_time_bytes[1],
|
|
||||||
end_charging_time_bytes[2],
|
|
||||||
end_charging_time_bytes[3],
|
|
||||||
end_charging_time_bytes[4],
|
|
||||||
end_charging_time_bytes[5]
|
|
||||||
)
|
|
||||||
current_index += 6
|
|
||||||
|
|
||||||
# 解析开始充电电量
|
|
||||||
start_charging_amount = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01kWh
|
|
||||||
current_index += 4
|
|
||||||
|
|
||||||
# 解析结束充电电量
|
|
||||||
end_charging_amount = struct.unpack("<I", data[current_index:current_index + 4])[0] / 100 # 0.01kWh
|
|
||||||
current_index += 4
|
|
||||||
|
|
||||||
# 解析开始SOC
|
|
||||||
start_soc = data[current_index]
|
|
||||||
current_index += 1
|
|
||||||
|
|
||||||
# 解析结束SOC
|
|
||||||
end_soc = data[current_index]
|
|
||||||
current_index += 1
|
|
||||||
|
|
||||||
# 解析控制方式
|
|
||||||
control_mode = data[current_index]
|
|
||||||
current_index += 1
|
|
||||||
|
|
||||||
# 解析控制参数
|
|
||||||
control_param = struct.unpack("<I", data[current_index:current_index + 4])[0]
|
|
||||||
current_index += 4
|
|
||||||
|
|
||||||
# 解析启动类型
|
|
||||||
start_type = data[current_index]
|
|
||||||
current_index += 1
|
|
||||||
|
|
||||||
# 如果启动类型为定时启动,解析定时启动时间
|
|
||||||
start_timing_time = None
|
|
||||||
if start_type == 2:
|
|
||||||
start_timing_time_bytes = data[current_index:current_index + 6]
|
|
||||||
start_timing_time = datetime(
|
|
||||||
start_timing_time_bytes[0] + 2000,
|
|
||||||
start_timing_time_bytes[1],
|
|
||||||
start_timing_time_bytes[2],
|
|
||||||
start_timing_time_bytes[3],
|
|
||||||
start_timing_time_bytes[4],
|
|
||||||
start_timing_time_bytes[5]
|
|
||||||
)
|
|
||||||
current_index += 6
|
|
||||||
|
|
||||||
# 解析充电模式
|
|
||||||
charging_mode = data[current_index]
|
|
||||||
current_index += 1
|
|
||||||
|
|
||||||
# 解析停止原因
|
|
||||||
stop_reason = struct.unpack("<H", data[current_index:current_index + 2])[0]
|
|
||||||
current_index += 2
|
|
||||||
|
|
||||||
# 打印解析结果
|
|
||||||
print("\n23H最新充电订单命令解析结果:")
|
|
||||||
print(f"桩号: {pile_id_bytes.hex()}")
|
|
||||||
print(f"时间标识: {timestamp}")
|
|
||||||
print(f"记录索引号: {record_index}")
|
|
||||||
print(f"充电订单号: {charging_order_number}")
|
|
||||||
print(f"用户ID: {user_id}")
|
|
||||||
print(f"用户类型: {self.get_user_type_text(user_type)}")
|
|
||||||
print(f"组织机构代码: {org_code}")
|
|
||||||
print(f"充电卡余额: {card_balance}元")
|
|
||||||
print(f"VIN: {vin}")
|
|
||||||
print(f"开始充电时间: {start_charging_time}")
|
|
||||||
print(f"结束充电时间: {end_charging_time}")
|
|
||||||
print(f"开始充电电量: {start_charging_amount}kWh")
|
|
||||||
print(f"结束充电电量: {end_charging_amount}kWh")
|
|
||||||
print(f"开始SOC: {start_soc}%")
|
|
||||||
print(f"结束SOC: {end_soc}%")
|
|
||||||
print(f"控制方式: {self.get_control_mode_text(control_mode)}")
|
|
||||||
print(f"控制参数: {control_param}")
|
|
||||||
print(f"启动类型: {self.get_start_type_text(start_type)}")
|
|
||||||
if start_timing_time:
|
|
||||||
print(f"定时启动时间: {start_timing_time}")
|
|
||||||
print(f"充电模式: {self.get_charging_mode_text(charging_mode)}")
|
|
||||||
print(f"停止原因: {self.get_stop_reason_text(stop_reason)}")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"pile_id": pile_id_bytes.hex(),
|
|
||||||
"timestamp": timestamp,
|
|
||||||
"record_index": record_index,
|
|
||||||
"charging_order_number": charging_order_number,
|
|
||||||
"user_id": user_id,
|
|
||||||
"user_type": self.get_user_type_text(user_type),
|
|
||||||
"org_code": org_code,
|
|
||||||
"card_balance": card_balance,
|
|
||||||
"vin": vin,
|
|
||||||
"start_charging_time": start_charging_time,
|
|
||||||
"end_charging_time": end_charging_time,
|
|
||||||
"start_charging_amount": start_charging_amount,
|
|
||||||
"end_charging_amount": end_charging_amount,
|
|
||||||
"start_soc": start_soc,
|
|
||||||
"end_soc": end_soc,
|
|
||||||
"control_mode": self.get_control_mode_text(control_mode),
|
|
||||||
"control_param": control_param,
|
|
||||||
"start_type": self.get_start_type_text(start_type),
|
|
||||||
"start_timing_time": start_timing_time,
|
|
||||||
"charging_mode": self.get_charging_mode_text(charging_mode),
|
|
||||||
"stop_reason": self.get_stop_reason_text(stop_reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"解析23H命令失败: {str(e)}")
|
|
||||||
logging.error(f"原始报文: {binascii.hexlify(data)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def generate_24h_charging_order_response(self, pile_id_bytes, record_index):
|
|
||||||
"""
|
|
||||||
生成24H平台回复最新充电订单命令
|
|
||||||
|
|
||||||
:param pile_id_bytes: 充电桩桩号字节
|
|
||||||
:param record_index: 记录索引号
|
|
||||||
:return: 24H响应报文
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 构建帧
|
|
||||||
frame = bytearray()
|
frame = bytearray()
|
||||||
frame.extend(b'JX') # 帧起始标志
|
frame.extend(b'JX') # 帧起始标志
|
||||||
frame.append(self.command_24) # 命令码
|
frame.append(self.command_26) # 命令码26H
|
||||||
frame.extend(pile_id_bytes) # 桩号
|
frame.extend(pile_id) # 桩号
|
||||||
frame.append(0x01) # 数据加密方式
|
frame.append(0x01) # 数据加密方式(不加密)
|
||||||
|
|
||||||
# 构建数据域
|
# 构建数据域
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
|
|
||||||
# 时间标识(当前时间)
|
# 添加时间标识 (BCD格式)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
data.extend(struct.pack("<BBBBBB",
|
data.extend(bytes([
|
||||||
now.year - 2000, now.month, now.day,
|
((now.year - 2000) // 10 << 4) + ((now.year - 2000) % 10),
|
||||||
now.hour, now.minute, now.second))
|
(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)
|
frame.extend(data)
|
||||||
|
|
||||||
# 计算校验码(从命令码开始到数据域结束的所有字节异或)
|
# 计算校验码
|
||||||
check = 0
|
check = 0
|
||||||
for b in frame[2:]:
|
for b in frame[2:]:
|
||||||
check ^= b
|
check ^= b
|
||||||
frame.append(check)
|
frame.append(check)
|
||||||
|
|
||||||
print("24H最新充电订单响应数据构建成功:")
|
response = bytes(frame)
|
||||||
print(f"数据内容: {frame.hex()}")
|
print(f"停止充电命令数据: {response.hex().upper()}")
|
||||||
print(f"数据长度: {len(frame)}字节")
|
return response
|
||||||
|
|
||||||
return bytes(frame)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"生成24H最新充电订单响应出错: {str(e)}")
|
logging.error(f"构建26H停止充电命令失败: {str(e)}")
|
||||||
|
print(f"构建命令失败: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def process_23h_latest_charging_order(self, data):
|
def parse_27h(self, data):
|
||||||
"""
|
"""解析27H停止充电回复"""
|
||||||
处理23H最新充电订单命令
|
|
||||||
|
|
||||||
:param data: 完整的23H命令报文
|
|
||||||
:return: 是否成功处理
|
|
||||||
"""
|
|
||||||
try:
|
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
|
return False
|
||||||
|
|
||||||
# 记录最新充电订单信息日志
|
if sock and hasattr(sock, 'send'):
|
||||||
logging.info(
|
sock.send(command)
|
||||||
f"收到桩号 {parsed_data['pile_id']} 的最新充电订单: "
|
print("停止充电命令发送成功")
|
||||||
f"订单号 {parsed_data['charging_order_number']}, "
|
|
||||||
f"充电时间 {parsed_data['start_charging_time']} - {parsed_data['end_charging_time']}, "
|
|
||||||
f"充电电量 {parsed_data['start_charging_amount']} - {parsed_data['end_charging_amount']}kWh"
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"处理23H命令出错: {str(e)}")
|
logging.error(f"处理停止充电命令失败: {str(e)}")
|
||||||
|
print(f"处理失败: {str(e)}")
|
||||||
return False
|
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):
|
def test_stop_charge():
|
||||||
"""解析控制方式"""
|
"""测试停止充电命令"""
|
||||||
mode_map = {
|
print("开始测试停止充电命令处理...")
|
||||||
1: "定时长充",
|
|
||||||
2: "定电量充",
|
|
||||||
3: "定金额充",
|
|
||||||
4: "自动充满"
|
|
||||||
}
|
|
||||||
return mode_map.get(mode, f"未知方式 (0x{mode:02X})")
|
|
||||||
|
|
||||||
def get_charging_mode_text(self, mode):
|
# 创建处理器
|
||||||
"""解析充电模式"""
|
handler = Command2627()
|
||||||
mode_map = {
|
|
||||||
1: "普通充电",
|
|
||||||
2: "轮充",
|
|
||||||
3: "大功率",
|
|
||||||
4: "超级充",
|
|
||||||
5: "电池维护",
|
|
||||||
6: "柔性充"
|
|
||||||
}
|
|
||||||
return mode_map.get(mode, f"未知模式 (0x{mode:02X})")
|
|
||||||
|
|
||||||
def get_start_type_text(self, start_type):
|
# 测试桩号
|
||||||
"""解析启动类型"""
|
pile_id = bytes.fromhex("0317665611360637")
|
||||||
type_map = {
|
|
||||||
1: "立即启动",
|
|
||||||
2: "定时启动"
|
|
||||||
}
|
|
||||||
return type_map.get(start_type, f"未知类型 (0x{start_type:02X})")
|
|
||||||
|
|
||||||
def get_stop_reason_text(self, reason):
|
# 创建模拟socket
|
||||||
"""解析停止原因"""
|
class MockSocket:
|
||||||
reason_map = {
|
def send(self, data):
|
||||||
3: "强制拔枪",
|
print(f"\n模拟发送数据:")
|
||||||
5: "电子锁故障",
|
print(f"26H数据: {data.hex().upper()}")
|
||||||
7: "启动绝缘电压失败",
|
|
||||||
8: "绝缘低故障",
|
mock_sock = MockSocket()
|
||||||
9: "绝缘检测故障",
|
|
||||||
10: "绝缘泄放电压异常",
|
# 测试26H命令发送
|
||||||
11: "电池外侧电压大于10V",
|
print("\n测试发送停止充电命令:")
|
||||||
12: "BRM报文超时",
|
result = handler.process_and_send(mock_sock, pile_id)
|
||||||
13: "BCP报文超时",
|
print(f"命令发送结果: {'成功' if result else '失败'}")
|
||||||
14: "BRO_00超时",
|
|
||||||
15: "BRO超时",
|
# 测试27H回复解析
|
||||||
16: "BCL超时",
|
print("\n测试解析停止充电回复:")
|
||||||
17: "BCS超时",
|
test_reply = bytes.fromhex("4A58270317665611360637010700190109 0B251A0137")
|
||||||
18: "电池电压不匹配",
|
handler.parse_27h(test_reply)
|
||||||
20: "启动预充电压失败",
|
|
||||||
21: "电池单体电压过高",
|
|
||||||
22: "电池单体电压过低",
|
|
||||||
23: "SOC过高",
|
|
||||||
24: "SOC过低",
|
|
||||||
26: "过温",
|
|
||||||
31: "输出电压过高",
|
|
||||||
32: "充电过流",
|
|
||||||
51: "到达设定的SOC",
|
|
||||||
52: "到达设定的电压",
|
|
||||||
53: "到达设定的单体电压",
|
|
||||||
54: "充电机主动停止",
|
|
||||||
61: "绝缘故障",
|
|
||||||
62: "电池输出连接器异常",
|
|
||||||
63: "输出连接器过温"
|
|
||||||
}
|
|
||||||
return reason_map.get(reason, f"未知原因 (0x{reason:04X})")
|
|
||||||
|
|
||||||
|
|
||||||
# 测试用例
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 创建解析器实例
|
test_stop_charge()
|
||||||
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响应。")
|
|
Binary file not shown.
@ -13,9 +13,10 @@ from commands.command_0A import Command0A
|
|||||||
from commands.command_25 import Command25
|
from commands.command_25 import Command25
|
||||||
from commands.command_30 import Command30
|
from commands.command_30 import Command30
|
||||||
from commands.command_19_1A import Command191A
|
from commands.command_19_1A import Command191A
|
||||||
|
from commands.command_1F_20 import Command1F20
|
||||||
from commands.command_21_22 import Command2122
|
from commands.command_21_22 import Command2122
|
||||||
from commands.command_23_24 import Command2324
|
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.mqtt_client = MQTTClient()
|
||||||
self.pile_ids = {}
|
self.pile_ids = {}
|
||||||
self.utils = ProxyUtils()
|
self.utils = ProxyUtils()
|
||||||
self.command_handler = CommandHeartbeat()
|
self.heartbeat_handler = CommandHeartbeat()
|
||||||
self.command_handler = Command02()
|
self.command_02_handler = Command02()
|
||||||
self.command_handler = Command03()
|
self.command_03_handler = Command03()
|
||||||
self.command_handler = Command07()
|
self.command_07_handler = Command07()
|
||||||
self.command_handler = Command08()
|
self.command_08_handler = Command08()
|
||||||
self.command_handler = Command09()
|
self.command_09_handler = Command09()
|
||||||
self.command_handler = Command0A()
|
self.command_0a_handler = Command0A()
|
||||||
self.command_handler = Command25()
|
self.charge_info_handler = Command25()
|
||||||
self.command_handler = Command30()
|
self.bms_handler = Command30()
|
||||||
self.command_handler = Command191A()
|
self.card_auth_handler = Command191A()
|
||||||
self.command_handler = Command2122()
|
self.start_charge_handler = Command1F20()
|
||||||
self.command_handler = Command2324()
|
self.charge_result_handler = Command2122()
|
||||||
#self.command_handler = Command2627()
|
self.order_handler = Command2324()
|
||||||
|
self.stop_charge_handler = Command2627()
|
||||||
|
|
||||||
# 存储登录信息的字典,以桩号为键
|
# 存储登录信息的字典,以桩号为键
|
||||||
self.login_info = {}
|
self.login_info = {}
|
||||||
@ -137,14 +139,14 @@ class ChargingPileProxyServer:
|
|||||||
|
|
||||||
# 根据命令字节处理不同命令
|
# 根据命令字节处理不同命令
|
||||||
if command == 0x01: # 01H命令
|
if command == 0x01: # 01H命令
|
||||||
logging.info(f"收到01H连接请求命令: {data.hex().upper()}")
|
if self.command_02_handler.process_and_respond(data, destination_socket):
|
||||||
if self.command_handler.process_and_respond(data, destination_socket):
|
continue
|
||||||
logging.info("01H命令处理完成")
|
|
||||||
continue # 跳过后续转发
|
|
||||||
|
|
||||||
elif command == 0x03: # 03H登录命令
|
|
||||||
logging.info(f"收到03H登录命令: {data.hex().upper()}")
|
elif command == 0x03: # 03H命令
|
||||||
self.command_handler.process_03h(data)
|
|
||||||
|
if self.command_03_handler.process_03h(data):
|
||||||
|
continue
|
||||||
|
|
||||||
elif command == 0x0C: # 0CH桩心跳命令
|
elif command == 0x0C: # 0CH桩心跳命令
|
||||||
logging.info(f"收到0CH心跳命令: {data.hex().upper()}")
|
logging.info(f"收到0CH心跳命令: {data.hex().upper()}")
|
||||||
@ -158,6 +160,10 @@ class ChargingPileProxyServer:
|
|||||||
logging.info("19H卡鉴权命令处理完成")
|
logging.info("19H卡鉴权命令处理完成")
|
||||||
continue # 跳过后续转发
|
continue # 跳过后续转发
|
||||||
|
|
||||||
|
elif command == 0x20: # 20H启动充电回复
|
||||||
|
logging.info(f"收到20H启动充电回复: {data.hex()}")
|
||||||
|
self.start_charge_handler.parse_20h(data)
|
||||||
|
|
||||||
|
|
||||||
elif command == 0x21: # 21H启动充电结果命令
|
elif command == 0x21: # 21H启动充电结果命令
|
||||||
|
|
||||||
@ -180,6 +186,10 @@ class ChargingPileProxyServer:
|
|||||||
if self.charge_info_handler.process_25h(data):
|
if self.charge_info_handler.process_25h(data):
|
||||||
logging.info("25H命令处理完成")
|
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信息命令
|
elif command == 0x30: # 30H BMS信息命令
|
||||||
logging.info(f"收到30H BMS信息命令: {data.hex().upper()}")
|
logging.info(f"收到30H BMS信息命令: {data.hex().upper()}")
|
||||||
if self.bms_handler.process_30h(data):
|
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