Привет Хабр.
Наверное, многие, кто покупает часы или метеостанцию, видели на упаковке логотип «Радиоуправляемые часы» или даже «Атомные часы».
Это очень удобно, ведь нужно просто положить часы на стол, и через некоторое время они автоматически настроятся на точное время.
Давайте разберемся, как это работает, и напишем декодер на Python.
Существуют разные системы синхронизации времени.
Наиболее популярной в Европе является немецкая система.
DCF-77 , в Японии своя система ДЖЖИ , в США есть система WWVB , и так далее.
Далее речь пойдет о DCF77, как о наиболее актуальном и доступном для приема в некоторых местах европейской части России и странах ближнего зарубежья (жители Дальнего Востока могут иметь противоположное мнение, однако они, в свою очередь, могут получить и проанализировать японский сигнал ;).
Все написанное ниже будет про DCF77.
Прием сигнала
DCF77 — длинноволновая станция, работающая на частоте 77,5 кГц и передающая сигналы в амплитудной модуляции.Станция мощностью 50 КВт расположена в 25 км от Франкфурта, она начала работу в 1959 году, а в 1973 году к точному времени добавилась информация о дате.
Длина волны на частоте 77 КГц очень большая, поэтому размеры антенного поля тоже вполне приличные (фото из Википедии):
При такой антенне и входной мощности зона приема покрывает практически всю Европу, Беларусь, Украину и часть России.
Любой может записать сигнал.
Для этого достаточно зайти в онлайн-приемник http://websdr.ewi.utwente.nl:8901/ , выберите там частоту 76,5КГц и USB-модуляцию.
Должно открыться изображение, которое выглядит примерно так:
Там нажимаем кнопку скачать и записываем фрагмент длиной в несколько минут. Конечно, если у вас есть «настоящий» приемник, способный записывать частоту 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])
Увеличенный результат:
Сгладим шумовые выбросы с помощью фильтра нижних частот, а заодно посчитаем среднее значение, которое пригодится в дальнейшем для разбора.
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 бит, в которых закодировано довольно много информации:
Биты описаны в Википедия , и они весьма любопытны.
Первые 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 = ('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 также привлекают любителей DIY. Всего за $10-20 можно купить готовый антенный модуль с готовым приемником и ТТЛ-выходом, который можно подключить к Arduino или другому контроллеру.
Уже написано для Arduino. готовые библиотеки .
Однако уже известно, что бы вы ни делали на микроконтроллере, в итоге вы получите либо часы, либо метеостанцию.
С таким устройством узнать точное время действительно легко, при условии, конечно, что вы находитесь в приемной.
Ну а можно повесить на часы надпись «Атомные часы», и заодно объяснить всем, что устройство действительно синхронизируется с помощью атомных часов.
Желающие могут даже обновить свои старые бабушкины часы, установив новый механизм с радиосинхронизацией:
Вы можете найти его на eBay, используя ключевые слова «Радиоуправляемое движение».
И напоследок лайфхак для тех, кто дочитал до этого места.
Даже если в ближайшие пару тысяч км не будет ни одного передатчика радиосигнала, сгенерировать такой сигнал самостоятельно не составит труда.
В Google Play есть программа «DCF77 Emulator», которая выводит сигнал на наушники.
По словам автора, если обернуть провод наушников вокруг часов, то они поймают сигнал (интересно как, ведь обычные наушники не будут выдавать сигнал 77КГц, а прием, вероятно, происходит за счет гармоник).
На Android 9 у меня программа вообще не заработала - звука просто не было (а может я его и не слышал - ведь 77КГц :), но может кому-то повезет больше.
Некоторые, правда, делают из себя полноценный генератор сигналов DCF, который легко сделать на том же Arduino или ESP32:
(источник sgfantasytoys.wordpress.com/2015/05/13/synchronize-radio-controlled-watch-without-access )
Заключение
Система DCF оказалась действительно достаточно простой и удобной.С помощью простого и дешевого приемника вы сможете всегда и везде знать точное время, конечно же в зоне ресепшн.
Думается, что даже несмотря на повсеместную цифровизацию и Интернет вещей, такие простые решения еще долго будут востребованы.
Теги: #python #Алгоритмы #программирование #Инженерные системы #стандарты связи #радио #часы #часы #синхронизация времени #DCF77
-
Создание Warcraft (Часть 1)
19 Oct, 24 -
Обзор Зарплат Тестировщиков
19 Oct, 24 -
«Просконс» — Выбираем Электронику
19 Oct, 24 -
Страсть К Программированию
19 Oct, 24