Commit Graph

36 Commits

Author SHA1 Message Date
subochev e21c27b0ee stt(dump): WAV-заголовок — размеры little-endian (плееры открывают дампы) 2026-08-24 21:36:53 +03:00
subochev 0d79b5aa33 stt(mic): усиление микрофона очков ×10 — VAD/Whisper видят речь (пустая фраза) 2026-08-24 20:57:06 +03:00
subochev 40edd88c1d stt(mic): вынести кадровую арифметику в micChunkBytes + регрессионные тесты MicChunkTest
- CHUNK_BYTES=3200, micChunkBytes(buf, frames): кадры read() -> байты (*2)
- 4 теста: полный чанк 3200 Б без копирования, короткий read,
  документация старого бага (copyOf(кадры) = полка), inFrameCount=1600
2026-08-24 12:23:33 +03:00
subochev fc84ba16b6 stt(mic): request frames in frames (inFrameCount), not bytes — each chunk now carries full 100 ms 2026-08-24 11:10:05 +03:00
subochev c940a7e0eb stt(mic): AudioRecord.read возвращает КАДРЫ, а не байты — не терять вторую половину каждого 100-мс чанка (впечатление 'пропускает через один') 2026-08-24 11:08:27 +03:00
subochev 22eb83fd17 stt: debug-дампы аудио-пути (очки: сырой микрофон в WAV; телефон: принятые PCM) по старту от stt_dump.marker 2026-08-24 05:58:59 +03:00
subochev 7557d7229f Очки: отмена слушания двойным тапом — SttCancel reason=cancel 2026-08-24 03:01:55 +03:00
subochev 504e66f0c6 Очки: красный индикатор записи микрофона в режиме «слушаю» 2026-08-23 21:58:36 +03:00
subochev e24caaa684 Очки: разграничение SttCancel (user/timeout) — выход из режима при тишине 30с, микрофон закрывается при потере фокуса 2026-08-23 21:17:43 +03:00
subochev eaca47c443 Очки: единый скролл диалога на оба глаза + мгновенная пауза звука телефона при вызове ассистента 2026-08-23 21:08:40 +03:00
subochev 706cc10d1e Очки: история диалога в свайпе + распознавание в LLM только по клику (касяки 1-3) 2026-08-23 20:15:21 +03:00
subochev f75e672819 Очки: жесты и режимы общения с ассистентом (по docs/gestures-and-modes.md)
- AssistantMode: NONE/LISTENING/THINKING/ANSWER + чистая gestureDecision (матрица жестов)
- Клик в «ответ виден» = новая фраза; DoubleClick = выход (кино на паузе); свайпы = скролл
- Плавный скролл ответа: SlideContinuous delta → answerScrollDelta → scrollOffset (зажим 0..max)
- Индикатор «думаю...» (thinking, сброс на ShowText)
- AssistantOverlay: затемнение 72%, по глазам, скролл-аккумулятор
- Тесты: 22 (AssistantGesturesTest) — glasses 91 зелёных
2026-08-23 01:55:24 +03:00
subochev bbf7a020d4 Ассистент Порфирий: тула времени, сессии, чат-UI, голосовой цикл; BT-транспорт очки↔телефон
- BT (RFCOMM SPP 00001105) вместо WiFi: BtGlassesTransport/BtServerTransport, реконнект 3с
- PlaybackService (foreground, WAKE_LOCK) против морозов HONOR
- Ассистент: AgentTools (TimeTool ISO 8601), TOOL_CALL-протокол, сессии SQLite (UUID)
- Чат-UI: создание/сброс/переключение сессий, автообновление ленты 2с
- LlmClient на прямом OkHttp (Koog давал пустой content на длинных историях)
- Голосовой цикл: тройной тап на очках → STT → активная сессия → ShowText
- Тесты: AgentToolsTest (3), GlassesServerTest переписан (6) — зелёные
2026-08-22 16:31:04 +03:00
subochev ee9700c558 CICD: сборка APK на release → F-Droid; versionCode из тега; фикс скролла каталога и масштаба
build / Android APK to F-Droid (release) Failing after 36s
2026-08-21 19:27:02 +03:00
Hermes Agent a434d5bb12 feat: потоковый локальный ассистент — фраза → LLM → ответ на очки
- agent/Assistant.kt: оркестратор диалога (SQLite-история + Koog), таймаут 30с,
  сессия одна живая, история — последние 20 сообщений
