Skip to content

🧗 Rock Climbing logger - #2476

Closed
oisinmulvihill wants to merge 15 commits into
InfiniTimeOrg:mainfrom
oisinmulvihill:climb-logger
Closed

🧗 Rock Climbing logger#2476
oisinmulvihill wants to merge 15 commits into
InfiniTimeOrg:mainfrom
oisinmulvihill:climb-logger

Conversation

@oisinmulvihill

Copy link
Copy Markdown

🧗 Rock Climbing logger

  • Allows me to log climbs I do at two local gyms I have
  • I can download logged climbs to my desktop
  • The app remembers the previously selected climb to allow faster logging
    • Memory persists across app reload/exits

oisinmulvihill and others added 15 commits August 10, 2026 12:37
Skeletal user app (button + on-screen counter) built and confirmed running
in InfiniSim. Proves the scaffold -> register -> launch loop works before
building ClimbLogger on top of the same pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Placeholder screen only, registered via the same AppTraits pattern
TapCounter proved: enum entry, header/cpp pair, included in UserApps.h.
Confirms the build->register->launch loop for the real app name before
Step 2 builds the selection UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FontAwesome5 has no climbing-specific glyph (that's a v6 addition), so
picked the closest already-licensed icon in the bundled woff: 'mountain'
(0xf6fc). Added its codepoint to fonts.json's jetbrains_mono_bold_20 range
and defined Symbols::mountain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Step 2)

Single Screen instance, internal Step enum (Area -> Colour -> Grade ->
[Climb, if the filter doesn't land on exactly one] -> Result -> Attempts),
rebuilding its lv_btnmatrix/Counter widgets per step rather than pushing
separate Screen objects -- same pattern FirmwareUpdate.h uses for its own
multi-step flow.

- CatalogEntry + an 8-entry hard-coded fixture (2 placeholder areas, mixed
  boulder/route climbs) stand in for catalog.csv until Step 3.
- Result capture (flash/send/fell/project) and the attempts counter reuse
  lv_btnmatrix and the existing Widgets::Counter, matching how Calculator
  and Alarm already use them elsewhere in this codebase.
- Logging a climb prints a CSV-shaped line via NRF_LOG_INFO (visible on
  the host running the simulator) and triggers a short haptic buzz via
  MotorController::RunForDuration -- the "something visible/testable"
  placeholder Step 2 called for, ahead of Step 4's real log.csv write.
- Wired dateTimeController/motorController through AppTraits::Create().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
lv_btnmatrix fires PRESSED on press-down but VALUE_CHANGED once, on
release, after the gesture against the current matrix has fully resolved.
OnButtonMatrixEvent rebuilds the whole screen (destroying this exact
matrix) in response to a selection, so reacting to PRESSED re-triggered
against the freshly-created matrix at the same coordinates while the
input device was still mid-gesture -- cascading through several steps
on a single tap instead of stopping at the one selected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous fix (PRESSED -> VALUE_CHANGED) was based on a wrong
assumption. lv_btnmatrix's ctrl_bits default LV_BTNMATRIX_CTRL_CLICK_TRIG
to off (allocate_btn_areas_and_controls memsets ctrl_bits to 0), and with
that bit off, lv_btnmatrix's own VALUE_CHANGED signal fires on *press*,
same timing problem as PRESSED -- still cascaded.

LV_EVENT_CLICKED, sent generically by lv_indev.c itself (the same
mechanism the existing, already-correct logButton handler relies on),
only fires once a press/release gesture has fully resolved against the
current object -- safe to rebuild/destroy the screen in response to.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the area/colour/grade catalog-matching flow with a simpler
5-step taxonomy that reflects how climbs are actually logged across two
gyms:

- Gym: The Castle / Climbing District / Other
- Style: Boulder / Top Rope / Lead
- Type: Slab / Overhang / Vertical / Mixed (wall angle, not gym area)
- Grade: V-scale for Boulder, Font-scale for Top Rope/Lead (auto-picked
  from style, no separate scale-picking step)
- Attempt: Send / Did Not Finish -- replaces the numeric attempts counter
  entirely, so logging now happens the instant Attempt is tapped, with
  no separate confirm button

No more catalog/CatalogEntry -- there's no fixed route list to match
against anymore, just a taxonomy of what was climbed. Also adds "remember
previous selection": selectedGym/Style/Type/Grade/Attempt are no longer
cleared after logging, so they double as next log's defaults, and
ShowOptionsStep pre-checks the matching button (lv_btnmatrix_set_btn_ctrl
+ CTRL_CHECK_STATE, same API Calculator.cpp already uses for its
toggle-state operator buttons) so repeating the same climb type is one
confirming tap per step. In-memory only for this run of the app; real
persistence is a later roadmap step.

Grade ladders and gym/type lists are placeholders (still open questions
per ROADMAP.md), enough to exercise the flow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the NRF_LOG_INFO stdout placeholder from Step 2 with a real
append-only write via Controllers::FS, wired through AppTraits::Create()
same as motorController/dateTimeController already were. Follows
AlarmController::SaveSettingsToFile's established idiom: DirOpen the
containing directory and DirCreate it if that fails (first-boot case),
then FileOpen with LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND so each log
is a cheap single-line append, not a full-file rewrite.

Controllers::FS has no locking/queueing -- every call is a direct
synchronous lfs_* call -- so, per the existing codebase convention
(AlarmController/Settings both do this), it's safe to call straight from
this Screen's own event-handling code on the DisplayApp task.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WriteLogEntry() now logs via NRF_LOG_INFO after a successful file write,
reusing the same formatted line (no trailing "\n", added separately for
the file) rather than duplicating the format string -- so you can see in
the terminal that something was actually appended without a make
pull-log round-trip every time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WriteLogEntry() now uses dateTimeController.UTCDateTime() (subtracts the
BLE-reported timezone/DST offset, 0 if never set e.g. in the simulator)
rather than the Year()/Hours()/etc. getters, which read `localTime` --
already offset-adjusted. Formats as "%04d-%02d-%02dT%02d:%02d:%02dZ"
(ISO 8601, "Z" = UTC) instead of the previous naive "YYYY-MM-DD HH:MM:SS"
with no timezone info at all.

Keeps the log unambiguous regardless of whether/where a companion app
ever set the watch's timezone correctly. Local-time display, if wanted,
is a host-side concern for whatever ingests log.csv later (Step 6), not
something to bake into the on-device file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The firmware's src/CMakeLists.txt SOURCE_FILES list is explicit (no
glob, unlike InfiniSim's simulator CMake, so new screens must be
added here too or the real hardware build fails to link.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EOF
)
CLIMBLOGGER_BUILD_TAG (CMake cache var, empty by default) appends an
"om<NNN>" suffix to the version shown on the System info and post-flash
Firmware validation screens, e.g. "1.16.0.om005" instead of "1.16.0" --
makes a custom ClimbLogger build unmistakable from an official InfiniTime
release at a glance, without touching the numeric project version used
internally by MCUboot. Wired through docker/build.sh so the top-level
project's `make firmware` can set it per build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Remembered per-step defaults previously lived only in the Screen
object's memory, so they were lost whenever ClimbLogger was destroyed
and recreated (exiting the app, at minimum -- confirmed on real
hardware). Now written to /climbs/last_selection.csv after each
successful log and read back in the constructor, resolved against the
current option lists rather than pointing into a stale buffer, so a
changed option list degrades to no pre-check instead of a bogus one.

Also fixes format-string warnings in SystemInfo/FirmwareValidation's
version display (%ld/%lu vs uint32_t) surfaced while rebuilding for
this change -- harmless on the 32-bit ARM firmware target but real on
the simulator's 64-bit long.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous %u fix compiled fine on the simulator's host toolchain
(where uint32_t is unsigned int) but broke the real firmware build
with -Werror=format (where uint32_t is unsigned long on this ARM ABI).
Neither %u nor %lu is portable across both toolchains. Using PRIu32
from <cinttypes> instead, which resolves correctly on either.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Build size and comparison to main:

Section Size Difference
text 385320B 88B
data 944B 0B
bss 22640B 0B

Run in InfiniEmu

@oisinmulvihill

Copy link
Copy Markdown
Author

Opened PR here by accident, only meant to do so on my own fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant