98 lines
3.3 KiB
Python
98 lines
3.3 KiB
Python
"""Firmware identification from the boot log.
|
|
|
|
The app version string is "1.0" in every build seen so far, so builds are
|
|
told apart by the ELF SHA256 prefix printed by app_init. Boxes receive new
|
|
firmware over OTA; an unknown SHA usually means a box has already updated.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class KnownFirmware:
|
|
name: str
|
|
source: str
|
|
sd_wifi: bool # reads SSID/PASSWORD from ENV.TXT
|
|
setup_portal: bool # short press -> Wi-Fi setup portal
|
|
|
|
|
|
KNOWN: dict[str, KnownFirmware] = {
|
|
"45fa9d5b9": KnownFirmware(
|
|
name="v1.0 (built Jun 29 2026)",
|
|
source="factory image new_system.bin",
|
|
sd_wifi=False,
|
|
setup_portal=False,
|
|
),
|
|
"c94ba0e9b": KnownFirmware(
|
|
name="v1.0 source build (Sep 26 2026)",
|
|
source="developer build, built by Krzysztof Kallas",
|
|
sd_wifi=True,
|
|
setup_portal=True,
|
|
),
|
|
}
|
|
|
|
_ELF = re.compile(r"app_init: ELF file SHA256:\s+(?P<sha>[0-9a-f]{6,})")
|
|
_VER = re.compile(r"app_init: App version:\s+(?P<v>\S+)")
|
|
_BUILT = re.compile(r"app_init: Compile time:\s+(?P<t>.+)$")
|
|
_IDF = re.compile(r"app_init: ESP-IDF:\s+(?P<v>\S+)")
|
|
# Evidence of features, for builds not in KNOWN.
|
|
_FEATURE_SD_WIFI = re.compile(r"WIFI_CREDENTIALS: ")
|
|
_FEATURE_PORTAL = re.compile(r"WIFI_PROVISIONING: |Short press: ")
|
|
# Deployed v1.0 logs the SD mount under the INFLUX_BUFFER tag.
|
|
_LEGACY_SD_TAG = re.compile(r"INFLUX_BUFFER: SD card mounted")
|
|
|
|
|
|
@dataclass
|
|
class FirmwareInfo:
|
|
elf_sha: str | None = None
|
|
version: str | None = None
|
|
built: str | None = None
|
|
idf: str | None = None
|
|
evidence: set[str] = field(default_factory=set)
|
|
|
|
def observe(self, text: str) -> None:
|
|
if m := _ELF.search(text):
|
|
self.elf_sha = m.group("sha")
|
|
elif m := _VER.search(text):
|
|
self.version = m.group("v")
|
|
elif m := _BUILT.search(text):
|
|
self.built = m.group("t").strip()
|
|
elif m := _IDF.search(text):
|
|
self.idf = m.group("v")
|
|
if _FEATURE_SD_WIFI.search(text):
|
|
self.evidence.add("sd_wifi")
|
|
if _FEATURE_PORTAL.search(text):
|
|
self.evidence.add("setup_portal")
|
|
if _LEGACY_SD_TAG.search(text):
|
|
self.evidence.add("legacy_v1")
|
|
|
|
@property
|
|
def known(self) -> KnownFirmware | None:
|
|
if not self.elf_sha:
|
|
return None
|
|
return next((k for sha, k in KNOWN.items() if self.elf_sha.startswith(sha)), None)
|
|
|
|
@property
|
|
def supports_sd_wifi(self) -> bool | None:
|
|
"""True/False when determinable, None when unknown (no boot seen)."""
|
|
if k := self.known:
|
|
return k.sd_wifi
|
|
if "sd_wifi" in self.evidence:
|
|
return True
|
|
if "legacy_v1" in self.evidence:
|
|
return False
|
|
return None
|
|
|
|
def describe(self) -> str:
|
|
if k := self.known:
|
|
return f"{k.name} - {k.source}"
|
|
if self.elf_sha:
|
|
built = f", built {self.built}" if self.built else ""
|
|
return f"Unknown build {self.elf_sha[:9]}{built} (probably updated over OTA)"
|
|
if "legacy_v1" in self.evidence:
|
|
return "Probably v1.0 factory image (press Reset & capture to confirm)"
|
|
return "Not identified yet - press Reset & capture"
|