package lora import ( "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/config" "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" ) // ChirpStackTransport 是一个客户端,用于封装与 ChirpStack REST API 的交互。 type ChirpStackTransport struct { client *client.ChirpStackRESTAPI authInfo runtime.ClientAuthInfoWriter config config.ChirpStackConfig deviceCommandLogRepo repository.DeviceCommandLogRepository deviceRepo repository.DeviceRepository logger *logs.Logger } // NewChirpStackTransport 创建一个新的通信实例,用于与 ChirpStack 通信。 func NewChirpStackTransport( config config.ChirpStackConfig, logger *logs.Logger, deviceCommandLogRepo repository.DeviceCommandLogRepository, deviceRepo repository.DeviceRepository, ) *ChirpStackTransport { // 使用配置中的服务器地址创建一个 HTTP transport。 // 它会使用生成的客户端中定义的默认 base path 和 schemes。 transport := httptransport.New(config.APIHost, 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: int64(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 }