WattFlow documentatie

WattFlow gids en Bluetooth architectuur

Dit document bundelt de brede uitleg van WattFlow zoals de app nu werkt op 24 maart 2026: van tabbladen, workouts en trainingsmodi tot Strava, Apple Health en de diepe Bluetooth-laag. Het idee is dat je hier zowel een leesbare gebruikersgids als de technische architectuur terugvindt, zonder dat je eerst door Xcode hoeft te bladeren.

Gebaseerd op ContentView.swift, AppModel.swift, TrainingenTabView.swift, WorkoutTabView.swift, ActivitiesTabView.swift, SettingsTabView.swift, StravaConnectService.swift, AppleHealthService.swift, BluetoothManager.swift en WorkoutSessionEngine.swift Doel: leesbare uitleg buiten Xcode Scope: gebruikerslaag plus technische Bluetooth-verdieping

WattFlow appicoonStart hier: kies je onderwerp

Deze HTML werkt nu als een soort WattFlow-startpagina. Je kunt hem lezen als gebruikershandleiding, als uitlegdocument voor teamleden, of als technische naslag. De kaarten hieronder brengen je direct naar het juiste niveau.

Leestip. Voor iemand die de app alleen wil gebruiken, zijn de secties tot en met Apple Health meestal genoeg. Het Bluetooth-gedeelte daaronder is expres veel technischer.

WattFlow appicoonZo werkt WattFlow als app

Op hoofdlijnen draait WattFlow om vier tabbladen: apparaten instellen, een workout kiezen, de workout rijden en daarna alles terugzien in Historie. De app is dus niet alleen een trainer-controller, maar ook een bibliotheek, workoutplayer, samenvattingsscherm en import/export-hub.

Instellingen

Hier koppel je trainer, powermeter, hartslag- en cadanssensoren. Ook kies je hier FTP- of level-modus, PowerMatch, FreeRide-gedrag, kalibratie en externe koppelingen zoals Strava en Apple Health.

Workouts

Dit is de trainingsbibliotheek. Je zoekt op naam, filtert op categorie of duur, markeert favorieten en importeert bestanden van buitenaf.

Workout

Hier rijd je de gekozen training echt. De app toont live vermogen, cadans en hartslag, bewaakt intervallen en laat je starten, pauzeren, hervatten en blokken overslaan.

Historie

Na afloop belanden workouts en geïmporteerde ritten hier. Vanuit deze tab kun je samenvattingen bekijken, delen, opnieuw exporteren en Strava-imports binnenhalen.

De kortste route voor een nieuwe gebruiker ziet er in de praktijk zo uit:

  1. Koppel je trainer en sensoren in Instellingen en controleer of de juiste bronnen actief zijn.
  2. Kies je trainingsbasis: FTP-modus als je met een vaste drempelwaarde werkt, of level-modus als je meer op gevoel wilt trainen.
  3. Kies of importeer een workout in de Workouts-tab.
  4. Rijd de sessie in de Workout-tab, waar WattFlow de trainer aanstuurt en elke seconde samples opneemt.
  5. Sla op en kijk terug in Historie, eventueel met upload naar Strava of sync naar Apple Health.
Belangrijk app-principe. Er is maar één actieve workout-sessie tegelijk. Zodra je een training selecteert, bouwt de app één centrale WorkoutSessionEngine op die live data leest, targets uitstuurt en samples bewaart.

Workouts en import

De Workouts-tab is tegelijk bibliotheek en importpunt. Je ziet ingebouwde trainingen en eigen imports samen in één lijst. De lijst is dus niet alleen een menu, maar ook de plek waar WattFlow zijn trainingsbestandencollectie beheert.

Onderdeel Wat de gebruiker ziet Wat er technisch gebeurt
Zoekveld Snel zoeken op naam van de workout Filtert de reeds geïndexeerde workoutlijst live in de UI
Categorieën en duurfilter Alleen relevante workouts tonen De app bewaart de gekozen filterstappen in AppModel
Favorieten Snelle terugkeer naar favoriete trainingen Favorieten hangen aan de bestandsnaam, zodat ze ook na opnieuw indexeren behouden blijven
Importeren Bestanden ophalen uit Bestanden, Mail of iCloud Drive .zwo en workout-.fit gaan naar de workoutbibliotheek; activiteiten-.fit en .tcx gaan naar Historie
Ververs Pull-to-refresh op de workoutlijst Forceert een nieuwe index van bundle-workouts en document-workouts

Een belangrijk detail is dat een .fit-bestand in WattFlow twee rollen kan hebben: het kan een echte workout met stappen zijn, of een al gereden activiteit. De app probeert daarom eerst te bepalen of het bestand workoutstappen bevat. Pas als dat niet zo is, behandelt WattFlow het als ritbestand en stuurt het door naar de activiteitenimport.

Waarom dit slim is. Voor de gebruiker voelt “importeren” als één knop, maar onder water beslist WattFlow zelf of een bestand in de workoutbibliotheek thuishoort of in de geschiedenis.

FTP, level, ERG en FreeRide

WattFlow heeft in de kern twee trainingsmodi: FTP-modus en level-modus. Daarbovenop heb je workoutgedrag zoals ERG en FreeRide. Dat klinkt veel, maar in gewone taal betekent het vooral: wil je trainen op een vaste vermogensbasis, of wil je meer op gevoel en niveau rijden?

