调整 ChirpStackListener

This commit is contained in:
2025-09-29 19:17:42 +08:00
parent ee8039b301
commit 4f730cf58f
4 changed files with 106 additions and 56 deletions

View File

@@ -1,7 +1,7 @@
package transport
import (
"encoding/base64" // 新增导入
"encoding/base64"
"encoding/json"
"io"
"net/http"
@@ -32,8 +32,9 @@ type ChirpStackListener struct {
logger *logs.Logger
sensorDataRepo repository.SensorDataRepository
deviceRepo repository.DeviceRepository
areaControllerRepo repository.AreaControllerRepository // 新增
deviceCommandLogRepo repository.DeviceCommandLogRepository
pendingCollectionRepo repository.PendingCollectionRepository // 新增
pendingCollectionRepo repository.PendingCollectionRepository
}
// NewChirpStackListener 创建一个新的 ChirpStackListener 实例
@@ -41,15 +42,17 @@ func NewChirpStackListener(
logger *logs.Logger,
sensorDataRepo repository.SensorDataRepository,
deviceRepo repository.DeviceRepository,
areaControllerRepo repository.AreaControllerRepository, // 新增
deviceCommandLogRepo repository.DeviceCommandLogRepository,
pendingCollectionRepo repository.PendingCollectionRepository, // 新增
pendingCollectionRepo repository.PendingCollectionRepository,
) ListenHandler { // 返回接口类型
return &ChirpStackListener{
logger: logger,
sensorDataRepo: sensorDataRepo,
deviceRepo: deviceRepo,
areaControllerRepo: areaControllerRepo, // 新增
deviceCommandLogRepo: deviceCommandLogRepo,
pendingCollectionRepo: pendingCollectionRepo, // 新增
pendingCollectionRepo: pendingCollectionRepo,
}
}
@@ -149,10 +152,11 @@ func (c *ChirpStackListener) handler(data []byte, eventType string) {
// --- 业务处理函数 ---
// GenericSensorReading 表示单个传感器读数包含设备ID、类型和值。
// 此结构体已不再使用,但保留以供参考或兼容旧代码。
type GenericSensorReading struct {
DeviceID uint `json:"device_id"` // 传感器设备的ID
Type models.SensorDataType `json:"type"` // 传感器类型 (复用 models.SensorDataType)
Value float64 `json:"value"` // 传感器读数
DeviceID uint `json:"device_id"` // 传感器设备的ID
Type string `json:"type"` // 传感器类型 (现在直接使用 ValueDescriptor.Name)
Value float64 `json:"value"` // 传感器读数
}
// handleUpEvent 处理上行数据事件
@@ -160,11 +164,16 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) {
c.logger.Infof("开始处理 'up' 事件, DevEui: %s", event.DeviceInfo.DevEui)
// 1. 查找区域主控设备
regionalController, err := c.deviceRepo.FindByDevEui(event.DeviceInfo.DevEui)
regionalController, err := c.areaControllerRepo.FindByNetworkID(event.DeviceInfo.DevEui)
if err != nil {
c.logger.Errorf("处理 'up' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err)
return
}
// 依赖 SelfCheck 确保区域主控有效
if err := regionalController.SelfCheck(); err != nil {
c.logger.Errorf("处理 'up' 事件失败:区域主控 %v(ID: %d) 未通过自检: %v", regionalController.Name, regionalController.ID, err)
return
}
c.logger.Infof("找到区域主控: %s (ID: %d)", regionalController.Name, regionalController.ID)
// 2. 记录区域主控的信号强度 (如果存在)
@@ -178,7 +187,9 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) {
RssiDbm: rx.Rssi,
SnrDb: rx.Snr,
}
c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, models.SensorDataTypeSignalMetrics, signalMetrics)
// 记录信号强度,使用 ValueDescriptor 的 Name 作为 sensorName
// 简化处理,只记录 RSSI如果需要记录 SNR可以再添加一个 ValueDescriptor
c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, "signal_strength", float64(signalMetrics.RssiDbm))
c.logger.Infof("已记录区域主控 (ID: %d) 的信号强度: RSSI=%d, SNR=%.2f", regionalController.ID, rx.Rssi, rx.Snr)
} else {
c.logger.Warnf("处理 'up' 事件时未找到 RxInfo无法记录信号数据。DevEui: %s", event.DeviceInfo.DevEui)
@@ -210,7 +221,7 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) {
return
}
// 2.4 解包内层 CollectResult
// 3.4 解包内层 CollectResult
var collectResp proto.CollectResult
if err := instruction.Data.UnmarshalTo(&collectResp); err != nil {
c.logger.Errorf("解包数据信息失败: %v", err)
@@ -220,7 +231,7 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) {
correlationID := collectResp.CorrelationId
c.logger.Infof("成功解析采集响应 (CorrelationID: %s),包含 %d 个值。", correlationID, len(collectResp.Values))
// 3. 根据 CorrelationID 查找待处理请求
// 4. 根据 CorrelationID 查找待处理请求
pendingReq, err := c.pendingCollectionRepo.FindByCorrelationID(correlationID)
if err != nil {
c.logger.Errorf("处理采集响应失败:无法找到待处理请求 (CorrelationID: %s): %v", correlationID, err)
@@ -233,12 +244,11 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) {
return
}
// 4. 匹配数据并存入数据库
// 5. 匹配数据并存入数据库
deviceIDs := pendingReq.CommandMetadata
values := collectResp.Values
if len(deviceIDs) != len(values) {
c.logger.Errorf("数据不匹配:下行指令要求采集 %d 个设备,但上行响应包含 %d 个值 (CorrelationID: %s)", len(deviceIDs), len(values), correlationID)
// TODO 数量不匹配是否全改成失败
// 即使数量不匹配,也更新状态为完成,以防止请求永远 pending
err = c.pendingCollectionRepo.UpdateStatusToFulfilled(correlationID, event.Time)
if err != nil {
@@ -248,37 +258,46 @@ func (c *ChirpStackListener) handleUpEvent(event *UpEvent) {
}
for i, deviceID := range deviceIDs {
value := values[i]
rawSensorValue := values[i] // 这是设备上报的原始值
// 5.1 获取设备及其模板
dev, err := c.deviceRepo.FindByID(deviceID)
if err != nil {
c.logger.Errorf("处理采集数据失败:无法找到设备 (ID: %d): %v", deviceID, err)
continue
}
sensorDataType, ok := models.DeviceSubTypeToSensorDataTypeMap[dev.SubType]
if !ok {
c.logger.Warnf("设备 %d 的子类型 '%s' 没有对应的传感器数据类型,跳过记录。", dev.ID, dev.SubType)
// 依赖 SelfCheck 确保设备和模板有效
if err := dev.SelfCheck(); err != nil {
c.logger.Warnf("跳过设备 %d因其未通过自检: %v", dev.ID, err)
continue
}
if err := dev.DeviceTemplate.SelfCheck(); err != nil {
c.logger.Warnf("跳过设备 %d因其设备模板未通过自检: %v", dev.ID, err)
continue
}
var sensorData interface{}
switch sensorDataType {
case models.SensorDataTypeTemperature:
sensorData = models.TemperatureData{TemperatureCelsius: float64(value)}
case models.SensorDataTypeHumidity:
sensorData = models.HumidityData{HumidityPercent: float64(value)}
case models.SensorDataTypeWeight:
sensorData = models.WeightData{WeightKilograms: float64(value)}
default:
c.logger.Warnf("未处理的传感器数据类型 '%s' (设备ID: %d)", sensorDataType, dev.ID)
// 5.2 从设备模板中解析 ValueDescriptor
var valueDescriptors []*models.ValueDescriptor
if err := dev.DeviceTemplate.ParseValues(&valueDescriptors); err != nil {
c.logger.Warnf("跳过设备 %d因其设备模板的 Values 属性解析失败: %v", dev.ID, err)
continue
}
// 根据 DeviceTemplate.SelfCheck这里应该只有一个 ValueDescriptor
if len(valueDescriptors) == 0 {
c.logger.Warnf("跳过设备 %d因其设备模板缺少 ValueDescriptor 定义", dev.ID)
continue
}
valueDescriptor := valueDescriptors[0]
c.recordSensorData(pendingReq.DeviceID, dev.ID, event.Time, sensorDataType, sensorData)
c.logger.Infof("成功记录传感器数据: 设备ID=%d, 类型=%s, 值=%.2f", dev.ID, sensorDataType, value)
// 5.3 应用乘数和偏移量计算最终值
parsedValue := float64(rawSensorValue)*valueDescriptor.Multiplier + valueDescriptor.Offset
// 5.4 记录传感器数据
c.recordSensorData(regionalController.ID, dev.ID, event.Time, valueDescriptor.Name, parsedValue)
c.logger.Infof("成功记录传感器数据: 设备ID=%d, 类型=%s, 原始值=%d, 解析值=%.2f", dev.ID, valueDescriptor.Name, rawSensorValue, parsedValue)
}
// 5. 更新请求状态为“已完成”
// 6. 更新请求状态为“已完成”
if err := c.pendingCollectionRepo.UpdateStatusToFulfilled(correlationID, event.Time); err != nil {
c.logger.Errorf("更新待采集请求状态为 'fulfilled' 失败 (CorrelationID: %s): %v", correlationID, err)
} else {
@@ -295,13 +314,13 @@ func (c *ChirpStackListener) handleStatusEvent(event *StatusEvent) {
MarginDb: event.Margin,
}
// 这里的 event.DeviceInfo.DevEui 对应的是区域主控的 DevEui
regionalController, err := c.deviceRepo.FindByDevEui(event.DeviceInfo.DevEui)
regionalController, err := c.areaControllerRepo.FindByNetworkID(event.DeviceInfo.DevEui)
if err != nil {
c.logger.Errorf("处理 'status' 事件失败:无法通过 DevEui '%s' 找到区域主控设备: %v", event.DeviceInfo.DevEui, err)
return
}
// 记录区域主控的信号强度
c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, models.SensorDataTypeSignalMetrics, signalMetrics)
c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, "signal_metrics", float64(signalMetrics.RssiDbm))
// 记录 电量
batteryLevel := models.BatteryLevel{
@@ -310,7 +329,7 @@ func (c *ChirpStackListener) handleStatusEvent(event *StatusEvent) {
ExternalPower: event.ExternalPower,
}
// 记录区域主控的电池电量
c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, models.SensorDataTypeBatteryLevel, batteryLevel)
c.recordSensorData(regionalController.ID, regionalController.ID, event.Time, "battery_level", float64(batteryLevel.BatteryLevelRatio))
}
@@ -378,24 +397,29 @@ func (c *ChirpStackListener) handleIntegrationEvent(event *IntegrationEvent) {
// recordSensorData 是一个通用方法,用于将传感器数据存入数据库。
// regionalControllerID: 区域主控设备的ID
// sensorDeviceID: 实际产生传感器数据的普通设备的ID
func (c *ChirpStackListener) recordSensorData(regionalControllerID uint, sensorDeviceID uint, eventTime time.Time, dataType models.SensorDataType, data interface{}) {
// 2. 序列化数据结构体为 JSON
jsonData, err := json.Marshal(data)
// sensorName: 传感器值的名称 (例如 "temperature", "weight")
// parsedValue: 已经解析并应用了乘数和偏移量的浮点数值
func (c *ChirpStackListener) recordSensorData(regionalControllerID uint, sensorDeviceID uint, eventTime time.Time, sensorName string, parsedValue float64) {
// 1. 将解析后的值封装成一个简单的 JSON 对象
dataToMarshal := map[string]float64{
"value": parsedValue,
}
jsonData, err := json.Marshal(dataToMarshal)
if err != nil {
c.logger.Errorf("记录传感器数据失败:序列化数据为 JSON 时出错: %v", err)
return
}
// 3. 构建 SensorData 模型
// 2. 构建 SensorData 模型
sensorData := &models.SensorData{
Time: eventTime,
DeviceID: sensorDeviceID,
RegionalControllerID: regionalControllerID,
SensorDataType: dataType,
SensorDataType: sensorName, // 直接使用 sensorName 作为数据类型
Data: datatypes.JSON(jsonData),
}
// 4. 调用仓库创建记录
// 3. 调用仓库创建记录
if err := c.sensorDataRepo.Create(sensorData); err != nil {
c.logger.Errorf("记录传感器数据失败:存入数据库时出错: %v", err)
}

View File

@@ -73,7 +73,7 @@ type DeviceTemplate struct {
// Description 提供了关于此设备类型的更多详细信息。
Description string `json:"description"`
// Category 将模板分类为传感器、执行器或复合设备。
// Category 将模板分类为传感器、执行器
Category DeviceCategory `gorm:"not null;index" json:"category"`
// Commands 存储了一个从“动作名称”到“原始指令”的映射。

View File

@@ -7,23 +7,9 @@ import (
)
// SensorDataType 定义了 SensorData 记录中 Data 字段的整体类型
type SensorDataType string
const (
SensorDataTypeSignalMetrics SensorDataType = "signal_metrics" // 信号强度
SensorDataTypeBatteryLevel SensorDataType = "battery_level" // 电池电量
SensorDataTypeTemperature SensorDataType = "temperature" // 温度
SensorDataTypeHumidity SensorDataType = "humidity" // 湿度
SensorDataTypeWeight SensorDataType = "weight" // 重量
)
// DeviceSubTypeToSensorDataTypeMap 定义了设备子类型到其产生的传感器数据类型的静态映射.
// 这个公开的 map 是连接设备定义和数据记录的桥梁, 供其他包直接查询.
var DeviceSubTypeToSensorDataTypeMap = map[DeviceSubType]SensorDataType{
SubTypeSensorTemp: SensorDataTypeTemperature,
SubTypeSensorHumidity: SensorDataTypeHumidity,
SubTypeSensorWeight: SensorDataTypeWeight,
}
// SignalMetrics 存储信号强度数据
type SignalMetrics struct {
@@ -67,7 +53,7 @@ type SensorData struct {
RegionalControllerID uint `json:"regional_controller_id"`
// SensorDataType 是传感数据的类型
SensorDataType SensorDataType `gorm:"not null;index" json:"sensor_data_type"`
SensorDataType string `gorm:"not null;index" json:"sensor_data_type"`
// Data 存储一个或多个传感器读数,格式为 JSON。
Data datatypes.JSON `gorm:"type:jsonb" json:"data"`

View File

@@ -0,0 +1,40 @@
package repository
import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口
type AreaControllerRepository interface {
FindByID(id uint) (*models.AreaController, error)
FindByNetworkID(networkID string) (*models.AreaController, error) // New method
}
// gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。
type gormAreaControllerRepository struct {
db *gorm.DB
}
// NewGormAreaControllerRepository 创建一个新的 AreaControllerRepository GORM 实现实例。
func NewGormAreaControllerRepository(db *gorm.DB) AreaControllerRepository {
return &gormAreaControllerRepository{db: db}
}
// FindByID 通过 ID 查找一个 AreaController。
func (r *gormAreaControllerRepository) FindByID(id uint) (*models.AreaController, error) {
var areaController models.AreaController
if err := r.db.First(&areaController, id).Error; err != nil {
return nil, err
}
return &areaController, nil
}
// FindByNetworkID 通过 NetworkID 查找一个 AreaController。
func (r *gormAreaControllerRepository) FindByNetworkID(networkID string) (*models.AreaController, error) {
var areaController models.AreaController
if err := r.db.Where("network_id = ?", networkID).First(&areaController).Error; err != nil {
return nil, err
}
return &areaController, nil
}