diff --git a/.gitignore b/.gitignore
index 34376472..aee06ddc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ C/Users/
dependencies.lock
.vscode/settings.json
*.map
+graphify-out/
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 475682b2..b42585b9 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -122,4 +122,8 @@
"idf.pythonInstallPath": "C:\\Users\\anthony\\.espressif\\tools\\idf-python\\3.11.2\\python.exe",
"idf.flashType": "UART",
"liveServer.settings.port": 5501,
+ "clangd.arguments": [
+ "--compile-commands-dir=d:/git/SmartSpin2k/.cache/clangd",
+ "--query-driver=C:/Users/emadm/.platformio/packages/toolchain-*/bin/*,C:/Users/emadm/.platformio/packages/tool-*/bin/*"
+ ],
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 327aa862..628fbbae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+- Support for Spinning Bike Chrono
+
+### Changed
+
+### Hardware
+
+
+## [26.5.27]
+
### Added
### Changed
diff --git a/data/bluetoothscanner.html b/data/bluetoothscanner.html
index 4b79e67e..4ea0e487 100644
--- a/data/bluetoothscanner.html
+++ b/data/bluetoothscanner.html
@@ -113,7 +113,7 @@
Device Selection
let option = document.createElement('option');
option.text = device.name || device.address;
- if (['0x1818', '0x1826', '0x1816', '6e400001-b5a3-f393-e0a9-e50e24dcca9e', '0bf669f0-45f2-11e7-9598-0800200c9a66'].includes(device.UUID)) {
+ if (['0x1818', '0x1826', '0x1816', '6e400001-b5a3-f393-e0a9-e50e24dcca9e', '0bf669f0-45f2-11e7-9598-0800200c9a66', 'a026ee07-0a7d-4ab3-97fa-f1500f9feb8b'].includes(device.UUID)) {
PMDropdown.add(option.cloneNode(true));
}
if (device.UUID === '0x180d') {
diff --git a/dependencies.lock b/dependencies.lock
index f84838b8..6f1aafb5 100644
--- a/dependencies.lock
+++ b/dependencies.lock
@@ -96,6 +96,6 @@ direct_dependencies:
- espressif/network_provisioning
- idf
- joltwallet/littlefs
-manifest_hash: df9baa925ebc2a028ee0e349be53f2169bb7ac38b2cb9c63b3ebf69123c43ef8
+manifest_hash: e7ce7a886dfdb3cc5cd59acbdb4ff333c0a91c6e4de55b9fd6323d866c7cc691
target: esp32
version: 2.0.0
diff --git a/include/BLE_Common.h b/include/BLE_Common.h
index 0a881942..7edfed89 100644
--- a/include/BLE_Common.h
+++ b/include/BLE_Common.h
@@ -44,6 +44,7 @@ const std::vector SUPPORTED_SERVICES = {{CYCLINGPOWERSERVICE_UUI
{HEARTSERVICE_UUID, HEARTCHARACTERISTIC_UUID, "Heart Rate Service"},
{ECHELON_DEVICE_UUID, ECHELON_SERVICE_UUID, "Echelon Device"}, // Two lines for Echelon
{ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, // Because one is for search, the other for data
+ {CHRONO_SERVICE_UUID, CHRONO_DATA_UUID, "Spinner Chrono"},
{FITNESSMACHINESERVICE_UUID, FITNESSMACHINEINDOORBIKEDATA_UUID, "Fitness Machine Service"},
{HID_SERVICE_UUID, HID_REPORT_DATA_UUID, "HID Service"},
{FLYWHEEL_UART_SERVICE_UUID, FLYWHEEL_UART_TX_UUID, "Flywheel UART Service"}};
diff --git a/lib/SS2K/include/Constants.h b/lib/SS2K/include/Constants.h
index 7a308ff9..314b7c43 100644
--- a/lib/SS2K/include/Constants.h
+++ b/lib/SS2K/include/Constants.h
@@ -76,6 +76,11 @@
#define ECHELON_WRITE_UUID NimBLEUUID("0bf669f2-45f2-11e7-9598-0800200c9a66")
#define ECHELON_DATA_UUID NimBLEUUID("0bf669f4-45f2-11e7-9598-0800200c9a66")
+// Spinning / Precor Spinner Chrono Power (proprietary, no standard CPS/CSC)
+#define CHRONO_SERVICE_UUID NimBLEUUID("a026ee07-0a7d-4ab3-97fa-f1500f9feb8b")
+#define CHRONO_DATA_UUID NimBLEUUID("a026e01d-0a7d-4ab3-97fa-f1500f9feb8b")
+#define CHRONO_BLE_NAME "CHRONO" // device advertises "CHRONO "
+
// Dummy UUID for Peloton Serial Data Interface
#define PELOTON_DATA_UUID NimBLEUUID("00000000-0000-0000-0000-000000000321")
#define PELOTON_ADDRESS NimBLEAddress("00:00:00:00:00:00", 0)
diff --git a/lib/SS2K/include/sensors/ChronoData.h b/lib/SS2K/include/sensors/ChronoData.h
new file mode 100644
index 00000000..933ba53e
--- /dev/null
+++ b/lib/SS2K/include/sensors/ChronoData.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 Anthony Doud & Joel Baranick
+ * All rights reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+
+#pragma once
+
+#include "SensorData.h"
+
+class ChronoData : public SensorData {
+ public:
+ ChronoData() : SensorData("CHRO") {}
+
+ bool hasHeartRate();
+ bool hasCadence();
+ bool hasPower();
+ bool hasSpeed();
+ bool hasResistance();
+ int getHeartRate();
+ float getCadence();
+ int getPower();
+ float getSpeed();
+ int getResistance();
+ void decode(uint8_t *data, size_t length);
+
+ private:
+ bool hasData = false;
+ float cadence = nanf("");
+ int power = INT_MIN;
+};
diff --git a/lib/SS2K/src/sensors/ChronoData.cpp b/lib/SS2K/src/sensors/ChronoData.cpp
new file mode 100644
index 00000000..0ccbf10f
--- /dev/null
+++ b/lib/SS2K/src/sensors/ChronoData.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 Anthony Doud & Joel Baranick
+ * All rights reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ */
+
+#include "endian.h"
+#include "sensors/ChronoData.h"
+
+bool ChronoData::hasHeartRate() { return false; }
+
+bool ChronoData::hasCadence() { return this->hasData; }
+
+bool ChronoData::hasPower() { return this->hasData; }
+
+bool ChronoData::hasSpeed() { return false; }
+
+bool ChronoData::hasResistance() { return false; }
+
+int ChronoData::getHeartRate() { return INT_MIN; }
+
+float ChronoData::getCadence() { return this->cadence; }
+
+int ChronoData::getPower() { return this->power; }
+
+float ChronoData::getSpeed() { return nanf(""); }
+
+int ChronoData::getResistance() { return INT_MIN; }
+
+void ChronoData::decode(uint8_t *data, size_t length) {
+ // Validate the fixed 19-byte telemetry packet with its magic header 0x53 0x59 0x16.
+ if (length < 19 || data[0] != 0x53 || data[1] != 0x59 || data[2] != 0x16) {
+ hasData = false;
+ cadence = nanf("");
+ power = INT_MIN;
+ return;
+ }
+ cadence = get_le16(&data[8]) / 10.0f; // bytes 8-9, uint16 little-endian, ÷10 RPM
+ power = get_le16(&data[17]); // bytes 17-18, uint16 little-endian, watts
+ hasData = true;
+}
diff --git a/lib/SS2K/src/sensors/SensorDataFactory.cpp b/lib/SS2K/src/sensors/SensorDataFactory.cpp
index 36b47fe9..947d1b54 100644
--- a/lib/SS2K/src/sensors/SensorDataFactory.cpp
+++ b/lib/SS2K/src/sensors/SensorDataFactory.cpp
@@ -15,6 +15,7 @@
#include "sensors/EchelonData.h"
#include "sensors/PelotonData.h"
#include "sensors/CscSensorData.h"
+#include "sensors/ChronoData.h"
std::shared_ptr SensorDataFactory::getSensorData(const NimBLEUUID characteristicUUID, std::string& uniqueName, uint8_t *data, size_t length) {
for (auto &it : SensorDataFactory::knownDevices) {
@@ -34,6 +35,8 @@ std::shared_ptr SensorDataFactory::getSensorData(const NimBLEUUID ch
sensorData = std::shared_ptr(new FlywheelData());
} else if (characteristicUUID == ECHELON_DATA_UUID) {
sensorData = std::shared_ptr(new EchelonData());
+ } else if (characteristicUUID == CHRONO_DATA_UUID) {
+ sensorData = std::shared_ptr(new ChronoData());
} else if (characteristicUUID == PELOTON_DATA_UUID) {
sensorData = std::shared_ptr(new PelotonData());
} else if (characteristicUUID == CSCMEASUREMENT_UUID) {
diff --git a/src/BLE_Client.cpp b/src/BLE_Client.cpp
index cd9b8f14..042c8e73 100644
--- a/src/BLE_Client.cpp
+++ b/src/BLE_Client.cpp
@@ -344,7 +344,8 @@ void MyClientCallback::onDisconnect(NimBLEClient* pClient, int reason) {
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Detected %s Disconnect", spinBLEClient.myBLEDevices[i].serviceUUID.toString().c_str());
if ((spinBLEClient.myBLEDevices[i].charUUID == CYCLINGPOWERMEASUREMENT_UUID) || (spinBLEClient.myBLEDevices[i].charUUID == FITNESSMACHINEINDOORBIKEDATA_UUID) ||
(spinBLEClient.myBLEDevices[i].charUUID == FLYWHEEL_UART_RX_UUID) || (spinBLEClient.myBLEDevices[i].charUUID == ECHELON_SERVICE_UUID) ||
- (spinBLEClient.myBLEDevices[i].charUUID == CYCLINGPOWERSERVICE_UUID) || (spinBLEClient.myBLEDevices[i].charUUID == CSCSERVICE_UUID)) {
+ (spinBLEClient.myBLEDevices[i].charUUID == CHRONO_DATA_UUID) || (spinBLEClient.myBLEDevices[i].charUUID == CYCLINGPOWERSERVICE_UUID) ||
+ (spinBLEClient.myBLEDevices[i].charUUID == CSCSERVICE_UUID)) {
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Deregistered PM on Disconnect");
}
if ((spinBLEClient.myBLEDevices[i].charUUID == HEARTCHARACTERISTIC_UUID)) {
@@ -1094,7 +1095,7 @@ void SpinBLEAdvertisedDevice::set(const NimBLEAdvertisedDevice* device, int id,
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered CSC on Connect");
} else if (serviceUUID == CYCLINGPOWERSERVICE_UUID || serviceUUID == FITNESSMACHINESERVICE_UUID ||
(serviceUUID == FLYWHEEL_UART_SERVICE_UUID && this->uniqueName.find("Flywheel") != std::string::npos) || serviceUUID == ECHELON_DEVICE_UUID ||
- serviceUUID == PELOTON_DATA_UUID) {
+ serviceUUID == CHRONO_SERVICE_UUID || serviceUUID == PELOTON_DATA_UUID) {
this->isPM = true;
spinBLEClient.connectedPM = true;
SS2K_LOG(BLE_CLIENT_LOG_TAG, "Registered PM on Connect");