201 lines
16 KiB
Markdown
201 lines
16 KiB
Markdown
# Требования к модулю: m12-watch-channel
|
||
|
||
> Модуль — **WatchChannel** телефона `Phone.WatchBody` (архитектура
|
||
> разд. 1.2): приём live-пульса с часов Fenix 6X по штатной трансляции
|
||
> BLE (Heart Rate Service), статус связи, заряд (если передаётся),
|
||
> подача в экран «Тренировка» (m10), «Допуск» и запись среднего
|
||
> пульса тренировки (`avg_hr`, цепочка m09). Самодостаточная копия
|
||
> требований (d-dev-6). Источники: Ф-3.1–Ф-3.3 (статусная строка,
|
||
> пульс, таймер), Ф-13.2 (пульс сидя «с часов, если связаны»),
|
||
> Ф-2.4 (вибрация отдыха — errata, см. разд. 1.1), ОВ-3 (tech-req
|
||
> 3 п. 3, прототип 7 п. 1), ui-ux разд. 2.
|
||
|
||
## 1. Назначение и границы
|
||
|
||
Live-пульс на телефоне во время тренировки: часы Fenix 6X транслируют
|
||
пульс штатной функцией «Broadcast Heart Rate», телефон принимает и
|
||
показывает; отсутствие связи ничего не ломает (🛡 Ф-2.4, Ф-3.2 —
|
||
деградация молча, прочерки).
|
||
|
||
### 1.1. Транспорт: обоснование (errata ОВ-3)
|
||
|
||
ОВ-3 (tech-req 3 п. 3) предполагал companion-канал Connect IQ
|
||
Communications. Исследование 2026-10-19 показало, что на якорных
|
||
часах он нереализуем для живого пульса:
|
||
|
||
1. **Поля данных не имеют `Toybox.Communications` до API 5.0.0**,
|
||
а Fenix 6X навсегда остался на CIQ API ≤ 4.1.7 — поле данных
|
||
«Силовая» (Ф-2, m13) не может ни передавать, ни принимать.
|
||
2. **CIQ-приложение** (Communications доступен) не может работать
|
||
одновременно со штатной активностью «Силовая» — живой пульс
|
||
во время тренировки из приложения не транслировать; companion
|
||
SDK (`com.garmin.connectiq:ciq-companion-app-sdk`) для v1
|
||
неприменим.
|
||
3. **Штатная трансляция «Broadcast Heart Rate»** на fenix 6 серии
|
||
с прошивки июня 2020 идёт одновременно по ANT+ и BLE
|
||
(стандартный Heart Rate Service 0x180D; подтверждено на
|
||
Pro-линии — 6/6S/6X Pro; у базовой «шестёрки» 2022 г. сообщали
|
||
только ANT+). BLE-приём работает на любом Android без Garmin
|
||
Connect Mobile и без ANT-железа; канал односторонний
|
||
(часы → телефон).
|
||
4. Следствие для **Ф-2.4** (вибрация конца отдыха «по таймеру,
|
||
переданному с телефона»): передать секунды отдыха на часы
|
||
в v1 нечем — таймер отдыха на часах делает m13 **автономно**
|
||
(длительность — настройка поля данных в Garmin Connect Mobile,
|
||
умолчание = `Rest.DefaultSec` телефона). Таймер телефона (Ф-3.3)
|
||
работает независимо, как сейчас. Errata НД — разд. 5.
|
||
|
||
Критерий прототипа ОВ-3 (tech-req 7 п. 1 «пульс и таймер доходят
|
||
при экране часов активным») исполняется в части «пульс доходит»
|
||
(проверка на устройстве владельца); часть про таймер переносится
|
||
в m13 как автономная вибрация (errata 7 п. 1).
|
||
|
||
**В границах модуля:**
|
||
|
||
- **транспорт-интерфейс** `WatchTransport` + первая реализация —
|
||
**BLE-клиент** `HrBleClient` (требование владельца при утверждении
|
||
гейта 1.3, 2026-10-19: инфраструктура должна поддерживать и другие
|
||
умные часы): скан по UUID Heart Rate Service
|
||
(0x180D), подключение, подписка на изменения характеристики
|
||
Measurement (0x2A37, indications), разбор пакета (флаги: разрядность
|
||
8/16 бит, поля Energy/RR пропускаются), автопереподключение при
|
||
потере, чтение Battery Service (0x180F) при его наличии — иначе
|
||
заряд не показывается (Ф-3.1 «если передаётся»); интерфейс —
|
||
для unit-тестов на фейках и для будущих транспортов (CIQ 5+,
|
||
ANT+) без правок фасада и UI; только стандартные профили GATT,
|
||
платформо-специфичных UUID/имён/логики в коде нет — любые часы,
|
||
транслирующие стандартный Heart Rate Service, работают без
|
||
изменений кода; платформо-специчное — только тексты подсказок
|
||
включения трансляции в «Настройках» (расширяемый справочник
|
||
по производителям, v1 — Garmin);
|
||
- **буфер пульса** `HrSampleStore` (чистый, в памяти): сэмплы
|
||
(bpm, `t_offset_s` от старта тренировки), текущее значение,
|
||
среднее за тренировку, серия «последний подход» (сэмплы от
|
||
последнего записанного подхода до текущего момента — Ф-3.2
|
||
«линия за последний подход»), устаревание (> 10 с без сэмплов —
|
||
«нет данных», прочерк);
|
||
- **фасад** `WatchChannel`: состояние канала — `StateFlow` со
|
||
статусом (не выбраны / скан / связь есть / потеряна / Bluetooth
|
||
выключен / нет разрешения 🛡 текстом), текущим пульсом, зарядом,
|
||
серией последнего подхода и средним; жизненный цикл: канал активен,
|
||
пока открыты экраны «Тренировка» или «Допуск» (v1 — без
|
||
foreground-сервиса; пульс считается за время открытого экрана —
|
||
ограничение решения 5 гейта 1.3);
|
||
- **выбор устройства**: в «Настройках» блок «Часы» — скан, список
|
||
найденных пульс-датчиков (имя + адрес), выбор сохраняется
|
||
(`Watch.HrDeviceAddress` в DataStore); не задано — статус «часы
|
||
не выбраны», канал не сканирует; сброс выбора;
|
||
- **экран «Тренировка»** (интеграция m10, замена заглушек
|
||
WorkoutScreen: статус-строка): связь часов — зелёный/красный/
|
||
серый, дублируется текстом («часы: связь есть (заряд 80 %)» /
|
||
«часы: нет связи» / «часы: не выбраны»); пульс — крупное текущее
|
||
значение, окраска по зонам Танака (`ZoneCalculator` m10, границы
|
||
из `Hr.MaxBpm`), под значением — линия последнего подхода
|
||
(Canvas, серия буфера); при отсутствии/устаревании — прочерк,
|
||
голос и ручной ввод работают (Ф-1.1, Ф-3.2);
|
||
- **финиш тренировки**: `WorkoutCore.finishWorkout(avgHr = среднее
|
||
буфера)` — готовая цепочка m09 (`workout.avg_hr` → sync);
|
||
канал сам в БД не пишет;
|
||
- **экран «Допуск»** (Ф-13.2): если канал активен и есть свежий
|
||
сэмпл (≤ 30 с) — кнопка «взять с часов» подставляет пульс сидя
|
||
в поле допуска; иначе — ручной ввод пикером (как в m10);
|
||
- **разрешения Bluetooth**: Android 12+ — `BLUETOOTH_SCAN`
|
||
(neverForLocation) + `BLUETOOTH_CONNECT` (runtime); Android
|
||
8–11 — `ACCESS_FINE_LOCATION` (runtime) + `BLUETOOTH`/
|
||
`BLUETOOTH_ADMIN` (манифест); отказ в разрешении — статус
|
||
«нет разрешения», экран не блокируется (🛡);
|
||
- unit-тесты (pure JVM): разбор 0x2A37 (фикстуры байтов: 8/16 бит,
|
||
с полями Energy/RR), буфер (среднее, серия последнего подхода,
|
||
устаревание), статусные переходы фасада на фейках клиента.
|
||
|
||
**Вне границ модуля:** m13 поле данных (Monkey C — отображение,
|
||
зоны, автономный таймер отдыха с настройкой длительности;
|
||
мультитаргет-сборка по моделям — там же), дополнительные
|
||
реализации `WatchTransport` — ANT+-приём (плагин Garmin, нужен
|
||
ANT-транспорт в телефоне) и CIQ companion (часы с CIQ 5+) —
|
||
кандидаты v2 по мере появления устройств, foreground-фон пульса
|
||
при погашенном экране (v2),
|
||
запись live-сэмплов в LocalDb/sync (история пульса — из .fit,
|
||
m07/m08), пульсограмма всей тренировки на телефоне (Ф-5 —
|
||
карточка факт из .fit на сервере m08), wake-слова, смена устройства
|
||
голосом.
|
||
|
||
## 2. Решения интерфейса (ui-ux разд. 2, tech-req 6)
|
||
|
||
- Статусная строка часов не конкурирует с пульсом: статус — мелкой
|
||
строкой сверху, пульс — крупным значением (≥ 40 sp) в зоне пульса;
|
||
- статус и доступность связи дублируются текстом (🛡 цвет — только
|
||
дублирование, не единственный носитель);
|
||
- линия последнего подхода — без сеток и подписей, минимум чернил;
|
||
значение пульса — цифрой всегда;
|
||
- «взять с часов» на допуске — вторичная кнопка рядом с полем
|
||
«Пульс сидя», после нажатия значение видно в поле (правка
|
||
пикером сохраняется);
|
||
- блок «Часы» в «Настройках» — по образцу блока «Распознавание
|
||
речи» (m11): статус, действие, инструкция текстом («включите
|
||
Broadcast Heart Rate на часах»).
|
||
|
||
## 3. Инварианты (🛡 — нарушать запрещено)
|
||
|
||
1. **Офлайн-first**: приём пульса — локальный BLE, сеть и облако
|
||
не используются; сервер не участвует (БЦ-3, Ф-1.1).
|
||
2. **Деградация молча** (Ф-2.4, Ф-3.2): отсутствие часов, потери
|
||
связи, отказ разрешения, выключенный Bluetooth — только прочерки
|
||
и статус; тренировка, голос, ручной ввод и sync не блокируются.
|
||
3. **Канал не пишет в БД**: единственная точка записи —
|
||
`WorkoutCore.finishWorkout(avgHr)` (единственный писатель
|
||
LocalDb — инвариант m09); live-сэмплы никуда не сохраняются.
|
||
4. **Часы не зависят от телефона** (Ф-2.4 «не зависимость»):
|
||
телефон ничего не «требует» от часов; трансляцию включает сам
|
||
атлет на часах (инструкция в «Настройках»).
|
||
5. **Тайны**: BLE-адрес устройства хранится только в DataStore
|
||
телефона; в лог/журнал команд адреса не пишутся (З-6).
|
||
6. **Платформо-независимость канала** (указание владельца
|
||
2026-10-19): любые часы, транслирующие стандартный Heart Rate
|
||
Service, работают без изменений кода; Garmin/fenix-специфика
|
||
в коде запрещена; новые транспорты подключаются реализацией
|
||
`WatchTransport` без правок фасада, UI и БД; то же требование
|
||
наследуется m13 — мультитаргет-сборка поля данных по моделям
|
||
(vision 1.1, Ф-2.8).
|
||
|
||
## 4. Критерии приёмки модуля
|
||
|
||
1. `assembleDebug` собирается; unit-тесты (все, включая m09–m11)
|
||
зелёные.
|
||
2. Приёмка на устройстве владельца (Fenix 6X, см. инструкцию
|
||
в readme): включить «Broadcast Heart Rate» → «Настройки → Часы»
|
||
→ скан → выбрать часы → экран «Тренировка»: статус «связь есть»,
|
||
пульс обновляется ≈1 Гц при активном экране часов, линия
|
||
последнего подхода рисуется; финиш → `avg_hr` в записи
|
||
(История на телефоне, карточка в консоли m08 после sync);
|
||
«Допуск» → «взять с часов» подставляет пульс.
|
||
3. Отрицательные исходы: часы не выбраны / трансляция выключена /
|
||
Bluetooth выключен / отказ разрешения — прочерки и статус
|
||
текстом, приложение работает (ручной смок владельца).
|
||
4. Прототип ОВ-3 (tech-req 7 п. 1) — в части «пульс доходит при
|
||
экране часов активным»: подтверждается пунктом 2; фиксируется
|
||
в журнале приёмки владельца; часть «таймер» — errata, m13.
|
||
|
||
## 5. Errata НД (по утверждению гейта 1.3, решение 2; ВНЕСЕНА 2026-10-19)
|
||
|
||
- requirements **Ф-2.4**: «вибрация — конец таймера отдыха,
|
||
переданного с телефона» → «вибрация — конец автономного таймера
|
||
отдыха поля; длительность — настройка поля в Garmin Connect
|
||
(умолчание `Rest.DefaultSec`); передача с телефона — v2
|
||
(кандидат при смене часов на CIQ 5+)»;
|
||
- requirements **Ф-3.2**: источник пульса — «штатная трансляция
|
||
пульса часов (BLE Heart Rate Service; ОВ-3 закрыт: companion-
|
||
канал Connect IQ на fenix 6X недоступен для полей данных)»;
|
||
- tech-requirements **3 п. 3 (ОВ-3)**: описание канала фактически =
|
||
односторонняя штатная трансляция «Broadcast Heart Rate»
|
||
(BLE/ANT+), Garmin Connect Mobile для пульса не нужен;
|
||
- tech-requirements **7 п. 1**: критерий прототипа — «пульс
|
||
доходит на телефон при активном экране часов» (таймер —
|
||
автономный, проверяется в m13);
|
||
- architecture **1.1/1.2/3** (телефон↔часы): канал = BLE-приём
|
||
штатной трансляции, односторонний.
|
||
|
||
Волна правок НД вносится после «утверждаю» по гейту 1.3 одним
|
||
пакетом (без подъёма версий документов — errata в телах разделов,
|
||
версии поднимутся на ближайшем ревью).
|