Hallo Habr.
Velen die een klok of een weerstation kopen, hebben waarschijnlijk het logo Radio Controlled Clock of zelfs Atomic Clock op de verpakking gezien. Dit is erg handig, want je hoeft de klok alleen maar op tafel te zetten, en na een tijdje stelt hij zichzelf automatisch in op de exacte tijd.

Laten we bekijken hoe dit werkt en een decoder schrijven in Python.
Er zijn verschillende tijdsynchronisatiesystemen. Het meest populaire in Europa is het Duitse systeem , in Japan is er een eigen systeem , in de VS is er een systeem , enzovoort. Het volgende verhaal gaat over DCF77, als het meest actuele en beschikbare systeem voor ontvangst in bepaalde delen van het Europese deel van Rusland en aangrenzende landen (bewoners van het Verre Oosten hebben misschien een andere mening, maar zij kunnen op hun beurt ook het Japanse signaal ontvangen en analyseren;).
Alles wat hierna wordt besproken, gaat over DCF77.
Ontvangst van het signaal
DCF77 is een lange golfstation dat werkt op een frequentie van 77.5 kHz en signalen in amplitude-modulatie verstuurt. Het station met een vermogen van 50 kW bevindt zich op 25 km van Frankfurt en begon zijn werkzaamheden al in 1959. In 1973 werd er informatie over de datum aan de exacte tijd toegevoegd. De golflengte bij een frequentie van 77 kHz is vrij groot, waardoor de afmetingen van het antenneveld ook aanzienlijk zijn (foto van Wikipedia):

Met zo'n antenne en aangevoerde kracht beslaat het ontvangstgebied vrijwel heel Europa, Wit-Rusland, OekraĆÆne en een deel van Rusland.

Iedereen kan het signaal opnemen. Hiervoor is het voldoende om naar een online ontvanger te gaan , daar de frequentie 76.5 kHz en USB-modulatie te kiezen. Er zou een afbeelding moeten verschijnen die er ongeveer zo uitziet:

Daar klikken we op de knop download en nemen we een fragment op van enkele minuten. Natuurlijk, met een "echte" ontvanger die in staat is om de frequentie van 77.5 kHz op te nemen, kan die ook worden gebruikt.
Natuurlijk, als we radiosignalen van exacte tijd via het internet ontvangen, krijgen we niet echt exact de tijd ā het signaal wordt met vertraging verzonden. Maar ons doel is alleen de structuur van het signaal te begrijpen, hiervoor zijn internetopnames meer dan voldoende. In de praktijk worden uiteraard gespecialiseerde apparaten gebruikt voor ontvangst en decodering, waarover later meer zal worden verteld.
Dus, we hebben de opname, laten we beginnen met de verwerking.
Decodering van het signaal
Laten we het bestand openen met Python en naar de structuur ervan kijken:
van scipy.io import wavfile
van 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()
We zien typische amplitude modulatie:

Om de decodering te vereenvoudigen, nemen we de enveloppe van het signaal met behulp van de Hilbert-transformatie:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000]) Het resultaat in vergroot formaat:

We filteren de uitbijters door een low-pass filter te gebruiken en berekenen tegelijkertijd het gemiddelde, dat later nuttig zal zijn voor de 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 Het resultaat (gele lijn): praktisch een rechthoekig signaal, dat relatief eenvoudig te analyseren is.

Parsing
Allereerst moeten we de bitreeks verkrijgen. De structuur van het signaal is heel eenvoudig.

De pulsen zijn verdeeld over seconden. Als de afstand tussen de pulsen 0,1 s bedraagt (d.w.z. de lengte van de puls 0,9 s), voegen we een '0' toe aan de bitreeks; als de afstand 0,2 s is (d.w.z. de lengte 0,8 s), voegen we een '1' toe. Het einde van elke minuut wordt gemarkeerd met een 'lange' puls van 2 s, en de bitreeks wordt gereset, waarna de telling opnieuw begint.
Het bovenstaande is niet moeilijk te coderen in Python.
sig_start, sig_stop = 0, 0
pos = 0
bits_str = ""
while pos < cnt - 4:
if A[pos] avg:
# Begin signaal
sig_start = pos
if A[pos] > avg and A[pos+1] < avg:
# Einde signaal
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
Als resultaat krijgen we een reeks bits, in ons voorbeeld van twee seconden wordt het als volgt weergegeven:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
Overigens is het interessant dat er ook een 'tweede laag' van gegevens in het signaal zit. De bitreeks is ook gecodeerd met behulp van . Theoretisch zou dit een meer robuuste decodering moeten garanderen, zelfs bij een verzwakt signaal.
Onze laatste stap: de eigenlijke gegevens verkrijgen. De bits worden ƩƩn keer per seconde verzonden, waardoor we in totaal 59 bits hebben die genoeg informatie coderen:

