# 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 собираются только заменой `` в манифесте (правки кода — нулевые; проверочные манифесты во временной папке, в репо не попадают). Симулятор 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 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` вручную.