- ChatDao.latestSessionId(); SYSTEM_PROMPT нейтральный (TODO: персона)
- PhoneApp: очередь фраз (Channel UNLIMITED), assistantLoop (THINKING/ShowText/IDLE),
  ensureAssistant ленивый; ключ/модель из BuildConfig (local.properties)
- Очки: assistantText StateFlow, ShowText → оверлей (белый текст речи + серый ответ),
  оверлей остаётся после Click до новой сессии
- AssistantTest (androidTest, 3 теста: сессия/история/таймаут) — OK (6 tests всего)
- README: строка про ассистента
2026-08-20 12:35:49 +03:00
Hermes Agent 3ed63012f4 build: Kotlin 2.2.0 → 2.4.10 (AGP 8.9.2, Gradle 8.14 — в официальном диапазоне)
- libs.versions.toml: kotlin = 2.4.10
- jvmTarget '17' (kotlinOptions, String) — в 2.4.10 ошибка → compilerOptions DSL
  (JvmTarget.JVM_17) в app-phone и app-glasses
- Compose-плагин (org.jetbrains.kotlin.plugin.compose) версионируется с Kotlin — 2.4.10
- Проверено: lib-core allTests + phone/glasses тесты + оба APK — BUILD SUCCESSFUL
- Открывает дорогу Koog (требует Kotlin 2.3.10+)
2026-08-20 10:51:07 +03:00
Hermes Agent 1304c6d921 feat: голосовой ввод на очках — TripleClick + PCM → телефон → текст на очки
Протокол: SttAudio (PCM s16 16k mono кусками), SttPhrase/SttDone/SttCancel
(host→очки), StopStt(cancel) (очки→host).

Очки: TripleClick — безусловный вызов (кино: пауза + затемнение + слушаем);
во время распознавания Click — завершить (весь текст в консоль, затемнение
уходит, видео на паузе), DoubleClick — отмена; VAD-тишина 30с — автоотмена.
Микрофон (AudioRecord 16k) → SttAudio; на эмуляторе — PCM из filesDir/samples
(куски 3200 + пауза 200мс). Оверлей текста в Binocular (полупрозрачный фон,
текст по центру, в оба глаза). RECORD_AUDIO в манифесте. DEBUG-ресивер
(am broadcast DEBUG_GESTURE type=triple_click|click|double_click).

Телефон: SttStreamer — стриминговый VAD (Silero fp32, окна 512) → фразы →
Whisper-small int8 (executor, не блокирует приём) → SttPhrase на очки;
StopStt → SttDone (полный текст) / SttCancel; watchdog 30с тишины.

Фиксы (найдены на эмуляторе): IllegalThreadStateException — двойной старт
watchdog-потока (thread{}.apply{start()}); decode входящих — try/catch с
логом (не роняет сессию).