Onderwerp FTP-modus Level-modus
Basis van de training Je echte FTP in watt Een virtuele FTP op basis van gewicht, geslacht en gekozen preset
Wat je invult Een vaste FTP-waarde Gewicht, geslacht en een startniveau zoals rustig, normaal of stevig
Wat je in de workout ziet Doelvermogen en live vermogen Huidig niveau plus live vermogen als feedback
Beste voor Gestructureerde intervallen en nauwkeurige zone-training Meer gevoel, meer speelruimte en rijden zonder constant op één exact wattgetal vast te zitten
Bijstellen tijdens de training Doel volgt de workoutblokken Je kunt niveaus omhoog of omlaag zetten; de app bewaart optioneel het laatst gebruikte niveau

FTP in gewone taal

FTP is in WattFlow de referentiewaarde voor zones, intervaldoelen, IF, TSS en andere trainingsscores. Als je in FTP-modus rijdt, vertaalt de app workoutpercentages direct naar doelwatts. Na een testtraining vraagt WattFlow altijd of een nieuwe FTP moet worden overgenomen. Bij gewone trainingen doet de app dat alleen als de berekende kandidaat hoger is dan je huidige FTP.

Level-modus in gewone taal

Level-modus is meer “rijden op gevoel”. Onder water rekent WattFlow nog steeds met een virtuele FTP, maar jij stuurt vooral via niveaus. De app gebruikt hiervoor een schaal van -3 tot 20, waarbij niveau 1 de neutrale start is en niveau 0 expres wordt overgeslagen. Hoe hoger het niveau, hoe hoger de factor die op de virtuele FTP wordt toegepast.

Concrete vertaalslag. In level-modus bepaalt je gewicht samen met de gekozen preset een virtuele FTP. Daarna zet WattFlow het gekozen niveau om naar een factor. Zo voelt level-modus simpeler voor de gebruiker, terwijl de workoutlogica onder water toch een vaste referentie houdt.

ERG en FreeRide

ERG is de stand waarin de trainer een doelvermogen probeert vast te houden. Dat is ideaal voor klassieke intervaltraining. FreeRide werkt anders: dan stuurt WattFlow geen vaste wattdoelen, maar een weerstandsniveau. De instellingen Start weerstand en Max weerstand bepalen hoe zwaar zo’n FreeRide-blok aanvoelt.

Workout rijden, opslaan en terugkijken

Zodra je een workout kiest, bouwt WattFlow een sessie op rond die training. Vanaf dat moment wordt de Workout-tab het live controlecentrum. Na afloop verschuift dezelfde sessie naar samenvatting, opslag en historie.

De levenscyclus van een workout is in de app grofweg als volgt:

  1. Selecteren: de gekozen workout wordt via prepareWorkout(...) klaargezet met training mode, FTP/virtual FTP en PowerMatch-instellingen.
  2. Starten: de gebruiker start via een overlay; tijdens de sessie kun je pauzeren, hervatten en blokken vooruit of achteruit zetten.
  3. Opnemen: de engine legt elke seconde samples vast met onder meer power, cadence, hartslag, targetinformatie en L/R-balans.
  4. Afronden: aan het einde bouwt de app een ActivityRecord, exporteert naar TCX/FIT en maakt een samenvattingsscherm.
  5. Bewaren of delen: daarna kun je de activiteit opslaan, naar Historie sturen en eventueel uploaden naar Strava.

Tijdens het rijden

De Workout-tab toont live vermogen, cadans, hartslag en de huidige intervalstatus. In level-modus staat het niveau centraal; in andere blokken vooral het doelvermogen.

Na afloop

De samenvatting laat onder meer duur, IF, TSS, vermogen, zones en eventueel PowerMatch-statistiek zien. Vanuit Historie kun je later dezelfde activiteit opnieuw bekijken of delen.

Historie is bovendien niet alleen voor app-workouts. Ook geïmporteerde .fit- en .tcx-activiteiten komen hier terecht. Daardoor wordt Historie het centrale archief van alles wat je in WattFlow wilt bewaren of terugzien.

Strava logoStrava-koppeling

De Strava-koppeling in WattFlow heeft twee hoofdrichtingen: uploaden van afgeronde activiteiten en importeren van recente ritten vanuit Strava naar je lokale historie.

Strava appicoon
Strava als upload- en importlaag

WattFlow gebruikt Strava niet alleen om ritten te versturen, maar ook om recente Strava-activiteiten weer terug de app in te halen als lokale historie.

Stap Wat WattFlow doet Wat dat voor de gebruiker betekent
Verbinden Start een OAuth-flow via ASWebAuthenticationSession Je logt in bij Strava zonder de app handmatig te verlaten
Uploaden Probeert eerst FIT te uploaden en valt alleen terug op TCX als FIT niet beschikbaar is Normaal gesproken krijgt Strava dus de rijkste export die WattFlow heeft
Importeren Haalt recente activiteiten op en leest streams voor tijd, vermogen, hartslag en cadans Een Strava-rit kan daardoor in WattFlow als gewone lokale activiteit terugkomen
Loskoppelen Verwijdert de lokale sessie en probeert ook remote te deauthoriseren De gebruiker kan de koppeling volledig resetten

Belangrijk om te weten: Strava-import is geen “live sync van alles”. WattFlow haalt bewust een beperkte set recente importeerbare ritten op. Bij import wordt een nieuwe lokale activiteit opgebouwd met samples, statistiek en een eventueel FTP-kandidaatadvies.

Praktisch voordeel. Dankzij die import heb je in Historie één plek voor zowel je app-workouts als ritten die eigenlijk in Strava begonnen zijn.

Apple Health logoApple Health-koppeling

