2025-11-29 17:57:45 +08:00
|
|
|
|
# LoRa 通信层统一重构方案
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 目标
|
|
|
|
|
|
|
|
|
|
|
|
统一项目当前并存的两种 LoRa 通信模式(基于 ChirpStack API 和基于串口透传),使其在架构层面遵循相同的接口和设计模式。最终实现:
|
|
|
|
|
|
|
|
|
|
|
|
- **业务逻辑统一**:所有上行业务处理逻辑集中在一个地方,与具体的通信方式无关。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
- **发送接口统一**:上层服务使用同一个接口发送下行指令,无需关心底层实现。
|
|
|
|
|
|
- **架构清晰**:明确划分基础设施层(负责传输)和应用层(负责业务)的职责,并确保正确的依赖方向 (`app` -> `infra`)。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
- **高扩展性**:未来支持新的通信方式时,只需添加新的“适配器”,而无需改动核心业务代码。
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 背景与问题分析
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1. 当前存在两种 LoRa 通信模式
|
|
|
|
|
|
|
|
|
|
|
|
1. **ChirpStack 模式**: 通过 `internal/infra/transport/lora/chirp_stack.go` 实现发送,通过 `internal/app/listener/chirp_stack/chirp_stack.go` 监听并处理 ChirpStack Webhook 推送的数据。
|
|
|
|
|
|
2. **串口透传模式**: 通过 `internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` 实现发送和接收处理。
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2. 核心差异
|
|
|
|
|
|
|
|
|
|
|
|
| 特性 | ChirpStack 模式 | 串口透传模式 |
|
|
|
|
|
|
| :--- | :--- | :--- |
|
|
|
|
|
|
| **通信模型** | 双向、有状态、异步API调用 | 单向、无状态、直接串口读写 |
|
|
|
|
|
|
| **接收机制** | Webhook (HTTP POST) 推送 | 主动从串口读取字节流 |
|
|
|
|
|
|
| **数据格式** | JSON 包装 + Base64 编码 | 自定义二进制物理帧 |
|
|
|
|
|
|
| **寻址方式**| `DevEui` | 自定义 16 位网络地址 |
|
|
|
|
|
|
| **核心职责** | LNS,管理会话、ACK、队列 | 纯粹的“无线串口” |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3. 问题
|
|
|
|
|
|
|
|
|
|
|
|
- **业务逻辑分散**:处理 `CollectResult` 的业务逻辑在 `chirp_stack.go` 和 `lora_mesh_uart_passthrough_transport.go` 中都存在,造成代码重复和维护困难。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
- **职责不清**:`lora_mesh_uart_passthrough_transport.go` 同时承担了基础设施(串口读写)和应用(处理业务)两种职责。
|
|
|
|
|
|
- **依赖关系混乱**:为了让 `infra` 层的串口模块能调用业务逻辑,可能会导致 `infra` 层反向依赖 `app` 层,破坏了项目的核心架构原则。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
## 3. 统一架构设计方案
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1. 核心思想
|
|
|
|
|
|
|
2025-12-01 14:32:50 +08:00
|
|
|
|
采用 **端口与适配器模式 (Ports and Adapters Pattern)**,严格遵守 **依赖倒置原则**。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
2025-12-01 14:32:50 +08:00
|
|
|
|
- **端口 (Port)**:在 `infra` 层定义一个 `UpstreamHandler` 接口。这个接口是 `infra` 层向上层暴露的“端口”,它规定了上行业务处理器必须满足的协约。
|
|
|
|
|
|
- **适配器 (Adapter)**:在 `app` 层创建一个 `LoRaListener` 作为“适配器”,它实现 `infra` 层定义的 `UpstreamHandler` 接口,并封装所有核心业务处理逻辑。
|
|
|
|
|
|
- **依赖注入**:在系统启动时,将 `app` 层的 `LoRaListener` 实例注入到需要它的 `infra` 层组件中。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
### 3.2. 统一接口定义
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.2.1. 发送接口 (已存在,无需修改)
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// file: internal/infra/transport/transport.go
|
|
|
|
|
|
package transport
|
|
|
|
|
|
|
|
|
|
|
|
type Communicator interface {
|
|
|
|
|
|
Send(ctx context.Context, address string, payload []byte) (*SendResult, error)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-12-01 14:32:50 +08:00
|
|
|
|
#### 3.2.2. 接收处理接口 (端口定义)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
2025-12-01 14:32:50 +08:00
|
|
|
|
此接口定义了 `infra` 层对上行业务处理器的期望,是 `infra` 层向上层暴露的“端口”。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2025-12-01 14:32:50 +08:00
|
|
|
|
// file: internal/infra/transport/transport.go
|
|
|
|
|
|
package transport
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/proto"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// UpstreamHandler 定义了处理所有来源的上行数据的统一协约。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
// 任何实现了上行消息监听的基础设施(如串口、MQTT客户端),都应该在收到消息后调用此接口的实现者。
|
|
|
|
|
|
// 这样,基础设施层只负责“接收和解析”,而将“业务处理”的控制权交给了上层。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
type UpstreamHandler interface {
|
|
|
|
|
|
// HandleInstruction 处理来自设备的、已解析为Instruction的业务指令。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
HandleInstruction(ctx context.Context, sourceAddr string, instruction *proto.Instruction) error
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
// HandleStatus 处理非业务指令的设备状态更新,例如信号强度、电量等。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
HandleStatus(ctx context.Context, sourceAddr string, status map[string]interface{}) error
|
2025-11-29 17:57:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3. 组件职责划分 (重构后)
|
|
|
|
|
|
|
2025-12-01 14:32:50 +08:00
|
|
|
|
#### 3.3.1. 统一业务处理器 (应用层适配器)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
- **文件**: `internal/app/listener/lora_listener.go` (新)
|
|
|
|
|
|
- **职责**:
|
2025-12-01 14:32:50 +08:00
|
|
|
|
- 实现 `transport.UpstreamHandler` 接口。
|
|
|
|
|
|
- 包含所有处理业务所需的依赖(如领域服务、仓储等)。
|
|
|
|
|
|
- 实现 `HandleInstruction` 方法,通过 `switch-case` 编排所有核心业务。
|
|
|
|
|
|
- 实现 `HandleStatus` 方法,处理设备状态更新。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
- **这是项目中唯一处理 LoRa 上行业务的地方。**
|
|
|
|
|
|
|
2025-12-01 14:32:50 +08:00
|
|
|
|
#### 3.3.2. 基础设施层 (Infra Layer)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
- **文件 1**: `internal/app/listener/chirp_stack/chirp_stack.go` (重构)
|
|
|
|
|
|
- **职责**: 纯粹的 Webhook 适配器。
|
|
|
|
|
|
- 移除所有业务逻辑和数据库依赖。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
- 依赖 `transport.UpstreamHandler` 接口。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
- 功能:接收 Webhook -> 解析 JSON -> 调用 `handler.HandleInstruction` 或 `handler.HandleStatus`。
|
|
|
|
|
|
|
|
|
|
|
|
- **文件 2**: `internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` (重构)
|
2025-12-01 14:32:50 +08:00
|
|
|
|
- **职责**: 纯粹的串口传输工具。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
- 移除所有业务逻辑和数据库依赖。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
- 依赖 `transport.UpstreamHandler` 接口。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
- 功能:管理串口 -> 读字节流 -> 重组分片 -> 解析 `proto.Instruction` -> 调用 `handler.HandleInstruction`。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4. 架构图 (重构后)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
+--------------------------------+
|
|
|
|
|
|
| Upper-Level Services |
|
|
|
|
|
|
| (e.g., DeviceService) |
|
|
|
|
|
|
+--------------------------------+
|
|
|
|
|
|
|
|
|
|
|
|
|
v (uses)
|
|
|
|
|
|
+--------------------------------+
|
2025-12-01 14:32:50 +08:00
|
|
|
|
| transport.Communicator (I) | <-- Infra Layer (Send Port)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
+--------------------------------+
|
|
|
|
|
|
^ ^
|
|
|
|
|
|
| | (implements)
|
|
|
|
|
|
+------------------+------------------+
|
2025-12-01 14:32:50 +08:00
|
|
|
|
| ChirpStackSender | UartSender | <-- Infra Layer (Senders)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
+------------------+------------------+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+--------------------------------+
|
2025-12-01 14:32:50 +08:00
|
|
|
|
| listener.LoRaListener | <-- App Layer (Adapter)
|
|
|
|
|
|
| (Implements UpstreamHandler) |
|
|
|
|
|
|
+--------------------------------+
|
|
|
|
|
|
^
|
|
|
|
|
|
| (dependency, via interface)
|
|
|
|
|
|
+--------------------------------+
|
|
|
|
|
|
| transport.UpstreamHandler (I) | <-- Infra Layer (Receive Port)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
+--------------------------------+
|
|
|
|
|
|
^ ^
|
|
|
|
|
|
| | (calls)
|
|
|
|
|
|
+------------------+------------------+
|
2025-12-01 14:32:50 +08:00
|
|
|
|
| ChirpStackWebhook| UartPassthrough | <-- Infra Layer (Receivers)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
+------------------+------------------+
|
|
|
|
|
|
^ ^
|
|
|
|
|
|
| | (receives from)
|
|
|
|
|
|
+------------------+------------------+
|
2025-12-01 14:32:50 +08:00
|
|
|
|
| HTTP Webhook | Serial Port |
|
2025-11-29 17:57:45 +08:00
|
|
|
|
+------------------+------------------+
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.5. 依赖注入与组装示例
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// file: internal/core/component_initializers.go
|
|
|
|
|
|
|
2025-12-01 14:32:50 +08:00
|
|
|
|
// 1. 创建统一的业务处理器 (App层适配器)
|
|
|
|
|
|
// 它实现了 infra 层的 transport.UpstreamHandler 接口
|
|
|
|
|
|
loraListener := listener.NewLoRaListener(logger, dbRepo1, dbRepo2)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 2. 初始化 ChirpStack 模式
|
|
|
|
|
|
// 2a. 创建 ChirpStack 的发送器 (infra)
|
|
|
|
|
|
chirpStackCommunicator := chirp_stack.NewChirpStackTransport(...)
|
2025-12-01 14:32:50 +08:00
|
|
|
|
// 2b. 创建 ChirpStack 的监听器 (infra),并注入 App 层的业务处理器
|
|
|
|
|
|
chirpStackListener := chirp_stack.NewChirpStackListener(loraListener)
|
2025-11-29 17:57:45 +08:00
|
|
|
|
// 2c. 注册 Webhook 路由
|
2025-12-01 14:32:50 +08:00
|
|
|
|
api.RegisterWebhook("/chirpstack", chirpStackListener.Handler())
|
2025-11-29 17:57:45 +08:00
|
|
|
|
|
|
|
|
|
|
// 3. 初始化串口透传模式
|
2025-12-01 14:32:50 +08:00
|
|
|
|
// 3a. 创建串口的传输工具 (infra),并注入 App 层的业务处理器
|
2025-11-29 17:57:45 +08:00
|
|
|
|
uartTransport := lora.NewLoRaMeshUartPassthroughTransport(port, loraListener)
|
|
|
|
|
|
// 3b. 启动串口监听
|
|
|
|
|
|
uartTransport.Listen()
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 向上层业务提供统一的发送器
|
|
|
|
|
|
var finalCommunicator transport.Communicator
|
|
|
|
|
|
if config.UseChirpStack {
|
|
|
|
|
|
finalCommunicator = chirpStackCommunicator
|
|
|
|
|
|
} else {
|
|
|
|
|
|
finalCommunicator = uartTransport
|
|
|
|
|
|
}
|
|
|
|
|
|
// 将 finalCommunicator 注入到需要发送指令的服务中...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 实施步骤
|
|
|
|
|
|
|
2025-12-01 14:32:50 +08:00
|
|
|
|
1. **定义端口**: 在 `internal/infra/transport/transport.go` 中定义 `UpstreamHandler` 接口。
|
|
|
|
|
|
2. **创建适配器**: 创建 `internal/app/listener/lora_listener.go`,定义 `LoRaListener` 结构体,并实现 `transport.UpstreamHandler` 接口。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
3. **迁移业务逻辑**: 将 `chirp_stack.go` 和 `lora_mesh_uart_passthrough_transport.go` 中的业务逻辑(查库、存数据等)逐步迁移到 `lora_listener.go` 的对应方法中。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
4. **重构基础设施**:
|
|
|
|
|
|
- 清理 `chirp_stack.go`,移除 Repo 依赖,改为依赖 `transport.UpstreamHandler` 接口,并调用其方法。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
- 清理 `lora_mesh_uart_passthrough_transport.go`,做同样的操作。
|
|
|
|
|
|
5. **更新依赖注入**: 修改 `component_initializers.go`,按照 `3.5` 中的示例完成组件的创建和注入。
|
|
|
|
|
|
6. **测试与验证**: 对两种模式分别进行完整的上下行通信测试。
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 收益
|
|
|
|
|
|
|
|
|
|
|
|
- **消除代码重复**:业务逻辑仅存在于一处。
|
|
|
|
|
|
- **职责清晰**:基础设施层只管传输,应用层只管业务。
|
2025-12-01 14:32:50 +08:00
|
|
|
|
- **正确的依赖关系**:确保了 `app` -> `infra` 的单向依赖,核心架构更加稳固。
|
2025-11-29 17:57:45 +08:00
|
|
|
|
- **可维护性**:修改业务逻辑只需改一个文件,修改传输细节不影响业务。
|
|
|
|
|
|
- **可测试性**:可以轻松地对 `LoRaListener` 进行单元测试,无需真实的硬件或网络。
|