AI生成代码
This commit is contained in:
173
DEVELOPMENT.md
Normal file
173
DEVELOPMENT.md
Normal file
@@ -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 功能需要特殊处理,因为标准包不可用
|
||||||
199
README.md
199
README.md
@@ -32,67 +32,162 @@
|
|||||||
5. 临时保存栏内设备故障信息,直到上报成功后清除
|
5. 临时保存栏内设备故障信息,直到上报成功后清除
|
||||||
6. 根据批量指令控制对应设备工作
|
6. 根据批量指令控制对应设备工作
|
||||||
|
|
||||||
|
# 猪舍控制器
|
||||||
|
|
||||||
|
猪舍控制器是一个用于监控和控制猪舍环境的系统。它可以通过LoRa与上位机通信,并通过RS485总线控制传感器和执行器设备。
|
||||||
|
|
||||||
|
## 协议栈和技术选型
|
||||||
|
|
||||||
|
本系统采用以下物联网标准协议栈:
|
||||||
|
|
||||||
|
### 物理层
|
||||||
|
- **LoRa**:低功耗广域网物理层技术,提供远距离无线传输能力
|
||||||
|
|
||||||
|
### 数据链路层和网络层
|
||||||
|
- **LoRaWAN**:基于LoRa物理层的广域网协议,提供设备认证、加密和网络管理
|
||||||
|
|
||||||
|
### 传输层
|
||||||
|
- **CoAP**:受限应用协议,轻量级的RESTful协议,适用于资源受限设备
|
||||||
|
|
||||||
|
### 应用层
|
||||||
|
- **LwM2M**:轻量级机器到机器协议,提供设备管理、固件更新等功能
|
||||||
|
|
||||||
|
### 数据格式
|
||||||
|
- **SenML**:传感器标记语言,标准化的传感器数据表示格式
|
||||||
|
|
||||||
|
这种协议栈选择具有以下优势:
|
||||||
|
1. **低功耗**:适合电池供电或节能要求高的场景
|
||||||
|
2. **远距离传输**:LoRa技术可实现数公里覆盖
|
||||||
|
3. **标准化**:采用业界标准协议,便于系统集成和扩展
|
||||||
|
4. **安全性**:LoRaWAN和CoAP均提供安全机制
|
||||||
|
5. **互操作性**:基于标准协议,便于与不同厂商设备集成
|
||||||
|
|
||||||
## 系统架构
|
## 系统架构
|
||||||
|
|
||||||
### 通信协议
|
```
|
||||||
- 上位机和猪舍主控间通过LoRa协议互联
|
猪舍控制器
|
||||||
- 主控与设备/传感器通过Modbus RTU协议通信
|
├── 通信层 (LoRa)
|
||||||
|
├── 控制层 (核心逻辑)
|
||||||
### 硬件组成
|
├── 设备层 (传感器和执行器)
|
||||||
- 主控制器:树莓派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[气动三通阀]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 使用说明
|
## 抽象接口设计
|
||||||
|
|
||||||
### 硬件准备
|
为了提高系统的可扩展性和可维护性,我们定义了以下抽象接口:
|
||||||
1. 树莓派 PICO RP2040 开发板
|
|
||||||
2. RS485 转 UART 模块
|
|
||||||
3. LoRa 模块 (如 SX1276/SX1278)
|
|
||||||
4. 传感器和设备(如上述硬件拓扑图所示)
|
|
||||||
|
|
||||||
### 安装步骤
|
### 通信接口 (BaseComm)
|
||||||
1. 按照硬件拓扑图连接所有设备和传感器
|
定义了通信模块的基本操作,包括连接、断开连接、发送和接收数据等方法。
|
||||||
2. 将程序上传到树莓派 PICO
|
|
||||||
3. 配置系统参数和设备信息
|
|
||||||
4. 启动系统
|
|
||||||
|
|
||||||
### 启动系统
|
### 设备接口 (BaseDevice)
|
||||||
```bash
|
定义了设备的基本操作,包括连接、断开连接、读取数据、写入数据和获取状态等方法。
|
||||||
# 使用 mpremote 运行主程序
|
|
||||||
mpremote connect [设备路径] run main.py
|
|
||||||
```
|
|
||||||
或者直接将 main.py 设置为启动脚本,让 PICO 上电后自动运行
|
|
||||||
|
|
||||||
## 开发相关
|
### 存储接口 (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)**:最后实现主程序,作为应用程序的入口点,将所有模块整合在一起。
|
||||||
|
|
||||||
|
这种顺序将帮助你逐步构建项目,并确保每个模块在开发过程中得到充分的测试和验证。
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,72 @@
|
|||||||
# 通信接口
|
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
|
||||||
73
config.json.example
Normal file
73
config.json.example
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
165
config.py
165
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', [])
|
||||||
50
core/base_handler.py
Normal file
50
core/base_handler.py
Normal file
@@ -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
|
||||||
43
core/enums.py
Normal file
43
core/enums.py
Normal file
@@ -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"
|
||||||
@@ -1 +1,100 @@
|
|||||||
# 设备抽象层
|
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
|
||||||
|
}
|
||||||
21
requirements.txt
Normal file
21
requirements.txt
Normal file
@@ -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"
|
||||||
@@ -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
|
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
|
raise NotImplementedError
|
||||||
|
|
||||||
def delete(self, key: str):
|
@abstractmethod
|
||||||
|
def delete(self, key: str) -> bool:
|
||||||
|
"""
|
||||||
|
删除数据
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: 要删除的键
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 删除是否成功
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
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
|
||||||
Reference in New Issue
Block a user