From 9ae5bc8f83cb9d0f114cdf421bc42ae19ea6b4d2 Mon Sep 17 00:00:00 2001 From: stu2not Date: Tue, 16 Jun 2026 11:36:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86OTA=E4=B8=8B=E5=8F=91?= =?UTF-8?q?=E6=97=A0=E5=9B=9E=E6=89=A7=E8=B6=85=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- talkingq-url/banban/service/device.py | 38 ++++++++++++++++++++++++++- talkingq-url/config.py | 2 ++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/talkingq-url/banban/service/device.py b/talkingq-url/banban/service/device.py index fabefed..a77434c 100644 --- a/talkingq-url/banban/service/device.py +++ b/talkingq-url/banban/service/device.py @@ -1,5 +1,5 @@ from collections.abc import Mapping -from datetime import datetime, time, timedelta +from datetime import datetime, timezone, time, timedelta from typing import Any, List, Literal from services.database_service_base import DatabaseServiceBase @@ -8,6 +8,7 @@ from fastapi import HTTPException from banban.dao.device import DeviceDAO from banban.service.device_setting import device_setting_service from banban.service.ota_firmware import ota_firmware_service +from config import settings from services.device_update_manager import device_firmware_update_manager @@ -301,6 +302,29 @@ class DeviceService(DatabaseServiceBase): latest_parts.extend([0] * (max_len - len(latest_parts))) return latest_parts > current_parts + def _firmware_sent_timed_out(self, updated_at: Any) -> bool: + timeout_seconds = max(0, int(settings.ota_sent_timeout_seconds or 0)) + if timeout_seconds <= 0 or not updated_at: + return False + + if isinstance(updated_at, str): + text = updated_at.strip() + if not text: + return False + try: + updated_at = datetime.fromisoformat(text.replace("Z", "+00:00")) + except ValueError: + try: + updated_at = datetime.strptime(text, "%Y-%m-%d %H:%M:%S") + except ValueError: + return False + + if not isinstance(updated_at, datetime): + return False + + now = datetime.now(timezone.utc) if updated_at.tzinfo else datetime.now() + return (now - updated_at).total_seconds() >= timeout_seconds + async def get_firmware_status( self, *, @@ -320,6 +344,18 @@ class DeviceService(DatabaseServiceBase): progress = update_record.get("progress") if update_record else 0.0 target_version = update_record.get("firmware_version") if update_record else None updated_at = update_record.get("updated_at") if update_record else None + if str(update_status or "").strip().lower() == "sent" and self._firmware_sent_timed_out(updated_at): + update_status = "failed" + progress = 0.0 + await device_firmware_update_manager.update_firmware_update( + device_id=device_id, + firmware_version=target_version or latest_version or current_version or "unknown", + update_status="failed", + progress=0.0, + ) + update_record = await device_firmware_update_manager.get_firmware_update_dict(device_id) + if update_record: + updated_at = update_record.get("updated_at") or updated_at return { "device_id": device_id, diff --git a/talkingq-url/config.py b/talkingq-url/config.py index 946f094..b9f00ba 100644 --- a/talkingq-url/config.py +++ b/talkingq-url/config.py @@ -78,6 +78,8 @@ class Settings(BaseSettings): talkingq_mqtt_keepalive: int = Field(default=60, validation_alias="TALKINGQ_MQTT_KEEPALIVE") talkingq_mqtt_nfc_notice_interval: int = Field(default=600, validation_alias="TALKINGQ_MQTT_NFC_NOTICE_INTERVAL") + ota_sent_timeout_seconds: int = Field(default=300, validation_alias="OTA_SENT_TIMEOUT_SECONDS") + admin_api_key: str = Field(default="", validation_alias="ADMIN_API_KEY") client_api_key: str = Field(default="", validation_alias="CLIENT_API_KEY")