De bits worden beschreven in , en ze zijn vrij interessant. De eerste 15 bits worden niet gebruikt, hoewel er plannen waren om ze te gebruiken voor waarschuwingssystemen en civiele verdediging. Bit A1 geeft aan dat de klok in het volgende uur op zomer tijd zal worden gezet. Bit A2 geeft aan dat er in het volgende uur een , die soms wordt gebruikt om de tijd aan te passen aan de rotatie van de aarde. De overige bits coderen uren, minuten, seconden en de datum.

Voor degenen die zelf willen experimenteren, is de code voor het decoderen hieronder in de spoiler.
Broncode
def decode(bits):
if bits[0] != '0' or bits[20] != '1':
return
minuten, uren, dag_van_de_maand, weekdag, maand, jaar = map(convert_block,
(bits[21:28], bits[29:35], bits[36:42], bits[42:45],
bits[45:50], bits[50:58]))
dagen = ('Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag')
print('{dow}, {dom:02}.{mon:02}.{y}, {h:02}:{m:02}'.format(h=uren, m=minuten, dow=dagen[weekdag],
dom=dag_van_de_maand, mon=maand, y=jaar))
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)
Als we het programma uitvoeren, zullen we ongeveer deze output zien:
0011110110111000001011000001010000100110010101100010011000
Dinsdag, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Dinsdag, 26.03.19, 21:42
Dat is eigenlijk de hele magie. Het voordeel van dit systeem is dat het decoderen uiterst eenvoudig is, en kan worden gedaan op elke, zelfs de simpelste microcontroller. We tellen gewoon de duur van de pulsen, accumuleren 60 bits, en aan het einde van elke minuut krijgen we de exacte tijd. In vergelijking met andere methoden voor tijdsynchronisatie (bijvoorbeeld GPS, of erger nog, het internet :), vereist deze radiosynchronisatie praktisch geen energie ā ter vergelijking, een gewone huishoudelijke weerstation werkt ongeveer een jaar op 2 AA-batterijen. Daarom worden zelfs polshorloges gemaakt met radiosynchronisatie, om nog maar te zwijgen van wand- of straatklokken.
Het gemak en de eenvoud van DCF trekt ook doe-het-zelfliefhebbers aan. Voor slechts 10-20$ kan je een kant-en-klaar module kopen met een antenne en een kant-en-klaar ontvanger met TTL-uitgang, die kan worden aangesloten op een Arduino of een andere controller.

Voor Arduino zijn al geschreven en . Het is echter al bekend ā wat je ook doet met een microcontroller, het resultaat is altijd of een klok of een weerstation. Met zo'n apparaat is het echt niet moeilijk om de exacte tijd te krijgen, mits je je binnen het ontvangstgebied bevindt. En je kunt zelfs de klok de opschrift "Atomic Clock" geven, en uitleggen aan iedereen die het wil horen dat het apparaat daadwerkelijk gesynchroniseerd wordt met behulp van atoomklokken.
GeĆÆnteresseerden kunnen zelfs de oude klok van oma upgraden door een nieuw mechanisme met radiosynchronisatie te installeren:

Zo'n mechanisme is te vinden op ebay met de zoekwoorden "Radio Controlled Movement".
En tot slot, een hack voor degenen die tot hier hebben gelezen. Zelfs als er in de komende paar duizend kilometer geen enkele radiosignaalzender in de buurt is, is het niet moeilijk om zo'n signaal zelf te genereren. In de Google Play Store is er een programma genaamd "DCF77 Emulator" dat een signaal naar de hoofdtelefoon stuurt. Volgens de schrijver, als je de draad van de hoofdtelefoon om de klok wikkelt, zullen ze het signaal opvangen (interessant hoe, want gewone hoofdtelefoons zullen geen 77 kHz signaal uitzenden, maar waarschijnlijk komt de ontvangst door harmonischen). Ik kon het programma op mijn Android 9 helemaal niet laten werken ā er was gewoon geen geluid (of misschien hoorde ik het niet ā 77 kHz is immers:), maar misschien heeft iemand anders meer geluk. Sommigen maken trouwens ook een volledige DCF-signaalgenerator, die je eenvoudig kunt maken met dezelfde Arduino of ESP32:

(bron )
Conclusie
Het DCF-systeem bleek echt vrij eenvoudig en handig te zijn. Met behulp van een eenvoudige en goedkope ontvanger heb je altijd en overal de exacte tijd, uiteraard in het ontvangstgebied. Ik denk dat, zelfs ondanks de wijdverspreide digitalisering en het "internet der dingen", zulke eenvoudige oplossingen nog lange tijd gewild blijven.
Bron: habr.com
