{"id":19892215,"url":"https://github.com/berg0162/rt-critical-power","last_synced_at":"2026-03-06T10:32:12.375Z","repository":{"id":190345944,"uuid":"385912797","full_name":"Berg0162/RT-Critical-Power","owner":"Berg0162","description":"Real Time (RT) measuring the Critical Power provides a quick and reliable testing method to monitor changes in endurance fitness. ","archived":false,"fork":false,"pushed_at":"2021-07-22T11:48:16.000Z","size":18507,"stargazers_count":9,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-09-18T23:08:48.142Z","etag":null,"topics":["airflow","android","awc","critical","feather","frc","ftp","nrf52840","power","real-time","realtime","w-prime","xert"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Berg0162.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-07-14T11:15:45.000Z","updated_at":"2025-04-10T17:28:21.000Z","dependencies_parsed_at":"2023-08-24T08:12:07.832Z","dependency_job_id":null,"html_url":"https://github.com/Berg0162/RT-Critical-Power","commit_stats":null,"previous_names":["berg0162/rt-critical-power"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Berg0162/RT-Critical-Power","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Berg0162%2FRT-Critical-Power","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Berg0162%2FRT-Critical-Power/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Berg0162%2FRT-Critical-Power/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Berg0162%2FRT-Critical-Power/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Berg0162","download_url":"https://codeload.github.com/Berg0162/RT-Critical-Power/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Berg0162%2FRT-Critical-Power/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30171869,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T07:56:45.623Z","status":"ssl_error","status_checked_at":"2026-03-06T07:55:55.621Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airflow","android","awc","critical","feather","frc","ftp","nrf52840","power","real-time","realtime","w-prime","xert"],"created_at":"2024-11-12T18:22:31.842Z","updated_at":"2026-03-06T10:32:12.339Z","avatar_url":"https://github.com/Berg0162.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Critical Power on the fly\n\nI was puzzled by a publication that claimed: it is possible to \u003ci\u003eMeasure your FTP in a Minute\u003c/i\u003e in [BikeRadar](https://www.bikeradar.com/advice/fitness-and-training/measure-your-ftp-in-a-minute-app-claims/?image=4\u0026type=gallery\u0026gallery=1\u0026embedded_slideshow=1). Further Googling revealed that baronbiosys.com developed an \u003cb\u003eXert-app\u003c/b\u003e that claims this:\n\n\u003e The method uses sophisticated techniques and pattern recognition to determine your FTP. Whereas in the past you either needed to test using a 20 minute FTP protocol for example, or examine many months’ worth of data to get a realistic FTP value, this method enables you to determine your FTP on that day or even at that moment. cf [Baronbiosys]( https://baronbiosys.com/real-time-ftp-determination/)\n\u003e \nAfter installing the \u003cb\u003eXert app\u003c/b\u003e on my Garmin (830), I have experienced, until free trial time was expended, that it does its estimates of \u003cb\u003eFTP\u003c/b\u003e remarkably well. DcRainmaker reviewed it and is quite positive about its performance and accuracy and concluded:\n\u003e Whereas here it is the fact that I am getting FTP feedback in real-time that is so unique.  I can go out for a ride and start to see these values formulate as I am giving hard efforts.  No waiting hours, or even minutes later.  I am not aware of any other platform or app that does that. cf [DCRainmaker](https://www.dcrainmaker.com/2017/07/xert-rolls-out-free-real-time-ftp-app-on-garmin-devices.html) \n\u003e \nI decided to develop C++ code for an indoor training application that is running on an \u003cb\u003eArduino nRF52840 Express\u003c/b\u003e board and that comes as close as possible to the functionality of the \u003cb\u003eXert app\u003c/b\u003e (for Garmin Connect). It became an integral part of a larger project: [AIRFLOW](https://github.com/Berg0162/airflow). The following is an explanation of the science and math behind its fundamentals. \u003cbr\u003e\n\nIt was clear to me that the \u003cb\u003eXert app\u003c/b\u003e is based on depletion of the so-called \u003cb\u003eAnaerobic Work Capacity\u003c/b\u003e (\u003cb\u003eAWC\u003c/b\u003e) or \u003cb\u003eFunctional Reserve Capacity\u003c/b\u003e (\u003cb\u003eFRC\u003c/b\u003e). To keep it simple, assume that a cyclist has a given amount of this \u003cins\u003efinite work capacity\u003c/ins\u003e (an energy reserve) stored internally at the beginning of a ride. This amount of energy is known in mathematical terms as \u003cb\u003eW'\u003c/b\u003e (pronounce \u003cb\u003eW prime\u003c/b\u003e), it is energy and consequently measured in joules. While you are riding at a low intensity, \u003cb\u003eW'\u003c/b\u003e remains at its full level since it is not expended, and you are able to continue riding at this intensity for a long time. But if you push harder, you will start using this energy. The limit at which you will start expending this energy reserve is known as \u003cb\u003eCritical Power\u003c/b\u003e (\u003cb\u003eCP\u003c/b\u003e). If you push on the pedals harder than \u003cb\u003eCP\u003c/b\u003e, \u003cb\u003eW'\u003c/b\u003e will decrease. As soon as your produced power (watts) get lower than \u003cb\u003eCP\u003c/b\u003e, \u003cb\u003eW'\u003c/b\u003e will “regenerate”, and the energy reserve will increase again. When you ride long enough below \u003cb\u003eCP\u003c/b\u003e, \u003cb\u003eW'\u003c/b\u003e will approach the 100% level again. However, when you work hard and long enough above \u003cb\u003eCP\u003c/b\u003e, \u003cb\u003eW'\u003c/b\u003e will be depleted completely and you will be exhausted at the very moment (a.k.a. \u003cb\u003eT\u003csub\u003elim\u003c/sub\u003e\u003c/b\u003e)! The variations in \u003cb\u003eW'\u003c/b\u003e are expressed as “\u003cb\u003eW' Balance\u003c/b\u003e” in the Dr. Skiba algorithm (2). Another parameter of the algorithm is \u003cb\u003eTau\u003c/b\u003e that defines the speed at which \u003cb\u003eW'\u003c/b\u003e is regenerating when the power is below \u003cb\u003eCP\u003c/b\u003e. \u003cbr\u003e\n\n\u003cb\u003eCP\u003c/b\u003e is defined as the highest exercise intensity that can be maintained for prolonged periods of time, typically for 45 to 60 min. Functional Threshold Power (\u003cb\u003eFTP\u003c/b\u003e) is more well-known in recreational cycling and has been defined as the highest average power output that can be maintained for 60 min (1). Given the great similarity in definition, we assume for non-elite cycling the absence of a significant difference between \u003cb\u003eCP\u003c/b\u003e and \u003cb\u003eFTP\u003c/b\u003e values! \u003cbr\u003e\n\n# It is all about algorithms \u003cbr\u003e\nThe algorithms that had to be implemented are the original Dr. Skiba algorithm (2) and an optimization (approximation) of the Integral Skiba algorithm by Dave Waterworth (3). Aart Goossens published code (in Python) and explanatory information on Github (4) that helped a great deal to understand and implement the different algorithms in an Arduino setting. The following information is paraphrased from his original work, to give the reader some insight in the mathematical background of the algorithms, see his work at: [Aart Goosens @ Github](https://github.com/AartGoossens/publications/blob/master/w_balance_algorithms/article.ipynb) \u003cbr\u003e\n# Integral Skiba algorithm \u003cbr\u003e\nThe integral Skiba algorithm is the best-known algorithm to calculate \u003cb\u003eW' balance\u003c/b\u003e and has been scientifically validated (5). The equations for the algorithm are: \u003cbr\u003e\n\u003cimg src=\"../main/images/W_Prime_equations.png\" width=\"537\" height=\"223\" align = \"middle\" alt=\"W Prime equations\"\u003e \u003cbr\u003e\nWhere \u003cb\u003eW'\u003csub\u003ebal(t)\u003c/sub\u003e\u003c/b\u003e is equal to \u003cb\u003eW'\u003csub\u003ebal\u003c/sub\u003e\u003c/b\u003e at time \u003cb\u003et\u003c/b\u003e, \u003cb\u003eW'\u003c/b\u003e is the amount of available energy above \u003cb\u003eCP\u003c/b\u003e (Critical Power), \u003cb\u003et\u003c/b\u003e the time for which \u003cb\u003eW'\u003csub\u003ebal\u003c/sub\u003e\u003c/b\u003e is calculated, \u003cb\u003eu\u003c/b\u003e the iterator of the summation, \u003cb\u003eW'\u003csub\u003eexp(u)\u003c/sub\u003e\u003c/b\u003e amount of energy above \u003cb\u003eCP\u003c/b\u003e that is used at time \u003cb\u003eu\u003c/b\u003e (expended), \u003cb\u003ee\u003c/b\u003e the Euler number and \u003c/b\u003eƮ\u003csub\u003eW'\u003c/sub\u003e\u003c/b\u003e (pronounced \u003cb\u003eTau\u003c/b\u003e) a time constant that describes the recovery speed. The numbers 546, -0.01 and 316 are determined experimentally in Skiba's original article and do not change between individuals. \u003cb\u003eD\u003csub\u003eCP\u003c/sub\u003e\u003c/b\u003e is the difference between \u003cb\u003eCP\u003c/b\u003e and the average power of the intervals in which the power was below \u003cb\u003eCP\u003c/b\u003e. \u003cb\u003eD\u003csub\u003eCP\u003csub\u003e\u003c/b\u003e can be calculated dynamically (the average until time \u003cb\u003et\u003c/b\u003e) or calculated once for the entire workout and used as a static value. Skiba recommends using a static value for \u003cb\u003eD\u003csub\u003eCP\u003csub\u003e\u003c/b\u003e. \u003cb\u003eP(t)\u003c/b\u003e is the power produced at time \u003cb\u003et\u003c/b\u003e.\n\n# Dave Waterworth optimization of integral Skiba algorithm \u003cbr\u003e\nMathematician Dave Waterworth (3) helped the core developer of [Golden Cheetah](http://www.goldencheetah.org/), Mark Liversedge, to develop an optimization of the Skiba algorithm (6). This reformulation approximates the Skiba algorithm so results can vary a little in extreme cases only, especially when (\u003cb\u003eTau\u003c/b\u003e) is very small compared with the sample time.\nThe \u003cb\u003eW'\u003csub\u003ebal\u003c/sub\u003e\u003c/b\u003e integral part of the equations of Skiba is rewritten by Waterworth to: \u003cbr\u003e\n\u003cimg src=\"../main/images/Waterworth_equations.png\" width=\"355\" height=\"160\" align = \"middle\" alt=\"Waterworth equations\"\u003e \u003cbr\u003e\nWhere \u003cb\u003eS(t)\u003c/b\u003e is a running sum at time \u003cb\u003et\u003c/b\u003e after the start, other symbols conform the previous equations. \u003cb\u003eTau\u003c/b\u003e (Ʈ\u003csub\u003eW'\u003c/sub\u003e) and \u003cb\u003eW'\u003csub\u003eexp(t)\u003c/sub\u003e\u003c/b\u003e are calculated with the original equations presented by Skiba. The integral Skiba algorithm is quite expensive to compute, even on fast computers since the summation must be repeated for every time \u003cb\u003et\u003c/b\u003e again. \nThe big advantage of the Waterworth optimization is that now \u003cb\u003eW' balance\u003c/b\u003e can be calculated at real time: during the ride and not only afterwards! In addition it is very helpful when one wants to determine the \u003cb\u003eCritical Power\u003c/b\u003e on the fly during HIIT workouts or strenuous workouts when \u003cb\u003eW' balance\u003c/b\u003e becomes negative and has been depleted!\n\n# The Power-Duration Relationship\n\u003cimg src=\"../main/images/2-Parameter-Algorithm.png\" width=\"800\" height=\"400\" align = \"middle\" alt=\"Power Duration\"\u003e \u003cbr\u003e\nMathematically, the Power-Duration relationship is described as a hyperbolic function. The 4 different points on the curve represent points in Time (\u003cb\u003eT\u003csub\u003elim\u003c/sub\u003e\u003c/b\u003e) when corresponding maximum sustainable Power above \u003cb\u003eCP\u003c/b\u003e is reached and exhaustion occurs. When exercise tolerance is considered, the power-asymptote is known as \u003cb\u003eCP\u003c/b\u003e (Watts). The curvature constant is known as \u003cb\u003eW'\u003c/b\u003e (i.e., \u003cb\u003eW prime\u003c/b\u003e), it is measured in units of work done (Joules). Notice that the 4 greyed areas, representing \u003cb\u003eW'\u003c/b\u003e, are different in form but about equal in size. This hyperbolic power-duration relationship can be transformed into a linear relationship if work done is plotted against time, such that the slope of the line equals \u003cb\u003eCP\u003c/b\u003e and the intercept equals \u003cb\u003eW'\u003c/b\u003e. It should be emphasised that the power-duration relationship describes exercise tolerance but does not explain it. Nevertheless, the physiological responses to exercise performed below and above \u003cb\u003eCP\u003c/b\u003e may provide important insights into the fatigue process. \u003cb\u003eCP\u003c/b\u003e was originally defined as the external power output that could be sustained “indefinitely” or for a very long time without fatigue. This definition should be considered theoretical, however, since no exercise can ever be undertaken indefinitely. It is now understood that \u003cb\u003eCP\u003c/b\u003e separates power outputs for which exercise tolerance is predictably limited (exercise power \u003e \u003cb\u003eCP\u003c/b\u003e). The actual time to intolerance (\u003cb\u003eT\u003csub\u003elim\u003c/sub\u003e\u003c/b\u003e) for exercise performed above \u003cb\u003eCP\u003c/b\u003e is defined, and therefore closely predicted, by the equation: \u003cbr\u003e\u003cbr\u003e\n   \u003cstrong\u003eT\u003csub\u003elim\u003c/sub\u003e = W′/(P-CP)\u003c/strong\u003e \u003cbr\u003e\u003cbr\u003e\nThis equation highlights that the time to intolerance above CP is a function of the proximity of the power output (\u003cb\u003eP\u003c/b\u003e) being sustained to \u003cb\u003eCP\u003c/b\u003e and the size of \u003cb\u003eW'\u003c/b\u003e. When \u003cb\u003eP\u003c/b\u003e is considerably above \u003cb\u003eCP\u003c/b\u003e, the constant amount of work represented by the \u003cb\u003eW'\u003c/b\u003e parameter will be utilized rapidly and \u003cb\u003eT\u003csub\u003elim\u003c/sub\u003e\u003c/b\u003e will be short. Should \u003cb\u003eP\u003c/b\u003e be closer to \u003cb\u003eCP\u003c/b\u003e, then \u003cb\u003eW'\u003c/b\u003e would be ‘used’ more slowly and \u003cb\u003eT\u003csub\u003elim\u003c/sup\u003e\u003c/b\u003e would be longer. A crucial consideration here is that \u003cb\u003eW'\u003c/b\u003e is assumed to be constant for all \u003cb\u003eP\u003c/b\u003e above \u003cb\u003eCP\u003c/b\u003e. This ‘\u003cb\u003etwo parameter\u003c/b\u003e’ power-time or power-duration model therefore implies that absolute exercise performance depends on simply the value of \u003cb\u003eCP\u003c/b\u003e (in Watts) and the value of \u003cb\u003eW'\u003c/b\u003e (in Joules). Both \u003cb\u003eCP\u003c/b\u003e and \u003cb\u003eW'\u003c/b\u003e parameters can vary considerably among individuals as a function of health/disease, age, fitness, and training.\n\n# Code\n```C++\n// ------------ W' Balance calculation -------------------\n// Global variables related to Cycling Power and W-Prime\nuint16_t TAWC_Mode = 1;                   // Track Anaerobic Capacity Depletion Mode == TRUE -\u003e APPLY and SHOW\nuint16_t CP60 = 160;                      // Your (estimate of) Critical Power, more or less the same as FTP\nuint16_t eCP = CP60;                      // Algorithmic estimate of Critical Power during intense workout\nuint16_t w_prime_usr = 7500;              // Your (estimate of) W-prime or a base value\nuint16_t ew_prime_mod = w_prime_usr;      // First order estimate of W-prime modified during intense workout\nuint16_t ew_prime_test = w_prime_usr;     // 20-min-test algorithmic estimate (20 minute @ 5% above eCP) of W-prime for a given eCP! \nlong int w_prime_balance = 0;             // Can be negative !!!\nbool IsShowWprimeValuesDominant = false;  // Boolean that determines to show W Prime data on Oled or not\n//-------------------------------------------------------   \n```\n   \n```C++\n// ------------------------   W'Balance Functions  -----------------------------------\nuint16_t CalculateAveragePowerBelowCP(uint16_t iPower, uint16_t iCP);\nvoid CalculateAveragePowerAboveCP(uint16_t iPower, uint16_t \u0026iavPwr, unsigned long \u0026iCpACp);\ndouble tau_w_prime_balance(uint16_t iPower, uint16_t iCP);\nvoid w_prime_balance_waterworth(uint16_t iPower, uint16_t iCP, uint16_t iw_prime);\nvoid ConstrainW_PrimeValue(uint16_t \u0026iCP, uint16_t \u0026iw_prime);\nuint16_t GetCPfromTwoParameterAlgorithm(uint16_t iav_Power, unsigned long iT_lim, uint16_t iw_prime);\nuint16_t GetWPrimefromTwoParameterAlgorithm(uint16_t iav_Power, double iT_lim, uint16_t iCP);\n// ------------------------   W'Balance Functions  ------------------------------------\n```\n\n```C++  \nuint16_t CalculateAveragePowerBelowCP(uint16_t iPower, uint16_t iCP){\n  // calculate avg_power_below_cp real time using a running sum and counter\n  static unsigned long int CountPowerBelowCP = 0;\n  static unsigned long int SumPowerBelowCP = 0;\n    if (iPower \u003c iCP) { \n      SumPowerBelowCP += (unsigned long int)iPower;\n      CountPowerBelowCP++;\n      }\n  return uint16_t(SumPowerBelowCP/CountPowerBelowCP); // average power below CP\n}   // end calculate avg_power_below_cp\n\nvoid CalculateAveragePowerAboveCP(uint16_t iPower, uint16_t \u0026iavPwr, unsigned long int \u0026iCpACp){\n  // calculate avg_power_above_cp real time using a running sum and counter\n  // returning the values by C++ reference!\n  static unsigned long int SumPowerAboveCP = 0;\n      SumPowerAboveCP += (unsigned long int)iPower;\n      iCpACp++;\n      iavPwr = uint16_t(SumPowerAboveCP/iCpACp); // average power above CP\n}   // end calculate avg_power_above_cp\n\ndouble tau_w_prime_balance(uint16_t iPower, uint16_t iCP){  \n    uint16_t avg_power_below_cp = CalculateAveragePowerBelowCP(iPower, iCP);\n    double delta_cp = double(iCP - avg_power_below_cp);\n    return (double(546.00) * exp(-0.01 * delta_cp) + double(316.00));\n}   // end Tau W Prime Balance\n\nvoid w_prime_balance_waterworth(uint16_t iPower, uint16_t iCP, uint16_t iw_prime) {\n    // Most power meters measure power, torque a.o. in a high frequency (20-60 Hz) but \n    // transmit (BLE) datasets to a monitoring device in much lower frequency: 1-4 times per second.\n    int power_above_cp = 0; // Power \u003e CP\n    static double T_lim = 0; // Time (duration) while Power is above CP, the summed value of every sample time value P \u003e CP\n    double w_prime_expended = 0.0; // Expended energy in Joules\n    double ExpTerm1 = 0.0, ExpTerm2 = 0.0;\n    static double TimeSpent = 0.0; // Total Time spent in the workout, the summed value of every sample time value\n    static double running_sum = 0.0;\n    static unsigned long int CountPowerAboveCP = 0; // Count the Power readings above CP \n    static uint16_t avPower = 0; // Average power above CP\n    const long int NextLevelStep = 1000; // Stepsize of the next level of w-prime modification --\u003e 1000 Joules step\n    static long int NextUpdateLevel = 0; // The next level at which to update eCP, e_w_prime_mod and ew_prime_test\n    // Quarq Dfour Zero Spider power meter sends between 2 and 1.2 power readings per second, dependent of POWER level !!!\n    // We assume that the sample frequency (number of samples per second) is VARIABLE !!!\n    // Determine the individual sample time in seconds, it may/will vary during the workout !!! \n    static unsigned long PrevReadingTime = 0;\n    double SampleTime  = double(millis()-PrevReadingTime)/1000; // Time or duration since the previous sample, convert from millis to seconds\n    PrevReadingTime = millis(); // Update for the next sample\n    double tau = tau_w_prime_balance(iPower, iCP); // Determine the value for tau\n    TimeSpent += SampleTime ; // The summed value of all sample time values during the workout\n    power_above_cp = (iPower - iCP);\n#ifdef DEBUGAIR\n    Serial.printf(\"Time:%6.1f ST: %4.2f tau: %f \", TimeSpent, SampleTime , tau);\n#endif\n    // w_prime is energy and measured in Joules = Watt*second\n    // Determine the expended energy above CP since the previous measurement (--\u003e i.e. during sample time)\n    w_prime_expended = double(max(0, power_above_cp))*SampleTime; // Determine (Watts_above_CP) * (its duration in seconds) = expended energy in Joules!\n    // Calculate some terms of the equation\n    ExpTerm1 = exp(TimeSpent/tau); // Exponential term1\n    ExpTerm2 = exp(-TimeSpent/tau); // Exponential term2\n#ifdef DEBUGAIR\n    Serial.printf(\"W prime expended: %3.0f exp-term1: %f exp-term2: %f \", w_prime_expended , ExpTerm1, ExpTerm2);\n#endif\n    running_sum = running_sum + (w_prime_expended*ExpTerm1); // Determine the running sum\n#ifdef DEBUGAIR\n    Serial.printf(\"Running Sum: %f \", running_sum);\n#endif    \n    w_prime_balance = (long int)( (double)iw_prime - (running_sum*ExpTerm2) ) ; // Determine w prime balance and cast from double to int\n#ifdef DEBUGAIR\n    Serial.printf(\" w_prime_balance: %d \", w_prime_balance);\n#endif\n    //--------------- extra --------------------------------------------------------------------------------------\n    // Workout starts at a certain W'= ##,### Joules and CP = ### watts, set by the user; the algorithm increases CP and W' stepwise\n    // to more realistic values every time when W'balance is depleted to a certain level; -\u003e 2-Parameter Algorithm updates CP and W'\n    if (power_above_cp \u003e 0) { \n      CalculateAveragePowerAboveCP(iPower, avPower, CountPowerAboveCP); // Average power above CP is to be calculated for future use\n      T_lim += SampleTime ; // Time to exhaustion: the accurate sum of every second spent above CP, calculated for future use\n    }\n#ifdef DEBUGAIR\n    Serial.printf(\" [%d]\\n\", CountPowerAboveCP);\n#endif\n    // When working above CP, the moment comes that we need to update eCP and ew_prime !!\n    if ( (w_prime_balance \u003c NextUpdateLevel) \u0026\u0026 (w_prime_expended \u003e 0) ) { // W' balance is further depleted --\u003e test for an update moment\n       NextUpdateLevel -= NextLevelStep; // Move down another level of depletion, update eCP, ew_prime_mod and ew_prime_test\n       eCP = GetCPfromTwoParameterAlgorithm(avPower, T_lim, iw_prime); // Estimate a new eCP value\n       ew_prime_mod = w_prime_usr - NextUpdateLevel; // Adjust ew_prime_modified to the next level of depletion to be checked\n       ew_prime_test = GetWPrimefromTwoParameterAlgorithm(uint16_t(eCP*1.045), double(1200), eCP); // 20-Min-test estimate for W-Prime\n#ifdef DEBUGAIR\n       Serial.printf(\"Update of eCP - ew_prime %5d - avPower: %3d - T-lim:%6.1f --\u003e eCP: %3d \", ew_prime_mod, avPower, T_lim, eCP);\n       Serial.printf(\"--\u003e Test estimate of W-Prime: %d \\n\", ew_prime_test );\n#endif\n    }\n    //-----------------extra -------------------------------------------------------------------------------\n} // end\n\n// Check and Set starting value of w_prime to realistic numbers!!\nvoid ConstrainW_PrimeValue(uint16_t \u0026iCP, uint16_t \u0026iw_prime) {\n    if (iCP \u003c 100) { iCP = 100; } // Update to lowest level that we allow for\n    // First determine the \"minimal\" value for W_Prime according to a 20-min-test estimate, given the iCP value! \n    uint16_t w_prime_estimate = GetWPrimefromTwoParameterAlgorithm(uint16_t(iCP*1.045), double(1200), iCP); \n    if (iw_prime \u003c w_prime_estimate) { iw_prime = w_prime_estimate; } // Update iw_prime to a realistic level\n    return;\n} // end\n\nuint16_t GetCPfromTwoParameterAlgorithm(uint16_t iav_Power, double iT_lim, uint16_t iw_prime) {\n     uint16_t WprimeDivTlim = uint16_t( double(iw_prime)/iT_lim ); // type cast for correct calculations\n     if (iav_Power \u003e WprimeDivTlim){ // test for out of scope\n      return (iav_Power - WprimeDivTlim); // Solve 2-parameter algorithm to estimate CP\n      } else {\n      return eCP; // Something went wrong do'nt allow an update of CP\n     }\n} // end\n\nuint16_t GetWPrimefromTwoParameterAlgorithm(uint16_t iav_Power, double iT_lim, uint16_t iCP) {\n     if (iav_Power \u003e iCP){ // test for out of scope\n      return (iav_Power-iCP)*((uint16_t)iT_lim); // Solve 2-parameter algorithm to estimate new W-Prime\n      } else {\n      return w_prime_usr; // Something went wrong don't allow an update of w_prime\n     }\n} // end\n```  \n# Airflow Device and Companion app\n\u003cimg src=\"../main/images/Screenshot.jpg\" width=\"250\" height=\"400\" align = \"right\" alt=\"Airflow app\"\u003e \u003cbr\u003e\nThe code has been integrated in a larger project called \u003cb\u003eAirflow\u003c/b\u003e. Setting or changing your basal \u003cb\u003eCP\u003c/b\u003e and \u003cb\u003eW Prime\u003c/b\u003e are an integral part of an \u003cb\u003eAIRFLOW\u003c/b\u003e Companion app! The \u003cb\u003eAIRFLOW\u003c/b\u003e smart device continuously tunes the requested airflow velocity of the cooling fan(s) for a stable Heat Balance during all phases of an indoor cycling workout, warm-up, intensity intervals, intermittent recovery and at cooldown. The cyclist has no on-the-way interference and can fully concentrate on the demands of the stationay trainer workout, always facing the ideal airstream that will cool him/her appropriately. In addition the cyclists gets (as a bonus) insight in the development of his/her \u003cb\u003eCritical Power\u003c/b\u003e when training intensity is intense and long enough! The applied \u003cb\u003eArduino nRF52480 Express\u003c/b\u003e CPU is so powerfull that Real Time calculation of \u003cb\u003eCP\u003c/b\u003e and \u003cb\u003eW Prime\u003c/b\u003e can be accomplished aside all the calculations for determining the Heat Balance terms and setting the fans to the appropriate blowing capacity. \u003cbr clear=\"left\"\u003e\n   \n* [See the Airflow project](https://github.com/Berg0162/airflow) \u003cbr\u003e\n\n# Example Critical Power on the fly\nA cyclist (properties: \u003cb\u003eCP = 140 watt\u003c/b\u003e and \u003cb\u003eW Prime = 7.2 kJ\u003c/b\u003e) is doing an intense workout with the duration and intensity as shown in the figure. \u003cb\u003eW Prime\u003c/b\u003e is depleted several times during the first interval block. New values for \u003cb\u003eCP\u003c/b\u003e and \u003cb\u003eW Prime\u003c/b\u003e will be estimated by the algorithm during workout time only when appropriate. Run the video to see 100% depletion of W Prime and the estimated values as they were calculated in \u003cb\u003eReal Time\u003c/b\u003e and presented on an OLED screen... Notice how the horizontal bar shrinks when power is above \u003cb\u003eCP\u003c/b\u003e at 175 and 195 watts. The bar is proportional with \u003cb\u003eW' Balance\u003c/b\u003e and in addition what is \"left in the tank\" is shown as a percentage! Readings are shown in an accellerated sequence and do not conform indicated time duration of the workout!\n\u003cimg src=\"../main/images/Workout.png\" width=\"6730\" height=\"274\" align = \"middle\" alt=\"Workout\"\u003e \u003cbr\u003e\n\nhttps://user-images.githubusercontent.com/57005514/125782300-2790e8e7-a683-4040-8e79-d6eaab223613.mp4\n \n* [You can find the code of this example in the repository](../main/arduino/AirFlow_Test_W_Balance_RealTime.ino) \u003cbr\u003e\n   \n# References\n1. Hunter A, Coggan A. Training and Racing with a Power Meter. Boulder (CO): Velo Press; 2010. p. 5-20, 41-65 p. 9\n2. Skiba, P. F., Chidnok, W., Vanhatalo, A., \u0026 Jones, A. M. (2012). Modelling the expenditure and reconstitution of work capacity above critical power. Medicine and science in sports and exercise, 44(8), 1526-1532.\n3. http://markliversedge.blogspot.nl/2014/10/wbal-optimisation-by-mathematician.html\n4. https://github.com/AartGoossens/publications/blob/master/w_balance_algorithms/article.ipynb\n5. Skiba, P. F., Jackman, S., Clarke, D., Vanhatalo, A., \u0026 Jones, A. M. (2014). Effect of work and recovery durations on W' reconstitution during intermittent exercise. Medicine and science in sports and exercise, 46(7), 1433-1440.\n6. https://github.com/GoldenCheetah/GoldenCheetah/blob/master/src/Metrics/WPrime.cpp\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberg0162%2Frt-critical-power","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fberg0162%2Frt-critical-power","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberg0162%2Frt-critical-power/lists"}