Ejemplo n.º 1
0
    def __demod(self):
        chunks = self._samples.size / DEMOD_BINS
        if chunks == 0:
            Utils.error('Sample time too long')

        signals = numpy.empty((chunks, len(self._frequencies)),
                              dtype=numpy.float16)

        # Split samples into chunks
        freqBins = fftpack.fftfreq(DEMOD_BINS, 1. / self._fs)
        freqInds = freqBins.argsort()

        for chunkNum in range(chunks):
            if self._timing is not None:
                self._timing.start('Demod')

            chunkStart = chunkNum * DEMOD_BINS
            chunk = self._samples[chunkStart:chunkStart + DEMOD_BINS]

            # Analyse chunk
            fft = fftpack.fft(chunk, overwrite_x=True)
            fft /= DEMOD_BINS
            mags = numpy.absolute(fft)
            bins = numpy.searchsorted(freqBins[freqInds], self._frequencies)
            levels = mags[freqInds][bins]
            signals[chunkNum] = levels

            if self._timing is not None:
                self._timing.stop()

        signals = signals.T
        self.__smooth(signals, 4)

        return signals
Ejemplo n.º 2
0
    def start(self, _timing=None):
        sampleSize = self.fs * SAMPLE_TIME
        sampleBlocks = self.iq.size / sampleSize
        if sampleBlocks == 0:
            Utils.error('Capture too short')

        for blockNum in range(sampleBlocks):
            sampleStart = blockNum * sampleSize
            samples = self.iq[sampleStart:sampleStart + sampleSize]

            self._callback(samples)
Ejemplo n.º 3
0
    def start(self, _timing=None):
        sampleSize = self.fs * SAMPLE_TIME
        sampleBlocks = self.iq.size / sampleSize
        if sampleBlocks == 0:
            Utils.error('Capture too short')

        for blockNum in range(sampleBlocks):
            sampleStart = blockNum * sampleSize
            samples = self.iq[sampleStart:sampleStart + sampleSize]

            self._callback(samples)
Ejemplo n.º 4
0
    def start(self, _timing=None):
        length = os.path.getsize(self._filename) / 2
        sampleSize = int(self.fs * SAMPLE_TIME)
        sampleBlocks = int(length / sampleSize)
        if sampleBlocks == 0:
            Utils.error('Capture too short')

        f = open(self._filename, 'rb')

        for _i in range(sampleBlocks):
            data = bytearray(f.read(sampleSize * 2))
            iq = numpy.array(data).astype(numpy.float32).view(numpy.complex64)
            iq /= 255.

            self._callback(iq)

        f.close()
Ejemplo n.º 5
0
    def start(self, _timing=None):
        length = os.path.getsize(self._filename) / 2
        sampleSize = int(self.fs * SAMPLE_TIME)
        sampleBlocks = int(length / sampleSize)
        if sampleBlocks == 0:
            Utils.error('Capture too short')

        f = open(self._filename, 'rb')

        for _i in range(sampleBlocks):
            data = bytearray(f.read(sampleSize * 2))
            iq = numpy.array(data).astype(numpy.float32).view(numpy.complex64)
            iq /= 255.

            self._callback(iq)

        f.close()
