Canyon ECLIPS: Lichtsteuerung fehlt in der App – Lichter per Web Bluetooth steuern
Nach der Nutzung der Canyon-App war die komplette ECLIPS-Steuerung verschwunden – kein Verbinden, kein Licht, kein Akkustand. Die Ursache liegt nicht am Rad oder an der Kopplung, sondern an fehlenden Daten vom Canyon-Server. Bis das behoben ist, lassen sich die Lichter über ein paar Zeilen Web Bluetooth direkt ansteuern.
$ git log --graph Der Weg zur Lösung
-
Problem
Steuerung weg
Nach der App-Nutzung fehlt der komplette ECLIPS-Bereich in der Canyon-App, ohne Fehlermeldung.
-
Irrweg
Erst an App und Kopplung gedacht
Neuinstallation, alle Berechtigungen, neu koppeln – half nichts. Die Lichter am Rad gehen ja, nur die App bietet nichts an.
-
Erkenntnis
Es liegt an den Server-Daten
Die App zeigt ECLIPS nur, wenn die Fahrraddaten die Fähigkeit eclips enthalten. Für das Rad kam capabilities leer zurück; der Funktionsschalter war an.
-
Versuch
Gegenprobe per Laufzeit-Analyse
Der App zur Laufzeit die Fähigkeit eclips untergeschoben → die Steuerung erschien sofort. Also kein App-Bug, sondern fehlende Server-Daten (Fall für den Support).
-
Erkenntnis
Die Lichter sind direkt erreichbar
Aus der App die BLE-Befehle extrahiert – Nordic UART, ein Byte je Befehl, keine Verschlüsselung.
-
Lösung
Web-Bluetooth-Seite
Eine kleine Seite verbindet sich direkt mit dem ECLIPS und schaltet Front/Rück und liest den Status. Am Rad bestätigt: Lichter reagieren korrekt.
An meinem Canyon-Rad sitzt die ECLIPS-Anlage (Lichter vorne/hinten mit Dynamo-gepuffertem Akku). Gesteuert wird sie sonst über die Canyon-App (Android, Version 8.5.0). Nach einem Update war in der App der komplette ECLIPS-Bereich weg: kein „Mit ECLIPS-System verbinden", keine Lichtschalter, kein Akkustand. Keine Fehlermeldung – die Funktion ist einfach nicht mehr da.
Das Problem
Die App zeigt mein Rad korrekt in der Garage an, aber die ganze ECLIPS-Steuerung fehlt. Neuinstallation, alle Berechtigungen (Bluetooth, Standort, Kamera) erteilt, neu gekoppelt – nichts half. Die Lichter selbst funktionieren am Rad, nur die App bietet keine Steuerung mehr an.
Die Ursache: fehlt beim Server, nicht am Rad
Die App blendet den ECLIPS-Bereich nur ein, wenn die Fahrraddaten vom Canyon-Server die Fähigkeit eclips enthalten. Genau die fehlte für mein Rad – das Feld capabilities kam leer zurück. Der interne Funktionsschalter für ECLIPS war dagegen aktiv, lag also nicht daran.
Bestätigt habe ich das mit Laufzeit-Instrumentierung (Frida) auf einem gerooteten Android-Telefon: Sobald man der App die Fähigkeit eclips unterschiebt, erscheint die komplette Steuerung sofort wieder. Es ist also kein Bug in der App, sondern ein fehlender Eintrag in den Server-Daten. Das kann nur der Hersteller-Support korrigieren – mit Rad- und ECLIPS-Seriennummer.
Die Lösung: direkt per Bluetooth
Das ECLIPS meldet sich per Bluetooth Low Energy als Canyon Power Supply und spricht den Nordic UART Service – unverschlüsselt, ein einzelnes Byte je Befehl. Die Befehle habe ich aus der App herausgelesen:
| Byte | Funktion |
|---|---|
0x03 | Frontlicht an |
0x04 | Frontlicht aus |
0x05 | Rücklicht an |
0x06 | Rücklicht aus |
0x0E | Status abfragen (Antwort als Text) |
Das reicht für eine kleine Steuerung im Browser. Web Bluetooth kann sich als Zentrale mit dem Gerät verbinden – es braucht nur Chrome oder Edge und eine HTTPS-Seite:
// Nordic UART Service des ECLIPS ("Canyon Power Supply")
const NUS = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; // zum Gerät schreiben
const TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; // Antworten vom Gerät
let rx;
async function verbinden() {
const dev = await navigator.bluetooth.requestDevice({
filters: [{ services: [NUS] }, { namePrefix: "Canyon" }],
});
const server = await dev.gatt.connect();
const svc = await server.getPrimaryService(NUS);
rx = await svc.getCharacteristic(RX);
const tx = await svc.getCharacteristic(TX);
tx.addEventListener("characteristicvaluechanged", (e) => {
console.log(new TextDecoder().decode(e.target.value));
});
await tx.startNotifications();
}
// ein Byte je Befehl: 0x03 Front an, 0x04 Front aus,
// 0x05 Rück an, 0x06 Rück aus, 0x0E Status
const senden = (byte) => rx.writeValue(new Uint8Array([byte]));
Nach verbinden() schaltet senden(0x03) das Frontlicht ein, senden(0x05) das Rücklicht. Auf 0x0E antwortet das Gerät mit einer Statuszeile im Klartext:
UID: cps_02 Chg: 0, 0 iBat: -10 mA VBat: 8280 mV SOC: 82%
USB: 0 FL: 0 RL: 0 Speed: 0 km/h FW: 0.1.0 Power State: 0
Daraus liest man alles Wichtige: SOC Akkustand in Prozent, VBat Spannung in mV, iBat Strom in mA (negativ = Entladen), FL/RL Licht vorne/hinten (0/1), Speed die Dynamo-Geschwindigkeit, Chg die Ladephase.
Warum das funktioniert
Die App macht beim Schalten nichts anderes: Sie schreibt dieses eine Byte auf die Schreib-Charakteristik des UART-Service. Es gibt keine Verschlüsselung und kein App-Token auf der Datenebene – die einzige Hürde ist die normale Bluetooth-Kopplung des Betriebssystems. Was die App vom Server braucht (die eclips-Fähigkeit), betrifft nur die Anzeige in der App, nicht die Kommunikation mit dem Gerät. Deshalb funktioniert der direkte Weg, auch wenn die App streikt.
Stolperfallen
- Kopplung: Beim ersten Verbinden fragt Android einen PIN – das sind die letzten 6 Ziffern der Seriennummer des ECLIPS. Danach merkt sich das System die Kopplung.
- Gerätename: In der Bluetooth-Auswahl heißt es Canyon Power Supply, nicht „ECLIPS".
- Keine Helligkeitsstufen: Jedes Licht ist nur an oder aus. Was der Lenkertaster umschaltet, ist Automatik (nach Akku/Dynamo) gegen manuell – keine Dimmstufen. Das sieht man auch an der Firmware (ein Nordic nRF52840 mit Zephyr): es gibt dort schlicht keine Helligkeits- oder Blinkmodi für die Lichter.
- Nach jedem Schalten lohnt ein
0x0E, um den echten Zustand gegenzulesen – so sieht man sofort, ob der Befehl angekommen ist.