update: firmware and provisioning now supports bootloader and partition tables
This commit is contained in:
@@ -1,13 +1,18 @@
|
||||
from fastapi import APIRouter, Depends, Query, UploadFile, File, Form
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
import logging
|
||||
|
||||
from auth.models import TokenPayload
|
||||
from auth.dependencies import require_permission
|
||||
from firmware.models import FirmwareVersion, FirmwareListResponse, FirmwareMetadataResponse, UpdateType
|
||||
from firmware import service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/firmware", tags=["firmware"])
|
||||
ota_router = APIRouter(prefix="/api/ota", tags=["ota-telemetry"])
|
||||
|
||||
|
||||
@router.post("/upload", response_model=FirmwareVersion, status_code=201)
|
||||
@@ -44,11 +49,16 @@ def list_firmware(
|
||||
|
||||
|
||||
@router.get("/{hw_type}/{channel}/latest", response_model=FirmwareMetadataResponse)
|
||||
def get_latest_firmware(hw_type: str, channel: str):
|
||||
def get_latest_firmware(
|
||||
hw_type: str,
|
||||
channel: str,
|
||||
hw_version: Optional[str] = Query(None, description="Hardware revision from NVS, e.g. '1.0'"),
|
||||
current_version: Optional[str] = Query(None, description="Currently running firmware semver, e.g. '1.2.3'"),
|
||||
):
|
||||
"""Returns metadata for the latest firmware for a given hw_type + channel.
|
||||
No auth required — devices call this endpoint to check for updates.
|
||||
"""
|
||||
return service.get_latest(hw_type, channel)
|
||||
return service.get_latest(hw_type, channel, hw_version=hw_version, current_version=current_version)
|
||||
|
||||
|
||||
@router.get("/{hw_type}/{channel}/{version}/info", response_model=FirmwareMetadataResponse)
|
||||
@@ -76,3 +86,52 @@ def delete_firmware(
|
||||
_user: TokenPayload = Depends(require_permission("manufacturing", "delete")),
|
||||
):
|
||||
service.delete_firmware(firmware_id)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# OTA event telemetry — called by devices (no auth, best-effort)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
class OtaDownloadEvent(BaseModel):
|
||||
device_uid: str
|
||||
hw_type: str
|
||||
hw_version: str
|
||||
from_version: str
|
||||
to_version: str
|
||||
channel: str
|
||||
|
||||
|
||||
class OtaFlashEvent(BaseModel):
|
||||
device_uid: str
|
||||
hw_type: str
|
||||
hw_version: str
|
||||
from_version: str
|
||||
to_version: str
|
||||
channel: str
|
||||
sha256: str
|
||||
|
||||
|
||||
@ota_router.post("/events/download", status_code=204)
|
||||
def ota_event_download(event: OtaDownloadEvent):
|
||||
"""Device reports that firmware was fully written to flash (pre-commit).
|
||||
No auth required — best-effort telemetry from the device.
|
||||
"""
|
||||
logger.info(
|
||||
"OTA download event: device=%s hw=%s/%s %s → %s (channel=%s)",
|
||||
event.device_uid, event.hw_type, event.hw_version,
|
||||
event.from_version, event.to_version, event.channel,
|
||||
)
|
||||
service.record_ota_event("download", event.model_dump())
|
||||
|
||||
|
||||
@ota_router.post("/events/flash", status_code=204)
|
||||
def ota_event_flash(event: OtaFlashEvent):
|
||||
"""Device reports that firmware partition was committed and device is rebooting.
|
||||
No auth required — best-effort telemetry from the device.
|
||||
"""
|
||||
logger.info(
|
||||
"OTA flash event: device=%s hw=%s/%s %s → %s (channel=%s sha256=%.16s...)",
|
||||
event.device_uid, event.hw_type, event.hw_version,
|
||||
event.from_version, event.to_version, event.channel, event.sha256,
|
||||
)
|
||||
service.record_ota_event("flash", event.model_dump())
|
||||
|
||||
Reference in New Issue
Block a user