feat: localize K1 console to Russian

This commit is contained in:
DCCONSTRUCTIONS
2026-07-15 22:08:18 +03:00
parent 6be96f0b85
commit ea834e09e8
9 changed files with 431 additions and 194 deletions
+24 -27
View File
@@ -45,7 +45,7 @@ class VisualizationRuntime:
self._thread: threading.Thread | None = None
self._stop_event = threading.Event()
self._phase: RuntimePhase = "idle"
self._message = "Ready for a live scanner or replay capture."
self._message = "Готово. Включите K1 и начните с поиска по Bluetooth."
self._source_mode: SourceMode = "idle"
self._foxglove_ws_url: str | None = None
self._foxglove_viewer_url: str | None = None
@@ -65,22 +65,22 @@ class VisualizationRuntime:
def start_replay(self, path: Path, *, speed: float = 1.0, loop: bool = False) -> None:
resolved = path.expanduser().resolve()
if not resolved.is_file():
raise ValueError("replay path must be an existing backend-local file")
raise ValueError("файл записи не найден на этом MacBook")
if not math.isfinite(speed) or speed < 0:
raise ValueError("replay speed must be finite and non-negative")
raise ValueError("скорость повтора должна быть неотрицательным числом")
# Validate the reviewed shape before changing runtime state.
iterator = iter_replay_messages(resolved)
try:
next(iterator)
except StopIteration as exc:
raise ValueError("replay capture contains no messages") from exc
raise ValueError("в записи нет сообщений") from exc
finally:
iterator.close()
self._start(
source_mode="replay",
phase="replay",
message=f"Starting replay: {resolved.name}",
message=f"Запускаем повтор записи: {resolved.name}",
target=lambda: self._run_replay(resolved, speed=speed, loop=loop),
)
@@ -92,11 +92,11 @@ class VisualizationRuntime:
duration_seconds: float = 3600.0,
) -> None:
if not math.isfinite(duration_seconds) or duration_seconds <= 0:
raise ValueError("live duration must be finite and greater than zero")
raise ValueError("длительность приёма должна быть больше нуля")
self._start(
source_mode="live",
phase="starting_live",
message="Starting read-only MQTT capture and Foxglove bridge.",
message="Запускаем приём MQTT и мост Foxglove.",
target=lambda: self._run_live(
host,
out_dir.expanduser().resolve(),
@@ -111,11 +111,11 @@ class VisualizationRuntime:
if thread is None or not thread.is_alive():
self._phase = "idle"
self._source_mode = "idle"
self._message = "No active visualization session."
self._message = "Активного потока нет."
notify_only = True
else:
self._phase = "stopping"
self._message = "Stopping source after preserving queued evidence."
self._message = "Останавливаем поток и сохраняем полученные данные."
self._stop_event.set()
self._notify()
if notify_only:
@@ -133,7 +133,7 @@ class VisualizationRuntime:
) -> None:
with self._lock:
if self._thread is not None and self._thread.is_alive():
raise RuntimeError("a visualization session is already active")
raise RuntimeError("поток уже запущен; сначала остановите текущую сессию")
self._stop_event = threading.Event()
self._metrics = BridgeMetrics()
self._phase = phase
@@ -157,7 +157,7 @@ class VisualizationRuntime:
count = 0
for message in iter_replay_messages(path):
if self._stop_event.is_set():
return "Replay stopped by operator."
return "Повтор записи остановлен."
source_ns = (
message.received_monotonic_ns
if message.received_monotonic_ns is not None
@@ -169,12 +169,12 @@ class VisualizationRuntime:
target_ns = replay_started_ns + int((source_ns - first_source_ns) / speed)
remaining = (target_ns - time.monotonic_ns()) / 1_000_000_000
if remaining > 0 and self._stop_event.wait(remaining):
return "Replay stopped by operator."
return "Повтор записи остановлен."
put(message)
count += 1
if not loop:
return f"Replay completed: {count} MQTT messages."
return "Replay stopped by operator."
return f"Повтор завершён: обработано сообщений MQTT — {count}."
return "Повтор записи остановлен."
self._run_pipeline(produce, running_phase="replay")
@@ -199,15 +199,12 @@ class VisualizationRuntime:
out_dir / "captures" / "mqtt_live",
duration_seconds=duration_seconds,
on_ready=lambda: self._set_running(
"live", "MQTT subscribed; waiting for K1 scan frames."
"live", "Приём запущен. Теперь дважды нажмите физическую кнопку K1."
),
on_message_recorded=on_message,
should_stop=self._stop_event.is_set,
)
return (
f"Live capture stopped: {summary['stop_reason']}; "
f"{summary['message_count']} messages preserved."
)
return f"Приём остановлен. Сохранено сообщений: {summary['message_count']}."
try:
self._run_pipeline(produce, running_phase="live")
@@ -251,7 +248,7 @@ class VisualizationRuntime:
self._foxglove_viewer_url = bridge.viewer_url
if self._phase != "stopping":
self._phase = running_phase
self._message = "Foxglove bridge ready; source is active."
self._message = "Мост Foxglove готов; источник данных запущен."
publisher_ready.set()
self._notify()
while not source_done.is_set() or not messages.empty():
@@ -278,33 +275,33 @@ class VisualizationRuntime:
publisher = threading.Thread(target=publish, name="k1-foxglove-publisher", daemon=True)
publisher.start()
if not publisher_ready.wait(timeout=15.0):
self._finish_error("Foxglove bridge did not start within 15 seconds.")
self._finish_error("Мост Foxglove не запустился за 15 секунд.")
source_done.set()
self._stop_event.set()
return
if publisher_error:
self._finish_error(
f"Foxglove bridge failed: {type(publisher_error[0]).__name__}: {publisher_error[0]}"
f"Ошибка моста Foxglove: {type(publisher_error[0]).__name__}: {publisher_error[0]}"
)
source_done.set()
return
final_message = "Session stopped."
final_message = "Поток остановлен."
try:
final_message = producer(enqueue)
except CaptureError as exc:
self._finish_error(f"Live MQTT capture failed: {exc}")
self._finish_error(f"Ошибка приёма MQTT: {exc}")
except (OSError, RuntimeError, ValueError) as exc:
self._finish_error(f"Source failed: {type(exc).__name__}: {exc}")
self._finish_error(f"Ошибка источника: {type(exc).__name__}: {exc}")
finally:
source_done.set()
publisher.join(timeout=15.0)
if publisher.is_alive():
self._stop_event.set()
self._finish_error("Publisher did not drain within 15 seconds.")
self._finish_error("Очередь публикации не завершилась за 15 секунд.")
elif publisher_error:
self._finish_error(
f"Publisher failed: {type(publisher_error[0]).__name__}: {publisher_error[0]}"
f"Ошибка публикации: {type(publisher_error[0]).__name__}: {publisher_error[0]}"
)
elif self.snapshot()["phase"] != "error":
self._finish_idle(final_message)