Build and push server image / build-and-push (push) Successful in 39s
New Gitea Actions workflow builds both board variants and publishes them as release assets whenever firmware/version.txt is bumped. The server can now poll that repo's releases (next to the existing manual upload) and either surface an "Update frame" button or, with "Automatically apply updates" checked, stage the new build itself -- the frame still only updates on its own next wake either way.
40 lines
1.7 KiB
Python
40 lines
1.7 KiB
Python
"""Local firmware image storage + esp_app_desc_t parsing. Shared by the
|
|
manual upload path (POST /api/firmware) and the Gitea auto-update path
|
|
(see gitea_releases.py) -- both end up writing the same firmware.bin slot
|
|
that GET /frame/firmware streams to the device."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from . import config
|
|
|
|
# ESP-IDF app images embed an esp_app_desc_t at byte offset 32 (24-byte
|
|
# image header + 8-byte first-segment header): magic word, then version
|
|
# (32 bytes, NUL-padded) at +16 and project name (32 bytes) at +48 --
|
|
# verified against this project's real build artifact.
|
|
APP_DESC_OFFSET = 32
|
|
APP_DESC_MAGIC = 0xABCD5432
|
|
EXPECTED_PROJECT_NAME = "espresso_frame"
|
|
|
|
|
|
def firmware_path():
|
|
return config.CONFIG_PATH.parent / "firmware.bin"
|
|
|
|
|
|
def parse_app_version(data: bytes) -> str:
|
|
"""Extracts the embedded version from an ESP-IDF app image, raising
|
|
HTTPException(400) for anything that isn't this project's firmware."""
|
|
if len(data) < APP_DESC_OFFSET + 80:
|
|
raise HTTPException(400, "File is too small to be a firmware image")
|
|
magic = int.from_bytes(data[APP_DESC_OFFSET : APP_DESC_OFFSET + 4], "little")
|
|
if magic != APP_DESC_MAGIC:
|
|
raise HTTPException(400, "Not an ESP-IDF application image")
|
|
version = data[APP_DESC_OFFSET + 16 : APP_DESC_OFFSET + 48].split(b"\x00")[0].decode(errors="replace")
|
|
project = data[APP_DESC_OFFSET + 48 : APP_DESC_OFFSET + 80].split(b"\x00")[0].decode(errors="replace")
|
|
if project != EXPECTED_PROJECT_NAME:
|
|
raise HTTPException(400, f"Image is for project '{project}', not '{EXPECTED_PROJECT_NAME}'")
|
|
if not version:
|
|
raise HTTPException(400, "Image has no embedded version")
|
|
return version
|