From 0f6f07782f0baf82d5cdb7a9d64f95bee7b6bff9 Mon Sep 17 00:00:00 2001 From: subochev Date: Sun, 27 Sep 2026 15:56:49 +0300 Subject: [PATCH] =?UTF-8?q?UI:=20=D1=80=D0=B0=D0=B7=D0=B2=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81=D1=8B=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=D0=BE=D0=B2=20=E2=80=94=20=D1=81?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=BA=D0=B0=20=C2=AB=D0=A1=D0=B2=D1=8F=D0=B7?= =?UTF-8?q?=D1=8C=C2=BB=20(BLE=20/=20TCP/IP);=20transport=200.1.1-SNAPSHOT?= =?UTF-8?q?;=20BLOCKERS=20B-5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Обе строки назывались «Связь» (Канал 1 WebSocket и Канал 2 Mercury BLE), из-за чего статус BLE читался как статус TCP: при выключенном Wi-Fi на очках телефон показывал «подключено». Теперь вверху вкладки «Очки» сводка с отдельными строками BLE (Mercury) и TCP/IP (Wi-Fi), в секциях — «BLE» и «TCP/IP». mercuryTransport -> 0.1.1-SNAPSHOT (клиентский WS-пинг, см. BLOCKERS B-5). --- BLOCKERS.md | 34 +++++++++++++++++++ .../rayneovm/phone/ui/GlassesStatusScreen.kt | 21 +++++++++--- gradle/libs.versions.toml | 2 +- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/BLOCKERS.md b/BLOCKERS.md index 4fb3f65..22da627 100644 --- a/BLOCKERS.md +++ b/BLOCKERS.md @@ -121,3 +121,37 @@ adb -s A06B4AB933C4103 shell input keyevent 223 Shizuku `SET_SCREEN`. **Статус:** выполнено (mWakefulness=Dozing подтверждён в конце ночной сессии). + +--- + +## B-5 · Телефон показывал «подключено» при мёртвом TCP-канале — РЕШЕНО + +**Когда замечено:** 2026-09-27, пользователь: «телефон говорит подключено, очки говорят что нет связи». + +**Причина:** у очков Wi-Fi был выключен (`wifi_on=0`, нет default network) — реального Канала 1 не +было. Очки при выключении Wi-Fi локально теряют сокеты мгновенно и честно писали «нет связи». +Телефон же об этом не узнавал: FIN/RST до него не доходит (у очков уже нет интерфейса), а у +**клиента `:transport` не было ни пинга, ни таймаута** — `WifiTransport.connect` вечно висел в +`for (frame in s.incoming)`. TCP-сокет телефона оставался в `ESTAB`, `onDisconnected` не срабатывал, +`VmPhoneController` оставался в `Connected` → UI показывал «подключено». + +**Доказательство (воспроизведено):** выключили Wi-Fi очкам — через 20 c с телефона: +``` +ESTAB 192.168.76.121:50438 → 192.168.76.248:8080 +``` +сокет так и висел, телефон даже не переподключался (те же local-порты после возврата Wi-Fi). + +**Фикс (`rayneo-mercury :transport` 0.1.1-SNAPSHOT):** клиентским транспортам добавлен +`install(WebSockets) { pingIntervalMillis = WS_PING_INTERVAL_MS }` (15 c) — `WifiTransport` и +`WifiByteTransport`; константа в `Transport.kt`. Серверные транспорты пингуют и так +(`pingPeriod=15s`, `timeout=30s`), но они ловят мёртвого клиента, а не наоборот. + +**Проверено вживую 2026-09-27:** Wi-Fi очкам выключен → на 10-й секунде у телефона `Send-Q=82` +(пошли пинги, недоставленные) → на ~41-й секунде сокет ушёл в `FIN-WAIT-1` (клиент сам закрыл +мёртвую сессию, `onDisconnected` сработал) → после возврата Wi-Fi новый `ESTAB :57996` и в логах +очков `Phone connected: ws-83ad…` (авто-реконнект). + +**Побочно:** в UI телефона обе строки назывались одинаково — «Связь» (Канал 1 WebSocket и +Канал 2 Mercury BLE), из-за чего статус BLE читался как статус TCP. Теперь вверху вкладки «Очки» +сводка «Связь» с отдельными строками **BLE (Mercury)** и **TCP/IP (Wi-Fi)**, а в секциях каналов +строки переименованы в «BLE» и «TCP/IP». diff --git a/app-phone-vm/src/main/kotlin/pw/binom/rayneovm/phone/ui/GlassesStatusScreen.kt b/app-phone-vm/src/main/kotlin/pw/binom/rayneovm/phone/ui/GlassesStatusScreen.kt index ecb14c3..89db5ec 100644 --- a/app-phone-vm/src/main/kotlin/pw/binom/rayneovm/phone/ui/GlassesStatusScreen.kt +++ b/app-phone-vm/src/main/kotlin/pw/binom/rayneovm/phone/ui/GlassesStatusScreen.kt @@ -113,6 +113,17 @@ fun GlassesStatusScreen( fontWeight = FontWeight.Bold, ) + SectionCard(title = "Связь") { + StatusRow( + "BLE (Mercury)", + if (mercuryConnected) "есть" else "нет", + ) + StatusRow( + "TCP/IP (Wi-Fi)", + if (connection is ConnectionState.Connected) "есть" else "нет", + ) + } + Channel1WebSocketSection(connection = connection, glasses = glasses) ShizukuSection( @@ -183,7 +194,7 @@ private fun Channel1WebSocketSection( glasses: pw.binom.rayneovm.phone.protocol.GlassesStatusInfo?, ) { SectionCard(title = "Канал 1 — наше приложение (WebSocket)") { - StatusRow("Связь", connection.label) + StatusRow("TCP/IP", connection.label) when (connection) { is ConnectionState.Connected -> glasses?.let { g -> StatusRow("Аккумулятор", g.batteryPercent?.let { "$it%" } ?: "—") @@ -270,13 +281,13 @@ private fun Channel2MercurySection( OutlinedButton(onClick = onScan) { Text("Проверить ещё раз") } } is MercuryState.Disconnected -> { - StatusRow("Связь", "нет связи") + StatusRow("BLE", "нет связи") mercury.message?.let { Hint(it) } Spacer(modifier = Modifier.height(8.dp)) Button(onClick = onScan) { Text("Сканировать") } } is MercuryState.Scanning -> { - StatusRow("Связь", "поиск… (найдено ${mercury.found.size})") + StatusRow("BLE", "поиск… (найдено ${mercury.found.size})") Spacer(modifier = Modifier.height(8.dp)) if (mercury.found.isNotEmpty()) { Text("Найденные устройства:", color = Color(0xFFB0B0B0), fontSize = 12.sp) @@ -288,11 +299,11 @@ private fun Channel2MercurySection( } } is MercuryState.Connecting -> { - StatusRow("Связь", "сопрягаем ${mercury.device.name ?: mercury.device.address}…") + StatusRow("BLE", "сопрягаем ${mercury.device.name ?: mercury.device.address}…") Hint("Если очки запрашивают PIN — подтвердите на них.") } is MercuryState.Connected -> { - StatusRow("Связь", "подключено (${mercury.device.name ?: mercury.device.address})") + StatusRow("BLE", "подключено (${mercury.device.name ?: mercury.device.address})") StatusRow("Адрес", mercury.device.address) StatusRow("Аккумулятор", mercury.batteryPercent?.let { "$it%" } ?: "—") Spacer(modifier = Modifier.height(8.dp)) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5f7f6b2..7ace7e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ shizukuApi = "13.1.5" shizukuProvider = "13.1.5" navCompose = "2.10.2" mercury = "0.1.0-SNAPSHOT" -mercuryTransport = "0.1.0-SNAPSHOT" +mercuryTransport = "0.1.1-SNAPSHOT" mercuryBinocular = "0.1.0-SNAPSHOT" mercuryShizuku = "0.1.0-SNAPSHOT"