* Looks like dead code Dead code????? * Improvement of the MovingAverager startup Improvement of the MovingAverager's startup-behaviour, to allow use for the dragcalculation. In this approach, the defaultValue is only used when the array isn't sufficiently filled (less then two pushed values), but after that it is directly released and the average only depends on pushed values. This is especially important for the drag calculation, as the drag calcuation is sufficiently robust in handling errornous values, and drag factors might make big jumps in the beginning (i.e. from the middle setting to a maximum value). * Allow the settings to set a specific priority The nice-level of -20 is arbitrary and very counterproductive, as it dwarfs OS processes including the internal timer. This results in a more noisy measurement, even on a high-speced Pi 4. As the priority is dependent on the hardware/software configuration of the Pi anyway, I made the configuration setting more granular. * Added Concept2 PM5 wiring diagram Added wiring diagram for the PM5 (with 18V generator) * Update server.js * Delete app/engine/averager directory * Creation of the series.js Creation of a series of values, as an abstraction away from the array, with added standard functionality. This is the basis for the more complex functions like the regression series. * Update the server.js to adapt to new architecture Server.js has been adapted to fit the new architecture. It is now in control of the session, directing others how to proceed. * Deleted Timer.js as it has become obselete Deleted Timer.js as it has become obselete * Updated to more abstract Flywheel.js Renamed and rewritten to Flywheel.js. It provides an abstraction from the individual currentDt's and provides a more abstract view of the flywheel and its current state, which can be interrogated by Rower.js * Created an initial testset for Flywheel.js An initial set of tests to check whether Flywheel.js works as expected. * Renaming variables and added new functionality Renamed variables to fit the naming scheme of Rower.js. Added the option to create a RowingData file, hich can be read by RowingData, RowsAndAll and Excel. Also added new fields in the notes of a TCX, including HRR. * Update Pm5Constants.js * Update to the constants Update to the constants, to make them closer to the actual firmware options. Also I added the Row to the name, as Concept2 also seems to do this on their current firmware. * Added additional defensive programming Added additional defensive programming * updated variable names Updated variable names to keep them in sync with RowingStatistics * Added metrics and updated variable names Added new metrics, like projected time and distance, stroke calories Updated variable names to keep them in sync with RowingStatistics * Added metric, states and variable rename Added the dragfactor metric, aded the state for the workoutType, workoutDurationType, workoutState, RowingState and strokeState Renamed most variable to allign them with RowingStatistics * Added metrics, rename variables Added several metrics (driveLength, driveTime, strokeRecoveryTime, strokeDistance, peakDriveForce, averageDriveForce and workPerStroke) Alignment of variable names with RowingStatistics.js * Renamed variables Renamed variables to align them with the RowingStatistics naming convention * Update variable definitions and naming Update to the variable definition of speed: as almost all consumers use the m/s unit, we now use this internally, and only convert to km/h for the bike Alignment with the naming scheme of RowingStatistics * Update based on PM5 spec workoutDurationType was a more complex enum * Complete redesign Complete redesign: * Switched to a Finite State machine to maintain session state * To reduce complexity, it directly inspects Rower.js, instead of awaiting messages to be processed * Small snippets process all data, where updates are grouped based on their update moment * Renaming all variables to clearly identify when they are updated (as determined by the state machine) * Introduced a first version of interval management, as a preperation for future solutions with more complex training scheme's This should help in reducing the code base while making the code easier to understand. * Rename and adoptation to the new architecture Split RowingEngine into two blocks: * Rower (which represents the key metrics from the rower) and * flywheel (which represents the key metrics of the flywheel, formally known as MovingFlankDetector). This should simplify the reading of the code, as Flywheel reports key metrics without the need to many specific configuration parameters. Also added many metrics (including force curve, power curve and handle speed). * Update and rename RowingEngine.test.js to Rower.test.js Update to fit Rower.js better, includes testing with known rowers * Introduction of VO2Max (Beta) Introduction of a Beta-version of VO2-Max, which will be calculated each time a tcx-file is created. It introduces two versions of VO2-Max calculation: * extrapolation-based VO2Max: it extrapolates the power to maxpower based on MaxHR and the relation between HR and power in the session * interpolation based VO2Max: it projects the session onto a 2K, and then calculates the VO2Max based on Concept2's research * Beta version Beta version of the BucketLinearSeries to accomodate VO2Max * Create CurveAligner.js CurveAligner for cleaning up the (power, force and handle velocity) curves * Create Series.test.js Added test-class to series.js, as it is so fundamental to OpenRowingMonitor * Create StreamFilter.js Streamfilter implements a running streamfilter. It is designed to be robust to outliers, and replaces all averaging functions. * Create StreamFilter.test.js Tests the steamfilter * Create OLSLinearSeries.js Creation of the OLSLinearSeries, to allow for drag calculation, VO2Max calculation and projecting calories, distance and time. * Create OLSLinearSeries.test.js Test of the Simple Linear Regression method * Create FullTSQuadraticSeries.js FullTSQuadraticSeries delivers Full Theil-Senn Quadratic regression, which is essential to determine Angular Velocity and Angular Acceleration in a noise-resistant way. These metrics are essential for force curve, power curve, handle velocity curve and stroke detection (as it is triggered by the force on the handle). * Create FullTSQuadraticSeries.test.js Test of the FullTSQuadraticSeries * Create curveMetrics.js Object to manage the specifics of curves * Delete curveMetrics.js * Create curveMetrics.js Object to create curves and the associated curve metrics * Update rowerProfiles.js Update to accomodate the new RowingEngine, and its parameters * Renaming variables Renaming variables to align with the variable renaming in RowingStatistics.js * Rename variables Update variables to align with RowingStatistics naming conventions * Update GpioTimerService.js * Added example CurrentDt curve Added example CurrentDt curve for explanation of good and bad noise. * Testfile for Concept2 RowErg Testfile for Concept2 RowErg, 2000 meters, for validation purposes * Increasing consistency in logging Increased consistency in logging specific messages * Improved logging to facilitate configuration Improved the logging of specific data to facilitate an easier setup for new rowers. * Update backlog.md * Update rower_settings.md * Adaptation to the newly developed physics model Adaptation of the text to the newly developed physics model. * Removed the settings analysis as it isn't needed Removed this tool, as it isn't up-to-date with the current physics model, and logging does a better job in helping the analysis. * Updated to reflect the new approach Updated the setup procedure to: * Adapt it to the new physics model * Adapt it to the new logging-based setup procedure. * Added the Concept2 RowErg Added the Concept2 RowErg configuration * Added the Concept2 as test-object Added the Concept2 as test-object * Added the Concept2 as test-object Added the Concept2 as test-object * Update Rower.test.js * Adaptation to C2 Flywheel Inertia change Adaptation to C2 Flywheel Inertia change * Created to explain the current architecture Created an architecture document to explain the current architecture, as well as the major components in Open Rowing Monitor to maintainers and reviewers of the code. * Create hardware_setup_Concept2_RowErg.md * Added Concept 2 manual Added concept 2 RowErg manual, as some minor updates * Added performance improvement guide Added a guide for improving the performance (Latency) of the Raspberry Pi. * Fixed typos, filled in some blanks Fixed some typos, improved some text for clarity and added some text to complete this description. * Update to reflect new capabilities Update to reflect new capabilities of OpenRowingMonitor and make the text easier to parse for novice users (make it less technical). * Updated with new settings Updated with the new settings available * Update default.config.js * Update to allow for the 64Bit PREEMPT kernel Update of the install script to allow for the 64Bit PREEMPT kernel of Raspberry Pi, which handles low latency much better. * Fixed a typo in a file reference Thanks to @Abasz , dound a typo which i now fixed. * Fixed typo Fixed typo Co-authored-by: Lars Berning <151194+laberning@users.noreply.github.com> * Fixed a typo Fixed a typo Co-authored-by: Lars Berning <151194+laberning@users.noreply.github.com> * Fixed a cut-copy-paste error Fixed a cut-copy-paste error in the infinity symbol * Fixed error in repo-name Fixed an error in the repo-name when moving for the experimental branch to the PR branch * Fixed a copy error in the Repo-name Missed updating the repo-name when moving the code from private experimental branch to a production PR repo * Updated ToDo descriptions Updated ToDo descriptions * Added more detailed ToDo's Added more detailed ToDo's * Better explanation of design issue A much better (and English) and updated explanation of the open design issues in Flywheel.js * Fixed a bug in the strokecount reinitialization Fixed a bug in the stroke count reinitialization (great find by @Abasz), which was inconsistent with the normal init. * Graphic showing systematic error File showing systematic (periodic) deviations in the magnet placement * Added an image of misaligned magnets * Removed non-working code Removed the trimmedMedian function, as its design contains fundamental mathematical errors. The initially proposed way is the only good way of doing it. * Added observation about noise overfitting Added a note about Noise overfitting, based on the Concept2 and the observation of Gordon_Shumway (see https://github.com/laberning/openrowingmonitor/discussions/87) * Added the pigpio library Added the pigpio library * Added an explanation into the GPIO settings Added an explanation of the GPIO settings * Added the settings for the new GPIO library Added the settings to configure the new GPIO library * Replaced onoff with pigpio, added debounce param. Replaced the onoff library (goodbye my old loyal friend) with pigpio, to facilitate much more accurate timing calculations and add more flexibility to the flank detection (including debounce) * Replaced onoff with pigpio Replaced onoff with pigpio library * Replaced onoff with pigpio Replaced onoff with pigpio * Install and config pigpio dependency Added the installation of the pigpio C-library and setting the key parameters. * Added a bit clarification about the setup guide Added a bit better description about the setup guides. * Removed and added spaces.... * Remove an unnecessary space.... * Resolved open design issue in noise filter In the previous design, we were uncertain that the noise filter would handle all cases. With pigpio, we are quite certain it will handle all cases internally, and here we detect any deviations. Please note, startup-behaviour after a cold or warm (re)start still has to be handled as time since the privious pulse tends to be in the minutes, throwing off all timekeeping functions. * Removed obsolete variables Removed two obsolete variables: lastKnownGoodDatapoint and numberOfErrorsAllowed * Removed obsolete variable Removed the now defunct variable numberOfErrorsAllowed * Changes in startup and config Due to changes in the gpio library, the startup behaviour of flywheel.js has changes, as have the Concept2 configuration settings. These changes modify the results of the testcase accordingly. * Updated testcases after update pigpio Due to changes in the gpio library, the startup behaviour of flywheel.js has changed, as have the Concept2 configuration settings. These changes modify the results of the testcase accordingly. * Update testcase to accomodate lib change Update testcase to accomodate lib change * Update due to lib modification Update due to lib modification * Update Rower.test.js * Update Rower.test.js * Update Rower.test.js * Update Rower.test.js * Update due to gpio library change * Update due to removal obsolete parameter Update due to the removal of numberOfErrorsAllowed * Removed obsolute parameter Removed the numberOfErrorsAllowed parameter * Performance improvement A small performance improvement: by moving the evaluation of the flywheel.isPowered(), flywheel.isUnpowered() and flywheel.isDwelling() backwards, we move these potentially expensive operations further in the evaluation. As Javascript is using lazy evaluation, it won't be called unless all other conditions are true. Especially flywheel.isDwelling() checks if the entire flank is above maximumTimeBetweenImpulses, which is expensive (flywheel.isPowered() and flywheel.isUnpowered() are actually quite simple, but are moved along to maintain symmetry). * Fixed a missing bracket Fixed a missing bracket * Allignment with main branch (64Bit support) Alignment with the improvement of the install script of the main branch to support the 64 bit version of the Lite kernel. * removes some obsolete variables * adds a missing async statement Signed-off-by: Lars Berning <151194+laberning@users.noreply.github.com> * pin dependency Signed-off-by: Lars Berning <151194+laberning@users.noreply.github.com> * default setting should not fiddle with nice levels of os, otherwise we require root permissions by default Signed-off-by: Lars Berning <151194+laberning@users.noreply.github.com> * prettyfies a log message Signed-off-by: Lars Berning <151194+laberning@users.noreply.github.com> * unifies VO2max namings Signed-off-by: Lars Berning <151194+laberning@users.noreply.github.com> * Introduced appPriority as setting Added the appPriority setting, to allow a clear entry to set the priority for the main process. * Removed some obscure priority behaviour Removed the obscure approach of using the gpio-priority to set the main app priority. * Added a distinction between stop and pause Based on a bugreport by @Carlito1979 about the pause behaviour, added a distinction between the stopMoving() and pauseMoving() command, as a pause would lead to an undesired stop state. * Added a distinction between stop and pause Based on a bugreport by @Carlito1979 about the pause behaviour, added a distinction between the stopMoving() and pauseMoving() command, as a pause would lead to an undesired stop state. * Improved description of the various states Based on bugs reported, and some internal soul searching, improved the description of states. * Removed a trailing space * Added profiles, put them in alphabetic order Added the Force USA R3 Air Rower, and put the rowers in alphabetic order * Removal of surplus comma * Improvement of pause-behaviour Improvement of the pause behaviour based on feedback from @Carlito1979 * Improved RestingHR behaviour, clarifications Improvement of the resting HR functionality, it will now also trigger when rowing is paused Clarification of "Stopped" sessionState behavior: this is a less than obvious but deliberate approach. * Change to const postExerciseHR Due to Lint error, changed postExerciseHR from "let" to "const" * Create and advertising data builder Since Bleno library does not provide a way to set/tweak appearance data implement a builder that enables to add name and appearance information to the advertisement data. * Expose time of last completed stroke For the CPS and CSC BLE Services the time stamp of the last stoke event is necessary. This is available in the Rower as `drivePhaseStartTime` so expose it * Add 32bit capability to BufferBuilder Since the wheel revolution count in the CSC and CPS measurement service is a 32bin UINT extend the BufferBuilder to be able to handle it. * Add static data and functions used in CSCS and CPS Implement device information service (necessary for garmin to recognise the device as sensor) a wrapper around static read characteristics to ease creation. * Add cycling speed and cadence profile Add necessary services and related characteristics. Implement CscPeripheral interface to be able to properly communicate to the PeripheralManager. * Add cycling power profile Add necessary services and related characteristics. Implement CpsPeripheral interface to be able to properly communicate to the PeripheralManager. * Wire up PeripheralManager with the new profiles Add the new CSCP and CPP profiles to the PeripheralManager. Update comments in settings and front end web view with the new profiles * Add new Rower Profile Zoco Body Fit Add a rowing profile for a Concept2 clone air rower * Update FTMS profile with new services Add device information service to the FTMS peripheral and move to the new advertising data builder. * Improvement of startup behaviour Modification of the startup behaviour: now unrealistic values (i.e. above maximumTimeBetweenImpulses) are ignored. The rower will only start when all values in the flank are above the minmumSpeed (i.e. all currentDt's are below maximumTimeBetweenImpulses). * Improvement of startup behaviour Modification of the startup behaviour: now unrealistic values (i.e. above maximumTimeBetweenImpulses) are ignored. The rower will only start when all values in the flank are above the minmumSpeed (i.e. all currentDt's are below maximumTimeBetweenImpulses). * Improvement of startup behaviour Modification of the startup behaviour: now unrealistic values (i.e. above maximumTimeBetweenImpulses) are ignored. The rower will only start when all values in the flank are above the minmumSpeed (i.e. all currentDt's are below maximumTimeBetweenImpulses). * Improvement of startup behaviour Modification of the startup behaviour: now unrealistic values (i.e. above maximumTimeBetweenImpulses) are ignored. The rower will only start when all values in the flank are above the minmumSpeed (i.e. all currentDt's are below maximumTimeBetweenImpulses). * Finalize Generic Air Rower profile * Change name of displayed name of new profiles * Rename lastStrokeTime to driveLastStartTime * Fixed some small bugs Fixed * the missing pause command * a small typo in the totalmovingtime * Fixed a small bug Fixed a small bug * Improvement of browser performence Improved some startup settings to increase browser performance (disables some security settings as they are not needed at a local system). Please note these settings are NOT RECOMENDED for general purpose browsers, but as we run on a localhost machine only, showing our GUI, this is quite acceptable. * Update due to increased BLE profiles Thanks to @Abasz, we can now use BLE Cycling Power Profile and the Cycling Speed and Cadence Profile. * Added Abasz to the list of great contributors * More expanded explanation of BLE profiles * Update of the testfile to align with new GPIO conf Update of the testfile to reflect the new GPIO-module * Introduction of supporting Linear TS Introduction of the Linear Theil-Senn algorithm to improve the Quadratic Theil-Senn algorithm * Improvement of the Quad. Theil-Senn Algorithm Improvement of the accuracy and robustness of the Quadratic Theil-Senn algorithm, as well as improving (reducing) the CPU load. It employs the Linear Theil-Senn Algorithm to calculate the optimal B and C for the selected A (instead of a marginally related B and C in the older algoithm). * Update of the stroke detection algorithm Update of the stroke detection algorithm: as the Qudratic Theil-Senn algorithm is more robust, we can rely on the sole combination of dragslope being gone and seeing torque. * Updated to reflect more accurate algorithm Updated the testscript to reflect the much more accurate and robust algorithm. * Updated to reflect more improved algorithms Updated test scripts to reflect the improvements in the Quadratic Theil-Senn algorithm and the Flywheel stroke detection algorithm. * Updated to reflect more improved algorithms Updated test scripts to reflect the improvements in the Quadratic Theil-Senn algorithm and the Flywheel stroke detection algorithm. * Removed dead code Removal of dead code * Improvement of Code quality * Improvement of code quality Improvement of code quality * Added space to survive Lint Added space to survive Lint * Addition of the isAboveMinimumSpeed function Addition of the isAboveMinimumSpeed function * Removed unused variable * Add modification for new startup behaviour Add modification for new startup behaviour * Update to fix regression issues Update to fix regression issues * Update Rower.test.js * Update of the C2 testcase Update of the C2 testcase due to GPIO update * Update of the C2 testcase due to GPIO changes Update of the C2 testcase due to changing the testdata due to GPIO changes * fixes some review findings Signed-off-by: Lars Berning <151194+laberning@users.noreply.github.com> Co-authored-by: Lars Berning <151194+laberning@users.noreply.github.com> Co-authored-by: Abász <> Co-authored-by: Abasz <32517724+Abasz@users.noreply.github.com> |
||
|---|---|---|
| .github/workflows | ||
| app | ||
| bin | ||
| config | ||
| docs | ||
| install | ||
| recordings | ||
| .editorconfig | ||
| .eslintrc.json | ||
| .gitignore | ||
| .markdownlint.json | ||
| LICENSE | ||
| babel.config.json | ||
| jsconfig.json | ||
| package-lock.json | ||
| package.json | ||
| rollup.config.js | ||
| snowpack.config.js | ||
docs/README.md
Open Rowing Monitor
Open Rowing Monitor is a free and open source performance monitor for rowing machines. It upgrades almost any rowing machine into a smart trainer that can be used with training applications and games.
It is a Node.js application that runs on a Raspberry Pi and measures the rotation of the rower's flywheel (or similar) to calculate rowing specific metrics, such as power, split time, speed, stroke rate, distance and calories. It can share these metrics for controling games and record these metrics for further analysis.
It is currently developed and tested with a Sportstech WRX700 water-rower and a Concept2 air-rower. In the past, it was also tested extensively on a NordicTrack RX-800 hybrid air/magnetic rower. But it should run fine with any rowing machine that uses some kind of damping mechanism, as long as you can add something to measure the speed of the flywheel. It has shown to work well with DIY rowing machines like the Openergo, providing the construction is decent.
Features
Open Rowing Monitor aims to provide you with metrics directly, connect to apps and games via bluetooth and allow you to export your data to the analysis tool of your choice. The following items describe most of the current features in more detail.
Rowing Metrics
Open Rowing Monitor implements a physics model to simulate the typical metrics of a rowing boat based on the pull on the handle. The physics model can be tuned to the specifics of a rower by changing some model parameters in the configuration file, where we also provide these settings for machines known to us.
Open Rowing Monitor displays the following key metrics on the user interfaces:
- Distance rowed (meters)
- Training Duration
- Power (watts)
- Pace (/500m)
- Strokes per Minute (SPM)
- Calories used (kcal)
- Total number of strokes
- Heart Rate (supports BLE and ANT+ heart rate monitors, ANT+ requires an ANT+ USB stick)
It calculates and can export many other key rowing metrics, including Drag factor, Drive length (meters), Drive time (milliseconds), Recovery Time (milliseconds), Average handle force (Newton), Peak handle force (Newton) and the associated handle force curve, handle velocity curve and handle power curve.
Web Interface
The web interface visualizes the basic rowing metrics on any device that can run a web browser (i.e. a smartphone that you attach to your rowing machine while training). It uses web sockets to show the rowing status in realtime. It can also be used to reset the training metrics and to select the type of bluetooth connection.
If you connect a physical screen directly to the Raspberry Pi, then this interface can also be directly shown on the device. The installation script can set up a web browser in kiosk mode that runs on the Raspberry Pi.

