Skip to content
This repository was archived by the owner on May 19, 2026. It is now read-only.

Add Workout App - #60

Merged
rryles merged 11 commits into
mainfrom
feature/workout
May 12, 2026
Merged

Add Workout App#60
rryles merged 11 commits into
mainfrom
feature/workout

Conversation

@rryles

@rryles rryles commented May 12, 2026

Copy link
Copy Markdown
Contributor

Generic indoor workout app. Tracks heart rate and calculates calories from time spent in each HR zone and user weight (configured in phone app).

rryles added 11 commits May 12, 2026 09:51
…king

Introduce the Workout app, an indoor activity tracker derived from the
Running app and tailored to a heart-rate-first, GPS-free experience:

- Bootstrap from a copy of the Running app, fully renamed (paths,
  symbols, identifiers, build targets, TouchGFX project, assets).
- Drop GPS end-to-end: no GPS sensor connection, no map builder, no
  fix indicator, no "Acquiring Signal" / pre-fix start confirmation
  flows.
- Remove the Intervals feature in full: Track/Settings types, Commands
  messages and senders, Service state machine, SettingsSerializer JSON,
  AppMenu navigation node, GUI screens/containers, and Model/
  ModelListener/TrackPresenter callbacks.
- HR-centric tracking: new Track::Data fields (hrZone, totalCalories,
  lapCalories, cumulative time-in-zone) populated by a fixed-weight
  zone-weighted MET calorie model in the Service.
- Reworked GUI: three track faces (HR + calories + total time;
  lap-avg HR + lap number + lap time; time of day + battery) and a
  lap popup showing lap number, lap time, lap-avg HR, and lap calories.
- FIT export records the session as FIT_SPORT_GENERIC and drops all
  position/altitude/distance/speed/ascent/descent fields from records,
  laps, sessions, and the activity summary JSON; collapses unused
  mFHRecord variants and splits BATTERY into BATTERY_LEVEL/VOLTAGE.
…ories

Initialize zone to 0 in getHrZone so HR at or below the lowest kernel threshold
returns no zone, matching the on-screen gauge and preventing calorie/zone-time
accrual at idle/resting heart rates.
…tabolic_calories

Switch the Workout app over to the newer vendored FIT SDK
(FitSDKRelease_21.202.00, profile RELEASE) so it can write
session.metabolic_calories as a standard FIT field instead of a
developer field. Also adds basal-metabolic-rate (MET 1.0) accumulation
for resting/metabolic calories that always accrue during active
tracking, written as session.metabolic_calories and a per-lap
"resting_calories" developer field.

Build wiring:
- CubeIDE service .project/.cproject point at the new SDK and define
  FIT_PRODUCT_RELEASE.
- Workout-CMake CMakeLists overrides UNA_FIT_SDK_DIR before including
  una-sdk.cmake and defines FIT_PRODUCT_RELEASE.
- TouchGFX simulator (msvs/gcc) and una/Makefile updated likewise.

Source changes:
- ActivityWriter.{hpp,cpp}: include fit_product.h, drop the
  metabolic_calories developer field and add it to the Session message
  def, populate session_mesg.metabolic_calories natively, write
  per-lap restingCalories as a developer field.
- Track.hpp: add restingCaloriesTotal / restingCaloriesLap.
- Service.cpp: accrue BMR (MET 1.0) every active second regardless of
  HR; fall back to MET 1.0 for HR below zone 1; reset per-lap counter
  in saveLap.

Other apps continue to build against the existing 21.171.00 EXAMPLE
SDK; this change is opt-in per app.
The Alerts Menu only contains a single item. In this case we want to
disable the scroll indicator. We also want to disable the circular list
behaviour, so that we don't get a duplicate entry shown.
…alories

The TrackAction screen's info carousel cycled through four items
(Calories, Avg.HR, total zone time, current zone) but we only want
Avg.HR and Total Calories, so the carousel is reduced
to those two.

- setCount(4) -> setCount(2); cases trimmed accordingly.
- Drop now-unused setZoneTimes / setCurrentZone setters and the
  mZoneTotalSec / mCurrentZone members from TrackActionView.
