VEU.WathBody/1-docs/4-modules/m13-fenix-data-field/readme.md

188 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# m13-fenix-data-field — план работ (d-dev)
> Модуль: FenixDataField — поле данных Connect IQ (Monkey C)
> «Феникс» в штатном профиле «Силовая»: пульс + зона, метрики подхода
> (Lap→Lap — errata Ф-2.3, см. [module_reqs.md](module_reqs.md)
> разд. 1.1/5), автономный таймер отдыха с вибрацией. Мультитаргет:
> якорь fenix6x 280×280, добавление моделей — строкой таргета.
> Алгоритм: `std1\_rules\d\d-dev.md` (адаптация: домен/функции/БД →
> единицы Monkey C, гейты те же).
> Зависимости: m12 (DONE — канал телефона не затрагивается; errata
> Ф-2.4 об автономном таймере — исполнение здесь). Сервер/телефон —
> без изменений.
| Шаг | Что делаем | Статус |
|---|---|---|
| 0 | Модульные документы: module_reqs.md / readme.md / SKILL.md | ✅ 2026-10-19 |
| 1 | Список единиц (манифест/ядро×3/экран/вибрации/настройки/тесты/доставка) | ✅ 2026-10-19 |
| 1.3 | **Пользователь!** утверждение списка (+ errata Ф-2.3, toolchain, настройки-на-устройстве, таргеты) | ✅ 2026-10-19 (все 5 решений утверждены) |
| 2 | Спецификации: api_specs (манифест/таргеты, ядро×3, экран, настройки/вибрации) | ✅ 2026-10-19 (01-проект/манифест/сборка, 02-ядро SetTracker/RestTimer/ZoneCalc, 03-экран, 04-настройки/вибрации) |
| 2.4 | **Пользователь!** утверждение спецификаций | ✅ 2026-10-19 (утверждены 4 api_specs) |
| 3 | Реализация + тесты + сборка таргетов + симулятор | ✅ 2026-10-19 (сборки 3 таргетов; тесты 54/54; смок-в-симуляторе ограничен средой — примечания 5–6) |
| 3.4 | **Пользователь!** утверждение реализации (закрытие модуля) | ✅ 2026-10-19 («заккомить» — утверждение и команда коммита) |
| 4 | Завершение: план, примечания, волна errata НД | ✅ 2026-10-19 (errata внесена после гейта 1.3 — примечание 2; план/примечания обновлены) |
Статусы: ⬜ не начат · 🔄 в работе · ✅ готов.
## Список единиц (шаг 1)
| # | Единица | Что делает |
|---|---|---|
| 1 | Проект + манифест + таргеты | `3-app\5-watch\FenixDataField`: тип datafield, UUID «Феникс», minApiVersion ≤ 2.1.0; monkey.jungle — матрица таргетов (якорь fenix6x), новая модель — строка |
| 2 | `SetTracker` | метрики подхода: время с границы (Lap), средний пульс интервала (сэмплы из `compute`), обнуление по `onTimerLap`; null-пульс пропускается |
| 3 | `RestTimer` | автономный отдых: старт по Lap, длительность `restSec` (умолчание 120 = `Rest.DefaultSec`), обратный отсчёт, конец → однократная лёгкая вибрация; пауза активности останавливает |
| 4 | `ZoneCalc` | зоны 50/70/85 % от `maxBpm`: `hrMaxBpm` или Танака 208−0,7×`age`; цвет MIP-палитры + текстовая подпись (те же названия, что на «Профиле» m10) |
| 5 | Экран поля | параметрический макет семейства квадратных MIP (якоря, dc-размеры): пульс крупно (`FONT_NUMBER_*`), плашка зоны цветом + подпись, строка «ПОДХОД мм:сс · ср N» / «ОТДЫХ мм:сс», прочерк без пульса |
| 6 | Вибрации | `Attention.vibrate` + `VibeProfile`: конец отдыха — однократная лёгкая; красная зона — двойная, гистерезис (повтор ≥ 60 с, сброс ниже жёлтой) |
| 7 | Настройки поля | properties: `restSec` (30–600, умолчание 120), `hrMaxBpm` (0–250, 0 = формула), `age` (14–100, 45); подписи ru/en; умолчания работают без магазина |
| 8 | Тесты Toybox.Test | ZoneCalc / RestTimer / SetTracker: asserts (компилируются out в release), прогон инструментарием SDK |
| 9 | Сборка и доставка | `monkeyc` по всем таргетам; developer-ключ; инструкция sideload (developer mode + GCM) и приёмки на Fenix 6X — в этом readme |
## Решения на гейт 1.3 (для владельца)
1. **Errata Ф-2.3 / Ф-2.1 — границы подхода.** «Штатного события
Strength Set» в Connect IQ API нет (проверено по актуальному API:
классов повторений не существует ни в одной версии платформы —
родная детекция повторений «Силовой» стороннему коду недоступна).
Рекомендация: границы подхода = между нажатиями **Lap** (штатная
семантика «Силовой»: Lap — конец подхода, метки в `.fit`
сохраняются, КУ-2 жив). «Время подхода» — интервал с последнего
Lap (включает предшествующий отдых; точно «под нагрузкой» не
измерить), «средний пульс подхода» — среднее за интервал.
Отвергнутая альтернатива: эвристика «начало подхода = рост
пульса» — ненадёжна (зал, интервалы отдыха разные). Пакет errata
НД — module_reqs разд. 5, вносится после «утверждаю».
2. **Toolchain.** Агент устанавливает Connect IQ SDK для Windows
(актуальная 9.2; SDK Manager — zip с developer.garmin.com) в
`C:\tools` по прецеденту m09 (JDK 21 уже стоит). Сборка — CLI
`monkeyc`; смок — симулятор SDK; приёмка — реальный Fenix 6X
(ui-ux: симулятор только для разработки).
3. **Настройки поля на устройстве.** Стандартные properties поля
(`restSec`, `hrMaxBpm`, `age`) на реальном устройстве
редактируются только через Garmin Connect Mobile/Express **для
приложений из магазина Connect IQ**; sideload-дев-сборка живёт на
зашитых умолчаниях (120 с и т.д.). Смена поведения в v1 =
пересборка агентом. Опция владельца: публикация поля приватной
бетой в магазине (бесплатный Garmin-аккаунт разработчика) — даёт
правку настроек в GCM; в границы v1 не входит.
4. **Таргетная матрица v1.** Собираем только `fenix6x` (якорь
владельца); макет параметрический на семейство квадратных MIP —
fenix 6 (260×260) / 6S (240×240) добавляются строкой таргета без
правок кода (tech-req ОВ-7 «по фактическому парку», указание
2026-10-19 о лёгкой сборке под другие часы).
5. **Красная зона на часах — процентная** (Ф-2.2: > 85 % от
`maxBpm` поля), а не абсолютный `Safety.HrStrengthMax` = 120
телефона: часам абсолютный порог неизвестен (передать нельзя —
канал односторонний), по НД у часов процентная норма. Зоны
настраиваются вручную в поле (`hrMaxBpm`/`age`), синхронизация с
профилем телефона — открытый вопрос 3 (v2).
## Примечания по ходу работ
0. **Исследование API (2026-10-19, веб)** — обоснование errata
Ф-2.3, см. module_reqs разд. 1.1: `Toybox.Activity` — 6 классов,
повторений нет нигде (вплоть до актуального API платформы);
у DataField есть `onTimerLap()` (API 1.3.0), `compute()` раз/с
(`currentHeartRate` с 1.0.0), `Attention.vibrate` (1.0.0);
`Toybox.Test` — с API 2.1.0, asserts компилируются out в release.
SDK: актуальная 9.2, Windows SDK Manager
`developer.garmin.com/downloads/connect-iq/sdk-manager/connectiq-sdk-manager-windows.zip`.
1. Расположение проекта — `3-app\5-watch\FenixDataField` (продолжение
нумерации 1-lib/2-cli/3-web/4-phone); «5-watch» — место будущих
полей/приложений под другие часы (мультичасовая инфраструктура).
2. **Волна errata НД внесена (2026-10-19, после «утверждаю» гейта
1.3):** requirements Ф-2.1 («время с последнего Lap — точное
„под нагрузкой“ не измеряется») и Ф-2.3 (границы подхода =
Lap→Lap, события Strength Set в CIQ API нет); Ф-2.2 — правка не
потребовалась (норма уже содержит «настройки поля», открытый
вопрос 3); tech-req 7 п. 1 — дополнено «вибрация конца отдыха и
метрики Lap→Lap — приёмка m13». Версии документов не поднимались
(ближайшее ревью). UUID поля зафиксирован в спеке 01:
`34f470ff-b5d1-4141-ad06-c483fa79a1f8`.
3. **Тулчейн фактически (2026-10-19).** Рабочая связка: **SDK 4.2.2**
(Wayback-архив zip; актуальная 9.2 скачана, но её симулятор
отвергает dev-подписи нашего пака — примечание 7) + JDK 21 +
девайс-паки fenix6xpro/fenix6/fenix6s из docker-образа
`piccobit/docker-connectiq:4.1.7` → `%APPDATA%\Garmin\ConnectIQ\Devices`
(путь виндового стора найден декомпиляцией Monkeybrains).
Идентификатор таргета — **fenix6xpro** (6X существует только в
Pro-линии). Ключ разработчика: **RSA 4096**, PKCS#8 DER
(`C:\Users\virus-home\.watchbody\ciq\developer_key.der`; PEM и
2048-битные отвергаются — см. примечание 4).
4. **Developer-ключ обязан быть RSA 4096.** Симуляционная прошивка
отвергала 2048-битную подпись «Signature check failed» (проверено
на штатном сэмпле Garmin — падает одинаково; JDK ни при чём:
сборка под Java 11 в docker даёт то же). В `DeveloperSignature`
(monkeybrains.jar) модуль = 512 байт = 4096 бит. Ключ
перегенерирован 2026-10-19; старый 2048 сохранён как
`developer_key2048.obsolete.der` — подписи им больше не собираются.
5. **Тесты: собственный раннер вместо `monkeydo /t`.** Штатный
(:test)-harness Toybox.Test не стартует в связке «симулятор 4.2.2 +
девайс-пак docker»: ЛЮБОЙ тестовый prg (включая минимальный проект
из 15 строк) падает на старте «Unexpected Type Error: Failed to
start CIQ Application»; поле данных, к тому же, нельзя запускать
как приложение. Решение: `test-source/` — watch-app-вход
`TestEntry` (test-manifest.xml, тип watch-app) + `CoreChecks`
(счётчик/печать) + те же 13 тестовых функций из спеки 02;
тестовая сборка `monkey-test.jungle` (без --unit-test), прогон —
`monkeydo` без /t, вывод println → консоль. Итог: **54/54
проверок** (зоны 12, метрики подхода 6, отдых 10, вибрации 7,
формат 10, клампы 9 — состав уточнён против спеки 02 числом
проверок, случаи те же). Раннер отработал как положено: нашёл
3 реальных бага ядра (шкала порогов ZoneCalc ×10, лишнее слагаемое
в округлении avgBpm, сборка строки mmss не со строки) — исправлены,
повторный прогон зелёный. Поле данных (release, monkey.jungle) в
тестовые сборки не входит. Хвостовой «app crash» после RESULT —
штатный выход watch-app без view, на результаты не влияет.
6. **Смок поля в симуляторе — ограничение среды.** Симулятор 4.2.2 не
хостит поля данных в симулируемой активности (поле нельзя
«открыть» как приложение), поэтому интерактивные проверки спеки 03
§5 (проводка пульса по зонам, Lap, пауза, вибрации глазами) —
приёмка владельца на реальном Fenix 6X (прецедент m12: «на
устройстве — приёмка владельца»). Поведенческое ядро этих проверок
закрыто юнит-тестами: пороги всех 4 зон (границы 89/90, 125/126,
152/153 при max 180), прочерк без пульса (ZONE_NONE), обнуление по
Lap + отсчёт отдыха + однократность сигнала, гистерезис красной
60 с, целочисленный mmss. Отрисовка — параметрическая, собирается
под 280×280/260×260/240×240 без правок кода.
7. **Мультитаргет-инвариант подтверждён:** fenix6 (260×260) и fenix6s
(240×240) — release и тестовые prg собираются только заменой
`<iq:product>` в манифесте (правки кода — нулевые; проверочные
манифесты во временной папке, в репо не попадают). Симулятор 9.2 в
контуре не используется (отвергает подписи нашего пака); если
понадобится — отдельное исследование (sdk-settings.json симулятора).
8. **Monkey C-ловушки, пойманные тестами:** `==` для строк — по ссылке
(литерал ≠ собранная строка; сверять `Lang.String.equals`);
`число + строка` не склеивается — конкатенация только со строки
(`"" + x + …`); дефолтная джунгла компилирует `.\**.mc` рекурсивно
— джунглы проекта обязаны задавать `base.sourcePath` явно (иначе
test-source попадает в release).
9. **Сборка/прогон (команды).** Сборка: `monkeyc.bat -d fenix6xpro -w
-f monkey.jungle -y <ключ> -o bin\FenixDataField.prg` (тест —
`-f monkey-test.jungle … FenixDataField-test.prg`; JAVA_HOME =
JDK 21). Симулятор: `bin\connectiq.bat` (запуск вне песочницы),
затем `monkeydo.bat <prg> fenix6xpro` (без /t) — юнит-вывод в
консоль; лог прошивки —
`%TEMP%\com.garmin.connectiq\GARMIN\APPS\LOGS\CIQ_LOG.YML`.
## Sideload на Fenix 6X (приёмка владельца)
1. На часах включить режим разработчика: **Settings → System →
Developer Mode → On** (перезагрузка по запросу).
2. Подключить часы по USB (режим MTP), открыть диск **GARMIN**.
3. Скопировать `FenixDataField.prg` в **`GARMIN\APPS\DataField\`**
(если папки нет — создать; вариант совместимости —
`GARMIN\APPS\WatchApp\`).
4. Отсоединить USB. Профиль **Strength (Силовая)** → настройки
тренировки → Data Screens → страница → Data Field 1 → **Феникс**
(Phoenix).
5. Проверки: пульс крупно + плашка зоны с подписью; Lap (кнопка
BACK/LAP на 6X) — строка «ОТДЫХ 2:00» тикает вниз, по нулю —
короткая вибрация; пульс > 85 % maxBpm (умолчание Танака 45 лет =
176) — двойная вибрация не чаще раза в минуту; пауза активности —
отсчёты стоят; без нагрудного датчика/оптического пульса — «—»,
поле не падает. Настройки `restSec`/`hrMaxBpm`/`age` в dev-сборке —
зашитые умолчания (решение 3 гейта 1.3).
6. Удаление: часы → Settings → System → Storage → Connect IQ /
`GARMIN\APPS\DataField\FenixDataField.prg` вручную.