Проверено на эмуляторах (5554 очки + 5556 телефон, forward 8080):
triple_click → фразы «Раз, два» и «3 4 5» на очках (оверлей) → click →
«ВЕСЬ ТЕКСТ: Раз, два 3 4 5»; 30с-тишина → «отменено». Тесты: lib-core,
phone, glasses — зелёные.
2026-08-20 04:12:28 +03:00
Hermes Agent 97aaabeb65 fix(app-glasses): не застревать на fallback 10.0.2.2 — перепоиск телефона
Если при старте очков телефон недоступен, discoverPhoneUrl возвращал
fallback (эмуляторный 10.0.2.2), он запоминался как lastUrl — очки вечно
реконнектили туда. Теперь на устройстве fallback не используется: не
нашли — повторный поиск через 3с (connectLoop рекурсивно). На эмуляторе
fallback остался. Железо: очки сами нашли телефон после его включения.
2026-08-20 01:41:43 +03:00
Hermes Agent 9f1c6735c1 feat(app-glasses): AR-метка RayNeoX2 (com.rayneo.mercury.app) + тема + landscape
Из pair-view: meta-data com.rayneo.mercury.app=true — очки распознают
приложение как AR (без неё открывалось как обычное). Тема
Theme.ViewMateGlasses (чёрный фон, fullscreen), screenOrientation
landscape, hardwareAccelerated. Плюс фикс краша: prefs читались в
конструкторе Application (до attachBaseContext) — перенесено в onCreate.
Проверено на железе: открывается через лаунчер нормально.
2026-08-20 01:22:27 +03:00
Hermes Agent 896185244b fix(app-glasses): scale на паузе — принудительная перерисовка кадра
На паузе ExoPlayer не шлёт кадры — поверхность меняла размер/позицию,
но кадр оставался старым (часть видео не видно). requestRedraw():
updateTexImage + drawFrame для всех outputs — вызывается при смене
scale (LaunchedEffect) и в surfaceChanged. Подтверждено на железе.
2026-08-20 00:52:21 +03:00
Hermes Agent 9f20093bbf fix(app-glasses): панель статуса — по центру в оба глаза, процент цветной
Binocular-контент растягивался на весь 1280 (fillMaxSize внутри
PositionedBox) — панель оказывалась по центру экрана, половина в каждом
глазу. Теперь явная ширина половины (как у видео-Row) — панель по центру
каждой половины, бинокулярно сливается в одну. ConnectionScreen тоже
обёрнут в Binocular. Квадратик-индикатор убран — процент окрашивается
цветом статуса (зелёный/белый/красный). Скрин: обе половины идентичны.
2026-08-20 00:24:12 +03:00
Hermes Agent 3f858c4a1a feat(app-glasses): панель статуса — время + заряд (цвет по статусу)
Поверх видео (GL-оверлей в EglVideoRenderer: bitmap-текстура, правый
верхний угол, обе поверхности) и композ-панель на экране соединения.
Цвета (из удачной панели pair-view StatusComponent): CHARGING/FULL —
зелёный, DISCHARGING — белый, NOT_CHARGING — красный.
Данные: StatusPanelProvider (sticky ACTION_BATTERY_CHANGED + тик 1с,
HH:mm). Тесты 4 (chargeColorArgb). Железо: «00:02 94%» + зелёный
квадрат (очки на зарядке), скрин подтверждён.
2026-08-20 00:03:23 +03:00
Hermes Agent 2b6fdb7148 fix(app-glasses): старт быстрее — mDNS-таймаут 15с → 3с
На RayNeo mDNS мёртв (WiFi режет multicast, 0 пакетов) — 15с уходили
впустую перед сканом /24. Теперь: mDNS 3с → скан ~1с → коннект.
Железо: старт до соединения ~9с (было ~17с).
2026-08-19 23:47:28 +03:00
Hermes Agent cc3dac7a54 fix(app-glasses): фон очков чёрный полностью
ConnectionScreen был без фона — светлый MaterialTheme. Теперь
darkColorScheme + чёрный Box-корень вокруг обоих экранов + страховка
background(Color.Black) в ConnectionScreen. Проверено скрином:
все пиксели #000000.
2026-08-19 23:42:31 +03:00
Hermes Agent 345e7b74fa feat(app-glasses): жесты RayNeo SDK (TempleAction) + отключение звуков трекпада
Перенос из pair-view (проверено на железе): RayNeoARSDK V0.1.3 (aar в
libs/, MercurySDK.init в GlassesApp), MainActivity → BaseEventActivity,
templeActionViewModel.state → команды (Click=пауза/плей, SlideForward/
Backward=±30с), dispatchTouchEvent→onTouchEvent + toggleSoundEffect(0..3,
false) — системные щелчки трекпада отключены.