Apple Health werkt in WattFlow vooral als betrouwbare gegevensuitwisseling met iOS: de app leest profielinformatie terug waar dat nuttig is, en schrijft afgeronde workouts weg zodat de training ook buiten WattFlow zichtbaar blijft.

Apple Health appicoon
Apple Health als iOS-gegevenslaag

Hier haalt WattFlow vooral profieldata zoals FTP en gewicht op, en hier schrijft de app na afloop je indoor cycling-workouts weer naar terug.

Richting Gegevens Effect in WattFlow
Health -> WattFlow Laatste lichaamsgewicht en laatste cycling FTP Level-modus kan gewicht updaten; FTP in de app kan worden bijgewerkt naar de Health-waarde
WattFlow -> Health Indoor cycling workout plus power, cadence, speed, distance, energie, hartslag en HRV waar beschikbaar Afgeronde trainingen leven niet alleen in WattFlow maar ook in Apple Health
WattFlow -> Health Nieuwe FTP-waarde Als Health-sync aan staat, probeert WattFlow wijzigingen in FTP ook terug te schrijven
Import sync Geïmporteerde activiteiten Ook geïmporteerde ritten kunnen naar Health worden weggeschreven als sync aan staat

Wanneer je Apple Health inschakelt, vraagt de app eerst toestemming. Als die toestemming ontbreekt of later wordt ingetrokken, schakelt WattFlow de sync niet blind door, maar zet hij die veilig terug. Zo voorkom je dat de UI “aan” zegt terwijl er onder water niets meer mag.

Goede mentale kapstok. Strava is in WattFlow vooral de sport-sociale export/importlaag. Apple Health is vooral de iOS-gegevenslaag voor workouts, FTP en profieldata.

Bluetooth logoBluetooth-verdieping

Vanaf hier schakelt het document een versnelling dieper. Dit deel beschrijft niet meer alleen wat je als gebruiker ziet, maar hoe WattFlow intern beslist welke sensor wint, welke data-stromen worden gebruikt en hoe trainersturing, PowerMatch, L/R-balans en kalibratie precies zijn opgezet.

Bluetooth appicoon
Hier begint de sensorlogica echt

In dit deel zie je hoe WattFlow tegelijk met FTMS, CPS, CSC en hartslagservices werkt en daarna beslist welke data op dat moment leidend is.

Instapadvies. Als je vooral wilt begrijpen wat begrippen als BLE, FTMS, CPS en CSC betekenen, begin dan meteen hieronder bij WattFlow for dummies. Vanaf hoofdstuk 1 wordt het technisch preciezer.

0. WattFlow for dummies

Als je geen achtergrond hebt in Bluetooth of fietssensoren, dan helpt deze simpele vertaling: WattFlow probeert eigenlijk maar drie dingen tegelijk te doen:

  1. data lezen van sensoren, zoals vermogen, cadans en hartslag;
  2. beslissen welke sensor op dat moment het meest betrouwbaar is;
  3. de trainer opdrachten geven om zwaarder of lichter te worden.
Heel kort. BLE is de draadloze verbinding. FTMS is de taal waarmee WattFlow een smart trainer kan besturen. CPS is de taal waarmee een powermeter zijn vermogen doorgeeft. CSC is de taal voor snelheid en cadans. WattFlow luistert dus naar meerdere "talen" tegelijk.
In gewone mensentaal:

iPhone met WattFlow
  <- leest watts van trainer of powermeter
  <- leest cadence van trainer, cadanssensor of powermeter
  <- leest hartslag van HR-band of trainer
  -> stuurt trainer zwaarder / lichter via FTMS

Doel:
  de juiste live waarden tonen
  workouts correct opnemen
  de trainer op het juiste vermogen houden

0.1 Begrippenlijst

Begrip Betekent in gewone taal Rol in WattFlow
BLE Bluetooth Low Energy: de zuinige draadloze verbinding tussen iPhone en sensoren De transportlaag waar alles overheen loopt
FTMS Fitness Machine Service: de "smart trainer taal" Wordt gebruikt om trainerdata te lezen en om ERG, weerstand en spin-down te sturen
CPS Cycling Power Service: de "powermeter taal" Levert vermogen, vaak ook cadans, soms ook pedal balance, en ondersteunt zero offset
CSC Cycling Speed and Cadence: de taal voor snelheid- en cadanssensoren Wordt vooral gebruikt om cadans uit crank-omwentelingen te halen
Service Een categorie functies op een BLE-device Bijvoorbeeld FTMS, CPS, CSC, Heart Rate of Battery
Characteristic Een concreet datapunt of commando binnen zo'n service Bijvoorbeeld "Indoor Bike Data" of "Control Point"
Advertising Het korte visitekaartje dat een apparaat tijdens scannen uitzendt Helpt WattFlow snel te raden wat een device waarschijnlijk is
Trainer De smart trainer die zwaarder of lichter kan worden gemaakt Houdt de weerstand vast en ontvangt FTMS-opdrachten
Powermeter De meter die je echte vermogen in watt meet Kan intern in de trainer zitten of extern op fiets, pedalen of crank
Cadence Je trapfrequentie in omwentelingen per minuut Wordt gebruikt voor live-weergave en als gate voor PowerMatch
ERG Een modus waarin de trainer een doelvermogen probeert vast te houden WattFlow stuurt dan target watts naar de trainer
Resistance Een modus waarin je een vaste weerstand instelt in plaats van vaste watts Gebruikt in FreeRide; WattFlow stuurt dan een FTMS resistance level
PowerMatch De app vergelijkt trainer-power met externe powermeter-power en corrigeert de trainer Maakt ERG nauwkeuriger vanuit het perspectief van je externe powermeter
L/R-balans Hoeveel van je vermogen van links komt en hoeveel van rechts Wordt gelogd en opgeslagen, maar stuurt de trainer niet aan
RR-interval De tijd tussen twee hartslagen Nodig voor HRV-berekeningen zoals RMSSD
RMSSD Een bekende HRV-maat, afgeleid uit RR-intervallen WattFlow berekent die alleen bij voldoende stabiele RR-data
Kalibratie Een sensor opnieuw ijken of nulstellen Bij trainers via spin-down, bij powermeters via zero offset

