gr-satnogs/python/doppler_compensation.py

135 lines
6.0 KiB
Python

#! /usr/bin/python3
#
# gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module
#
# Copyright (C) 2019
# Libre Space Foundation <http://libre.space>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
from gnuradio import gr
from gnuradio import filter
from gnuradio.filter import firdes
from gnuradio.filter import pfb
from . import satnogs_swig as satnogs
import weakref
class doppler_compensation(gr.hier_block2):
""" Doppler compensation and resampling block
This block performs Doppler compensation and resampling.
Rather than using directly the available Doppler correction blocks,
based on the user parameters of the incoming sampling rate and the
desired target sampling rate, it applies proper decimation on the signal
so the frequency shift to be applied to a smaller sampling rate,
reducing significantly the CPU resources required. At the previous
architecture (gr-satnogs 1.x.x) we used seperate blocks for the doppler
correction and the LO digital shift, operating at the device sampling rate.
These two blocks, performing almost the same operation, contributed to a
30% CPU usage of the overall application. Now the LO is compensated by
the Doppler correction block, taking into account at the same time the
Doppler drift.
After the digital LO shift, the Doppler corrected signal is passed through
an Polyphase Arbitrary Resampler, to match exactly the sampling rate
requested by the user.
Parameters
----------
samp_rate : double
The sampling rate of the input signal
sat_freq : double
The target frequency of the satellite. This highly depends on the
Doppler frequency messages. Some blocks (e.g rigctld) may produce
the target observed RF frequency, whereas others (e.g gr-leo) are
sending messages containing only the frequency drift. In the first
case, this field should contain the actual frequency of the satellite.
On the other hand, for the second case this field should be 0.
lo_offset : double
The LO offset from the actuall observation frequency. In most cases,
we use a LO offset to avoid the DC spikes. This offset should be positive
if the hardware RF frequency is less than the target frequency, negative
otherwise.
compensate : bool
This parameter instructs the Doppler correction block to apply doppler
compensation. The LO offset compensation is still applied regardless of
the value of this field.
"""
def __init__(self, samp_rate, sat_freq, lo_offset, out_samp_rate,
compensate=True):
gr.hier_block2.__init__(self,
"doppler_compensation",
gr.io_signature(1, 1, gr.sizeof_gr_complex),
gr.io_signature(1, 1, gr.sizeof_gr_complex))
self.message_port_register_hier_in('doppler')
# Decimate the incoming signal using the rational resampler first.
# Then perform the doppler correctio and a fractional resampler
# to a lower rate to save some CPU cycles
if(out_samp_rate > samp_rate):
gr.log.info("satnogs.doppler_compensation: Output sampling rate sould be "
"less or equal the device sampling rate")
raise AttributeError
if(lo_offset > samp_rate // 4):
gr.log.info("satnogs.doppler_compensation: The LO offset frequency "
"should be less than samp_rate/4")
raise AttributeError
self.decimation = 1
# FIXME: As we now the direction of the LO offset maybe we can narrow
# more
min_s = max(abs(4 * lo_offset), out_samp_rate + 4 * abs(lo_offset)) + 48e3
while(samp_rate / (self.decimation + 1) > min_s):
self.decimation = self.decimation + 1
print(self.decimation)
if(self.decimation > 1):
# As we now that we are going to only decimate, we use a LPF
# filter instead of a resampler taking care of the passband
# to avoid aliasing. Also due to the fact that we know the output
# sampling rate we try to relax the transition woidth as much
# as possible to reduce CPU usage
self.dec = filter.fir_filter_ccf(self.decimation,
firdes.low_pass(1, samp_rate, out_samp_rate / 2.0,
(samp_rate / 2.0) / self.decimation - out_samp_rate / 2.0,
firdes.WIN_HAMMING))
# Even with no doppler compensation enabled we need this
# block to correct the LO offset
self.doppler = satnogs.coarse_doppler_correction_cc(sat_freq,
lo_offset,
samp_rate / self.decimation)
self.pfb_rs = pfb.arb_resampler_ccf(
out_samp_rate / (samp_rate / self.decimation),
taps=None,
flt_size=32)
self.pfb_rs.declare_sample_delay(0)
if(self.decimation > 1):
self.connect((self, 0), (self.dec, 0),
(self.doppler, 0), (self.pfb_rs, 0), (self, 0))
else:
self.connect((self, 0), (self.doppler, 0),
(self.pfb_rs, 0), (self, 0))
if(compensate):
self.msg_connect(weakref.proxy(self), "doppler",
self.doppler, "freq")