Фиксы по ходу (железо):
- applyMediaCommand(msg, onDone) — sendPlaybackPositionNow после seekTo
  (иначе телефон получал позицию ДО seek)
- догонка seekTo через 1с на телефоне (первый seekTo в ogg неточен)
- playbackPosition: isPlaying → playWhenReady — ложное playing=false при
  буферизации после seekTo паузило звук на телефоне (~5с «пропажи»);
  pauseIfPlaying тоже по playWhenReady

Тесты: glasses 59+5, phone 71. Железо: клик пауза/плей ✓, свайпы ±30с ✓,
звук при seek не пропадает ✓ (подтверждено пользователем)
2026-08-19 23:38:45 +03:00
Hermes Agent 8182a62885 feat: стабилизация гашения экрана очков (пауза/резюм звука)
Проблемы на железе (RayNeo X2 + HONOR): при гашении экрана позиция
ExoPlayer «набегала» (мусор 42 мин), откат не срабатывал (markPosition
сохранял мусор), доставка паузы телефону 5-10с (RayNeo вырубает
WiFi-радио при screen-off, игнорируя WifiLock).

Решение:
- markPosition(positionMs, trusted): защита от мусорных скачков (>30с за
  цикл = игнор); seek через трекбар помечает позицию trusted (rollback
  откатывает к seek, не к началу)
- rollback при resume: откат к последней достоверной позиции, если
  текущая убежала вперёд >5с
- WifiLock (FULL_HIGH_PERF) при просмотре + ensureWifiLock в точках
  гашения (страховка; RayNeo игнорирует, но не мешает)
- Watchdog на телефоне: очки молчат >5с (AUDIO_SILENCE_PAUSE_MS) → пауза
  звука (гарантированная верхняя граница)
- sendPlaybackPositionNow с повторами (0/2/5с) при включении экрана —
  свежая позиция уходит при пробуждении радио
- Диагностика: события пишутся в files/events.log (logcat очков забит
  EGL-спамом)
- Тесты: +14 (rollback/скачки/trusted seek)
- Железо подтверждено: позиция после гашения идеальная (продолжение с
  места паузы, не в начало), звук пауза ≤5-6с, синк diff < 1000
2026-08-19 22:55:49 +03:00
Hermes Agent 4309d4edd2 feat(app-glasses): гашение экрана очков → пауза видео+звука, включение → продолжение
Требование пользователя (железо): погасил экран очков — звук тоже должен
остановиться (раньше ExoPlayer «тормозил», звук играл, синк дёргал звук
к видео). MainActivity: onPause → pauseIfPlaying (флаг resumeAfterScreenOn),
PlaybackPosition(playing=false) → звук пауза ≤5с; onResume → resume +
PlaybackPosition(playing=true) → звук синхронно. MovieController.resume()
получил guard на существующий плеер. Железо: «экран погас — пауза» →
sync стоп → «экран включён — продолжаю» → diff 247.
2026-08-19 21:56:46 +03:00
Hermes Agent e29bc5032d feat: автоподхват звука после рестарта телефона (очки играют — звук молчит)
Сценарий (железо): телефон перезапущен, очки продолжают играть — звук
не создавался (плеер живёт только по watch). Решение:
- PlaybackPosition теперь несёт itemId+audioIndex (дефолты — совместимость)
- очки: MovieController хранит itemId/audioIndex (playVideo/close), шлёт в позиции
- телефон: onPlaybackPosition → autostartAudio: если itemId есть, аудио не
  активно и не было неудачи — mirrorByItem → resolveAudioSource → play(source,
  positionMs); lastFailed защита от спама каждые 5с
