@@ -168,15 +168,15 @@ octanetreadmill::octanetreadmill(uint32_t pollDeviceTime, bool noConsole, bool n
168168
169169 actualPaceSign.clear ();
170170 actualPace2Sign.clear ();
171+ actualPace3Sign.clear ();
171172
172173 // ZR_PACE
173174 actualPaceSign.append (0x02 );
174175 actualPaceSign.append (0x23 );
175- actualPace2Sign.append (0x03 );
176176 actualPace2Sign.append (0x01 );
177177 actualPace2Sign.append (0x23 );
178- cadenceSign .append (0x2c );
179- cadenceSign .append (0x01 );
178+ actualPace3Sign .append (( char ) 0x00 );
179+ actualPace3Sign .append (0x23 );
180180 cadenceSign.append (0x3A );
181181
182182 m_watt.setType (metric::METRIC_WATT , deviceType ());
@@ -259,11 +259,27 @@ void octanetreadmill::changeInclinationRequested(double grade, double percentage
259259}
260260
261261void octanetreadmill::update () {
262+ if (!m_control)
263+ return ;
264+
262265 if (m_control->state () == QLowEnergyController::UnconnectedState) {
263266 emit disconnected ();
264267 return ;
265268 }
266269
270+ // ZR8: Check if cadence has been missing for too long (indicates treadmill stopped)
271+ if (ZR8 && !lastValidCadenceTime.isNull ()) {
272+ qint64 secondsSinceLastCadence = lastValidCadenceTime.secsTo (QDateTime::currentDateTime ());
273+ if (secondsSinceLastCadence > 30 && Speed.value () > 0 ) {
274+ emit debug (QStringLiteral (" ZR8: No cadence received for " ) + QString::number (secondsSinceLastCadence) +
275+ QStringLiteral (" seconds, resetting speed and cadence to 0" ));
276+ Speed = 0 ;
277+ Cadence = 0 ;
278+ emit speedChanged (0 );
279+ lastValidCadenceTime = QDateTime (); // Reset to avoid repeated logging
280+ }
281+ }
282+
267283 qDebug () << m_control->state () << bluetoothDevice.isValid () << gattCommunicationChannelService
268284 << gattWriteCharacteristic.isValid () << initDone << requestSpeed << requestInclination;
269285
@@ -350,38 +366,171 @@ void octanetreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
350366 emit debug (QStringLiteral (" resetting speed" ));
351367 Speed = 0 ;
352368 Cadence = 0 ;
353- } else if (ZR8 == true && Speed.lastChanged ().secsTo (QDateTime::currentDateTime ()) > 15 &&
354- Cadence.lastChanged ().secsTo (QDateTime::currentDateTime ()) > 15 ) {
355- emit debug (QStringLiteral (" resetting speed" ));
356- Speed = 0 ;
357- Cadence = 0 ;
358369 }
359370
360- if ((newValue.length () != 20 ))
361- return ;
371+ // ZR8: Packet reassembly logic for fragmented BLE messages
372+ if (ZR8 && (uint8_t )newValue[0 ] == 0xa5 ) {
373+ // Start of a new ZR8 packet
374+ if (newValue.length () >= 2 ) {
375+ uint8_t lengthByte = (uint8_t )newValue[1 ];
376+
377+ // Map length byte to expected packet size
378+ int expectedLen = 0 ;
379+ if (lengthByte == 0x1d ) {
380+ expectedLen = 29 ; // A5 1D format
381+ } else if (lengthByte == 0x26 ) {
382+ expectedLen = 38 ; // A5 26 format (Gait Analysis)
383+ } else if (lengthByte == 0x23 ) {
384+ expectedLen = 35 ; // A5 23 format
385+ } else if (lengthByte == 0x20 || lengthByte == 0x21 ) {
386+ expectedLen = 32 ; // A5 20/21 format
387+ }
388+
389+ if (expectedLen > 0 ) {
390+ packetBuffer.clear ();
391+ packetBuffer.append (newValue);
392+ expectedPacketLength = expectedLen;
393+
394+ if (packetBuffer.length () >= expectedPacketLength) {
395+ // Complete packet received
396+ value = packetBuffer.mid (0 , expectedPacketLength);
397+ packetBuffer.clear ();
398+ expectedPacketLength = 0 ;
399+ emit debug (QStringLiteral (" ZR8: Complete packet assembled: " ) +
400+ QString::number (expectedLen) + QStringLiteral (" bytes" ));
401+ } else {
402+ // Incomplete, need more fragments
403+ return ;
404+ }
405+ } else {
406+ return ;
407+ }
408+ } else {
409+ return ;
410+ }
411+ } else if (ZR8 && expectedPacketLength > 0 ) {
412+ // Continuation of a fragmented packet
413+ packetBuffer.append (newValue);
414+
415+ if (packetBuffer.length () >= expectedPacketLength) {
416+ // Complete packet now assembled
417+ value = packetBuffer.mid (0 , expectedPacketLength);
418+ packetBuffer.clear ();
419+ expectedPacketLength = 0 ;
420+ emit debug (QStringLiteral (" ZR8: Complete packet assembled from fragments" ));
421+ } else {
422+ return ;
423+ }
424+ } else {
425+ // Non-ZR8 or non-A5 packet
426+ if ((value.length () != 20 ))
427+ return ;
428+
429+ // ZR8: Reject 20-byte packets that don't start with 0xa5 (incomplete/corrupted)
430+ if (ZR8 && (uint8_t )value[0 ] != 0xa5 ) {
431+ qDebug () << " ZR8: Rejecting 20-byte non-a5 packet:" << value.toHex ().left (6 );
432+ return ;
433+ }
362434
363- if (ZR8 && newValue.contains (cadenceSign)) {
364- int16_t i = newValue.indexOf (cadenceSign) + 3 ;
435+ if ((uint8_t )newValue[0 ] == 0xa5 && newValue[1 ] == 0x17 )
436+ return ;
437+ }
365438
366- if (i >= newValue.length ())
439+ // ZR8: Speed data is only in packets starting with a5 2[0123] 06
440+ // Other packets containing 02 23 signature do not have valid speed data
441+ if (ZR8 ) {
442+ qDebug () << " ZR8 Check: value.length()=" << value.length () << " value[0]=" << QString::number ((uint8_t )value[0 ], 16 );
443+ if (value.length () < 18 ) {
444+ qDebug () << " ZR8: Packet too short, rejecting" ;
367445 return ;
446+ }
368447
369- Cadence = ((uint8_t )newValue.at (i));
448+ // Check for valid speed packet header: a5 20 06, a5 21 06, a5 23 06, or a5 1d (cadence format)
449+ uint8_t byte0 = (uint8_t )value[0 ];
450+ uint8_t byte1 = (uint8_t )value[1 ];
451+ uint8_t byte2 = (uint8_t )value[2 ];
452+ bool isValidSpeedPacket = (byte0 == 0xa5 ) &&
453+ (byte1 == 0x20 || byte1 == 0x21 || byte1 == 0x23 || byte1 == 0x1d ) &&
454+ (byte2 == 0x06 );
455+ qDebug () << " ZR8 Header Check: byte0=" << QString::number (byte0, 16 )
456+ << " byte1=" << QString::number (byte1, 16 )
457+ << " byte2=" << QString::number (byte2, 16 )
458+ << " isValidSpeedPacket=" << isValidSpeedPacket;
459+ if (!isValidSpeedPacket) {
460+ qDebug () << " ZR8: Invalid header, rejecting packet" ;
461+ return ; // Invalid packet header, do not process
462+ }
370463 }
371464
372- if ((uint8_t )newValue[0 ] == 0xa5 && newValue[1 ] == 0x17 )
373- return ;
465+ // ZR8: Cadence parsing from 0x3A marker with anomaly filtering
466+ if (ZR8 && value.contains (cadenceSign)) {
467+ int16_t cadenceIdx = value.indexOf (cadenceSign) + 1 ;
468+
469+ if (cadenceIdx >= value.length () || cadenceIdx < 1 ) {
470+ // Marker not found or invalid position
471+ emit debug (QStringLiteral (" ZR8: Cadence marker (0x3A) not found or invalid" ));
472+ } else {
473+ uint8_t rawCadence = (uint8_t )value.at (cadenceIdx);
474+
475+ // Anomaly filtering: accept values in realistic range (20-200 RPM)
476+ // Values outside this range are likely parsing errors from packet format shifts
477+ if (rawCadence >= 20 && rawCadence <= 200 ) {
478+ Cadence = rawCadence;
479+ lastValidCadenceTime = QDateTime::currentDateTime ();
480+ emit debug (QStringLiteral (" ZR8: Cadence parsed: " ) + QString::number (rawCadence));
481+
482+ // Reset cadence zero timer when valid cadence is received
483+ if (!lastCadenceZeroTime.isNull ()) {
484+ lastCadenceZeroTime = QDateTime ();
485+ }
486+ } else {
487+ // Anomalous cadence value - likely from packet format shift
488+ emit debug (QStringLiteral (" ZR8: Cadence anomaly filtered: " ) + QString::number (rawCadence) +
489+ QStringLiteral (" RPM (outside 20-200 range)" ));
490+
491+ // Track time when cadence drops below 20 (potential stop condition)
492+ if (rawCadence < 20 && rawCadence > 0 ) {
493+ if (lastCadenceZeroTime.isNull ()) {
494+ lastCadenceZeroTime = QDateTime::currentDateTime ();
495+ emit debug (QStringLiteral (" ZR8: Cadence low (< 20 RPM), starting 5-second timer" ));
496+ } else {
497+ qint64 secondsSinceLowCadence = lastCadenceZeroTime.secsTo (QDateTime::currentDateTime ());
498+ if (secondsSinceLowCadence >= 5 && Speed.value () > 0 ) {
499+ emit debug (QStringLiteral (" ZR8: Cadence < 20 for 5+ seconds, resetting speed to 0" ));
500+ Speed = 0 ;
501+ Cadence = 0 ;
502+ emit speedChanged (0 );
503+ }
504+ }
505+ } else if (rawCadence == 0 ) {
506+ // Cadence explicitly zero - reset speed immediately
507+ if (Speed.value () > 0 ) {
508+ emit debug (QStringLiteral (" ZR8: Cadence = 0, resetting speed" ));
509+ Speed = 0 ;
510+ Cadence = 0 ;
511+ emit speedChanged (0 );
512+ }
513+ }
514+ }
515+ }
516+ }
374517
375- if (!newValue .contains (actualPaceSign) && !newValue .contains (actualPace2Sign))
518+ if (!value .contains (actualPaceSign) && !value .contains (actualPace2Sign) && !value. contains (actualPace3Sign ))
376519 return ;
377520
378- int16_t i = newValue.indexOf (actualPaceSign) + 2 ;
521+ int16_t i = value.indexOf (actualPaceSign) + 2 ;
522+ if (i <= 1 )
523+ i = value.indexOf (actualPace2Sign) + 2 ;
379524 if (i <= 1 )
380- i = newValue .indexOf (actualPace2Sign ) + 3 ;
525+ i = value .indexOf (actualPace3Sign ) + 2 ;
381526
382- if (i + 1 >= newValue .length ())
527+ if (i + 1 >= value .length ())
383528 return ;
384529
530+ qDebug () << " ZR8 Speed Debug: i=" << i << " value.toHex()=" << value.toHex ()
531+ << " byte[i]=" << QString::number ((uint8_t )value.at (i), 16 )
532+ << " byte[i+1]=" << QString::number ((uint8_t )value.at (i+1 ), 16 );
533+
385534 double speed = GetSpeedFromPacket (value, i);
386535 if (isinf (speed))
387536 return ;
@@ -396,6 +545,8 @@ void octanetreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
396545 }
397546 emit debug (QStringLiteral (" Current speed: " ) + QString::number (speed));
398547
548+ lastValidCadenceTime = QDateTime::currentDateTime ();
549+
399550 if (Speed.value () != speed) {
400551 emit speedChanged (speed);
401552 }
@@ -433,8 +584,9 @@ void octanetreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
433584
434585 emit debug (QStringLiteral (" Current Distance Calculated: " ) + QString::number (Distance.value ()));
435586 emit debug (QStringLiteral (" Current KCal: " ) + QString::number (KCal.value ()));
587+ emit debug (QStringLiteral (" Current Cadence: " ) + QString::number (Cadence.value ()));
436588
437- if (m_control->error () != QLowEnergyController::NoError) {
589+ if (m_control && m_control ->error () != QLowEnergyController::NoError) {
438590 qDebug () << QStringLiteral (" QLowEnergyController ERROR!!" ) << m_control->errorString ();
439591 }
440592
0 commit comments