Bei mir in der Nähe gibt es einen Mystery Cache GC6CYF1 mit dem Namen Komische Töne 1. Es dauerte nicht lang und die Vermutung lag nahe dass es sich hierbei um Morsecodes handeln könnte. Nur wie entschlüssle ich diesen am besten? Vorallem, wie würde ich das machen wenn ich bei einem Cache unterwegs Morsecodes entschlüsseln muss? Einfach eine App installieren? Viel zu einfach! Als Bastler kann man da doch sicher was basteln…
Nachdem ich Micropython verwenden wollte und nicht viel Geld ausgeben war ein ESP32 NodeMCU Board die richtige Wahl. Kriegt man für ein paar Euro und im Gegensatz zum ESP8266 NodeMCU hat man mehr RAM und vorallem einen ADC der über die kompletten 0V – 3.3V geht. Der vom ESP8266 geht nur von 0V – 1V. Das erfordert dann also einen Spannungsteiler und den wollte ich mir sparen. Das der ESP32 WiFi kann ist zwar toll. Hier aber nicht nötig!
Zum hören hatte ich in irgendeinem Arduino-Sortiment noch ein „1 piece High Sensitivity Audio Microphone Module Audio Amplifier 20 dB Low Noise Absorb DC 3.3/5 V“ um es mal im schönsten Denglish zu schreiben. Prinzipiell nichts anderes als ein Elekt-Mikrofon und ein LM393 Op-Amp mit ein bisschen Hühnerfutter. Das Modul läuft mit 3.3V und bietet sowohl einen digitalen Ausgang (Ton ist da, oder nicht) und einen analogen Ausgang der die verstärkte und angepasste Spannung des Mikrofons ausgibt.
Der digitale Ausgang kann mit einem Poti auf eine Lautstärke eingestellt werden ab der er einen Ton erkennt. Sobald er einen Ton erkennt geht D0 auf High und die LED leuchtet. Das ganze war mir aber zu unsicher und ich wollte nicht unterwegs irgendwo einen Lautstärkeregler mit einem Schraubenzier erst an meine Morsecodes anpassen müssen. Deshalb also der analoge Ausgang A0 den wir mit dem ESP32 (GPIO36 / A0 / ADC0 / SVP) verbinden. Dann noch 3.3V und GND verbinden und schon wären wir soweit.
Davor hatte ich mit dem Oszi gemessen, das Modul gibt bei keinem Ton ca. 1.6V aus (also in etwa die Mitte von 3.3V). Bei einem leisen Ton schwankt es 200mV Peak-To-Peak und bei einem lauten Ton bis zu 2V PP.
Den Programmcode habe ich in ein Modul gepackt, das Modul sieht so aus:
import time import machine class Morse: def __init__(self, adcpin, minloudness): # Globals self.minloudness = minloudness # Local Attributes self.hearingactive = 0 self.tones = [] self.pauses = [] self.morsestring = '' self.decodedstring = '' # Init the ADC self.adc_pin = machine.Pin(adcpin) self.adc = machine.ADC(self.adc_pin) self.adc.atten(self.adc.ATTN_11DB) # Morse alphabet used: # https://de.wikipedia.org/wiki/Morsezeichen self.morsecode = [] self.morsecode.append({'A': '.-'}) self.morsecode.append({'B': '-...'}) self.morsecode.append({'C': '-.-.'}) self.morsecode.append({'D': '-..'}) self.morsecode.append({'E': '.'}) self.morsecode.append({'F': '..-.'}) self.morsecode.append({'G': '--.'}) self.morsecode.append({'H': '....'}) self.morsecode.append({'I': '..'}) self.morsecode.append({'J': '.---'}) self.morsecode.append({'K': '-.-'}) self.morsecode.append({'L': '.-..'}) self.morsecode.append({'M': '--'}) self.morsecode.append({'N': '-.'}) self.morsecode.append({'O': '---'}) self.morsecode.append({'P': '.--.'}) self.morsecode.append({'Q': '--.-'}) self.morsecode.append({'R': '.-.'}) self.morsecode.append({'S': '...'}) self.morsecode.append({'T': '-'}) self.morsecode.append({'U': '..-'}) self.morsecode.append({'V': '...-'}) self.morsecode.append({'W': '.--'}) self.morsecode.append({'X': '-..-'}) self.morsecode.append({'Y': '-.--'}) self.morsecode.append({'Z': '--..'}) self.morsecode.append({'1': '.----'}) self.morsecode.append({'2': '..---'}) self.morsecode.append({'3': '...--'}) self.morsecode.append({'4': '....-'}) self.morsecode.append({'5': '.....'}) self.morsecode.append({'6': '-....'}) self.morsecode.append({'7': '--...'}) self.morsecode.append({'8': '---..'}) self.morsecode.append({'9': '----.'}) self.morsecode.append({'0': '-----'}) self.morsecode.append({'À': '.--.-'}) self.morsecode.append({'Ä': '.-.-'}) self.morsecode.append({'È': '.-..-'}) self.morsecode.append({'É': '..-..'}) self.morsecode.append({'Ö': '---.'}) self.morsecode.append({'Ü': '..--'}) self.morsecode.append({'ß': '...--..'}) self.morsecode.append({'-CH-': '----'}) self.morsecode.append({'Ñ': '--.--'}) self.morsecode.append({'.': '.-.-.-'}) self.morsecode.append({',': '--..--'}) self.morsecode.append({':': '---...'}) self.morsecode.append({';': '-.-.-.'}) self.morsecode.append({'?': '..--..'}) self.morsecode.append({'-': '-....-'}) self.morsecode.append({'_': '..--.-'}) self.morsecode.append({'(': '-.--.'}) self.morsecode.append({')': '-.--.-'}) self.morsecode.append({"'": '.----.'}) self.morsecode.append({'=': '-...-'}) self.morsecode.append({'+': '.-.-.'}) self.morsecode.append({'/': '-..-.'}) self.morsecode.append({'@': '.--.-.'}) self.morsecode.append({'-KA-': '-.-.-'}) self.morsecode.append({'-BT-': '-...-'}) self.morsecode.append({'-AR-': '.-.-.'}) self.morsecode.append({'-VE-': '...-.'}) self.morsecode.append({'-SK-': '...-.-'}) self.morsecode.append({'-SOS-': '...---...'}) self.morsecode.append({'-HH-': '........'}) # Sample at around 8 kHz (120 µS between samples) # Sample 50 times, which needs 0.006 seconds def sample(self): values = [] start = time.ticks_ms() for i in range(50): val = self.adc.read() values.append(val) return (time.ticks_ms() - start, max(values) - min(values)) def getloudness(self): # Sample for around 0.1 seconds and return loudness maxloudness = 0 for i in range(16): timetaken, loudness = self.sample() if loudness > maxloudness: maxloudness = loudness return maxloudness def hearformorse(self): toneduration = 0 pauseduration = 0 intone = 0 inpause = 0 self.tones = [] self.pauses = [] # Check how loud it is minloud = self.getloudness() minloud = minloud + self.minloudness # Hear while our attribute is != 0 - an Interrupt can deactivate it self.hearingactive = 1 while self.hearingactive != 0: timetaken, loudness = self.sample() if loudness > minloud: intone = 1 if inpause == 1: # We come from a pause, this is a new tone, save the old one if toneduration > 0: self.tones.append(toneduration) toneduration = 0 inpause = 0 toneduration = toneduration + timetaken else: inpause = 1 if intone == 1: # We come from a tone, this is a new pause, save the old one if pauseduration > 0: self.pauses.append(pauseduration) pauseduration = 0 intone = 0 pauseduration = pauseduration + timetaken # Done? Add the last pause and tone! self.tones.append(toneduration) self.pauses.append(pauseduration) def decodeintomorse(self): # Merge the two lists tones_and_pauses = [] for pair in zip(self.pauses, self.tones): tones_and_pauses.extend(pair) # Calculate the thresholds # For tones threshes = [] for i in range(len(self.tones)-1): threshes.append({'jump': abs((self.tones[i+1] - self.tones[i]) / 2), 'val': abs((self.tones[i] + self.tones[i+1]) / 2)}) threshes = sorted(threshes, reverse = True, key=lambda k: k['jump']) tonethresh = threshes[0]['val'] threshes.clear() # For pauses - remove the first and the last one, because it is a (very long) pause templist = self.pauses[1:-1] templist.sort() threshes = [] for i in range(len(templist)-1): threshes.append({'jump': abs((templist[i+1] - templist[i]) / 2), 'val': abs((templist[i] + templist[i+1]) / 2)}) threshes = sorted(threshes, reverse = True, key=lambda k: k['jump']) longpausethres = threshes[0]['val'] mediumpausethres = threshes[1]['val'] templist.clear() threshes.clear() # Build morse string self.morsestring = '' i = -1 for actsign in tones_and_pauses: i = i + 1 if i % 2 != 0: # We're a tone if actsign > tonethresh: self.morsestring = self.morsestring + '-' else: self.morsestring = self.morsestring + '.' else: # We're a pause if actsign > longpausethres: # New word self.morsestring = self.morsestring + 'XZ' if actsign > mediumpausethres: # New sign self.morsestring = self.morsestring + 'X' return self.morsestring def decodemorseintotext(self): self.decodedstring = '' for actval in self.morsestring.split('X'): if actval == 'Z': self.decodedstring = self.decodedstring + ' ' else: for acttest in self.morsecode: acttestval = list(acttest.values())[0] acttestlen = len(acttestval) acttestdecoded = list(acttest.keys())[0] if actval == acttestval: self.decodedstring = self.decodedstring + acttestdecoded # Because of the first long pause we have a space at the beginning, remove it self.decodedstring = self.decodedstring[1:] return self.decodedstring
Und die main.py die das Modul nutzt sieht so aus:
#esptool.py --port /dev/ttyUSB0 erase_flash #esptool.py --port /dev/ttyUSB0 --chip esp32 write_flash -z 0x1000 /home/tc/Downloads/esp32-20181118-v1.9.4-684-g51482ba92.bin #shell --buffer-size=30 -p /dev/ttyUSB0 #cp /home/ingres/Desktop/morse.py /pyboard import morse import machine # Globals # Where is the Microphone connected? adcpin = 36 # Where is the button connected inputbuttonpin = 0 # How much above normal loudness level is a beep? minloudness = 150 def stophearing(pin): # Setting this to zero will stop the haring of the class mymorse.hearingactive = 0 btn1 = machine.Pin(inputbuttonpin, machine.Pin.IN, machine.Pin.PULL_UP) btn1.irq(trigger=machine.Pin.IRQ_RISING, handler = stophearing) mymorse = morse.Morse(adcpin, minloudness) mymorse.hearformorse() mymorse.decodeintomorse() mymorse.decodemorseintotext()
Das ganze gibt es auf Github:
https://github.com/ThomasChr/ESP32-Micropython-MorseDecode
Von der Logik her nehme ich einfach mit dem ADC ganz viele kleine Schnippselchen und messe wie groß MIN und MAX in einem Schnippselchen war. Ist es über einem bestimmten Wert so gehe ich davon aus dass es wohl ein Ton war. War es ein Ton so wird es zu einem Array der Töne hinzugefügt, war es kein Ton so wird es zu einem Array der Pausen hinzugefügt.
Die Ermittlung der Tonlänge für einen kurzen und langen Ton, sowie die Ermmittlung der Pausen-Längen (Pause zwischen den Tönen, Pause zwischen Buchstaben, Pause zwischen Wörtern) erfolgt automatisch.
Der ADC des ESP32 scheint dabei mit Micropython ca. 8 kHz Sampling zu erreichen, was wohl so ziemlich für den schnellsten Morser reichen sollte.
Bei dem Cache hat es meistens (wenn niemand im Hintergrund laut geredet hat) den Morsecode problemlos entschlüsseln können. Hier kann man sicherlich noch etwas Zeit reinstecken um das ganze stabiler zu gestalten.
Wenn man es unterwegs mitnehmen will so wäre es durchaus denkbar dass sobald man eine Powerbank anschließt dass Modul gleich loslegt und sobald man den Button drückt es fertig ist und entschlüsselt. Stromverbrauch ist für ein paar Minuten Anwendung kein Thema deshalb muss auch kein stromsparen implementiert werden. Es fehlt also nur noch ein Gehäuse mit einem Button und ein LCD um den entschlüsselten Text anzuzeigen. Der Button beendet dabei sowohl das hören auf Morsecodes und kann danach zum scrollen des Textes verwendet werden.
Gehäuse, LCD und Button ist noch nicht fertig. Mal sehen ob ich dazu mal lust habe…
Schreibe einen Kommentar