Ejemplo n.º 6
0
    def __find_pulses(self, signal, negIndices, posIndices, pulseWidths):
        pulse = None
        length = signal.size
        # Find pulses of pulseWidths
        widths = negIndices - posIndices

        for wMax, wMin in pulseWidths:
            posValid = numpy.where((widths > wMin) & (widths < wMax))[0]
            # Must have between 3 and 6 pulses
            if posValid.size > 2 and posValid.size < 7 and posValid.size == widths.size:
                pulseValid = posIndices[posValid]
                pulseRate = numpy.diff(pulseValid)
                pulseAvg = numpy.average(pulseRate)
                # Constant rate?
                maxDeviation = pulseAvg * PULSE_RATE_DEVIATION / 100.
                if numpy.std(pulseRate) < maxDeviation:
                    # Calculate frequency
                    freq = length / (pulseAvg * float(SAMPLE_TIME))
                    rate = freq * 60
                    # Limit to PULSE_RATES
                    closest = min(PULSE_RATES, key=lambda x: abs(x - rate))
                    if (abs(closest -
                            rate)) <= closest * PULSE_RATE_TOL / 100.0:
                        # Test for missing pulses
                        if pulseValid[0] - min(pulseRate) < 0 and pulseValid[
                                -1] + min(pulseRate) > length:
                            # Get pulse levels
                            level = 0
                            for posValid in range(len(pulseValid)):
                                pos = pulseValid[posValid]
                                width = widths[posValid]
                                pulseSignal = signal[pos:pos + width - 1]
                                level += numpy.average(pulseSignal)
                            level /= len(pulseValid)
                            # Store valid pulse
                            pulse = collar.Collar(
                                widths.size, freq * 60., level,
                                width * SAMPLE_TIME * 1000. / length)
                            break
                        elif self._debug is not None and self._debug.verbose:
                            Utils.error('Missing pulses', False)
                    elif self._debug is not None and self._debug.verbose:
                        Utils.error('Invalid rate {:.1f}PPM'.format(rate),
                                    False)
                elif self._debug is not None and self._debug.verbose:
                    msg = 'Collar rate deviation {:.1f} >= {:.1f}ms'
                    msg = msg.format(
                        1000 * numpy.std(pulseRate) * SAMPLE_TIME / length,
                        1000 * maxDeviation * SAMPLE_TIME / length)
                    Utils.error(msg, False)
            elif self._debug is not None and self._debug.verbose:
                Utils.error(
                    'Invalid number of pulses ({}) or invalid pulse widths'.
                    format(posValid.size), False)

        return pulse
Ejemplo n.º 7
0
    def __detect(self, signals, baseband):
        collars = []

        # Calculate valid pulse widths with PULSE_WIDTH_TOL tolerance
        sampleRate = signals.shape[1] / float(SAMPLE_TIME)
        pulseWidths = [width * sampleRate for width in sorted(PULSE_WIDTHS)]
        pulseWidths = Utils.calc_tolerances(pulseWidths, PULSE_WIDTH_TOL)

        signalNum = 0
        for signal in signals:
            if self._timing is not None:
                self._timing.start('Detect')

            self._signals.append(signal)

            (threshPos, threshNeg, posIndices,
             negIndices) = self.__find_edges(signal, pulseWidths)

            # Find CW collars
            pulse = self.__find_pulses(signal, negIndices, posIndices,
                                       pulseWidths)

            # Find AM collars
            if pulse is None:
                if self._debug is not None and not self._debug.disableAm:
                    am, posIndicesAm, negIndicesAm = self.__find_am(
                        signal, posIndices, negIndices)
                if self._debug is not None:
                    if not self._debug.disableAm and am is not None:
                        pulse = self.__find_pulses(am, negIndicesAm,
                                                   posIndicesAm, pulseWidths)
                        if pulse is not None:
                            pulse.mod = collar.AM
                            posIndices = posIndicesAm
                            negIndices = negIndicesAm
            else:
                pulse.mod = collar.CW

            if pulse is not None:
                pulse.signalNum = signalNum
                freq = self._frequencies[signalNum] + baseband
                freq = int(round(freq / CHANNEL_SPACE) * CHANNEL_SPACE)
                pulse.freq = freq
                pulse.rate = min(PULSE_RATES,
                                 key=lambda x: abs(x - pulse.rate))
                collars.append(pulse)

            if self._timing is not None:
                self._timing.stop()

            if self._debug is not None:
                self._debug.callback_edge(baseband, signal, signalNum, pulse,
                                          self._frequencies, threshPos,
                                          threshNeg, posIndices, negIndices)

            signalNum += 1

        return collars
Ejemplo n.º 8
0
    def __find_tone(self, signal, indices, freqs):
        if not len(indices):
            return None, None

        sampleRate = signal.size / float(SAMPLE_TIME)
        periods = [sampleRate / freq for freq in freqs]
        periods = Utils.calc_tolerances(periods, TONE_TOL)

        # Edge widths
        if indices[0] != 0:
            indices = numpy.insert(indices, 0, 0)
        widths = numpy.diff(indices)

        # Count valid widths for each period
        counts = []
        for maxPeriod, minPeriod in periods:
            valid = (widths > minPeriod) & (widths < maxPeriod)
            counts.append(numpy.sum(valid))
        # Find maximum
        maxCounts = max(counts)
        if maxCounts == 0:
            if self._debug is not None and self._debug.verbose:
                Utils.error('No tone found', False)
            return None, None
        maxPos = counts.index(maxCounts)
        maxPeriod, minPeriod = periods[maxPos]
        # Matching widths
        periodsValid = (widths > minPeriod) & (widths < maxPeriod)
        periodAvg = numpy.average(widths[periodsValid])
        freq = sampleRate / periodAvg

        # Create pulses from signal
        pulse = numpy.zeros((signal.size), dtype=numpy.float16)
        pos = 0
        for i in range(widths.size):
            width = widths[i]
            valid = periodsValid[i]
            signalPos = indices[i]
            level = numpy.average(signal[signalPos:signalPos + width])
            level = abs(level)
            pulse[pos:pos + width].fill(valid * level)
            pos += width

        return freq, pulse
Ejemplo n.º 9
0
    def search(self):
        if self._samples.size < SCAN_BINS:
            Utils.error('Sample too short')

        if self._timing is not None:
            self._timing.start('Scan')

        f, l = psd(self._samples, SCAN_BINS, self._fs)

        decibels = 10 * numpy.log10(l)

        freqIndices = self.__peak_detect(decibels)

        self._freqs = f
        self._levels = decibels
        self._peaks = decibels[freqIndices]
        freqs = f[freqIndices]

        if self._timing is not None:
            self._timing.stop()

        return freqs
Ejemplo n.º 10
0
    def search(self):
        if self._samples.size < SCAN_BINS:
            Utils.error('Sample too short')

        if self._timing is not None:
            self._timing.start('Scan')

        f, l = psd(self._samples, SCAN_BINS, self._fs)

        decibels = 10 * numpy.log10(l)

        freqIndices = self.__peak_detect(decibels)

        self._freqs = f
        self._levels = decibels
        self._peaks = decibels[freqIndices]
        freqs = f[freqIndices]

        if self._timing is not None:
            self._timing.stop()

        return freqs
Ejemplo n.º 11
0
    def __init__(self, filename, noiseLevel, callback):
        self._callback = callback

        name = os.path.split(filename)[1]

        print 'Wav file:'
        print '\tLoading capture file: {}'.format(name)
        self.fs, data = wavfile.read(filename)

        if data.shape[1] != 2:
            Utils.error('Not an IQ file')
        if data.dtype not in ['int16', 'uint16', 'int8', 'uint8']:
            Utils.error('Unexpected format')

        # Get baseband from filename
        regex = re.compile(r'_(\d+)kHz_IQ')
        matches = regex.search(name)
        if matches is not None:
            self.baseband = int(matches.group(1)) * 1000
        else:
            self.baseband = 0

        print '\tSample rate: {:.2f}MSPS'.format(self.fs / 1e6)
        print '\tLength: {:.2f}s'.format(float(len(data)) / self.fs)

        # Scale data to +/-1
        data = data.astype(numpy.float32, copy=False)
        data /= 256.
        # Convert right/left to complex numbers
        self.iq = 1j * data[..., 0]
        self.iq += data[..., 1]

        # Add noise
        if noiseLevel > 0:
            noiseI = numpy.random.uniform(-1, 1, self.iq.size)
            noiseQ = numpy.random.uniform(-1, 1, self.iq.size)

            self.iq += (noiseI + 1j * noiseQ) * 10. ** (noiseLevel / 10.)
Ejemplo n.º 12
0
    def __init__(self, filename, noiseLevel, callback):
        self._callback = callback

        name = os.path.split(filename)[1]

        print 'Wav file:'
        print '\tLoading capture file: {}'.format(name)
        self.fs, data = wavfile.read(filename)

        if data.shape[1] != 2:
            Utils.error('Not an IQ file')
        if data.dtype not in ['int16', 'uint16', 'int8', 'uint8']:
            Utils.error('Unexpected format')

        # Get baseband from filename
        regex = re.compile(r'_(\d+)kHz_IQ')
        matches = regex.search(name)
        if matches is not None:
            self.baseband = int(matches.group(1)) * 1000
        else:
            self.baseband = 0

        print '\tSample rate: {:.2f}MSPS'.format(self.fs / 1e6)
        print '\tLength: {:.2f}s'.format(float(len(data)) / self.fs)

        # Scale data to +/-1
        data = data.astype(numpy.float32, copy=False)
        data /= 256.
        # Convert right/left to complex numbers
        self.iq = 1j * data[..., 0]
        self.iq += data[..., 1]

        # Add noise
        if noiseLevel > 0:
            noiseI = numpy.random.uniform(-1, 1, self.iq.size)
            noiseQ = numpy.random.uniform(-1, 1, self.iq.size)

            self.iq += (noiseI + 1j * noiseQ) * 10. ** (noiseLevel / 10.)