- Тесты: 13 lib-core (сериализация + старый формат), 4 glasses
  (contentId/close), 70/0 phone; железо: рестарт телефона → «audio sync»
  через ~5с (diff 483), подтверждено пользователем
2026-08-19 21:45:23 +03:00
Hermes Agent 8072d0c95a feat(app-phone): трекбар-таймлайн в пульте (seek на любую позицию)
- MediaCommand.SEEK (абсолютный seek на seekMs, поле в протоколе уже было)
- MovieController (очки): SEEK → player.seekTo(seekMs.clamp(0,duration)); чистая seekTargetMs
- GlassesScreen (пульт): material3 Slider под статус-карточкой (виден при durationMs>0),
  локальный стейт драга — время под слайдером следует за пальцем (sliderValue ?: positionMs),
  после отпускания цель держится до подтверждения позиции от очков (±2.5с, таймаут 10с),
  звук: SEEK → audio.seekTo(seekMs)
- Тесты 70/0 phone, 37/0 glasses (MovieControllerSeekTest, сериализация SEEK)
2026-08-19 21:35:20 +03:00
Hermes Agent b4420f446c fix(app-glasses): би-нокулярный рендер RayNeo — letterbox + scale=false (полный кадр в каждый глаз)
Живой тест на RayNeo X2: каждый глаз = 640x480 (пол-экрана 1280x480), видео 896x480 (16:9) растягивалось на весь глаз → «квадратное» (сплющено по горизонтали).
- letterboxRect(videoW,videoH,viewW,viewH): FIT-вьюпорт с полями (страховка для произвольных поверхностей)
- Binocular(scale=true→false): два полных кадра 640x343 в двух половинах экрана (вместо scaleX 0.5 половинки кадра)
- onVideoSizeChanged → videoWidth/Height для letterbox
- Тесты 32/0 (LetterboxRectTest 5)
- Пользователь: «на глаз размер поменялся, будто бы норм»
2026-08-19 21:03:55 +03:00
Hermes Agent 698b6cf6dd feat(app-glasses): авто-обнаружение телефона — mDNS-клиент + скан /24 (RayNeo mDNS мёртв)
- MdnsClient: свой mDNS (UDP 5353, PTR _viewmate._tcp, парсер со сжатием 0xC0), join на всех Wi-Fi-интерфейсах, MulticastLock
- PhoneScan: план Б — TCP-скан своей /24 :8080 батчами, детект по WS-handshake (101), таймаут 5с
- HostConnection: цепочка mDNS → scan /24 (пропуск на эмуляторе) → fallback 10.0.2.2
- app-phone: NsdPublisher (_viewmate._tcp, порт 8080) + NEARBY_WIFI_DEVICES
- Тесты 27/0 glasses (MdnsClientTest, PhoneScanTest), 67/0 phone
- Железо (RayNeo X2 + HONOR): mDNS multicast режется WiFi-стеком очков («ни одного пакета за 15с»), скан /24 нашёл телефон за 1.2с, ws://192.168.76.121:8080, hello→welcome, статусы идут
2026-08-19 20:06:27 +03:00
Hermes Agent 35f462b34a feat: кнопка «⏹ Закрыть» в пульте + автоматика обрыва связи (README «Автоматика»)
- GlassesScreen: кнопка ⏹ Закрыть → sendCommand(CLOSE) (обработчики были: телефон audio.stop, очки MovieController.close)
- app-glasses: MovieController.pauseIfPlaying()/resume(); HostConnection — при обрыве WS пауза + флаг, при восстановлении — continue (лог «связь потеряна — пауза» / «связь восстановлена — продолжаю»); флаг сбрасывается на PlayVideo/CLOSE
- app-phone: ConnectionAutomatics (connectionEvent, авто-resume по флагу «играл ли») + коллектор на hub.connected
- Тесты: app-phone 67/0, app-glasses 15/0
- OFF7 3/3: A5 стоп + обрыв-пауза-продолжение подтверждены живым прогоном
2026-08-19 19:20:04 +03:00
Hermes Agent 49dac8b33f fix(app-phone): звук при watch (диагностика+подстраховка mirrorByItem) + качка не воскресает после удаления
- onWatch: явные логи в каждой точке отказа (watch result / audio source / уже играет / не скачано / mediaDir пуст), свежий mirrorByItem если files не догрузился, звук стартует с позиции очков при уже идущем видео
- resolveAudioSource → resolveAudioSourceDetailed (AudioSourceResult с причиной null)
- DownloadManager: поколения generations — delete() инвалидирует старую корутину (не воскрешает качку), ensureActive перед .part/rename, jobs.remove(itemId, job)
- Тесты 62/0 (в т.ч. поколения — поймали null != 0L)

