6.5 KiB
Спецификация 02: ядро — SetTracker / RestTimer / ZoneCalc
Источники: module_reqs разд. 1 (единицы 2–4), Ф-2.1–Ф-2.4 (с errata Ф-2.3), решение гейта 1.3 № 1/5. Чистые классы Monkey C без UI и без обращений к Toybox.WatchUi — тестируются Toybox.Test.
Общее для всех: часы ядра — Activity.Info.timerTime (активное
время активности, мс; при паузе стоит — таймеры поля замирают сами,
отдельная обработка паузы не нужна). Значения времени приходят
извне (параметрами) — ядро системное время не читает.
1. ZoneCalc — зоны пульса
Вход: maxBpmFor(hrMaxBpm, age):
hrMaxBpm > 0→hrMaxBpm(ручной ввод);- иначе формула Танаки:
208 − 0,7 × age, округление до целого вверх (Math.ceil);ageвне 14–100 → умолчание 45.
zoneFor(bpm, maxBpm) → зона:
| Зона | Диапазон от maxBpm | Цвет (MIP) | Подпись ru / en |
|---|---|---|---|
| BLUE | bpm < 50 % | Graphics.COLOR_BLUE | «восстановление» / «recovery» |
| GREEN | 50 % ≤ bpm < 70 % | COLOR_GREEN | «лёгкая» / «easy» |
| YELLOW | 70 % ≤ bpm < 85 % | COLOR_YELLOW | «средняя» / «moderate» |
| RED | 85 % ≤ bpm | COLOR_RED | «высокая» / «high» |
- сравнение целочисленное без Float:
bpm*2 vs maxBpm(50 %),bpm*10 vs maxBpm*7(70 %),bpm*20 vs maxBpm*17(85 %); bpm == nullили ≤ 0 → зона NONE (серый, прочерк) — «нет данных»;- подписи зон совпадают с «Профилем» телефона m10 (согласованный словарь системы).
2. SetTracker — метрики подхода (границы Lap→Lap, errata Ф-2.3)
Состояние: lapAtMs (timerTime последнего Lap), sumBpm, nBpm.
onLap(timerTimeMs):lapAtMs = timerTimeMs; sumBpm = 0; nBpm = 0— метрики обнуляются на границе (Ф-2.3);onSample(bpm):bpm != null && bpm > 0→sumBpm += bpm; nBpm += 1; null-пульс пропускается (в среднее не попадает);elapsedMs(timerTimeMs) as Long:timerTimeMs − lapAtMs;avgBpm() as Number or Null:nBpm > 0→ округление(sumBpm + nBpm/2) / nBpm, иначе null («—» до первого сэмпла);- стартовая граница —
onTimerStartактивности (lapAtMs= 0): метрики считаются с начала активности до первого Lap.
«Время подхода» = elapsedMs (включает предшествующий отдых —
errata Ф-2.1); «средний пульс подхода» = avgBpm за тот же
интервал.
3. RestTimer — автономный таймер отдыха (Ф-2.4 errata)
Состояние: active, restEndMs, fired.
start(timerTimeMs, restSec):active = true; restEndMs = timerTimeMs + restSec*1000; fired = false;remainingMs(timerTimeMs) as Long:active→restEndMs − timerTimeMs, иначе 0;isResting(timerTimeMs):active && remainingMs > 0— на экране строка «ОТДЫХ mm:ss» (остаток);onEnd(timerTimeMs):active && remainingMs ≤ 0 && !fired→fired = true, вернутьtrue— сигнал на однократную лёгкую вибрацию (спека 04); повторные вызовыfalse;- пауза активности:
timerTimeстоит → остаток не меняется (отдых «заморожен», Ф-2.6 штатная пауза); отдельной логики нет; - до первого Lap таймер не активен (
active = falseпри старте активности).
Умолчание длительности — FieldSettings.restSec (120 =
Rest.DefaultSec телефона, спека 04).
4. Vibe — паттерны и гистерезис (спека 04 — параметры)
Красная зона: onZone(zone, timerTimeMs):
- зона RED и прошло ≥ 60 000 мс от последней двойной вибрации → сигнал двойной вибрации, фиксируется время;
- выход из RED (зона GREEN/BLUE/NONE) → гистерезис сброшен — следующее входжение в RED вибрирует сразу.
5. Тесты (Toybox.Test, test\)
ZoneCalcTest:
- Танака:
maxBpmFor(0, 45) == 177(208 − 31,5 = 176,5 → ceil); - ручной приоритет:
maxBpmFor(190, 45) == 190; - возраст вне диапазона → расчёт от 45 (
maxBpmFor(0, 5) == 177); - границы зон при maxBpm 180: 89 → BLUE, 90 → GREEN, 125 → GREEN, 126 → YELLOW, 152 → YELLOW, 153 → RED (50 % = 90, 70 % = 126, 85 % = 153 — целочисленные пороги);
zoneFor(null, 180) == NONE.
SetTrackerTest:
- среднее округление: сэмплы [100, 101, 101] → 101;
- null-сэмплы не влияют: [100, null, 102] → 101;
onLapобнуляет: сэмплы до/после Lap — среднее только после;elapsedMsот границы: Lap на 60 000, сейчас 75 500 → 15 500;- до сэмплов
avgBpm() == null.
RestTimerTest:
- старт по Lap на t=60 000, restSec 120:
remainingMs(60 000)==120 000,remainingMs(120 000) == 60 000,remainingMs(180 000) == 0; onEndсрабатывает однократно: false при остатке > 0; true при 180 000; повторный вызов false;isResting: true при остатке > 0, false при ≤ 0;- после конца отдыха
isResting == false, ноactiveдо следующего Lap не перезапускается; - новый
startсбрасываетfired.
Vibe (гистерезис) — в тестах RestTimer/Vibe:
6. RED в t=0 → вибрация; RED в t=30 000 → нет (< 60 с); RED в
t=61 000 → вибрация;
7. GREEN между — сброс: RED t=0 → вибрация; GREEN t=10 000; RED
t=20 000 → вибрация (сброс сработал).