9.0 KiB
9.0 KiB
LoRa 通信层统一重构方案
1. 目标
统一项目当前并存的两种 LoRa 通信模式(基于 ChirpStack API 和基于串口透传),使其在架构层面遵循相同的接口和设计模式。最终实现:
- 业务逻辑统一:所有上行业务处理逻辑集中在一个地方,与具体的通信方式无关。
- 发送接口统一:上层服务(如
DeviceService)使用同一个接口发送下行指令,无需关心底层实现。 - 架构清晰:明确划分基础设施层(负责传输)和应用层(负责业务)的职责。
- 高扩展性:未来支持新的通信方式时,只需添加新的“适配器”,而无需改动核心业务代码。
2. 背景与问题分析
2.1. 当前存在两种 LoRa 通信模式
- ChirpStack 模式: 通过
internal/infra/transport/lora/chirp_stack.go实现发送,通过internal/app/listener/chirp_stack/chirp_stack.go监听并处理 ChirpStack Webhook 推送的数据。 - 串口透传模式: 通过
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中都存在,造成代码重复和维护困难。 - 职责不清:
lora_mesh_uart_passthrough_transport.go同时承担了基础设施(串口读写、分片重组)和应用(处理采集结果、写数据库)两种职责,违反了分层架构原则。 - 抽象缺失:两种模式没有统一的接口,导致上层代码如果需要切换模式,将产生大量修改。
3. 统一架构设计方案
3.1. 核心思想
采用 适配器模式 (Adapter Pattern) 和 依赖倒置原则 (Dependency Inversion Principle)。
- 将所有上行业务逻辑抽离到统一的业务处理器中。
- 将
chirp_stack.go和lora_mesh_uart_passthrough_transport.go的接收部分重构为适配器。它们的唯一职责是将各自协议的数据,适配并转发给统一的业务处理器。 - 上层依赖抽象接口,而不是具体实现。
3.2. 统一接口定义
3.2.1. 发送接口 (已存在,无需修改)
此接口设计良好,上层业务通过它下发指令,无需关心底层实现。
// file: internal/infra/transport/transport.go
package transport
type Communicator interface {
Send(ctx context.Context, address string, payload []byte) (*SendResult, error)
}
3.2.2. 接收处理接口 (新定义)
此接口是应用层对所有上行数据处理能力的抽象。
// file: internal/app/listener/handler.go
package listener
import (
"context"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/proto"
)
// UpstreamHandler 定义了处理所有来源的上行数据的统一协约。
type UpstreamHandler interface {
// HandleInstruction 处理来自设备的、已解析为Instruction的业务指令。
HandleInstruction(ctx context.Context, sourceAddr string, instruction *proto.Instruction)
// HandleStatus 处理非业务指令的设备状态更新,例如信号强度、电量等。
HandleStatus(ctx context.Context, sourceAddr string, status map[string]interface{})
}
3.3. 组件职责划分 (重构后)
3.3.1. 统一业务处理器 (应用层核心)
- 文件:
internal/app/listener/lora_listener.go(新) - 职责:
- 实现
listener.UpstreamHandler接口。 - 包含所有处理业务所需的数据库仓库依赖。
- 实现
HandleInstruction方法,通过switch-case处理CollectResult,OtaUpgradeStatus,LogUploadRequest等所有核心业务。 - 实现
HandleStatus方法,处理 ChirpStack 上报的电量、信号等旁路信息。 - 这是项目中唯一处理 LoRa 上行业务的地方。
- 实现
3.3.2. 适配器层 (连接 Infra 与 App)
-
文件 1:
internal/app/listener/chirp_stack/chirp_stack.go(重构)- 职责: 纯粹的 Webhook 适配器。
- 移除所有业务逻辑和数据库依赖。
- 依赖
listener.UpstreamHandler接口。 - 功能:接收 Webhook -> 解析 JSON -> 调用
handler.HandleInstruction或handler.HandleStatus。
-
文件 2:
internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go(重构)- 职责: 纯粹的串口适配器。
- 移除所有业务逻辑和数据库依赖。
- 依赖
listener.UpstreamHandler接口。 - 功能:管理串口 -> 读字节流 -> 重组分片 -> 解析
proto.Instruction-> 调用handler.HandleInstruction。
3.3.3. 发送器 (基础设施层)
- 文件 1:
internal/infra/transport/lora/chirp_stack.go(或拆分出的新文件)- 实现
transport.Communicator接口,通过调用 ChirpStack API 完成发送。
- 实现
- 文件 2:
internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go- 实现
transport.Communicator接口,通过向串口写入数据完成发送。
- 实现
3.4. 架构图 (重构后)
+--------------------------------+
| Upper-Level Services |
| (e.g., DeviceService) |
+--------------------------------+
|
v (uses)
+--------------------------------+
| transport.Communicator (I) |
+--------------------------------+
^ ^
| | (implements)
+------------------+------------------+
| ChirpStackTransport | UartPassthroughTransport | (Infra Layer - Senders)
+------------------+------------------+
+--------------------------------+
| listener.LoRaListener |
| (Unified Business Logic) |
+--------------------------------+
^ ^
| | (calls)
+------------------+------------------+
| ChirpStackAdapter| UartPassthroughAdapter | (Adapter Layer)
+------------------+------------------+
^ ^
| | (receives from)
+------------------+------------------+
| HTTP Webhook | Serial Port | (Physical/Network Layer)
+------------------+------------------+
3.5. 依赖注入与组装示例
// file: internal/core/component_initializers.go
// 1. 创建统一的业务处理器 (单例)
loraListener := listener.NewLoRaListener(dbRepo1, dbRepo2, ...)
// 2. 初始化 ChirpStack 模式
// 2a. 创建 ChirpStack 的发送器 (infra)
chirpStackCommunicator := chirp_stack.NewChirpStackTransport(...)
// 2b. 创建 ChirpStack 的监听适配器 (app),并注入统一的业务处理器
chirpStackListenerAdapter := chirp_stack.NewChirpStackListener(loraListener)
// 2c. 注册 Webhook 路由
api.RegisterWebhook("/chirpstack", chirpStackListenerAdapter.Handler())
// 3. 初始化串口透传模式
// 3a. 创建串口的发送器/监听器 (infra),并注入统一的业务处理器
uartTransport := lora.NewLoRaMeshUartPassthroughTransport(port, loraListener)
// 3b. 启动串口监听
uartTransport.Listen()
// 4. 向上层业务提供统一的发送器
var finalCommunicator transport.Communicator
if config.UseChirpStack {
finalCommunicator = chirpStackCommunicator
} else {
finalCommunicator = uartTransport
}
// 将 finalCommunicator 注入到需要发送指令的服务中...
4. 实施步骤
- 定义接口: 在
internal/app/listener/下创建handler.go并定义UpstreamHandler接口。 - 创建统一处理器: 创建
internal/app/listener/lora_listener.go,定义LoRaListener结构体,并实现UpstreamHandler接口的空方法。 - 迁移业务逻辑: 将
chirp_stack.go和lora_mesh_uart_passthrough_transport.go中的业务逻辑(查库、存数据等)逐步迁移到lora_listener.go的对应方法中。 - 重构适配器:
- 清理
chirp_stack.go,移除 Repo 依赖,改为依赖UpstreamHandler接口,并调用其方法。 - 清理
lora_mesh_uart_passthrough_transport.go,做同样的操作。
- 清理
- 更新依赖注入: 修改
component_initializers.go,按照3.5中的示例完成组件的创建和注入。 - 测试与验证: 对两种模式分别进行完整的上下行通信测试。
5. 收益
- 消除代码重复:业务逻辑仅存在于一处。
- 职责清晰:基础设施层只管传输,应用层只管业务。
- 可维护性:修改业务逻辑只需改一个文件,修改传输细节不影响业务。
- 可测试性:可以轻松地对
LoRaListener进行单元测试,无需真实的硬件或网络。