Здравей, Хабр.
Вероятно, много от тех, кто покупает часы или метеостанцию, видели на упаковке логотип Radio Controlled Clock или даже Atomic Clock. Это очень удобно, ведь достаточно поставить часы на стол, и они через некоторое время автоматически настроятся на точное время.

Давайте разберемся, как это работает, и напишем декодер на языке Python.
Существуют разные системы синхронизации времени. Наиболее популярная в Европе — немецкая система , в Японии есть своя система , в США есть система , и так далее. В дальнейшем речь пойдет о DCF77, как о наиболее практичной и доступной для приема в некоторых местах европейской части России и соседних странах (у жителей Дальнего Востока может быть другое мнение, но они, в свою очередь, могут принимать и анализировать японский сигнал;).
Все, что будет написано далее, касается DCF77.
Прием сигнала
DCF77 — это длинноволновая станция, которая работает на частоте 77.5КГц и передает сигналы в амплитудной модуляции. Станция мощностью 50КВт расположена в 25км от Франкфурта и начала свою работу еще в 1959 году, в 1973 году к точному времени была добавлена информация о дате. Длина волны при частоте 77КГц довольно большая, поэтому размеры антенны также весьма значительные (фото из Википедии):

С такой антенной и мощностью, зона приема охватывает практически всю Европу, Белоруссию, Украину и часть России.

Записать сигнал может каждый. Для этого достаточно зайти на онлайн-приемник , выбрать там частоту 76.5КГц и USB-модуляцию. Должна открыться картинка примерно типа такой:

Там же нажимаем кнопку download и записываем фрагмент длиной в несколько минут. Разумеется, при наличии «настоящего» приемника, способного записать частоту 77.5КГц, можно использовать и его.
Конечно, принимая радиосигналы точного времени через Интернет, мы не получим действительно точное время — сигнал передается с задержкой. Но наша цель — лишь понять структуру сигнала, для этого интернет-записи более чем достаточно. На самом деле, конечно, используются специализированные устройства для приема и декодирования, о них будет сказано ниже.
Итак, мы получили запись, приступим к ее обработке.
Декодирование сигнала
Загрузим файл с помощью Python и посмотрим его структуру:
от scipy.io import wavfile
от scipy import signal
импортировать matplotlib.pyplot как plt
импортировать numpy как np
sample_rate, data = wavfile.read("dcf_websdr_2019-03-26T20_25_34Z_76.6kHz.wav")
plt.plot(data[:100000])
plt.show()
Виждаме типична амплитудна модулация:

За опростяване на декодирането ще вземем огивката на сигнала с помощта на преобразуването на Хилберт:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000]) Резултатът в увеличен вид:

Ще изгладим изблиците от смущенията с помощта на нискочестотен филтър и едновременно ще изчислим средната стойност, която ще ни е полезна по-късно за парсирането.
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 Резултат (жълта линия): практически правоъгълен сигнал, който е доста лесен за анализ.

Парсиране
Първо трябва да получим битова последователност. Самата структура на сигнала е много проста.

Импулсите са разделени на секундни интервали. Ако разстоянието между импулсите е 0.1с (т.е. дължината на самия импулс е 0.9с), добавяме "0" към битовата последователност, ако разстоянието е 0.2с (т.е. дължината е 0.8с), добавяме "1". Краят на всяка минута се обозначава с "дълъг" импулс с дължина 2с, битовата последователност при това се нулира и запълването започва отново.
По-гореописаното не е сложно да се запише на Python.
sig_start, sig_stop = 0, 0
pos = 0
bits_str = ""
while pos < cnt - 4:
if A[pos] avg:
# Начало на сигнала
sig_start = pos
if A[pos] > avg and A[pos+1] < avg:
# Край на сигнала
sig_stop = pos
diff = sig_stop - sig_start
if diff 0.85*sample_rate and diff 1.5*sample_rate:
print(bits_str)
bits_str = ""
pos += 1
В резултат получаваме последователност от битове, в нашия пример за два секунди тя изглежда така:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
Интересно е, че в сигнала има и "втори слой" данни. Последователността от битове също е кодирана с помощта на . Теоретично това трябва да осигури по-устойчиво декодиране дори в случай на отслабен сигнал.
Нашата последна стъпка: да получим самите данни. Битовете се предават веднъж в секунда, така че имаме само 59 бита, в които е кодирана доста информация:

Битовете са описани в , и те са доста любопитни. Първите 15 бита не се използват, въпреки че имаше планове да се използват за системи за предупреждение и гражданска защита. Бит A1 указва, че през следващия час часовниците ще бъдат преместени на лятно време. Бит A2 указва, че през следващия час ще бъде добавена , която понякога се използва за корекция на времето в съответствие с въртенето на Земята. Останалите битове кодирани часовете, минутите, секундите и датата.

За тези, които искат да експериментират сами, кодът за декодиране е представен под спойлера.
Изходен код
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 = ('Неделя', 'Понеделник', 'Вторник', 'Сряда', 'Четвъртък', 'Петък', 'Събота', 'Неделя')
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)
След стартиране на програмата, ще видим приблизително такъв изход:
0011110110111000001011000001010000100110010101100010011000
Вторник, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Вторник, 26.03.19, 21:42
Всъщност, ето я цялата магия. Предимството на тази система е, че декодирането е изключително простичко и може да се направи на всякакъв, дори и най-прост микроконтролер. Просто измерваме дължината на импулсите, натрупваме 60 бита и в края на всяка минута получаваме точно време. В сравнение с други методи за синхронизация на времето (например GPS, или не дай боже, Интернет:), тази радиосинхронизация практически не изисква електрическа енергия — за пример, обикновена домашна метеостанция работи около година с 2 батерии АА. Затова с радиосинхронизация се правят дори часовници, да не споменаваме, разбира се, настенни или за улични вокзали.
Удобството и простотата на DCF привлекат и любителите на самоделки. Само за 10-20$ можете да купите готов модул с антенка и готов приемник с TTL-изход, който може да бъде свързан към Arduino или друг контролер.

Вече са написани и . Впрочем, известно, че каквото и да правим на микроконтролера, получаваме или часовник, или метеостанция. С такова устройство получаването на точно време наистина е лесно, разбира се, ако се намираме в зоната на приемане. И може да сложим надпис «Atomic Clock» на часовника, за да обясняваме на всеки, че устройството наистина се синхронизира с атомни часовници.
Желаещите могат дори да ъпгрейднат старинните часовници на баба си, като инсталират нов механизъм с радиосинхронизация:

Такъв може да се намери на eBay с ключови думи «Radio Controlled Movement».
И накрая, полезен съвет за тези, които стигнаха дотук. Дори ако в близките хиляда километра няма нито един предавател на радиосигнал, такъв сигнал не е трудно да се генерира самостоятелно. В Google Play има програма с наименование «DCF77 Emulator», която излъчва сигнал към слушалките. Според автора, ако обвием кабела на слушалките около часовника, те ще уловят сигнала (интересно е как, защото обикновените слушалки не могат да предадат сигнал 77КГц, но вероятно приемането става чрез гармоници). На моя Android 9 програмата изобщо не проработи — просто нямаше звук (или може би не го чух — 77КГц е:), но може би на някого ще му повезе повече. Някои обаче си правят и пълноценен генератор на DCF сигнали, който не е трудно да се направи и с Arduino или ESP32:

(източник )
Заключение
Системата DCF се оказа наистина сравнително простичка и удобна. С помощта на прост и евтин приемник можем да имаме точно време навсякъде, разбира се, в зоната на приемане. Смятам, че дори и при пълната цифровизация и «интернет на нещата», такива прости решения ще бъдат търсени още дълго.
Източник: habr.com