1. Kort samengevat

  • WattFlow houdt een centrale apparaatregistratie bij per CBPeripheral.identifier. Vanuit die ene registry worden rollen afgeleid: trainer, powermeter, cadanssensor en hartslagmeter.
  • Een apparaat met FTMS-capability geldt in deze code als trainer. Zo'n device mag niet tegelijk als externe powermeter of cadanssensor worden gebruikt.
  • Live cadans en live vermogen worden niet hard aan een enkel device gekoppeld, maar telkens opnieuw gerouteerd op basis van "recente" samples. De recency window is 1,5 seconde.
  • PowerMatch stuurt nooit direct op een externe powermeter. Het gebruikt de externe CPS-power als feedbackbron, maar de trainer krijgt nog steeds FTMS ERG-targets.
  • L/R-balans is metadata. Die wordt gelogd, opgenomen in workout-samples en meegenomen in FIT-export, maar niet gebruikt om de trainer aan te sturen.
  • Trainer-kalibratie is FTMS spin-down; externe powermeter-kalibratie is CPS zero offset. De trainer-spin-down wordt in de app afgerond op basis van FTMS-snelheid en een timer, niet op basis van een aparte geparste eindwaarde.

2. Hoofdarchitectuur

De Bluetooth-stack bestaat functioneel uit twee lagen:

  • BluetoothManager: scan, connect, capability-detectie, characteristic-subscriptions, parsing van BLE-pakketten, live-routing, FTMS-control, kalibratie en logging.
  • WorkoutSessionEngine: workout-timer, 1 Hz sample-opname, trainer-aansturing per blok en de PowerMatch-regelaar bovenop de BLE-data.
CoreBluetooth (CBCentralManager / CBPeripheral)
        -> BluetoothManager
           -> deviceRegistry[UUID]
           -> rolpointers: trainer / power / cadence / heart rate
           -> raw streams per device (FTMS, CPS, CSC, HR)
           -> live routing (power, cadence, HR)
           -> FTMS control / CPS calibration / logs
        -> WorkoutSessionEngine
           -> 1 Hz workout tick
           -> PowerMatch controller
           -> ActivitySample recording
           -> FIT / activiteit export
Belangrijk ontwerpprincipe. De code bewaart een enkele registry per periferal UUID en leidt daar pas later rollen uit af. Daardoor kan een trainer niet per ongeluk tegelijk als "trainer" en als "powermeter" in de pipeline terechtkomen.

3. Rollenmodel per apparaat

Rollen worden niet alleen uit advertising gehaald, maar ook later verrijkt met services en characteristics die na connect bekend worden.

Rol Wanneer geldig Belangrijk gevolg
Trainer Device heeft FTMS-advertising, FTMS-service of FTMS control/data characteristics Mag ERG en weerstand ontvangen. Wordt uitgesloten als externe power- of cadansbron.
Externe powermeter Device heeft Cycling Power, maar geen FTMS Mag externe powerfeedback leveren voor live power en PowerMatch.
Cadanssensor Device heeft CSC of CPS, maar geen FTMS Mag live cadans leveren. Wiel-only speed sensors worden actief geweerd.
Hartslagmeter Device heeft Heart Rate service Levert BPM en eventueel RR-intervallen voor HRV.

Extra guards die in de code zitten:

  • Als een gekozen powermeter later toch FTMS-capability blijkt te hebben, wordt die direct losgekoppeld en wordt de voorkeur gewist.
  • Hetzelfde geldt voor een gekozen cadanssensor die later FTMS blijkt te hebben.
  • Een geselecteerde trainer moet na service-discovery echt FTMS hebben; anders verbreekt WattFlow die connectie meteen.
  • Een wheel-only CSC-sensor zonder crank-data telt niet als cadanssensor. Ook een naam die op een pure snelheidssensor duidt (speed of spd, zonder cad) wordt geweerd.

4. BLE services en characteristics die WattFlow gebruikt

Vertaling. Zie een service als een map met functies op een apparaat. Een characteristic is dan een concreet datapunt of commando in die map. Dus: FTMS is de map, Indoor Bike Data en Control Point zijn twee onderdelen daarin.
Service UUID Characteristics Gebruik in WattFlow
Fitness Machine Service (FTMS) 1826 2AD2 Indoor Bike Data, 2AD9 Control Point, 2ACC Feature, 2AD8 Supported Power Range Trainerdetectie, live speed/cadans/power, HR-forwarding, ERG, weerstand en trainer spin-down.
Cycling Power Service (CPS) 1818 2A63 Measurement, 2A65 Feature, 2A66 Control Point Externe powermeterdata, trainer-CPS fallback, pedal balance, cadans uit crank-data, zero-offset kalibratie.
Cycling Speed and Cadence (CSC) 1816 2A5B Measurement Cadans uit crank-revoluties, plus detectie van wheel-only speed sensors.
Heart Rate 180D 2A37 Measurement BPM, RR-intervallen en HRV/RMSSD.
Battery 180F 2A19 Battery Level Batterijniveau per verbonden sensor voor UI en status.