- Drop the matching view.setZoneTimes / view.setCurrentZone calls
  from TrackActionPresenter::onTrackData.
The summary overview face previously displayed total time, average HR
and total calories.  For an HR-centric indoor workout, max HR is more
informative than calories there (calories are still emitted in the FIT
file), and matches the data shown alongside avg HR on the dedicated
heart-rate face.

Designer changes (already done by the user):
- Added a heart icon next to the bottom row.
- Renamed the bottom value/text widgets caloriesValue/Text to
  maxHRValue/Text and pointed the label at T_TEXT_MAX_HR_UC.
- Regenerated the gui_generated base, texts and typed-text DB.

Code changes:
- Replace SummaryFaceOverview::setCalories(float) with
  setMaxHR(float).  Mirrors setAvgHR: shows "---" when hr <
  App::Display::kMinHR, otherwise the integer bpm value.
- TrackSummaryView::setSummary now feeds s.hrMax to the overview face
  (the dedicated heart-rate face still receives both avg and max).
Insert a dedicated calories face on the TrackSummary screen so the
zone-MET split is visible without rummaging through the FIT file.
Swipe order is now Overview -> Calories -> HeartRate -> Laps.

GUI:
- New SummaryFaceCal container (Designer-authored, added by the user)
  showing total active time plus total / active / resting kcal.  The
  cpp populates totalValue, activeValue, restingValue and the timer
  via Unicode::snprintfFloat("%.0f", ...) so values stay whole numbers.
- TrackSummaryView gains a FACE_CALORIES = 1 enumerator, shifting
  FACE_HEARTRATE to 2 and FACE_LAPS to 3.  scrollIndicator count is
  now 3 + lap pages.  setSummary forwards s.time, s.calories,
  s.activeCalories and s.restingCalories to the new face.

Data model:
- ActivitySummary gains restingCalories and activeCalories (kcal).
  The existing 'calories' field still holds the zone-MET total.
- Service::buildPartialSummary populates the new fields from
  mTrackData.totalCalories / restingCaloriesTotal, clamping
  active = max(0, total - resting) via std::fmax.
- ActivitySummarySerializer writes "resting_calories" /
  "active_calories" and tolerates older summaries that only stored
  "calories" (treated as active, resting = 0).
Swap the heart-rate face on the TrackSummary screen for a new
SummaryFaceZones that shows the time spent in each of the five HR
zones with a zone number, percentage of total active time, a coloured
progress bar, and the time spent in that zone.  Zone 5 is at the top
of the list, zone 1 at the bottom -- matching the in-activity HR-zone
indicator.

Data:
- ActivitySummary gains zoneTimeSec[5] (cumulative seconds in zones
  1..5).  buildPartialSummary() copies it from Track::Data.
- ActivitySummarySerializer persists/loads the new "zone_times_sec"
  array; old summaries without the key keep zeros.

GUI:
- New ZoneInfo row container: setRow(zone, seconds, pct, color)
  writes the three wildcard text areas (zone digit, time formatted
  via SDK::Utils::toHMS, percent via T_TEXT_PERCENT typed text),
  resizes the progress bar to 6..48 px based on pct, retints
  barPainter, and hides the bar at 0%.
- New SummaryFaceZones: title set to "HR ZONES", setZoneSummary()
  folds any active seconds spent below zone 1 into the displayed
  zone-1 row so the five percentages add up to 100% of session time.
- TrackSummaryView: FACE_HEARTRATE -> FACE_ZONES (value still 2),
  calls summaryFaceZones.setZoneSummary(s) and toggles its visibility
  on swipe.

Cleanup:
- Deleted SummaryFaceHeartRate.{hpp,cpp} and updated the MSVS
  simulator project + filters to drop the entries.  CubeIDE/gcc
  Makefile pick up sources via globs.
Calorie calculations need the user weight. This was previously hard
coded at 75kg. Now it will use the weight from the settings.json if it
is above zero.
@rryles
rryles merged commit 7a8463c into main May 12, 2026
27 checks passed
@rryles
rryles deleted the feature/workout branch May 12, 2026 15:09
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants