Skip to content

Latest commit

 

History

History
353 lines (232 loc) · 36.7 KB

File metadata and controls

353 lines (232 loc) · 36.7 KB

Health & Fueling Tabs — Full Reference

Find this too technical? Read the Easy Setup guide first — it has a plain-English "Fueling made simple" section (pair a sensor, fill age/sex, set a drink target, tap when you eat). This page is the complete reference.

Full configuration reference for the Health tab (HR-based detectors) and the Fueling tab (carb / hydration tracker). The README has a one-paragraph overview of each; this page covers every field, every tier, every alert mode, and the FIT-file integration.

For the underlying algorithms (thresholds, baselines, hysteresis) see:

Important

Model changes affecting this document (May 2026) — sections about "configured carb target g/h", "0.4×–1.5× zone multiplier", ride-type presets and intensity-multiplier-based deficit are out of date. The carb tracker now estimates real physiological burn from power (Tier 1), HR + age + sex + weight (Tier 2 Keytel) or HR + maxHr + restingHr (Tier 3 Swain), modulated by the CHO fraction at the current intensity zone (0.30 → 0.95 from Z1 → Z5+). The integrator clamps at the 90 g/h absorption ceiling. The deleted carbTargetGperHour config has no replacement on the carb side — only the deficit threshold (when to alert) and reminder cadence (how often) are configurable. Two new Settings fields: Age and Sex, needed to enable Keytel; without them the tracker falls back to Swain. See fueling-algorithm.md for the full spec and references (Keytel 2005, Swain & Leutholtz 1997, Romijn 1993, Jeukendrup 2014). Hydration retains the older target-based model.


Health tab

Note

Available from v2.0. The Health tab and all HR-based detectors (medical episodes, three-tier wellness monitor) require KSafe v2.0 or newer.

The Health tab adds two HR-based detectors that complement the accelerometer-based crash detection. Both are optional and only fire when a heart-rate sensor (ANT+ or BLE) is paired to the Karoo. Without HR data the detectors stay completely silent — no false negatives, no false positives.

Important

A heart-rate sensor is never required for KSafe to work. Crash detection, manual SOS, check-in timer, speed drop, and webhook actions all work without HR. The Health tab is purely additive — turn it on if you ride with a chest strap or optical HR monitor and want the extra detection layer.

Medical episode detection

