Skip to content

Commit

Permalink
add SSB-family demodulator
Browse files Browse the repository at this point in the history
  • Loading branch information
baskiton committed Jul 23, 2024
1 parent 7604bbc commit c9d31a1
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 2 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ Each frequency object contain:
| raw_out_subformat | String | _Optional. Only for **RAW** decoder._ WAV output subformat. `FLOAT` by default |
| proto_deframer | String | _Optional. Only for **PROTO** decoder._ Name of the gr-satellites deframer. See [proto](#proto) for detail. |
| proto_options | String | _Optional. Only for **PROTO** decoder._ Deframer options. See [proto](#proto) for detail. |
| ssb_bandwidth | Number | _Optional. Only for **SSB**-family mode._ SSB Bandwidth, Hz. `4600` for DSB nd `2800` foa another by default |
| ssb_out_sr | Number | _Optional. Only for **SSB**-family mode._ SSB out sample rate, Hz. `8000` by default |

* `iq_waterfall` Create waterfall. Mapping with options (might be empty):
* `fft_size` FFT size (int) `4096` by default
Expand All @@ -225,6 +227,9 @@ Each frequency object contain:
* `FSK`
* `GFSK`
* `GMSK`
* `USB`
* `LSB`
* `DSB`

#### decoders
* `RAW` Saved to 2-channel float32 WAV file with `bandwidth` sample rate. Other parameters:
Expand Down
71 changes: 71 additions & 0 deletions sats_receiver/gr_modules/demodulators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import satellites.components.demodulators as grs_demodulators

from sats_receiver.gr_modules.epb import DelayOneImag
from sats_receiver import utils


class QpskDemod(gr.gr.hier_block2):
Expand Down Expand Up @@ -180,3 +181,73 @@ class GfskDemod(FskDemod):

class GmskDemod(FskDemod):
pass


class SsbDemod(gr.gr.hier_block2):

def __init__(self,
samp_rate: Union[int, float],
mode: utils.SsbMode,
bandwidth: Union[int, float] = None,
out_rate: Union[int, float] = 8000):
super(SsbDemod, self).__init__(
f'{mode.name} Demodulator',
gr.gr.io_signature(1, 1, gr.gr.sizeof_gr_complex),
gr.gr.io_signature(1, 1, gr.gr.sizeof_float)
)

max_bw = 12000
min_bw = 500
if bandwidth is None:
if mode == utils.SsbMode.DSB:
min_bw = 1000
bandwidth = 4600
elif mode == utils.SsbMode.USB:
bandwidth = 2800
elif mode == utils.SsbMode.LSB:
bandwidth = 2800
else:
raise ValueError(f'Unknown mode: {mode}')

bandwidth = max(min(bandwidth, max_bw), min_bw)

if mode == utils.SsbMode.DSB:
left = -bandwidth // 2
right = bandwidth // 2
elif mode == utils.SsbMode.USB:
left = 0
right = bandwidth
elif mode == utils.SsbMode.LSB:
left = -bandwidth
right = 0
else:
raise ValueError(f'Unknown mode: {mode}')

decim = samp_rate // max(out_rate, bandwidth // 2)
work_rate = samp_rate // decim
resamp_gcd = math.gcd(work_rate, out_rate)

self.agc = gr.analog.agc2_cc(50, 5, 1.0, 1.0, 65536)
bpf = gr.filter.firdes.complex_band_pass(1, samp_rate, left, right, 100)
self.xlate_fir = gr.filter.freq_xlating_fir_filter_ccc(decim, bpf, 0, samp_rate)
self.ctf = gr.blocks.complex_to_float(1)
self.add = gr.blocks.add_vff(1)
self.mult_const = gr.blocks.multiply_const_ff(0.3)
self.resamp = gr.filter.rational_resampler_fff(
interpolation=(out_rate // resamp_gcd),
decimation=(work_rate // resamp_gcd),
taps=[],
fractional_bw=0,
)

self.connect(
self,
self.agc,
self.xlate_fir,
self.ctf,
self.add,
self.mult_const,
self.resamp,
self,
)
self.connect((self.ctf, 1), (self.add, 1))
12 changes: 12 additions & 0 deletions sats_receiver/gr_modules/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ def __init__(self,
self.post_demod = None
self.iq_demod = 0

elif self.mode in (utils.Mode.USB, utils.Mode.LSB, utils.Mode.DSB):
self.demodulator = demodulators.SsbDemod(self.bandwidth, utils.SsbMode[self.mode.name])
self.iq_demod = 0

channels = getattr(self.demodulator, 'channels', (self.bandwidth,))
self.decoders = []
if self.decode == utils.Decode.APT:
Expand Down Expand Up @@ -420,6 +424,14 @@ def raw_out_subformat(self) -> utils.RawOutSubFormat:
return utils.RawOutSubFormat[self.config.get('raw_out_subformat',
utils.RawOutDefaultSub[self.raw_out_format.name].value.name)]

@property
def ssb_bandwidth(self):
return self.config.get('ssb_bandwidth')

@property
def ssb_out_sr(self):
return self.config.get('ssb_out_sr', 8000)

@property
def iq_waterfall(self) -> Mapping:
return self.config.get('iq_waterfall')
Expand Down
9 changes: 9 additions & 0 deletions sats_receiver/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class Mode(enum.StrEnum):
FSK = enum.auto()
# AFSK = enum.auto()
# BPSK = enum.auto()
LSB = enum.auto()
USB = enum.auto()
DSB = enum.auto()


class Decode(enum.StrEnum):
Expand Down Expand Up @@ -143,6 +146,12 @@ class RawFileType(enum.StrEnum):
AUDIO = enum.auto()


class SsbMode(enum.StrEnum):
LSB = enum.auto()
USB = enum.auto()
DSB = enum.auto()


class Phase(enum.IntEnum):
PHASE_0 = 0
PHASE_90 = 1
Expand Down
Binary file added tests/files/KASHIWA_USB.wav
Binary file not shown.
35 changes: 33 additions & 2 deletions tests/test_decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from PIL import Image, ExifTags
from sats_receiver import utils
from sats_receiver.gr_modules.decoders import Decoder, AptDecoder, CcsdsConvConcatDecoder, ProtoDecoder, RawDecoder, SatellitesDecoder, SstvDecoder
from sats_receiver.gr_modules.demodulators import FskDemod, SstvQuadDemod
from sats_receiver.gr_modules.demodulators import FskDemod, SstvQuadDemod, SsbDemod
from sats_receiver.gr_modules.epb.prober import Prober
from sats_receiver.observer import Observer
from sats_receiver.systems.apt import Apt
Expand Down Expand Up @@ -118,7 +118,7 @@ def __init__(self,
self.connect((self.wav_src, i), (self.ftc, i))
modules = [
self.ftc,
self.thr,
# self.thr,
# self.shifter,
# self.pll,
]
Expand Down Expand Up @@ -508,3 +508,34 @@ def test_raw_channels_2(self):
wav = gr.blocks.wavfile_source(str(fp), False)
self.assertEqual(2, wav.channels())
self.assertEqual(wav_samp_rate, wav.sample_rate())

def test_ssb_usb(self):
wav_fp = FILES / 'KASHIWA_USB.wav'
wav_samp_rate = 48000
ssb_bandwidth = 2400
ssb_out_sr = 8000

demod = SsbDemod(wav_samp_rate, utils.SsbMode.USB, ssb_bandwidth, ssb_out_sr)
decoder = RawDecoder(self.recorder, ssb_out_sr, iq_in=0)
self.tb = DecoderTopBlock(2, wav_fp, decoder, self.executor, demod=demod)
self.tb.start()

while self.tb.prober.changes():
time.sleep(self.tb.prober.measure_s)
time.sleep(self.tb.prober.measure_s)

self.tb.stop()
self.tb.wait()

x = self.tb.executor.action(TIMEOUT)
self.assertIsInstance(x, tuple)
dtype, sat_name, observation_key, files, end_time = x
self.assertEqual(utils.Decode.RAW, dtype)
self.assertEqual(self.recorder.satellite.name, sat_name)
self.assertEqual(1, len(files))
self.assertIn(utils.RawFileType.AUDIO, files)

fp = files[utils.RawFileType.AUDIO]
wav = gr.blocks.wavfile_source(str(fp), False)
self.assertEqual(1, wav.channels())
self.assertEqual(ssb_out_sr, wav.sample_rate())

0 comments on commit c9d31a1

Please sign in to comment.