feat(k1): stabilize LAB bridge and isolate onboard device integration
This commit is contained in:
@@ -3,13 +3,14 @@ import { Button } from "@nodedc/ui-react";
|
||||
|
||||
import type { XgridsConnectionAttempt } from "../api";
|
||||
import { hostFailureDiagnosticPresentation } from "../hostDiagnosticPresentation";
|
||||
import { STATION_WIFI_FAILURE_MESSAGES } from "../networkFailurePresentation";
|
||||
|
||||
const connectionAttemptStageLabels: Record<string, string> = {
|
||||
accepted: "Запрос принят",
|
||||
"scan-selection-admitted": "Результат выбран",
|
||||
"host-wifi-profile-preflight": "Подготовка профиля Wi‑Fi",
|
||||
"device-ap-activation": "Подготовка локальной сети",
|
||||
"ble-provisioning-write": "Передаются настройки сети",
|
||||
"ble-provisioning-write": "Подключение K1 к Wi‑Fi",
|
||||
"ble-write-dispatched": "Настройки переданы",
|
||||
"status-observing": "Ожидание ответа",
|
||||
"device-topology-applied": "Целевая сеть подтверждена",
|
||||
@@ -62,6 +63,8 @@ export function attemptNextActionLabel(
|
||||
return "Проверить управление без изменения сети";
|
||||
case "start-acquisition":
|
||||
return "Готово к запуску приёма";
|
||||
case "stop-acquisition":
|
||||
return "Остановить текущий приём";
|
||||
case "stop-local-receiver":
|
||||
return "Завершить только локальный приём";
|
||||
case "retire-unavailable-physical-target":
|
||||
@@ -74,6 +77,9 @@ export function attemptNextActionLabel(
|
||||
}
|
||||
|
||||
const publicConnectionErrorLabels: Readonly<Record<string, string>> = {
|
||||
...STATION_WIFI_FAILURE_MESSAGES,
|
||||
BleakGATTProtocolError:
|
||||
"K1 ответил ошибкой Bluetooth. Подключение к Wi‑Fi не подтверждено; проверьте состояние устройства или начните новое подключение.",
|
||||
"network-provision-discovery-generation-conflict":
|
||||
"Результат Bluetooth-поиска устарел до отправки. Настройки устройства не изменялись; выполните новый поиск.",
|
||||
"connection-mode-draft-revision-conflict":
|
||||
@@ -121,6 +127,7 @@ export function K1OperatorError({
|
||||
onClear: () => void;
|
||||
}) {
|
||||
const structured = hostFailureDiagnosticPresentation(diagnostic);
|
||||
const stationFailure = STATION_WIFI_FAILURE_MESSAGES[attempt?.public_error_code ?? ""];
|
||||
const [diagnosticCopied, setDiagnosticCopied] = useState(false);
|
||||
const copyDiagnosticBundle = async () => {
|
||||
if (!attempt?.diagnostic_bundle || !navigator.clipboard) return;
|
||||
@@ -137,7 +144,7 @@ export function K1OperatorError({
|
||||
>
|
||||
<span className="error-banner__dot" aria-hidden="true" />
|
||||
<div className="error-banner__copy">
|
||||
<strong>{title}</strong>
|
||||
<strong>{stationFailure ? "Не удалось подключить K1 к Wi‑Fi" : title}</strong>
|
||||
<p>{publicConnectionErrorLabel(attempt, structured)}</p>
|
||||
{recoveryActions ? (
|
||||
<div className="error-banner__recovery-actions">
|
||||
|
||||
@@ -31,6 +31,7 @@ import type {
|
||||
} from "../api";
|
||||
import { profileSelectionForConnectionMode } from "../compatibility";
|
||||
import {
|
||||
BLE_DISCOVERY_TIMEOUT_SECONDS,
|
||||
DEFAULT_CONNECTION_MODE,
|
||||
connectionModeOptions,
|
||||
type ConnectionMode,
|
||||
@@ -66,10 +67,12 @@ import {
|
||||
type ReadOnlyConnectionObservationTarget,
|
||||
} from "../lifecycle";
|
||||
import { finiteMetric } from "../presentation";
|
||||
import { STATION_WIFI_FAILURE_MESSAGES } from "../networkFailurePresentation";
|
||||
import type { XgridsK1Controller } from "../runtimeContext";
|
||||
import { K1OperatorError } from "./K1OperatorError";
|
||||
import {
|
||||
connectionActionAuthoritySnapshot,
|
||||
networkProvisionFailureMessage,
|
||||
type BleDiscoverySubmitResult,
|
||||
type ConnectionActionAuthoritySnapshot,
|
||||
} from "../useXgridsK1Runtime";
|
||||
@@ -1866,7 +1869,7 @@ export function K1ProvisioningPipeline({
|
||||
const connectionRecoveryRequired = connectionRecoveryIsRequired(
|
||||
connectionRecoveryKey,
|
||||
escapedConnectionRecoveryKey,
|
||||
) && !(searchRequested && !searchDisplayActive);
|
||||
) && (connectionAttemptFailed || !(searchRequested && !searchDisplayActive));
|
||||
const presentedConnectionRecoveryRequired = !livePreparationPending && (
|
||||
connectionRecoveryRequired || connectionRecoveryVerificationPending
|
||||
);
|
||||
@@ -2089,7 +2092,7 @@ export function K1ProvisioningPipeline({
|
||||
const startedAt = Date.now();
|
||||
const updateCountdown = () => {
|
||||
const elapsedSeconds = Math.floor((Date.now() - startedAt) / 1_000);
|
||||
setScanSecondsRemaining(Math.max(0, 6 - elapsedSeconds));
|
||||
setScanSecondsRemaining(Math.max(0, BLE_DISCOVERY_TIMEOUT_SECONDS - elapsedSeconds));
|
||||
};
|
||||
updateCountdown();
|
||||
const timer = window.setInterval(updateCountdown, 250);
|
||||
@@ -2350,7 +2353,7 @@ export function K1ProvisioningPipeline({
|
||||
let scanResult: BleDiscoverySubmitResult | null = null;
|
||||
try {
|
||||
if (!runtimeActionIsCurrent(actionFence)) return;
|
||||
scanResult = await scanWithResult({ durationSeconds: 6 });
|
||||
scanResult = await scanWithResult({ durationSeconds: BLE_DISCOVERY_TIMEOUT_SECONDS });
|
||||
if (
|
||||
!localScenarioActionEpochIsCurrent(
|
||||
actionEpoch,
|
||||
@@ -2573,12 +2576,12 @@ export function K1ProvisioningPipeline({
|
||||
}
|
||||
|
||||
const freshStartAllowed = result.intentDisposition === "release";
|
||||
const failureMessage = freshStartAllowed
|
||||
const failureMessage = networkProvisionFailureMessage(observedOperation) ?? (freshStartAllowed
|
||||
&& provisioningFailureRequiresFreshCandidate(result.failureReasonCode)
|
||||
? "Результат Bluetooth-поиска устарел до отправки. Настройки не отправлены; выполните явный повторный поиск."
|
||||
: freshStartAllowed
|
||||
? "Подключение не началось. Настройки удалены; после устранения причины начните новый явный поиск."
|
||||
: "Результат отправки неизвестен. Повторное применение заблокировано, чтобы не отправить BLE-команду дважды; дождитесь, пока система определит безопасное продолжение.";
|
||||
: "Подключение не подтверждено. Проверьте состояние K1 или начните новое подключение; автоматического повтора не было.");
|
||||
setConnectionAttemptPresentation((current) => (
|
||||
current?.idempotencyKey === idempotencyKey
|
||||
? {
|
||||
@@ -3203,7 +3206,9 @@ export function K1ProvisioningPipeline({
|
||||
disabled={isBusy || modeResetInFlight}
|
||||
onClick={() => void changeDesiredConnectionMode(connectionMode)}
|
||||
>
|
||||
{savedBridgeNetworkSetupRequired
|
||||
{STATION_WIFI_FAILURE_MESSAGES[connectionRecoveryAttempt?.public_error_code ?? ""]
|
||||
? "Указать сеть Wi‑Fi заново"
|
||||
: savedBridgeNetworkSetupRequired
|
||||
? "Подключить K1 к общей сети"
|
||||
: "Подключить новый K1"}
|
||||
</Button>
|
||||
@@ -3256,7 +3261,7 @@ export function K1ProvisioningPipeline({
|
||||
title="Подключение"
|
||||
status={
|
||||
searchActive
|
||||
? `Поиск · ${scanSecondsRemaining ?? 6} с`
|
||||
? `Поиск · до ${scanSecondsRemaining ?? BLE_DISCOVERY_TIMEOUT_SECONDS} с`
|
||||
: connectionAttemptSettling
|
||||
? "Устройство выбрано"
|
||||
: presentedPhysicalRecoveryRequired
|
||||
@@ -3316,7 +3321,7 @@ export function K1ProvisioningPipeline({
|
||||
>
|
||||
<ActivityIndicator />
|
||||
<strong>
|
||||
Поиск Bluetooth · {scanSecondsRemaining ?? 6} с
|
||||
Поиск Bluetooth · до {scanSecondsRemaining ?? BLE_DISCOVERY_TIMEOUT_SECONDS} с
|
||||
</strong>
|
||||
</div>
|
||||
) : networkRecoveryRequired
|
||||
|
||||
Reference in New Issue
Block a user