Bluetooth Low Energy (BLE)
Open Rowing Monitor also implements different Bluetooth Low Energy (BLE) protocols so you can use your rowing machine with different fitness applications. Some apps use the Fitness Machine Service (FTMS), which is a standardized GATT protocol for different types of fitness machines. Other apps prefer to see a Concept 2 PM5. To help you connect to your app and game of choice, Open Rowing Monitor currently supports the following Bluetooth protocols:
-
Concept2 PM: Open Rowing Monitor implements part of the Concept2 PM Bluetooth Smart Communication Interface Definition. This is still work in progress and only implements the most common parts of the spec, so it is not guaranteed to work with all applications that support C2 rowing machines. Our interface currently can only report metrics, but can't recieve commands and session parameters from the app yet. It is known to work with EXR and all the samples from The Erg Arcade, for example you can row in the clouds.
-
FTMS Rower: This is the FTMS profile for rowing machines and supports all rowing specific metrics (such as stroke rate). So far not many training applications for this profile exist, but the market is evolving. We've successfully tested it with EXR, MyHomeFit and Kinomap.
-
FTMS Indoor Bike: This FTMS profile is used by Smart Bike Trainers and widely adopted by training applications for bike training. It does not support rowing specific metrics. But it can present metrics such as power and distance to the biking application and use cadence for stroke rate. So why not use your virtual rowing bike to row up a mountain in Zwift, Bkool, The Sufferfest or similar :-)
-
BLE Cycling Power Profile: This Bluetooth simulates a bike, which allows you to connect the rower to a bike activity on your (mostly Garmin) sportwatch. It will translate the rowing metrics to the appropriate fields. This profile is only supported by specific watches, so it might provide a solution.
-
BLE Cycling Speed and Cadence Profile: used for older Garmin Forerunner and Garmin Venu watches and similar types, again simulating a bike activity. Please note to set the wheel circumference to 10mm to make this work well.
Export of Training Sessions
Open Rowing Monitor is based on the idea that metrics should be easily accessible for further analysis. Therefore, Open Rowing Monitor can create the following files:
-
Training Center XML files (TCX): These are XML-files that contain the most essential metrics of a rowing session. Most training analysis tools will accept a tcx-file. You can upload these files to training platforms like Strava, Garmin Connect or Trainingpeaks to track your training sessions;
-
RowingData files, which are comma-seperated files with all metrics Open Rowing Monitor can produce. These can be used with RowingData to display your results locally, or uploaded to RowsAndAll for a webbased analysis (including dynamic in-stroke metrics). The csv-files can also be processed manually in Excel, allowing your own custom analysis. Please note that for visualising in-stroke metrics in RowsAndAll (i.e. force, power and handle speed curves), you need their yearly subscription;
-
Raw flywheel measurements of the flywheel, also in CSV files. These files are great to start to learn about the specifics of your rowing machine (some Excel visualistion can help with this).
Uploading your sessions to Strava is an integrated feature, for all other platforms this is currently a manual step. Uploading to RowsAndAll can be automated through their e-mail interface, see this description. The Open rowing Monito installer can also set up a network share that contains all training data so it is easy to grab the files from there and manually upload them to the training platform of your choice.
Installation
You will need a Raspberry Pi Zero W, Raspberry Pi Zero 2 W, Raspberry Pi 3 or a Raspberry Pi 4 with a fresh installation of Raspberry Pi OS Lite for this (the 64Bit kernel is preferred). Connect to the device with SSH and initiate the following command to install Open Rowing Monitor as an automatically starting system service:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/laberning/openrowingmonitor/HEAD/install/install.sh)"
Also have a look at the Detailed Installation Instructions for more information on the software installation and for instructions on how to connect the rowing machine.
How it all started
Lars originally started this project, because his rowing machine (Sportstech WRX700) has a very simple computer and he wanted to build something with a clean interface that calculates more realistic metrics. Also, this was a good reason to learn a bit more about Bluetooth and all its specifics.
The original proof of concept version started as a sketch on an Arduino, but the web frontend and BLE needed the much more powerful Raspberry Pi. Maybe using a Raspberry Pi for this small IoT-project is a bit of an overkill, but it has the capacity for further features such as syncing training data or rowing games. And it has USB-Ports that you can use to charge your phone while rowing :-)
Further information
This project is already in a very usable stage, but some things are still a bit rough on the edges. More functionality will be added in the future, so check the Development Roadmap if you are curious. Contributions are welcome, please read the Contributing Guidelines first.
Feel free to leave a message in the GitHub Discussions if you have any questions or ideas related to this project.
Check the advanced information on the Physics behind Open Rowing Monitor.
This project uses some great work by others, see the Attribution here.