Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added new BLE firmware update protocol.

### Changed
- Truncate only the BLE-advertised device name when needed so the SmartSpin2k service UUID remains present in the legacy scan response.
- Resume the latest ERG target automatically when cadence recovers after a low-cadence fallback, unless another FTMS command selects a different mode.

### Hardware

Expand Down
3 changes: 2 additions & 1 deletion include/ERG_Mode.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ class ErgMode {
// What used to be in the ERGTaskLoop(). This is the main control function for ERG Mode and the powertable operations.
void runERG();
void computeErg();
void onFTMSCommand(uint8_t opcode);
void _writeLog(float currentIncline, float newIncline, int currentSetPoint, int newSetPoint, int currentWatts, int newWatts, int currentCadence, int newCadence);

private:
bool engineStopped = false;
bool resumeErgOnCadence = false;

int mode = Mode::MAINTAIN;
Measurement prevWatts;
Expand Down
3 changes: 3 additions & 0 deletions src/BLE_Fitness_Machine_Service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
#include "BLE_Fitness_Machine_Service.h"
#include "DirConManager.h"
#include "ERG_Mode.h"
#include "Main.h"
#include <Constants.h>
#include <vector>
Expand Down Expand Up @@ -168,6 +169,8 @@ void BLE_Fitness_Machine_Service::processFTMSWrite() {
int logBufLength = ss2k_log_hex_to_buffer(pData, length, logBuf, 0, kLogBufCapacity);
int port = 0;

ergMode->onFTMSCommand(static_cast<uint8_t>(rxValue[0]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve pending ERG recovery across StartOrResume

Calling onFTMSCommand() for every opcode causes StartOrResume to clear resumeErgOnCadence, even though that command only updates training/status state and does not select another control mode. If an app sends Start/Resume while the rider is still below the cadence threshold, subsequent cadence recovery leaves the trainer in simulation mode instead of restoring the latest ERG target; restrict cancellation to commands that actually change the control mode, or explicitly re-arm recovery on Start/Resume.

AGENTS.md reference: AGENTS.md:L300-L307

Useful? React with 👍 / 👎.


switch ((uint8_t)rxValue[0]) {
case FitnessMachineControlPointProcedure::RequestControl:
returnValue[2] = FitnessMachineControlPointResultCode::Success;
Expand Down
26 changes: 25 additions & 1 deletion src/BLE_Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
#include <WiFi.h>
#include <host/ble_gatt.h>
#include <cmath>
#include <cstring>
#include <limits>
#include <string>
#include "BLE_Cycling_Speed_Cadence.h"
#include "BLE_Cycling_Power_Service.h"
#include "BLE_Heart_Service.h"
Expand Down Expand Up @@ -43,6 +45,22 @@ BLE_OpenBikeControl_Service openBikeControlService;

namespace {
constexpr uint8_t SMARTSPIN2K_IP_ADVERTISEMENT_VERSION = 1;
// Leaves room for the 128-bit SmartSpin2k service UUID in the 31-byte scan response.
constexpr size_t BLE_ADVERTISED_NAME_MAX_SIZE = 11;

std::string bleAdvertisementName(const char* deviceName) {
std::string name = deviceName;
if (name.size() <= BLE_ADVERTISED_NAME_MAX_SIZE) {
return name;
}

size_t length = BLE_ADVERTISED_NAME_MAX_SIZE;
while (length > 0 && (static_cast<uint8_t>(name[length]) & 0xc0) == 0x80) {
--length;
}
name.resize(length);
return name;
}

void addIpAddressToAdvertisement(NimBLEAdvertising* advertising) {
IPAddress ipAddress = WiFi.status() == WL_CONNECTED ? WiFi.localIP() : WiFi.softAPIP();
Expand Down Expand Up @@ -103,7 +121,13 @@ void startBLEServer() {
// Keep the name and 128-bit SmartSpin2k UUID in the scan response. The primary
// advertisement uses the space previously occupied by the duplicate name for the IP address.
addIpAddressToAdvertisement(pAdvertising);
oScanResponseData.setName(userConfig->getDeviceName());
const std::string advertisedName = bleAdvertisementName(userConfig->getDeviceName());
if (advertisedName.size() < std::strlen(userConfig->getDeviceName())) {
oScanResponseData.setShortName(advertisedName);
SS2K_LOGW(BLE_SERVER_LOG_TAG, "BLE device name shortened to '%s' to fit scan response", advertisedName.c_str());
} else {
oScanResponseData.setName(advertisedName);
}
oScanResponseData.setCompleteServices(SMARTSPIN2K_SERVICE_UUID);
pAdvertising->setScanResponseData(oScanResponseData);

Expand Down
23 changes: 20 additions & 3 deletions src/ERG_Mode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,20 @@ double clampErgGain(double gain, double sensitivity) {
} // namespace

void ErgMode::runERG() {
static ErgMode ergMode;
static PowerBuffer powerBuffer;
static bool hasConnectedPowerMeter = false;
static bool simulationRunning = false;
static int loopCounter = 0;

if (resumeErgOnCadence && rtConfig->cad.getValue() > MIN_ERG_CADENCE) {
if (rtConfig->getFTMSMode() == FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters) {
rtConfig->setFTMSMode(FitnessMachineControlPointProcedure::SetTargetPower);
SS2K_LOG(ERG_MODE_LOG_TAG, "Cadence resumed; restoring ERG mode at %dw", rtConfig->watts.getTarget());
Comment thread
doudar marked this conversation as resolved.
Outdated
}
resumeErgOnCadence = false;
ergTimer = 0;
}

if (mode == Mode::INCREASING) {
if (rtConfig->watts.getValue() > rtConfig->watts.getTarget()) { // Resume PID control
ergTimer = 0;
Expand Down Expand Up @@ -144,7 +152,7 @@ void ErgMode::runERG() {

// compute ERG
if ((rtConfig->getFTMSMode() == FitnessMachineControlPointProcedure::SetTargetPower) && (hasConnectedPowerMeter || simulationRunning)) {
ergMode.computeErg();
this->computeErg();
}

// Set Min and Max Stepper positions
Expand Down Expand Up @@ -357,14 +365,23 @@ void ErgMode::_updateValues(float newIncline) {

bool ErgMode::_userIsSpinning(int cadence, float incline) {
if (cadence <= MIN_ERG_CADENCE) {
resumeErgOnCadence = true;
mode = Mode::MAINTAIN;
isDelayed = false;
rtConfig->setFTMSMode(FitnessMachineControlPointProcedure::SetIndoorBikeSimulationParameters);
rtConfig->setTargetIncline(1.0f);
return false; // Cadence too low, nothing to do here
}
this->engineStopped = false;
return true;
}

void ErgMode::onFTMSCommand(uint8_t opcode) {
if (resumeErgOnCadence && opcode != FitnessMachineControlPointProcedure::SetTargetPower) {
resumeErgOnCadence = false;
SS2K_LOG(ERG_MODE_LOG_TAG, "ERG cadence resume cancelled by FTMS command 0x%02x", opcode);
}
}

void ErgMode::_writeLog(float currentIncline, float newIncline, int currentSetPoint, int newSetPoint, int currentWatts, int newWatts, int currentCadence, int newCadence) {
SS2K_LOGW(ERG_MODE_LOG_CSV_TAG, "%d;%.2f;%.2f;%d;%d;%d;%d;%d", currentIncline, newIncline, currentSetPoint, newSetPoint, currentWatts, newWatts, currentCadence, newCadence);
}
Loading