# m11-voice-input — план работ (d-dev) > Модуль: VoiceInput — офлайн-STT (Vosk) + грамматика Ф-1.2 + > TTS-подтверждения + интеграция в экран «Тренировка». > Алгоритм: `std1\_rules\d\d-dev.md`. Требования: > [module_reqs.md](module_reqs.md). > Зависимости: m09 (WorkoutCore/LocalDb/SyncQueue — DONE), m10 > (экран «Тренировка», таймер отдыха, «Настройки» — DONE); часы — m12. | Шаг | Что делаем | Статус | |---|---|---| | 0 | Модульные документы: module_reqs.md / readme.md / SKILL.md | ✅ 2026-10-19 | | 1 | Список единиц (парсер/движок/модель/исполнитель/UI/тесты) | ✅ 2026-10-19 | | 1.3 | **Пользователь!** утверждение списка (+ решения по объёму v1) | ✅ 2026-10-19 | | 2 | Спецификации: api_specs (парсер, движок/модель, исполнитель, UI) | ✅ 2026-10-19 | | 2.4 | **Пользователь!** утверждение спецификаций | ✅ 2026-10-19 | | 3 | Реализация + тесты + сборка | ✅ 2026-10-19 | | 3.4 | **Пользователь!** утверждение реализации (закрытие модуля) | ✅ 2026-10-19 | | 4 | Завершение: план, примечания | ✅ 2026-10-19 | Статусы: ⬜ не начат · 🔄 в работе · ✅ готов. ## Список единиц (шаг 1) | # | Единица | Что делает | |---|---|---| | 1 | `VoiceCommandParser` (+ `NumberRu`) | чистая функция: текст → `VoiceCommand` (Approach/Weight/Rest/Rpe) или `Unknown`; числа словами 0–999 и цифрами, «с половиной» → +0,5; диапазоны Ф-1.2; semantics для журнала | | 2 | `SttEngine`/`VoskEngine` | обёртка Vosk `SpeechService`: инициализация модели из `filesDir`, старт/стоп прослушивания, финальные гипотезы в колбэк; опциональный `setGrammar` (JSON-словарь); интерфейс — для тестов | | 3 | `ModelStore` | загрузка zip модели по `Voice.ModelUrl` (OkHttp, уже в проекте), распаковка в `filesDir`, идемпотентность, статус (нет/загружена, МБ) | | 4 | `VoiceCommandExecutor` | применение команды: Approach → `logSet(source=voice)`, Weight → рабочий вес экрана, Rest → старт таймера m10, Rpe → `WorkoutCore.setLastSetRpe`; журнал `logCommand` (принято/отклонено 🛡); будит SyncWorker | | 5 | `TtsFeedback` | системный Android TTS (ru-RU) + ToneGenerator (щелчок/сигнал ошибки); деградация без TTS (текст на экране) | | 6 | UI «Тренировка» | кнопка микрофона (toggle, индикатор «Слушаю…»), runtime-разрешение `RECORD_AUDIO`, строка последней команды, «текущее упражнение» (тап в блоке плана), рабочий вес от «Вес W» | | 7 | UI «Настройки» | блок «Распознавание речи»: статус модели, «Загрузить», «Проверить TTS»; `Voice.ModelUrl` | | 8 | `WorkoutCore.setLastSetRpe` | расширение m09 (errata, как `renameExercise` в m10): RPE последнего подхода + повторная отправка в sync | | 9 | unit-тесты | парсер (4 команды × формы чисел, границы, мусор), NumberRu, исполнитель на фейках | ## Решения на гейт 1.3 (для владельца) 1. **Доставка модели Vosk** (~45 МБ, `vosk-model-small-ru-0.22`): рекомендуем (а) — загрузка zip по https при первом включении микрофона / из «Настроек» (`Voice.ModelUrl`, умолчание alphacephei.com; можно указать свой сервер), модель живёт в `filesDir`, в APK и репозиторий не входит. Альтернативы: (б) вшить в APK assets (+45 МБ и бинарник в git); (в) ручной сайдлоад adb. 2. **Текущее упражнение для голоса** — выбор тапом в блоке плана (умолчание — первый пункт плана); голосовой выбор упражнения по имени в v1 отсутствует (имена произвольные, распознавание ненадёжно). Форма ручного ввода m10 сохраняется (правка ≤ 2 касаний, Ф-1.6). 3. **«РПЭ N»** — новый метод `WorkoutCore.setLastSetRpe` (расширение m09 по образцу `renameExercise`: обновление строки + повторный enqueue; сервер идемпотентен по client_guid). 4. **Грамматика Vosk** — `setGrammar` применяется при поддержке моделью; обязательна строгая постобработка парсером (малые модели не гарантируют грамматику; лишнее отклоняется по Ф-1.3). 5. **TTS** — системный Android TTS; на устройстве владельца должен быть ru-голос (Google TTS); при отсутствии — деградация до щелчка/сигнала + текст на экране. ## Примечания по ходу работ 1. **Состав реализации** (`app/src/main/java/com/watchbody/phone/voice/`): `VoiceCommandParser.kt` (sealed VoiceCommand + NumberRu 0–999 + tokenize с «эр пэ э»→«рпэ» + semantics), `VoskResult.kt` (разбор JSON Vosk), `SttEngine.kt` (интерфейс + VoskEngine: грамматика-список слов с откатом на свободный разбор, n-best 3), `ModelStore.kt` (zip→filesDir, zip-slip, single-root срезка, no-op при готовой модели), `VoiceCommandExecutor.kt` (VoiceCoreGateway/эффекты/отклонения), `TtsFeedback.kt` (TTS ru + ToneGenerator BEEP/NACK, деградация). Расширения: `WorkoutCore.setLastSetRpe` + DAO `lastSet` (m09), `AppSettings.voiceModelUrl` + валидатор https (m10), контейнер (modelStore/ttsFeedback), WorkoutScreen (Mic-кнопка, RECORD_AUDIO, диалог модели, «текущее упражнение» тапом в плане, строка голоса), SettingsScreen (блок «Распознавание речи»: URL/статус/загрузка/ «Проверить TTS»), манифест RECORD_AUDIO, gradle: vosk-android 0.3.47 + icons-extended + MockWebServer (test). 2. **Сборка/тесты**: BUILD SUCCESSFUL; APK 57,3 МБ (рост с 12 МБ — JNI Vosk по 4 ABI + icons-extended; debug без минификации — release c R8/abiFilters сожмёт, кандидат оптимизации); unit **63/63** (m09 18 + m10 18 + m11 27: парсер 9, исполнитель 10, модель+валидатор 8). 3. **Живой URL модели проверен** (2026-10-19): alphacephei.com → 200 OK, 46 236 750 байт (~44 МБ) — умолчание `Voice.ModelUrl` рабочее. 4. **Errata API vosk-android 0.3.47** (вскрыты компиляцией, spec 02 скорректирован по факту): `SpeechService` управляется `stop()`/ `shutdown()` (stopListening/destroy нет); `RecognitionListener` — 5 методов, команды приходят в `onResult` (onFinalResult — при остановке); сигнал ошибки — `TONE_PROP_NACK` (TONE_PROP_ERR нет). 5. **Решения по ходу**: парсер в approach при out_of_range пишет ОБА значения ({"weight_kg":100.0,"reps":101} — информативнее для разбора журнала); `syncRequest`_executor срабатывает после ЛЮБОЙ журналируемой команды (журнал отклонённых тоже уходит в sync; spec 03 п.8 уточнён по смыслу); инициализация Model Vosk — в Dispatchers.IO (не main). 6. **Не проверено в среде** (нет устройства/микрофона): живое распознавание, TTS-озвучка, щелчки/сигналы, загрузка модели на устройстве, точность КУ-1 — критерии 2/4 приёмки на стороне владельца; сетевой контур модели проверен MockWebServer + живым URL.