Tretmann X-Bike smart machen: Trittfrequenz mit ESP32 und Bluetooth an die Amazfit Bip 6 übertragen
DIY-Projekt · ESP32 · Bluetooth Low Energy · Arduino · Firmware 1.2.0
Mein Tretmann X-Bike besitzt einen Trainingscomputer, der unter anderem die Trittfrequenz anzeigt. Die Messwerte sollten zusätzlich auf meiner Amazfit Bip 6 erfasst und in der Zepp-App gespeichert werden. Statt einen weiteren Magneten und Sensor zu montieren, verwende ich den bereits eingebauten Drehimpulsgeber und einen ESP32 als Bluetooth-Trittfrequenzsensor.
Ergebnis: Die Uhr erkennt den ESP32 als externen Trittfrequenzsensor, zeichnet die Werte beim Training „Drinnen Radfahren“ auf und verbindet sich auch nach mehr als zehn Minuten Ruhe bei einem späteren Training erneut.
1. Vorhandenen Sensor untersuchen
Vom Pedalantrieb führt ein zweiadriges Kabel zu einem zweipoligen Klinkenstecker. Bei abgezogenem Stecker habe ich den Widerstand zwischen Spitze und Schaft gemessen. Einmal pro vollständiger Pedalumdrehung schließt der Kontakt mit etwa 2–3 Ω, ansonsten ist er hochohmig. Das spricht für einen einfachen Reed-Kontakt. Die Messung bestätigt außerdem: Ein Impuls entspricht einer Pedalumdrehung.
Die Widerstandsstufe des X-Bikes wird über diesen Kontakt nicht mit übertragen. Für eine spätere Leistungsschätzung müsste sie separat erfasst werden.
2. Benötigte Bauteile
| Bauteil | Aufgabe |
|---|---|
| Tretmann X-Bike | Heimtrainer mit integriertem Reed-Kontakt |
| ESP32 DevKit (ESP32-WROOM-32) | Impulsauswertung und BLE-GATT-Server |
| 2-polige Klinkenbuchse | Anschluss an das vorhandene Sensorkabel |
| LED und 330-Ω-Widerstand | Optische Impulskontrolle |
| USB-Netzteil 5 V | Stromversorgung des ESP32 |
| Amazfit Bip 6 | Empfang und Aufzeichnung der Trittfrequenz |
3. Verdrahtung
| Anschluss | Verbindung |
|---|---|
| Klinkenbuchse, äußerer Kontakt 1 | GPIO27 |
| Klinkenbuchse, äußerer Kontakt 2 | GND |
| LED-Anode (+) | GPIO26 über 330 Ω |
| LED-Kathode (−) | GND |
| ESP32-Stromversorgung | USB 5 V |
X-Bike-Sensor (Reed-Kontakt) Klinkenbuchse Kontakt 1 ─── GPIO27 (INPUT_PULLUP) Klinkenbuchse Kontakt 2 ─── GND ESP32 GPIO26 ─── 330 Ω ─── LED Anode (+) ESP32 GND ───────────── LED Kathode (−) ESP32 USB ─── 5-V-USB-Netzteil
Bei meiner Klinkenbuchse waren die beiden äußeren Lötpins die richtigen Sensorkontakte. Das ist keine allgemeingültige Pinbelegung: Bei anderen Buchsen müssen die Anschlüsse durchgemessen werden. Der ursprüngliche Trainingscomputer ist während dieses Aufbaus vom Sensor getrennt.
4. Bluetooth-Protokoll
Der ESP32 verwendet das standardisierte Bluetooth Cycling Speed and Cadence Profile (CSC). Er muss keinen proprietären Garmin-Sensor imitieren. Der ESP32 arbeitet als BLE-Peripheral mit GATT-Server, die Uhr als Empfänger. Alle 1.000 ms sendet der ESP32 die kumulierten Kurbelumdrehungen und den Zeitpunkt des letzten Impulses als Notification. Aus diesen Daten ermittelt die Uhr die Trittfrequenz.
| UUID | Bedeutung |
|---|---|
| 0x1816 | Cycling Speed and Cadence Service |
| 0x2A5B | CSC Measurement: Kurbelumdrehungen und Ereigniszeit |
| 0x2A5C | CSC Feature |
| 0x2A5D | Sensor Location |
| 0x180A | Device Information Service |
| 0x180F | Battery Service (simulierte 100 %) |
Zusätzlich meldet die Firmware den Hersteller „XBike DIY“, die Softwareversion „1.2.0“ und einen fest simulierten Akkustand von 100 %. Dieser Akkustand ist keine Messung, weil der ESP32 über USB betrieben wird.
5. Arduino IDE vorbereiten
- Ein passendes ESP32-Boardpaket in der Arduino IDE installieren und das ESP32-DevKit auswählen.
- Über den Bibliotheksverwalter NimBLE-Arduino 2.x installieren.
- Den folgenden Sketch einfügen, kompilieren und auf den ESP32 hochladen.
- Den seriellen Monitor auf 115200 Baud einstellen.
6. Vollständiger Arduino-Code – Firmware 1.2.0
Die Firmware zählt die Impulse über einen Interrupt, lässt die Kontroll-LED kurz aufleuchten, überträgt CSC-Daten und startet nach einer Bluetooth-Trennung das Advertising erneut. Zusätzlich prüft sie regelmäßig, ob sie ohne aktive Verbindung wieder auffindbar ist.
#include <Arduino.h>
#include <NimBLEDevice.h>
/* Tretmann X-Bike BLE Cadence Sensor – Firmware 1.2.0
* ESP32 DevKit / NimBLE-Arduino 2.x
* Reed-Kontakt: GPIO27 gegen GND
* Kontroll-LED: GPIO26 über 330 Ohm gegen GND
*/
#define REED_PIN 27
#define LED_PIN 26
#define DEVICE_NAME "XBike Cadence"
#define FIRMWARE_VERSION "1.2.0"
#define DEBOUNCE_US 80000
#define LED_DURATION_MS 100
#define BLE_INTERVAL_MS 1000
volatile uint16_t revolutions = 0;
volatile uint16_t eventTime = 0;
volatile uint32_t lastPulse = 0;
NimBLEServer* bleServer = nullptr;
NimBLECharacteristic* cscMeasurement = nullptr;
uint32_t lastLedPulse = 0;
uint32_t lastSend = 0;
uint32_t lastAdvertisingCheck = 0;
uint16_t lastDisplayedRevs = 0;
void IRAM_ATTR reedInterrupt() {
uint32_t now = micros();
if ((uint32_t)(now - lastPulse) < DEBOUNCE_US) return;
lastPulse = now;
revolutions++;
eventTime = (uint16_t)(((uint64_t)now * 1024ULL) / 1000000ULL);
}
class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer* server, NimBLEConnInfo& connInfo) override {
Serial.println("BLE: Verbunden");
}
void onDisconnect(NimBLEServer* server, NimBLEConnInfo& connInfo,
int reason) override {
Serial.printf("BLE: Getrennt (Grund: %d)\n", reason);
NimBLEDevice::startAdvertising();
Serial.println("BLE: Advertising neu gestartet");
}
};
void setup() {
Serial.begin(115200);
delay(300);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
pinMode(REED_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(REED_PIN), reedInterrupt, FALLING);
NimBLEDevice::init(DEVICE_NAME);
bleServer = NimBLEDevice::createServer();
bleServer->setCallbacks(new ServerCallbacks());
NimBLEService* cscService = bleServer->createService("1816");
cscMeasurement = cscService->createCharacteristic(
"2A5B", NIMBLE_PROPERTY::NOTIFY);
auto* feature = cscService->createCharacteristic(
"2A5C", NIMBLE_PROPERTY::READ);
uint8_t featureData[2] = {0x02, 0x00};
feature->setValue(featureData, 2);
auto* location = cscService->createCharacteristic(
"2A5D", NIMBLE_PROPERTY::READ);
uint8_t sensorLocation = 0x05; // Left Crank
location->setValue(&sensorLocation, 1);
cscService->start();
NimBLEService* deviceInfo = bleServer->createService("180A");
auto* manufacturer = deviceInfo->createCharacteristic(
"2A29", NIMBLE_PROPERTY::READ);
manufacturer->setValue("XBike DIY");
auto* model = deviceInfo->createCharacteristic(
"2A24", NIMBLE_PROPERTY::READ);
model->setValue("ESP32 Cadence");
auto* firmware = deviceInfo->createCharacteristic(
"2A26", NIMBLE_PROPERTY::READ);
firmware->setValue(FIRMWARE_VERSION);
auto* software = deviceInfo->createCharacteristic(
"2A28", NIMBLE_PROPERTY::READ);
software->setValue(FIRMWARE_VERSION);
deviceInfo->start();
NimBLEService* battery = bleServer->createService("180F");
auto* batteryLevel = battery->createCharacteristic(
"2A19", NIMBLE_PROPERTY::READ);
uint8_t level = 100; // simuliert, USB-Versorgung
batteryLevel->setValue(&level, 1);
battery->start();
NimBLEAdvertising* advertising = NimBLEDevice::getAdvertising();
advertising->addServiceUUID("1816");
advertising->setName(DEVICE_NAME);
NimBLEDevice::startAdvertising();
Serial.println("XBike Cadence gestartet – Firmware " FIRMWARE_VERSION);
}
void loop() {
uint16_t revs, timeValue;
noInterrupts();
revs = revolutions;
timeValue = eventTime;
interrupts();
if (revs != lastDisplayedRevs) {
lastDisplayedRevs = revs;
lastLedPulse = millis();
digitalWrite(LED_PIN, HIGH);
Serial.printf("Pedalumdrehungen: %u\n", revs);
}
if (millis() - lastLedPulse >= LED_DURATION_MS)
digitalWrite(LED_PIN, LOW);
if (millis() - lastSend >= BLE_INTERVAL_MS) {
lastSend = millis();
uint8_t data[5] = {
0x02,
(uint8_t)(revs & 0xFF), (uint8_t)(revs >> 8),
(uint8_t)(timeValue & 0xFF), (uint8_t)(timeValue >> 8)
};
cscMeasurement->setValue(data, 5);
if (bleServer->getConnectedCount() > 0) cscMeasurement->notify();
}
if (millis() - lastAdvertisingCheck >= 5000) {
lastAdvertisingCheck = millis();
if (bleServer->getConnectedCount() == 0) {
NimBLEAdvertising* advertising = NimBLEDevice::getAdvertising();
if (!advertising->isAdvertising()) {
Serial.println("BLE: Advertising inaktiv - Neustart");
NimBLEDevice::startAdvertising();
}
}
}
delay(5);
}
7. Amazfit Bip 6 verbinden und testen
- ESP32 über USB einschalten.
- Auf der Amazfit Bip 6 die Einstellungen für externe Geräte öffnen.
- Nach einem Trittfrequenzsensor suchen und XBike Cadence auswählen.
- Ein Training „Drinnen Radfahren“ starten und die Pedale bewegen.
- Nach dem Training die Trittfrequenzkurve in der Zepp-App kontrollieren.
Bei meinem Test wurden in 3:26 Minuten eine durchschnittliche Trittfrequenz von 61 U/min und ein Höchstwert von 69 U/min aufgezeichnet. Die Uhr zeigt außerdem Hersteller, Softwareversion und den simulierten Akkustand an.
8. Fehlerbehebung und Wiederverbindung
In einer früheren Firmware konnte die Uhr den Sensor nach dem Beenden eines Trainings zeitweise nicht erneut finden. Ein Aus- und Einschalten des ESP32 stellte die Erkennung wieder her. Version 1.2.0 ergänzt deshalb einen onDisconnect()-Callback und eine regelmäßige Advertising-Kontrolle. Bei einem späteren Test konnte die Uhr den Sensor nach über zehn Minuten Ruhe wieder verbinden, ohne dass der ESP32 neu gestartet werden musste.
Falls der Sensor nicht auftaucht: USB-Stromversorgung kurz trennen, den ESP32 erneut starten und die Suche wiederholen. Für eine weitergehende Diagnose im seriellen Monitor auf Meldungen wie „BLE: Verbunden“ und „BLE: Getrennt“ achten. Ein fehlender Suchtreffer kann auch bedeuten, dass der Sensor noch verbunden ist.
9. Fazit und mögliche Erweiterungen
Mit dem vorhandenen Reed-Kontakt, einem ESP32 und wenigen Bauteilen lässt sich das Tretmann X-Bike um eine standardisierte Bluetooth-Trittfrequenzmessung erweitern. Die Übertragung und Aufzeichnung mit der Amazfit Bip 6 wurden praktisch bestätigt.
Als spätere Erweiterung ist die Erfassung der festen Widerstandsstufen denkbar. Daraus könnte zusammen mit der Trittfrequenz eine geschätzte Leistung in Watt berechnet werden. Ohne Kalibrierung wäre dies allerdings kein verlässlicher Leistungsmesswert.
Den Einbau in ein entsprechendes Gehäuse überlasse ich jetzt jedem selbst. Alternativ tut es auch etwas Klebeband ;-)
Hinweis: Der Aufbau wurde mit dem beschriebenen Tretmann X-Bike und einer Amazfit Bip 6 getestet. Die Funktion anderer Geräte, Sensorbeschaltungen und Firmwarestände ist nicht automatisch gewährleistet.

Keine Kommentare:
Kommentar veröffentlichen