De manager subscribe't waar mogelijk op notify/indicate en leest kenmerken die readbaar zijn. Voor FTMS en CPS betekent dit dat control points niet alleen voor schrijven worden gebruikt, maar ook voor het ontvangen van responses.

5. Scannen, detecteren en verbinden

5.1 Scanmodes

Scanfunctie Services Timeout Opmerking
startScanTrainer() FTMS 30 s Trainer is expliciet FTMS; alleen power of cadans is niet genoeg.
startScanPowerSource() CPS + FTMS 30 s FTMS wordt mee gescand om trainers te herkennen en later te blokkeren.
startScanCadence() CSC + FTMS + CPS 30 s CPS telt ook mee, omdat sommige meters cadans uit CPS leveren.
startScanHeartRate() Heart Rate 30 s Alleen directe HR-sensoren.
scanAllSources() FTMS + CPS + CSC + Heart Rate 30 s Settings gebruikt dit voor een brede scan.

5.2 Device discovery

  • Bij didDiscover slaat WattFlow het periferalobject op, bewaart RSSI, onthoudt geadverteerde services en verrijkt het apparaatrecord.
  • De lijsten in Settings worden gesorteerd op hoogste RSSI eerst, daarna alfabetisch op naam.
  • Advertised services zijn niet de hele waarheid. Het capability-model wordt verder aangevuld na service- en characteristic-discovery.

5.3 Auto-connect gedrag

  • Na een volledige Settings-scan verbindt de huidige UI alleen de trainer automatisch, en dan alleen na 6 seconden scan-time.
  • Powermeter, cadanssensor en HR-sensor vragen in de huidige Settings-flow een expliciete gebruikerskeuze.
  • Een handmatige trainer-disconnect zet een cooldown van 180 seconden om autoconnect-loops te vermijden.

6. Live datastromen en prioriteitsregels

BluetoothManager.recomputeLiveMetrics() is het knooppunt waar alle live-metrics opnieuw worden bepaald. Die functie draait niet alleen na nieuwe BLE-notificaties, maar ook via een watchdog-task elke 400 ms.

Een stream telt als "recent" als de laatste sample maximaal 1,5 seconde oud is. Alle prioriteitsbeslissingen hieronder zijn dus recency-based, niet alleen selection-based.

6.1 Cadans-routing

De prioriteitsvolgorde voor live cadans is exact als volgt:

  1. Geselecteerde externe cadanssensor via CSC
  2. Geselecteerde externe cadanssensor via CPS
  3. Trainer via CPS
  4. Trainer via CSC
  5. Trainer via FTMS
  6. Geselecteerde powermeter via CPS

Alleen de eerste recente bron wint. Daardoor kan een externe cadanssensor een trainer-cadans volledig overrulen zolang hij verse data blijft leveren.

6.2 Vermogens-routing

De vermogensprioriteit hangt af van twee dingen:

  • is er een externe powermeter geselecteerd?
  • staat de speciale modus "external balance from powermeter" runtime-matig aan?

Standaard zonder externe powermeter

  1. Trainer via CPS
  2. Trainer via FTMS

Met externe powermeter geselecteerd, normale power-routing

  1. Externe powermeter via CPS
  2. Trainer via CPS
  3. Trainer via FTMS

Met externe powermeter geselecteerd, maar "external balance routing" actief

  1. Trainer via CPS
  2. Trainer via FTMS
  3. Externe powermeter via CPS, alleen als tijdelijke fallback wanneer trainer-power niet vers genoeg is
Belangrijk detail. In deze "external balance routing" modus blijft live power dus trainer-gebaseerd zolang dat kan, terwijl L/R-balans alsnog van de externe powermeter mag komen. Dat is expres zo gebouwd.

6.3 Idle filter en pedaling-detectie

  • De app beschouwt iemand als "pedaling" wanneer er een recente cadansbron is en de cadans minimaal 5 rpm is.
  • Als er niet wordt getrapt en het ruwe vermogen lager of gelijk is aan 25 W, zet WattFlow de getoonde live power op 0 W.
  • Bij hogere ruwe power blijft de waarde zichtbaar, ook zonder geldige pedaling-state.

6.4 Hartslag-routing

  • Een directe HR-sensor via Heart Rate service levert BPM en eventueel RR-intervallen.
  • Een trainer mag via FTMS ook hartslag forwarden. Dat levert alleen BPM, geen RR-intervallen en dus geen HRV.
  • Trainer-forwarded HR wordt alleen gebruikt als er geen directe HR-sensor actief is, of als de gebruiker expliciet viaTrainer heeft gekozen.
  • Voor HRV/RMSSD is dus een directe HR-sensor nodig die RR-intervallen uitzendt.

7. Parsing per protocol

Met "parsing" bedoelen we hier simpelweg: een ontvangen BLE-pakket uitlezen en omzetten naar bruikbare app-waarden zoals watts, rpm of bpm.

7.1 FTMS Indoor Bike Data

  • Instantaneous speed wordt alleen gelezen als FTMS-flag bit 0 aangeeft dat speed wel in het pakket zit.
  • Instantaneous cadence komt uit FTMS bit 2 en wordt omgerekend van 0,5 rpm-units naar hele rpm.
  • Instantaneous power komt uit FTMS bit 6 en voedt de trainer-FTMS powerstream.
  • Heart rate komt uit FTMS bit 9 en kan live BPM voeden, maar niet de RR/HRV-keten.
  • Supported Power Range wordt gelezen om de maximale trainer-target power op te slaan. Alleen de maximumwaarde wordt gebruikt; min en step worden niet verder verwerkt.

