diff --git a/.idea/.name b/.idea/.name index 11a5d8e..27f63ea 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -main.py \ No newline at end of file +setup.py \ No newline at end of file diff --git a/battery_swap_station/README.md b/battery_swap_station/README.md new file mode 100644 index 0000000..9bdc2f3 --- /dev/null +++ b/battery_swap_station/README.md @@ -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 许可证 \ No newline at end of file diff --git a/battery_swap_station/config/__init__.py b/battery_swap_station/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/config/config.json b/battery_swap_station/config/config.json new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/config/logging_config.json b/battery_swap_station/config/logging_config.json new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/docs/玖行充换电云平台与站控系统数据接口协议(MQTT)V2.6.2.pdf b/battery_swap_station/docs/玖行充换电云平台与站控系统数据接口协议(MQTT)V2.6.2.pdf new file mode 100644 index 0000000..d0ee4aa Binary files /dev/null and b/battery_swap_station/docs/玖行充换电云平台与站控系统数据接口协议(MQTT)V2.6.2.pdf differ diff --git a/battery_swap_station/examples/__init__.py b/battery_swap_station/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/examples/event_example.py b/battery_swap_station/examples/event_example.py new file mode 100644 index 0000000..97058eb --- /dev/null +++ b/battery_swap_station/examples/event_example.py @@ -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() \ No newline at end of file diff --git a/battery_swap_station/examples/request_example.py b/battery_swap_station/examples/request_example.py new file mode 100644 index 0000000..e17a44c --- /dev/null +++ b/battery_swap_station/examples/request_example.py @@ -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() \ No newline at end of file diff --git a/battery_swap_station/examples/state_example.py b/battery_swap_station/examples/state_example.py new file mode 100644 index 0000000..fa5fba7 --- /dev/null +++ b/battery_swap_station/examples/state_example.py @@ -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 \ No newline at end of file diff --git a/battery_swap_station/main.py b/battery_swap_station/main.py deleted file mode 100644 index 43ecd13..0000000 --- a/battery_swap_station/main.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/battery_swap_station/mqtt/__init__.py b/battery_swap_station/mqtt/__init__.py new file mode 100644 index 0000000..84dcda7 --- /dev/null +++ b/battery_swap_station/mqtt/__init__.py @@ -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' +] \ No newline at end of file diff --git a/battery_swap_station/mqtt/base_message.py b/battery_swap_station/mqtt/base_message.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/mqtt/encryption_handler.py b/battery_swap_station/mqtt/encryption_handler.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/mqtt/event_messages.py b/battery_swap_station/mqtt/event_messages.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/mqtt/request_response.py b/battery_swap_station/mqtt/request_response.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/mqtt/state_messages.py b/battery_swap_station/mqtt/state_messages.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/mqtt/utils/__init__.py b/battery_swap_station/mqtt/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/mqtt/utils/constants.py b/battery_swap_station/mqtt/utils/constants.py new file mode 100644 index 0000000..fa5fba7 --- /dev/null +++ b/battery_swap_station/mqtt/utils/constants.py @@ -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 \ No newline at end of file diff --git a/battery_swap_station/mqtt/utils/validators.py b/battery_swap_station/mqtt/utils/validators.py new file mode 100644 index 0000000..fa5fba7 --- /dev/null +++ b/battery_swap_station/mqtt/utils/validators.py @@ -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 \ No newline at end of file diff --git a/battery_swap_station/readme b/battery_swap_station/readme deleted file mode 100644 index 5161bed..0000000 --- a/battery_swap_station/readme +++ /dev/null @@ -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 \ No newline at end of file diff --git a/battery_swap_station/requirements.txt b/battery_swap_station/requirements.txt new file mode 100644 index 0000000..c880a74 --- /dev/null +++ b/battery_swap_station/requirements.txt @@ -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 \ No newline at end of file diff --git a/battery_swap_station/setup.py b/battery_swap_station/setup.py new file mode 100644 index 0000000..aac7ff2 --- /dev/null +++ b/battery_swap_station/setup.py @@ -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", +) \ No newline at end of file diff --git a/battery_swap_station/tests/__init__.py b/battery_swap_station/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/tests/test_event.py b/battery_swap_station/tests/test_event.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/tests/test_request.py b/battery_swap_station/tests/test_request.py new file mode 100644 index 0000000..e69de29 diff --git a/battery_swap_station/tests/test_state.py b/battery_swap_station/tests/test_state.py new file mode 100644 index 0000000..c3ed195 --- /dev/null +++ b/battery_swap_station/tests/test_state.py @@ -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" \ No newline at end of file