ΠΠ΄ΡΠ°Π²ΠΎ Π₯Π°Π±Ρ. ΠΠ΅ΡΠΎΡΠ°ΡΠ½ΠΎ ΡΠ΅ΠΊΠΎΡ ΡΡΠΎ Π½Π΅ΠΊΠΎΠ³Π°Ρ ΡΡΠ΅ΡΠ½Π°Π» ΠΈΠ»ΠΈ ΠΈΡΠΏΡΠ°ΡΠΈΠ» ΡΠΎΠ΄Π½ΠΈΠ½ΠΈ ΠΈΠ»ΠΈ ΠΏΡΠΈΡΠ°ΡΠ΅Π»ΠΈ Π²ΠΎ Π°Π²ΠΈΠΎΠ½, ΡΠ° ΠΊΠΎΡΠΈΡΡΠ΅Π» Π±Π΅ΡΠΏΠ»Π°ΡΠ½Π°ΡΠ° ΡΡΠ»ΡΠ³Π° Flightradar24. ΠΠ²Π° Π΅ ΠΌΠ½ΠΎΠ³Ρ ΡΠ΄ΠΎΠ±Π΅Π½ Π½Π°ΡΠΈΠ½ Π·Π° ΡΠ»Π΅Π΄Π΅ΡΠ΅ Π½Π° ΠΏΠΎΠ·ΠΈΡΠΈΡΠ°ΡΠ° Π½Π° Π°Π²ΠΈΠΎΠ½ΠΎΡ Π²ΠΎ ΡΠ΅Π°Π»Π½ΠΎ Π²ΡΠ΅ΠΌΠ΅.
Π
ΠΡΠΈΠΊΠ°Π·Π½Π°
ΠΡΠΈΠ³Π»Π΅Π΄Π½ΠΎ, ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈΡΠ΅ Π·Π° Π°Π²ΠΈΠΎΠ½ΠΈΡΠ΅ Π½Π΅ ΡΠ΅ ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π°Π°Ρ Π·Π° ΠΊΠΎΡΠΈΡΠ½ΠΈΡΠΈΡΠ΅ Π΄Π° Π³ΠΈ Π²ΠΈΠ΄Π°Ρ Π½Π° Π½ΠΈΠ²Π½ΠΈΡΠ΅ ΠΏΠ°ΠΌΠ΅ΡΠ½ΠΈ ΡΠ΅Π»Π΅ΡΠΎΠ½ΠΈ. Π‘ΠΈΡΡΠ΅ΠΌΠΎΡ ΡΠ΅ Π½Π°ΡΠ΅ΠΊΡΠ²Π° ADS-B (Automatic dependent surveillance-broadcast) ΠΈ ΡΠ΅ ΠΊΠΎΡΠΈΡΡΠΈ Π·Π° Π°Π²ΡΠΎΠΌΠ°ΡΡΠΊΠΎ ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π°ΡΠ΅ Π½Π° ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ Π·Π° Π°Π²ΠΈΠΎΠ½ΠΎΡ Π΄ΠΎ ΠΊΠΎΠ½ΡΡΠΎΠ»Π½ΠΈΠΎΡ ΡΠ΅Π½ΡΠ°Ρ - ΡΠ΅ ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π°Π°Ρ Π½Π΅Π³ΠΎΠ²ΠΈΠΎΡ ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ, ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΠΈΡΠ΅, Π½Π°ΡΠΎΠΊΠ°ΡΠ°, Π±ΡΠ·ΠΈΠ½Π°ΡΠ°, Π½Π°Π΄ΠΌΠΎΡΡΠΊΠ°ΡΠ° Π²ΠΈΡΠΎΡΠΈΠ½Π° ΠΈ Π΄ΡΡΠ³ΠΈ ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈ. ΠΡΠ΅ΡΡ ΠΎΠ΄Π½ΠΎ, ΠΏΡΠ΅Π΄ ΠΏΠΎΡΠ°Π²Π°ΡΠ° Π½Π° ΡΠ°ΠΊΠ²ΠΈ ΡΠΈΡΡΠ΅ΠΌΠΈ, Π΄ΠΈΡΠΏΠ΅ΡΠ΅ΡΠΎΡ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π΄Π° Π²ΠΈΠ΄ΠΈ ΡΠ°ΠΌΠΎ ΡΠΎΡΠΊΠ° Π½Π° ΡΠ°Π΄Π°ΡΠΎΡ. ΠΠ²Π° Π²Π΅ΡΠ΅ Π½Π΅ Π±Π΅ΡΠ΅ Π΄ΠΎΠ²ΠΎΠ»Π½ΠΎ ΠΊΠΎΠ³Π° ΠΈΠΌΠ°ΡΠ΅ ΠΏΡΠ΅ΠΌΠ½ΠΎΠ³Ρ Π°Π²ΠΈΠΎΠ½ΠΈ.
Π’Π΅Ρ Π½ΠΈΡΠΊΠΈ, ADS-B ΡΠ΅ ΡΠΎΡΡΠΎΠΈ ΠΎΠ΄ ΠΏΡΠ΅Π΄Π°Π²Π°ΡΠ΅Π» Π½Π° Π°Π²ΠΈΠΎΠ½ ΠΊΠΎΡ ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ½ΠΎ ΠΈΡΠΏΡΠ°ΡΠ° ΠΏΠ°ΠΊΠ΅ΡΠΈ ΡΠΎ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ Π½Π° ΠΏΡΠΈΠ»ΠΈΡΠ½ΠΎ Π²ΠΈΡΠΎΠΊΠ° ΡΡΠ΅ΠΊΠ²Π΅Π½ΡΠΈΡΠ° ΠΎΠ΄ 1090 MHz (ΠΈΠΌΠ° ΠΈ Π΄ΡΡΠ³ΠΈ ΡΠ΅ΠΆΠΈΠΌΠΈ, Π½ΠΎ Π½ΠΈΠ΅ Π½Π΅ ΡΠΌΠ΅ ΡΠΎΠ»ΠΊΡ Π·Π°ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΈΡΠ°Π½ΠΈ Π·Π° Π½ΠΈΠ², Π±ΠΈΠ΄Π΅ΡΡΠΈ ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΠΈΡΠ΅ ΡΠ΅ ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π°Π°Ρ ΡΠ°ΠΌΠΎ ΠΎΠ²Π΄Π΅). Π‘Π΅ΠΊΠ°ΠΊΠΎ, ΠΏΠΎΠΊΡΠ°Ρ ΠΏΡΠ΅Π΄Π°Π²Π°ΡΠ΅Π»ΠΎΡ, Π½Π΅ΠΊΠ°Π΄Π΅ Π½Π° Π°Π΅ΡΠΎΠ΄ΡΠΎΠΌΠΎΡ ΠΈΠΌΠ° ΠΈ ΡΠ΅ΡΠΈΠ²Π΅Ρ, Π½ΠΎ Π·Π° Π½Π°Ρ ΠΊΠ°ΠΊΠΎ ΠΊΠΎΡΠΈΡΠ½ΠΈΡΠΈ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ΅Π½ Π΅ Π½Π°ΡΠΈΠΎΡ ΡΠΎΠΏΡΡΠ²Π΅Π½ ΡΠ΅ΡΠΈΠ²Π΅Ρ.
ΠΠ°ΡΠ΅ΠΌ, Π·Π° ΡΠΏΠΎΡΠ΅Π΄Π±Π°, ΠΏΡΠ²ΠΈΠΎΡ ΡΠ°ΠΊΠΎΠ² ΡΠΈΡΡΠ΅ΠΌ, Airnav Radarbox, Π΄ΠΈΠ·Π°ΡΠ½ΠΈΡΠ°Π½ Π·Π° ΠΎΠ±ΠΈΡΠ½ΠΈ ΠΊΠΎΡΠΈΡΠ½ΠΈΡΠΈ, ΡΠ΅ ΠΏΠΎΡΠ°Π²ΠΈ Π²ΠΎ 2007 Π³ΠΎΠ΄ΠΈΠ½Π° ΠΈ ΡΠΈΠ½Π΅ΡΠ΅ ΠΎΠΊΠΎΠ»Ρ 900 Π΄ΠΎΠ»Π°ΡΠΈ; ΠΏΡΠ΅ΡΠΏΠ»Π°ΡΠ° Π½Π° ΠΌΡΠ΅ΠΆΠ½ΠΈΡΠ΅ ΡΡΠ»ΡΠ³ΠΈ ΡΠΈΠ½Π΅ΡΠ΅ ΡΡΡΠ΅ 250 Π΄ΠΎΠ»Π°ΡΠΈ Π³ΠΎΠ΄ΠΈΡΠ½ΠΎ.
ΠΡΠ²ΡΡΠΈ Π½Π° ΡΠΈΠ΅ ΠΏΡΠ²ΠΈ ΡΡΡΠΊΠΈ ΡΠΎΠΏΡΡΠ²Π΅Π½ΠΈΡΠΈ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ ΠΏΡΠΎΡΠΈΡΠ°Π°Ρ Π½Π° ΡΠΎΡΡΠΌΠΎΡ
ΠΡΠΈΠΌΠ°ΡΠ΅ ΡΠΈΠ³Π½Π°Π»ΠΈ
ΠΡΠ²ΠΎ, ΡΠΈΠ³Π½Π°Π»ΠΎΡ ΡΡΠ΅Π±Π° Π΄Π° ΡΠ΅ ΡΠ½ΠΈΠΌΠΈ. Π¦Π΅Π»ΠΈΠΎΡ ΡΠΈΠ³Π½Π°Π» ΠΈΠΌΠ° Π²ΡΠ΅ΠΌΠ΅ΡΡΠ°Π΅ΡΠ΅ ΠΎΠ΄ ΡΠ°ΠΌΠΎ 120 ΠΌΠΈΠΊΡΠΎΡΠ΅ΠΊΡΠ½Π΄ΠΈ, ΠΏΠ° Π·Π° ΡΠ΄ΠΎΠ±Π½ΠΎ ΡΠ°ΡΠΊΠ»ΠΎΠΏΡΠ²Π°ΡΠ΅ Π½Π° Π½Π΅Π³ΠΎΠ²ΠΈΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΈ, ΠΏΠΎΠΆΠ΅Π»Π΅Π½ Π΅ SDR ΠΏΡΠΈΠ΅ΠΌΠ½ΠΈΠΊ ΡΠΎ ΡΡΠ΅ΠΊΠ²Π΅Π½ΡΠΈΡΠ° Π½Π° Π·Π΅ΠΌΠ°ΡΠ΅ ΠΏΡΠΈΠΌΠ΅ΡΠΎΡΠΈ ΠΎΠ΄ Π½Π°ΡΠΌΠ°Π»ΠΊΡ 5 MHz.
ΠΠΎ ΡΠ½ΠΈΠΌΠ°ΡΠ΅ΡΠΎ, Π΄ΠΎΠ±ΠΈΠ²Π°ΠΌΠ΅ WAV-Π΄Π°ΡΠΎΡΠ΅ΠΊΠ° ΡΠΎ Π±ΡΠ·ΠΈΠ½Π° Π½Π° Π·Π΅ΠΌΠ°ΡΠ΅ ΠΏΡΠΈΠΌΠ΅ΡΠΎΡΠΈ ΠΎΠ΄ 5000000 ΠΏΡΠΈΠΌΠ΅ΡΠΎΡΠΈ/ΡΠ΅ΠΊ; 30 ΡΠ΅ΠΊΡΠ½Π΄ΠΈ ΠΎΠ΄ ΡΠ°ΠΊΠ²ΠΎΡΠΎ ΡΠ½ΠΈΠΌΠ°ΡΠ΅ βΡΠ΅ΠΆΠ°Ρβ ΠΎΠΊΠΎΠ»Ρ 500 MB. Π‘Π»ΡΡΠ°ΡΠ΅ΡΠΎ ΡΠΎ ΠΌΠ΅Π΄ΠΈΠ° ΠΏΠ»Π΅Π΅Ρ, ΡΠ΅ ΡΠ°Π·Π±ΠΈΡΠ°, Π΅ Π±Π΅ΡΠΊΠΎΡΠΈΡΠ½ΠΎ - Π΄Π°ΡΠΎΡΠ΅ΠΊΠ°ΡΠ° Π½Π΅ ΡΠΎΠ΄ΡΠΆΠΈ Π·Π²ΡΠΊ, ΡΡΠΊΡ Π΄ΠΈΡΠ΅ΠΊΡΠ½ΠΎ Π΄ΠΈΠ³ΠΈΡΠ°Π»ΠΈΠ·ΠΈΡΠ°Π½ ΡΠ°Π΄ΠΈΠΎΡΠΈΠ³Π½Π°Π» - ΡΠΎΠΊΠΌΡ Π²Π°ΠΊΠ° ΡΠ°Π±ΠΎΡΠΈ Software Defined Radio.
ΠΠ΅ ΡΠ° ΠΎΡΠ²ΠΎΡΠΈΠΌΠ΅ ΠΈ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΈΠΌΠ΅ Π΄Π°ΡΠΎΡΠ΅ΠΊΠ°ΡΠ° ΠΊΠΎΡΠΈΡΡΠ΅ΡΡΠΈ Python. ΠΠ½ΠΈΠ΅ ΠΊΠΎΠΈ ΡΠ°ΠΊΠ°Π°Ρ ΡΠ°ΠΌΠΈ Π΄Π° Π΅ΠΊΡΠΏΠ΅ΡΠΈΠΌΠ΅Π½ΡΠΈΡΠ°Π°Ρ ΠΌΠΎΠΆΠ°Ρ Π΄Π° ΠΏΡΠ΅Π·Π΅ΠΌΠ°Ρ ΠΏΡΠΈΠΌΠ΅Ρ Π·Π° ΡΠ½ΠΈΠΌΠ°ΡΠ΅
ΠΡΠ΄Π΅ Π΄Π° ΡΠ° ΠΏΡΠ΅Π·Π΅ΠΌΠ΅ΠΌΠ΅ Π΄Π°ΡΠΎΡΠ΅ΠΊΠ°ΡΠ° ΠΈ Π΄Π° Π²ΠΈΠ΄ΠΈΠΌΠ΅ ΡΡΠΎ ΠΈΠΌΠ° Π²Π½Π°ΡΡΠ΅.
from scipy.io import wavfile
import matplotlib.pyplot as plt
import numpy as np
fs, data = wavfile.read("adsb_20190311_191728Z_1090000kHz_RF.wav")
data = data.astype(float)
I, Q = data[:, 0], data[:, 1]
A = np.sqrt(I*I + Q*Q)
plt.plot(A)
plt.show()
Π Π΅Π·ΡΠ»ΡΠ°Ρ: Π³Π»Π΅Π΄Π°ΠΌΠ΅ ΠΎΡΠΈΠ³Π»Π΅Π΄Π½ΠΈ βΠΏΡΠ»ΡΠΈΡΠ°ΡΠ°β Π½Π°ΡΠΏΡΠΎΡΠΈ Π±ΡΡΠ°Π²Π°ΡΠ° Π²ΠΎ ΠΏΠΎΠ·Π°Π΄ΠΈΠ½Π°.
Π‘Π΅ΠΊΠΎΡ βΠΏΡΠ»Ρβ Π΅ ΡΠΈΠ³Π½Π°Π», ΡΠΈΡΠ° ΡΡΡΡΠΊΡΡΡΠ° Π΅ ΡΠ°ΡΠ½ΠΎ Π²ΠΈΠ΄Π»ΠΈΠ²Π° Π°ΠΊΠΎ ΡΠ° Π·Π³ΠΎΠ»Π΅ΠΌΠΈΡΠ΅ ΡΠ΅Π·ΠΎΠ»ΡΡΠΈΡΠ°ΡΠ° Π½Π° Π³ΡΠ°ΡΠΈΠΊΠΎΠ½ΠΎΡ.
ΠΠ°ΠΊΠΎ ΡΡΠΎ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π΄Π° Π²ΠΈΠ΄ΠΈΡΠ΅, ΡΠ»ΠΈΠΊΠ°ΡΠ° Π΅ ΡΠΎΡΠ΅ΠΌΠ° ΠΊΠΎΠ½Π·ΠΈΡΡΠ΅Π½ΡΠ½Π° ΡΠΎ ΠΎΠ½Π° ΡΡΠΎ Π΅ Π΄Π°Π΄Π΅Π½ΠΎ Π²ΠΎ ΠΎΠΏΠΈΡΠΎΡ ΠΏΠΎΠ³ΠΎΡΠ΅. ΠΠΎΠΆΠ΅ΡΠ΅ Π΄Π° Π·Π°ΠΏΠΎΡΠ½Π΅ΡΠ΅ ΡΠΎ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° Π½Π° ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈΡΠ΅.
ΠΠ΅ΠΊΠΎΠ΄ΠΈΡΠ°ΡΠ΅
ΠΡΠ²ΠΎ, ΡΡΠ΅Π±Π° Π΄Π° Π΄ΠΎΠ±ΠΈΠ΅ΡΠ΅ ΠΌΠ°Π»ΠΊΡ ΠΏΠΎΡΠΎΠΊ. Π‘Π°ΠΌΠΈΠΎΡ ΡΠΈΠ³Π½Π°Π» Π΅ ΠΊΠΎΠ΄ΠΈΡΠ°Π½ ΡΠΎ ΠΊΠΎΡΠΈΡΡΠ΅ΡΠ΅ Π½Π° ΠΊΠΎΠ΄ΠΈΡΠ°ΡΠ΅ ΠΠ°Π½ΡΠ΅ΡΡΠ΅Ρ:
ΠΠ΄ ΡΠ°Π·Π»ΠΈΠΊΠ°ΡΠ° Π²ΠΎ Π½ΠΈΠ²ΠΎΠ°ΡΠ° Π²ΠΎ Π³ΡΠΈΡΠΊΠ°ΡΠ΅ΡΠΎ Π»Π΅ΡΠ½ΠΎ Π΅ Π΄Π° ΡΠ΅ Π΄ΠΎΠ±ΠΈΡΠ°Ρ Π²ΠΈΡΡΠΈΠ½ΡΠΊΠΈ β0β ΠΈ β1β.
bits_str = ""
for p in range(8):
pos = start_data + bit_len*p
p1, p2 = A[pos: pos + bit_len/2], A[pos + bit_len/2: pos + bit_len]
avg1, avg2 = np.average(p1), np.average(p2)
if avg1 < avg2:
bits_str += "0"
elif avg1 > avg2:
bits_str += "1"
Π‘ΡΡΡΠΊΡΡΡΠ°ΡΠ° Π½Π° ΡΠ°ΠΌΠΈΠΎΡ ΡΠΈΠ³Π½Π°Π» Π΅ ΠΊΠ°ΠΊΠΎ ΡΡΠΎ ΡΠ»Π΅Π΄ΡΠ²Π°:
ΠΠ° Π³ΠΈ ΠΏΠΎΠ³Π»Π΅Π΄Π½Π΅ΠΌΠ΅ ΠΏΠΎΠ»ΠΈΡΠ°ΡΠ° ΠΏΠΎΠ΄Π΅ΡΠ°Π»Π½ΠΎ.
DF (Π€ΠΎΡΠΌΠ°Ρ Π½Π° Π΄ΠΎΠ»Π½Π° Π²ΡΡΠΊΠ°, 5 Π±ΠΈΡΠ°) - Π³ΠΎ ΠΎΠ΄ΡΠ΅Π΄ΡΠ²Π° ΡΠΈΠΏΠΎΡ Π½Π° ΠΏΠΎΡΠ°ΠΊΠ°ΡΠ°. ΠΠΎΡΡΠΎΡΠ°Ρ Π½Π΅ΠΊΠΎΠ»ΠΊΡ Π²ΠΈΠ΄ΠΎΠ²ΠΈ:
(
ΠΠ°Ρ Π½Γ¨ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΈΡΠ° ΡΠ°ΠΌΠΎ ΡΠΈΠΏΠΎΡ DF17, Π±ΠΈΠ΄Π΅ΡΡΠΈ ... Π’ΠΎΠΊΠΌΡ ΡΠΎΠ° Π³ΠΈ ΡΠΎΠ΄ΡΠΆΠΈ ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΠΈΡΠ΅ Π½Π° Π°Π²ΠΈΠΎΠ½ΠΎΡ.
ICAO (24 Π±ΠΈΡΠ°) - ΠΌΠ΅ΡΡΠ½Π°ΡΠΎΠ΄Π΅Π½ Π΅Π΄ΠΈΠ½ΡΡΠ²Π΅Π½ ΠΊΠΎΠ΄ Π½Π° Π°Π²ΠΈΠΎΠ½ΠΎΡ. ΠΠΎΠΆΠ΅ΡΠ΅ Π΄Π° Π³ΠΎ ΠΏΡΠΎΠ²Π΅ΡΠΈΡΠ΅ Π°Π²ΠΈΠΎΠ½ΠΎΡ ΡΠΏΠΎΡΠ΅Π΄ Π½Π΅Π³ΠΎΠ²ΠΈΠΎΡ ΠΊΠΎΠ΄
Π£ΡΠ΅Π΄ΡΠ²Π°ΡΠ΅: Π²ΠΎ
ΠΠΠΠΠ’ΠΠ¦Π (56 ΠΈΠ»ΠΈ 112 Π±ΠΈΡΠ°) - Π²ΠΈΡΡΠΈΠ½ΡΠΊΠΈΡΠ΅ ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈ ΡΡΠΎ ΡΠ΅ Π³ΠΈ Π΄Π΅ΡΠΈΡΡΠΈΡΠ°ΠΌΠ΅. ΠΡΠ²ΠΈΡΠ΅ 5 Π±ΠΈΡΠ° ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈ ΡΠ΅ ΠΏΠΎΠ»Π΅ΡΠΎ ΠΠ½Π΅ΡΠ΅ΡΠ΅ ΠΊΠΎΠ΄, ΠΊΠΎΡ Π³ΠΎ ΡΠΎΠ΄ΡΠΆΠΈ ΠΏΠΎΠ΄ΡΠΈΠΏΠΎΡ Π½Π° ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈΡΠ΅ ΡΡΠΎ ΡΠ΅ ΡΠΊΠ»Π°Π΄ΠΈΡΠ°Π°Ρ (Π΄Π° Π½Π΅ ΡΠ΅ ΠΌΠ΅ΡΠ° ΡΠΎ DF). ΠΠΎΡΡΠΎΡΠ°Ρ Π½Π΅ΠΊΠΎΠ»ΠΊΡ ΠΎΠ΄ ΠΎΠ²ΠΈΠ΅ ΡΠΈΠΏΠΎΠ²ΠΈ:
(
ΠΡΠ΄Π΅ Π΄Π° ΠΏΠΎΠ³Π»Π΅Π΄Π½Π΅ΠΌΠ΅ Π½Π΅ΠΊΠΎΠ»ΠΊΡ ΠΏΡΠΈΠΌΠ΅ΡΠΈ Π½Π° ΠΏΠ°ΠΊΠ΅ΡΠΈ.
ΠΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΈΡΠ° Π½Π° Π°Π²ΠΈΠΎΠ½
ΠΡΠΈΠΌΠ΅Ρ Π²ΠΎ Π±ΠΈΠ½Π°ΡΠ½Π° ΡΠΎΡΠΌΠ°:
00100 011 000101 010111 000111 110111 110001 111000
ΠΠΎΠ»ΠΈΡΠ° ΡΠΎ ΠΏΠΎΠ΄Π°ΡΠΎΡΠΈ:
+------+------+------+------+------+------+------+------+------+------+
| TC,5 | EC,3 | C1,6 | C2,6 | C3,6 | C4,6 | C5,6 | C6,6 | C7,6 | C8,6 |
+------+------+------+------+------+------+------+------+------+------+
TC = 00100b = 4, ΡΠ΅ΠΊΠΎΡ Π·Π½Π°ΠΊ C1-C8 ΡΠΎΠ΄ΡΠΆΠΈ ΡΠΈΡΡΠΈ ΡΡΠΎ ΠΎΠ΄Π³ΠΎΠ²Π°ΡΠ°Π°Ρ Π½Π° ΠΈΠ½Π΄Π΅ΠΊΡΠΈΡΠ΅ Π²ΠΎ Π»ΠΈΠ½ΠΈΡΠ°ΡΠ°:
#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789#################XNUMX######
Π‘ΠΎ Π΄Π΅ΠΊΠΎΠ΄ΠΈΡΠ°ΡΠ΅ Π½Π° Π½ΠΈΠ·Π°ΡΠ°, Π»Π΅ΡΠ½ΠΎ Π΅ Π΄Π° ΡΠ΅ Π΄ΠΎΠ±ΠΈΠ΅ ΡΠΈΡΡΠ°ΡΠ° Π½Π° Π°Π²ΠΈΠΎΠ½ΠΎΡ: EWG7184
symbols = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
code_str = ""
for p in range(8):
c = int(bits_str[8 + 6*p:8 + 6*(p + 1)], 2)
code_str += symbols[c]
print("Aircraft Identification:", code_str.replace('#', ''))
ΠΠΎΠ·Π΄ΡΡΠ½Π° ΠΏΠΎΠ»ΠΎΠΆΠ±Π°
ΠΠΊΠΎ ΠΈΠΌΠ΅ΡΠΎ Π΅ Π΅Π΄Π½ΠΎΡΡΠ°Π²Π½ΠΎ, ΡΠΎΠ³Π°Ρ ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΠΈΡΠ΅ ΡΠ΅ ΠΏΠΎΠΊΠΎΠΌΠΏΠ»ΠΈΡΠΈΡΠ°Π½ΠΈ. Π’ΠΈΠ΅ ΡΠ΅ ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π°Π°Ρ Π²ΠΎ ΡΠΎΡΠΌΠ° Π½Π° 2, ΠΏΠ°ΡΠ½ΠΈ ΠΈ Π½Π΅ΠΏΠ°ΡΠ½ΠΈ ΡΠ°ΠΌΠΊΠΈ. ΠΠΎΠ΄ Π½Π° ΠΏΠΎΠ»Π΅ TC = 01011b = 11.
ΠΡΠΈΠΌΠ΅Ρ Π·Π° ΠΏΠ°ΡΠ½ΠΈ ΠΈ Π½Π΅ΠΏΠ°ΡΠ½ΠΈ ΠΏΠ°ΠΊΠ΅ΡΠΈ:
01011 000 000101110110 00 10111000111001000 10000110101111001
01011 000 000110010000 01 10010011110000110 10000011110001000
Π‘Π°ΠΌΠ°ΡΠ° ΠΏΡΠ΅ΡΠΌΠ΅ΡΠΊΠ° Π½Π° ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΠΈΡΠ΅ ΡΠ΅ ΡΠ»ΡΡΡΠ²Π° ΡΠΏΠΎΡΠ΅Π΄ ΠΏΡΠΈΠ»ΠΈΡΠ½ΠΎ Π½Π΅Π·Π³ΠΎΠ΄Π½Π° ΡΠΎΡΠΌΡΠ»Π°:
ΠΠ°Ρ Π½Π΅ ΡΡΠΌ Π΅ΠΊΡΠΏΠ΅ΡΡ Π·Π° ΠΠΠ‘, ΠΏΠ° Π½Π΅ Π·Π½Π°ΠΌ ΠΎΠ΄ ΠΊΠ°Π΄Π΅ Π΄ΠΎΠ°ΡΠ°. ΠΠΎΡ Π·Π½Π°Π΅, ΠΏΠΈΡΠ΅ΡΠ΅ Π²ΠΎ ΠΊΠΎΠΌΠ΅Π½ΡΠ°ΡΠΈ.
ΠΠΈΡΠΈΠ½Π°ΡΠ° ΡΠ΅ ΡΠΌΠ΅ΡΠ° Π·Π° ΠΏΠΎΠ΅Π΄Π½ΠΎΡΡΠ°Π²Π½Π° - Π²ΠΎ Π·Π°Π²ΠΈΡΠ½ΠΎΡΡ ΠΎΠ΄ ΡΠΏΠ΅ΡΠΈΡΠΈΡΠ½ΠΈΠΎΡ Π±ΠΈΡ, ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ ΠΏΡΠ΅ΡΡΡΠ°Π²ΠΈ ΠΊΠ°ΠΊΠΎ ΠΌΠ½ΠΎΠΆΠΈΡΠ΅Π» ΠΎΠ΄ 25 ΠΈΠ»ΠΈ 100 ΡΡΠ°ΠΏΠΊΠΈ.
ΠΠΎΠ·Π΄ΡΡΠ½Π° Π±ΡΠ·ΠΈΠ½Π°
ΠΠ°ΠΊΠ΅Ρ ΡΠΎ Π’Π¦=19. ΠΠ½ΡΠ΅ΡΠ΅ΡΠ½Π°ΡΠ° ΡΠ°Π±ΠΎΡΠ° ΠΎΠ²Π΄Π΅ Π΅ ΡΡΠΎ Π±ΡΠ·ΠΈΠ½Π°ΡΠ° ΠΌΠΎΠΆΠ΅ Π΄Π° Π±ΠΈΠ΄Π΅ ΠΈΠ»ΠΈ ΡΠΎΡΠ½Π°, Π²ΠΎ ΠΎΠ΄Π½ΠΎΡ Π½Π° Π·Π΅ΠΌΡΠ°ΡΠ° (Π±ΡΠ·ΠΈΠ½Π° Π½Π° Π·Π΅ΠΌΡΠ°ΡΠ°) ΠΈΠ»ΠΈ Π²ΠΎΠ·Π΄ΡΡΠ½Π°, ΠΌΠ΅ΡΠ΅Π½Π° ΡΠΎ ΡΠ΅Π½Π·ΠΎΡ Π½Π° Π°Π²ΠΈΠΎΠ½ (Airspeed). ΠΡΡΠΎ ΡΠ°ΠΊΠ°, ΡΠ΅ ΠΏΡΠ΅Π½Π΅ΡΡΠ²Π°Π°Ρ ΠΌΠ½ΠΎΠ³Ρ ΡΠ°Π·Π»ΠΈΡΠ½ΠΈ ΠΏΠΎΠ»ΠΈΡΠ°:
ΠΠ°ΠΊΠ»ΡΡΠΎΠΊ
ΠΠ°ΠΊΠΎ ΡΡΠΎ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π΄Π° Π²ΠΈΠ΄ΠΈΡΠ΅, ΡΠ΅Ρ Π½ΠΎΠ»ΠΎΠ³ΠΈΡΠ°ΡΠ° ADS-B ΡΡΠ°Π½Π° ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½Π° ΡΠΈΠΌΠ±ΠΈΠΎΠ·Π°, ΠΊΠΎΠ³Π° ΡΡΠ°Π½Π΄Π°ΡΠ΄ΠΎΡ Π΅ ΠΊΠΎΡΠΈΡΠ΅Π½ Π½Π΅ ΡΠ°ΠΌΠΎ Π·Π° ΠΏΡΠΎΡΠ΅ΡΠΈΠΎΠ½Π°Π»ΡΠΈΡΠ΅, ΡΡΠΊΡ ΠΈ Π·Π° ΠΎΠ±ΠΈΡΠ½ΠΈΡΠ΅ ΠΊΠΎΡΠΈΡΠ½ΠΈΡΠΈ. ΠΠΎ, ΡΠ΅ ΡΠ°Π·Π±ΠΈΡΠ°, ΠΊΠ»ΡΡΠ½Π° ΡΠ»ΠΎΠ³Π° Π²ΠΎ ΠΎΠ²Π° ΠΎΠ΄ΠΈΠ³ΡΠ° ΠΏΠΎΠ΅Π²ΡΠΈΠ½Π°ΡΠ° ΡΠ΅Ρ Π½ΠΎΠ»ΠΎΠ³ΠΈΡΠ° Π½Π° Π΄ΠΈΠ³ΠΈΡΠ°Π»Π½ΠΈ SDR ΠΏΡΠΈΠ΅ΠΌΠ½ΠΈΡΠΈ, ΠΊΠΎΡΠ° ΠΌΡ ΠΎΠ²ΠΎΠ·ΠΌΠΎΠΆΡΠ²Π° Π½Π° ΡΡΠ΅Π΄ΠΎΡ Π±ΡΠΊΠ²Π°Π»Π½ΠΎ Π΄Π° ΠΏΡΠΈΠΌΠ° ΡΠΈΠ³Π½Π°Π»ΠΈ ΡΠΎ ΡΡΠ΅ΠΊΠ²Π΅Π½ΡΠΈΠΈ Π½Π°Π΄ Π³ΠΈΠ³Π°Ρ Π΅ΡΡΠΈ βΠ·Π° ΠΏΠ΅Π½ΠΈβ.
ΠΠΎ ΡΠ°ΠΌΠΈΠΎΡ ΡΡΠ°Π½Π΄Π°ΡΠ΄, ΡΠ΅ ΡΠ°Π·Π±ΠΈΡΠ°, ΠΈΠΌΠ° ΠΌΠ½ΠΎΠ³Ρ ΠΏΠΎΠ²Π΅ΡΠ΅. ΠΠ°ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΈΡΠ°Π½ΠΈΡΠ΅ ΠΌΠΎΠΆΠ°Ρ Π΄Π° Π³ΠΎ ΠΏΠΎΠ³Π»Π΅Π΄Π½Π°Ρ PDF Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ°ΡΠ°
ΠΠ°Π»ΠΊΡ Π΅ Π²Π΅ΡΠΎΡΠ°ΡΠ½ΠΎ Π΄Π΅ΠΊΠ° ΡΠ΅ΡΠΎ Π³ΠΎΡΠ΅Π½Π°Π²Π΅Π΄Π΅Π½ΠΎ ΡΠ΅ Π±ΠΈΠ΄Π΅ ΠΊΠΎΡΠΈΡΠ½ΠΎ Π·Π° ΠΌΠ½ΠΎΠ³ΡΠΌΠΈΠ½Π°, Π½ΠΎ Π±Π°ΡΠ΅ΠΌ ΠΎΠΏΡΡΠ°ΡΠ° ΠΈΠ΄Π΅ΡΠ° Π·Π° ΡΠΎΠ° ΠΊΠ°ΠΊΠΎ ΡΡΠ½ΠΊΡΠΈΠΎΠ½ΠΈΡΠ°, ΡΠ΅ Π½Π°Π΄Π΅Π²Π°ΠΌ, ΠΎΡΡΠ°Π½ΡΠ²Π°.
ΠΠ°ΡΠ΅ΠΌ, Π³ΠΎΡΠΎΠ² Π΄Π΅ΠΊΠΎΠ΄Π΅Ρ Π²ΠΎ Python Π²Π΅ΡΠ΅ ΠΏΠΎΡΡΠΎΠΈ, ΠΌΠΎΠΆΠ΅ΡΠ΅ Π΄Π° Π³ΠΎ ΠΏΡΠΎΡΡΠΈΡΠ΅
ΠΠ·Π²ΠΎΡΠ½ΠΈΠΎΡ ΠΊΠΎΠ΄ Π½Π° ΠΏΠ°ΡΡΠ΅ΡΠΎΡ ΠΎΠΏΠΈΡΠ°Π½ Π²ΠΎ ΡΡΠ°ΡΠΈΡΠ°ΡΠ° Π΅ Π΄Π°Π΄Π΅Π½ ΠΏΠΎΠ΄ ΡΠ΅ΡΠ΅ΡΠ΅ΡΠΎ. ΠΠ²Π° Π΅ ΡΠ΅ΡΡ ΠΏΡΠΈΠΌΠ΅Ρ ΠΊΠΎΡ Π½Π΅ ΡΠ΅ ΠΏΡΠ΅ΠΏΡΠ°Π²Π° Π΄Π΅ΠΊΠ° Π΅ ΠΏΡΠΎΠ΄ΡΠΊΡΠΈΡΠ°, Π½ΠΎ Π½Π΅ΠΊΠΎΠΈ ΡΠ°Π±ΠΎΡΠΈ ΡΡΠ½ΠΊΡΠΈΠΎΠ½ΠΈΡΠ°Π°Ρ Π²ΠΎ Π½Π΅Π³ΠΎ ΠΈ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ ΠΊΠΎΡΠΈΡΡΠΈ Π·Π° Π°Π½Π°Π»ΠΈΠ·Π° Π½Π° Π΄Π°ΡΠΎΡΠ΅ΠΊΠ°ΡΠ° ΡΠ½ΠΈΠΌΠ΅Π½Π° ΠΏΠΎΠ³ΠΎΡΠ΅.
ΠΠ·Π²ΠΎΡΠ½ΠΈΠΎΡ ΠΊΠΎΠ΄ (Python)
from __future__ import print_function
from scipy.io import wavfile
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
import math
import sys
def parse_message(data, start, bit_len):
max_len = bit_len*128
A = data[start:start + max_len]
A = signal.resample(A, 10*max_len)
bits = np.zeros(10*max_len)
bit_len *= 10
start_data = bit_len*8
# Parse first 8 bits
bits_str = ""
for p in range(8):
pos = start_data + bit_len*p
p1, p2 = A[pos: pos + bit_len/2], A[pos + bit_len/2: pos + bit_len]
avg1, avg2 = np.average(p1), np.average(p2)
if avg1 < avg2:
bits_str += "0"
elif avg1 > avg2:
bits_str += "1"
df = int(bits_str[0:5], 2)
# Aircraft address (db - https://junzis.com/adb/?q=3b1c5c )
bits_str = ""
for p in range(8, 32):
pos = start_data + bit_len * p
p1, p2 = A[pos: pos + bit_len / 2], A[pos + bit_len / 2: pos + bit_len]
avg1, avg2 = np.average(p1), np.average(p2)
if avg1 < avg2:
bits_str += "0"
elif avg1 > avg2:
bits_str += "1"
# print "Aircraft address:", bits_str, hex(int(bits_str, 2))
address = hex(int(bits_str, 2))
# Filter specific aircraft (optional)
# if address != "0x3c5ee2":
# return
if df == 16 or df == 17 or df == 18 or df == 19 or df == 20 or df == 21:
# print "Pos:", start, "DF:", msg_type
# Data (56bit)
bits_str = ""
for p in range(32, 88):
pos = start_data + bit_len*p
p1, p2 = A[pos: pos + bit_len/2], A[pos + bit_len/2: pos + bit_len]
avg1, avg2 = np.average(p1), np.average(p2)
if avg1 < avg2:
bits_str += "0"
# bits[pos + bit_len / 2] = 50
elif avg1 > avg2:
bits_str += "1"
# http://www.lll.lu/~edward/edward/adsb/DecodingADSBposition.html
# print "Data:"
# print bits_str[:8], bits_str[8:20], bits_str[20:22], bits_str[22:22+17], bits_str[39:39+17]
# Type Code:
tc, ec = int(bits_str[:5], 2), int(bits_str[5:8], 2)
# print("DF:", df, "TC:", tc)
# 1 - 4 Aircraft identification
# 5 - 8 Surface position
# 9 - 18 Airborne position (w/ Baro Altitude)
# 19 Airborne velocities
if tc >= 1 and tc <= 4: # and (df == 17 or df == 18):
print("Aircraft address:", address)
print("Data:")
print(bits_str[:8], bits_str[8:14], bits_str[14:20], bits_str[20:26], bits_str[26:32], bits_str[32:38], bits_str[38:44])
symbols = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
code_str = ""
for p in range(8):
c = int(bits_str[8 + 6*p:8 + 6*(p + 1)], 2)
code_str += symbols[c]
print("Aircraft Identification:", code_str.replace('#', ''))
print()
if tc == 11:
print("Aircraft address:", address)
print("Data: (11)")
print(bits_str[:8], bits_str[8:20], bits_str[20:22], bits_str[22:22+17], bits_str[39:39+17])
# Bit 22 contains the F flag which indicates which CPR format is used (odd or even)
# First frame has F flag = 0 so is even and the second frame has F flag = 1 so odd
# f = bits_str[21:22]
# print("F:", int(f, 2))
# Altitude
alt1b = bits_str[8:20]
if alt1b[-5] == '1':
bits = alt1b[:-5] + alt1b[-4:]
n = int(bits, 2)
alt_ft = n*25 - 1000
print("Alt (ft)", alt_ft)
# lat_dec = int(bits_str[22:22+17], 2)
# lon_dec = int(bits_str[39:39+17], 2)
# print("Lat/Lon:", lat_dec, lon_dec)
# http://airmetar.main.jp/radio/ADS-B%20Decoding%20Guide.pdf
print()
if tc == 19:
print("Aircraft address:", address)
print("Data:")
# print(bits_str)
print(bits_str[:5], bits_str[5:8], bits_str[8:10], bits_str[10:13], bits_str[13] ,bits_str[14:24], bits_str[24], bits_str[25:35], bits_str[35:36], bits_str[36:65])
subtype = int(bits_str[5:8], 2)
# https://mode-s.org/decode/adsb/airborne-velocity.html
spd, hdg, rocd = -1, -1, -1
if subtype == 1 or subtype == 2:
print("Velocity Subtype 1: Ground speed")
v_ew_sign = int(bits_str[13], 2)
v_ew = int(bits_str[14:24], 2) - 1 # east-west velocity
v_ns_sign = int(bits_str[24], 2)
v_ns = int(bits_str[25:35], 2) - 1 # north-south velocity
v_we = -1*v_ew if v_ew_sign else v_ew
v_sn = -1*v_ns if v_ns_sign else v_ns
spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts
hdg = math.atan2(v_we, v_sn)
hdg = math.degrees(hdg) # convert to degrees
hdg = hdg if hdg >= 0 else hdg + 360 # no negative val
if subtype == 3:
print("Subtype Subtype 3: Airspeed")
hdg = int(bits_str[14:24], 2)/1024.0*360.0
spd = int(bits_str[25:35], 2)
vr_sign = int(bits_str[36], 2)
vr = int(bits_str[36:45], 2)
rocd = -1*vr if vr_sign else vr # rate of climb/descend
print("Speed (kts):", spd, "Rate:", rocd, "Heading:", hdg)
print()
# print()
def calc_coordinates():
def _cprN(lat, is_odd):
nl = _cprNL(lat) - is_odd
return nl if nl > 1 else 1
def _cprNL(lat):
try:
nz = 15
a = 1 - math.cos(math.pi / (2 * nz))
b = math.cos(math.pi / 180.0 * abs(lat)) ** 2
nl = 2 * math.pi / (math.acos(1 - a/b))
return int(math.floor(nl))
except:
# happens when latitude is +/-90 degree
return 1
def floor_(x):
return int(math.floor(x))
lat1b, lon1b, alt1b = "10111000111010011", "10000110111111000", "000101111001"
lat2b, lon2b, alt2b = "10010011101011100", "10000011000011011", "000101110111"
lat1, lon1, alt1 = int(lat1b, 2), int(lon1b, 2), int(alt1b, 2)
lat2, lon2, alt2 = int(lat2b, 2), int(lon2b, 2), int(alt2b, 2)
# 131072 is 2^17, since CPR lat and lon are 17 bits each
cprlat_even, cprlon_even = lat1/131072.0, lon1/131072.0
cprlat_odd, cprlon_odd = lat2/131072.0, lon2/131072.0
print(cprlat_even, cprlon_even)
j = floor_(59*cprlat_even - 60*cprlat_odd)
print(j)
air_d_lat_even = 360.0 / 60
air_d_lat_odd = 360.0 / 59
# Lat
lat_even = float(air_d_lat_even * (j % 60 + cprlat_even))
lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd))
if lat_even >= 270:
lat_even = lat_even - 360
if lat_odd >= 270:
lat_odd = lat_odd - 360
# Lon
ni = _cprN(lat_even, 0)
m = floor_(cprlon_even * (_cprNL(lat_even)-1) - cprlon_odd * _cprNL(lat_even) + 0.5)
lon = (360.0 / ni) * (m % ni + cprlon_even)
print("Lat", lat_even, "Lon", lon)
# Altitude
# Q-bit (bit 48) indicates whether the altitude is encoded in multiples of 25 or 100 ft (0: 100 ft, 1: 25 ft)
# The value can represent altitudes from -1000 to +50175 ft.
if alt1b[-5] == '1':
bits = alt1b[:-5] + alt1b[-4:]
n = int(bits, 2)
alt_ft = n*25 - 1000
print("Alt (ft)", alt_ft)
fs, data = wavfile.read("adsb_20190311_191728Z_1090000kHz_RF.wav")
T = 1/fs
print("Sample rate %f MS/s" % (fs / 1e6))
print("Cnt samples %d" % len(data))
print("Duration: %f s" % (T * len(data)))
data = data.astype(float)
cnt = data.shape[0]
# Processing only part on file (faster):
# cnt = 10000000
# data = data[:cnt]
print("Processing I/Q...")
I, Q = data[:, 0], data[:, 1]
A = np.sqrt(I*I + Q*Q)
bits = np.zeros(cnt)
# To see scope without any processing, uncomment
# plt.plot(A)
# plt.show()
# sys.exit(0)
print("Extracting signals...")
pos = 0
avg = 200
msg_start = 0
# Find beginning of each signal
while pos < cnt - 16*1024:
# P1 - message start
while pos < cnt - 16*1024:
if A[pos] < avg and A[pos+1] > avg and pos - msg_start > 1000:
msg_start = pos
bits[pos] = 100
pos += 4
break
pos += 1
start1, start2, start3, start4 = msg_start, 0, 0, 0
# P2
while pos < cnt - 16*1024:
if A[pos] < avg and A[pos+1] > avg:
start2 = pos
bits[pos] = 90
pos += 1
break
pos += 1
# P3
while pos < cnt - 16*1024:
if A[pos] < avg and A[pos+1] > avg:
start3 = pos
bits[pos] = 80
pos += 1
break
pos += 1
# P4
while pos < cnt - 16*1024:
if A[pos] < avg and A[pos+1] > avg:
start4 = pos
bits[pos] = 70
pos += 1
break
pos += 1
sig_diff = start4 - start1
if 20 < sig_diff < 25:
bits[msg_start] = 500
bit_len = int((start4 - start1) / 4.5)
# print(pos, start1, start4, ' - ', bit_len)
# start = start1 + 8*bit_len
parse_message(A, msg_start, bit_len)
pos += 450
# For debugging: check signal start
# plt.plot(A)
# plt.plot(bits)
# plt.show()
Π‘Π΅ Π½Π°Π΄Π΅Π²Π°ΠΌ Π΄Π΅ΠΊΠ° Π½Π΅ΠΊΠΎΡ Π±Π΅ΡΠ΅ Π·Π°ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΈΡΠ°Π½, Π±Π»Π°Π³ΠΎΠ΄Π°ΡΠ°ΠΌ Π·Π° Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅ΡΠΎ.
ΠΠ·Π²ΠΎΡ: www.habr.com