Hallo Habr.
Wahrscheinlich haben viele, die eine Uhr oder eine Wetterstation kaufen, das Logo einer Funkuhr oder sogar einer Atomuhr auf der Verpackung gesehen. Das ist sehr praktisch, denn es genügt, die Uhr auf den Tisch zu stellen, und nach einer Weile stellt sie sich automatisch auf die genaue Uhrzeit ein.
Lassen Sie uns herausfinden, wie es funktioniert, und einen Decoder in Python schreiben.
Es gibt verschiedene Zeitsynchronisationssysteme. Am beliebtesten in Europa ist das deutsche System
Alles, was im Folgenden geschrieben wird, dreht sich um DCF77.
Signalempfang
Der DCF77 ist ein Langwellensender, der mit 77.5 kHz arbeitet und AM-Signale sendet. Die Station mit einer Leistung von 50 kW liegt 25 km von Frankfurt entfernt, sie nahm 1959 ihre Arbeit auf, 1973 wurde die genaue Uhrzeit durch die Angabe des Datums ergänzt. Die Wellenlänge bei einer Frequenz von 77 kHz ist sehr groß, daher sind auch die Abmessungen des Antennenfeldes sehr ordentlich (Foto aus Wikipedia):
Mit einer solchen Antenne und Eingangsleistung deckt das Empfangsgebiet fast ganz Europa, Weißrussland, die Ukraine und einen Teil Russlands ab.
Jeder kann aufnehmen. Gehen Sie dazu einfach zum Online-Receiver
An derselben Stelle drücken wir den Download-Button und nehmen ein paar Minuten langes Fragment auf. Wenn Sie einen „echten“ Empfänger haben, der eine Frequenz von 77.5 kHz aufzeichnen kann, können Sie ihn natürlich verwenden.
Wenn wir Funksignale mit genauer Zeit über das Internet empfangen, erhalten wir natürlich keine wirklich genaue Zeit – das Signal wird verzögert übertragen. Unser Ziel ist jedoch nur, die Struktur des Signals zu verstehen, dafür reicht die Internetaufzeichnung völlig aus. Im wirklichen Leben werden zum Empfangen und Dekodieren natürlich spezielle Geräte verwendet, auf die weiter unten eingegangen wird.
Wir haben also den Datensatz erhalten, beginnen wir mit der Bearbeitung.
Signaldekodierung
Laden wir die Datei mit Python und sehen uns ihre Struktur an:
from scipy.io import wavfile
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
sample_rate, data = wavfile.read("dcf_websdr_2019-03-26T20_25_34Z_76.6kHz.wav")
plt.plot(data[:100000])
plt.show()
Wir sehen eine typische Amplitudenmodulation:
Um die Dekodierung zu vereinfachen, nehmen wir die Signalhüllkurve mithilfe der Hilbert-Transformation:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000])
Erweitertes Ergebnis:
Lassen Sie uns die Geräuschemissionen mithilfe eines Tiefpassfilters glätten und gleichzeitig den Durchschnittswert berechnen. Dies wird später beim Parsen nützlich sein.
b, a = signal.butter(2, 20.0/sample_rate)
zi = signal.lfilter_zi(b, a)
A, _ = signal.lfilter(b, a, A, zi=zi*A[0])
avg = (np.amax(A) + np.amin(A))/2
Ergebnis (gelbe Linie): ein nahezu rechteckiges Signal, das relativ einfach zu analysieren ist.
Parsing
Zuerst müssen Sie die Bitfolge ermitteln. Die Signalstruktur selbst ist sehr einfach.
Die Impulse werden in Sekundenintervalle unterteilt. Wenn der Abstand zwischen den Impulsen 0.1 s beträgt (d. h. die Länge des Impulses selbst beträgt 0.9 s), fügen wir „0“ zur Bitfolge hinzu, wenn der Abstand 0.2 s beträgt (d. h. die Länge beträgt 0.8 s), fügen wir hinzu „1“. Das Ende jeder Minute wird durch einen „langen“ Impuls von 2 s Länge angezeigt, die Bitfolge wird auf Null zurückgesetzt und die Befüllung beginnt von neuem.
Das Obige ist einfach in Python zu schreiben.
sig_start, sig_stop = 0, 0
pos = 0
bits_str = ""
while pos < cnt - 4:
if A[pos] < avg and A[pos+1] > avg:
# Signal begin
sig_start = pos
if A[pos] > avg and A[pos+1] < avg:
# Signal end
sig_stop = pos
diff = sig_stop - sig_start
if diff < 0.85*sample_rate:
bits_str += "1"
if diff > 0.85*sample_rate and diff < 1.25*sample_rate:
bits_str += "0"
if diff > 1.5*sample_rate:
print(bits_str)
bits_str = ""
pos += 1
Als Ergebnis erhalten wir eine Folge von Bits, die in unserem Beispiel für zwei Sekunden so aussieht:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
Interessant ist übrigens, dass es im Signal auch eine „zweite Datenschicht“ gibt. Die Bitfolge wird ebenfalls mit kodiert
Unser letzter Schritt: Holen Sie sich die tatsächlichen Daten. Bits werden einmal pro Sekunde übertragen, wir haben also nur 59 Bits, in denen ziemlich viele Informationen kodiert sind:
Die Bits sind in beschrieben
Für diejenigen, die selbst experimentieren möchten, finden Sie den Dekodierungscode unter dem Spoiler.
Quellcode
def decode(bits):
if bits[0] != '0' or bits[20] != '1':
return
minutes, hours, day_of_month, weekday, month, year = map(convert_block,
(bits[21:28], bits[29:35], bits[36:42], bits[42:45],
bits[45:50], bits[50:58]))
days = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday')
print('{dow}, {dom:02}.{mon:02}.{y}, {h:02}:{m:02}'.format(h=hours, m=minutes, dow=days[weekday],
dom=day_of_month, mon=month, y=year))
def convert_ones(bits):
return sum(2**i for i, bit in enumerate(bits) if bit == '1')
def convert_tens(bits):
return 10*convert_ones(bits)
def right_parity(bits, parity_bit):
num_of_ones = sum(int(bit) for bit in bits)
return num_of_ones % 2 == int(parity_bit)
def convert_block(bits, parity=False):
if parity and not right_parity(bits[:-1], bits[-1]):
return -1
ones = bits[:4]
tens = bits[4:]
return convert_tens(tens) + convert_ones(ones)
Wenn wir das Programm ausführen, sehen wir etwa diese Ausgabe:
0011110110111000001011000001010000100110010101100010011000
Tuesday, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Tuesday, 26.03.19, 21:42
Eigentlich ist das die ganze Magie. Der Vorteil eines solchen Systems besteht darin, dass die Dekodierung äußerst einfach ist und auf jedem noch so unkomplizierten Mikrocontroller durchgeführt werden kann. Wir zählen einfach die Länge der Impulse, sammeln 60 Bits und am Ende jeder Minute erhalten wir die genaue Zeit. Im Vergleich zu anderen Methoden der Zeitsynchronisierung (z. B. GPS oder Gott bewahre das Internet :) benötigt eine solche Funksynchronisierung praktisch keinen Strom – beispielsweise funktioniert eine gewöhnliche Heimwetterstation etwa ein Jahr lang mit 2 AA-Batterien. Daher werden auch Armbanduhren mit Funksynchronisation hergestellt, ganz zu schweigen von Wanduhren oder Bahnhofsuhren.
Der Komfort und die Einfachheit des DCF ziehen auch Heimwerker an. Für nur 10–20 US-Dollar können Sie ein fertiges Antennenmodul mit einem fertigen Empfänger und einem TTL-Ausgang kaufen, das an einen Arduino oder einen anderen Controller angeschlossen werden kann.
Für Arduino bereits geschrieben und
Wer möchte, kann die alte Großmutteruhr sogar aufwerten, indem er ein neues Uhrwerk mit Funksynchronisation einbaut:
Sie können eines bei eBay unter dem Stichwort „Radio Controlled Movement“ finden.
Und zum Schluss noch ein Life-Hack für diejenigen, die bis hierher gelesen haben. Auch wenn es in den nächsten paar tausend Kilometern keinen einzigen Funksignalsender gibt, lässt sich ein solches Signal leicht selbst erzeugen. Bei Google Play gibt es ein Programm namens „DCF77 Emulator“, das ein Signal an die Kopfhörer ausgibt. Laut dem Autor werden die Kopfhörer das Signal empfangen, wenn man sie rund um die Uhr umwickelt (ich frage mich, wie, denn gewöhnliche Kopfhörer geben kein 77-kHz-Signal aus, aber der Empfang ist wahrscheinlich auf Oberwellen zurückzuführen). Das Programm funktionierte bei mir unter Android 9 überhaupt nicht – es gab einfach keinen Ton (oder vielleicht habe ich ihn auch nicht gehört – immerhin 77 kHz :), aber vielleicht hat jemand mehr Glück. Einige bauen sich jedoch selbst einen vollwertigen DCF-Signalgenerator, was auf demselben Arduino oder ESP32 einfach zu bewerkstelligen ist:
(Quelle
Abschluss
Das DCF-System erwies sich als wirklich recht einfach und praktisch. Mit Hilfe eines einfachen und günstigen Empfängers haben Sie immer und überall die genaue Uhrzeit, natürlich auch im Empfangsbereich. Es scheint, dass solch einfache Lösungen trotz der weit verbreiteten Digitalisierung und des „Internets der Dinge“ noch lange gefragt sein werden.
Source: habr.com