@@ -401,7 +401,7 @@ void ScanCallbacks::onResult(const NimBLEAdvertisedDevice* advertisedDevice) {
401401 if (serviceInfo) {
402402 SS2K_LOG (BLE_CLIENT_LOG_TAG, " Supported Device: %s with service %s" , aDevName.c_str (), serviceInfo->name .c_str ());
403403 const NimBLEUUID& primaryServiceUUID = serviceInfo->serviceUUID ;
404- // check to see if we're already connected to this device
404+ // check to see if we're already connected to this device
405405 for (size_t i = 0 ; i < NUM_BLE_DEVICES; i++) {
406406 if (spinBLEClient.myBLEDevices [i].advertisedDevice != nullptr ) {
407407 if (aDevName == String (spinBLEClient.myBLEDevices [i].uniqueName .c_str ())) {
@@ -671,50 +671,80 @@ void SpinBLEClient::postConnect() {
671671 // Enable device notifications
672672 byte message[] = {0xF0 , 0xB0 , 0x01 , 0x01 , 0xA2 };
673673 writeCharacteristic->writeValue (message, 5 );
674- SS2K_LOG (BLE_CLIENT_LOG_TAG, " Activated Echelon callbacks. " );
674+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " Activated Echelon callbacks on device: %s " , _BLEd. uniqueName . c_str () );
675675 rtConfig->setMinResistance (MIN_ECHELON_RESISTANCE);
676676 rtConfig->setMaxResistance (MAX_ECHELON_RESISTANCE);
677677 }
678678
679- if ((_BLEd.charUUID == FITNESSMACHINEINDOORBIKEDATA_UUID)) {
680- SS2K_LOG (BLE_CLIENT_LOG_TAG, " Updating Connection Params for: %s" , _BLEd.peerAddress .toString ().c_str ());
681- spinBLEClient.handleBattInfo (pClient, true );
679+ if (pClient->getService (FITNESSMACHINESERVICE_UUID)) {
680+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " Initializing FTMS on device: %s" , _BLEd.uniqueName .c_str ());
682681
683682 auto featuresCharacteristic = pClient->getService (FITNESSMACHINESERVICE_UUID)->getCharacteristic (FITNESSMACHINEFEATURE_UUID);
684683 if (featuresCharacteristic == nullptr ) {
685684 SS2K_LOG (BLE_CLIENT_LOG_TAG, " Failed to find FTMS features characteristic UUID: %s" , FITNESSMACHINEFEATURE_UUID.toString ().c_str ());
686- return ;
687- }
688-
689- if (featuresCharacteristic->canRead ()) {
690- auto value = featuresCharacteristic->readValue ();
691- if (value.size () < sizeof (uint64_t )) {
692- SS2K_LOG (BLE_CLIENT_LOG_TAG, " Failed to read FTMS features characteristic" );
693- return ;
694- }
695-
696- // We're only interested in the machine fitness features, not the target setting features.
697- auto features = *reinterpret_cast <const uint32_t *>(value.data ());
698- if (!(features & FitnessMachineFeatureFlags::Types::ElapsedTimeSupported) || !(features & FitnessMachineFeatureFlags::Types::RemainingTimeSupported)) {
699- SS2K_LOG (BLE_CLIENT_LOG_TAG, " FTMS Control Point StartOrResume not supported" );
700- return ;
685+ } else {
686+ if (featuresCharacteristic->canRead ()) {
687+ auto value = featuresCharacteristic->readValue ();
688+ if (value.size () < sizeof (uint64_t )) {
689+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " Failed to read FTMS features characteristic for: %s" , _BLEd.uniqueName .c_str ());
690+ } else {
691+ // We're only interested in the machine fitness features, not the target setting features.
692+ auto features = *reinterpret_cast <const uint32_t *>(value.data ());
693+ if (!(features & FitnessMachineFeatureFlags::Types::ElapsedTimeSupported) || !(features & FitnessMachineFeatureFlags::Types::RemainingTimeSupported)) {
694+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " FTMS Control Point StartOrResume not supported on: %s" , _BLEd.uniqueName .c_str ());
695+ }
696+
697+ NimBLERemoteCharacteristic* writeCharacteristic = pClient->getService (FITNESSMACHINESERVICE_UUID)->getCharacteristic (FITNESSMACHINECONTROLPOINT_UUID);
698+ if (writeCharacteristic == nullptr ) {
699+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " Failed to find FTMS control characteristic UUID: %s, on %s" , FITNESSMACHINECONTROLPOINT_UUID.toString ().c_str (),
700+ _BLEd.uniqueName .c_str ());
701+ } else {
702+ // If we would like to control an external FTMS trainer. With most spin bikes we would want this off, but it's useful if you want to use the SmartSpin2k as an
703+ // appliance.
704+ if (userConfig->getFTMSControlPointWrite ()) {
705+ writeCharacteristic->writeValue (FitnessMachineControlPointProcedure::RequestControl, 1 );
706+ delay (BLE_NOTIFY_DELAY);
707+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " Activated FTMS Training on device: %s" , _BLEd.uniqueName .c_str ());
708+ }
709+ writeCharacteristic->writeValue (FitnessMachineControlPointProcedure::StartOrResume, 1 );
710+ }
711+ }
701712 }
702713 }
714+ // update resistance range if supported:
715+ auto resistanceRangeCharacteristic = pClient->getService (FITNESSMACHINESERVICE_UUID)->getCharacteristic (FITNESSMACHINERESISTANCELEVELRANGE_UUID);
716+ if (resistanceRangeCharacteristic && resistanceRangeCharacteristic->canRead ()) {
717+ auto rr = resistanceRangeCharacteristic->readValue ();
718+ if (rr.size () >= 6 ) {
719+ const uint8_t * b = reinterpret_cast <const uint8_t *>(rr.data ());
720+ int16_t minRaw = static_cast <int16_t >(b[0 ] | (static_cast <uint16_t >(b[1 ]) << 8 ));
721+ int16_t maxRaw = static_cast <int16_t >(b[2 ] | (static_cast <uint16_t >(b[3 ]) << 8 ));
722+ uint16_t incRaw = static_cast <uint16_t >(b[4 ] | (static_cast <uint16_t >(b[5 ]) << 8 ));
723+
724+ float incF = static_cast <float >(incRaw) / 10 .0f ; // FTMS resolution 0.1, convert to actual increment value
725+ float minF = (static_cast <float >(minRaw) / 10 .0f )/incF; // Convert FTMS 0.1 units to normalized scale
726+ float maxF = (static_cast <float >(maxRaw) / 10 .0f )/incF; // Convert FTMS 0.1 units to normalized scale
727+
728+ // Internal resistance is integer-based; round to nearest
729+ int minRes = static_cast <int >(minF >= 0 .0f ? (minF + 0 .5f ) : (minF - 0 .5f ));
730+ int maxRes = static_cast <int >(maxF >= 0 .0f ? (maxF + 0 .5f ) : (maxF - 0 .5f ));
731+
732+ if (minRes > maxRes) {
733+ // Defensive: swap if device reports reversed
734+ int tmp = minRes;
735+ minRes = maxRes;
736+ maxRes = tmp;
737+ }
703738
704- NimBLERemoteCharacteristic* writeCharacteristic = pClient->getService (FITNESSMACHINESERVICE_UUID)->getCharacteristic (FITNESSMACHINECONTROLPOINT_UUID);
705- if (writeCharacteristic == nullptr ) {
706- SS2K_LOG (BLE_CLIENT_LOG_TAG, " Failed to find FTMS control characteristic UUID: %s" , FITNESSMACHINECONTROLPOINT_UUID.toString ().c_str ());
707- return ;
708- }
709-
710- // If we would like to control an external FTMS trainer. With most spin bikes we would want this off, but it's useful if you want to use the SmartSpin2k as an
711- // appliance.
712- if (userConfig->getFTMSControlPointWrite ()) {
713- writeCharacteristic->writeValue (FitnessMachineControlPointProcedure::RequestControl, 1 );
714- delay (BLE_NOTIFY_DELAY);
715- SS2K_LOG (BLE_CLIENT_LOG_TAG, " Activated FTMS Training." );
739+ rtConfig->resistance .setMin (minRes);
740+ rtConfig->resistance .setMax (maxRes);
741+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " FTMS Resistance Range: raw min=%.1f raw max=%.1f inc=%.1f -> set %d->%d" , minF, maxF, incF, minRes, maxRes);
742+ } else {
743+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " FTMS Resistance Range characteristic too short (%d bytes)" , rr.size ());
744+ }
745+ } else {
746+ SS2K_LOG (BLE_CLIENT_LOG_TAG, " FTMS Resistance Range characteristic unavailable or unreadable" );
716747 }
717- writeCharacteristic->writeValue (FitnessMachineControlPointProcedure::StartOrResume, 1 );
718748 }
719749 }
720750 }
0 commit comments