Gqrx имеет интересную фичу — управлять программой можно по TCP при помощи незамысловатого протокола. Помимо прочего, это позволяет интегрировать Gqrx с Gpredict для компенсации эффекта Допплера . Я давно хотел поиграться с этой возможностью, только не мог придумать правдоподобную задачу. Идею подкинул Kevin Loughin, KB9RLW в своем видео Network sockets and remote control of GQRX SDR with telnet and python .
Первым делом был сделан простенький клиент к Gqrx:
class GqrxClient:
def __init__ ( self , host , port ) :
«»»Creates a new GqrxClient»»»
self . tn = telnetlib . Telnet ( host , port )
def _write_line ( self , line ) :
self . tn . write ( ( line+ ‘ r n ‘ ) . encode ( ‘ascii’ ) )
def _read_line ( self ) :
return self . tn . read_some ( ) . decode ( ‘ascii’ )
def _get_rprt ( self ) :
resp = self ._read_line ( )
( rprt , code ) = resp. split ( )
if rprt != «RPRT» :
raise Exception ( «Unexpected response: » +resp )
return int ( code )
def get_frequency ( self ) :
«»»Returns current frequency in Hz»»»
self ._write_line ( «f» )
freq = self ._read_line ( )
return int ( freq )
def set_frequency ( self , freq ) :
«»»Sets current frequency to `freq` Hz»»»
self ._write_line ( «F » + str ( freq ) )
return self ._get_rprt ( )
def get_mode ( self ) :
«»»Returns current (mode, width) pair»»»
self ._write_line ( «m» )
resp = self ._read_line ( )
( mode , width ) = resp. split ( )
return ( mode , int ( width ) )
def set_mode ( self , mode , width ) :
«»»Sets mode (AM, FM, …) and width»»»
self ._write_line ( «M » +mode+ » » + str ( width ) )
return self ._get_rprt ( )
def get_signal_strength ( self ) :
«»»Returns signal strength in dBFS»»»
self ._write_line ( «l» )
strength = self ._read_line ( )
return float ( strength )
Более подробное описание протокола можно найти здесь .
Далее была написана мониторилка уровня сигнала от разных КВ-радиостанций:
from GqrxClient import GqrxClient
from datetime import datetime
import argparse
import time
import sys
scan = [
( ‘wwv_2.5Mhz’ , 2500000 , ‘AM’ , 4000 ) ,
( ‘wwv_5Mhz’ , 5000000 , ‘AM’ , 4000 ) ,
( ‘wwv_10Mhz’ , 10000000 , ‘AM’ , 4000 ) ,
( ‘wwv_15Mhz’ , 15000000 , ‘AM’ , 4000 ) ,
( ‘wwv_20Mhz’ , 20000000 , ‘AM’ , 4000 ) ,
( ‘rwm_4996’ , 4996000 , ‘AM’ , 4000 ) ,
( ‘rwm_9996’ , 9996000 , ‘AM’ , 4000 ) ,
( ‘rwm_14996’ , 14996000 , ‘AM’ , 4000 ) ,
( ‘chu_3330’ , 3330000 , ‘USB’ , 2250 ) ,
( ‘chu_7850’ , 7850000 , ‘USB’ , 2250 ) ,
( ‘chu_14670’ , 14670000 , ‘USB’ , 2250 ) ,
( ‘buzzer’ , 4625000 , ‘USB’ , 2500 ) ,
( ‘sqwheel_night’ , 3828000 , ‘USB’ , 2500 ) ,
( ‘sqwheel_day’ , 5473000 , ‘USB’ , 2500 ) ,
( ‘pip_night’ , 3756000 , ‘USB’ , 2500 ) ,
( ‘pip_day’ , 5448000 , ‘USB’ , 2500 ) ,
]
parser = argparse . ArgumentParser (
description = ‘Monitor HF stations and record signal level’
)
parser . add_argument (
‘-p’ , ‘—port’ , metavar = ‘N’ , type = int , default = 7356 ,
help = ‘gqrx port’ )
parser . add_argument (
‘—host’ , metavar = ‘H’ , type = str , default = ‘127.0.0.1’ ,
help = ‘gqrx host’ )
parser . add_argument (
‘-s’ , ‘—sleep’ , metavar = ‘S’ , type = int , default = 300 ,
help = ‘sleep time before scans in seconds’ )
args = parser . parse_args ( )
client = GqrxClient ( args. host , args. port )
print ( «Connected.» , file = sys . stderr )
while True :
print ( «Scanning frequencies…» , file = sys . stderr )
tstamp = datetime . utcnow ( ) . strftime ( «%Y-%m-%d %H:%M:%SZ» )
for ( name , freq , mode , width ) in scan:
client. set_mode ( mode , width )
client. set_frequency ( freq )
levels = [ ]
for i in range ( 0 , 50 ) :
levels + = [ str ( client. get_signal_strength ( ) ) ]
time . sleep ( .1 )
print ( «{};{};{}» . format ( tstamp , name , «;» . join ( levels ) ) )
print ( «Sleeping {} seconds» . format ( args. sleep ) , file = sys . stderr )
time . sleep ( args. sleep )
Номерные радиостанции нам уже знакомы. WWV и CHU — это радиостанции точного времени, расположенные недалеко от Форт-Коллинс, США и Оттавы, Канада. WWV и CHU широчайше известны среди радиолюбителей Северной Америки. В России сигналы от этих радиостанция бывают слышны не всегда. Зато у нас превосходно слышна RWM . Это радиостанция эталонного времени, расположенная в Москве.
Идея была в том, чтобы понаблюдать за уровнем сигнала от этих радиостанций в течение суток, а затем построить графики в Matplotlib . В итоге получилось довольно красиво.
Так, например, выглядят графики «скрипучего колеса»:
На графике видно, что переключение радиостанции на дневную частоту было произведено около 05:00 UTC, а обратно на ночную — около 18:00 UTC. Время переключения соответствует приведенному на сайте priyom.org . По графику также можно сделать выводы о том, как менялось прохождение в течение дня.
График WWV вышел таким:
Видим, что максимальный уровен сигнала наблюдался около 10:00 UTC на частоте 15 МГц. Меня такой поворот ничуть не удивляет. Действительно, работая на 20 метрах в телеграфе на общий вызов где-то с 10:00 до 12:00 UTC, мне нередко удавалось провести QSO с Северной Америкой.
Полный код скрипта, строящего графики:
import matplotlib as mpl
import matplotlib . pyplot as plt
import argparse
import csv
import re
parser = argparse . ArgumentParser ( description = ‘Plot dBFS vs time’ )
parser . add_argument (
‘-i’ , ‘—input’ , metavar = ‘IF’ , type = str , required = True ,
help = ‘input file’ )
parser . add_argument (
‘-o’ , ‘—output’ , metavar = ‘OF’ , type = str , required = True ,
help = ‘output file’ )
parser . add_argument (
‘-s’ , ‘—stations’ , metavar = ‘S’ , type = str , required = True ,
help = ‘comma separated list of station names’ )
args = parser . parse_args ( )
stations = args. stations . split ( «,» )
hours = [ ]
values = { }
with open ( args. input , newline = » ) as f:
for row in csv . reader ( f , delimiter = ‘;’ , quotechar = ‘»‘ ) :
m = re . search ( «[ d -]{10} ( d {2}):» , row [ 0 ] )
h = m. group ( 1 )
name = row [ 1 ]
vals = [ float ( x ) for x in row [ 2 : ] ]
new_val = round ( max ( vals ) )
if name not in stations:
continue
if name not in values:
values [ name ] = [ ]
if hours == [ ] or hours [ — 1 ] != h:
hours + = [ h ]
if len ( values [ name ] ) < len ( hours ) :
# make a new list of values for a given hour
values [ name ] + = [ [ new_val ] ]
else :
# append to the list of values for a given hour
values [ name ] [ — 1 ] + = [ new_val ]
dpi = 80
fig = plt. figure ( dpi = dpi , figsize = ( 512 / dpi , 384 / dpi ) )
mpl. rcParams . update ( { ‘font.size’ : 10 } )
plt. xlabel ( ‘UTC Time’ )
plt. ylabel ( ‘dBFS’ )
ax = plt. axes ( )
ax. yaxis . grid ( True )
for name in values. keys ( ) :
vals = [ min ( x ) for x in values [ name ] ]
plt. plot ( hours , vals , linestyle = ‘solid’ , label = name )
plt. legend ( loc = ‘upper left’ , frameon = False )
fig. savefig ( args. output )
Зачем все это может быть нужно? Допустим, меня интересует день, время и частота, оптимальные для приема какой-то конкретной радиостанции, например, Kyodo News . Я могу понаблюдать за ее частотами при помощи приведенных скриптов. Теперь мне известно, как проходила радиостанция в заданный день. С хорошей степенью уверенности можно утверждать, что условия прохождения будут похожими через 27 дней. В двух словах, эффект связан с периодом обращения Солнца вокруг своей оси при наблюдении с Земли. Этот момент более подробно описан в соответствующей литературе. Список книг, посвященных прохождению, ранее приводился в заметке Прогнозируем прохождение на КВ с помощью VOACAP .