(
Englisch version) Seit
meinem letzten Beitrag können wir bereits erfolgreich mit der Funksteckdose kommunizieren. Das Ein- und wieder Ausschalten des Stroms für den Rolladen ist also schonmal kein Problem. Der spannende Teil ist die Kommunikation mit dem Rolladen selbst. Bekannt ist nur, dass sie (wie bei der Steckdose) auf dem 433MHz-Band abläuft. Zum verwendeten Protokoll waren aber keine Infos aufzutreiben. Als erstes habe ich einfach mal die "ReceiveDemo" der rc-switch-Library compiliert, auf den Arduino übertragen und damit versucht, die Signale der Fernbedienung des Rolladens zu lesen. Wie zu erwarten war, hatte ich hier natürlich kein Glück. Die Demo kann nur die üblichen Prokolle der Funksteckdosen auslesen und konnte daher mit der Fernbedienung des Rolladens nichts anfangen.
Reverse Engineering
Um überhaupt einen groben Überblick über das verwendete Protokoll zu erhalten, müssten die Signale der Fernbedienung ausgelesen werden. Dies wäre am einfachsten zu erledigen mit einem Logic Analyzer. Leider habe ich solches Geräte nicht daheim herumfahren - das gibt das Budget nicht her ;)
Also ein bisschen Google bemüht und eine Möglichkeit gefunden, wie man den Arduino als einfachen Logic Analyzer benutzen kann. Wie genau das geht werde ich in einem seperaten Beitrag beschreiben.
Also habe ich einfach den Daten-Pin des Empfangsmoduls mit dem Logic-Analyzer-Arduino verbunden und konnte damit direkt das Signal der Fernbedienung auslesen.
Ein Druck auf den "Hoch"-Knopf lieferte folgendes Signal:
Runter ergab folgendes Signal:
Und Stopp:
Die Übertragung wird immer mit einer Art Sync-Bit gestartet. Dazu sendet die Fernbedienung erst 4500µs ein HIGH-Signal und dann für 1500µs ein LOW-Signal. Danach folgt die Datenübertragung. Das Muster ist ähnlich wie das Tri-State-System bei den Steckdosen - aber eben nur ähnlich. Neben den dort bekannten Zuständen 0, 1 und F ist hier noch ein vierter Zustand zu erkennen:
_ _ _ _
| | _ | | _
Es handelt sich also im Prinzip um ein "Quad-State-System". Dementsprechend habe ich den vierten Zustand "Q" genannt. Ein "kurzes" Signal hat die Dauer von 250µs, ein "langes" dauert dementsprechend 750µs.
Theoretisch könnte man das Signal auch rein binär auswerten und ein kurzes HIGH-Signal als "0" und ein langes HIGH-Signal als "1" deuten. Dies wäre jedoch (meiner Meinung nach) unübersichtlich, da man mit ewig langen Ketten aus 0 und 1 hantieren müsste.
Die Signale können nun also nach dem Quad-State-System ausgewertet werden. Es ergeben sich dabei folgende übertragene Code-Wörter:
Hoch = 0F0F0100QQ0F100F 0F0F
Runter = 0F0F0100QQ0F100F 0101
Stopp = 0F0F0100QQ0F100F FFFF
Es fällt sofort auf, dass die ersten 16 Bits aller Codewörter identisch sind, lediglich die letzten 4 Bits unterscheiden sich. Es liegt also die Vermutung nahe, dass es sich bei den ersten 16 Bits um die Adresse des Rolladens handelt und bei den restlichen Bits um den Befehl.
Fernbedienung auslesen
Um das Signal der Fernbedienung einfach auslesen zu können, habe ich zunächst ein Programm geschrieben, das auf Signale wartet und diese dann im Quad-State-Format ausgibt.
int logfile[40];
int i = 0;
float lastTime = 0;
boolean capturing = false;
boolean checking = false;
boolean dataIncoming = false;
void setup() {
Serial.begin(9600);
Serial.println("Bereit...");
pinMode(2, INPUT);
attachInterrupt(0, handleInterrupt, CHANGE);
}
void loop() {
}
Zuerst werden diverse Variablen deklariert - unter anderem ein "logfile", in dem später der ausgelesene Code gespeichert wird. Die setup()-Funktion ist sehr übersichtlich, hier wird nur die serielle Verbindung gestartet sowie der Interrupt auf Pin 2 aktiviert (dort hängt der Daten-Pin des Empfangsmoduls). Der Interrupt wird jedes Mal aktiviert, wenn sich das Signal am Pin ändert.
Noch übersichtlicher ist die loop()-Funktion - sie ist komplett leer ;)
Die eigentliche "Arbeit" erfolgt in der Interrupt-Funktion:
void handleInterrupt() {
if (!capturing) { //wenn keine Aufnahme läuft
if (!checking) { //wenn nicht gerade auf "Start-Signal" geprüft wird
if (digitalRead(2) == HIGH) { //wenn Wechsel von LOW nach (jetzt) HIGH
lastTime = micros();
checking = true;
}
}
else { //wenn gerade auf Start-Signal geprüft wird
if ((micros() - lastTime > 4000) && (digitalRead(2) == LOW)) { //wenn HIGH-Phase länger als 4ms war und wir jetzt LOW sind
//das war das Start-Signal
checking = false;
capturing = true;
lastTime = micros();
}
else {
//das war nicht das Start-Signal
checking = false;
}
}
}
else { //es läuft eine Aufnahme
if (!dataIncoming) { //bisher noch keine Nutzdaten empfangen
if ((micros() - lastTime > 1000) && digitalRead(2) == HIGH) { //das war die lange LOW-Phase vor Beginn der Übertragung
dataIncoming = true; //ab jetzt kommen Daten
lastTime = micros();
}
}
else { //jetzt wird es interessant, jetzt kommen die Daten
//wenn steigene Flanke (also jetzt HIGH)
if (digitalRead(2) == HIGH) {
//Beginn der HIGH-Phase merken
lastTime = micros();
}
//wenn fallende Flanke (also jetzt LOW)
else if (digitalRead(2) == LOW) {
//=> prüfe wie lange HIGH war
if (micros() - lastTime > 500) {
//long
logfile[i] = 1;
}
else {
//short
logfile[i] = 0;
}
if (i < 39) {
//solange noch nicht alle Bits empfangen wurden
i++;
}
else {
//wir sind fertig
noInterrupts(); //Interrupts aus damit Ausgabe nicht gestört wird
Serial.println("Empfangene Daten:");
//Ausgabe als "quad-bit"
for (i = 0; i <= 38; i = i + 2) {
if ((logfile[i] == 0) && (logfile[i+1] == 0))
Serial.print("0");
else if ((logfile[i] == 0) && (logfile[i+1] == 1))
Serial.print("F");
else if ((logfile[i] == 1) && (logfile[i+1] == 0))
Serial.print("Q");
else if ((logfile[i] == 1) && (logfile[i+1] == 1))
Serial.print("1");
}
Serial.println();
i = 0;
dataIncoming = false;
capturing = false;
interrupts(); //Interrupts wieder an
return; //und alles auf Anfang
}
}
}
}
}
Sie besteht aus mehreren if-else-Abfragen, die den Zustand der diversen Bool-Variablen überprüfen. Als erstes wird überprüft, ob gerade eine "Aufnahme" läuft (capturing). Falls dies nicht der Fall ist, wird untersucht, ob gerade auf das Start-Signal (Sync-Bit) geprüft wird (checking). Falls dies auch nicht der Fall ist (und gerade ein Wechsel von LOW nach HIGH statt gefunden hat) merkt sich der Arduino die aktuelle Zeit und setzt "checking" auf true. Beim nächsten Flankenwechsel läuft das Programm also in den else-Zweig (es wird auf Start-Signal geprüft). Ist der Wechsel auf HIGH länger als 4000µs her kann man davon ausgehen, dass man soeben die lange HIGH-Phase des Sync-Bits empfangen hat. Also merkt sich der Arduino die Zeit, setzt checking auf false und capturing auf true. Dauerte die HIGH-Phase nicht lange genug, wird checking wieder auf false gesetzt, sonst geschieht nichts.
Wurde checking auf true gesetzt, läuft das Programm beim nächsten Flankenwechsel in den entsprechenden else-Zweig. In diesem Zweig wird als erstes geprüft, ob bereits Nutzdaten empfangen werden oder ob das Ende des Sync-Bits erwartet wird (dataIncoming). Das Auslesen der Nutzdaten verläuft relativ simpel. Bei steigender Flanke wird die aktuelle Zeit gespeichert. Bei fallender Flanke wie geprüft, ob die HIGH-Phase länger als 500µs (und somit lang) oder kürzer (und somit kurz) war. Dementsprechend wird im logfile eine 1 oder eine 0 gespeichert - intern arbeitet das Programm als mit dem "doofen" unübersichtlichen Binär-System ;)
Das Ganze wird insgesamt 40mal durchgeführt, dann wurde das komplette Signal empfangen. Nun wird das empfangene Signal über die serielle Schnittstelle im Quad-State-Format ausgegeben und alle Bedingungen werden wieder auf ihren Ursprungszustand gesetzt. Dann wird das Programm mit dem return-Befehl neu gestartet.
Wurde alles passend verkabelt und das Programm auf den Arduino gespielt, sollte der Serielle Monitor nun bei jedem Druck auf eine Taste der Fernbedienung den passenden Code ausspucken.
Das klappt sogar auf Anhieb.
Dabei sind mir zwei Dinge aufgefallen, die bei der reinen Analyse mit dem Logic Analyzer nicht offensichtlich waren.
1. Die Fernbedieung sendet alle Signale jeweils 4mal - vermutlich um Übertragungsfehler auszuschließen
2. Der Signal zum hoch und runter fahren besteht jeweils aus zwei Befehlen: "hoch" sendet 4mal den schon bekannten Befehl "0F0F0100QQ0F100F 0F0F" gefolgt von 4mal "0F0F0100QQ0F100F 0F1Q" - "runter" ist entsprechend "0F0F0100QQ0F100F 0101" und "0F0F0100QQ0F100F 0110". Warum genau das so ist habe ich leider nicht heraus gefunden.
Im nächsten Beitrag werde ich beschreiben, wie man mit den so gewonnenen Erkenntnissen endlich den Rolladen steuern kann.