Прывітанне Хабр.
Напэўна многія, якія набываюць гадзіны ці метэастанцыю, бачылі на пакаванні лагатып Radio Controlled Clock ці нават Atomic Clock. Гэта вельмі зручна, бо дастаткова паставіць гадзіннік на стол, і яны праз некаторы час аўтаматычна настрояцца на дакладны час.
Разбярэмся як гэта працуе і напішам дэкодэр на мове Python.
Існуюць розныя сістэмы сінхранізацыі часу. Найбольш папулярная ў Еўропе - нямецкая сістэма
Усё напісанае далей, будзе пра DCF77.
Прыём сігналу
DCF77 гэта даўгахвалевая станцыя, якая працуе на частаце 77.5КГц, і якая перадае сігналы ў амплітуднай мадуляцыі. Станцыя магутнасцю 50КВт размешчана ў 25км ад Франкфурта, яна пачала працу яшчэ ў 1959 годзе, у 1973 да дакладнага часу была дададзена інфармацыя аб даце. Даўжыня хвалі пры частаце 77Кгц вельмі вялікая, таму памеры антэннага поля таксама вельмі прыстойныя (фота з Вікіпедыі):
Пры такой антэне і падводнай магутнасці, зона прыёму ахоплівае практычна ўсю Еўропу, Беларусь, Украіну і частку Расіі.
Запісаць сігнал можа кожны. Для гэтага дастаткова зайсці на анлайн-прымач
Там жа націскаем кнопку download і запісваем фрагмент даўжынёй у некалькі хвілін. Зразумела, пры наяўнасці "сапраўднага" прымача, здольнага запісаць частату 77.5Кгц, можна выкарыстоўваць і яго.
Вядома, прымаючы радыёсігналы дакладнага часу праз Інтэрнэт, мы не атрымаем сапраўды дакладны час - сігнал перадаецца з затрымкай. Але наша мэта толькі зразумець структуру сігналу, для гэтага інтэрнэт-запісы больш за досыць. У рэале вядома, выкарыстоўваюцца спецыялізаваныя прылады для прыёму і дэкадаванні, пра іх будзе сказана ніжэй.
Такім чынам, мы атрымалі запіс, прыступім да яго апрацоўкі.
Дэкадаванне сігналу
Загрузім файл з дапамогай Python і паглядзім яго структуру:
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()
Мы бачым тыповую амплітудную мадуляцыю:
Для спрашчэння дэкадавання возьмем абгінаючую сігналу з дапамогай пераўтварэння Гільберта:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000])
Вынік у павялічаным выглядзе:
Згладзім выкіды ад перашкод з дапамогай low-pass фільтра, заадно вылічым сярэдняе значэнне, яно спатрэбіцца потым для парсінгу.
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 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
У выніку атрымліваем паслядоўнасць біт, у нашым прыкладзе для двух секунд яна выглядае так:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
Дарэчы цікава, што ў сігнале ёсць і "другі пласт" дадзеных. Паслядоўнасць біт таксама закадавана з дапамогай
Наш апошні крок: атрымаць уласна дадзеныя. Біты перадаюцца раз у секунду, так што мы маем усяго 59 біт, у якіх закадзіравана дастаткова шмат інфармацыі:
Біты апісаны ў
Для тых, хто захоча паэксперыментаваць самастойна, код для дэкадавання прыведзены пад спойлерам.
зыходны код
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)
Запусціўшы праграму, мы ўбачым прыкладна такая выснова:
0011110110111000001011000001010000100110010101100010011000
Tuesday, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Tuesday, 26.03.19, 21:42
Уласна, вось і ўся магія. Плюс такой сістэмы ў тым, што дэкадаванне надзвычай простае, і можа быць зроблена на любым, самым нескладаным мікракантролеры. Проста лічым даўжыню імпульсаў, назапашваем 60 біт, і ў канцы кожнай хвіліны атрымліваем дакладны час. У параўнанні з іншымі спосабамі сінхранізацыі часу (GPS напрыклад, ці крый божа, Інтэрнэт:), такая радыёсінхранізацыя практычна не патрабуе электраэнергіі – для прыкладу, звычайная хатняя метэастанцыя працуе каля года ад 2х батарэек АА. Таму з радыёсінхранізацыяй робяць нават наручны гадзіннік, не кажучы ўжо вядома, пра насценныя ці пра вулічныя вакзальныя.
Выгода і прастата DCF прыцягваюць і аматараў самаробак. Усяго за 10-20$ можна купіць гатовы модуль з антэны з гатовым прымачом і TTL-выхадам, які можна падлучыць да Arduino ці іншаму кантролеру.
Для Arduino ужо напісаны і
Жадаючыя могуць нават праапгрэйдзіць старыя бабуліны гадзіны, усталяваўшы ў іх новы механізм з радыёсінхранізацыяй:
Знайсці такі можна на ebay па ключавых словах "Radio Controlled Movement".
І нарэшце, лайфхак для тых хто дачытаў да сюды. Нават калі ў бліжэйшых пары тысяч км няма ніводнага перадатчыка радыёсігналу, такі сігнал нескладана згенераваць самастойна. У Google Play ёсць праграма з назвай "DCF77 Emulator", якая выводзіць сігнал на навушнікі. Па запэўненнях аўтара, калі абматаць провад навушнікаў вакол гадзін, яны зловяць сігнал (цікава як, бо звычайныя навушнікі не выдадуць сігнал 77КГц, але верагодна прыём ідзе за кошт гарамонік). У мяне на Android 9 праграма не зарабіла зусім - проста не было гуку (а можа я яго не чуў - 77КГц бо:), але можа камусьці павязе больш. Некаторыя зрэшты, робяць сабе і паўнавартасны генератар сігналаў DCF, які нескладана зрабіць на той жа Arduino ці ESP32:
(крыніца
Заключэнне
Сістэма DCF, аказалася сапраўды цалкам просты і зручнай. З дапамогай нескладанага і таннага прымача можна мець дакладны час заўсёды і ўсюды, зразумела ў зоне прыёму. Думаецца, нават нягледзячы на паўсюдную цыфравізацыі і "інтэрнэт рэчаў", такія простыя рашэнні будуць запатрабаваны яшчэ доўга.
Крыніца: habr.com