Files
pig-farm-controller/internal/infra/transport/lora/chirp_stack.go

148 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package lora
import (
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora/chirp_stack_proto/client/device_service"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora/chirp_stack_proto/client"
)
// ChirpStackConfig 保存连接到 ChirpStack API 所需的配置。
type ChirpStackConfig struct {
// ServerAddress 是 ChirpStack API 服务器的地址,例如 "localhost:8080"。
ServerAddress string
// APIKey 是用于认证的 API 密钥。
APIKey string
// LoRaWAN 端口, 需要和设备一致
Fport int64
}
// GenerateAPIKey 用于补齐API Key作为请求头时缺失的部分
func (c ChirpStackConfig) GenerateAPIKey() string {
return "Bearer " + c.APIKey
}
// ChirpStackTransport 是一个客户端,用于封装与 ChirpStack REST API 的交互。
type ChirpStackTransport struct {
client *client.ChirpStackRESTAPI
authInfo runtime.ClientAuthInfoWriter
config ChirpStackConfig
deviceCommandLogRepo repository.DeviceCommandLogRepository
deviceRepo repository.DeviceRepository
logger *logs.Logger
}
// NewChirpStackTransport 创建一个新的通信实例,用于与 ChirpStack 通信。
func NewChirpStackTransport(
config ChirpStackConfig,
logger *logs.Logger,
deviceCommandLogRepo repository.DeviceCommandLogRepository,
deviceRepo repository.DeviceRepository,
) *ChirpStackTransport {
// 使用配置中的服务器地址创建一个 HTTP transport。
// 它会使用生成的客户端中定义的默认 base path 和 schemes。
transport := httptransport.New(config.ServerAddress, client.DefaultBasePath, client.DefaultSchemes)
// 使用 transport 和默认的字符串格式化器,创建一个 API 主客户端。
apiClient := client.New(transport, strfmt.Default)
// 使用 API Key 创建认证信息写入器。
authInfo := httptransport.APIKeyAuth("grpc-metadata-authorization", "header", config.GenerateAPIKey())
return &ChirpStackTransport{
client: apiClient,
authInfo: authInfo,
config: config,
logger: logger,
deviceCommandLogRepo: deviceCommandLogRepo,
deviceRepo: deviceRepo,
}
}
func (c *ChirpStackTransport) Send(address string, payload []byte) error {
// 1. 构建 API 请求体。
// - Confirmed: true 表示确认消息, 设为false将不保证消息送达(但可以节约下行容量)。
// - Data: 经过 Base64 编码的数据。
// - FPort: LoRaWAN 端口。
body := device_service.DeviceServiceEnqueueBody{
QueueItem: &device_service.DeviceServiceEnqueueParamsBodyQueueItem{
Confirmed: true,
Data: payload,
FPort: c.config.Fport,
},
}
// 2. 构建 API 请求参数。
// - WithTimeout 设置一个合理的请求超时。
// - WithQueueItemDevEui 指定目标设备的 EUI。
// - WithBody 设置请求体。
params := device_service.NewDeviceServiceEnqueueParams().
WithTimeout(10 * time.Second).
WithQueueItemDevEui(address).
WithBody(body)
// 3. 调用生成的客户端方法来发送请求。
// c.authInfo 是您在 NewChirpStackTransport 中创建的认证信息。
resp, err := c.client.DeviceService.DeviceServiceEnqueue(params, c.authInfo)
if err != nil {
c.logger.Errorf("设备 %s 调用ChirpStack Enqueue失败: %v", address, err)
return err
}
// 4. 成功发送后,尝试记录下行任务
messageID := ""
if resp != nil && resp.Payload != nil && resp.Payload.ID != "" { // 根据实际结构,使用 resp.Payload.ID
messageID = resp.Payload.ID
} else {
c.logger.Warnf("ChirpStack Enqueue 响应未包含 MessageID (ID),无法记录下行任务。设备: %s", address)
// 即使无法获取 MessageID也认为发送成功因为 ChirpStack Enqueue 成功了
return nil
}
// 调用私有方法记录下行任务
if err := c.recordDownlinkTask(address, messageID); err != nil {
// 记录失败不影响下行命令的发送成功
c.logger.Errorf("记录下行任务失败 (MessageID: %s, DevEui: %s): %v", messageID, address, err)
return nil
}
c.logger.Infof("设备 %s 调用ChirpStack Enqueue成功并创建下行任务记录 (MessageID: %s)", address, messageID)
return nil
}
// recordDownlinkTask 记录下行任务到数据库
func (c *ChirpStackTransport) recordDownlinkTask(devEui string, messageID string) error {
// 获取区域主控的内部 DeviceID
regionalController, err := c.deviceRepo.FindByDevEui(devEui)
if err != nil {
c.logger.Errorf("记录下行任务失败:无法通过 DevEui '%s' 找到区域主控设备: %v", devEui, err)
return err
}
// 创建 DeviceCommandLog
record := &models.DeviceCommandLog{
MessageID: messageID,
DeviceID: regionalController.ID,
SentAt: time.Now(),
AcknowledgedAt: nil, // 初始状态为未确认
}
if err := c.deviceCommandLogRepo.Create(record); err != nil {
c.logger.Errorf("创建下行任务记录失败 (MessageID: %s, DeviceID: %d): %v", messageID, regionalController.ID, err)
return err
}
c.logger.Infof("成功创建下行任务记录 (MessageID: %s, DeviceID: %d)", messageID, regionalController.ID)
return nil
}