Skip to content

Commit 79ca201

Browse files
committed
show boot progress
1 parent 898cc6a commit 79ca201

5 files changed

Lines changed: 36 additions & 5 deletions

File tree

‎robot_orchestrator/boot/fsm.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class BootResult:
3535
services_ready: dict[str, bool]
3636

3737

38+
@dataclass
39+
class BootProgress:
40+
step: str = "starting"
41+
42+
3843
class BootSequence:
3944
def __init__(
4045
self,
@@ -49,6 +54,7 @@ def __init__(
4954
scan_qr: Callable[[], SecretsPayload | None] | None = None,
5055
recovery_committing_handlers: dict | None = None,
5156
wifi_manager=None,
57+
progress: "BootProgress | None" = None,
5258
):
5359
self.settings = settings
5460
self.paths = paths
@@ -61,6 +67,7 @@ def __init__(
6167
self.scan_qr = scan_qr
6268
self.recovery_committing_handlers = recovery_committing_handlers
6369
self.wifi_manager = wifi_manager
70+
self.progress = progress or BootProgress()
6471

6572
def prepare_early_services(self) -> list[str]:
6673
placeholder_mode = ModeDecision(production=False, reasons=[], capabilities={})
@@ -86,22 +93,28 @@ async def run(self) -> BootResult:
8693
facts = BootFacts()
8794
facts.gpio_forced = self._read_gpio()
8895

96+
self.progress.step = "checking_secrets"
8997
await self._maybe_scan_qr(facts)
9098

99+
self.progress.step = "probing_hardware"
91100
camera_device = resolve_camera_device(self.settings.hardware.camera, self.hal.camera_probe)
92101
await asyncio.to_thread(self._probe_hardware, facts, camera_device)
93102

103+
self.progress.step = "updating_repos"
94104
await asyncio.to_thread(self._update_repos, facts)
105+
self.progress.step = "flashing_firmware"
95106
await asyncio.to_thread(self._run_firmware_targets, facts)
96107

97108
mode = decide_mode(facts)
98109

110+
self.progress.step = "starting_services"
99111
mcu_port = resolve_mcu_port(self.settings.hardware.mcu, self.hal.usb_inventory)
100112
self._prepare_services(mode, camera_device, mcu_port)
101113

102114
main_services = [s for s in self.settings.services if s.phase != "early"]
103115
order = topological_order(main_services)
104116
services_ready = await self.supervisor.start_ordered(order, mode.capabilities)
117+
self.progress.step = "done"
105118

106119
with self.db.transaction() as conn:
107120
store.update_boot_history(
@@ -132,6 +145,7 @@ async def _maybe_scan_qr(self, facts: BootFacts) -> None:
132145
required = self.settings.secrets.required
133146
missing = secrets_store.missing_required_secrets(self.paths, required)
134147
if missing and self.scan_qr is not None:
148+
self.progress.step = "waiting_for_qr"
135149
payload = await asyncio.to_thread(self.scan_qr)
136150
if payload is not None:
137151
ingest_payload(self.paths, self.journal, payload)

‎robot_orchestrator/cli.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from robot_orchestrator import log as logmod
1515
from robot_orchestrator.boot.decision import capability
16-
from robot_orchestrator.boot.fsm import BootSequence
16+
from robot_orchestrator.boot.fsm import BootProgress, BootSequence
1717
from robot_orchestrator.boot.hardware import resolve_camera_device, resolve_mcu_port
1818
from robot_orchestrator.config import Settings, load_settings
1919
from robot_orchestrator.firmware.github_artifacts import GithubArtifactClient
@@ -271,17 +271,20 @@ async def _run_orchestrator(settings: Settings, paths: Paths, args: argparse.Nam
271271
}
272272

273273
stop_event = asyncio.Event()
274+
boot_progress = BootProgress()
274275
context = AdminContext(
275276
settings=settings, paths=paths, db=db, journal=journal,
276277
repo_managers=repo_managers, firmware_workflows=firmware_workflows,
277278
supervisor=supervisor, scan_qr=scan_qr, wifi_manager=wifi_manager,
278279
self_update_manager=self_update_manager, request_restart=stop_event.set,
280+
boot_progress=boot_progress,
279281
)
280282

281283
sequence = BootSequence(
282284
settings, paths, db, journal, hal,
283285
repo_managers, firmware_workflows, supervisor,
284286
scan_qr=scan_qr, recovery_committing_handlers=recovery_handlers, wifi_manager=wifi_manager,
287+
progress=boot_progress,
285288
)
286289

287290
notifier = SdNotifier()

‎robot_orchestrator/web/app.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ def boot_page(request: Request):
7777
def boot_state(request: Request):
7878
require_loopback(request)
7979
if context is None or context.boot_result is None:
80-
return {"state": "booting"}
80+
step = context.boot_progress.step if context and context.boot_progress else "starting"
81+
return {"state": "booting", "step": step}
8182
boot = context.boot_result
8283
return {
8384
"state": "running",

‎robot_orchestrator/web/context.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass
22
from typing import Callable
33

4-
from robot_orchestrator.boot.fsm import BootResult
4+
from robot_orchestrator.boot.fsm import BootProgress, BootResult
55
from robot_orchestrator.config import Settings
66
from robot_orchestrator.firmware.workflow import FirmwareWorkflow
77
from robot_orchestrator.paths import Paths
@@ -25,3 +25,4 @@ class AdminContext:
2525
self_update_manager: object | None = None
2626
request_restart: Callable[[], None] | None = None
2727
boot_result: BootResult | None = None
28+
boot_progress: BootProgress | None = None

‎robot_orchestrator/web/static/boot/index.html‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,20 @@ <h1>robot-orchestrator</h1>
5555
const qrHint = document.getElementById("qr-hint");
5656
banner.innerHTML = "";
5757

58+
const STEP_LABELS = {
59+
starting: "Загрузка…",
60+
checking_secrets: "Проверка секретов…",
61+
waiting_for_qr: "Ожидание QR-кода",
62+
probing_hardware: "Проверка оборудования…",
63+
updating_repos: "Обновление репозиториев…",
64+
flashing_firmware: "Прошивка микроконтроллера…",
65+
starting_services: "Запуск сервисов…",
66+
done: "Загрузка…",
67+
};
68+
5869
if (data.state === "booting" || data.state === "unknown") {
5970
banner.className = "banner booting";
60-
banner.textContent = "Загрузка…";
71+
banner.textContent = STEP_LABELS[data.step] || "Загрузка…";
6172
} else if (data.production) {
6273
banner.className = "banner prod";
6374
banner.textContent = "PROD";
@@ -76,7 +87,8 @@ <h1>robot-orchestrator</h1>
7687
}
7788
}
7889

79-
const showQr = data.reasons && data.reasons.some((r) => r.startsWith("secrets_missing"));
90+
const showQr = data.step === "waiting_for_qr" ||
91+
(data.reasons && data.reasons.some((r) => r.startsWith("secrets_missing")));
8092
qrHint.style.display = showQr ? "block" : "none";
8193
if (showQr) {
8294
qr.style.display = "block";

0 commit comments

Comments
 (0)