7.2 Cycling Power Measurement

  • Instantaneous power komt uit de eerste signed 16-bit powerwaarde en wordt negatief-clamped naar minimaal 0 W.
  • Crank revolution data wordt gebruikt om cadans te berekenen, mits CPS-flag bit 5 aanwezig is.
  • De code loopt netjes langs optionele velden heen (pedal balance, torque, wheel data) voordat crank-data gelezen wordt.
  • Cadans uit CPS wordt alleen geaccepteerd als de uitkomst tussen 0 en 250 rpm ligt.

7.3 Pedal balance en afgeleide links/rechts power

  • Als CPS-flag bit 0 aangeeft dat pedal power balance aanwezig is, leest de app byte 4 en deelt die door 2. Dat geeft een percentage.
  • De huidige code interpreteert CPS-flag bit 1 als "left referenced".
  • Alleen wanneer die referentie expliciet links is, leidt WattFlow links/rechts wattages af uit het totale vermogen.
  • Als de referentie niet expliciet links is, bewaart de app wel het percentage maar markeert de referentie als onbekend en vult geen afgeleide links/rechts watts in.
  • Dit geldt zowel voor trainer-CPS als voor externe CPS-data.

7.4 CSC Measurement

  • CSC met crank-data levert cadans op basis van cumulatieve crankrevoluties en event time in stappen van 1/1024 seconde.
  • CSC zonder crank-data geldt in runtime als wheel-only. Als de gebruiker zo'n sensor toch als cadence selecteert, verbreekt de app die connectie weer.

7.5 Heart Rate Measurement en HRV

  • RR-intervallen worden uit Heart Rate Measurement gehaald als de RR-flag aanwezig is.
  • De app accepteert alleen RR's tussen 300 en 2000 ms.
  • Daarnaast wordt een sprongdetector gebruikt: een nieuwe RR mag niet te ver afwijken van de mediaan van recente waarden. De drempel is de maximum van 250 ms en 30% van de mediaan.
  • HRV/RMSSD wordt pas "stabiel" nadat voldoende geaccepteerde RR's zijn verzameld: minimaal 10 accepted intervallen of minimaal 15 seconden warmup.
  • RMSSD wordt berekend over een rolling window van 60 seconden.
  • Alleen geaccepteerde RR-intervallen gaan naar HRV en export; ruwe BLE-RR's blijven alleen in de logregel zichtbaar.

8. PowerMatch: hoe de control loop precies werkt

PowerMatch is ondergebracht in een aparte PowermatchController. Die controller draait los van de UI-smoothing. De workout-engine tikt hem op 1 Hz om oscillatie op BLE-jitter te beperken.

In gewone taal. Stel dat je workout 250 W vraagt. De trainer denkt misschien dat je 250 W trapt, maar je externe powermeter meet in werkelijkheid 240 W. PowerMatch verhoogt dan stap voor stap het trainer-target, totdat je externe powermeter dichter bij die 250 W uitkomt.

8.1 Wanneer PowerMatch actief kan zijn

  • De gebruiker heeft PowerMatch aangezet in Settings.
  • Er is een trainer geselecteerd.
  • Er is een externe powermeter geselecteerd; een trainer mag niet als externe powermeter tellen.
  • De workout draait in ERG-modus.

Bij een daadwerkelijke tick moet daarna ook nog aan runtime-voorwaarden worden voldaan:

  • workout draait en is niet gepauzeerd;
  • trainer-control is beschikbaar: trainer connected, FTMS control point gevonden en control verkregen;
  • externe powermeter-power is vers genoeg;
  • de gebruikte cadansbron is vers genoeg;
  • cadans is minimaal 5 rpm.

8.2 Welke samples PowerMatch gebruikt

Input Bronregel Opmerking
Trainer power Nieuwste van trainer-CPS en trainer-FTMS Voor afwijkingsstatistiek en vergelijking met powermeter.
Powermeter power Alleen externe CPS Dit is de feedbackbron voor de control loop.
Cadans De al gerouteerde live cadans Dus ook een aparte cadanssensor kan de gating van PowerMatch bepalen.

8.3 Smoothing en sampleverwerking

  • Powermeter-power krijgt een EWMA-smoothing met een tijdconstante van 2,0 seconden.
  • Als de gap tussen twee powermeter-samples groter is dan 2,0 seconden, reset de smoothwaarde direct naar de ruwe sample.
  • Naast die smoothing houdt de controller afwijkingsstatistiek bij: delta = powermeter - trainer, plus gemiddelde, gemiddeld absolute afwijking en gemiddelde over de laatste 10 seconden.

8.4 Regelparameters

Parameter Waarde Betekenis
Smoothing tau2,0 sEWMA voor powermeter feedback
Stale threshold2,0 sSample ouder dan dit telt niet meer mee
Deadband3 WGeen regelactie rond target
Kp0,22Proportionele versterking
Ki0,07 per secondeIntegrator
Integrator clamp+/-35 WBegrenst ophoping
Max correction110 WBovengrens op opwaartse correctie
Rate up160 W/sHoe snel target omhoog mag
Rate down45 W/sHoe snel target omlaag mag
Target jump reset20 WVanaf hier reset de regelaar bij blokwissel
Integrator hold na jump2,0 sVoorkomt na-ijlen van vorige interval
Large positive error boost start30 WExtra hulp bij duidelijke undershoot

