Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
50fe5c7
Xiaomi Content Helper
cagnulein Apr 2, 2026
1dec664
adding AndroidDocumentPickerReceiver
cagnulein Apr 3, 2026
d7a5619
Update homeform.cpp
cagnulein Apr 3, 2026
964c127
activity result
cagnulein Apr 3, 2026
a5ba6ee
Update CustomQtActivity.java
cagnulein Apr 3, 2026
540d23d
native file picker
cagnulein Apr 4, 2026
51b356e
interval names fixed
cagnulein Apr 4, 2026
cb2d90a
Update templateinfosenderbuilder.cpp
cagnulein Apr 4, 2026
6d69060
adding zwo
cagnulein Apr 7, 2026
d0269d7
hr timeout
cagnulein Apr 17, 2026
254c0da
Revert "hr timeout"
cagnulein Apr 18, 2026
9aab9e9
trying to connect first to the HR
cagnulein Apr 21, 2026
d1803d5
Revert "trying to connect first to the HR"
cagnulein Apr 23, 2026
0c80621
connect HR in parrallel
cagnulein Apr 24, 2026
b102248
Merge branch 'master' into Xiaomi-Content-Helper
cagnulein May 4, 2026
6522bf7
Erg Workout Difficulty via Target Watt tile
cagnulein Apr 24, 2026
a0094fd
powerjog to 5 instead of 10
cagnulein May 12, 2026
ca04ee5
gpx other folder button fixed
cagnulein May 12, 2026
6c1c5a7
fixing gpx
cagnulein May 14, 2026
26cfdac
pid hr on zone 1
cagnulein Jun 5, 2026
37e5b1c
Merge branch 'master' into Xiaomi-Content-Helper
cagnulein Jun 5, 2026
4f3976c
pid limited to 0.8
cagnulein Jun 5, 2026
8212fcb
trainprogram_pid_hr_pushy_zone_limit as configurable setting
cagnulein Jun 8, 2026
910ffb5
Merge branch 'master' into Xiaomi-Content-Helper
cagnulein Jun 8, 2026
4eae20e
Merge 910ffb50b38728623adfac83bf6ef004b6a93981 into 4bb9d55f04175acdd…
cagnulein Jun 8, 2026
db0c845
chore: update translation strings [automated]
github-actions[bot] Jun 8, 2026
3cd6e37
feat: symmetric pushy slowdown near upper zone boundary (#4480)
cagnulein Jun 12, 2026
8df67bf
chore: sync translation .ts files from master
cagnulein Jun 12, 2026
d1429e8
Merge branch 'master' into Xiaomi-Content-Helper
cagnulein Jun 12, 2026
7b13216
fix: correct settingCount in settings-catalog.json (950 -> 951)
cagnulein Jun 12, 2026
40c9b12
chore: add pre-commit hook to auto-sync settings-catalog settingCount
cagnulein Jun 12, 2026
4f1df0e
fix: add missing closing brace for handleAndroidDocumentPicked
cagnulein Jun 12, 2026
712003f
Fix pushy HR handling in zone 1
cagnulein Jun 12, 2026
fc356de
Merge master into Xiaomi-Content-Helper
cagnulein Jun 12, 2026
a0c0912
feat: add configurable Recovery zone lower limit for Pushy mode (#4480)
cagnulein Jun 13, 2026
f51485d
fix: increment allSettingsCount to 964 for recovery zone limit setting
cagnulein Jun 13, 2026
3ccfc1e
Merge branch 'master' into Xiaomi-Content-Helper
cagnulein Jul 6, 2026
57eca9f
Fix Android document picker flow
cagnulein Jul 6, 2026
f18564b
Merge remote-tracking branch 'origin/master' into codex/review-pr-4494
cagnulein Jul 6, 2026
b5fd82b
Resolve Xiaomi Content Helper merge conflict
cagnulein Jul 6, 2026
38a7be5
Merge branch 'master' into Xiaomi-Content-Helper
cagnulein Jul 6, 2026
eeac23c
Switch Stryd 5 to Indoor power mode on connect
cagnulein Jul 29, 2026
9cad3d3
Confirm Stryd5 Indoor mode switch with pod ack, retry if missing
cagnulein Jul 29, 2026
06af79a
Fix Stryd5 Indoor mode characteristic UUID
cagnulein Jul 29, 2026
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
67 changes: 43 additions & 24 deletions src/devices/bluetooth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,42 @@ bool bluetooth::deviceHasService(const QBluetoothDeviceInfo &device, QBluetoothU
return false;
}

void bluetooth::attachHeartRateBeltSignals() {
if (!heartRateBelt || !this->device()) {
return;
}

connect(heartRateBelt, &heartratebelt::heartRate, this->device(), &bluetoothdevice::heartRate,
Qt::UniqueConnection);
connect(heartRateBelt, &heartratebelt::rrIntervalReceived, this->device(), &bluetoothdevice::rrIntervalReceived,
Qt::UniqueConnection);
}

void bluetooth::connectHeartRateBelt(const QBluetoothDeviceInfo &deviceInfo) {
if (heartRateBelt) {
attachHeartRateBeltSignals();
return;
}

QSettings settings;
settings.setValue(QZSettings::hrm_lastdevice_name, deviceInfo.name());

#ifndef Q_OS_IOS
settings.setValue(QZSettings::hrm_lastdevice_address, deviceInfo.address().toString());
#else
settings.setValue(QZSettings::hrm_lastdevice_address, deviceInfo.deviceUuid().toString());
#endif

heartRateBelt = new heartratebelt();
connect(heartRateBelt, &heartratebelt::debug, this, &bluetooth::debug);
attachHeartRateBeltSignals();
heartRateBelt->deviceDiscovered(deviceInfo);

if (homeform::singleton()) {
homeform::singleton()->setToastRequested(deviceInfo.name() + " (HR sensor) connected!");
}
}

void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {

QSettings settings;
Expand Down Expand Up @@ -631,6 +667,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
settings
.value(QZSettings::pafers_treadmill_bh_iboxster_plus, QZSettings::default_pafers_treadmill_bh_iboxster_plus)
.toBool();
if (!heartRateBeltName.startsWith(QStringLiteral("Disabled")) && !heartRateBelt &&
device.name().startsWith(heartRateBeltName)) {
connectHeartRateBelt(device);
}
bool gem_module_inclination =
settings.value(QZSettings::gem_module_inclination, QZSettings::default_gem_module_inclination).toBool();
bool iconcept_elliptical =
Expand Down Expand Up @@ -3057,6 +3097,7 @@ void bluetooth::connectedAndDiscovered() {
}

if (this->device() != nullptr) {
attachHeartRateBeltSignals();

#ifdef Q_OS_IOS
if (settings.value(QZSettings::ios_cache_heart_device, QZSettings::default_ios_cache_heart_device).toBool()) {
Expand All @@ -3066,41 +3107,19 @@ void bluetooth::connectedAndDiscovered() {
settings.value(QZSettings::hrm_lastdevice_name, QZSettings::default_hrm_lastdevice_name).toString();
qDebug() << "last hrm name" << b;
if (!b.compare(heartRateBeltName) && b.length()) {

heartRateBelt = new heartratebelt();
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));

connect(heartRateBelt, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
connect(heartRateBelt, SIGNAL(heartRate(uint8_t)), this->device(), SLOT(heartRate(uint8_t)));
connect(heartRateBelt, SIGNAL(rrIntervalReceived(double)), this->device(), SLOT(rrIntervalReceived(double)));
QBluetoothDeviceInfo bt;
bt.setDeviceUuid(QBluetoothUuid(
settings.value(QZSettings::hrm_lastdevice_address, QZSettings::default_hrm_lastdevice_address)
.toString()));
qDebug() << "UUID" << bt.deviceUuid();
heartRateBelt->deviceDiscovered(bt);
connectHeartRateBelt(bt);
}
}
#endif
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().startsWith(heartRateBeltName))) && !heartRateBelt &&
!heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
settings.setValue(QZSettings::hrm_lastdevice_name, b.name());

#ifndef Q_OS_IOS
settings.setValue(QZSettings::hrm_lastdevice_address, b.address().toString());
#else
settings.setValue(QZSettings::hrm_lastdevice_address, b.deviceUuid().toString());
#endif
heartRateBelt = new heartratebelt();
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));

