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 zu Thomas Antwort abbrechen