Ejemplo n.º 13
0
    def __parse_arguments(self, argList=None):
        parser = argparse.ArgumentParser(description='Receiver testmode')

        parser.add_argument('-i', '--info', help='Display summary info',
                            action='store_true')
        parser.add_argument('-s', '--spectrum', help='Display capture spectrum',
                            action='store_true')
        parser.add_argument('-c', '--scan', help='Display signal search',
                            action='store_true')
        parser.add_argument('-e', '--edges', help='Display pulse edges',
                            type=float,
                            nargs='?', const=0, default=None)
        parser.add_argument('-a', '--am', help='Display AM detection',
                            action='store_true')
        parser.add_argument('-b', '--block', help='Block to process',
                            type=int, default=0)
        parser.add_argument('-da', '--disableAm', help='Disable AM detection',
                            action='store_true')
        parser.add_argument('--collars', help='Save capture if number of COLLARS not found',
                            type=int, default=None)
        parser.add_argument('-v', '--verbose', help='Be more verbose',
                            action='store_true')

        subparser = parser.add_subparsers(help='Source')

        parserWav = subparser.add_parser('wav')
        parserWav.add_argument('-n', '--noise', help='Add noise (dB)',
                               type=float, default=0)
        parserWav.add_argument('wav', help='IQ wav file')

        parserBin = subparser.add_parser('bin')
        parserBin.add_argument('bin', help='IQ bin file')

        parserRtl = subparser.add_parser('rtlsdr')
        parserRtl.add_argument('-f', '--frequency', help='RTLSDR frequency (MHz)',
                               type=float, required=True)
        parserRtl.add_argument('-g', '--gain', help='RTLSDR gain (dB)',
                               type=float, default=None)

        args = parser.parse_args(argList)

        if 'wav' in args and args.wav is not None and not os.path.isfile(args.wav):
            Utils.error('Cannot find wav file')

        if 'bin' in args and args.bin is not None and not os.path.isfile(args.bin):
            Utils.error('Cannot find bin file')

        if args.am and args.disableAm:
            Utils.error('AM detection disabled - will not display graphs', False)

        return args
Ejemplo n.º 14
0
    def __parse_arguments(self, argList=None):
        parser = argparse.ArgumentParser(description='Receiver testmode')

        parser.add_argument('-i', '--info', help='Display summary info',
                            action='store_true')
        parser.add_argument('-s', '--spectrum', help='Display capture spectrum',
                            action='store_true')
        parser.add_argument('-c', '--scan', help='Display signal search',
                            action='store_true')
        parser.add_argument('-e', '--edges', help='Display pulse edges',
                            type=float,
                            nargs='?', const=0, default=None)
        parser.add_argument('-a', '--am', help='Display AM detection',
                            action='store_true')
        parser.add_argument('-b', '--block', help='Block to process',
                            type=int, default=0)
        parser.add_argument('-da', '--disableAm', help='Disable AM detection',
                            action='store_true')
        parser.add_argument('--collars', help='Save capture if number of COLLARS not found',
                            type=int, default=None)
        parser.add_argument('-v', '--verbose', help='Be more verbose',
                            action='store_true')

        subparser = parser.add_subparsers(help='Source')

        parserWav = subparser.add_parser('wav')
        parserWav.add_argument('-n', '--noise', help='Add noise (dB)',
                               type=float, default=0)
        parserWav.add_argument('wav', help='IQ wav file')

        parserBin = subparser.add_parser('bin')
        parserBin.add_argument('bin', help='IQ bin file')

        parserRtl = subparser.add_parser('rtlsdr')
        parserRtl.add_argument('-f', '--frequency', help='RTLSDR frequency (MHz)',
                               type=float, required=True)
        parserRtl.add_argument('-g', '--gain', help='RTLSDR gain (dB)',
                               type=float, default=None)

        args = parser.parse_args(argList)

        if 'wav' in args and args.wav is not None and not os.path.isfile(args.wav):
            Utils.error('Cannot find wav file')

        if 'bin' in args and args.bin is not None and not os.path.isfile(args.bin):
            Utils.error('Cannot find bin file')

        if args.am and args.disableAm:
            Utils.error('AM detection disabled - will not display graphs', False)

        return args