Ciao Habr.
Probabilmente molti, acquistando un orologio o una stazione meteorologica, hanno visto sulla confezione il logo Radio Controlled Clock o persino Atomic Clock. È molto comodo, poiché basta posizionare l'orologio sulla scrivania e, dopo un po', si sincronizzerà automaticamente con l'ora esatta.

Analizziamo come funziona e scriviamo un decoder in Python.
Esistono diversi sistemi di sincronizzazione dell'ora. Il più popolare in Europa è il sistema tedesco , in Giappone c'è il sistema , negli Stati Uniti esiste il sistema , e così via. La storia che seguirà riguarda DCF77, essendo il più rilevante e facilmente ricepito in alcune aree della parte europea della Russia e nei paesi limitrofi (gli abitanti del Far East potrebbero avere un'opinione diversa, ma possono comunque ricevere e analizzare il segnale giapponese;).
Tutto ciò che segue riguarderà DCF77.
Ricezione del segnale
DCF77 è una stazione di onde lunghe che opera alla frequenza di 77,5 kHz, trasmettendo segnali in modulazione di ampiezza. La stazione ha una potenza di 50 kW e si trova a 25 km da Francoforte; è in funzione dal 1959, e nel 1973 è stata aggiunta l'informazione sulla data all'orario esatto. La lunghezza d'onda a 77 kHz è piuttosto grande, quindi anche le dimensioni del campo dell'antenna sono notevoli (foto da Wikipedia):

Con questa antenna e la potenza fornita, la zona di ricezione copre praticamente tutta l'Europa, Bielorussia, Ucraina e parte della Russia.

Chiunque può registrare il segnale. Basta accedere a un ricevitore online , selezionare la frequenza di 76,5 kHz e la modulazione USB. Dovrebbe aprirsi un'immagine simile a questa:

Lì, premiamo il pulsante di download e registriamo un frammento lungo alcuni minuti. Naturalmente, se si dispone di un ricevitore "reale" in grado di registrare la frequenza di 77,5 kHz, si può utilizzare anche quello.
Certo, ricevendo segnali radio di orario preciso tramite Internet, non otterremo un orario davvero preciso: il segnale viene trasmesso con un certo ritardo. Tuttavia, il nostro obiettivo è solo quello di comprendere la struttura del segnale, e a tale scopo le registrazioni internet sono più che sufficienti. In realtà, ovviamente, si utilizzano dispositivi specializzati per la ricezione e la decodifica, di cui parleremo più avanti.
Dunque, abbiamo ottenuto la registrazione, passiamo ora alla sua elaborazione.
Decodifica del segnale
Carichiamo il file con Python e osserviamo la sua struttura:
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()
Vediamo una tipica modulazione di ampiezza:

Per semplificare la decodifica, prendiamo l'inviluppo del segnale usando la trasformata di Hilbert:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000]) Il risultato ingrandito:

Attenueremo i picchi di disturbo usando un filtro passa-basso e calcoleremo anche il valore medio, che ci servirà poi per il parsing.
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 Risultato (linea gialla): un segnale praticamente rettangolare, che è piuttosto facile da analizzare.

Parsing
Per prima cosa, è necessario ottenere la sequenza di bit. La struttura del segnale è molto semplice.

Gli impulsi sono suddivisi in intervalli di un secondo. Se la distanza tra gli impulsi è di 0,1 s (cioè la lunghezza dell'impulso stesso è di 0,9 s), aggiungiamo un «0» alla sequenza di bit; se la distanza è di 0,2 s (cioè la lunghezza è di 0,8 s), aggiungiamo un «1». La fine di ogni minuto è segnata da un impulso «lungo» di 2 s, e la sequenza di bit viene azzerata, iniziando di nuovo il riempimento.
Quanto scritto sopra non è difficile da scrivere in linguaggio Python.
sig_start, sig_stop = 0, 0
pos = 0
bits_str = ""
while pos < cnt - 4:
if A[pos] avg:
# Inizio segnale
sig_start = pos
if A[pos] > avg and A[pos+1] < avg:
# Fine segnale
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
Il risultato è una sequenza di bit, nel nostro esempio per due secondi si presenta così:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
È interessante notare che nel segnale è presente un "secondo strato" di dati. La sequenza di bit è anch'essa codificata tramite . Teoricamente, questo dovrebbe garantire una decodifica più resistente anche in caso di segnale debole.
Il nostro passo finale: ottenere i dati propriamente detti. I bit vengono trasmessi una volta al secondo, quindi abbiamo solo 59 bit, in cui è codificata una quantità significativa di informazioni:

I bit sono descritti in , e sono piuttosto curiosi. I primi 15 bit non vengono utilizzati, nonostante ci fossero piani per usarli per i sistemi di allerta e per la difesa civile. Il bit A1 indica che nella prossima ora gli orologi verranno spostati avanti per l'ora legale. Il bit A2 indica che nella prossima ora verrà aggiunta , che a volte viene utilizzata per correggere il tempo in base alla rotazione della Terra. Gli altri bit codificano ore, minuti, secondi e data.

Per chi desidera sperimentare autonomamente, il codice per la decodifica è fornito sotto spoiler.
Codice sorgente
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 = ('Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica')
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)
Eseguendo il programma, vedremo un output simile a questo:
0011110110111000001011000001010000100110010101100010011000
Martedì, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Martedì, 26.03.19, 21:42
Ecco quindi tutta la magia. Il punto di forza di questo sistema è che il decoding è estremamente semplice e può essere eseguito su qualsiasi microcontrollore, anche il più semplice. Basta contare la lunghezza degli impulsi, accumulare 60 bit e alla fine di ogni minuto otteniamo l'ora esatta. Rispetto ad altri metodi di sincronizzazione temporale (come il GPS o, per carità, Internet:), questa sincronizzazione radio richiede praticamente poca energia — per esempio, una normale stazione meteorologica domestica funziona per circa un anno con due batterie AA. Pertanto, la sincronizzazione radio viene utilizzata anche per orologi da polso, senza contare, ovviamente, gli orologi da parete o quelli di stazione all'aperto.
La comodità e la semplicità del DCF attirano anche gli appassionati del fai-da-te. Per soli 10-20 € è possibile acquistare un modulo pronto con un'antenna e un ricevitore già assemblato con uscita TTL, che può essere collegato ad Arduino o ad un altro controllore.

Già sono state scritte . Tuttavia, è risaputo che, qualunque cosa si faccia con un microcontrollore, si ottiene o un orologio o una stazione meteorologica. Con un dispositivo del genere è davvero facile ottenere l'ora esatta, a patto di trovarsi nella zona di ricezione. E si può anche appendere una scritta sull'orologio «Atomic Clock» e spiegare a chiunque sia interessato che il dispositivo si sincronizza effettivamente tramite orologi atomici.
Coloro che lo desiderano possono persino aggiornare i vecchi orologi della nonna, installando al loro interno un nuovo meccanismo con sincronizzazione radio:

Si può trovare uno di questi su ebay cercando le parole chiave «Radio Controlled Movement».
E infine, un consiglio per chi è arrivato fino a qui. Anche se nei prossimi migliaia di chilometri non ci sono trasmettitori di segnali radio, è possibile generare un segnale autonomamente. Su Google Play esiste un'app chiamata "DCF77 Emulator" che emette un segnale attraverso le cuffie. Secondo l'autore, avvolgendo il cavo delle cuffie attorno a un orologio, riescono a ricevere il segnale (è interessante capire come, dato che le cuffie normali non possono emettere un segnale a 77 kHz, ma probabilmente la ricezione avviene grazie agli armonici). Sul mio Android 9 l'app non ha funzionato affatto: non si sentiva alcun suono (o forse non lo sentivo io — 77 kHz, appunto:), ma magari a qualcun altro andrà meglio. Alcuni, comunque, si costruiscono un generatore di segnali DCF completo, che non è difficile realizzare con la stessa Arduino o ESP32.

(fonte )
Conclusione
Il sistema DCF si è rivelato davvero semplice e conveniente. Con un ricevitore economico e non complicato, è possibile avere l'orario esatto sempre e ovunque, ovviamente nell'area di ricezione. Si pensa che, anche nonostante la digitalizzazione ubiquitaria e l'"internet delle cose", queste soluzioni semplici saranno richieste ancora a lungo.
Fonte: habr.com