Watches the rider's HR for two patterns that strongly indicate a medical incident:

  • Flatline — HR drops below 30 bpm for 30 continuous seconds while the rider is active. Catches asystole and severe bradycardia.
  • Collapse — HR drops by ≥ 40% from the 5-minute rolling average within 10 seconds. Catches vasovagal syncope and other events where the heart keeps beating at a low rate (where flatline alone wouldn't fire).

Both checks have built-in guards: HR data must be fresh (sensor connected within the last 15 s), and the rider must have been moving above 5 km/h within the last 60 seconds. This avoids spurious alerts when the Karoo is sitting on a desk with a paired strap nearby, or when the sensor briefly disconnects.

Default: enabled. Response level Emergency — same flow as a crash detection (configurable countdown + alert to contacts). The alert message uses your standard emergency message template; the {reason} placeholder reads "Medical episode detected".

You can also override the on-screen popup's title and detail per rider — see Custom alert text below.

Wellness monitor

A three-tier HR-based fatigue / overexertion / heat-stress monitor. Each tier is independent and can be turned on or off in the Health tab; the master toggle gates the whole monitor. Defaults are conservative — turn on the tier(s) that match your goals.

Tier What it watches Default trigger Why
Critical HR HR very high for a short time 95 % of max HR (or 175 bpm absolute) sustained for 5 min Acute overexertion — early warning.
Sustained HR HR moderately high for a long time 92 % of max HR (or 180 bpm absolute) sustained for 30 min Long-tail fatigue — same rule that existed before, kept for back-compat.
Cardiac decoupling HR / power ratio drift vs. baseline 7 % drift sustained for 10 min (after a 10-min baseline) Clinical indicator of dehydration / heat stress; requires a paired power meter (auto-skips when no power data).

The HR-based tiers can use either absolute bpm thresholds (the legacy mode) or % of max HR read from the Karoo profile (auto-scales across riders, no biometric entry needed). The decoupling tier always uses % drift relative to the rider's own ride-specific baseline, so the absolute / % toggle does not apply to it.

The streak resets if the watched signal drops below the tier's threshold even briefly — only continuous sustained excursions trigger the alert, not cumulative time. Per-tier cooldowns prevent spam during long climbs or sustained interval workouts.

Default: master toggle off (opt-in — the right thresholds depend on rider age and fitness). When the master is on, all three tiers default to enabled. Response level Warning by default — on-screen notification + beep, never sent to emergency contacts unless you explicitly raise the response level to Emergency.

Custom alert text

Every HR-based on-screen alert (medical, plus each of the three wellness tiers) has its own customisable title and detail in the Health tab. The fields show the built-in default text as a placeholder when empty — leave blank to use the default, or write your own message. The text is rendered with {token} placeholders substituted at runtime so you can include the live data in your own wording:

Alert Tokens you can use in title / detail
Medical {bpm}
Wellness Critical / Sustained {bpm}, {threshold}, {minutes}
Wellness Decoupling {drift} (% drift, 1 decimal), {minutes}

For example, a critical-HR detail of "HR at {bpm} bpm — slow down ({threshold} for {minutes} min)" will render at fire time as something like "HR at 178 bpm — slow down (175 for 5 min)".

Response levels

Each detector has a configurable response level:

Level What happens
Silent Logged to calibration data only — useful for testing without producing UI noise
Warning On-screen notification + beep on the Karoo, no alert to emergency contacts. The beep is configurable — see Beep patterns — including Off if you want the alert overlay without any sound.
Emergency Full crash flow: countdown + alert to contacts. Beep stays on the urgent default — not configurable by design, this is the safety-critical path.

Beep patterns

Karoo's SDK has no sound catalogue — every "different sound" is a sequence of synthesised tones. KSafe exposes a small set of presets so you can pick a different cadence per category rather than the same single long beep for everything:

Preset Pattern
Off No sound — visual InRideAlert only
Single long beep 880 Hz × 800 ms (default — historical behaviour)
Double short pip 880 Hz × 150 ms, 100 ms gap, again
Rising triple chime 660 → 880 → 1100 Hz × 200 ms each
Urgent pulse pip-pause-pip-pause-rising (same shape the emergency countdown uses)

Three independent pickers:

  • Fueling tab → Carbs → Sound: applies to carb deficit / time alerts.
  • Fueling tab → Hydration → Sound: applies to hydration deficit / time alerts.
  • Health tab → wellness → Sound: applies to all WARNING-level alerts (wellness sustained / critical / decoupling, plus any medical incident the rider downgraded to Warning).

Emergency-level countdown beeps (crash, medical-collapse on Emergency, the in-emergency urgent pulses) stay on the urgent default. Making them rider-mutable would defeat the safety guarantee that the Karoo grabs your attention when something serious happens.

Privacy

Heart-rate readings are consumed only by the on-device detectors. They are never sent to your emergency contacts unless a medical episode actually triggers an alert — and even then, the alert message is your standard emergency template. Your raw HR value itself is not included in the outgoing message.

When calibration logging is enabled, anonymised HR data does appear in the local CSV (same handling as the accelerometer / cadence / grade data already collected) — see Calibration Logging.


Fueling tab

Note

Available from v2.0. The Fueling tab — carb / hydration tracker, log-slot fields, customisable alert templates, FIT export — requires KSafe v2.0 or newer.

Tip

The Fueling tab is KSafe's preventive safety layer. The other safety features (crash detection, medical episodes, SOS) react after something has gone wrong. Fueling tries to keep things from going wrong in the first place: a rider who is properly fueled and hydrated has clearer judgment, faster reaction time, and fewer mistakes — and is much less likely to crash, blow up, or need to be rescued. Bonking and dehydration are real, common causes of cycling incidents, not just performance problems.

The Fueling tab is fully optional. It is disabled by default because the right targets depend on each rider. When you enable it, KSafe begins integrating a per-second carb and fluid target while you ride, watches what you log, and warns you when you fall behind.

How it works (no biometrics required)

KSafe does not measure your blood glucose or hydration in real time — there is no sensor on a bike that can. Instead the model compares two numbers and alerts when they diverge too much:

What How it's obtained
Burned so far (carbs) Real physiological carb burn, integrated over active ride time. The kcal/h is computed from the highest-confidence tier of [CarbBurnEstimator]: power × 3.6 (Tier 1), Keytel HR + age + sex + weight (Tier 2), or Swain HRR METs (Tier 3). Multiplied by the CHO fraction at the current intensity zone (linearly 0.30 → 0.95 from Z1 → Z5+, Romijn / Jeukendrup). Clamped at the 90 g/h gut-absorption ceiling. When no HR or power sensor is paired, the integrator stops and the data field displays Pair HR/Pwr.
Target so far (hydration only) The per-hour rate you configure (e.g. 750 ml/h), integrated over ride time. Optionally modulated by the dynamic sweat-rate estimator (HR + power + weight + ambient temperature + humidity).
Logged so far The sum of every log-slot tap you make during the ride.

The difference is the deficit. When it exceeds the threshold you configure (defaults: 25 g for carbs, 300 ml for hydration) KSafe fires a beep + on-screen alert. The first deficit and time alerts of a session are both suppressed for an initial-delay grace period (default 30 min, configurable per-tracker) so a fresh ride doesn't nag you at minute 25 with "behind 25 g". Deficit reminders after the first one fire at a configurable cadence (default 10 min, options 5/10/15/20/30 min).

If you keep ignoring them, they slow down (v2.2.1). Field logs showed riders getting a deficit reminder every 10 minutes for a whole ride — in the worst case one every ~2.4 minutes with both trackers on a 5-minute cadence — because the deficit only grows until you log something. The reminder interval now stretches as reminders go unacknowledged: your configured cadence for the first two, then double, then ×4, where it stops. It never goes fully silent (with the 10-min default you still get a nudge every 40 min — being behind on fluids matters most on exactly the long rides where this kicks in), and logging anything puts it straight back to your normal cadence — including a tap on the alert's own EAT!/DRINK! button. Time-based reminders are unaffected: "every N minutes" means every N minutes.

Three consequences worth understanding before you trust the alerts:

  1. Carbs reference your real physiology, not a target you set. Pair a power meter (Tier 1, ~5-10 % error) or HR sensor + fill in your age and sex in Settings (Tier 2 Keytel, ~10-15 % error). Without age/sex, HR + weight + maxHr + restingHr falls back to Swain (~20-30 % error). Without HR and without power, the carb tracker can't compute anything and the data fields show Pair HR/Pwr. Hydration still uses a rider-configured target (default 750 ml/h) because no biosensor for sweat rate exists.
  2. If you eat or drink without tapping a slot, KSafe doesn't know. The integrator's "burned" curve keeps climbing; the deficit grows until you tap. A missed log is indistinguishable from a missed gel. The slot's on-screen undo (a second tap within ~6 s) protects against the opposite mistake — an accidental tap that didn't correspond to a real intake.
  3. At Z4+ on a power meter the burn estimate will routinely exceed 90 g/h. That's physiologically real — your gut can't keep up with peak burn. The integrator clamps the recorded rate at 90 g/h so the deficit doesn't race away faster than any plausible intake plan, but the deficit will still grow at high intensity even if you're hitting your absorption ceiling. The alert there means "you're working harder than you can refuel — pace it".

How the carb burn estimator picks a tier

Carb burning depends on intensity (more power → more kcal/h → more carbs as the fuel mix shifts toward CHO). KSafe estimates the real burn from whichever sensor inputs are available:

Available inputs Tier picked Method Error
Power meter POWER (T1) kcal/h = power_W × 3.6 (Coyle 1992, standard cycling formula) ~5-10 %
HR + age + sex + weight (no power) KEYTEL (T2) Keytel et al. 2005 regression (sex-specific) ~10-15 %
HR + maxHr + restingHr + weight (no age/sex) SWAIN (T3) %HRR → METs → kcal/h (Swain & Leutholtz 1997) ~20-30 %
Neither HR nor power NONE Integrator stops; data field shows Pair HR/Pwr

Each tier's kcal/h is multiplied by the CHO fraction at the current intensity zone, then divided by 4 kcal/g:

g/h carb burn = (kcal/h × CHO_fraction) / 4

The CHO fraction is interpolated linearly from 0.30 at the lowest zone to 0.95 at the highest zone — your Karoo's HR-zone count (typically 5) or power-zone count (typically 7) determines the exact step. Anchors come from Romijn 1993, Achten & Jeukendrup 2003, Jeukendrup 2014. The integrator clamps the resulting g/h at the 90 g/h gut-absorption ceiling (Jeukendrup 2014, ISSN 2017, IOC 2019 consensus).

The legacy "rider-configured base target × intensity multiplier" model (≤v2.0 early development) was replaced because the field labelled "burned" was actually "planned intake scaled by intensity" — misleading. The new estimator gives you data you can compare with your fueling plan.

How the hydration target adapts to your effort and the weather

Hydration is more dynamic than carbs because evaporative cooling depends on heat, humidity and intensity, not just time. KSafe offers two modes:

  • Flat per-hour target (default) — same number every hour. You raise it manually for hot days.
  • Dynamic estimate (optional toggle) — target varies with HR / power, body weight, ambient temperature and humidity. Uses karoo-headwind weather data when that extension is installed; otherwise the Karoo onboard temperature sensor (biased +3–8 °C) and 50 % assumed humidity. Expect ±25–35 % accuracy in the common HR + onboard configuration; closer to ±20 % with power meter + Headwind.

The dynamic mode biases high in hot conditions by design: the estimator's job is to set a hydration target, not measure your sweat. Under-targeting risks dehydration (heat illness, cramps, performance collapse); over-targeting costs at most a few extra sips. See SweatEstimator.kt for the full bias rationale.

How to set up your fueling

Carbs: there is no per-hour target to configure. The burn estimator computes real physiological carb burn from your paired sensors. Your job is to:

  1. Pair a sensor (in this order of accuracy): power meter → HR sensor.
  2. If HR-only: fill in Age and Sex in the Fueling tab. This unlocks the Keytel formula (~10-15 % error). Without them the tracker falls back to Swain (~20-30 % error).
  3. Pick a deficit threshold (default 25 g, configurable). The threshold is "how many grams behind real burn before I want to be reminded". For a guideline:
    • Casual / endurance riding: 25-35 g (about 25-35 min of typical intake).
    • Race / long events with trained gut: 15-25 g (tighter — you can absorb a lot, want to stay on top).
  4. Pick a reminder cadence (default 10 min, options 5/10/15/20/30). After the first deficit alert, how often to remind you. Reminders you never act on stretch this out automatically (×2, then ×4 and no further); logging anything restores your chosen cadence.

The integrator clamps the recorded rate at 90 g/h (Jeukendrup 2014, ISSN 2017, IOC 2019 consensus) — the established single-transportable gut-absorption ceiling for un-trained guts. At Z4+ intensity on a power meter your real burn can exceed this; the deficit will keep growing because you can't physically refuel as fast as you burn. That's data telling you to pace yourself, not noise.

Bodyweight ceiling: research puts the gut-trained max at roughly 1.0–1.2 g/kg/h. A 75 kg rider can sustainably absorb up to ~90 g/h; a 60 kg rider up to ~70 g/h. Race-trained riders push 120-150 g/h after months of adaptation — they can manually raise the absorption-cap constant if needed, but the default is conservative.

Hydration: still needs a configured target (no biosensor for sweat rate exists). Defaults to 750 ml/h. See the hydration sub-section below for the table by conditions.

Hydration target (ml/h)

Hydration is dominated by heat, humidity and bodyweight, much more than by intensity. Field-tested rate by conditions:

Temperature Suggested ml/h
< 15 °C (cool) 400–600
15–22 °C (mild) 600–800
22–28 °C (warm) 800–1100
28–32 °C (hot) 1100–1400
> 32 °C (very hot) 1400–1800

KSafe default (750) suits a mild day. For a summer MTB ride at 14:00 with the sun on your back, bump it manually to 1000–1200 — or, better, enable the Dynamic estimate toggle and let the sweat-rate model derive it from your HR/power, weight (from your Karoo profile), and the temperature/humidity stream. With karoo-headwind installed you also get real meteo humidity; without it KSafe falls back to the Karoo onboard temperature sensor (biased high by device self-heating — the estimator compensates by erring on the high side because under-targeting hydration is far more dangerous than over-targeting).

Bodyweight scaling: ~10 ml/kg/h in moderate heat, scaling up to ~20 ml/kg/h in extreme heat for a heavy sweater. A 60 kg rider in 25 °C might need 600 ml/h; a 90 kg rider in the same weather might need 900–1000 ml/h.

How to calibrate against a real ride

Defaults are a starting point — your true rate is rider-specific. The data fields make tuning straightforward:

  1. Configure age + sex + hydration target and ride with the carb-status and hydration-status fields visible (and optionally the burn-rate + carbs-burned fields too).
  2. At 2 h, check the screen: KSafe shows cum target (what it integrated), cum logged (what you actually consumed), and the deficit between them.
  3. Tune for the next ride:
    • Felt bonk-y despite hitting your logged target → your true burn rate is higher than KSafe integrated. Increase the base 10–15 g/h.
    • Finished with a full belly and unused gels → drop the base 5–10 g/h.
    • Light-headed / cramping in warm conditions → increase the hydration target 100–200 ml/h, or enable Dynamic estimate.
    • No symptoms, low deficit consistently → defaults are working; no change needed.

After two or three calibration rides in varied conditions you'll have personalised numbers. The defaults are a starting hypothesis, not a prescription.

Measuring your personal sweat rate

The cheapest accurate calibration for hydration is to weigh yourself before and after a 1–2 h ride in representative conditions:

sweat_rate (L/h) = ((kg_before - kg_after) + litres_consumed) / hours

Subtract any urination volume if relevant. Repeat in cool, mild and hot conditions to build a personal curve — sweat rate can vary 2–3× between a 12 °C ride and a 32 °C ride for the same rider.

References and further reading

The tables above synthesise the following sources, which are the standard citations in the sports-nutrition literature and in the product copy of any serious hydration / fuelling brand:

Carbohydrates

  • Jeukendrup A. (2014). A step towards personalized sports nutrition: carbohydrate intake during exercise. Sports Medicine 44(Suppl 1):S25–S33. Open access. Origin of the duration-keyed ladder (30 → 60 → 90 → 120 g/h) and the multi-transportable-carbs rationale.
  • Thomas DT, Erdman KA, Burke LM (2016). Position of the Academy of Nutrition and Dietetics, Dietitians of Canada, and the American College of Sports Medicine: Nutrition and Athletic Performance. MSSE 48(3):543–568. The joint position statement most often cited in cycling federations' nutrition guidance.
  • mysportscience.com — Jeukendrup's accessible blog; the carb-ladder infographics widely reused in cycling content originate here.

Hydration

  • Sawka MN et al. (2007). ACSM Position Stand: Exercise and Fluid Replacement. MSSE 39(2):377–390. Source of the "<2 % bodyweight loss" rule and of the by-intensity / by-environment ranges that this document's table mirrors.
  • Baker LB (2017). Sweating Rate and Sweat Na+ Concentration in Athletes: A Review of Methodology and Intra/Interindividual Variability. Sports Medicine 47(Suppl 1):111–128. Open access. Documents the 0.5–2.5 L/h real-world spread and supports the dynamic-estimator's deliberate upper-bound bias.

Practical calculators (free)

  • Precision Fuel & Hydration Knowledge Hubprecisionhydration.com/knowledge — articles by Andy Blow, free online sweat-test that returns a personalised ml/h and a sodium ladder.
  • TrainingPeaks blog — overlapping divulgation of the same methodology.

These are starting hypotheses, not prescriptions. KSafe's SweatEstimator (see fueling-algorithm.md) explicitly biases the hydration target toward the upper end of the literature because under-targeting fluids carries far more risk (heat illness, cramps, judgment collapse) than over-targeting (a few extra sips).

Two combinable alert modes

For each category (carbs, hydration) independently:

  • Alert by deficit: silent until (target_so_far − logged) > threshold (e.g. >25 g for carbs, >300 ml for hydration). Then beep + on-screen InRideAlert (full-screen overlay, auto-dismissable in 10 s).
  • Alert by time: silent until time_since_last_log > interval (e.g. >25 min for carbs, >20 min for hydration). Same alert UX.

The time alert also has a per-category initial delay (default 30 min, configurable, set to 0 to disable). Most riders don't eat or drink in the first 20 minutes; the initial delay prevents a nag at minute 25 of an otherwise good ride. After the first alert fires or the user logs an item, normal interval logic takes over.

Both modes can be on at the same time. A 5-minute cooldown prevents the two from firing within seconds of each other; once one fires, neither will fire again until the cooldown elapses.

Picking the deficit threshold

Defaults are 25 g for carbs and 300 ml for hydration. To pick a value that fires when you actually want it to, think in two reference frames:

As "minutes of intake behind" — divide the threshold by your per-hour target. A 25 g threshold at a 60 g/h target = ~25 min behind; the same 25 g at a 90 g/h target = ~17 min behind. A 300 ml threshold at a 750 ml/h target = ~24 min behind; at 1200 ml/h = ~15 min. The sweet spot is 15–25 min of buffer — under 10 min you get false alarms every time you delay a sip by one descent, over 40 min the alert fires only once performance is already noticeably degrading.

As "one typical refuel item" — a standard energy gel is ~22–30 g carbs, a sip-bag is ~30–35 g, a third of a 750 ml bottle is ~250 ml. Setting the threshold ≈ one refuel item means the first alert lands when you are one item behind schedule — actionable and unambiguous.

Rider profile Carb threshold Hydration threshold Behaviour
Early-warner (long events, anti-bonk priority) 15–20 g 200–250 ml Fires after one missed gel / one missed sip — tight monitoring
Default (most riders) 25 g 300 ml Fires after about one gel / a third of a bottle behind — the shipped defaults
Late-warner (short rides, hates nags) 35–45 g 400–500 ml Fires only after roughly two missed items — minimal interruption

Two important guardrails:

  • For hydration, the ACSM "keep loss under 2 % body weight" rule sets the upper bound. For a 75 kg rider that's 1500 ml total; a 300 ml threshold leaves a huge safety margin. Setting it above ~700 ml for a typical rider defeats the point of the alert.
  • For carbs, the bonk-protective ceiling is roughly 45–50 g of cumulative deficit. Beyond that, blood glucose stability is already compromised and any "alert" arrives late. Don't set the threshold above ~40 g unless you're consciously running a fasted / low-carb ride.

If your target is set sensibly (see the tables above), the default 25 g / 300 ml will land in the actionable window for the great majority of rides.

Both the alert title ("Eat something" / "Drink something") and the detail line ("Behind by 25g" / "30 min since last log") are customisable per category. Each field shows the default as placeholder when empty — leave blank to use the default, or write your own template. Tokens are substituted at fire time:

Token Substituted with
{deficit} Current deficit (g for carbs, ml for hydration)
{elapsed} Minutes since the last log entry
{target} Configured per-hour target

For example, a custom carb detail of "You're {deficit}g down — eat now ({elapsed} min)" renders at fire time as "You're 35g down — eat now (15 min)". When the field is empty, the source-specific defaults are used (deficit alerts get "Behind by Xg", time alerts get "X min since last log").

Logging in-ride

Two complementary mechanisms:

  • Data fields: 3 carb log slots + 2 drink log slots, each with its own configurable label (e.g. "Gel", "Bar", "Bottle"), amount (g or ml), idle background colour (Karoo default auto day/night, or any of 20 dark hues — see field-colours.md) and icon (emoji like 🍫 / 🥤 / 💧, or one of the two bundled vector drawables for sports gel pouch and cyclist bidón — Unicode has no good emoji for those shapes). One tap = one log. The slot flashes green for 6 seconds showing +Xg ✓ (or +Xml) with the hint TAP UNDO, then returns to its idle label. Add as many or as few slots to your ride profile as you want.
    • On-screen undo: a second tap on the same slot during the 6 s green window reverses the log. The slot then flashes red −Xg ✓ (or −Xml) for ~1.5 s as confirmation and returns to idle. Per-slot and one-shot: a third tap is a no-op until the next log populates the slot again. Undo restores the time-alert clock to its value before the wrong tap, so the next time-based alert isn't shifted by the bad entry.
  • Combined fuel-log fields (Fuel Combo 1 / 2): a tappable field that logs a drink volume (ml) and carbs (g) in one tap, feeding the same hydration and carb cumulative totals as the separate drink-log / carb-log slots above. It logs only the enabled side(s): with only the carb tracker on it logs only the carbs, with only the hydration tracker on it logs only the drink, and with neither on the field renders grey/OFF and tap is disabled. It has the same ~6 s green confirmation + undo window as the other slots (a second tap reverses the log). Each button stores its own volume (ml), carbs (g), label and idle background colour, configured in the Fueling tab's Combined logging section. That section also has a single carb concentration value (grams of carb per 500 ml of mix) that auto-fills each button's carbs from its volume via carbsFromVolume(ml, conc) = round(ml × conc / 500); the auto-filled value is an editable override, not a hard binding. Unlike the drink-log slots, the combined field's icon is fixed (a bundled bottle + gel drawable) and is not rider-pickable.
  • Hardware buttons (BonusActions, SRAM AXS only): KSafe registers two extra actions, "KSafe: Log Carb" and "KSafe: Log Drink", both wired to slot 1 of each category. Map them to your AXS shifter buttons so you can log without looking at the screen.
  • Log straight from the alert (optional): in the Fueling tab you can enable an in-alert log button. When a carb or hydration alert fires, it appears as a tappable prompt that names the item it suggests (e.g. "Gel") with a big EAT! / DRINK! button showing the suggested amount (e.g. 25 g) — so you log without hunting for its field. Three modes: Off (default — alert only, no button), One tap (Log), and Log + undo (after logging, a brief UNDO appears for ~4 s, confirming what was logged, e.g. "Logged Gel 25 g"). Logging from the alert updates the on-screen log field and the running totals exactly like tapping the field. The suggested item is the slot whose size is closest to your current shortfall (or your first slot for a time-based reminder); if you've left every slot at 0 the alert simply shows with no button. The prompt is tinted with the category's configured alert background colour (kept translucent, so a fueling nudge never reads as the red/amber emergency overlay), and its title runs on its own full-width line so a custom title isn't truncated. It needs the overlay permission, and during a crash / SOS countdown the fueling alert is held back entirely so it can never cover the emergency screen.
    • Preview alert: each channel section (Carbs, Hydration) has a Preview alert button that shows the prompt exactly as it will look mid-ride — same title, detail, icon, colour, verb and the real Log→Undo flow — but records no intake and plays no beep, so you can check your wording and colour from Settings before a ride. It honours whichever button mode you've selected; if you have overlay permission off or every slot at 0, the status line tells you why nothing appeared.

When the master Carb / Hydration toggle is off, the corresponding log fields render in grey with OFF and tap is disabled — the data field is still visible on the ride profile but clearly inactive, so a stray tap does nothing instead of silently no-op'ing. Re-enable the master in the Fueling tab and the colour / emoji come back.

There are also four status data fields for carbs (deficit, burn rate, cumulative burned) plus one for hydration (deficit). All are optional — only the ones you add to your ride profile actually appear:

Field What it shows
KSafe Carb Status (carb-status) Current carb deficit (target − logged), colour-coded green / amber / red
KSafe Carb Burn Rate (carb-burn-rate) Instantaneous burn rate in g/h (target × zone multiplier). Shows --- until the first HR/power sample arrives (otherwise the field would display base × 1.0, which riders consistently read as "the app already thinks I'm burning 60 g/h before I've clipped in"). Once a zone is computed it switches to the live zone-modulated rate.
KSafe Carbs Burned (carbs-burned) Cumulative g the body should have burned this session — companion to the deficit field, lets you see "total need" alongside "currently behind".
KSafe Hydration Status (hyd-status) Current hydration deficit (target − logged), colour-coded

Post-ride summary

When you stop the recording, KSafe shows an InRideAlert with totals: "Carbs: 85/120g (71%) • Hyd: 1100/1500ml (73%)". Configurable on/off; nothing is sent to your contacts.

Carbs and hydration in your FIT file (Strava / Intervals.icu / TrainingPeaks)

KSafe writes the cumulative carbohydrates and hydration you log into the Karoo's FIT file as developer fields. When you upload the activity to Strava / Intervals.icu / TrainingPeaks, your fueling appears as two extra graphs alongside HR / power / cadence — coaches can correlate fueling with effort, and you can answer questions like "I bonked at hour 4 → looking at the FIT, I had 0 g carbs that hour" from the data instead of guessing.

Field Type Where in the FIT
ksafe_carbs_g (#0) float32, "g" record (step-curve timeline) + session (activity header) — cumulative carbs logged by tap or AXS button
ksafe_hyd_ml (#1) float32, "ml" Same — cumulative hydration logged
ksafe_hr_drift_pct (#2) float32, "%" record only — instantaneous cardiac-decoupling reading (per-second time series)
ksafe_max_drift_pct (#3) float32, "%" session only — peak cardiac-decoupling reached during the ride
ksafe_wellness_fires (#4) float32, "count" session only — number of wellness alerts that fired
ksafe_carbs_burned_g (#5) float32, "g" record + session — cumulative carbs the body actually burned (real-physiology estimate: power × CHO_fraction or Keytel HR or Swain HR)
ksafe_carb_burn_rate_gph (#6) float32, "g/h" record only — instantaneous burn rate from the physiological estimator

The logged / burned / hydration fields are cumulative step curves. The burn rate is instantaneous, so it tracks intensity changes — drop in HR or power, rate drops. Tools that prefer rate from cumulative data can still derive a logged-rate (g/h) locally by differencing.

Session-average burn rate is shown live on the Karoo via the carb-avg-burn-rate data field but is not written to the FIT — downstream analysis tools can compute it themselves by averaging the per-record ksafe_carb_burn_rate_gph time series, so writing it again would duplicate information.

Note

Field-definition numbers 0-6 are immutable once shipped — historical FIT files reference them by number, so they cannot be repurposed. Number 7 is reserved (was briefly used during development for the avg burn rate before that field was reduced to in-app only).

Toggleable via the "Write to FIT" switch in the Settings tab. Default OFF (opt-in) — the cost is negligible (~0.05 % battery over a 5 h ride, no perceptible CPU), but extra developer columns are off by default; riders who want their fueling in the FIT turn it on cleanly.

Pacing aligns with the Karoo's native 1 Hz Record sampling, so the developer fields land on the same timestamps as HR / power. Outside Recording (Idle / Paused) nothing is written.

What you configure

Per category (Carbs, Hydration) the Fueling tab lets you:

  • Enable / disable the tracker (master toggle — when off, the rest of the fields collapse for a tidier screen)
  • Set the per-hour target
  • Toggle the deficit alert + threshold
  • Toggle the time alert + interval + initial delay
  • Customise the alert title and detail templates (optional — placeholder shows the default; leave blank to use it, or write your own with {deficit} / {elapsed} / {target} tokens)
  • Pick the alert sound (Off / Single long / Double pip / Rising chime / Urgent pulse) — see Beep patterns
  • Configure each slot's label, amount, idle background colour (Karoo default or one of 20 dark hues — see field-colours.md), and icon (emoji or one of the bundled vector drawables for sports gel and bidón)
  • For hydration only: toggle the dynamic estimate mode
  • Toggle FIT export (default OFF (opt-in); controls whether your fueling appears as developer fields in the Karoo's FIT file)

That's it — no biometric data, no FTP, no zone numbers, no max HR. KSafe reads all of that from the Karoo profile.

Privacy

Carb and hydration logs never leave your Karoo unless calibration logging is enabled (in which case anonymised counters and HR/power zone snapshots appear in the local CSV — same handling as the existing crash and HR data).