no message
This commit is contained in:
parent
94ce47a4fe
commit
5f53e0edff
2
.idea/.name
generated
2
.idea/.name
generated
@ -1 +1 @@
|
|||||||
main.py
|
setup.py
|
150
battery_swap_station/README.md
Normal file
150
battery_swap_station/README.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# 换电站 MQTT 协议解析器
|
||||||
|
|
||||||
|
该软件包提供了一个基于 Python 的换电站 MQTT 协议实现,符合 V2.6.2 规范。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
- 完整实现所有消息类型(状态类、事件类、请求/响应类)
|
||||||
|
- 支持加密功能(RSA/AES)
|
||||||
|
- 全面的消息字段验证
|
||||||
|
- 简单易用的消息创建和解析 API
|
||||||
|
- 完整的测试覆盖
|
||||||
|
|
||||||
|
## 安装方法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
下面是创建和解析站点状态消息的简单示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mqtt_protocol import StationStateMessage
|
||||||
|
|
||||||
|
# 创建站点状态消息
|
||||||
|
state_msg = StationStateMessage.create(
|
||||||
|
state=1, # 运营状态
|
||||||
|
smoke="1,1,1,1", # 所有烟感正常
|
||||||
|
fire=1, # 消防系统正常
|
||||||
|
temp=25, # 温度
|
||||||
|
humid=60, # 湿度
|
||||||
|
totalElect=1000.5 # 总电量
|
||||||
|
)
|
||||||
|
|
||||||
|
# 转换为 JSON
|
||||||
|
json_data = state_msg.to_json()
|
||||||
|
|
||||||
|
# 从 JSON 解析
|
||||||
|
parsed_msg = StationStateMessage.from_json(json_data)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 消息类型
|
||||||
|
|
||||||
|
### 状态类消息
|
||||||
|
- 换电站属性信息
|
||||||
|
- 换电站状态
|
||||||
|
- 机器人状态
|
||||||
|
- 换电车辆状态
|
||||||
|
- 换电过程实时状态
|
||||||
|
- 电池状态
|
||||||
|
- 充电机状态
|
||||||
|
|
||||||
|
### 事件类消息
|
||||||
|
- 充电事件记录
|
||||||
|
- 换电事件记录
|
||||||
|
- 故障告警事件记录
|
||||||
|
|
||||||
|
### 请求/响应类消息
|
||||||
|
- 换电启动请求/响应
|
||||||
|
- 充电启动请求/响应
|
||||||
|
- 记录查询请求/响应
|
||||||
|
- 费率模型查询请求/响应
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
mqtt_protocol/
|
||||||
|
├── README.md # 项目说明文档
|
||||||
|
├── requirements.txt # 项目依赖
|
||||||
|
├── setup.py # 安装配置
|
||||||
|
├── docs/ # 文档目录
|
||||||
|
├── examples/ # 示例代码
|
||||||
|
├── tests/ # 测试用例
|
||||||
|
└── mqtt_protocol/ # 主代码目录
|
||||||
|
├── base_message.py # 基础消息类
|
||||||
|
├── state_messages.py # 状态类消息
|
||||||
|
├── event_messages.py # 事件类消息
|
||||||
|
├── request_response.py # 请求响应类消息
|
||||||
|
├── encryption_handler.py # 加密处理
|
||||||
|
└── utils/ # 工具类目录
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行测试
|
||||||
|
|
||||||
|
使用 pytest 运行测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例代码
|
||||||
|
|
||||||
|
查看 `examples/` 目录中的使用示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/state_example.py # 状态消息示例
|
||||||
|
python examples/event_example.py # 事件消息示例
|
||||||
|
python examples/request_example.py # 请求响应示例
|
||||||
|
```
|
||||||
|
|
||||||
|
## 协议文档
|
||||||
|
|
||||||
|
详细的协议规范请参见 `docs/protocol_spec.md`。
|
||||||
|
|
||||||
|
## 系统要求
|
||||||
|
|
||||||
|
- Python 3.7+
|
||||||
|
- pycryptodome>=3.15.0(加密库)
|
||||||
|
- pytest>=7.0.0(测试用)
|
||||||
|
- paho-mqtt>=1.6.1(MQTT客户端)
|
||||||
|
- python-dateutil>=2.8.2(日期处理)
|
||||||
|
|
||||||
|
## 主要功能模块说明
|
||||||
|
|
||||||
|
### 1. 基础消息模块 (base_message.py)
|
||||||
|
- 定义了所有消息的基础类
|
||||||
|
- 包含消息头部处理
|
||||||
|
- 提供 JSON 序列化和反序列化功能
|
||||||
|
|
||||||
|
### 2. 状态消息模块 (state_messages.py)
|
||||||
|
- 实现各类状态信息的处理
|
||||||
|
- 包括站点状态、设备状态等
|
||||||
|
- 支持实时状态更新
|
||||||
|
|
||||||
|
### 3. 事件消息模块 (event_messages.py)
|
||||||
|
- 处理充电、换电等事件记录
|
||||||
|
- 支持事件确认机制
|
||||||
|
- 包含事件重试逻辑
|
||||||
|
|
||||||
|
### 4. 请求响应模块 (request_response.py)
|
||||||
|
- 实现所有请求响应类消息
|
||||||
|
- 支持同步和异步请求处理
|
||||||
|
- 包含超时和重试机制
|
||||||
|
|
||||||
|
### 5. 加密模块 (encryption_handler.py)
|
||||||
|
- 实现 RSA 和 AES 加密
|
||||||
|
- 提供密钥管理功能
|
||||||
|
- 支持安全通信
|
||||||
|
|
||||||
|
## 使用注意事项
|
||||||
|
|
||||||
|
1. 在使用前确保正确配置 MQTT 服务器连接信息
|
||||||
|
2. 注意处理所有可能的异常情况
|
||||||
|
3. 建议在生产环境中启用加密功能
|
||||||
|
4. 定期检查并处理消息重试队列
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT 许可证
|
0
battery_swap_station/config/__init__.py
Normal file
0
battery_swap_station/config/__init__.py
Normal file
0
battery_swap_station/config/config.json
Normal file
0
battery_swap_station/config/config.json
Normal file
0
battery_swap_station/config/logging_config.json
Normal file
0
battery_swap_station/config/logging_config.json
Normal file
BIN
battery_swap_station/docs/玖行充换电云平台与站控系统数据接口协议(MQTT)V2.6.2.pdf
Normal file
BIN
battery_swap_station/docs/玖行充换电云平台与站控系统数据接口协议(MQTT)V2.6.2.pdf
Normal file
Binary file not shown.
0
battery_swap_station/examples/__init__.py
Normal file
0
battery_swap_station/examples/__init__.py
Normal file
79
battery_swap_station/examples/event_example.py
Normal file
79
battery_swap_station/examples/event_example.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
Examples of using event messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from mqtt_protocol import ChargeRecord, SwapRecord
|
||||||
|
from mqtt_protocol.event_messages import RateDetail, ChargeStartType, SwapType, SwapMode
|
||||||
|
|
||||||
|
|
||||||
|
def charge_record_example():
|
||||||
|
# Create rate details
|
||||||
|
rate_details = [
|
||||||
|
RateDetail(rateType=1, startTime="2024-02-24 10:00:00",
|
||||||
|
stopTime="2024-02-24 12:00:00", elect=50.5),
|
||||||
|
RateDetail(rateType=2, startTime="2024-02-24 12:00:00",
|
||||||
|
stopTime="2024-02-24 14:00:00", elect=45.8)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create a charge record
|
||||||
|
charge_record = ChargeRecord.create(
|
||||||
|
equipNo="CHG001",
|
||||||
|
orderSn="CR202402240001",
|
||||||
|
gunNo="1",
|
||||||
|
startTime="2024-02-24 10:00:00",
|
||||||
|
stopTime="2024-02-24 14:00:00",
|
||||||
|
startSOC=20.5,
|
||||||
|
endSOC=85.3,
|
||||||
|
chgQty=96.3,
|
||||||
|
startMeter=1000.0,
|
||||||
|
endMeter=1096.3,
|
||||||
|
plateNo="京A12345",
|
||||||
|
vin="LSVAA12345G123456",
|
||||||
|
stopReason=1,
|
||||||
|
sharpElect=50.5,
|
||||||
|
peakElect=45.8,
|
||||||
|
flatElect=0.0,
|
||||||
|
valleyElect=0.0,
|
||||||
|
rateModelID="RATE001",
|
||||||
|
detailsList=rate_details,
|
||||||
|
swapSn="",
|
||||||
|
chgSwitch=0,
|
||||||
|
startType=ChargeStartType.STATION_AUTO
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Charge Record Example:")
|
||||||
|
print(charge_record.to_json())
|
||||||
|
|
||||||
|
|
||||||
|
def swap_record_example():
|
||||||
|
# Create a swap record
|
||||||
|
swap_record = SwapRecord(
|
||||||
|
equipNo="ROBOT001",
|
||||||
|
orderSn="SR202402240001",
|
||||||
|
startTime="2024-02-24 15:00:00",
|
||||||
|
stopTime="2024-02-24 15:10:00",
|
||||||
|
vin="LSVAA12345G123456",
|
||||||
|
rfidCode="123456789012345678901234",
|
||||||
|
plateNo="京A12345",
|
||||||
|
newBatID="BAT001",
|
||||||
|
newCabinetNo=1,
|
||||||
|
newBatSoc=95.5,
|
||||||
|
oldBatID="BAT002",
|
||||||
|
oldCabinetNo=2,
|
||||||
|
oldBatSoc=15.5,
|
||||||
|
swapType=SwapType.BATTERY_SWAP,
|
||||||
|
swapMode=SwapMode.FULL_AUTO,
|
||||||
|
swapStartType=1,
|
||||||
|
lane=1,
|
||||||
|
userID="USER001",
|
||||||
|
stationMode=1
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\nSwap Record Example:")
|
||||||
|
print(swap_record.to_json())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
charge_record_example()
|
||||||
|
swap_record_example()
|
103
battery_swap_station/examples/request_example.py
Normal file
103
battery_swap_station/examples/request_example.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"""
|
||||||
|
Examples of using request and response messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
from mqtt_protocol import (
|
||||||
|
SwapStartRequest,
|
||||||
|
SwapStartResponse,
|
||||||
|
ChargeStartRequest,
|
||||||
|
ChargeStartResponse
|
||||||
|
)
|
||||||
|
from mqtt_protocol.request_response import (
|
||||||
|
RateModelQuery,
|
||||||
|
RateModelResponse,
|
||||||
|
RateInfo,
|
||||||
|
RateTimeSegment
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def swap_request_example():
|
||||||
|
# Create a swap start request
|
||||||
|
swap_req = SwapStartRequest(
|
||||||
|
vin="LSVAA12345G123456",
|
||||||
|
rfid_code="123456789012345678901234",
|
||||||
|
plate_no="京A12345",
|
||||||
|
lane=1,
|
||||||
|
user_id="USER001"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Swap Start Request Example:")
|
||||||
|
print(swap_req.to_json())
|
||||||
|
|
||||||
|
# Create a response
|
||||||
|
swap_resp = SwapStartResponse(
|
||||||
|
vin="LSVAA12345G123456",
|
||||||
|
rfid_code="123456789012345678901234",
|
||||||
|
plate_no="京A12345",
|
||||||
|
result=1, # Success
|
||||||
|
order_sn="SR202402240001"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\nSwap Start Response Example:")
|
||||||
|
print(swap_resp.to_json())
|
||||||
|
|
||||||
|
|
||||||
|
def charge_request_example():
|
||||||
|
# Create a charge start request
|
||||||
|
charge_req = ChargeStartRequest(
|
||||||
|
chg_id="CHG001",
|
||||||
|
gun_no=1
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\nCharge Start Request Example:")
|
||||||
|
print(charge_req.to_json())
|
||||||
|
|
||||||
|
# Create a response
|
||||||
|
charge_resp = ChargeStartResponse(
|
||||||
|
result=1, # Success
|
||||||
|
chg_id="CHG001",
|
||||||
|
gun_no=1,
|
||||||
|
order_sn="CR202402240001"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\nCharge Start Response Example:")
|
||||||
|
print(charge_resp.to_json())
|
||||||
|
|
||||||
|
|
||||||
|
def rate_model_example():
|
||||||
|
# Create rate model query
|
||||||
|
rate_query = RateModelQuery("RATE001")
|
||||||
|
print("\nRate Model Query Example:")
|
||||||
|
print(rate_query.to_json())
|
||||||
|
|
||||||
|
# Create rate info list
|
||||||
|
rate_list = [
|
||||||
|
RateInfo(rateType=1, electPrice=1.2, servicePrice=0.5),
|
||||||
|
RateInfo(rateType=2, electPrice=1.0, servicePrice=0.4),
|
||||||
|
RateInfo(rateType=3, electPrice=0.8, servicePrice=0.3),
|
||||||
|
RateInfo(rateType=4, electPrice=0.6, servicePrice=0.2)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create rate time segments
|
||||||
|
time_segments = [
|
||||||
|
RateTimeSegment(rateType=1, index=1,
|
||||||
|
startTime="10:00:00", stopTime="12:00:00"),
|
||||||
|
RateTimeSegment(rateType=2, index=2,
|
||||||
|
startTime="12:00:00", stopTime="14:00:00")
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create response
|
||||||
|
rate_resp = RateModelResponse(
|
||||||
|
rate_model_id="RATE001",
|
||||||
|
rate_list=rate_list,
|
||||||
|
rate_details_list=time_segments
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\nRate Model Response Example:")
|
||||||
|
print(rate_resp.to_json())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
swap_request_example()
|
||||||
|
charge_request_example()
|
||||||
|
rate_model_example()
|
68
battery_swap_station/examples/state_example.py
Normal file
68
battery_swap_station/examples/state_example.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
Constants used throughout the MQTT protocol implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Protocol version
|
||||||
|
PROTOCOL_VERSION = "V2.6.2"
|
||||||
|
|
||||||
|
# Topic related constants
|
||||||
|
TOPIC_PREFIX = "HCMS" # Heavy truck Charging Management System
|
||||||
|
TOPIC_SEPARATOR = "/"
|
||||||
|
|
||||||
|
# Message directions
|
||||||
|
M2S = "M2S" # Master to Slave (Cloud Platform to Station)
|
||||||
|
S2M = "S2M" # Slave to Master (Station to Cloud Platform)
|
||||||
|
|
||||||
|
# Message types
|
||||||
|
STATE = "state"
|
||||||
|
EVENT = "event"
|
||||||
|
CONFIRM = "confirm"
|
||||||
|
REQUEST = "request"
|
||||||
|
RESPONSE = "response"
|
||||||
|
KEEPALIVE = "keepalive"
|
||||||
|
ENCRYPT_KEY_REQ = "encryptKeyReq"
|
||||||
|
ENCRYPT_KEY_RESP = "encryptKeyResp"
|
||||||
|
|
||||||
|
# Station modes
|
||||||
|
STATION_MODE_OPERATION = 1
|
||||||
|
STATION_MODE_DEBUG = 2
|
||||||
|
STATION_MODE_MAINTENANCE = 3
|
||||||
|
|
||||||
|
# Device states
|
||||||
|
DEVICE_STATE_UNKNOWN = 0
|
||||||
|
DEVICE_STATE_NORMAL = 1
|
||||||
|
DEVICE_STATE_ALARM = 2
|
||||||
|
|
||||||
|
# Charging states
|
||||||
|
CHARGING_STATE_STANDBY = 1
|
||||||
|
CHARGING_STATE_CHARGING = 2
|
||||||
|
CHARGING_STATE_COMPLETE = 3
|
||||||
|
CHARGING_STATE_FAULT = 4
|
||||||
|
CHARGING_STATE_OFFLINE = 5
|
||||||
|
|
||||||
|
# Swap states
|
||||||
|
SWAP_STATE_NOT_STARTED = 1
|
||||||
|
SWAP_STATE_STARTED = 2
|
||||||
|
SWAP_STATE_PAUSED = 3
|
||||||
|
SWAP_STATE_RESUMED = 4
|
||||||
|
SWAP_STATE_TERMINATED = 5
|
||||||
|
SWAP_STATE_COMPLETED = 6
|
||||||
|
|
||||||
|
# Rate types
|
||||||
|
RATE_TYPE_SHARP = 1
|
||||||
|
RATE_TYPE_PEAK = 2
|
||||||
|
RATE_TYPE_FLAT = 3
|
||||||
|
RATE_TYPE_VALLEY = 4
|
||||||
|
|
||||||
|
# Lane types
|
||||||
|
LANE_TYPE_SINGLE = 1
|
||||||
|
LANE_TYPE_DOUBLE = 2
|
||||||
|
|
||||||
|
# Error codes
|
||||||
|
ERROR_SUCCESS = 1
|
||||||
|
ERROR_FAILURE = 2
|
||||||
|
|
||||||
|
# Timeouts (in seconds)
|
||||||
|
KEEPALIVE_INTERVAL = 30
|
||||||
|
EVENT_RETRY_INTERVAL = 15
|
||||||
|
COMMUNICATION_TIMEOUT = 90 # 3 * KEEPALIVE_INTERVAL
|
@ -1,104 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
主程序入口,负责初始化各个组件并启动MQTT客户端
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import signal
|
|
||||||
import time
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
from config.config_manager import ConfigManager
|
|
||||||
from utils.logger import setup_logger
|
|
||||||
from mqtt.mqtt_client import MQTTClient
|
|
||||||
from message.message_router import MessageRouter
|
|
||||||
from handlers.state_handler import StateHandler
|
|
||||||
from handlers.event_handler import EventHandler
|
|
||||||
from handlers.request_handler import RequestHandler
|
|
||||||
from handlers.response_handler import ResponseHandler
|
|
||||||
from handlers.keepalive_handler import KeepaliveHandler
|
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments():
|
|
||||||
"""解析命令行参数"""
|
|
||||||
parser = argparse.ArgumentParser(description='玖行充换电云平台与站控系统通信程序')
|
|
||||||
parser.add_argument('-c', '--config', default='config/config.yaml',
|
|
||||||
help='配置文件路径')
|
|
||||||
parser.add_argument('-l', '--log-level', default='INFO',
|
|
||||||
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
|
|
||||||
help='日志级别')
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def setup_signal_handlers(mqtt_client):
|
|
||||||
"""设置信号处理器,优雅地关闭程序"""
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
|
||||||
print("\n正在关闭MQTT客户端...")
|
|
||||||
mqtt_client.disconnect()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""主函数"""
|
|
||||||
args = parse_arguments()
|
|
||||||
|
|
||||||
# 初始化日志
|
|
||||||
logger = setup_logger('main', args.log_level)
|
|
||||||
logger.info("启动玖行充换电云平台与站控系统通信程序")
|
|
||||||
|
|
||||||
# 加载配置
|
|
||||||
config_manager = ConfigManager(args.config)
|
|
||||||
config = config_manager.get_config()
|
|
||||||
|
|
||||||
# 初始化消息路由器
|
|
||||||
message_router = MessageRouter()
|
|
||||||
|
|
||||||
# 初始化各类处理器
|
|
||||||
state_handler = StateHandler(config)
|
|
||||||
event_handler = EventHandler(config)
|
|
||||||
request_handler = RequestHandler(config)
|
|
||||||
response_handler = ResponseHandler(config)
|
|
||||||
keepalive_handler = KeepaliveHandler(config)
|
|
||||||
|
|
||||||
# 注册处理器到路由器
|
|
||||||
message_router.register_handler('state', state_handler)
|
|
||||||
message_router.register_handler('event', event_handler)
|
|
||||||
message_router.register_handler('request', request_handler)
|
|
||||||
message_router.register_handler('response', response_handler)
|
|
||||||
message_router.register_handler('keepalive', keepalive_handler)
|
|
||||||
|
|
||||||
# 初始化MQTT客户端
|
|
||||||
mqtt_client = MQTTClient(
|
|
||||||
broker=config['mqtt']['broker'],
|
|
||||||
port=config['mqtt']['port'],
|
|
||||||
client_id=config['mqtt']['client_id'],
|
|
||||||
username=config['mqtt']['username'],
|
|
||||||
password=config['mqtt']['password'],
|
|
||||||
use_tls=config['mqtt']['use_tls'],
|
|
||||||
message_router=message_router
|
|
||||||
)
|
|
||||||
|
|
||||||
# 设置信号处理器
|
|
||||||
setup_signal_handlers(mqtt_client)
|
|
||||||
|
|
||||||
# 连接MQTT服务器
|
|
||||||
mqtt_client.connect()
|
|
||||||
|
|
||||||
# 主循环
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
time.sleep(1)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger.info("接收到退出信号,正在关闭程序...")
|
|
||||||
mqtt_client.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
40
battery_swap_station/mqtt/__init__.py
Normal file
40
battery_swap_station/mqtt/__init__.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
MQTT Protocol Parser for Battery Swap Station
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
|
from .base_message import BaseMessage, MessageHeader, MessageType, MessageDirection
|
||||||
|
from .state_messages import (
|
||||||
|
StationInfo,
|
||||||
|
StationStateMessage,
|
||||||
|
RobotStateMessage
|
||||||
|
)
|
||||||
|
from .event_messages import (
|
||||||
|
ChargeRecord,
|
||||||
|
SwapRecord
|
||||||
|
)
|
||||||
|
from .request_response import (
|
||||||
|
SwapStartRequest,
|
||||||
|
SwapStartResponse,
|
||||||
|
ChargeStartRequest,
|
||||||
|
ChargeStartResponse
|
||||||
|
)
|
||||||
|
from .encryption_handler import EncryptionHandler
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'BaseMessage',
|
||||||
|
'MessageHeader',
|
||||||
|
'MessageType',
|
||||||
|
'MessageDirection',
|
||||||
|
'StationInfo',
|
||||||
|
'StationStateMessage',
|
||||||
|
'RobotStateMessage',
|
||||||
|
'ChargeRecord',
|
||||||
|
'SwapRecord',
|
||||||
|
'SwapStartRequest',
|
||||||
|
'SwapStartResponse',
|
||||||
|
'ChargeStartRequest',
|
||||||
|
'ChargeStartResponse',
|
||||||
|
'EncryptionHandler'
|
||||||
|
]
|
0
battery_swap_station/mqtt/base_message.py
Normal file
0
battery_swap_station/mqtt/base_message.py
Normal file
0
battery_swap_station/mqtt/encryption_handler.py
Normal file
0
battery_swap_station/mqtt/encryption_handler.py
Normal file
0
battery_swap_station/mqtt/event_messages.py
Normal file
0
battery_swap_station/mqtt/event_messages.py
Normal file
0
battery_swap_station/mqtt/request_response.py
Normal file
0
battery_swap_station/mqtt/request_response.py
Normal file
0
battery_swap_station/mqtt/state_messages.py
Normal file
0
battery_swap_station/mqtt/state_messages.py
Normal file
0
battery_swap_station/mqtt/utils/__init__.py
Normal file
0
battery_swap_station/mqtt/utils/__init__.py
Normal file
68
battery_swap_station/mqtt/utils/constants.py
Normal file
68
battery_swap_station/mqtt/utils/constants.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
Constants used throughout the MQTT protocol implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Protocol version
|
||||||
|
PROTOCOL_VERSION = "V2.6.2"
|
||||||
|
|
||||||
|
# Topic related constants
|
||||||
|
TOPIC_PREFIX = "HCMS" # Heavy truck Charging Management System
|
||||||
|
TOPIC_SEPARATOR = "/"
|
||||||
|
|
||||||
|
# Message directions
|
||||||
|
M2S = "M2S" # Master to Slave (Cloud Platform to Station)
|
||||||
|
S2M = "S2M" # Slave to Master (Station to Cloud Platform)
|
||||||
|
|
||||||
|
# Message types
|
||||||
|
STATE = "state"
|
||||||
|
EVENT = "event"
|
||||||
|
CONFIRM = "confirm"
|
||||||
|
REQUEST = "request"
|
||||||
|
RESPONSE = "response"
|
||||||
|
KEEPALIVE = "keepalive"
|
||||||
|
ENCRYPT_KEY_REQ = "encryptKeyReq"
|
||||||
|
ENCRYPT_KEY_RESP = "encryptKeyResp"
|
||||||
|
|
||||||
|
# Station modes
|
||||||
|
STATION_MODE_OPERATION = 1
|
||||||
|
STATION_MODE_DEBUG = 2
|
||||||
|
STATION_MODE_MAINTENANCE = 3
|
||||||
|
|
||||||
|
# Device states
|
||||||
|
DEVICE_STATE_UNKNOWN = 0
|
||||||
|
DEVICE_STATE_NORMAL = 1
|
||||||
|
DEVICE_STATE_ALARM = 2
|
||||||
|
|
||||||
|
# Charging states
|
||||||
|
CHARGING_STATE_STANDBY = 1
|
||||||
|
CHARGING_STATE_CHARGING = 2
|
||||||
|
CHARGING_STATE_COMPLETE = 3
|
||||||
|
CHARGING_STATE_FAULT = 4
|
||||||
|
CHARGING_STATE_OFFLINE = 5
|
||||||
|
|
||||||
|
# Swap states
|
||||||
|
SWAP_STATE_NOT_STARTED = 1
|
||||||
|
SWAP_STATE_STARTED = 2
|
||||||
|
SWAP_STATE_PAUSED = 3
|
||||||
|
SWAP_STATE_RESUMED = 4
|
||||||
|
SWAP_STATE_TERMINATED = 5
|
||||||
|
SWAP_STATE_COMPLETED = 6
|
||||||
|
|
||||||
|
# Rate types
|
||||||
|
RATE_TYPE_SHARP = 1
|
||||||
|
RATE_TYPE_PEAK = 2
|
||||||
|
RATE_TYPE_FLAT = 3
|
||||||
|
RATE_TYPE_VALLEY = 4
|
||||||
|
|
||||||
|
# Lane types
|
||||||
|
LANE_TYPE_SINGLE = 1
|
||||||
|
LANE_TYPE_DOUBLE = 2
|
||||||
|
|
||||||
|
# Error codes
|
||||||
|
ERROR_SUCCESS = 1
|
||||||
|
ERROR_FAILURE = 2
|
||||||
|
|
||||||
|
# Timeouts (in seconds)
|
||||||
|
KEEPALIVE_INTERVAL = 30
|
||||||
|
EVENT_RETRY_INTERVAL = 15
|
||||||
|
COMMUNICATION_TIMEOUT = 90 # 3 * KEEPALIVE_INTERVAL
|
68
battery_swap_station/mqtt/utils/validators.py
Normal file
68
battery_swap_station/mqtt/utils/validators.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
Constants used throughout the MQTT protocol implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Protocol version
|
||||||
|
PROTOCOL_VERSION = "V2.6.2"
|
||||||
|
|
||||||
|
# Topic related constants
|
||||||
|
TOPIC_PREFIX = "HCMS" # Heavy truck Charging Management System
|
||||||
|
TOPIC_SEPARATOR = "/"
|
||||||
|
|
||||||
|
# Message directions
|
||||||
|
M2S = "M2S" # Master to Slave (Cloud Platform to Station)
|
||||||
|
S2M = "S2M" # Slave to Master (Station to Cloud Platform)
|
||||||
|
|
||||||
|
# Message types
|
||||||
|
STATE = "state"
|
||||||
|
EVENT = "event"
|
||||||
|
CONFIRM = "confirm"
|
||||||
|
REQUEST = "request"
|
||||||
|
RESPONSE = "response"
|
||||||
|
KEEPALIVE = "keepalive"
|
||||||
|
ENCRYPT_KEY_REQ = "encryptKeyReq"
|
||||||
|
ENCRYPT_KEY_RESP = "encryptKeyResp"
|
||||||
|
|
||||||
|
# Station modes
|
||||||
|
STATION_MODE_OPERATION = 1
|
||||||
|
STATION_MODE_DEBUG = 2
|
||||||
|
STATION_MODE_MAINTENANCE = 3
|
||||||
|
|
||||||
|
# Device states
|
||||||
|
DEVICE_STATE_UNKNOWN = 0
|
||||||
|
DEVICE_STATE_NORMAL = 1
|
||||||
|
DEVICE_STATE_ALARM = 2
|
||||||
|
|
||||||
|
# Charging states
|
||||||
|
CHARGING_STATE_STANDBY = 1
|
||||||
|
CHARGING_STATE_CHARGING = 2
|
||||||
|
CHARGING_STATE_COMPLETE = 3
|
||||||
|
CHARGING_STATE_FAULT = 4
|
||||||
|
CHARGING_STATE_OFFLINE = 5
|
||||||
|
|
||||||
|
# Swap states
|
||||||
|
SWAP_STATE_NOT_STARTED = 1
|
||||||
|
SWAP_STATE_STARTED = 2
|
||||||
|
SWAP_STATE_PAUSED = 3
|
||||||
|
SWAP_STATE_RESUMED = 4
|
||||||
|
SWAP_STATE_TERMINATED = 5
|
||||||
|
SWAP_STATE_COMPLETED = 6
|
||||||
|
|
||||||
|
# Rate types
|
||||||
|
RATE_TYPE_SHARP = 1
|
||||||
|
RATE_TYPE_PEAK = 2
|
||||||
|
RATE_TYPE_FLAT = 3
|
||||||
|
RATE_TYPE_VALLEY = 4
|
||||||
|
|
||||||
|
# Lane types
|
||||||
|
LANE_TYPE_SINGLE = 1
|
||||||
|
LANE_TYPE_DOUBLE = 2
|
||||||
|
|
||||||
|
# Error codes
|
||||||
|
ERROR_SUCCESS = 1
|
||||||
|
ERROR_FAILURE = 2
|
||||||
|
|
||||||
|
# Timeouts (in seconds)
|
||||||
|
KEEPALIVE_INTERVAL = 30
|
||||||
|
EVENT_RETRY_INTERVAL = 15
|
||||||
|
COMMUNICATION_TIMEOUT = 90 # 3 * KEEPALIVE_INTERVAL
|
@ -1,41 +0,0 @@
|
|||||||
# 文件结构
|
|
||||||
# -----------------
|
|
||||||
# main.py # 主程序入口
|
|
||||||
# config/
|
|
||||||
# __init__.py
|
|
||||||
# config_manager.py # 配置管理器
|
|
||||||
# config.yaml # 配置文件
|
|
||||||
# mqtt/
|
|
||||||
# __init__.py
|
|
||||||
# mqtt_client.py # MQTT客户端
|
|
||||||
# encryption/
|
|
||||||
# __init__.py
|
|
||||||
# encryption.py # 加密/解密模块
|
|
||||||
# message/
|
|
||||||
# __init__.py
|
|
||||||
# message_parser.py # 消息解析器
|
|
||||||
# message_router.py # 消息路由器
|
|
||||||
# handlers/
|
|
||||||
# __init__.py
|
|
||||||
# base_handler.py # 基础处理器类
|
|
||||||
# state_handler.py # 状态类处理器
|
|
||||||
# event_handler.py # 事件类处理器
|
|
||||||
# request_handler.py # 请求类处理器
|
|
||||||
# response_handler.py # 响应类处理器
|
|
||||||
# keepalive_handler.py # 心跳处理器
|
|
||||||
# models/
|
|
||||||
# __init__.py
|
|
||||||
# base_model.py # 基础数据模型
|
|
||||||
# station_model.py # 站控相关数据模型
|
|
||||||
# battery_model.py # 电池相关数据模型
|
|
||||||
# charging_model.py # 充电相关数据模型
|
|
||||||
# swapping_model.py # 换电相关数据模型
|
|
||||||
# utils/
|
|
||||||
# __init__.py
|
|
||||||
# logger.py # 日志工具
|
|
||||||
# helpers.py # 辅助函数
|
|
||||||
# tests/ # 单元测试
|
|
||||||
# __init__.py
|
|
||||||
# test_mqtt_client.py
|
|
||||||
# test_encryption.py
|
|
||||||
# test_message_parser.py
|
|
6
battery_swap_station/requirements.txt
Normal file
6
battery_swap_station/requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pycryptodome>=3.15.0
|
||||||
|
pytest>=7.0.0
|
||||||
|
paho-mqtt>=1.6.1
|
||||||
|
dataclasses>=0.6
|
||||||
|
typing-extensions>=4.0.0
|
||||||
|
python-dateutil>=2.8.2
|
15
battery_swap_station/setup.py
Normal file
15
battery_swap_station/setup.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="mqtt_protocol",
|
||||||
|
version="1.0.0",
|
||||||
|
packages=find_packages(),
|
||||||
|
install_requires=[
|
||||||
|
"pycryptodome>=3.15.0",
|
||||||
|
],
|
||||||
|
author="Your Name",
|
||||||
|
author_email="your.email@example.com",
|
||||||
|
description="MQTT Protocol Parser for Battery Swap Station",
|
||||||
|
long_description=open("README.md").read(),
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
)
|
0
battery_swap_station/tests/__init__.py
Normal file
0
battery_swap_station/tests/__init__.py
Normal file
0
battery_swap_station/tests/test_event.py
Normal file
0
battery_swap_station/tests/test_event.py
Normal file
0
battery_swap_station/tests/test_request.py
Normal file
0
battery_swap_station/tests/test_request.py
Normal file
109
battery_swap_station/tests/test_state.py
Normal file
109
battery_swap_station/tests/test_state.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
"""
|
||||||
|
Tests for state messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from mqtt_protocol import (
|
||||||
|
StationStateMessage,
|
||||||
|
StationInfo,
|
||||||
|
RobotStateMessage
|
||||||
|
)
|
||||||
|
from mqtt_protocol.utils.constants import (
|
||||||
|
STATION_MODE_OPERATION,
|
||||||
|
DEVICE_STATE_NORMAL,
|
||||||
|
DEVICE_STATE_ALARM
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_station_state_message():
|
||||||
|
# Test creation
|
||||||
|
state_msg = StationStateMessage.create(
|
||||||
|
state=STATION_MODE_OPERATION,
|
||||||
|
smoke="1,1,1,1",
|
||||||
|
fire=DEVICE_STATE_NORMAL,
|
||||||
|
temp=25,
|
||||||
|
humid=60,
|
||||||
|
totalElect=1000.5
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test serialization
|
||||||
|
json_data = state_msg.to_json()
|
||||||
|
assert isinstance(json_data, str)
|
||||||
|
|
||||||
|
# Test deserialization
|
||||||
|
parsed_msg = StationStateMessage.from_json(json_data)
|
||||||
|
assert parsed_msg.state == STATION_MODE_OPERATION
|
||||||
|
assert parsed_msg.temp == 25
|
||||||
|
assert parsed_msg.humid == 60
|
||||||
|
assert parsed_msg.totalElect == 1000.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_station_info():
|
||||||
|
info = StationInfo(
|
||||||
|
name="Test Station",
|
||||||
|
stationID="100001",
|
||||||
|
version="V2.6.2",
|
||||||
|
startDate="2024-02-24",
|
||||||
|
robotNum=2,
|
||||||
|
robotID="1234567890123456,1234567890123457",
|
||||||
|
chgNum=8,
|
||||||
|
chgID="CHG001,CHG002,CHG003,CHG004,CHG005,CHG006,CHG007,CHG008",
|
||||||
|
laneType=1,
|
||||||
|
rateModelID="RATE001",
|
||||||
|
chgVersion="V1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert info.name == "Test Station"
|
||||||
|
assert info.stationID == "100001"
|
||||||
|
assert info.robotNum == 2
|
||||||
|
assert info.chgNum == 8
|
||||||
|
|
||||||
|
|
||||||
|
def test_station_state_validation():
|
||||||
|
# Test invalid state
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
StationStateMessage.create(
|
||||||
|
state=999, # Invalid state
|
||||||
|
smoke="1,1,1,1",
|
||||||
|
fire=DEVICE_STATE_NORMAL,
|
||||||
|
temp=25,
|
||||||
|
humid=60,
|
||||||
|
totalElect=1000.5
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test invalid smoke detector format
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
StationStateMessage.create(
|
||||||
|
state=STATION_MODE_OPERATION,
|
||||||
|
smoke="invalid",
|
||||||
|
fire=DEVICE_STATE_NORMAL,
|
||||||
|
temp=25,
|
||||||
|
humid=60,
|
||||||
|
totalElect=1000.5
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_robot_state():
|
||||||
|
robot_msg = RobotStateMessage(
|
||||||
|
robotID="1234567890123456",
|
||||||
|
state=1,
|
||||||
|
fault=None,
|
||||||
|
runMode=1
|
||||||
|
)
|
||||||
|
|
||||||
|
assert robot_msg.robotID == "1234567890123456"
|
||||||
|
assert robot_msg.state == 1
|
||||||
|
assert robot_msg.fault is None
|
||||||
|
assert robot_msg.runMode == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_robot_state_with_fault():
|
||||||
|
robot_msg = RobotStateMessage(
|
||||||
|
robotID="1234567890123456",
|
||||||
|
state=3, # Fault state
|
||||||
|
fault="ERROR001,ERROR002",
|
||||||
|
runMode=1
|
||||||
|
)
|
||||||
|
|
||||||
|
assert robot_msg.state == 3
|
||||||
|
assert robot_msg.fault == "ERROR001,ERROR002"
|
Loading…
x
Reference in New Issue
Block a user