Sending and receiving morse signals via a Raspberry Pi using light and sound
calculating resistance of led: R=U/I ( Ohmsches Gesetz )
R=3.3V(output of pin)-2V(input of led) / I
R=1.3V/0.004A
R = 325Ohm -> 330 Ohm
Setup:
cd Light_Transmission
pip install -r requirements.txt
Usage of morse sender:
python3 sender.py "message"
Usage of morse receiver:
python3 receiver.py
documentation of functionality in Light_Transmission in german:
In unserem Projekt bauten und programmierten wir zwei verschiedene Morse-Code Transmitter mit jeweils einem Sender und Empfänger. Dazu benutzten wir für jede Übertragungsform einen Raspberry Pi.
Erik erstellte den Lichttransmitter mit einem besonderem Fokus auf eine starke Defragmentierung. Felix programmierte die Übertragung mit Schall, der Schwerpunkt lag dabei bei der einstellbaren Sendegeschwindindigkeit. Beide Transmitter schafften wir unabhängig voneinander, sodass wir später die Vor- und Nachteile der jeweiligen Spezialisierungen abwägen konnten.
Der Morse Transmitter basiert auf einer LED, welche an und abgeschaltet wird und einem Fotowiederstand (LDR), welcher an einen GPIO-Pin eines Raspberry Pi angeschlossen ist. Beim Senden einer Nachricht wird diese zunächst in Morse Code übersetzt (sender.encrypt) und an eine Instanz der Klasse Transmitter(transmitter.py) übergeben. Dieser hängt ein Trennungszeichen (SEPERATOR) and den Morse Code an, um die Checksum von der Nachricht zu separieren. Für jedes Signal in der Nachricht wird die LED für den Zeitraum transmitter.LONG bei "-" und für einen Zeitraum transmitter.SHORT bei "." aktiviert. Wenn nach einem Signal ein Abstand ist, wird nach dem Aktivieren der LED diese für den Zeitraum transmitter.LONG abgeschaltet, um die Buchstaben voneinander zu trennen andernfalls wird sie für den Zeitraum transmitter.SHORT deaktiviert, um zwischen Signalen unterscheiden zu können. Im Anschluss der Nachricht und dem Trennungszeichen z.B "HALLO WELT#" wird die Checksum gesendet. Diese Berechnet sich durch die Addition der Interpretation eines Zeichens in Form einer Zahl laut der ASCII Tabelle jedes Zeichens, welches übermittelt wird. Eine Vollständige Nachricht könnte zum Beispiel so aussehen: HALLO WELT#716 (.... .- .-.. .-.. --- /.-- . .-.. - #--... .---- -....).
Der Empfänger hingegen liest alle transmitter.delay_sleep Sekunden den Input Pin des Fotowiderstands ab und speichert sie in einer Liste. Ein Leuchten der LED wird als "+" repräsentiert und Dunkelheit als "-". Sobald sich der Status des Pins ändert, wird die Liste um ein neues Element erweitert. Erst nachdem die ganze Nachricht in der Liste abgespeichert wurde, wird diese ausgewertet. Beim Speichern wird ein Input unter 0.3 als hell interpretiert und alles darüber als dunkel um weniger sensitiv gegenüber dem Umgebungslicht zu sein. Man erhält durch dieses Vorgehen eine Liste wie diese: ['+++++', '-----', '+++++', '-----', '+++++', '-----', '+++++', '---------------', '+++++', '-----', '++++++++++', '---------------', '+++++', '-----', '++++++++++', '-----', '++++', '------', '+++++', '--------------', '++++++', '-----', '++++++++++', '-----', '++++', '-----', '++++++', '---------------', '+++++++++', '------', '+++++++++', '------', '+++++++++', '----------------', '+++++++++', '------', '++++', '-----', '+++++', '------', '+++++++++', '------', '++++', '---------------', '+++++', '------', '+++++++++', '-----', '++++++++++', '---------------', '+++++', '---------------', '+++++', '----', '++++++++++', '-----', '++++++', '-----', '++++', '---------------', '++++++++++', '---------------', '++++++++++', '-----', '++++++', '-----', '++++', '-----', '++++++++++', '-----', '++++++++++', '----------', '++++++++++', '-----', '++++++++++', '-----', '+++++', '------', '++++', '-----', '+++++', '----------------', '++++', '-----', '++++++++++', '-----', '++++++++++', '-----', '++++++++++', '-----', '++++++++++', '---------------', '++++++++++', '-----', '+++++', '-----', '++++', '------', '+++++', '-----', '++++'] Zur Interpretation dieser werden drei Bereiche deklariert die durch transmitter.delay_sleep, transmitter.LONG sowie transmitter.SHORT berechnet werden. z.B wäre bei der Standard Konfiguration "++++" als "." also kurzes Signal zu interpretieren und ein "++++++++++" als "-" also langes Signal. Ein neuer Buchstabe wird durch viele "---------------" erkennbar und die Trennung zwischen Signalen werden durch wenige "-----" gekennzeichnet. Daraus folgt eine Morse Botschaft: z.B .... .- .-.. .-.. --- /.-- . .-.. - #--... .---- -.... was als HALLO WELT#716 übersetzt werden kann. nach dieser Interpretation wird die Botschaft in Checksum und Nachricht aufgeteilt und überprüft, ob die übermittelte Botschaft mit der selbst ausgerechneten Checksum übereinstimmt. Sollte dies nicht der Fall sein und die Checksum eine Zahl ist also voraussichtlich richtig übermittelt wurde, wird versucht die Nachricht zu defragmentieren. Hierbei wird eine Wortliste aus häufigen Wörtern mit den Unbekannten Wörtern der Nachricht verglichen und bei einer Levenshtein Distanz unter drei als das Wort als Kandidat für ein unbekanntes Wort in Betracht gezogen.
Wenn nach Überprüfen aller möglichen Wörter in allen Variationen die erwartete Checksum erreicht wird und keine unbekannten Wörter mehr in der Nachrricht vorhanden sind, wird die defragmentierte Nachricht zurückgegeben, andernfalls wird die fragmentierte Nachricht ausgegeben und ein Fehler zurückgegeben. In dem Fall, dass die Checksum voraussichtlich falsch übertragen wurde, wird nicht versucht die Nachricht zu defragmentieren und ein Fehler wird zurückgegeben. Wenn die Nachricht wie erwartet eintrifft wird diese zurückgegeben.
Die Konstanten transmitter.LONG und transmitter.SHORT können dynamisch nach oben erweitert aber nicht weiter reduziert werden ohne massive Fehler in der Übertragung auszulösen. Weiterhin ist zu beachten, dass transmitter.LONG doppelt so groß wie transmitter.SHORT sein sollte, um eine einwandfreie Unterscheidung zu gewährleisten. Es ist zudem nicht möglich diese beiden Konstanten weiter zu verringern, und die Konstanten OFF_SHORT und OFF_LONG einzuführen, welche die übliche Größe von transmitter.LONG und transmitter.SHORT aufweisen. Auch dies führt zur vollständigen Fehlübertragung der Nachricht. Weiterhin ist es wichtig, dass transmitter.SEPERATOR nicht denselben Anfang von transmitter.EOB hat, da andernfalls transmitter.SEPERATOR andernfalls falsch interpretiert wird. Die erwartete Zeit wird durch die gleiche Funktion wie in transmitter.send berechnet, danach wird diese Schätzung ein wenig erhöht um tendenziell eher zu viel Zeit als zu wenig zu schätzen.
Das Morse Verfahren ist anderen Verfahren in der Geschwindigkeit deutlich unterlegen, da diese z.B das Licht modulieren und so mehr Daten gleichzeitig senden können, auch die Hardware ist im Vergleich zu Glasfaser Technolgien oder ähnlichem eher langsam, da diese mit einer Verzögerung schaltet keine besonders starke Lichtquelle verwendet wird. Jedoch wird dies ein Stück weit durch die Defragmentierung wieder wettgemacht, da eine Nachricht anhand einer Cheksum wiederhergestellt werden kann.
Dieser Transmitter basiert auf eine Übertragung über Schall. Ein Schallsensor und aktiver Buzzer wird jeweils an einen Raspberry PI angeschlossen.
Wie bei der Übertragung durch Licht wird dabei erstmals die Nachricht nach der Eingabe in dem Senderprogramm audiosender.py in Morse-Code verschlüsselt. Nach jedem Buchstaben wird ein "/" angehängt, Leerzeichen werden mit "|" ersetzt. Diese Zeichen stellen die verschiedenen Pausen dar. Man kann die Geschwindigkeit eines kurzen Tones (Dit) in Millisekunden bestimmen, wovon alle anderen Tonlängen ermittelt werden. Lange Töne und Pausen zwischen Buchstaben haben die dreifache, Pausen zwischen Wörtern die siebenfachewie Länge wie das eingegebene Tempo. Damit der Empfänger die Geschwindigkeit ermitteln kann, wird jeder Nachricht die Startsequenz "|-.-.-/" hinzugefügt. Diese wird zur Kalibrierung des Empfängers genutzt. Für jedes "." wird die Funktion shortsound(), für "-" longsound() abgerufen, welche den Buzzer für die vorher angegebene Zeit aktiviert. Um die Töne zu unterscheiden wird nach jedem Signal eine Pause mit der Länge eines Dits durchgeführt.
Der Empfänger wartet anfangs, bis der Lautstärkepegel überschritten wird. Daraufhin wird ein Timer aktiviert, welcher so lange zählt, bis entweder der Pegel unter- oder später überschritten wird. Wenn dies geschieht wird in die Liste "sequence" der Wert des Timers eingetragen und er auf 0 gesetzt. Diese Liste könnte entstehen: [65, 20, 24, 19, 57, 21, 17, 20, 67, 70, 20, 20, 20, 20, 20, 20, 20, 67, 18, 20, 59, 60, 18, 25, 59, 20, 21, 24, 13, 57, 24, 20, 65, 12, 19, 21, 10, 56, 70, 20, 66, 20, 61, 22] Es stellt diese Sequenz dar: "-.-.-/..../.-/.-../.-../---" -> HALLO Die Aufnahme endet, wenn einige Zeit kein Ton empfangen wurde.
Die Interpretation der Liste kann nur stattfinden, wenn die Sendegeschwindigkeit bekannt ist. Dazu wird die Startsequenz genutzt, um die durchschnittliche Länge eines Dit zu ermitteln. Der Durchschnitt von den ersten zehn Elementen der Liste (Töne und Pausen) wird genommen, wobei lange Signale vorerst durch drei geteilt werden. In dem aufgegührten Beispiel wäre die vom Programm genutzte Geschwindigkeit gerundet 21. Nun werden alle Werte in der Liste, welche ungefähr dieser Zahl entsprechen, auf den selben Wert gebracht. Größere Werte werden auf das drei- oder siebenfache gerundet. So würde die Liste nun aussehen: [62, 21, 21, 21, 62, 21, 62, 21, 62, 62, 21, 21, 21, 21, 21, 21, 21, 62, 21, 21, 62, 62, 21, 21, 62, 21, 21, 21, 21, 62, 21, 21, 62, 21, 21, 21, 21, 62, 62, 21, 62, 21, 62, 21] Als nächstes wird die Zahlenliste in den oben bereits aufgegriffenen String "..../.-/.-../.-../---" konvertiert und dabei die Startsequenz gelöscht. Dabei ist zu beachten, dass das erste Element immer ein Ton ist. Das zweite Element ist daher garantiert eine Pause, das dritte ein Signal und so weiter.
Schlussendlich werden alle Zeichen so lange in einen seperaten String namens "letter" gegeben bis ein "/" oder "|" erkannt wird, um alle Buchstaben voneinander zu trennen. "letter" wird zurück in einen Buchstaben übersetzt, welcher der letzten Variable "decoded" hinzugefügt wird. Der String "letter" wird geleert. Dieses Verfahren wird so oft wiederholt bis alle Buchstaben übersetzt worden sind. Die Startsequenz wird ignoriert. Falls die Sequenz falsch aufgenommen wurde und der Buchstabe nicht korrekt übersetzt werden kann wird eine Warnung ausgegeben und nach dem zweiten Zeichen des fragmentierten Buchstabens ein "/" eingefügt.
Wir sind auf besondere Probleme bei der Kalibrierung und korrekten Konfiguration des Audiosensors gestoßen. Anfangs mussten wir tatsächlich den Empfänger wegen einem Defekt ersetzen. Der Ersatz hat zwar funktioniert, jedoch gab er bei einem Signal keine konstante Ausgabe, wodurch sie als viele kurze Töne und Pausen interpretiert wurde. Beispiel: [3, 2, 4, 1, 3, 2, 1, 1, 2, 1, 4, 2, 1, 65] Das gesendete Signal: [27, 65] Beachte, dass die "27" ein Signal und die "65" eine Pause darstellt. Um dies zu beheben, wird während der Tonaufnahme die Liste "last20" mit den letzten 20 Inputs gefüllt. Die unregelmäßigen Sequenzen werden geglättet, da nur eine 1 in dieser Liste enthalten sein muss, um es als Ton wahrzunehmen. Es besteht aber weiterhin die Gefahr, dass fehlerhafte Elemente in die Liste eingetragen werden. Daher werden alle Einträge der Liste, welche kleiner als 20 sind, gelöscht. Leider führt diese Sicherheitsmaßnahme aber auch dazu, dass bei sehr hohen Geschwindigkeiten Töne von Störsignalen nicht mehr unterschieden werden können.
Nach einigen Versuchen stellte sich heraus, dass bis zu einer Geschwindigkeit von 60 Millisekunden pro Dit mit geringer Fehlerwahrscheinlichkeit gesendet werden kann. Allerdings ist auch bei besonders langsamen Sendetempo das Risiko einer inkorrekten Übersetzung größer, da ein langer Ton wegen einer kurzen Fehlaufnahme getrennt und als zwei kurze interpretiert werden kann. Die Obergrenze liegt bei durchschnittlich einer Sekunde.
Dieses Verfahren ist deutlich empfindlicher bei Störgeräuschen, da es kaum Defragmentierung besitzt, jedoch ist es auf die offiziellen Morse-Code Standards ausgelegt und passt sich automatisch an unterschiedliche Geschwindigkeiten an.