Skip to content

Commit 5c60fcd

Browse files
committed
Add PM5 Additional Stroke Data characteristic (CE060036)
- Add buildPM5AdditionalStrokeData() function with: - Stroke Power (bytes 3-4) - direct watts value - Stroke Calories (bytes 5-6) - Stroke Count (bytes 7-8) - Work Per Stroke (bytes 15-16) - Joules - Send Additional Stroke Data in rowerProvider() - This characteristic has Stroke Power directly which Mywhoosh may need https://claude.ai/code/session_01XWBgRPWze8Mb7DEEm4oXiF
1 parent ee6f0f6 commit 5c60fcd

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

src/virtualdevices/virtualrower.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,14 @@ void virtualrower::rowerProvider() {
590590
writeCharacteristic(servicePM5Rowing, charStrokeData, strokeData);
591591
qDebug() << "PM5 Stroke Data:" << strokeData.toHex(' ');
592592
}
593+
594+
// Send Additional Stroke Data (CE060036)
595+
QByteArray additionalStrokeData = buildPM5AdditionalStrokeData();
596+
QLowEnergyCharacteristic charAdditionalStrokeData = servicePM5Rowing->characteristic(PM5_ADDITIONAL_STROKE_DATA_UUID);
597+
if (charAdditionalStrokeData.isValid()) {
598+
writeCharacteristic(servicePM5Rowing, charAdditionalStrokeData, additionalStrokeData);
599+
qDebug() << "PM5 Additional Stroke Data:" << additionalStrokeData.toHex(' ');
600+
}
593601
} else {
594602
// FTMS protocol (original code)
595603
value.append((char)0x2C);
@@ -1238,3 +1246,84 @@ QByteArray virtualrower::buildPM5StrokeData() {
12381246

12391247
return value;
12401248
}
1249+
1250+
QByteArray virtualrower::buildPM5AdditionalStrokeData() {
1251+
// PM5 Additional Stroke Data - 17 bytes
1252+
// Bytes 0-2: Elapsed Time (UInt24LE, 0.01 sec)
1253+
// Bytes 3-4: Stroke Power (UInt16LE, watts)
1254+
// Bytes 5-6: Stroke Calories (UInt16LE, calories)
1255+
// Bytes 7-8: Stroke Count (UInt16LE)
1256+
// Bytes 9-11: Projected Work Time (UInt24LE, 0.01 sec)
1257+
// Bytes 12-14: Projected Work Distance (UInt24LE, meters)
1258+
// Bytes 15-16: Work Per Stroke (UInt16LE, Joules)
1259+
1260+
QByteArray value(17, 0);
1261+
1262+
// Calculate elapsed time in centiseconds (0.01 sec units)
1263+
uint32_t elapsedCentiseconds = (uint32_t)(
1264+
(Rower->movingTime().hour() * 3600 +
1265+
Rower->movingTime().minute() * 60 +
1266+
Rower->movingTime().second()) * 100 +
1267+
Rower->movingTime().msec() / 10);
1268+
1269+
// Elapsed time (24-bit LE)
1270+
value[0] = (char)(elapsedCentiseconds & 0xFF);
1271+
value[1] = (char)((elapsedCentiseconds >> 8) & 0xFF);
1272+
value[2] = (char)((elapsedCentiseconds >> 16) & 0xFF);
1273+
1274+
// Stroke Power in watts (16-bit LE)
1275+
uint16_t strokePower = (uint16_t)Rower->wattsMetricforUI();
1276+
value[3] = (char)(strokePower & 0xFF);
1277+
value[4] = (char)((strokePower >> 8) & 0xFF);
1278+
1279+
// Stroke Calories (16-bit LE) - calories per stroke
1280+
double strokeRate = Rower->currentCadence().value();
1281+
uint16_t strokeCalories = 0;
1282+
if (strokeRate > 0) {
1283+
double totalCalories = Rower->calories().value();
1284+
double totalSeconds = Rower->movingTime().hour() * 3600 +
1285+
Rower->movingTime().minute() * 60 +
1286+
Rower->movingTime().second();
1287+
if (totalSeconds > 0) {
1288+
double totalStrokes = strokeRate * totalSeconds / 60.0;
1289+
if (totalStrokes > 0) {
1290+
strokeCalories = (uint16_t)(totalCalories * 1000.0 / totalStrokes); // in 0.001 kCal
1291+
}
1292+
}
1293+
}
1294+
value[5] = (char)(strokeCalories & 0xFF);
1295+
value[6] = (char)((strokeCalories >> 8) & 0xFF);
1296+
1297+
// Stroke Count (16-bit LE)
1298+
uint16_t pm5StrokeCount = 0;
1299+
if (strokeRate > 0) {
1300+
double totalSeconds = Rower->movingTime().hour() * 3600 +
1301+
Rower->movingTime().minute() * 60 +
1302+
Rower->movingTime().second() +
1303+
Rower->movingTime().msec() / 1000.0;
1304+
pm5StrokeCount = (uint16_t)(strokeRate * totalSeconds / 60.0);
1305+
}
1306+
value[7] = (char)(pm5StrokeCount & 0xFF);
1307+
value[8] = (char)((pm5StrokeCount >> 8) & 0xFF);
1308+
1309+
// Projected Work Time - 0 (no target)
1310+
value[9] = 0x00;
1311+
value[10] = 0x00;
1312+
value[11] = 0x00;
1313+
1314+
// Projected Work Distance - 0 (no target)
1315+
value[12] = 0x00;
1316+
value[13] = 0x00;
1317+
value[14] = 0x00;
1318+
1319+
// Work Per Stroke in Joules (16-bit LE)
1320+
uint16_t workPerStroke = 0;
1321+
if (strokeRate > 0 && strokePower > 0) {
1322+
double strokeTime = 60.0 / strokeRate;
1323+
workPerStroke = (uint16_t)(strokePower * strokeTime);
1324+
}
1325+
value[15] = (char)(workPerStroke & 0xFF);
1326+
value[16] = (char)((workPerStroke >> 8) & 0xFF);
1327+
1328+
return value;
1329+
}

src/virtualdevices/virtualrower.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class virtualrower : public virtualdevice {
6969
QByteArray buildPM5AdditionalStatus();
7070
QByteArray buildPM5AdditionalStatus2();
7171
QByteArray buildPM5StrokeData();
72+
QByteArray buildPM5AdditionalStrokeData();
7273

7374
#ifdef Q_OS_IOS
7475
lockscreen *h = 0;

0 commit comments

Comments
 (0)