Hello Habr.
Probably many who purchase a watch or a weather station have seen the Radio Controlled Clock or even Atomic Clock logo on the packaging. This is very convenient, because it is enough to put the clock on the table, and after a while it will automatically adjust to the exact time.
Let's figure out how it works and write a decoder in Python.
There are different time synchronization systems. The most popular in Europe is the German system
Everything written below will be about DCF77.
Signal reception
The DCF77 is a longwave station operating at 77.5KHz and transmitting AM signals. The station with a capacity of 50 kW is located 25 km from Frankfurt, it began work in 1959, in 1973 information about the date was added to the exact time. The wavelength at a frequency of 77 kHz is very large, so the dimensions of the antenna field are also very decent (photo from Wikipedia):
With such an antenna and input power, the reception area covers almost the whole of Europe, Belarus, Ukraine and part of Russia.
Anyone can record. To do this, just go to the online receiver
In the same place, we press the download button and record a fragment a few minutes long. Of course, if you have a "real" receiver capable of recording a frequency of 77.5 kHz, you can use it.
Of course, when receiving accurate time radio signals over the Internet, we will not get a really accurate time - the signal is transmitted with a delay. But our goal is only to understand the structure of the signal, for this the Internet recording is more than enough. In real life, of course, specialized devices are used for receiving and decoding, they will be discussed below.
So, we received the record, let's start processing it.
Signal decoding
Let's load the file with Python and see its structure:
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()
We see a typical amplitude modulation:
To simplify decoding, we take the signal envelope using the Hilbert transform:
analytic_signal = signal.hilbert(data)
A = np.abs(analytic_signal)
plt.plot(A[:100000])
Expanded result:
Let's smooth out noise emissions using a low-pass filter, at the same time calculate the average value, it will come in handy later for 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
Result (yellow line): an almost rectangular signal that is fairly easy to analyze.
Parsing
First you need to get the bit sequence. The signal structure itself is very simple.
The pulses are divided into second intervals. If the distance between pulses is 0.1s (i.e., the length of the pulse itself is 0.9s), we add “0” to the bit sequence, if the distance is 0.2s (i.e., the length is 0.8s), we add “1”. The end of each minute is indicated by a "long" pulse, 2 s long, the bit sequence is reset to zero, and the filling starts again.
The above is easy to write in 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
As a result, we get a sequence of bits, in our example for two seconds it looks like this:
0011110110111000001011000001010000100110010101100010011000
0001111100110110001010100001010000100110010101100010011000
By the way, it is interesting that there is also a “second layer” of data in the signal. The bit sequence is also encoded with
Our last step: get the actual data. Bits are transmitted once per second, so we have only 59 bits, in which quite a lot of information is encoded:
The bits are described in
For those who want to experiment on their own, the decoding code is given under the spoiler.
Source
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)
Running the program, we will see something like this output:
0011110110111000001011000001010000100110010101100010011000
Tuesday, 26.03.19, 21:41
0001111100110110001010100001010000100110010101100010011000
Tuesday, 26.03.19, 21:42
Actually, that's all the magic. The advantage of such a system is that decoding is extremely simple, and can be done on any, the most uncomplicated microcontroller. We just count the length of the pulses, accumulate 60 bits, and at the end of each minute we get the exact time. Compared to other methods of time synchronization (GPS, for example, or God forbid, the Internet :), such radio synchronization practically does not require electricity - for example, an ordinary home weather station works for about a year on 2 AA batteries. Therefore, even wristwatches are made with radio synchronization, not to mention, of course, wall clocks or street station clocks.
The convenience and simplicity of the DCF also attract DIY enthusiasts. For only $10-20, you can buy a ready-made antenna module with a ready-made receiver and a TTL output that can be connected to an Arduino or other controller.
For Arduino already written and
Those who wish can even upgrade the old grandmother's watch by installing a new mechanism with radio synchronization:
You can find one on ebay using the keywords "Radio Controlled Movement".
And finally, a life hack for those who have read this far. Even if there is not a single radio signal transmitter in the next couple of thousand kilometers, such a signal is easy to generate on your own. There is a program on Google Play called "DCF77 Emulator" that outputs a signal to the headphones. According to the author, if you wrap the headphone wire around the clock, they will catch the signal (I wonder how, because ordinary headphones will not give out a 77KHz signal, but probably the reception is due to harmonics). The program didn’t work for me on Android 9 at all - there was simply no sound (or maybe I didn’t hear it - 77KHz, after all :), but maybe someone will be more lucky. Some, however, make themselves a full-fledged DCF signal generator, which is easy to do on the same Arduino or ESP32:
(a source
Conclusion
The DCF system turned out to be really quite simple and convenient. With the help of a simple and cheap receiver, you can have the exact time always and everywhere, of course in the reception area. It seems that even despite the widespread digitalization and the “Internet of Things”, such simple solutions will be in demand for a long time to come.
Source: habr.com