connect(heartRateBelt, &heartratebelt::debug, this, &bluetooth::debug);
connect(heartRateBelt, &heartratebelt::heartRate, this->device(), &bluetoothdevice::heartRate);
connect(heartRateBelt, &heartratebelt::rrIntervalReceived, this->device(), &bluetoothdevice::rrIntervalReceived);
heartRateBelt->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested(b.name() + " (HR sensor) connected!");
connectHeartRateBelt(b);
break;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/devices/bluetooth.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ class bluetooth : public QObject, public SignalHandler {
volatile bool homeformLoaded = false;

private:
void attachHeartRateBeltSignals();
void connectHeartRateBelt(const QBluetoothDeviceInfo &deviceInfo);
bool useDiscovery = false;
QFile *debugCommsLog = nullptr;
QBluetoothDeviceDiscoveryAgent *discoveryAgent = nullptr;
Expand Down
42 changes: 42 additions & 0 deletions src/devices/strydrunpowersensor/strydrunpowersensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,26 @@ void strydrunpowersensor::update() {
sec1Update = 0;
// updateDisplay(elapsed);
}

if (strydIndoorModeRequested && !strydIndoorModeConfirmed && strydIndoorModeRetries < 3 &&
strydIndoorModeRequestedAt.msecsTo(QDateTime::currentDateTime()) > 1000) {
qDebug() << QStringLiteral("Stryd5: no ack for Indoor mode switch, retrying");
requestStrydIndoorMode();
}
}
}

