Witaj Habr.
Prawdopodobnie wielu nabywców zegarka lub stacji pogodowej widziało na opakowaniu zegar sterowany radiowo lub nawet zegar atomowy. Jest to bardzo wygodne, ponieważ wystarczy położyć zegar na stole, a po chwili automatycznie ustawi się on na dokładną godzinę.
Zobaczmy, jak to działa i napiszmy dekoder w Pythonie.
Istnieją różne systemy synchronizacji czasu. Najpopularniejszy w Europie jest system niemiecki
Wszystko, co zostanie napisane poniżej, będzie dotyczyć DCF77.
Odbiór sygnału
DCF77 jest stacją długofalową pracującą na częstotliwości 77.5 kHz i nadającą sygnały w modulacji amplitudowej. Stacja o mocy 50 kW zlokalizowana jest 25 km od Frankfurtu, rozpoczęła działalność w 1959 r., a w 1973 r. do dokładnej godziny dodano datę. Długość fali przy częstotliwości 77 KHz jest bardzo duża, więc wymiary pola anteny też są całkiem przyzwoite (zdjęcie z Wikipedii):
Przy takiej antenie i poborze mocy zasięg odbioru obejmuje niemal całą Europę, Białoruś, Ukrainę i część Rosji.
Każdy może nagrać sygnał. W tym celu wystarczy udać się do odbiornika online
Tam wciskamy przycisk pobierania i nagrywamy kilkuminutowy fragment. Oczywiście, jeśli masz „prawdziwy” odbiornik zdolny do nagrywania częstotliwości 77.5 kHz, możesz go użyć.
Oczywiście odbierając radiowe sygnały czasu przez Internet, nie otrzymamy naprawdę dokładnego czasu – sygnał jest nadawany z opóźnieniem. Ale naszym celem jest jedynie zrozumienie struktury sygnału, w tym celu nagranie internetowe jest więcej niż wystarczające. W prawdziwym życiu do odbioru i dekodowania używane są oczywiście specjalistyczne urządzenia, które zostaną omówione poniżej.
Otrzymaliśmy więc nagranie, zacznijmy je przetwarzać.
Dekodowanie sygnału
Załadujmy plik za pomocą Pythona i zobaczmy jego strukturę:
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()
Widzimy typową modulację amplitudy:
Aby uprościć dekodowanie, weźmy obwiednię sygnału za pomocą transformaty Hilberta:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000])
Powiększony wynik:
Wygładźmy emisję szumów za pomocą filtra dolnoprzepustowego, a jednocześnie obliczmy wartość średnią, która przyda się później do analizy.
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
Wynik (żółta linia): sygnał o fali prawie prostokątnej, który jest dość łatwy do analizy.
Rozbiór gramatyczny zdania
Najpierw musisz uzyskać sekwencję bitów. Sama struktura sygnału jest bardzo prosta.
Impulsy są podzielone na sekundy. Jeżeli odległość pomiędzy impulsami wynosi 0.1 s (tj. długość samego impulsu wynosi 0.9 s), do sekwencji bitów należy dodać „0”, jeżeli odległość wynosi 0.2 s (tj. długość wynosi 0.8 s), należy dodać „1”. Koniec każdej minuty sygnalizowany jest „długim” impulsem trwającym 2 s, sekwencja bitów jest resetowana do zera i napełnianie rozpoczyna się od nowa.
Powyższe można łatwo napisać w Pythonie.
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
W rezultacie otrzymujemy ciąg bitów, w naszym przykładzie przez dwie sekundy wygląda to tak:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
Swoją drogą ciekawe, że sygnał ma też „drugą warstwę” danych. Sekwencja bitów jest również kodowana przy użyciu
Nasz ostatni krok: uzyskanie rzeczywistych danych. Bity przesyłane są raz na sekundę, więc mamy w sumie 59 bitów, w których zakodowanych jest całkiem sporo informacji:
Bity są opisane w
Dla tych, którzy chcą poeksperymentować samodzielnie, kod dekodujący podany jest pod spoilerem.
Kod źródłowy
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)
Kiedy uruchomimy program, zobaczymy dane wyjściowe podobne do tego:
0011110110111000001011000001010000100110010101100010011000
Tuesday, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Tuesday, 26.03.19, 21:42
Właściwie to cała magia. Zaletą takiego układu jest to, że dekodowanie jest niezwykle proste i można je wykonać na dowolnym, nawet najprostszym mikrokontrolerze. Po prostu liczymy długość impulsów, gromadzimy 60 bitów i na koniec każdej minuty otrzymujemy dokładny czas. W porównaniu do innych metod synchronizacji czasu (na przykład GPS, czy nie daj Boże Internet :), taka synchronizacja radiowa praktycznie nie wymaga prądu - np. zwykła domowa stacja pogodowa działa około roku na 2 bateriach AA. Dlatego nawet zegarki na rękę są produkowane z synchronizacją radiową, nie mówiąc już o zegarkach ściennych czy zegarkach ulicznych.
Wygoda i prostota DCF przyciągają także miłośników majsterkowania. Już za 10-20 dolarów można kupić gotowy moduł antenowy z gotowym odbiornikiem i wyjściem TTL, który można podłączyć do Arduino lub innego kontrolera.
Już napisany dla Arduino
Chętni mogą nawet ulepszyć zegarek swojej starej babci, instalując nowy mechanizm z synchronizacją radiową:
Można go znaleźć w serwisie eBay, używając słów kluczowych „Ruch sterowany radiowo”.
I na koniec lifehack dla tych, którzy przeczytali tak daleko. Nawet jeśli w promieniu kilku tysięcy kilometrów nie ma ani jednego nadajnika sygnału radiowego, samodzielne wygenerowanie takiego sygnału nie jest trudne. W Google Play dostępny jest program o nazwie „Emulator DCF77”, który wysyła sygnał do słuchawek. Zdaniem autora, jeśli owiniemy przewód słuchawek wokół zegarka, to one odbiorą sygnał (ciekawe jak, bo zwykłe słuchawki nie wygenerują sygnału o częstotliwości 77 kHz, ale za odbiór odpowiadają prawdopodobnie harmoniczne). Na Androidzie 9 program w ogóle mi nie zadziałał - po prostu nie było dźwięku (a może go nie słyszałem - w końcu to 77 KHz:), ale może komuś się poszczęści. Niektórzy jednak robią z siebie pełnoprawny generator sygnału DCF, który łatwo wykonać na tym samym Arduino lub ESP32:
(źródło
wniosek
System DCF okazał się naprawdę prosty i wygodny. Za pomocą prostego i taniego odbiornika możesz zawsze i wszędzie mieć dokładny czas, oczywiście w recepcji. Wydaje się, że nawet pomimo powszechnej cyfryzacji i Internetu Rzeczy na takie proste rozwiązania jeszcze długo będzie zapotrzebowanie.
Źródło: www.habr.com