handleUpdateStatusReport
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/proto"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/proto"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/file"
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
)
|
)
|
||||||
@@ -25,6 +27,7 @@ type loraListener struct {
|
|||||||
deviceRepo repository.DeviceRepository
|
deviceRepo repository.DeviceRepository
|
||||||
sensorDataRepo repository.SensorDataRepository
|
sensorDataRepo repository.SensorDataRepository
|
||||||
deviceCommandLogRepo repository.DeviceCommandLogRepository
|
deviceCommandLogRepo repository.DeviceCommandLogRepository
|
||||||
|
otaRepo repository.OtaRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLoRaListener 创建一个新的 loraListener 实例。
|
// NewLoRaListener 创建一个新的 loraListener 实例。
|
||||||
@@ -36,6 +39,7 @@ func NewLoRaListener(
|
|||||||
deviceRepo repository.DeviceRepository,
|
deviceRepo repository.DeviceRepository,
|
||||||
sensorDataRepo repository.SensorDataRepository,
|
sensorDataRepo repository.SensorDataRepository,
|
||||||
deviceCommandLogRepo repository.DeviceCommandLogRepository,
|
deviceCommandLogRepo repository.DeviceCommandLogRepository,
|
||||||
|
otaRepo repository.OtaRepository,
|
||||||
) transport.UpstreamHandler {
|
) transport.UpstreamHandler {
|
||||||
return &loraListener{
|
return &loraListener{
|
||||||
selfCtx: logs.AddCompName(ctx, "LoRaListener"),
|
selfCtx: logs.AddCompName(ctx, "LoRaListener"),
|
||||||
@@ -44,6 +48,7 @@ func NewLoRaListener(
|
|||||||
deviceRepo: deviceRepo,
|
deviceRepo: deviceRepo,
|
||||||
sensorDataRepo: sensorDataRepo,
|
sensorDataRepo: sensorDataRepo,
|
||||||
deviceCommandLogRepo: deviceCommandLogRepo,
|
deviceCommandLogRepo: deviceCommandLogRepo,
|
||||||
|
otaRepo: otaRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +64,9 @@ func (l *loraListener) HandleInstruction(upstreamCtx context.Context, sourceAddr
|
|||||||
case *proto.Instruction_Pong:
|
case *proto.Instruction_Pong:
|
||||||
return l.handlePong(ctx, sourceAddr, p.Pong)
|
return l.handlePong(ctx, sourceAddr, p.Pong)
|
||||||
|
|
||||||
|
case *proto.Instruction_UpdateStatusReport:
|
||||||
|
return l.handleUpdateStatusReport(ctx, sourceAddr, p.UpdateStatusReport)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.Warnw("收到一个当前未处理的上行指令类型", "来源地址", sourceAddr, "类型", fmt.Sprintf("%T", p))
|
logger.Warnw("收到一个当前未处理的上行指令类型", "来源地址", sourceAddr, "类型", fmt.Sprintf("%T", p))
|
||||||
return nil
|
return nil
|
||||||
@@ -140,9 +148,9 @@ func (l *loraListener) handleCollectResult(ctx context.Context, sourceAddr strin
|
|||||||
return fmt.Errorf("传入的CollectResult为nil")
|
return fmt.Errorf("传入的CollectResult为nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
correlationID := collectResp.CorrelationId
|
correlationID := collectResp.GetCorrelationId()
|
||||||
logger := logs.GetLogger(ctx).With("correlationID", correlationID, "来源地址", sourceAddr)
|
logger := logs.GetLogger(ctx).With("correlationID", correlationID, "来源地址", sourceAddr)
|
||||||
logger.Infow("开始处理采集响应", "数据点数量", len(collectResp.Values))
|
logger.Infow("开始处理采集响应", "数据点数量", len(collectResp.GetValues()))
|
||||||
|
|
||||||
// 1. 查找区域主控
|
// 1. 查找区域主控
|
||||||
areaController, err := l.areaControllerRepo.FindByNetworkID(ctx, sourceAddr)
|
areaController, err := l.areaControllerRepo.FindByNetworkID(ctx, sourceAddr)
|
||||||
@@ -167,7 +175,7 @@ func (l *loraListener) handleCollectResult(ctx context.Context, sourceAddr strin
|
|||||||
|
|
||||||
// 4. 匹配数据并存入数据库
|
// 4. 匹配数据并存入数据库
|
||||||
deviceIDs := pendingReq.CommandMetadata
|
deviceIDs := pendingReq.CommandMetadata
|
||||||
values := collectResp.Values
|
values := collectResp.GetValues()
|
||||||
if len(deviceIDs) != len(values) {
|
if len(deviceIDs) != len(values) {
|
||||||
err := fmt.Errorf("数据不匹配:下行指令要求采集 %d 个设备,但上行响应包含 %d 个值", len(deviceIDs), len(values))
|
err := fmt.Errorf("数据不匹配:下行指令要求采集 %d 个设备,但上行响应包含 %d 个值", len(deviceIDs), len(values))
|
||||||
// 即使数量不匹配,也尝试更新状态为完成,以防止请求永远 pending
|
// 即使数量不匹配,也尝试更新状态为完成,以防止请求永远 pending
|
||||||
@@ -271,8 +279,9 @@ func (l *loraListener) recordSensorData(ctx context.Context, areaControllerID ui
|
|||||||
|
|
||||||
// handlePong 处理设备上报的Pong响应或主动心跳。
|
// handlePong 处理设备上报的Pong响应或主动心跳。
|
||||||
func (l *loraListener) handlePong(ctx context.Context, sourceAddr string, pong *proto.Pong) error {
|
func (l *loraListener) handlePong(ctx context.Context, sourceAddr string, pong *proto.Pong) error {
|
||||||
|
|
||||||
reqCtx, logger := logs.Trace(ctx, l.selfCtx, "handlePong")
|
reqCtx, logger := logs.Trace(ctx, l.selfCtx, "handlePong")
|
||||||
logger.Infow("开始处理Pong", "来源地址", sourceAddr, "携带版本", pong.FirmwareVersion)
|
logger.Infow("开始处理Pong", "来源地址", sourceAddr, "携带版本", pong.GetFirmwareVersion())
|
||||||
|
|
||||||
// 1. 查找区域主控
|
// 1. 查找区域主控
|
||||||
areaController, err := l.areaControllerRepo.FindByNetworkID(reqCtx, sourceAddr)
|
areaController, err := l.areaControllerRepo.FindByNetworkID(reqCtx, sourceAddr)
|
||||||
@@ -281,13 +290,13 @@ func (l *loraListener) handlePong(ctx context.Context, sourceAddr string, pong *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 如果 Pong 中包含版本号,则更新
|
// 2. 如果 Pong 中包含版本号,则更新
|
||||||
if pong.FirmwareVersion != "" {
|
if firmwareVersion := pong.GetFirmwareVersion(); firmwareVersion != "" {
|
||||||
err := l.areaControllerRepo.UpdateFirmwareVersion(reqCtx, areaController.ID, pong.FirmwareVersion)
|
err := l.areaControllerRepo.UpdateFirmwareVersion(reqCtx, areaController.ID, firmwareVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 只记录错误,不中断流程,因为还要记录在线状态
|
// 只记录错误,不中断流程,因为还要记录在线状态
|
||||||
logger.Errorw("处理Pong时更新固件版本失败", "主控ID", areaController.ID, "error", err)
|
logger.Errorw("处理Pong时更新固件版本失败", "主控ID", areaController.ID, "error", err)
|
||||||
} else {
|
} else {
|
||||||
logger.Infow("处理Pong时成功更新固件版本", "主控ID", areaController.ID, "新版本", pong.FirmwareVersion)
|
logger.Infow("处理Pong时成功更新固件版本", "主控ID", areaController.ID, "新版本", firmwareVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,3 +307,88 @@ func (l *loraListener) handlePong(ctx context.Context, sourceAddr string, pong *
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleUpdateStatusReport 处理设备上报的 OTA 升级状态报告。
|
||||||
|
func (l *loraListener) handleUpdateStatusReport(ctx context.Context, sourceAddr string, report *proto.UpdateStatusReport) error {
|
||||||
|
if report == nil {
|
||||||
|
return fmt.Errorf("传入的UpdateStatusReport为nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := logs.GetLogger(ctx).With("任务ID", report.GetTaskId(), "来源地址", sourceAddr)
|
||||||
|
logger.Infow("开始处理 OTA 状态报告", "当前版本", report.GetCurrentVersion(), "状态", report.GetStatus().String())
|
||||||
|
|
||||||
|
// 1. 根据 task_id 查找对应的 OTA 任务
|
||||||
|
task, err := l.otaRepo.FindByID(ctx, report.GetTaskId())
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorw("处理 OTA 状态报告失败:未找到对应的 OTA 任务", "error", err)
|
||||||
|
return fmt.Errorf("处理 OTA 状态报告失败:未找到任务 %d: %w", report.GetTaskId(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查任务是否已处于终态,防止重复处理
|
||||||
|
if task.IsOver() {
|
||||||
|
logger.Warnw("OTA 任务已处于终态,忽略本次状态报告", "当前状态", task.Status)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 根据报告状态更新 OTA 任务
|
||||||
|
now := time.Now()
|
||||||
|
task.CompletedAt = &now
|
||||||
|
task.FinalReportedVersion = report.GetCurrentVersion()
|
||||||
|
task.FailedFilePath = report.GetFailedFile()
|
||||||
|
|
||||||
|
// 初始化错误信息,优先使用设备上报的原始信息
|
||||||
|
errorMessage := report.GetErrorMessage()
|
||||||
|
|
||||||
|
switch report.GetStatus() {
|
||||||
|
case proto.UpdateStatusReport_SUCCESS:
|
||||||
|
task.Status = models.OTATaskStatusSuccess
|
||||||
|
case proto.UpdateStatusReport_SUCCESS_ALREADY_UP_TO_DATE:
|
||||||
|
task.Status = models.OTATaskStatusAlreadyUpToDate
|
||||||
|
case proto.UpdateStatusReport_FAILED_PRE_CHECK:
|
||||||
|
task.Status = models.OTATaskStatusFailedPreCheck
|
||||||
|
case proto.UpdateStatusReport_FAILED_MANIFEST_VERIFY, proto.UpdateStatusReport_FAILED_DOWNLOAD:
|
||||||
|
task.Status = models.OTATaskStatusFailedDownload
|
||||||
|
case proto.UpdateStatusReport_FAILED_ROLLED_BACK:
|
||||||
|
task.Status = models.OTATaskStatusFailedRollback
|
||||||
|
case proto.UpdateStatusReport_FAILED_TIMEOUT:
|
||||||
|
task.Status = models.OTATaskStatusPlatformError
|
||||||
|
if errorMessage != "" {
|
||||||
|
errorMessage = fmt.Sprintf("平台诊断信息: 设备上报了平台推断的超时状态,可能存在逻辑错误。(设备原始报错: %s)", errorMessage)
|
||||||
|
} else {
|
||||||
|
errorMessage = "平台诊断信息: 设备上报了平台推断的超时状态,可能存在逻辑错误。"
|
||||||
|
}
|
||||||
|
case proto.UpdateStatusReport_STATUS_UNSPECIFIED:
|
||||||
|
task.Status = models.OTATaskStatusPlatformError
|
||||||
|
if errorMessage != "" {
|
||||||
|
errorMessage = fmt.Sprintf("平台诊断信息: 设备上报了未指定状态。(设备原始报错: %s)", errorMessage)
|
||||||
|
} else {
|
||||||
|
errorMessage = "平台诊断信息: 设备上报了未指定状态。"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
task.Status = models.OTATaskStatusPlatformError
|
||||||
|
if errorMessage != "" {
|
||||||
|
errorMessage = fmt.Sprintf("平台诊断信息: 设备上报了未知状态 '%s'。(设备原始报错: %s)", report.GetStatus().String(), errorMessage)
|
||||||
|
} else {
|
||||||
|
errorMessage = fmt.Sprintf("平台诊断信息: 设备上报了未知状态 '%s'。", report.GetStatus().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.ErrorMessage = errorMessage
|
||||||
|
|
||||||
|
if err := l.otaRepo.Update(ctx, task); err != nil {
|
||||||
|
logger.Errorw("更新 OTA 任务状态失败", "error", err)
|
||||||
|
return fmt.Errorf("更新 OTA 任务 %d 状态失败: %w", report.GetTaskId(), err)
|
||||||
|
}
|
||||||
|
logger.Infow("成功更新 OTA 任务状态", "新状态", task.Status)
|
||||||
|
|
||||||
|
// 4. 如果任务已完成(成功或失败),则清理临时文件目录
|
||||||
|
if task.IsOver() {
|
||||||
|
dirToRemove := filepath.Join(models.OTADir, fmt.Sprintf("%d", report.GetTaskId()))
|
||||||
|
if err := file.RemoveDir(dirToRemove); err != nil {
|
||||||
|
logger.Warnw("清理 OTA 任务临时文件目录失败", "目录", dirToRemove, "error", err)
|
||||||
|
} else {
|
||||||
|
logger.Infow("成功清理 OTA 任务临时文件目录", "目录", dirToRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user