From 0b63f3214ca308f5a61a398979653243ad2011cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 15:32:54 +0000 Subject: [PATCH 1/4] Initial plan From bc373eb97b2914778716907732d35871dc1fcdf4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 15:42:04 +0000 Subject: [PATCH 2/4] Switch zero-watt ERG targets to sim mode Agent-Logs-Url: https://github.com/doudar/SmartSpin2k/sessions/33f20c65-9299-4587-81af-67f21f7b2a65 Co-authored-by: doudar <17362216+doudar@users.noreply.github.com> --- include/FTMS_Utils.h | 12 ++++++++++++ src/BLE_Fitness_Machine_Service.cpp | 16 ++++++++++++++-- test/test.h | 6 ++++++ test/test_ftms_free_ride.cpp | 19 +++++++++++++++++++ test/test_unity.cpp | 9 ++++++++- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 include/FTMS_Utils.h create mode 100644 test/test_ftms_free_ride.cpp diff --git a/include/FTMS_Utils.h b/include/FTMS_Utils.h new file mode 100644 index 00000000..a33a093b --- /dev/null +++ b/include/FTMS_Utils.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2020 Anthony Doud & Joel Baranick + * All rights reserved + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#pragma once + +#include + +inline bool ftmsTargetPowerRequestsFreeRide(uint16_t targetPower) { return targetPower == 0; } diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 1ac61a5b..93a11d5a 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -6,6 +6,7 @@ */ #include "BLE_Fitness_Machine_Service.h" #include "DirConManager.h" +#include "FTMS_Utils.h" #include "Main.h" #include #include @@ -245,9 +246,20 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { case FitnessMachineControlPointProcedure::SetTargetPower: { rtConfig->setFTMSMode((uint8_t)rxValue[0]); - if (spinBLEClient.connectedPM || rtConfig->watts.getSimulate() || spinBLEClient.connectedCD) { + uint16_t requestedTargetPower = bytes_to_u16(rxValue[2], rxValue[1]); + if (ftmsTargetPowerRequestsFreeRide(requestedTargetPower)) { + const uint8_t simMode[] = {FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters, 0x00, 0x00, 0x00, 0x00, 0x28, 0x33}; + rtConfig->setFTMSMode(FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters); + rtConfig->watts.setTarget(0); + rtConfig->setTargetIncline(0); + returnValue[2] = FitnessMachineControlPointResultCode::Success; + logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> ERG Mode Target: 0w, switching to Sim Mode"); + ftmsStatus = {FitnessMachineStatus::IndoorBikeSimulationParametersChanged, simMode[1], simMode[2], simMode[3], simMode[4], simMode[5], simMode[6]}; + ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::ManualMode; + spinBLEClient.FTMSControlPointWrite(simMode, sizeof(simMode)); + } else if (spinBLEClient.connectedPM || rtConfig->watts.getSimulate() || spinBLEClient.connectedCD) { returnValue[2] = FitnessMachineControlPointResultCode::Success; // 0x01; - rtConfig->watts.setTarget(bytes_to_u16(rxValue[2], rxValue[1])); + rtConfig->watts.setTarget(requestedTargetPower); logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> ERG Mode Target: %d Current: %d Incline: %2f", rtConfig->watts.getTarget(), rtConfig->watts.getValue(), rtConfig->getTargetIncline() / 100); ftmsStatus = {FitnessMachineStatus::TargetPowerChanged, (uint8_t)rxValue[1], (uint8_t)rxValue[2]}; diff --git a/test/test.h b/test/test.h index 54a45ddc..087119d0 100644 --- a/test/test.h +++ b/test/test.h @@ -56,3 +56,9 @@ class TestAdevName2UniqueName { static void test_device_without_name(void); static void test_backward_compatibility(void); }; + +class TestFTMSFreeRide { + public: + static void test_zero_watt_target_requests_free_ride(void); + static void test_nonzero_watt_target_stays_erg(void); +}; diff --git a/test/test_ftms_free_ride.cpp b/test/test_ftms_free_ride.cpp new file mode 100644 index 00000000..795bcf91 --- /dev/null +++ b/test/test_ftms_free_ride.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2020 Anthony Doud & Joel Baranick + * All rights reserved + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include "test.h" +#include "FTMS_Utils.h" +#include + +void TestFTMSFreeRide::test_zero_watt_target_requests_free_ride(void) { + TEST_ASSERT_TRUE(ftmsTargetPowerRequestsFreeRide(0)); +} + +void TestFTMSFreeRide::test_nonzero_watt_target_stays_erg(void) { + TEST_ASSERT_FALSE(ftmsTargetPowerRequestsFreeRide(1)); + TEST_ASSERT_FALSE(ftmsTargetPowerRequestsFreeRide(250)); +} diff --git a/test/test_unity.cpp b/test/test_unity.cpp index 31f43996..26816edb 100644 --- a/test/test_unity.cpp +++ b/test/test_unity.cpp @@ -84,6 +84,13 @@ void setup() { RUN_TEST(test.test_backward_compatibility); } + // FTMS ERG free-ride handling tests + { + TestFTMSFreeRide test; + RUN_TEST(test.test_zero_watt_target_requests_free_ride); + RUN_TEST(test.test_nonzero_watt_target_stays_erg); + } + UNITY_END(); } @@ -123,4 +130,4 @@ int main(int argc, char** argv) { setup(); return 0; } -#endif \ No newline at end of file +#endif From 6c5fdc6ac377a4e93d6115bb0dfb14c2e4434c3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 15:46:06 +0000 Subject: [PATCH 3/4] Address ERG free-ride review feedback Agent-Logs-Url: https://github.com/doudar/SmartSpin2k/sessions/33f20c65-9299-4587-81af-67f21f7b2a65 Co-authored-by: doudar <17362216+doudar@users.noreply.github.com> --- src/BLE_Fitness_Machine_Service.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 93a11d5a..9d6388f9 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -248,12 +248,14 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { rtConfig->setFTMSMode((uint8_t)rxValue[0]); uint16_t requestedTargetPower = bytes_to_u16(rxValue[2], rxValue[1]); if (ftmsTargetPowerRequestsFreeRide(requestedTargetPower)) { + // Default sim params: wind 0, grade 0%, rolling resistance 0x28, wind resistance 0x33. const uint8_t simMode[] = {FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters, 0x00, 0x00, 0x00, 0x00, 0x28, 0x33}; rtConfig->setFTMSMode(FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters); rtConfig->watts.setTarget(0); rtConfig->setTargetIncline(0); returnValue[2] = FitnessMachineControlPointResultCode::Success; - logBufLength += snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> ERG Mode Target: 0w, switching to Sim Mode"); + logBufLength += + snprintf(logBuf + logBufLength, kLogBufCapacity - logBufLength, "-> ERG Mode Target: %dw, switching to Sim Mode", requestedTargetPower); ftmsStatus = {FitnessMachineStatus::IndoorBikeSimulationParametersChanged, simMode[1], simMode[2], simMode[3], simMode[4], simMode[5], simMode[6]}; ftmsTrainingStatus[1] = FitnessMachineTrainingStatus::ManualMode; spinBLEClient.FTMSControlPointWrite(simMode, sizeof(simMode)); From cc7c001293131ad0d81698b779b703049ac63776 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 21:46:54 +0000 Subject: [PATCH 4/4] Inline zero-watt ERG check Agent-Logs-Url: https://github.com/doudar/SmartSpin2k/sessions/760e2bb2-08f2-4f1e-b692-ec66f9a3726b Co-authored-by: doudar <17362216+doudar@users.noreply.github.com> --- include/FTMS_Utils.h | 12 ------------ src/BLE_Fitness_Machine_Service.cpp | 3 +-- test/test.h | 6 ------ test/test_ftms_free_ride.cpp | 19 ------------------- test/test_unity.cpp | 7 ------- 5 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 include/FTMS_Utils.h delete mode 100644 test/test_ftms_free_ride.cpp diff --git a/include/FTMS_Utils.h b/include/FTMS_Utils.h deleted file mode 100644 index a33a093b..00000000 --- a/include/FTMS_Utils.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (C) 2020 Anthony Doud & Joel Baranick - * All rights reserved - * - * SPDX-License-Identifier: GPL-2.0-only - */ - -#pragma once - -#include - -inline bool ftmsTargetPowerRequestsFreeRide(uint16_t targetPower) { return targetPower == 0; } diff --git a/src/BLE_Fitness_Machine_Service.cpp b/src/BLE_Fitness_Machine_Service.cpp index 9d6388f9..cc5a5b39 100644 --- a/src/BLE_Fitness_Machine_Service.cpp +++ b/src/BLE_Fitness_Machine_Service.cpp @@ -6,7 +6,6 @@ */ #include "BLE_Fitness_Machine_Service.h" #include "DirConManager.h" -#include "FTMS_Utils.h" #include "Main.h" #include #include @@ -247,7 +246,7 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() { case FitnessMachineControlPointProcedure::SetTargetPower: { rtConfig->setFTMSMode((uint8_t)rxValue[0]); uint16_t requestedTargetPower = bytes_to_u16(rxValue[2], rxValue[1]); - if (ftmsTargetPowerRequestsFreeRide(requestedTargetPower)) { + if (requestedTargetPower == 0) { // Default sim params: wind 0, grade 0%, rolling resistance 0x28, wind resistance 0x33. const uint8_t simMode[] = {FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters, 0x00, 0x00, 0x00, 0x00, 0x28, 0x33}; rtConfig->setFTMSMode(FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters); diff --git a/test/test.h b/test/test.h index 087119d0..54a45ddc 100644 --- a/test/test.h +++ b/test/test.h @@ -56,9 +56,3 @@ class TestAdevName2UniqueName { static void test_device_without_name(void); static void test_backward_compatibility(void); }; - -class TestFTMSFreeRide { - public: - static void test_zero_watt_target_requests_free_ride(void); - static void test_nonzero_watt_target_stays_erg(void); -}; diff --git a/test/test_ftms_free_ride.cpp b/test/test_ftms_free_ride.cpp deleted file mode 100644 index 795bcf91..00000000 --- a/test/test_ftms_free_ride.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2020 Anthony Doud & Joel Baranick - * All rights reserved - * - * SPDX-License-Identifier: GPL-2.0-only - */ - -#include "test.h" -#include "FTMS_Utils.h" -#include - -void TestFTMSFreeRide::test_zero_watt_target_requests_free_ride(void) { - TEST_ASSERT_TRUE(ftmsTargetPowerRequestsFreeRide(0)); -} - -void TestFTMSFreeRide::test_nonzero_watt_target_stays_erg(void) { - TEST_ASSERT_FALSE(ftmsTargetPowerRequestsFreeRide(1)); - TEST_ASSERT_FALSE(ftmsTargetPowerRequestsFreeRide(250)); -} diff --git a/test/test_unity.cpp b/test/test_unity.cpp index 26816edb..ac859dfb 100644 --- a/test/test_unity.cpp +++ b/test/test_unity.cpp @@ -84,13 +84,6 @@ void setup() { RUN_TEST(test.test_backward_compatibility); } - // FTMS ERG free-ride handling tests - { - TestFTMSFreeRide test; - RUN_TEST(test.test_zero_watt_target_requests_free_ride); - RUN_TEST(test.test_nonzero_watt_target_stays_erg); - } - UNITY_END(); }