Инфраструктурный блокер (не код): SeaweedFS на Server3 76.66 в IO-шторме (load ~184, wa 20%, чужой yt-dlp на хосте) — большие GET с s3.binom.pw висят/рвутся с ~14:00; качка на очки FAILED по этой причине.
2026-08-19 17:19:53 +03:00
Hermes Agent 4458678698 feat: бинокулярный видеорендер в app-glasses + иммерсивный режим + тест-план
- render/: Grafika-связка (EglCore/WindowSurface/FullFrameRect/Texture2dProgram/GlUtil/Drawable2d/EglSurfaceBase) — перенос из pair-view
- ui/BinocularVideoPlayer: один SimpleExoPlayer → SurfaceTexture → EGL-блит в оба глаза (EglVideoRenderer шарится между LEFT/RIGHT)
- ui/ViewUtils: Binocular/BinocularScreenSide/BinocularScreenSize (Compose, из pair-view)
- ui/GlassesMovieScreen: «нос к носу», calculateVideoSizeFull, FLAG_KEEP_SCREEN_ON
- MovieController: PlayVideo/MediaCommand (PLAY/PAUSE/TOGGLE/SEEK/CLOSE), доступ к ExoPlayer только с main-потока
- HostConnection: PlayVideo→плеер, реальная позиция в PlaybackPosition (withContext Main)
- MainActivity: иммерсивный режим (setDecorFitsSystemWindows(false) + hide systemBars)
- TESTING.md: 10 тест-кейсов human-style; QA-прогон (opencode, 88.19) — 10/10 ПРОЙДЕН
2026-08-19 04:15:55 +03:00
Hermes Agent 298d8e0b69 feat: сетевая часть для app-phone (WS-сервер) и app-glasses (WS-клиент), PhoneState/PhoneActions → lib-core
lib-core:
- core/phone/: PhoneState, PhoneActions, GlassesSender (интерфейс) — перенесены из app-host
- core/net/: GlassesWsClient — KMP WS-клиент очков (реконнект, Hello, send GlassesToHost)
- GlassesWsClientTest + PhoneActionsTest (перенесён)

app-phone (Android):
- GlassesServer: Ktor WS-сервер (CIO) 0.0.0.0:8080/ws/glasses, принимает очки, Welcome из стейта
- PhoneApp (Application): клиенты, стейт, действия, сервер
- PhoneConfig: креды Jellyfin/Mirror
- MainActivity: статус сервера (порт, счётчик подключений)
- GlassesServerTest: hello→welcome, gesture

app-glasses (Android):
- GlassesApp (Application), HostConnection: WS-клиент к телефону, Hello, реконнект, обработка HostToGlasses (PlayVideo/SetMode и т.д.), StateFlow для UI
- MainActivity: статус подключения

app-host: PhoneActions/PhoneState удалены (теперь lib-core), адаптирован под GlassesSender

171 тест, 0 провалов; app-host жив (catalog/watch/status работают через lib-core)
2026-08-11 15:06:07 +03:00
Hermes Agent 5268a12d20 view-mate: болванка + концепт + эскизы + сценарии (единый коммит, история очищена от hprof/build) 2026-08-11 11:34:50 +03:00