From b80a04bfc1973fac6aaa314b5b903fcccbd37ff4 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sun, 7 Sep 2025 15:46:10 +0800 Subject: [PATCH] =?UTF-8?q?AI=E7=94=9F=E6=88=90=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEVELOPMENT.md | 173 ++++++++++++++++++++++++++++++++++ README.md | 199 +++++++++++++++++++++++++++++----------- comms/base_comm.py | 73 ++++++++++++++- config.json.example | 73 +++++++++++++++ config.py | 165 +++++++++++++++++++++++++++++++++ core/base_handler.py | 50 ++++++++++ core/enums.py | 43 +++++++++ devices/base_device.py | 101 +++++++++++++++++++- main.py | 1 - requirements.txt | 21 +++++ storage/base_storage.py | 108 +++++++++++++++++++++- 11 files changed, 947 insertions(+), 60 deletions(-) create mode 100644 DEVELOPMENT.md create mode 100644 config.json.example create mode 100644 core/base_handler.py create mode 100644 core/enums.py create mode 100644 requirements.txt diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..849f845 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,173 @@ +# 开发环境搭建指南 + +## 系统要求 + +- Python 3.7+ +- Raspberry Pi 或其他兼容的嵌入式Linux系统 +- LoRa通信模块(如SX1278) +- RS485通信接口 + +## 协议栈架构 + +本系统采用标准物联网协议栈: + +``` +应用层: LwM2M +传输层: CoAP (Constrained Application Protocol) +网络层: LoRaWAN +数据链路层: LoRa +物理层: LoRa +``` + +## 安装步骤 + +### 1. 克隆项目 + +```bash +git clone <项目地址> +cd pig-house-controller +``` + +### 2. 创建虚拟环境 + +```bash +python3 -m venv venv +source venv/bin/activate # Linux/Mac +# 或 +venv\Scripts\activate # Windows +``` + +### 3. 安装依赖 + +```bash +pip install -r requirements.txt +``` + +### 4. LwM2M支持的特殊处理 + +由于 `lwm2m-client` 包在 PyPI 上不可用,我们需要手动安装或使用替代方案: + +#### 方案一:使用 wakaama-python (推荐) +```bash +# 克隆 wakaama-python 仓库 +git clone https://github.com/djarek/wakaama-python.git +cd wakaama-python +# 按照项目说明进行安装 +``` + +#### 方案二:使用 Eclipse Wakaama +Eclipse Wakaama 是一个成熟的 LwM2M 实现,可以作为 C 扩展集成到 Python 项目中。 + +#### 方案三:自行实现 LwM2M 客户端 +根据 LwM2M 规范自行实现必要的功能。 + +### 5. 配置系统 + +复制示例配置文件并根据实际环境进行修改: + +```bash +cp config.json.example config.json +``` + +修改 [config.json](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/config.json) 文件中的参数,包括: +- LoRa通信参数 +- 总线配置 +- 设备配置 +- 日志配置 + +### 6. 硬件连接 + +1. 连接LoRa模块到树莓派的SPI接口 +2. 连接传感器总线RS485模块到树莓派的UART接口 +3. 连接执行器总线RS485模块到树莓派的另一个UART接口 + +### 7. 运行系统 + +```bash +python main.py +``` + +## 开发工具推荐 + +- **IDE**: PyCharm 或 VS Code +- **版本控制**: Git +- **调试工具**: Python内置pdb或IDE调试器 +- **日志查看**: tail命令或专业日志查看工具 + +## 测试环境 + +建议在实际部署前进行充分测试: + +1. 单元测试: 验证各模块功能 +2. 集成测试: 验证模块间协作 +3. 系统测试: 验证完整功能流程 +4. 硬件测试: 验证实际硬件连接和通信 + +## 协议栈实现说明 + +### LoRaWAN层 +使用SX1278等LoRa芯片,通过SPI接口与树莓派通信。实现基本的LoRaWAN功能: +- OTAA/ABP入网 +- 数据加解密 +- 数据包重传机制 + +### CoAP层 +实现CoAP协议栈,支持: +- GET/POST/PUT/DELETE方法 +- 资源发现 +- 块传输 +- 观察者模式 + +### LwM2M层 +实现LwM2M客户端功能: +- 设备注册与管理 +- 固件更新 +- 参数配置 +- 数据上报 + +注意:由于 `lwm2m-client` 包不可用,需要使用替代方案实现 LwM2M 功能。 + +### SenML数据格式 +所有传感器数据使用SenML格式进行编码和传输,确保数据标准化和互操作性。 + +## 抽象接口开发说明 + +系统定义了以下抽象接口,开发者在实现具体功能时需要继承这些基类: + +### 通信接口 (BaseComm) +位于 [comms/base_comm.py](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/comms/base_comm.py),定义了通信模块的基本操作: +- `connect()`: 建立通信连接 +- `disconnect()`: 断开通信连接 +- `send()`: 发送数据 +- `receive()`: 接收数据 +- `is_connected()`: 检查连接状态 + +### 设备接口 (BaseDevice) +位于 [devices/base_device.py](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/devices/base_device.py),定义了设备的基本操作: +- `connect()`: 连接设备 +- `disconnect()`: 断开设备连接 +- `read_data()`: 读取设备数据 +- `write_data()`: 向设备写入数据 +- `get_status()`: 获取设备状态 + +### 存储接口 (BaseStorage) +位于 [storage/base_storage.py](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/storage/base_storage.py),定义了存储模块的基本操作: +- `save()`: 保存数据 +- `load()`: 加载数据 +- `delete()`: 删除数据 +- `exists()`: 检查键是否存在 +- `list_keys()`: 列出所有键 + +### 命令处理器接口 (BaseHandler) +位于 [core/base_handler.py](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/core/base_handler.py),定义了命令处理的基本操作: +- `handle_command()`: 处理命令 +- `register_command()`: 注册命令处理函数 +- `unregister_command()`: 注销命令处理函数 + +## 注意事项 + +1. 确保硬件连接正确,特别是UART接口不要接反 +2. 根据实际硬件调整配置文件中的串口设备路径 +3. 注意LoRa频段的合法性,遵守当地无线电管理规定 +4. 建议在开发阶段使用DEBUG日志级别,生产环境使用INFO或更高 +5. LwM2M 功能需要特殊处理,因为标准包不可用 \ No newline at end of file diff --git a/README.md b/README.md index a7d0f8e..af61f6e 100644 --- a/README.md +++ b/README.md @@ -32,67 +32,162 @@ 5. 临时保存栏内设备故障信息,直到上报成功后清除 6. 根据批量指令控制对应设备工作 +# 猪舍控制器 + +猪舍控制器是一个用于监控和控制猪舍环境的系统。它可以通过LoRa与上位机通信,并通过RS485总线控制传感器和执行器设备。 + +## 协议栈和技术选型 + +本系统采用以下物联网标准协议栈: + +### 物理层 +- **LoRa**:低功耗广域网物理层技术,提供远距离无线传输能力 + +### 数据链路层和网络层 +- **LoRaWAN**:基于LoRa物理层的广域网协议,提供设备认证、加密和网络管理 + +### 传输层 +- **CoAP**:受限应用协议,轻量级的RESTful协议,适用于资源受限设备 + +### 应用层 +- **LwM2M**:轻量级机器到机器协议,提供设备管理、固件更新等功能 + +### 数据格式 +- **SenML**:传感器标记语言,标准化的传感器数据表示格式 + +这种协议栈选择具有以下优势: +1. **低功耗**:适合电池供电或节能要求高的场景 +2. **远距离传输**:LoRa技术可实现数公里覆盖 +3. **标准化**:采用业界标准协议,便于系统集成和扩展 +4. **安全性**:LoRaWAN和CoAP均提供安全机制 +5. **互操作性**:基于标准协议,便于与不同厂商设备集成 + ## 系统架构 -### 通信协议 -- 上位机和猪舍主控间通过LoRa协议互联 -- 主控与设备/传感器通过Modbus RTU协议通信 - -### 硬件组成 -- 主控制器:树莓派PICO RP2040 -- 通信接口:RS485总线、LoRa无线模块 -- 传感器:硫化氢、氨气、二氧化碳、光照、温度、湿度、风速、气压等 -- 执行设备:风机、水帘、喷淋系统、除臭水帘、刮粪机、电磁阀等 - -### 硬件拓扑图 - -```mermaid -graph LR - A[猪场主控] -->|LoRa| B[猪舍主控] - B -->|Modbus RTU| C[RS485总线1] - B -->|Modbus RTU| D[RS485总线2] - C -->|Modbus RTU| E1[硫化氢传感器] - C -->|Modbus RTU| E2[氨气传感器] - C -->|Modbus RTU| E3[二氧化碳传感器] - C -->|Modbus RTU| E4[光照传感器] - C -->|Modbus RTU| E5[温度传感器] - C -->|Modbus RTU| E6[湿度传感器] - C -->|Modbus RTU| E7[风速传感器] - C -->|Modbus RTU| E8[气压传感器] - D -->|Modbus RTU| F[继电器] - F -->|通/断电| G1[风机] - F -->|通/断电| G2[水帘] - F -->|通/断电| G3[栏内喷淋] - F -->|通/断电| G4[除臭水帘] - F -->|通/断电| G5[刮粪机] - F -->|通/断电| H[电磁五通阀] - H -->|通/断气| I[气动三通阀] +``` +猪舍控制器 +├── 通信层 (LoRa) +├── 控制层 (核心逻辑) +├── 设备层 (传感器和执行器) +└── 存储层 (数据存储) ``` -## 使用说明 +## 抽象接口设计 -### 硬件准备 -1. 树莓派 PICO RP2040 开发板 -2. RS485 转 UART 模块 -3. LoRa 模块 (如 SX1276/SX1278) -4. 传感器和设备(如上述硬件拓扑图所示) +为了提高系统的可扩展性和可维护性,我们定义了以下抽象接口: -### 安装步骤 -1. 按照硬件拓扑图连接所有设备和传感器 -2. 将程序上传到树莓派 PICO -3. 配置系统参数和设备信息 -4. 启动系统 +### 通信接口 (BaseComm) +定义了通信模块的基本操作,包括连接、断开连接、发送和接收数据等方法。 -### 启动系统 -```bash -# 使用 mpremote 运行主程序 -mpremote connect [设备路径] run main.py -``` -或者直接将 main.py 设置为启动脚本,让 PICO 上电后自动运行 +### 设备接口 (BaseDevice) +定义了设备的基本操作,包括连接、断开连接、读取数据、写入数据和获取状态等方法。 -## 开发相关 +### 存储接口 (BaseStorage) +定义了存储模块的基本操作,包括保存、加载、删除数据等方法。 -详细的开发指南、项目结构、配置说明和技术规范请参考 [DEVELOPMENT.md](DEVELOPMENT.md) 文件。 +### 命令处理器接口 (BaseHandler) +定义了命令处理的基本操作,包括处理命令、注册和注销命令处理函数等方法。 + +## 设计理念 + +按照功能区分将传感器和执行器分别连接到不同的RS485总线上,可以带来以下优势: + +1. **减少总线负载**:传感器通常需要频繁读取数据,而执行器可能需要较大的电流,分离可以避免相互干扰。 +2. **提高响应速度**:控制命令可以直接发送到执行器总线,无需等待传感器数据采集完成。 +3. **增强系统稳定性**:一条总线故障不会影响另一条总线上的设备。 +4. **便于维护**:可以根据需要单独重启或维护某一总线。 + +## 配置说明 + +系统配置文件为 `config.json`,如果不存在,系统会根据默认配置创建。配置项包括: + +### LoRa通信配置 +- `lora.address`: 本机LoRa地址 +- `lora.frequency`: 工作频率(MHz) +- `lora.bandwidth`: 带宽(kHz) +- `lora.spreading_factor`: 扩频因子 +- `lora.coding_rate`: 编码率 +- `lora.encryption_key`: 加密密钥 + +### 上位机配置 +- `master.lora_address`: 上位机LoRa地址 +- `master.protocol`: 与上位机通信协议 + +### 总线配置 +- `bus.sensor.port`: 传感器总线串口 +- `bus.sensor.baudrate`: 传感器总线波特率 +- `bus.actuator.port`: 执行器总线串口 +- `bus.actuator.baudrate`: 执行器总线波特率 + +### 日志配置 +- `log.level`: 日志级别 (DEBUG, INFO, WARNING, ERROR) +- `log.file_path`: 日志文件路径 +- `log.max_size`: 日志文件最大大小 +- `log.backup_count`: 保留的日志文件数量 +- `log.report_errors`: 是否上报错误信息 +- `log.terminate_on_report_failure`: 错误上报失败时是否终止程序 + +### 系统参数 +- `system.heartbeat_interval`: 心跳包发送间隔(秒) +- `system.data_collection_interval`: 数据采集间隔(秒) +- `system.command_timeout`: 命令超时时间(秒) +- `system.retry_count`: 命令重试次数 +- `system.error_handling`: 错误处理策略 + +### 设备配置 +- `devices`: 设备列表(包括传感器和执行器) + +每个设备包含以下属性: +- `id`: 设备唯一标识 +- `type`: 设备类型 +- `address`: 设备地址 +- `bus`: 所在总线(sensor/actuator) +- `location`: 设备位置(可选) +- `unit`: 单位(仅传感器需要,如温度单位、湿度单位等) + +参考示例配置文件 `config.json.example` 创建您的配置文件。 + +## 枚举类型定义 + +为了提高代码的可读性和维护性,系统定义了以下枚举类型: + +1. `LogLevel`: 日志等级枚举 (DEBUG, INFO, WARNING, ERROR, CRITICAL) +2. `DeviceType`: 设备类型枚举 (包括温度、湿度等传感器类型和喂料口、阀门等执行器类型) +3. `BusType`: 总线类型枚举 (SENSOR, ACTUATOR) +4. `ErrorHandlingStrategy`: 错误处理策略枚举 (RETRY, SKIP, ALERT) + +## 日志和错误处理机制 + +考虑到树莓派等嵌入式设备的存储空间限制,系统采用以下策略: + +1. **限制日志文件大小**:默认将日志文件大小限制为1MB,仅保留一个备份文件 +2. **错误上报机制**:当发生错误时,系统会尝试通过LoRa将错误信息上报给上位机 +3. **上报成功处理**:错误信息上报成功后,系统会删除本地日志中的该条目 +4. **上报失败处理**:如果错误信息上报失败,说明与上位机之间的通信不稳定,系统将根据配置决定是否终止程序运行 + +这种机制既节省了本地存储空间,又能确保关键错误信息能够及时传递给上位机。 + +## 开发顺序建议 + +当然,从对其他模块依赖最小的模块开始开发是一个明智的策略,以便逐步构建项目的基础。以下是建议的开发顺序: + +1. **配置模块(config.py)**:首先实现配置模块,以定义应用程序所需的基本配置。这将为其他模块提供必要的设置。 + +2. **实用程序模块(utils/)**:开发实用程序函数,这些函数可以在整个项目中被重复使用。这样可以为其他模块提供基本的辅助功能。 + +3. **数据存储模块(storage/)**:实现数据存储逻辑,包括存储传感器数据、设备状态和配置信息。这一模块可以在独立于其他模块的情况下开发。 + +4. **设备交互模块(devices/)**:实现与设备交互的模块,包括传感器和执行器。这将为之后的通信和核心逻辑提供基础。 + +5. **通信模块(comms/)**:开发通信模块,以处理与上位机和设备的通信协议。此模块可能需要依赖设备交互模块。 + +6. **核心逻辑模块(core/)**:实现核心逻辑,包括处理命令、控制设备和管理传感器数据。这一模块将利用之前开发的模块。 + +7. **测试模块(tests/)**:在开发过程中,逐步添加测试用例以验证每个模块的功能。 + +8. **主程序(main.py)**:最后实现主程序,作为应用程序的入口点,将所有模块整合在一起。 + +这种顺序将帮助你逐步构建项目,并确保每个模块在开发过程中得到充分的测试和验证。 ## 许可证 diff --git a/comms/base_comm.py b/comms/base_comm.py index 00336a1..5b13f88 100644 --- a/comms/base_comm.py +++ b/comms/base_comm.py @@ -1 +1,72 @@ -# 通信接口 \ No newline at end of file +from abc import ABC, abstractmethod +from typing import Any, Callable, Optional + + +class BaseComm(ABC): + """ + 通信接口抽象基类 + 定义所有通信模块需要实现的基本方法 + """ + + @abstractmethod + def connect(self) -> bool: + """ + 建立通信连接 + + Returns: + bool: 连接是否成功 + """ + pass + + @abstractmethod + def disconnect(self) -> None: + """ + 断开通信连接 + """ + pass + + @abstractmethod + def send(self, data: bytes, address: Optional[str] = None) -> bool: + """ + 发送数据 + + Args: + data: 要发送的数据 + address: 目标地址(可选) + + Returns: + bool: 发送是否成功 + """ + pass + + @abstractmethod + def receive(self, timeout: Optional[float] = None) -> Optional[bytes]: + """ + 接收数据 + + Args: + timeout: 超时时间(秒) + + Returns: + bytes: 接收到的数据,如果没有数据则返回None + """ + pass + + @abstractmethod + def is_connected(self) -> bool: + """ + 检查通信连接状态 + + Returns: + bool: 是否已连接 + """ + pass + + def set_callback(self, callback: Callable[[bytes], None]) -> None: + """ + 设置数据接收回调函数 + + Args: + callback: 接收数据时调用的回调函数 + """ + pass \ No newline at end of file diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..d96818a --- /dev/null +++ b/config.json.example @@ -0,0 +1,73 @@ +{ + "lora": { + "address": "0x1234", + "frequency": 433, + "bandwidth": 125, + "spreading_factor": 7, + "coding_rate": 5, + "encryption_key": "your_encryption_key_here" + }, + "master": { + "lora_address": "0xABCD", + "protocol": "modbus" + }, + "bus": { + "sensor": { + "port": "/dev/ttyUSB0", + "baudrate": 9600, + "devices": [] + }, + "actuator": { + "port": "/dev/ttyUSB1", + "baudrate": 9600, + "devices": [] + } + }, + "log": { + "level": "INFO", + "file_path": "logs/pig_house.log", + "max_size": "1MB", + "backup_count": 1, + "report_errors": true, + "terminate_on_report_failure": true + }, + "system": { + "heartbeat_interval": 60, + "data_collection_interval": 30, + "command_timeout": 10, + "retry_count": 3, + "error_handling": "retry" + }, + "devices": [ + { + "id": "temp_01", + "type": "temperature", + "address": "0x01", + "bus": "sensor", + "unit": "celsius", + "location": "main_hall" + }, + { + "id": "humidity_01", + "type": "humidity", + "address": "0x02", + "bus": "sensor", + "unit": "%", + "location": "main_hall" + }, + { + "id": "feed_01", + "type": "feed_port", + "address": "0x10", + "bus": "actuator", + "location": "feeding_area_1" + }, + { + "id": "water_01", + "type": "water_valve", + "address": "0x11", + "bus": "actuator", + "location": "watering_area_1" + } + ] +} \ No newline at end of file diff --git a/config.py b/config.py index e69de29..cc4cc83 100644 --- a/config.py +++ b/config.py @@ -0,0 +1,165 @@ +import json +import os + +class Config: + def __init__(self, config_file="config.json"): + self.config_file = config_file + self.default_config = { + # LoRa通信配置 + "lora": { + "address": "0x1234", + "frequency": 433, + "bandwidth": 125, + "spreading_factor": 7, + "coding_rate": 5, + "encryption_key": "default_key" + }, + + # 上位机配置 + "master": { + "lora_address": "0x5678", + "protocol": "modbus" + }, + + # 总线配置 + "bus": { + "sensor": { + "port": "/dev/ttyUSB0", + "baudrate": 9600, + "devices": [] + }, + "actuator": { + "port": "/dev/ttyUSB1", + "baudrate": 9600, + "devices": [] + } + }, + + # 日志配置 + "log": { + "level": "INFO", + "file_path": "logs/pig_house.log", + "max_size": "1KB", # 减小日志文件大小 + "backup_count": 1, # 只保留一个备份文件 + "report_errors": True, # 是否上报错误 + "terminate_on_report_failure": True # 上报失败时是否终止程序 + }, + + # 系统参数 + "system": { + "heartbeat_interval": 60, # 心跳包间隔(秒) + "data_collection_interval": 300, # 数据采集间隔(秒) + "command_timeout": 10, # 命令超时时间(秒) + "retry_count": 3, # 重试次数 + "error_handling": "retry" + }, + + # 设备配置 + "devices": [ + { + "id": "temp_01", + "type": "temperature", + "address": "0x01", + "bus": "sensor", + "unit": "celsius" + }, + { + "id": "humidity_01", + "type": "humidity", + "address": "0x02", + "bus": "sensor", + "unit": "%" + }, + { + "id": "feed_01", + "type": "feed_port", + "address": "0x10", + "bus": "actuator" + }, + { + "id": "water_01", + "type": "water_valve", + "address": "0x11", + "bus": "actuator" + } + ] + } + self.config = {} + self.load_config() + + def load_config(self): + """加载配置文件""" + if os.path.exists(self.config_file): + try: + with open(self.config_file, "r", encoding="utf-8") as file: + config_data = json.load(file) + # 合并默认配置和文件配置 + self.config = self._merge_dict(self.default_config, config_data) + except Exception as e: + print(f"加载配置文件出错: {e},使用默认配置") + self.config = self.default_config + else: + print("配置文件不存在,使用默认配置") + self.config = self.default_config + self.save_config() + + def save_config(self): + """保存配置到文件""" + try: + # 确保目录存在 + os.makedirs(os.path.dirname(self.config_file), exist_ok=True) + except: + pass + + with open(self.config_file, "w", encoding="utf-8") as file: + json.dump(self.config, file, indent=4, ensure_ascii=False) + + def get(self, key_path, default=None): + """根据路径获取配置项,例如: get('lora.address')""" + keys = key_path.split('.') + value = self.config + try: + for key in keys: + value = value[key] + return value + except (KeyError, TypeError): + return default + + def set(self, key_path, value): + """根据路径设置配置项""" + keys = key_path.split('.') + config_ref = self.config + for key in keys[:-1]: + if key not in config_ref: + config_ref[key] = {} + config_ref = config_ref[key] + config_ref[keys[-1]] = value + + def _merge_dict(self, default, override): + """合并两个字典,保留默认值""" + merged = default.copy() + for key, value in override.items(): + if key in merged and isinstance(merged[key], dict) and isinstance(value, dict): + merged[key] = self._merge_dict(merged[key], value) + else: + merged[key] = value + return merged + + # 便捷访问方法 + def lora_config(self): + return self.config.get('lora', {}) + + def master_config(self): + return self.config.get('master', {}) + + def bus_config(self): + return self.config.get('bus', {}) + + def log_config(self): + return self.config.get('log', {}) + + def system_config(self): + return self.config.get('system', {}) + + def devices_config(self): + return self.config.get('devices', []) \ No newline at end of file diff --git a/core/base_handler.py b/core/base_handler.py new file mode 100644 index 0000000..6cbb3d2 --- /dev/null +++ b/core/base_handler.py @@ -0,0 +1,50 @@ +from abc import ABC, abstractmethod +from typing import Any, Dict, Optional + + +class BaseHandler(ABC): + """ + 命令处理器抽象基类 + 定义所有命令处理器需要实现的基本方法 + """ + + @abstractmethod + def handle_command(self, command: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + 处理命令 + + Args: + command: 命令类型 + data: 命令数据 + + Returns: + Dict[str, Any]: 处理结果 + """ + pass + + @abstractmethod + def register_command(self, command: str, handler_func) -> bool: + """ + 注册命令处理函数 + + Args: + command: 命令类型 + handler_func: 处理函数 + + Returns: + bool: 注册是否成功 + """ + pass + + @abstractmethod + def unregister_command(self, command: str) -> bool: + """ + 注销命令处理函数 + + Args: + command: 命令类型 + + Returns: + bool: 注销是否成功 + """ + pass \ No newline at end of file diff --git a/core/enums.py b/core/enums.py new file mode 100644 index 0000000..e2ed00c --- /dev/null +++ b/core/enums.py @@ -0,0 +1,43 @@ +from enum import Enum + + +class LogLevel(Enum): + """日志等级枚举""" + DEBUG = "DEBUG" + INFO = "INFO" + WARNING = "WARNING" + ERROR = "ERROR" + CRITICAL = "CRITICAL" + + +class DeviceType(Enum): + """设备类型枚举""" + # 传感器类型 + TEMPERATURE = "temperature" + HUMIDITY = "humidity" + PRESSURE = "pressure" + LIGHT = "light" + CO2 = "co2" + NH3 = "nh3" # 氨气 + H2S = "h2s" # 硫化氢 + + # 执行器类型 + FEED_PORT = "feed_port" + WATER_VALVE = "water_valve" + FAN = "fan" + HEATER = "heater" + COOLER = "cooler" + LIGHT_CONTROLLER = "light_controller" + + +class BusType(Enum): + """总线类型枚举""" + SENSOR = "sensor" + ACTUATOR = "actuator" + + +class ErrorHandlingStrategy(Enum): + """错误处理策略枚举""" + RETRY = "retry" + SKIP = "skip" + ALERT = "alert" \ No newline at end of file diff --git a/devices/base_device.py b/devices/base_device.py index ceb3ff9..e44a15f 100644 --- a/devices/base_device.py +++ b/devices/base_device.py @@ -1 +1,100 @@ -# 设备抽象层 \ No newline at end of file +from abc import ABC, abstractmethod +from typing import Any, Dict, Optional +from enum import Enum + + +class DeviceStatus(Enum): + """设备状态枚举""" + UNKNOWN = "unknown" + ONLINE = "online" + OFFLINE = "offline" + ERROR = "error" + BUSY = "busy" + + +class BaseDevice(ABC): + """ + 设备接口抽象基类 + 定义所有设备需要实现的基本方法 + """ + + def __init__(self, device_id: str, device_type: str, address: str, bus: str): + """ + 初始化设备 + + Args: + device_id: 设备唯一标识 + device_type: 设备类型 + address: 设备地址 + bus: 所在总线 + """ + self.device_id = device_id + self.device_type = device_type + self.address = address + self.bus = bus + self.status = DeviceStatus.UNKNOWN + + @abstractmethod + def connect(self) -> bool: + """ + 连接设备 + + Returns: + bool: 连接是否成功 + """ + pass + + @abstractmethod + def disconnect(self) -> None: + """ + 断开设备连接 + """ + pass + + @abstractmethod + def read_data(self) -> Optional[Dict[str, Any]]: + """ + 读取设备数据 + + Returns: + dict: 设备数据,格式为 {数据名: 数据值},失败时返回None + """ + pass + + @abstractmethod + def write_data(self, data: Dict[str, Any]) -> bool: + """ + 向设备写入数据 + + Args: + data: 要写入的数据,格式为 {数据名: 数据值} + + Returns: + bool: 写入是否成功 + """ + pass + + @abstractmethod + def get_status(self) -> DeviceStatus: + """ + 获取设备状态 + + Returns: + DeviceStatus: 设备状态 + """ + pass + + def get_info(self) -> Dict[str, Any]: + """ + 获取设备信息 + + Returns: + dict: 设备信息 + """ + return { + "device_id": self.device_id, + "device_type": self.device_type, + "address": self.address, + "bus": self.bus, + "status": self.status.value + } \ No newline at end of file diff --git a/main.py b/main.py index 62a1ccc..e550481 100644 --- a/main.py +++ b/main.py @@ -4,4 +4,3 @@ """ 猪舍主控系统主程序入口 """ - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f9eb0dc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,21 @@ +# 项目依赖包 + +# LoRa通信相关 +pyserial>=3.5 +paho-mqtt>=1.6.1 + +# CoAP协议支持 +aiocoap>=0.4.5 + +# LwM2M支持 (使用wakaama-python作为替代) +# 注意:lwm2m-client在PyPI上不可用,需要手动安装或使用其他实现 +# wakaama-python>=0.1.0 + +# 数据格式(SenML) +senml>=1.0.0 + +# 其他工具 +jsonschema>=4.0.0 + +# 通用依赖 +typing-extensions>=3.10.0; python_version < "3.10" \ No newline at end of file diff --git a/storage/base_storage.py b/storage/base_storage.py index a0e21d3..39a3ef8 100644 --- a/storage/base_storage.py +++ b/storage/base_storage.py @@ -1,11 +1,109 @@ -# 存储接口 +from abc import ABC, abstractmethod +from typing import Any, Dict, List, Optional -class BaseStorage: - def save(self, key: str, value): + +class BaseStorage(ABC): + """ + 存储接口抽象基类 + 定义所有存储模块需要实现的基本方法 + """ + + @abstractmethod + def save(self, key: str, value: Any) -> bool: + """ + 保存数据 + + Args: + key: 键 + value: 值 + + Returns: + bool: 保存是否成功 + """ raise NotImplementedError - def load(self, key: str, default=None): + @abstractmethod + def load(self, key: str, default: Any = None) -> Any: + """ + 加载数据 + + Args: + key: 键 + default: 默认值 + + Returns: + Any: 加载的数据 + """ raise NotImplementedError - def delete(self, key: str): + @abstractmethod + def delete(self, key: str) -> bool: + """ + 删除数据 + + Args: + key: 要删除的键 + + Returns: + bool: 删除是否成功 + """ raise NotImplementedError + + @abstractmethod + def exists(self, key: str) -> bool: + """ + 检查键是否存在 + + Args: + key: 键 + + Returns: + bool: 键是否存在 + """ + raise NotImplementedError + + @abstractmethod + def list_keys(self) -> List[str]: + """ + 列出所有键 + + Returns: + List[str]: 所有键的列表 + """ + raise NotImplementedError + + @abstractmethod + def clear(self) -> bool: + """ + 清空所有数据 + + Returns: + bool: 清空是否成功 + """ + raise NotImplementedError + + @abstractmethod + def save_batch(self, data: Dict[str, Any]) -> bool: + """ + 批量保存数据 + + Args: + data: 要保存的数据字典 + + Returns: + bool: 保存是否成功 + """ + raise NotImplementedError + + @abstractmethod + def load_batch(self, keys: List[str]) -> Dict[str, Any]: + """ + 批量加载数据 + + Args: + keys: 要加载的键列表 + + Returns: + Dict[str, Any]: 加载的数据字典 + """ + raise NotImplementedError \ No newline at end of file