8.5 Formule in woorden

  1. Error: error = intervalTarget - smoothedPowermeter.
  2. Deadband: als |error| <= 3 W, wordt de effectieve error op 0 gezet.
  3. Integrator: integrator += Ki * error * dt, daarna clamp op +/-35 W.
  4. Extra boost bij grote positieve error: boven 30 W positieve fout komt er nog een extra boost bovenop.
  5. Correctie: Kp * error + integrator + eventuele boost.
  6. Downward guard: neerwaartse correctie wordt extra beperkt zodat herstelblokken niet te ver onder doel wegzakken. In code is de negatieve limiet max(12 W, min(110 W, 18% van target)).
  7. Rate limiting: het nieuw toe te passen trainer-target mag per seconde sneller omhoog dan omlaag.

8.6 Gedrag bij target-jumps

  • Als een nieuw intervaltarget meer dan 20 W verschilt van het vorige, reset de controller zijn integrator, laatste error en correctie.
  • De smooth powermeterwaarde wordt dan hard gereset naar de laatste ruwe powermeter-sample, zodat restenergie uit het vorige blok niet blijft doorwerken.
  • Bij grote positieve sprongen krijgt het target een korte extra boost. Die boost is proportioneel aan de grootte van de sprong, met een schaalfactor van 0,32 en een maximum van 110 W.
  • Bij neerwaartse sprongen is er geen vergelijkbare negatieve jump-boost.

8.7 Fallback-gedrag

  • Als PowerMatch niet kan regelen op het moment dat een blok start of een target opnieuw toegepast wordt, valt de engine terug op het basis-ERG-target van het interval.
  • Tijdens een lopend, stabiel interval zonder blokwissel doet de 1 Hz tick die fallback niet telkens opnieuw. In dat geval blijft het laatst verzonden trainer-target staan totdat er weer een geldige regelactie komt of een nieuw blok begint.
  • Alle FTMS target-writes worden in BluetoothManager nog eens 200 ms gede-bounced en worden niet opnieuw verstuurd als het target onveranderd is.

9. FTMS control en trainer-aansturing

9.1 FTMS control acquisition

Voordat ERG, weerstand of trainer-kalibratie kan werken, probeert WattFlow trainer-control te claimen via FTMS control point opcode 0x00.

  • Pas na een succesvolle response wordt hasFtmsControl waar.
  • Als er op dat moment al een pending ERG-target of pending resistance is, stuurt de app eerst Start/Resume met opcode 0x07.
  • Daarna worden pending ERG, pending resistance en pending calibration geflusht.

9.2 Welke FTMS opcodes WattFlow verstuurt

Actie Bytes Uitleg
Request control 00 FTMS-control claimen
Start / resume 07 Alleen wanneer nodig na control-acquisitie
Set target power 05 lo hi Signed 16-bit watt target, little-endian
Set target resistance level 04 raw Eerste UI-mapping naar 0-100%, daarna naar FTMS level x 10
Start trainer spin-down 13 01 Kalibratie-opdracht voor FTMS trainer

9.3 Weerstandsmodus (FreeRide)

  • De UI werkt met 0-100% weerstand.
  • Intern rekent WattFlow dat om naar een FTMS resistance level tussen 0,0 en maxLevel, met 0,1 resolutie.
  • Default maxLevel is 10,0, maar dit komt uit Settings.
  • De ruwe FTMS-byte wordt berekend als (percent / 100) * maxLevel * 10, afgerond en geclamped tussen 0 en 255.

Voor ERG wordt geen expliciet "disable ERG" FTMS-commando gestuurd wanneer een workout stopt. De code kiest daar bewust voor, omdat trainers daar niet uniform op reageren.

10. L/R-balans: bronkeuze en opslag

L/R-balans leeft in WattFlow los van de vermogensregeling. Dat is belangrijk: de bron van live power en de bron van L/R-balans hoeven dus niet per se hetzelfde apparaat te zijn.

Simpel gezegd: L/R-balans vertelt hoeveel procent van het vermogen van links komt en hoeveel van rechts.

10.1 Bronkeuze voor balance snapshots

  1. Als de runtime-flag preferExternalBalanceFromPowermeter aan staat en de externe powermeter heeft recente balansdata, wint de externe powermeter altijd.
  2. Anders volgt de balancebron eerst de actuele live power source:
    • powermeterCps -> gebruik externe powermeterbalance
    • trainerCps -> gebruik trainer-CPS-balance
  3. Als de live power source iets anders is, bijvoorbeeld trainerFtms, valt de code terug op recente externe balance als die er is, anders op trainer-CPS balance.

10.2 Relatie met PowerMatch en de Settings-toggle

  • Als PowerMatch aangezet wordt, forceert de Settings-logica ook externalBalanceFromPowermeterEnabled = true.
  • De feitelijke runtime-flag preferExternalBalanceFromPowermeter staat echter alleen aan wanneer PowerMatch uit staat. Bij PowerMatch is live power toch al extern, dus extra balance-routing is dan niet nodig.
  • Resultaat: tijdens PowerMatch komt balance in de praktijk vanzelf van dezelfde externe powermeter die ook de powerfeedback levert.

10.3 Vastlegging in workout en export

  • Elke workout-tick (1 Hz) maakt een ActivitySample aan met balancePercent, balanceReference, leftPowerW en rightPowerW.
  • De engine logt balance maximaal een keer per seconde en forceert elke 5 seconden een heartbeat-regel als de tekst niet verandert.
  • In FIT-export schrijft WattFlow:
    • het standaard FIT-veld left_right_balance;
    • developer fields left_power_w en right_power_w.
  • Als alleen een balance-percentage aanwezig is en geen expliciete links/rechts watts, probeert de FIT-export die achteraf te reconstrueren uit totaal vermogen plus de bewaarde referentie.

