@@ -76,13 +76,17 @@ void proformtreadmill::forceIncline(double incline) {
7676 write[10] = 0x00;
7777 write[11] = 0x10;
7878 write[12] = (uint8_t)incline;
79+ } else if (proform_carbon_tl_PFTL59720_hci) {
80+ // Native PFTL59720 incline writes use 02 00 <incline>.
81+ write[9] = 0x02;
82+ write[10] = 0x00;
7983 write[14] = write[11] + write[12] + 0x11;
8084 } else if (norditrack_s25i_treadmill) {
8185 write[14] = write[11] + write[12] + 0x11;
8286 } else if (proform_treadmill_8_0 || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 || proform_treadmill_9_0 || proform_treadmill_se ||
8387 proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill || proform_8_5_treadmill || nordictrack_treadmill_exp_5i || proform_2000_treadmill ||
8488 proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_505_cst_80_44 || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
85- proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_treadmill_sport_70 || proform_treadmill_575i || proform_performance_300i || proform_performance_400i || proform_treadmill_c700 ||
89+ proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_carbon_tl_PFTL59720_hci || proform_treadmill_sport_70 || proform_treadmill_575i || proform_performance_300i || proform_performance_400i || proform_treadmill_c700 ||
8690 proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c || proform_treadmill_1500_pro || proform_trainer_8_0 || proform_trainer_8_0_pftl59721_int_0 || proform_treadmill_705_cst_V80_44 ||
8791 nordictrack_treadmill_ultra_le || nordictrack_treadmill_commercial_le || proform_treadmill_carbon_tls || proform_treadmill_sport_3_0 || proform_treadmill_995i || nordictrack_series_7 ||
8892 proform_carbon_tlx_treadmill || proform_carbon_tlx_v84_314_treadmill || proform_carbon_tl_PFTL59723_6 || proform_treadmill_cst_505_pftl59420_0
@@ -113,7 +117,7 @@ void proformtreadmill::forceSpeed(double speed) {
113117 } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_cadence_lt ||
114118 proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill || proform_8_5_treadmill || nordictrack_treadmill_exp_5i || proform_2000_treadmill ||
115119 proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_505_cst_80_44 || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
116- proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_treadmill_sport_70 || proform_treadmill_575i || proform_performance_300i || proform_performance_400i || proform_treadmill_c700 ||
120+ proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_carbon_tl_PFTL59720_hci || proform_treadmill_sport_70 || proform_treadmill_575i || proform_performance_300i || proform_performance_400i || proform_treadmill_c700 ||
117121 proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c || proform_treadmill_1500_pro || proform_trainer_8_0 || proform_trainer_8_0_pftl59721_int_0 || proform_treadmill_705_cst_V80_44 ||
118122 nordictrack_treadmill_ultra_le || nordictrack_treadmill_commercial_le || proform_treadmill_carbon_tls || proform_treadmill_sport_3_0 || proform_treadmill_995i || nordictrack_series_7 ||
119123 proform_carbon_tlx_treadmill || proform_carbon_tlx_v84_314_treadmill || proform_carbon_tl_PFTL59723_6 || proform_treadmill_cst_505_pftl59420_0 || proform_treadmill_105_cst) {
@@ -2726,6 +2730,71 @@ void proformtreadmill::update() {
27262730 if (counterPoll > 5) {
27272731 counterPoll = 0;
27282732 }
2733+ } else if (proform_carbon_tl_PFTL59720_hci) {
2734+ // Runtime poll sequence captured from the PFTL59720 I_TL HCI snoop.
2735+ uint8_t noOpData1[] = {0xfe, 0x02, 0x14, 0x03};
2736+ uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x10, 0x04, 0x10, 0x02, 0x00,
2737+ 0x0a, 0x1b, 0x94, 0x30, 0x00, 0x10, 0x40, 0x50, 0x00, 0x80};
2738+ uint8_t noOpData3[] = {0xff, 0x02, 0x18, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2739+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2740+ uint8_t noOpData4[] = {0xfe, 0x02, 0x19, 0x03};
2741+ uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
2742+ 0x0f, 0x80, 0x02, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2743+ uint8_t noOpData6[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x91, 0x00, 0x10, 0x90, 0x00,
2744+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2745+
2746+ switch (counterPoll) {
2747+ case 0:
2748+ writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
2749+ break;
2750+ case 1:
2751+ writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
2752+ break;
2753+ case 2:
2754+ writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"), false, true);
2755+ if (requestInclination != -100) {
2756+ if (requestInclination < 0)
2757+ requestInclination = 0;
2758+ if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
2759+ requestInclination <= 15) {
2760+ emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
2761+ forceIncline(requestInclination);
2762+ }
2763+ requestInclination = -100;
2764+ }
2765+ if (requestSpeed != -1) {
2766+ if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
2767+ emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
2768+ forceSpeed(requestSpeed);
2769+ }
2770+ requestSpeed = -1;
2771+ }
2772+ break;
2773+ case 3:
2774+ writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
2775+ break;
2776+ case 4:
2777+ writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
2778+ break;
2779+ case 5:
2780+ writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"), false, true);
2781+ if (requestStart != -1) {
2782+ emit debug(QStringLiteral("starting..."));
2783+ requestStart = -1;
2784+ emit tapeStarted();
2785+ }
2786+ if (requestStop != -1 || requestPause != -1) {
2787+ forceSpeed(0);
2788+ emit debug(QStringLiteral("stopping..."));
2789+ requestStop = -1;
2790+ requestPause = -1;
2791+ }
2792+ break;
2793+ }
2794+ counterPoll++;
2795+ if (counterPoll > 5) {
2796+ counterPoll = 0;
2797+ }
27292798 } else if (proform_carbon_tl_PFTL59720) {
27302799 uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
27312800 uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x80, 0x0a, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -3795,7 +3864,7 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
37953864 ((proform_trainer_8_0_pftl59721_int_0) &&
37963865 (newValue.at(4) != 0x02 || (newValue.at(5) != 0x2e && newValue.at(5) != 0x2f))) ||
37973866
3798- ((norditrack_s25i_treadmill || nordictrack_treadmill_ultra_le || nordictrack_treadmill_commercial_le || proform_treadmill_carbon_tls || proform_carbon_tlx_treadmill || proform_carbon_tlx_v84_314_treadmill || proform_carbon_tl_PFTL59723_6) &&
3867+ ((norditrack_s25i_treadmill || nordictrack_treadmill_ultra_le || nordictrack_treadmill_commercial_le || proform_treadmill_carbon_tls || proform_carbon_tlx_treadmill || proform_carbon_tlx_v84_314_treadmill || proform_carbon_tl_PFTL59723_6 || proform_carbon_tl_PFTL59720_hci ) &&
37993868 (newValue.at(4) != 0x02 || (newValue.at(5) != 0x2f))) ||
38003869
38013870 ((nordictrack_t65s_treadmill || nordictrack_t65s_treadmill_81_miles || proform_pro_1000_treadmill || nordictrack_t65s_83_treadmill || nordictrack_s30_treadmill ||
@@ -3813,7 +3882,7 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
38133882 ((uint8_t)newValue.at(18)) == 0xFF && ((uint8_t)newValue.at(19)) == 0xFF) ||
38143883
38153884 (((((uint8_t)newValue.at(18)) == 0xFF && ((uint8_t)newValue.at(19)) == 0xFF)) &&
3816- (proform_proshox2 || proform_595i_proshox2 || proform_treadmill_sport_3_0 || proform_carbon_tlx_treadmill || proform_carbon_tlx_v84_314_treadmill || proform_carbon_tl_PFTL59723_6))
3885+ (proform_proshox2 || proform_595i_proshox2 || proform_treadmill_sport_3_0 || proform_carbon_tlx_treadmill || proform_carbon_tlx_v84_314_treadmill || proform_carbon_tl_PFTL59723_6 || proform_carbon_tl_PFTL59720_hci ))
38173886 )
38183887 {
38193888 return;
@@ -3823,7 +3892,7 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
38233892 m_watts = (((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t)newValue.at(14)));
38243893
38253894 // for the proform_treadmill_se this field is the distance in meters ;)
3826- if (m_watts > 3000 && !proform_treadmill_se && !nordictrack_s20i_treadmill && !nordictrack_tseries5_treadmill && !proform_treadmill_sport_3_0 && !proform_carbon_tlx_treadmill && !proform_carbon_tlx_v84_314_treadmill && !proform_carbon_tl_PFTL59723_6) {
3895+ if (m_watts > 3000 && !proform_treadmill_se && !nordictrack_s20i_treadmill && !nordictrack_tseries5_treadmill && !proform_treadmill_sport_3_0 && !proform_carbon_tlx_treadmill && !proform_carbon_tlx_v84_314_treadmill && !proform_carbon_tl_PFTL59723_6 && !proform_carbon_tl_PFTL59720_hci ) {
38273896 m_watts = 0;
38283897 } else {
38293898 if (!proform_cadence_lt) {
@@ -3939,6 +4008,7 @@ void proformtreadmill::btinit() {
39394008 proform_595i_proshox2 = settings.value(QZSettings::proform_595i_proshox2, QZSettings::default_proform_595i_proshox2).toBool();
39404009 proform_treadmill_8_7 = settings.value(QZSettings::proform_treadmill_8_7, QZSettings::default_proform_treadmill_8_7).toBool();
39414010 proform_carbon_tl_PFTL59720 = settings.value(QZSettings::proform_carbon_tl_PFTL59720, QZSettings::default_proform_carbon_tl_PFTL59720).toBool();
4011+ proform_carbon_tl_PFTL59720_hci = settings.value(QZSettings::proform_carbon_tl_PFTL59720_hci, QZSettings::default_proform_carbon_tl_PFTL59720_hci).toBool();
39424012 proform_treadmill_sport_70 = settings.value(QZSettings::proform_treadmill_sport_70, QZSettings::default_proform_treadmill_sport_70).toBool();
39434013 proform_treadmill_575i = settings.value(QZSettings::proform_treadmill_575i, QZSettings::default_proform_treadmill_575i).toBool();
39444014 proform_performance_300i = settings.value(QZSettings::proform_performance_300i, QZSettings::default_proform_performance_300i).toBool();
@@ -4984,7 +5054,86 @@ void proformtreadmill::btinit() {
49845054 QThread::msleep(sleepms);
49855055 writeCharacteristic(noOpData12, sizeof(noOpData12), QStringLiteral("init"), false, false);
49865056 QThread::msleep(sleepms);
4987- } else if (proform_carbon_tl_PFTL59720) {
5057+ } else if (proform_carbon_tl_PFTL59720_hci) {
5058+ // PFTL59720 I_TL init sequence captured from the native HCI snoop.
5059+ // The 0x2c payload is session-specific; this preserves the captured handshake for comparison.
5060+ uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
5061+ uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5062+ uint8_t initData3[] = {0xfe, 0x02, 0x08, 0x02};
5063+ uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5064+ uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02};
5065+ uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5066+ uint8_t initData7[] = {0xfe, 0x02, 0x0b, 0x02};
5067+ uint8_t initData8[] = {0xff, 0x0b, 0x02, 0x04, 0x02, 0x07, 0x02, 0x07, 0x82, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5068+ uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02};
5069+ uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5070+ uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02};
5071+ uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5072+ uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04};
5073+ uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07, 0x01, 0xe2, 0x34, 0x84, 0x4d, 0x22, 0xe8, 0x8d, 0x03, 0xe9};
5074+ uint8_t initData15[] = {0x01, 0x12, 0xfc, 0x5c, 0xba, 0x16, 0x90, 0xf8, 0x46, 0xd2, 0x24, 0xb4, 0x02, 0x9e, 0x18, 0x60, 0xee, 0x6a, 0xec, 0x6c};
5075+ uint8_t initData16[] = {0xff, 0x08, 0xea, 0x66, 0x20, 0x98, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5076+ uint8_t initData17[] = {0xfe, 0x02, 0x19, 0x03};
5077+ uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5078+ uint8_t initData19[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5079+ uint8_t initData20[] = {0xfe, 0x02, 0x17, 0x03};
5080+ uint8_t initData21[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5081+ uint8_t initData22[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5082+ uint8_t initData23[] = {0xfe, 0x02, 0x19, 0x03};
5083+ uint8_t initData24[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
5084+ uint8_t initData25[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
5085+
5086+ writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
5087+ QThread::msleep(sleepms);
5088+ writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
5089+ QThread::msleep(sleepms);
5090+ writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
5091+ QThread::msleep(sleepms);
5092+ writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
5093+ QThread::msleep(sleepms);
5094+ writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
5095+ QThread::msleep(sleepms);
5096+ writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
5097+ QThread::msleep(sleepms);
5098+ writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
5099+ QThread::msleep(sleepms);
5100+ writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
5101+ QThread::msleep(sleepms);
5102+ writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
5103+ QThread::msleep(sleepms);
5104+ writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
5105+ QThread::msleep(sleepms);
5106+ writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
5107+ QThread::msleep(sleepms);
5108+ writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
5109+ QThread::msleep(sleepms);
5110+ writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
5111+ QThread::msleep(sleepms);
5112+ writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
5113+ QThread::msleep(sleepms);
5114+ writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
5115+ QThread::msleep(sleepms);
5116+ writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
5117+ QThread::msleep(sleepms);
5118+ writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
5119+ QThread::msleep(sleepms);
5120+ writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
5121+ QThread::msleep(sleepms);
5122+ writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
5123+ QThread::msleep(sleepms);
5124+ writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
5125+ QThread::msleep(sleepms);
5126+ writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
5127+ QThread::msleep(sleepms);
5128+ writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
5129+ QThread::msleep(sleepms);
5130+ writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
5131+ QThread::msleep(sleepms);
5132+ writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
5133+ QThread::msleep(sleepms);
5134+ writeCharacteristic(initData25, sizeof(initData25), QStringLiteral("init"), false, false);
5135+ QThread::msleep(sleepms);
5136+ } else if (proform_carbon_tl_PFTL59720) {
49885137 uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
49895138 uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
49905139 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
0 commit comments