from pydantic import BaseModel from typing import Optional, List from enum import Enum class UpdateType(str, Enum): optional = "optional" # user-initiated only mandatory = "mandatory" # auto-installs on next reboot emergency = "emergency" # auto-installs on reboot + daily check + MQTT push class FirmwareVersion(BaseModel): id: str hw_type: str # e.g. "vesper", "vesper_plus", "vesper_pro" channel: str # "stable", "beta", "alpha", "testing" version: str # semver e.g. "1.5" filename: str size_bytes: int sha256: str update_type: UpdateType = UpdateType.mandatory min_fw_version: Optional[str] = None # minimum fw version required to install this uploaded_at: str notes: Optional[str] = None is_latest: bool = False class FirmwareListResponse(BaseModel): firmware: List[FirmwareVersion] total: int class FirmwareMetadataResponse(BaseModel): """Returned by both /latest and /{version}/info endpoints. Two orthogonal axes: channel — the release track the device is subscribed to ("stable" | "beta" | "development") Firmware validates this matches the channel it requested. update_type — the urgency of THIS release, set by the publisher ("optional" | "mandatory" | "emergency") Firmware reads mandatory/emergency booleans derived from this. Additional firmware-compatible fields: size — binary size in bytes (firmware reads "size", not "size_bytes") mandatory — True when update_type is mandatory or emergency emergency — True only when update_type is emergency """ hw_type: str channel: str # release track — firmware validates this version: str size: int # firmware reads "size" size_bytes: int # kept for admin-panel consumers sha256: str update_type: UpdateType # urgency enum — for admin panel display mandatory: bool # derived: update_type in (mandatory, emergency) emergency: bool # derived: update_type == emergency min_fw_version: Optional[str] = None download_url: str uploaded_at: str notes: Optional[str] = None # Keep backwards-compatible alias FirmwareLatestResponse = FirmwareMetadataResponse