11. Kalibratie

Kalibratie betekent hier: de sensor of trainer eerst netjes "rechtzetten" zodat de meting of weerstand daarna beter klopt. Bij een trainer heet dat vaak spin-down, bij een powermeter vaak zero offset.

11.1 Trainer spin-down via FTMS

Voorwaarden om te starten:

  • er is een verbonden trainer;
  • het FTMS control point is ontdekt;
  • er loopt nog geen trainer-kalibratie.

Fasen in de app:

  1. requestingControl: app wil trainer-control claimen.
  2. pedalUp: spin-down command is verstuurd; gebruiker moet optrekken tot doeltempo.
  3. coastDown: zodra FTMS-speed minimaal 35 km/u is, moet de gebruiker stoppen met trappen.
  4. waitingForResult: zodra FTMS-speed tot 2 km/u of lager gedaald is, wacht de app nog 1 seconde.
  5. finished: daarna markeert de app de spin-down als afgerond.
Belangrijk implementatiedetail. De app parse't op dit moment geen aparte FTMS "final calibration result". De afronding gebeurt heuristisch: FTMS-speed moet onder 2 km/u komen, daarna volgt een timer van 1 seconde. Als de trainer in die tussenfase weer boven 3 km/u uitkomt, gaat de fase terug naar coastDown.

Mislukkingen die expliciet worden afgevangen:

  • geen verbonden trainer;
  • trainer heeft geen FTMS control point;
  • FTMS control request wordt geweigerd, bijvoorbeeld omdat een andere app de trainer nog bestuurt;
  • FTMS spin-down opcode retourneert een foutresultaat.

11.2 Externe powermeter zero offset via CPS

Voorwaarden om te starten:

  • de verbonden powerbron is een echte externe powermeter;
  • de CPS control point characteristic is ontdekt;
  • er loopt nog geen zero-offset proces.

Werking:

  1. De app stuurt CPS control point opcode 0x0C (start offset compensation).
  2. De gebruiker moet de cranks stilhouden.
  3. De response moet beginnen met 0x20, daarna requestcode 0x0C en resultaat 0x01 voor succes.
  4. Als er nog twee bytes volgen, bewaart WattFlow die als gerapporteerde zero-offset waarde.

Deze kalibratie geldt alleen voor de externe powermeter. De trainer-interne powermeter wordt via deze route niet gekalibreerd.

12. Wat wordt er tijdens een workout vastgelegd?

  • De workout-engine tikt elke seconde.
  • Per tick wordt een ActivitySample gemaakt met power, heart rate, HRV/RMSSD, RR-intervallen, cadence, block-index, target-percent en L/R-balansvelden.
  • Powermatch houdt daarnaast sessiestatistiek bij: gemiddelde afwijking, gemiddelde absolute afwijking en aantal samples.
  • De uiteindelijke ActivityRecord bewaart die PowerMatch-samenvatting naast de ruwe samples.

13. Praktische gevolgen en randgevallen

  • Een trainer met CPS en FTMS kan prima zowel trainer-power als trainer-cadans leveren, maar geldt nooit als externe powermeter in de app.
  • Als je een externe powermeter kiest en alleen L/R-balans van buiten wilt, dan blijft live power normaal van de trainer komen zolang trainer-power vers is.
  • Als trainer-power tijdelijk wegvalt in die modus, kan live power kort terugvallen op de externe powermeter.
  • Als live power uit FTMS komt maar de trainer geen CPS pedal balance heeft, kan de app toch externe L/R-balans tonen of vastleggen zolang die externe balans recent is.
  • Voor betrouwbare HRV heb je een directe HR-sensor met RR-intervalsupport nodig; trainer-forwarded HR is daarvoor niet genoeg.
  • De trainer max target power uit FTMS Supported Power Range wordt gebruikt om workout-targets te clampen. Daardoor zal de app geen doelvermogen boven die bekende bovengrens sturen.

14. Relevante bronbestanden

Bestand Rol in de architectuur
WattFlow/Services/BluetoothManager.swift CoreBluetooth, device registry, routing, FTMS/CPS-control, kalibratie, logs
WattFlow/Services/WorkoutSessionEngine.swift Workout-timer, sample-opname, trainer-aansturing, PowerMatch-regelaar
WattFlow/Services/SettingsStore.swift Persistente settings voor powermatch, externe balance en device-keuzes
WattFlow/Views/SettingsTabView.swift UI-logica rond scan, selectie, PowerMatch-toggle en externe balance-toggle
WattFlow/Views/ContentView.swift Synchroniseert runtime-flag preferExternalBalanceFromPowermeter
WattFlow/Services/FitExporter.swift Schrijft L/R-balans, left/right power en RR-data naar FIT
Aanvullende documenten. Er zijn nu ook vier compacte vervolgdocumenten: WattFlow_Productpagina.html voor de publieke product- en featurepresentatie, WattFlow_Screenshotplan.html voor screenshots, crops en assetbriefing, WattFlow_Sequentiediagrammen.html voor alleen de hoofdflows in volgorde, en WattFlow_Operator_Handleiding.html voor dagelijks gebruik, onboarding en support.

Einde hoofddocument. Gebruik deze pagina voor de volledige uitleg, het diagramdocument voor volgorde en interacties, en de operator handleiding voor dagelijkse praktijk.