void strydrunpowersensor::requestStrydIndoorMode() {
if (!strydIndoorModeService || !strydIndoorModeChar.isValid())
return;

qDebug() << QStringLiteral("Stryd5: switching power mode to Indoor");
strydIndoorModeService->writeCharacteristic(strydIndoorModeChar, QByteArray::fromHex("1500"));
strydIndoorModeRequested = true;
strydIndoorModeRequestedAt = QDateTime::currentDateTime();
strydIndoorModeRetries++;
}

void strydrunpowersensor::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
Expand Down Expand Up @@ -506,6 +523,18 @@ void strydrunpowersensor::characteristicChanged(const QLowEnergyCharacteristic &
//emit verticalOscillationChanged(VerticalOscillationMM.value());
qDebug() << QStringLiteral("Current GroundContactMS:") << GroundContactMS.value();
qDebug() << QStringLiteral("Current VerticalOscillationMM:") << VerticalOscillationMM.value();
} else if (strydIndoorModeRequested && newValue.length() >= 3 && (uint8_t)newValue.at(0) == 0x15 &&
(uint8_t)newValue.at(1) == 0x00) {
// ack of the 15 00/15 01 mode switch written to 7e78aa20: byte 2 echoes back the confirmed
// mode (0x00 = Indoor, 0x01 = Outdoor)
uint8_t confirmedMode = (uint8_t)newValue.at(2);
if (confirmedMode == 0x00) {
strydIndoorModeConfirmed = true;
qDebug() << QStringLiteral("Stryd5: Indoor mode confirmed by the pod");
} else {
qDebug() << QStringLiteral("Stryd5: pod acked mode ") << confirmedMode
<< QStringLiteral(" instead of Indoor, will retry");
}
}
}

Expand Down Expand Up @@ -585,6 +614,19 @@ void strydrunpowersensor::stateChanged(QLowEnergyService::ServiceState state) {
if (c.uuid() == QBluetoothUuid::CSCMeasurement) {
cadenceChar = c;
}

// Stryd 5 broadcasts a power value that already reacts to the incline it detects on its own
// (confirmed via BLE sniffing, see #4480), which conflicts with QZ's own inclination gain
// formula. Switching the pod to Indoor mode makes it stop compensating for incline on its
// own, leaving QZ's formula as the only source of the incline adjustment.
if (bluetoothDevice.name().startsWith(QStringLiteral("Stryd5"), Qt::CaseInsensitive) &&
c.uuid() == QBluetoothUuid(QStringLiteral("7e78aa20-72cd-d3b8-a81f-5b7e589bea0f")) &&
(c.properties() & QLowEnergyCharacteristic::Write)) {
strydIndoorModeService = s;
strydIndoorModeChar = c;
requestStrydIndoorMode();
}

qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
auto descriptors_list = c.descriptors();
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
Expand Down
8 changes: 8 additions & 0 deletions src/devices/strydrunpowersensor/strydrunpowersensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ class strydrunpowersensor : public treadmill {

bool FORERUNNER = false;

QLowEnergyService *strydIndoorModeService = nullptr;
QLowEnergyCharacteristic strydIndoorModeChar;
bool strydIndoorModeRequested = false;
bool strydIndoorModeConfirmed = false;
uint8_t strydIndoorModeRetries = 0;
QDateTime strydIndoorModeRequestedAt;
void requestStrydIndoorMode();

#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Expand Down
Loading