feat: build NODE.DC control station shell
This commit is contained in:
@@ -45,7 +45,7 @@ class VisualizationRuntime:
|
||||
self._thread: threading.Thread | None = None
|
||||
self._stop_event = threading.Event()
|
||||
self._phase: RuntimePhase = "idle"
|
||||
self._message = "Готово. Включите K1 и начните с поиска по Bluetooth."
|
||||
self._message = "Готово. Включите устройство и начните с поиска по Bluetooth."
|
||||
self._source_mode: SourceMode = "idle"
|
||||
self._foxglove_ws_url: str | None = None
|
||||
self._foxglove_viewer_url: str | None = None
|
||||
@@ -65,7 +65,7 @@ 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("файл записи не найден на этом MacBook")
|
||||
raise ValueError("файл записи не найден на этом компьютере")
|
||||
if not math.isfinite(speed) or speed < 0:
|
||||
raise ValueError("скорость повтора должна быть неотрицательным числом")
|
||||
# Validate the reviewed shape before changing runtime state.
|
||||
@@ -96,7 +96,7 @@ class VisualizationRuntime:
|
||||
self._start(
|
||||
source_mode="live",
|
||||
phase="starting_live",
|
||||
message="Запускаем приём MQTT и мост Foxglove.",
|
||||
message="Запускаем приём MQTT и локальный мост визуализации.",
|
||||
target=lambda: self._run_live(
|
||||
host,
|
||||
out_dir.expanduser().resolve(),
|
||||
@@ -199,7 +199,8 @@ class VisualizationRuntime:
|
||||
out_dir / "captures" / "mqtt_live",
|
||||
duration_seconds=duration_seconds,
|
||||
on_ready=lambda: self._set_running(
|
||||
"live", "Приём запущен. Теперь дважды нажмите физическую кнопку K1."
|
||||
"live",
|
||||
"Приём запущен. Теперь дважды нажмите физическую кнопку устройства.",
|
||||
),
|
||||
on_message_recorded=on_message,
|
||||
should_stop=self._stop_event.is_set,
|
||||
@@ -248,7 +249,9 @@ class VisualizationRuntime:
|
||||
self._foxglove_viewer_url = bridge.viewer_url
|
||||
if self._phase != "stopping":
|
||||
self._phase = running_phase
|
||||
self._message = "Мост Foxglove готов; источник данных запущен."
|
||||
self._message = (
|
||||
"Локальный мост визуализации готов; источник данных запущен."
|
||||
)
|
||||
publisher_ready.set()
|
||||
self._notify()
|
||||
while not source_done.is_set() or not messages.empty():
|
||||
@@ -275,13 +278,14 @@ 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 не запустился за 15 секунд.")
|
||||
self._finish_error("Локальный мост визуализации не запустился за 15 секунд.")
|
||||
source_done.set()
|
||||
self._stop_event.set()
|
||||
return
|
||||
if publisher_error:
|
||||
self._finish_error(
|
||||
f"Ошибка моста Foxglove: {type(publisher_error[0]).__name__}: {publisher_error[0]}"
|
||||
"Ошибка локального моста визуализации: "
|
||||
f"{type(publisher_error[0]).__name__}: {publisher_error[0]}"
|
||||
)
|
||||
source_done.set()
|
||||
return
|
||||
|
||||
+10
-8
@@ -80,13 +80,13 @@ class ConsoleService:
|
||||
message = runtime["message"]
|
||||
elif selected_device_id is not None:
|
||||
phase = "device_selected"
|
||||
message = "K1 выбран. Теперь введите название и пароль Wi-Fi."
|
||||
message = "Устройство выбрано. Теперь введите название и пароль Wi-Fi."
|
||||
elif devices:
|
||||
likely_count = sum(bool(item.get("likely_k1")) for item in devices)
|
||||
phase = "idle"
|
||||
message = (
|
||||
f"Найдено BLE-устройств: {len(devices)}. "
|
||||
f"Возможных K1: {likely_count}. Выберите нужное устройство."
|
||||
f"Совместимых профилей: {likely_count}. Выберите нужное устройство."
|
||||
)
|
||||
else:
|
||||
phase = "idle"
|
||||
@@ -144,7 +144,7 @@ class ConsoleService:
|
||||
raise ValueError("сначала найдите и выберите устройство через Bluetooth")
|
||||
self._set_operation(
|
||||
"provisioning",
|
||||
"Передаём в K1 настройки Wi-Fi одним подтверждённым запросом.",
|
||||
"Передаём устройству настройки Wi-Fi одним подтверждённым запросом.",
|
||||
)
|
||||
session_dir = _new_operation_session_dir(
|
||||
self.repository_root,
|
||||
@@ -177,12 +177,14 @@ class ConsoleService:
|
||||
)
|
||||
if ipv4 is None:
|
||||
raise RuntimeError(
|
||||
"K1 не сообщил адрес в локальной сети; автоматического повтора не было"
|
||||
"Устройство не сообщило адрес в локальной сети; автоматического повтора не было"
|
||||
)
|
||||
with self._lock:
|
||||
self._selected_device_id = request.device_id
|
||||
self._k1_ip = ipv4
|
||||
self._operation_message = "K1 подключён к Wi-Fi и сообщил локальный адрес."
|
||||
self._operation_message = (
|
||||
"Устройство подключено к Wi-Fi и сообщило локальный адрес."
|
||||
)
|
||||
finally:
|
||||
password = ""
|
||||
with self._lock:
|
||||
@@ -193,7 +195,7 @@ class ConsoleService:
|
||||
target = host or self.state()["k1_ip"]
|
||||
if not isinstance(target, str) or not target:
|
||||
raise ValueError(
|
||||
"сначала подключите K1 к Wi-Fi или укажите его локальный адрес"
|
||||
"сначала подключите устройство к Wi-Fi или укажите его локальный адрес"
|
||||
)
|
||||
target = validate_private_ipv4(target)
|
||||
out_dir = new_live_session_dir(self.repository_root)
|
||||
@@ -219,7 +221,7 @@ class ConsoleService:
|
||||
|
||||
service = ConsoleService(REPOSITORY_ROOT)
|
||||
app = FastAPI(
|
||||
title="NODE.DC K1 Live Console API",
|
||||
title="NODE.DC Device Control API",
|
||||
version=__version__,
|
||||
docs_url="/api/docs",
|
||||
redoc_url=None,
|
||||
@@ -257,7 +259,7 @@ async def connect(request: ConnectRequest) -> dict[str, Any]:
|
||||
except (BleakError, OSError, TimeoutError, RuntimeError, ValueError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
detail=f"Ошибка подключения K1 к Wi-Fi: {exc}",
|
||||
detail=f"Ошибка подключения устройства к Wi-Fi: {exc}",
|
||||
) from exc
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user