433MHz Funksignale entschlüsseln und anwenden - Teil 1
Motivation
Häufig wird die Frage gestellt, wie sich diverse Sender (oft die des 433MHz Bandes – aber das spielt eigentlich keine Rolle) in die Heimautomation einbinden lassen. Solche Sender findet man in Rolladensteuerungen, Funksteckdosen, Garagentorantrieben, Autoschlüsseln, Funkthermometern Tür-Gongs oder ähnlichem.
Bei der Verwendung in einer Automation gilt es dabei grundsätzlich zu unterscheiden, ob man innerhalb seiner Automation lediglich den Sender nachbilden will oder Antworten eines Gerätes verstehen und entsprechend auswerten will. Bei Funksteckdosen, Tür-Gongs oder Rolladensteuerungen wird häufig nur von der Automation in Richtung des Aktors gesendet (Gruppe 1). Gleiches gilt auch für ältere Funk-Autoschlüssel. Beim Funkthermometer ist die Richtung definitiv umgekehrt, da man ja den Wert der Temperatur irgendwie auswerten möchte (Gruppe 2).
Will man Gerätefunktionalität nun verwenden, so sieht man leicht, daß Geräte der Gruppe 2 (Sender, deren Signale man empfangen möchte) mehr als nur das einfache Verständnis eines Sendeprotokolls benötigen. In diesem Artikel beschäftigen wir uns jedoch mit Geräten der Gruppe 1. Geräte der Gruppe 2 werden im Teil 2 des Tutorials behandelt.
Modulation, Signalaufnahme und -wiedergabe (Capture and Replay)
Bleiben wir also zunächst bei Geräten der Gruppe 1 – also reinen Sendern, die von Automationsverfahren benutzt werden sollen. Nachdem das Thema der Funksteckdosen schon hinreichend erschöpft ist, wählen wir der Abwechslung wegen einen Tür-Gong wie er vom Discounter Ald* unter der Bezeichnung MD17177 vertrieben wird.
Daß Gerät verwendet (wie z.B. auch Funksteckdosen) das sehr einfache OOK-Modulationsverfahren (On-Off-Keying). Dabei wird die Trägerfrequenz des Senders einfach ein- bzw. ausgeschaltet. Somit ergeben sich Impulse deren Länge der der zu übertragenden digitalen Information entspricht. Aus Gründen der Störsicherheit verwendet man häufig Manchestercodes o.ä. womit ein simpler Rückschluß "Sender-An"=1, "Sender-Aus"=0 zwar nicht richtig ist, jedoch für die weitere Erklärung zunächst nicht von Relevanz ist. An dieser Stelle genügt es zunächst zu erkennen, daß verschiedene Sende- und Pausenzeiten jeweils eine unterschiedliche Information tragen und deren Abfolge einen gültigen Befehl (Datagramm) darstellt.
Zeichnet man nun eine Impulsfolge eines Senders, z.B. die unseres Tür-Gongs auf, und spielt diese Sequenz unter Einhaltung der genauen Zeiten wieder ab, so wird der Gong erklingen – so der Hersteller nicht weitere Maßnahmen ergriff, um dies zu unterbinden.
Meßmittel und Messdurchführung
Bild 1 zeigt einen einfachen Sender und Empfänger für das 433MHz Band.
Bild 1: Sender und Empfänger auf dem Breadboard
Aufgenommen werden die Codes des Gong-Tasters mittels des Programmes Transceiver, welches Bestandteil der Demoprogramme der Hubo Library ist (im Code wird auch die Pinbelegung erläutert).
Die in der Datei "bat_1.txt" (der Name ist frei wählbar) gespeicherten Daten sollten die Codes des Tür-Gongs enthalten. Spielen wir die Datei nun über den Sender ab, dann sollte der Gong ertönen.
Der erste Teil wäre geschafft. Wir besitzen gültige Codes in einer Datei, die wir in Form eines Binärstroms senden könn(t)en. Allerdings kann eine solche Sequenz aufgrund von mehrfachen Wiederholungen sehr lang ausfallen (im vorliegenden Fall etwa 16kB), was relativ unhandlich ist, wollte man z.B. eine Fernbedienung mit zig Tasten abbilden. Für jede dieser Tasten fiele eine ebensolche Datei an. Daher wollen wir im folgenden Schritt die Datagramme im Rohdatenstrom erkennen und extrahieren, um so eine erhebliche Datenreduktion zu erreichen.
Signale erkennen
Die eben erstellte Datei laden wir nun in das Tabellenkalkulationsprogramm unseres Vertrauens und erhalten eine Liste von Zeiten und den dazugehörigen Signalwerten ("0" oder "1"). Bild 2 zeigt den Anfang einer Liste von etwa 2500 Zeilen.
Bild 2: Anfang der Liste aufgenommener Zeichen.
Bereits der erste flüchtige Blick läßt erkennen, daß die Zeit 1118 (alle Zeiten sind in µs gemessen) mehrfach auftaucht bzw. daß es auch sehr ähnliche Zeiten (z.B. 1116) gibt. Ähnliches gilt für die Zeiten 341, 340, 339 oder auch 1062, 1064... Auch sind die danebenstehenden Bitwerte immer dieselben. Bevor wir uns jedoch dieser Werte genauer widmen, zunächst ein Wort zur Interpretation der Zeit-Bit Tupel. Zu einem Zeitwert (Spalte A) gehört das Signal der darüberliegenden Zeile aus der Spalte B.
Beispiel:
Die "0" aus Zelle B0 stand für 1118µs (Zelle A2) an. Dann wechselte das Bit zu "1" (Zelle B1) für die Dauer von 341µs (Zelle A3). Nun folgte wieder eine "0" für 389µs, gefolgt von einer "1" für 1064µs usw..
Signalwiederholungen erkennen
Es stellt sich nun die Frage, welche Zeiten eigentlich gültige Zeiten darstellen und bei welchen es sich um Meßfehler oder Störungen handelt. Dazu erstellen wir eine Art Histogramm aus den gemessenen Zeiten.
Im Sinne einer Fehlervermeidung zum Start und am Ende der Messung werfen wir einige Meßwerte am Anfang und Ende der Datei und sortieren die Zeiten dann.
Bilder 3 und 4 zeigen die Häufigkeiten des Auftretens der einzelnen Zeiten.
Bild 3: Alle Zeiten im Überblick.
Bild 4: Zeiten zwischen 0µ und 1200µs gezoomt.
Betrachten wir die Häufigkeit der vorkommenden Zeiten, so geben sich kaum gleiche Zeiten unterhalb von 300µs. D.h. wir können diese Zeiten als Meßfehler bzw. Störungen betrachten. Etwa bei 340µs, 400µs, 1060µs und 1120µs sehen wir hingegen relativ viele Werte dieser Größenordnung. Ähnliches gilt für den Wert um 5830µs.
Es liegt also nahe, daß diese (Zeit-) Werte codierte Information des Signals beinhalten. Um diese Vermutung zu erhärten, zählen wir die Vorkommen der einzelnen Signal in unserer sortierten Liste. Tabelle 1 zeigt die Anzahl des Auftretens der Signale in bestimmten Zeitbereichen.
Tabelle 1: Anzahl des Auftretens von Signalen gewisser Pulsbreite.
Leicht erkennt man, daß 5 Signale (die eingefärbten) relativ oft auftreten und das mit einer niedrigen Schwankung, d.h. die Differenz aus t_max - t_min fällt mit <14µs relativ niedrig aus. Offenbar handelt es sich bei diesen Zeiten also um tatsächlich in der Codierung verwendete Pulsbreiten. Der Medianwert der Zeit liefert später ein gutes Maß für gültige Pulsbreiten, wenn wir unser Signal senden wollen.
Fassen wir zunächst zusammen:
- Das Funksignal wird aus 5 (eingefärbten) diskreten Pulsbreiten zusammengesetzt.
- Der Jitter einer Pulsbreite beträgt <14µs (diese Information liefert eine Aussage über die Güte des Senders).
- Ein Signal (das gelbe) sticht aufgrund seiner Länge (5834µs) und des relativ geringen Auftretens hervor (67 mal).
- Die anderen (nicht eingefärbten) Zeiten stellen offenbar Störungen im Signal dar und werden nicht berücksichtigt.
Im Sinne einer Erhöhung der Datensicherheit wiederholen einfache Sender Ihr Datagramm, was es dem Empfänger erlaubt, erst nach mehrfachem korrektem Empfang desselben Datagramms seine Schaltaktion durchzuführen. Eingeleitet oder beendet werden solche Sequenzen häufig durch ein relativ langes Break-Signal. Offenbar stellt unser gelbes Signal eben genau jenes Break-Signal dar.
Wollen wir nun ein Datagramm aus dem Datenstrom extrahieren, so suchen wir genau diejenigen Daten, welche sich zwischen solchen Break-Signalen befinden. Aus dem Rohdatenstrom kopieren wir uns also alle Werte, welche zwischen den Break-Signalen von 5834µs liegen. Tabelle 2 zeigt das Resultat der Extraktion der sich somit ergebenden Datagramme 2 bis 6.
Tabelle 2: Aus dem Rohdatenstrom extrahierte Datagramme 2 bis 6
Die Spalte der Abweichungen der Pulsbreiten der einzelnen "Bits" liefert uns ein Indiz darüber, ob wir gültige Datagramme zum Vergleich ausgewählt haben oder sich ein Datagramm mit Störung dazwischen befindet. Nachdem keine der Differenzen den Wert von 5µs überschreitet (was also noch weit unterhalb der o.g. 14µs liegt), handelt es sich offenbar um gültige Datagramme. Ein weiteres Indiz ist, daß die Abfolge der Bits streng alternierend ist (also immer Wechsel zwischen "0" und "1" zu verzeichnen sind sowie die Längen der Datagramme gleich ausfallen. Letzteres ist protokollabhängig und muß nicht unbedingt auf einen Fehler hindeuten, vereinfacht aber die Beurteilung eines Datagramms erheblich.
In der Spalte "Bitsequenz" erkennt man bereits die Zuordnung der "logischen" Pulsbreiten, aus denen sich das Datagramm zusammensetzt.
Das Datagramm, d.h. die Abfolge von "0"en und "1"en welche mit den entsprechenden Pulsbreiten zu senden sind, läßt sich nun leicht ablesen und ist in Tabelle 3 wiedergegeben.
Tabelle_3.jpg
Tabelle 3: Pulsbreiten (Buchstaben) welche zur Wortbildung (Datagramm) verwendet werden.
Dem aufmerksamen Leser ist nicht entgangen, daß die Zuordnung der "1"en und "0"en zu den einzelnen Buchstaben A...E invertiert gegenüber Tabelle 2 dargestellt ist. Dies ist dem bereits weiter oben genannten Grund der Interpretation der Zuordnung des Wertes einer Zeit aus der jeweils darüberliegenden Zeile zuzuschreiben. A entspricht also einer "1" und nicht einer "0", B einer "0" und nicht einer "1" usw.
Bestimmung des Datagramms
Das Datagramm zum Senden eines Gong-Signales läßt sich nun unmittelbar aus der Tabelle 2 ablesen und lautet: "[font="Liberation Sans, serif"]CBCDADABCDABCBCDADADABCDABCBCBCBCBCE[/font]".
Die Pulsbreiten und Signalwerte definiert Tabelle 3.
Die Signaldauer eines Datagramms ermittelt sich über der Summe der einzelnen Pulsbreiten und Vorkommen der Buchstaben inkl. des Break-Signales (für das vorliegende Datagramm etwa 31,6ms). Nachdem wir 67 Break-Signale für einen Tastendruck erkannt haben, schickt der Sender also offenbar 67 Wiederholungen desselben Datagramms, womit sich eine gesamte Sendezeit von 67 * 31,6ms, also etwas mehr als 2 Sekunden ergibt. In der Anzahl der Wiederholungen liegt eine gewisse Sicherheit, welche der Hersteller seinem Sender mitgegeben hat. Dieser Punkt kann experimentell ermittelt werden.
Der Code zum Läuten des Türgongs MD17177 ist nachfolgend abgebildet und Teil der Hubo-Demoprogramme.
#include <stdio.h>
#include <string>
#include <unistd.h>
#include "../hubolib.h"
using namespace HuboLib;
using namespace BCM2835;
/*
Compile and link:
g++ MD17177.cpp -L../ -lhubo -lpthread -lrt -o MD17177.out
Run:
sudo ./MD17177.out
Purpose:
The demo runs the Medion MD17177 door bell.
*/
const char* g_pCode = "CBCDADABCDABCBCDADADABCDABCBCBCBCBCE";
class CCode
{
public :
CCode (unsigned short time, unsigned char signal)
{
m_time = time;
m_signal = signal;
}
public :
unsigned short m_time;
unsigned char m_signal;
};
CCode A( 340, 1);
CCode B( 395, 0);
CCode C(1060, 1);
CCode D(1120, 0);
CCode E(5830, 0);
void SendDatagram (unsigned short pin);
void BoostThreadPriority();
int main(int argc, char *argv[])
{
if (!BCM2835::IsGPIOInitialized ())
{
printf ("GPIO not properly initialized. Are you running the program as sudoer?\n");
return -1;
}
// Define the pin we will use for bit banging as output.
unsigned short transmit_pin = 17; // BCM GPIO=17 = GPIO0 = Raspi pinheader 11
FunctionSelectPin(transmit_pin, Output);
// Lift the priority of the thread in order to allow for a precise time keeping during bit banging.
BoostThreadPriority();
// The minimum of datagrams to repeat is around 10.
// Let's send it 60 times to be on the save side.
// The transmitter of the bell sends it around 67 times.
for (int i=0; i<60; i++)
{
SendDatagram(transmit_pin);
}
return 0;
}
void SendDatagram (unsigned short pin)
{
const char* pBuffer = g_pCode;
while (*pBuffer)
{
switch (*pBuffer)
{
case 'A' : WritePin (pin, A.m_signal);
Delay_MicroSeconds((unsigned long) A.m_time);
break;
case 'B' : WritePin (pin, B.m_signal);
Delay_MicroSeconds((unsigned long) B.m_time);
break;
case 'C' : WritePin (pin, C.m_signal);
Delay_MicroSeconds((unsigned long) C.m_time);
break;
case 'D' : WritePin (pin, D.m_signal);
Delay_MicroSeconds((unsigned long) D.m_time);
break;
case 'E' : WritePin (pin, E.m_signal);
Delay_MicroSeconds((unsigned long) E.m_time);
break;
default:
printf ("Error - unknown code\n");
return;
}
pBuffer++;
}
}
#include <pthread.h>
#include <assert.h>
void BoostThreadPriority()
{
// Scheduling params.
sched_param param;
int policy;
int ret;
pthread_t threadHandle = pthread_self();
// Get current scheduling parameters of thread.
ret = pthread_getschedparam (threadHandle, &policy, ¶m);
assert (ret == 0);
// Set scheduling parameters of thread to real time values (FIFO scheduling type and max prio).
policy = SCHED_FIFO; // SCHED_RR;
param.sched_priority = sched_get_priority_max(policy); // New max priority for new scheduling concept.
ret = pthread_setschedparam(threadHandle, policy, ¶m);
assert (ret == 0);
}
Display More
Hintergrundinformation zu den Meßmitteln
Nachdem es verschiedene Sender und Empfänger im Markt gibt, stellt sich die Frage, welche denn nun am besten geeignet wären. Betrachten wir zunächst den Vorgang des Aufnehmens der Signale. Nachdem die Sender des Herstellers immer ein wenig im Frequenzband abweichen, empfiehlt sich ggf. ein etwas "breitbandigerer" Empfänger zur Analyse der Signale. Insofern könnte ein Pendelaudionemfänger für derartige Untersuchungen besser geeignet sein, als ein Superhetempfänger. Leider empfängt eine solcher breitbandiger Empfänger jedoch auch mehr Störsignale, was der Analyse wiederum abträglich wäre. Abhilfe schafft hier das Abklemmen der Antenne. Für die Dauer der Analyse sollte es machbar sein, den Sender nahe dem Empfänger zu positionieren, um Störungen aus der Nachbarschaft weitgehend ausschließen zu können. Für Sender und Empfänger, die eine gute Frequenzabstimmung aufweisen, ist dem Superhetempfänger aufgrund dessen höherer Trennschärfe jedoch generell der Vorzug zu geben.
Ein Betriebssystem ist eine feine Sache, für derartige Aufgaben jedoch eher abträglich. Das liegt einfach am Umstand, daß Scheduler moderner Betriebssysteme dem "Programm" willkürlich die CPU entziehen können, was zu Abtastfehlern bei der Aufnahme oder dem Abspielen der Datagramme oder der aufgenommenen Sequenzen führt. Hintergründe und Messungen dazu finden sich z.B. >>> hier <<<.
Sofern möglich, bieten sich für solche Aufgaben Mikrocontroller ohne Betriebssystem an. Für einfache Aufgaben wie hier dargestellt, reicht es i.d.R. jedoch, die CPU-Priorität kurzfristig anzuheben, um auch auf
Kleincomputern mit preemptiven Betriebssystem gute Ergebnisse zu erzielen.
Grenzen des "Capture and Replay" Verfahrens
Wie man sieht, ist es sehr einfach, Signale aufzunehmen, und in der Folge wieder abzuspielen. Das mag im Falle einer Steuerung eines Tür-Gongs oder einer Funksteckdose erwünscht sein, wenn das Auto jedoch durch so eine Verfahren aufgeschlossen wird, so ist das ärgerlich.
Die Hersteller haben dazu verschiedene Techniken im Einsatz, um die Sicherheit ihrer Funkprotokolle zu steigern. Eine der einfachsten ist, im Datagramm einen Zählerwert zu übertragen, der vom Sender mit jedem neuen Funksignal erhöht wird. Der Empfänger merkt sich nun, welchen Zählerstand er bereits erhalten hat und verwirft Datagramme mit bereits bekannten (kleineren) Zählerwerten. Man nennt dieses Verfahren auch Rolling-Code. Einmal verwendete Datagramme sind somit (zunächst) nicht wiederverwendbar.
Soweit, so gut. Allerdings hat die Sache zwei Haken. Zum einen könnte der Code irgendwann überlaufen und wieder bei 0 beginnen. Das Auto würde dann wohl nicht mehr aufgehen. Das darf natürlich nicht passieren. Zum anderen könnte sich das Auto zufällig ein Störsignal einhandeln, welches einen sehr hohen Zyklenzählerstand besitzt. In diesem Fall müßte der rechtmäßige Besitzer solange einen Knopf der Fernbedienung drücken, bis der Zählerstand der Fernbedienung wieder größer ausfällt, als der im Auto gespeicherte. Das wäre unzumutbar. Insofern hat sich der Hersteller meines PKW dafür entschieden nur eine gewisse Historie an empfangenen Codes zu speichern. Empfängt das Auto mehr aufeinanderfolgende (und ansonsten gültige) Signale als das Auto speichern kann, dann akzeptiert das Auto auch wieder einen kleineren Zyklenzähler. Auto und Funkschlüssel sind dann also wieder "in Sync" und das Auto "geht auf".
Man erkennt den Umstand des "out of Sync" relativ einfach, wenn man direkt neben dem Auto steht, auf die Fernbedienung drückt, die Batterie der Fernbedienung voll ist, das Auto aber erst nach dem zigten Knopfdruck reagiert. Um keine Panik auszulösen – der Hersteller meines PKW hat im Nachfolgemodell nachgebessert und bringt eine Technik zum Einsatz, die sich mit einem einfachen "Capture and Replay" nicht aushebeln läßt. Z.B. stellen Challenge-Response Verfahren mit dynamischen Schlüsselwechsel sicherere Alternativen zum Rolling-Code dar, bedingen jedoch auch aufwändigere Hardware.
Viel Erfolg beim Basteln wünscht...
schnasseldag