Exemplo n.º 1
0
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)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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ƒ')