class PSK31Demodulator(gr.hier_block2, ExportedState): '''Demodulate PSK31.''' __symbol_rate = 31.25 __demod_rate = 4000 __cutoff = __symbol_rate __transition_width = __symbol_rate __audio_frequency = 1500 def __init__(self, mode, input_rate=0, context=None): assert input_rate > 0 self.__input_rate = input_rate gr.hier_block2.__init__( self, type(self).__name__, gr.io_signature(1, 1, gr.sizeof_gr_complex), gr.io_signature(1, 1, gr.sizeof_float)) channel_filter = self.__make_channel_filter() self.__text_cell = StringSinkCell(encoding='us-ascii') self.__text_sink = self.__text_cell.create_sink_internal() # The output of the channel filter is oversampled so we don't need to # interpolate for the audio monitor. So we'll downsample before going into # the demodulator. samp_per_sym = 8 downsample = self.__demod_rate / samp_per_sym / self.__symbol_rate assert downsample % 1 == 0 downsample = int(downsample) self.connect( self, channel_filter, blocks.keep_one_in_n(gr.sizeof_gr_complex, downsample), psk31_coherent_demodulator_cc(samp_per_sym=samp_per_sym), psk31_constellation_decoder_cb( varicode_decode=True, differential_decode=True), self.__text_sink) self.connect( channel_filter, blocks.rotator_cc(rotator_inc(self.__demod_rate, self.__audio_frequency)), blocks.complex_to_real(vlen=1), analog.agc2_ff( reference=dB(-10), attack_rate=8e-1, decay_rate=8e-1), self) def __make_channel_filter(self): '''Return the channel filter. psk31_demodulator_cbc includes filters, so this filter will be wide to assure the passband has no group delay and make it easier to listen to. Output has frequencies from -250 to +250. ''' return MultistageChannelFilter( input_rate=self.__input_rate, output_rate=self.__demod_rate, cutoff_freq=250 - 25, transition_width=25) def state_def(self): for d in super(PSK31Demodulator, self).state_def(): yield d # TODO make this possible to be decorator style yield 'text', self.__text_cell @exported_value(type=BandShape, changes='never') def get_band_shape(self): """implement IDemodulator""" return BandShape.bandpass_transition( low=-self.__cutoff, high=self.__cutoff, transition=self.__transition_width) def get_output_type(self): """implement IDemodulator""" return SignalType(kind='MONO', sample_rate=self.__demod_rate)
class PSK31Demodulator(gr.hier_block2, ExportedState): '''Demodulate PSK31.''' __symbol_rate = 31.25 __demod_rate = 4000 __cutoff = __symbol_rate __transition_width = __symbol_rate __audio_frequency = 1500 def __init__(self, mode, input_rate=0, context=None): assert input_rate > 0 self.__input_rate = input_rate gr.hier_block2.__init__(self, type(self).__name__, gr.io_signature(1, 1, gr.sizeof_gr_complex), gr.io_signature(1, 1, gr.sizeof_float)) channel_filter = self.__make_channel_filter() self.__text_cell = StringSinkCell(encoding='us-ascii') self.__text_sink = self.__text_cell.create_sink_internal() # The output of the channel filter is oversampled so we don't need to # interpolate for the audio monitor. So we'll downsample before going into # the demodulator. samp_per_sym = 8 downsample = self.__demod_rate / samp_per_sym / self.__symbol_rate assert downsample % 1 == 0 downsample = int(downsample) self.connect( self, channel_filter, blocks.keep_one_in_n(gr.sizeof_gr_complex, downsample), psk31_coherent_demodulator_cc(samp_per_sym=samp_per_sym), psk31_constellation_decoder_cb(varicode_decode=True, differential_decode=True), self.__text_sink) self.connect( channel_filter, blocks.rotator_cc( rotator_inc(self.__demod_rate, self.__audio_frequency)), blocks.complex_to_real(vlen=1), analog.agc2_ff(reference=dB(-10), attack_rate=8e-1, decay_rate=8e-1), self) def __make_channel_filter(self): '''Return the channel filter. psk31_demodulator_cbc includes filters, so this filter will be wide to assure the passband has no group delay and make it easier to listen to. Output has frequencies from -250 to +250. ''' return MultistageChannelFilter(input_rate=self.__input_rate, output_rate=self.__demod_rate, cutoff_freq=250 - 25, transition_width=25) def state_def(self): for d in super(PSK31Demodulator, self).state_def(): yield d # TODO make this possible to be decorator style yield 'text', self.__text_cell @exported_value(type=BandShape, changes='never') def get_band_shape(self): """implement IDemodulator""" return BandShape.bandpass_transition( low=-self.__cutoff, high=self.__cutoff, transition=self.__transition_width) def get_output_type(self): """implement IDemodulator""" return SignalType(kind='MONO', sample_rate=self.__demod_rate)
class RTTYDemodulator(gr.hier_block2, ExportedState): '''Demodulate typical amateur RTTY. Input should be centered on the mark frequency. (By convention, RTTY contacts are logged at the mark frequency.) Assumptions: - 45.45 baud - 170 Hz spacing - Mark tone high TODO: make these assumptions parameters. ''' __spacing = 170 __demod_rate = 6000 __low_cutoff = __spacing * -2 __high_cutoff = __spacing __transition_width = __spacing def __init__(self, mode, input_rate=0, context=None): assert input_rate > 0 self.__input_rate = input_rate gr.hier_block2.__init__( self, type(self).__name__, gr.io_signature(1, 1, gr.sizeof_gr_complex * 1), gr.io_signature(1, 1, gr.sizeof_float * 1)) channel_filter = self.__make_channel_filter() self.__text_cell = StringSinkCell(encoding='us-ascii') self.__text_sink = self.__text_cell.create_sink_internal() self.connect( self, channel_filter, self.__make_demodulator(), self.__text_sink) self.connect( channel_filter, self.__make_audio_filter(), blocks.rotator_cc(rotator_inc(self.__demod_rate, 2000 + self.__spacing / 2)), blocks.complex_to_real(vlen=1), analog.agc2_ff( reference=dB(-10), attack_rate=8e-1, decay_rate=8e-1), self) def __make_demodulator(self): return rtty_demod_cb( samp_rate=self.__demod_rate, # 6000 / 45.45 / 11 = 12.0012 samples per bit. We want a number # that's not too small to avoid quantization errors in the # timing. decimation=11, mark_freq=0, space_freq=-self.__spacing) def __make_channel_filter(self): '''Return the channel filter. rtty_demod_cb includes filters, so here we just need a broad, cheap filter to decimate. ''' return MultistageChannelFilter( input_rate=self.__input_rate, output_rate=self.__demod_rate, cutoff_freq=self.__spacing * 5, transition_width=self.__spacing * 5) def __make_audio_filter(self): '''Return a filter which selects just the RTTY signal and shifts to AF. This isn't anywhere in the digital processing chain, so doesn't need to be concerned with signal fidelity as long as it sounds good. ''' taps = firdes.complex_band_pass( gain=1.0, sampling_freq=self.__demod_rate, low_cutoff_freq=self.__low_cutoff, high_cutoff_freq=self.__high_cutoff, transition_width=self.__transition_width) af_filter = grfilter.fir_filter_ccc( decimation=1, taps=taps) return af_filter def state_def(self): for d in super(RTTYDemodulator, self).state_def(): yield d # TODO make this possible to be decorator style yield 'text', self.__text_cell @exported_value(type=BandShape, changes='never') def get_band_shape(self): """implement IDemodulator""" return BandShape.bandpass_transition( low=self.__low_cutoff, high=self.__high_cutoff, transition=self.__transition_width, markers={ -self.__spacing: u'S', 0: u'M', }) def get_output_type(self): """implement IDemodulator""" return SignalType(kind='MONO', sample_rate=self.__demod_rate)
class TestGRSinkCell(unittest.TestCase): # for lint cell = None sink = None dtype = None def setUp(self): self.info_value = 1000 def info_getter(): self.info_value += 1 return (self.info_value,) self.info_getter = info_getter def setUpForBulkData(self): self.cell = ElementSinkCell( info_getter=self.info_getter, type=BulkDataT(array_format='f', info_format='d'), interest_tracker=LoopbackInterestTracker()) self.dtype = numpy.uint8 self.sink = self.cell.create_sink_internal(self.dtype) def setUpForUnicodeString(self): self.cell = StringSinkCell( encoding='utf-8', interest_tracker=LoopbackInterestTracker()) self.dtype = numpy.uint8 self.sink = self.cell.create_sink_internal(self.dtype) def inject_bytes(self, bytestr): # calling implementation of sink block to not have to set up a flowgraph self.sink.work([numpy.frombuffer(bytestr, dtype=self.dtype)], []) # We wait because StringSinkCell uses reactor.callFromThread. We've skipped setting up the GNU Radio block thread, but it doesn't know that (and Clock does not support callFromThread). return deferLater(the_reactor, 0.0, lambda: None) @defer.inlineCallbacks def test_element_get_not_mutated(self): self.setUpForBulkData() gotten = self.cell.get() self.assertEqual(gotten, []) yield self.inject_bytes(b'ab') self.assertEqual(self.cell.get(), [ BulkDataElement(data=b'a', info=(1001,)), BulkDataElement(data=b'b', info=(1001,)) ]) self.assertEqual(gotten, []) # not mutating list @defer.inlineCallbacks def test_element_delta_subscriber(self): self.setUpForBulkData() st = CellSubscriptionTester(self.cell, delta=True) yield self.inject_bytes(b'ab') st.expect_now([ BulkDataElement(data=b'a', info=(1001,)), BulkDataElement(data=b'b', info=(1001,)) ], kind='append') st.unsubscribe() yield self.inject_bytes(b'ignored') st.advance() @defer.inlineCallbacks def test_element_plain_subscriber(self): self.setUpForBulkData() st = CellSubscriptionTester(self.cell, delta=False) yield self.inject_bytes(b'ab') st.expect_now([ BulkDataElement(data=b'a', info=(1001,)), BulkDataElement(data=b'b', info=(1001,)) ], kind='value') st.unsubscribe() yield self.inject_bytes(b'ignored') st.advance() @defer.inlineCallbacks def test_string_get(self): self.setUpForUnicodeString() gotten = self.cell.get() self.assertTrue(isinstance(gotten, six.text_type)) self.assertEqual(gotten, u'') yield self.inject_bytes('abç'.encode('utf-8')) gotten = self.cell.get() self.assertTrue(isinstance(gotten, six.text_type)) self.assertEqual(gotten, 'abç') # now append some more to test the buffer yield self.inject_bytes('deƒ'.encode('utf-8')) self.assertEqual(self.cell.get(), 'abçdeƒ') @defer.inlineCallbacks def test_string_coding_error(self): self.setUpForUnicodeString() yield self.inject_bytes(b'ab\xFF') self.assertEqual(self.cell.get(), 'ab\uFFFD') @defer.inlineCallbacks def test_string_delta_subscriber(self): self.setUpForUnicodeString() st = CellSubscriptionTester(self.cell, delta=True) yield self.inject_bytes('abç'.encode('utf-8')) st.expect_now('abç', kind='append') st.unsubscribe() yield self.inject_bytes(b'ignored') st.advance() @defer.inlineCallbacks def test_string_plain_subscriber(self): self.setUpForUnicodeString() st = CellSubscriptionTester(self.cell, delta=False) yield self.inject_bytes('abç'.encode('utf-8')) st.expect_now('abç') yield self.inject_bytes('deƒ'.encode('utf-8')) # TODO: This is not the correct result; it should match get(). # Poller currently does not deal with attaching simple subscribers correctly st.expect_now('deƒ')