SAMPLE_RATE = 2e6 class DeviceRTL(object):
    def __init__(self):
        self.isDevice = True
        self.indexRtl = None
        self.name = None
        self.serial = ''
        self.server = 'localhost'
        self.port = 1234
        self.gains = []
        self.gain = 0
        self.calibration = 0
        self.lo = 0
        self.offset = 250e3
        self.tuner = 0
        self.levelOff = 0

    def set(self, device):
        self.gain = device.gain
        self.calibration = device.calibration
        self.lo = device.lo
        self.offset = device.offset
        self.tuner = device.tuner
        self.levelOff = device.levelOff

    def get_gains_str(self):
        gainsStr = []
        for gain in self.gains:
            gainsStr.append(str(gain))

        return gainsStr

    def get_closest_gain_str(self, desired):
        gain = min(self.gains, key=lambda n: abs(n - desired))

        return str(gain) def get_devices_rtl(currentDevices=None, statusBar=None):
    if statusBar is not None:
        statusBar.set_general("Refreshing device list...")

    if currentDevices is None:
        currentDevices = []

    devices = []
    count = rtlsdr.librtlsdr.rtlsdr_get_device_count()

    for dev in range(0, count):
        device = DeviceRTL()
        device.indexRtl = dev
        device.name = format_device_rtl_name(rtlsdr.librtlsdr.rtlsdr_get_device_name(dev))
        buffer1 = (c_ubyte * 256)()
        buffer2 = (c_ubyte * 256)()
        serial = (c_ubyte * 256)()
        rtlsdr.librtlsdr.rtlsdr_get_device_usb_strings(dev, buffer1, buffer2,
                                                       serial)
        device.serial = string_at(serial)
        try:
            sdr = rtlsdr.RtlSdr(dev)
        except IOError:
            continue
        device.gains = sdr.valid_gains_db
        device.calibration = 0.0
        device.lo = 0.0
        for conf in currentDevices:
            if conf.isDevice and device.name == conf.name and device.serial == conf.serial:
                device.set(conf)

        devices.append(device)

    for conf in currentDevices:
        if not conf.isDevice:
            devices.append(conf)

    if statusBar is not None:
        statusBar.set_general("")

    return devices class Event(object):
    STARTING, STEPS, INFO, DATA, STOPPED, ERROR, FINISHED, PROCESSED, \
        CAL, LEVEL, UPDATED, DRAW, \
        DELAY_COUNT, DELAY_START, \
        LOC, LOC_RAW, LOC_WARN, LOC_ERR, LOC_SAT = range(19) def save_plot(filename, scanInfo, spectrum, location):
    data = [File.HEADER, {'Version': File.VERSION,
                          'Start': scanInfo.start,
                          'Stop': scanInfo.stop,
                          'Dwell': scanInfo.dwell,
                          'Nfft': scanInfo.nfft,
                          'Device': scanInfo.name,
                          'Gain': scanInfo.gain,
                          'LO': scanInfo.lo,
                          'Calibration': scanInfo.calibration,
                          'Tuner': scanInfo.tuner,
                          'Time': scanInfo.time,
                          'Latitude': scanInfo.lat,
                          'Longitude': scanInfo.lon,
                          'Description': scanInfo.desc,
                          'Spectrum': spectrum,
                          'Location': location}]

    handle = open(os.path.join(filename), 'wb')
    handle.write(json.dumps(data, indent=4))
    handle.close() def export_plot(filename, exportType, spectrum):
    handle = open(filename, 'wb')
    if exportType == File.PlotType.CSV:
        export_csv(handle, spectrum)
    elif exportType == File.PlotType.GNUPLOT:
        export_plt(handle, spectrum)
    elif exportType == File.PlotType.FREEMAT:
        export_freemat(handle, spectrum)
    elif exportType == File.PlotType.WWB:
        export_wwb(handle, spectrum)
    handle.close() class ScanInfo(object):
    start = None
    stop = None
    dwell = None
    nfft = None
    name = None
    gain = None
    lo = None
    calibration = None
    tuner = 0
    time = None
    timeFirst = None
    timeLast = None
    lat = None
    lon = None
    desc = ''

    def set_from_settings(self, settings):
        self.start = settings.start
        self.stop = settings.stop
        self.dwell = settings.dwell
        self.nfft = settings.nfft
        device = settings.devicesRtl[settings.indexRtl]
        if device.isDevice:
            self.name = device.name
        else:
            self.name = device.server + ":" + str(device.port)
        self.gain = device.gain
        self.lo = device.lo
        self.calibration = device.calibration
        self.tuner = device.tuner

    def set_to_settings(self, settings):
        settings.start = self.start
        settings.stop = self.stop
        settings.dwell = self.dwell
        settings.nfft = self.nfft class File(object):
    class Types(object):
        SAVE, PLOT, IMAGE, GEO, GMAP, TRACK, CONT = range(7)

    class SaveType(object):
        RFS = 0

    class PlotType(object):
        CSV, GNUPLOT, FREEMAT, WWB = range(4)

    class ImageType(object):
        BMP, EPS, GIF, JPEG, PDF, PNG, PPM, TIFF = range(8)

    class GeoType(object):
        KMZ, CSV, BMP, EPS, GIF, JPEG, PDF, PNG, PPM, TIFF = range(10)

    class GMapType(object):
        HTML = 0

    class TrackType(object):
        GPX = 0

    SAVE = [''] * 1
    SAVE[SaveType.RFS] = 'RTLSDR frequency scan (*.rfs)|*.rfs'

    PLOT = [''] * 4
    PLOT[PlotType.CSV] = "CSV table (*.csv)|*.csv"
    PLOT[PlotType.GNUPLOT] = "gnuplot script (*.plt)|*.plt"
    PLOT[PlotType.FREEMAT] = "FreeMat script (*.m)|*.m"
    PLOT[PlotType.WWB] = "Wireless Workbench (*.sdb2)|*.sdb2"

    IMAGE = [''] * 8
    IMAGE[ImageType.BMP] = 'Bitmap image (*.bmp)|*.bmp'
    IMAGE[ImageType.EPS] = 'Encapsulated PostScript (*.eps)|*.eps'
    IMAGE[ImageType.GIF] = 'GIF image (*.gif)|*.gif'
    IMAGE[ImageType.JPEG] = 'JPEG image (*.jpeg)|*.jpeg'
    IMAGE[ImageType.PDF] = 'Portable Document (*.pdf)|*.pdf'
    IMAGE[ImageType.PNG] = 'Portable Network Graphics Image (*.png)|*.png'
    IMAGE[ImageType.PPM] = 'Portable Pixmap image (*.ppm)|*.ppm'
    IMAGE[ImageType.TIFF] = 'Tagged Image File (*.tiff)|*.tiff'

    GEO = [''] * 10
    GEO[GeoType.BMP] = 'Bitmap image (*.bmp)|*.bmp'
    GEO[GeoType.CSV] = 'CSV Table (*.csv)|*.csv'
    GEO[GeoType.EPS] = 'Encapsulated PostScript (*.eps)|*.eps'
    GEO[GeoType.GIF] = 'GIF image (*.gif)|*.gif'
    GEO[GeoType.JPEG] = 'JPEG image (*.jpeg)|*.jpeg'
    GEO[GeoType.KMZ] = 'Google Earth (*.kmz)|*.kmz'
    GEO[GeoType.PDF] = 'Portable Document (*.pdf)|*.pdf'
    GEO[GeoType.PNG] = 'Portable Network Graphics Image (*.png)|*.png'
    GEO[GeoType.PPM] = 'Portable Pixmap image (*.ppm)|*.ppm'
    GEO[GeoType.TIFF] = 'Tagged Image File (*.tiff)|*.tiff'

    GMAP = [''] * 1
    GMAP[GMapType.HTML] = 'HTML Document (*.html)|*.html'

    TRACK = [''] * 1
    TRACK[TrackType.GPX] = 'GPX track (*.gpx)|*.gpx'

    CONT = [''] * 1
    CONT[PlotType.CSV] = PLOT[PlotType.CSV]

    HEADER = APP_NAME
    VERSION = 9

    @staticmethod
    def __get_types(type):
        return [File.SAVE, File.PLOT, File.IMAGE,
                File.GEO, File.GMAP, File.TRACK, File.CONT][type]

    @staticmethod
    def get_type_ext(index, type=Types.PLOT):
        types = File.__get_types(type)
        filter = types[index]
        delim = filter.index('|*')
        return filter[delim + 2:]

    @staticmethod
    def get_type_filters(type=Types.PLOT):
        types = File.__get_types(type)

        filters = ''
        length = len(types)
        for i in xrange(length):
            filters += types[i]
            if i < length - 1:
                filters += '|'

        return filters

    @staticmethod
    def get_type_pretty(type):
        types = File.__get_types(type)

        pretty = ''
        length = len(types)
        for i in xrange(length):
            pretty += File.get_type_ext(i, type)
            if i < length - 2:
                pretty += ', '
            elif i < length - 1:
                pretty += ' or '

        return pretty

    @staticmethod
    def get_type_index(extension, type=Types.PLOT):
        exports = File.__get_types(type)
        for i in xrange(len(exports)):
            if extension == File.get_type_ext(i, type):
                return i

        return -1 class ThreadLocation(threading.Thread):
    def __init__(self, notify, device, raw=False):
        threading.Thread.__init__(self)
        self.name = 'Location'
        self._notify = notify
        self._device = device
        self._raw = raw
        self._cancel = False
        self._comm = None
        self._timeout = None
        self._sats = {}
        self._send = None

        self.start()

    def __tcp_connect(self, defaultPort):
        self._comm = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._comm.settimeout(5)
        self._comm.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        url = urlparse('//' + self._device.resource)
        if url.hostname is not None:
            host = url.hostname
        else:
            host = 'localhost'
        if url.port is not None:
            port = url.port
        else:
            port = defaultPort
        if self._raw:
            text = 'Opening "{}"'.format(self._device.resource)
            post_event(self._notify, EventThread(Event.LOC_RAW, 0, text))
        try:
            self._comm.connect((host, port))
        except socket.error as error:
            post_event(self._notify, EventThread(Event.LOC_ERR,
                                                 0, error))
            return False

        return True

    def __tcp_read(self, isGpsd=False):
        buf = ''
        data = True

        while data and not self._cancel:
            reads, writes, errors = select.select([self._comm],
                                                  [self._comm],
                                                  [self._comm],
                                                  0.1)
            for read in reads:
                data = read.recv(64)
                buf += data
                while buf.find('\n') != -1:
                    line, buf = buf.split('\n', 1)
                    if not isGpsd:
                        pos = line.find('$')
                        if pos != -1 and pos + 1 < len(line):
                            yield line[pos + 1:].rstrip('\r\n')
                    else:
                        yield line
                    if self._raw:
                        line = limit_to_ascii(line)
                        post_event(self._notify, EventThread(Event.LOC_RAW,
                                                             0, line))

            for write in writes:
                if self._send is not None:
                    write.sendall(self._send)
                    self._send = None

            for _error in errors:
                post_event(self._notify, EventThread(Event.LOC_ERR,
                                                     0,
                                                     'Connection dropped'))
        return

    def __serial_timeout(self):
        self.stop()
        post_event(self._notify, EventThread(Event.LOC_ERR,
                                             0, 'GPS timed out'))

    def __serial_connect(self):
        self._timeout = Timeout(self.__serial_timeout)
        if self._raw:
            text = 'Opening "{}"'.format(self._device.resource)
            post_event(self._notify, EventThread(Event.LOC_RAW, 0, text))
        try:
            self._comm = serial.Serial(self._device.resource,
                                       baudrate=self._device.baud,
                                       bytesize=self._device.bytes,
                                       parity=self._device.parity,
                                       stopbits=self._device.stops,
                                       xonxoff=self._device.soft,
                                       timeout=0)

        except SerialException as error:
            post_event(self._notify, EventThread(Event.LOC_ERR,
                                                 0, error.message))
            return False
        except OSError as error:
            post_event(self._notify, EventThread(Event.LOC_ERR,
                                                 0, error))
            return False
        except ValueError as error:
            post_event(self._notify, EventThread(Event.LOC_ERR,
                                                 0, error))
            return False

        return True

    def __serial_read(self):
        isSentence = False
        sentence = ''
        while not self._cancel:
            data = self._comm.read(1)
            if data:
                self._timeout.reset()
                if data == '$':
                    isSentence = True
                    continue
                if data == '\r' or data == '\n':
                    isSentence = False
                    if sentence:
                        yield sentence
                        if self._raw:
                            line = limit_to_ascii(sentence)
                            post_event(self._notify, EventThread(Event.LOC_RAW,
                                                                 0, line))
                        sentence = ''
                if isSentence:
                    sentence += data
            else:
                time.sleep(0.1)

    def __gpsd_open(self):
        if not self.__tcp_connect(2947):
            return False

        try:
            if self._device.type == DeviceGPS.GPSD:
                self._send = '?WATCH={"enable": true,"json": true}\n'
            else:
                self._send = 'w'

        except IOError as error:
            post_event(self._notify, EventThread(Event.LOC_ERR,
                                                 0, error))
            self._comm.close()
            return False

        return True

    def __gpsd_read(self):
        for resp in self.__tcp_read(True):
            data = json.loads(resp)
            if data['class'] == 'TPV':
                if data['mode'] in [2, 3]:
                    try:
                        lat = data['lat']
                        lon = data['lon']
                    except KeyError:
                        return
                    try:
                        alt = data['alt']
                    except KeyError:
                        alt = None
                    self.__post_location(lat, lon, alt)
            elif data['class'] == 'SKY':
                self.__gpsd_sats(data['satellites'])

    def __gpsd_old_read(self):
        for resp in self.__tcp_read(True):
            data = resp.split(' ')
            if len(data) == 15 and data[0] == 'GPSD,O=GGA':
                try:
                    lat = float(data[4])
                    lon = float(data[3])
                except ValueError:
                    return
                try:
                    alt = float(data[5])
                except ValueError:
                    alt = None

                self.__post_location(lat, lon, alt)

    def __gpsd_close(self):
        if self._device.type == DeviceGPS.GPSD:
            self._send = '?WATCH={"enable": false}\n'
        else:
            self._send = 'W'
        if self._comm is not None:
            self._comm.close()

    def __gpsd_sats(self, satData):
        sats = {}
        for sat in satData:
            sats[sat['PRN']] = [sat['ss'], sat['used']]

        post_event(self._notify,
                   EventThread(Event.LOC_SAT, None, sats))

    def __nmea_open(self):
        if self._device.type == DeviceGPS.NMEA_SERIAL:
            return self.__serial_connect()
        else:
            return self.__tcp_connect(10110)

    def __nmea_read(self):
        if self._device.type == DeviceGPS.NMEA_SERIAL:
            comm = self.__serial_read()
        else:
            comm = self.__tcp_read()

        for resp in comm:
            nmea = resp.split('*')
            if len(nmea) == 2:
                data = nmea[0].split(',')
                if data[0] in ['GPGGA', 'GPGSV']:
                    checksum = self.__nmea_checksum(nmea[0])
                    if checksum == nmea[1]:
                        if data[0] == 'GPGGA':
                            self.__nmea_global_fix(data)
                        elif data[0] == 'GPGSV':
                            self.__nmea_sats(data)
                    else:
                        error = 'Invalid checksum {}, should be {}'.format(resp[1],
                                                                           checksum)
                        post_event(self._notify, EventThread(Event.LOC_WARN,
                                                             0, error))

    def __nmea_checksum(self, data):
        checksum = 0
        for char in data:
            checksum ^= ord(char)
        return "{0:02X}".format(checksum)

    def __nmea_global_fix(self, data):
        if data[6] in ['1', '2']:
            lat = self.__nmea_coord(data[2], data[3])
            lon = self.__nmea_coord(data[4], data[5])
            try:
                alt = float(data[9])
            except ValueError:
                alt = None

            self.__post_location(lat, lon, alt)

    def __nmea_sats(self, data):
        message = int(data[1])
        messages = int(data[1])
        viewed = int(data[3])

        if message == 1:
            self._sats.clear()

        blocks = (len(data) - 4) / 4
        for i in range(0, blocks):
            sat = int(data[4 + i * 4])
            level = data[7 + i * 4]
            used = True
            if level == '':
                level = None
                used = False
            else:
                level = int(level)
            self._sats[sat] = [level, used]

        if message == messages and len(self._sats) == viewed:
            post_event(self._notify,
                       EventThread(Event.LOC_SAT, None, self._sats))

    def __nmea_coord(self, coord, orient):
        pos = None

        if '.' in coord:
            if coord.index('.') == 4:
                try:
                    degrees = int(coord[:2])
                    minutes = float(coord[2:])
                    pos = degrees + minutes / 60.
                    if orient == 'S':
                        pos = -pos
                except ValueError:
                    pass
            elif coord.index('.') == 5:
                try:
                    degrees = int(coord[:3])
                    minutes = float(coord[3:])
                    pos = degrees + minutes / 60.
                    if orient == 'W':
                        pos = -pos
                except ValueError:
                    pass

        return pos

    def __nmea_close(self):
        if self._timeout is not None:
            self._timeout.cancel()
        if self._comm is not None:
            self._comm.close()

    def __post_location(self, lat, lon, alt):
        utc = time.time()
        post_event(self._notify,
                   EventThread(Event.LOC, 0, [lat, lon, alt, utc]))

    def run(self):
        conn = True

        if self._device.type in [DeviceGPS.NMEA_SERIAL, DeviceGPS.NMEA_TCP]:
            if not self.__nmea_open():
                self.__nmea_close()
                conn = False
        else:
            if not self.__gpsd_open():
                conn = False

        if conn:
            if self._device.type in [DeviceGPS.NMEA_SERIAL, DeviceGPS.NMEA_TCP]:
                self.__nmea_read()
            elif self._device.type == DeviceGPS.GPSD:
                self.__gpsd_read()
            elif self._device.type == DeviceGPS.GPSD_OLD:
                self.__gpsd_old_read()

            if self._device.type in [DeviceGPS.NMEA_SERIAL, DeviceGPS.NMEA_TCP]:
                self.__nmea_close()
            else:
                self.__gpsd_close()

        if self._raw:
            post_event(self._notify, EventThread(Event.LOC_RAW, 0, 'Stopped'))

    def stop(self):
        self._cancel = True
        if self._raw:
            self._notify.queue.clear() def nearest(value, values):
    offset = [abs(value - v) for v in values]
    return values[offset.index(min(offset))] def calc_real_dwell(dwell):
    samples = calc_samples(dwell)
    dwellReal = samples / SAMPLE_RATE
    return (int)(dwellReal * 1000.0) / 1000.0 def next_2_to_pow(val):
    val -= 1
    val |= val >> 1
    val |= val >> 2
    val |= val >> 4
    val |= val >> 8
    val |= val >> 16
    return val + 1 def get_dwells():
    dwells = ["8 ms", 0.008,
              "16 ms", 0.016,
              "32 ms", 0.032,
              "65 ms", 0.065,
              "131 ms", 0.131,
              "262 ms", 0.262,
              "524 ms", 0.524,
              "1 s", 1.048,
              "2 s", 2.097,
              "4 s", 4.194,
              "8 s", 8.388]

    if sys.platform == 'linux' or sys.platform == "linux2":
        del dwells[-4:]

    return dwells class ThreadScan(threading.Thread):
    def __init__(self, notify, queue, sdr, settings, device, samples, isCal):
        threading.Thread.__init__(self)
        self.name = 'Scan'
        self.notify = notify
        self.queue = queue
        self.sdr = sdr
        self.fstart = settings.start * 1e6
        self.fstop = settings.stop * 1e6
        self.samples = int(samples)
        self.isCal = isCal
        self.indexRtl = settings.indexRtl
        self.isDevice = settings.devicesRtl[device].isDevice
        self.server = settings.devicesRtl[device].server
        self.port = settings.devicesRtl[device].port
        self.gain = settings.devicesRtl[device].gain
        self.lo = settings.devicesRtl[device].lo * 1e6
        self.offset = settings.devicesRtl[device].offset
        self.cancel = False

        post_event(self.notify, EventThread(Event.STARTING))
        steps = int((self.__f_stop() - self.__f_start()) / self.__f_step())
        post_event(self.notify, EventThread(Event.STEPS, steps))
        self.start()

    def __f_start(self):
        return self.fstart - self.offset - BANDWIDTH

    def __f_stop(self):
        return self.fstop + self.offset + BANDWIDTH * 2

    def __f_step(self):
        return BANDWIDTH / 2

    def __rtl_setup(self):

        if self.sdr is not None:
            return

        tuner = 0

        if self.isDevice:
            try:
                self.sdr = rtlsdr.RtlSdr(self.indexRtl)
                self.sdr.set_sample_rate(SAMPLE_RATE)
                self.sdr.set_gain(self.gain)
                tuner = self.sdr.get_tuner_type()
            except IOError as error:
                post_event(self.notify, EventThread(Event.ERROR,
                                                    0, error.message))
        else:
            try:
                self.sdr = RtlTcp(self.server, self.port, self.notify)
                self.sdr.set_sample_rate(SAMPLE_RATE)
                self.sdr.set_gain(self.gain)
                tuner = self.sdr.get_tuner_type()
            except IOError as error:
                post_event(self.notify, EventThread(Event.ERROR,
                                                    0, error))

        return tuner

    def run(self):
        tuner = self.__rtl_setup()
        if self.sdr is None:
            return
        post_event(self.notify, EventThread(Event.INFO, None, tuner))

        freq = self.__f_start()
        timeStamp = math.floor(time.time())
        while freq <= self.__f_stop():
            if self.cancel:
                post_event(self.notify, EventThread(Event.STOPPED))
                self.rtl_close()
                return
            try:
                scan = self.rtl_scan(freq)
                if len(scan):
                    self.queue.put([freq, (timeStamp, scan)])
                    post_event(self.notify, EventThread(Event.DATA))
                else:
                    post_event(self.notify, EventThread(Event.ERROR, 0,
                                                        'No samples returned'))
                    return
            except (AttributeError, MemoryError, TypeError) as error:
                post_event(self.notify, EventThread(Event.ERROR,
                                                    0, error.message))
                return
            except (IOError, OSError) as error:
                if self.sdr is not None:
                    self.rtl_close()
                post_event(self.notify, EventThread(Event.ERROR,
                                                    0, error.message))
                return

            freq += self.__f_step()

        post_event(self.notify, EventThread(Event.FINISHED, 0, None))

        if self.isCal:
            post_event(self.notify, EventThread(Event.CAL))

    def abort(self):
        self.cancel = True

    def rtl_scan(self, freq):
        self.sdr.set_center_freq(freq + self.lo)
        capture = self.sdr.read_samples(self.samples)

        return capture

    def rtl_close(self):
        self.sdr.close()

    def get_sdr(self):
        return self.sdr def update_spectrum(notify, lock, start, stop, data, offset,
                    spectrum, average, alertLevel=None):
    with lock:
        updated = False
        if average:
            if len(spectrum) > 0:
                timeStamp = min(spectrum)
            else:
                timeStamp = data[0]
        else:
            timeStamp = data[0]

        freqCentre = data[1]
        scan = data[2]

        upperStart = freqCentre + offset
        upperEnd = freqCentre + offset + BANDWIDTH / 2
        lowerStart = freqCentre - offset - BANDWIDTH / 2
        lowerEnd = freqCentre - offset

        if timeStamp not in spectrum:
            spectrum[timeStamp] = OrderedDict()

        for freq in scan:
            if start <= freq < stop:
                power = 10 * math.log10(scan[freq])
                if upperStart <= freq * 1e6 <= upperEnd or \
                   lowerStart <= freq * 1e6 <= lowerEnd:
                    if freq in spectrum[timeStamp]:
                        spectrum[timeStamp][freq] = \
                            (spectrum[timeStamp][freq] + power) / 2
                        if alertLevel is not None and (spectrum[timeStamp][freq] >
                                                       alertLevel):
                            post_event(notify, EventThread(Event.LEVEL))
                        updated = True
                    else:
                        spectrum[timeStamp][freq] = power
                        updated = True

        if updated:
            spectrum[timeStamp] = OrderedDict(sorted(spectrum[timeStamp].items()))

    post_event(notify, EventThread(Event.UPDATED, None, updated)) class ThreadProcess(threading.Thread):
    def __init__(self, notify, freq, scan, cal, levelOff, nfft, overlap, winFunc):
        threading.Thread.__init__(self)
        self.name = 'ThreadProcess'
        self.notify = notify
        self.freq = freq
        self.scan = scan
        self.cal = cal
        self.levelOff = math.pow(10, levelOff / 10.0)
        self.nfft = nfft
        self.overlap = overlap
        self.winFunc = winFunc
        self.window = matplotlib.numpy.hamming(nfft)

    def run(self):
        spectrum = {}
        timeStamp = self.scan[0]
        samples = self.scan[1]
        pos = WINFUNC[::2].index(self.winFunc)
        function = WINFUNC[1::2][pos]

        powers, freqs = matplotlib.mlab.psd(samples,
                                            NFFT=self.nfft,
                                            Fs=SAMPLE_RATE / 1e6,
                                            window=function(self.nfft))
        for freqPsd, pwr in itertools.izip(freqs, powers):
            xr = freqPsd + (self.freq / 1e6)
            xr = xr + (xr * self.cal / 1e6)
            spectrum[xr] = pwr * self.levelOff
        post_event(self.notify, EventThread(Event.PROCESSED,
                                            (timeStamp, self.freq, spectrum))) class Settings(object):
    def __init__(self, load=True):
        self.cfg = None

        self.saveWarn = True
        self.backup = True
        self.fileHistory = wx.FileHistory(5)

        self.dirScans = "."
        self.dirExport = "."

        self.display = Display.PLOT

        self.annotate = True
        self.peaks = False
        self.peaksThres = -30

        self.retainScans = True
        self.retainMax = 20
        self.fadeScans = True
        self.lineWidth = 0.4
        self.colourMapUse = True
        self.colourMap = 'jet'
        self.background = '#f0f0f0'
        self.wireframe = False
        self.pointsLimit = False
        self.pointsMax = 5000
        self.grid = True
        self.plotFunc = PlotFunc.NONE
        self.smoothFunc = 'Hamming'
        self.smoothRatio = 50

        self.clickTune = True

        self.precisionFreq = 6
        self.precisionLevel = 2

        self.compareOne = True
        self.compareTwo = True
        self.compareDiff = True

        self.start = 87
        self.stop = 108
        self.mode = Mode.SINGLE
        self.dwell = 0.1
        self.nfft = 1024
        self.scanDelay = 0
        self.overlap = 0.0
        self.winFunc = "Hamming"

        self.startOption = 0
        self.stopOption = 0

        self.liveUpdate = False
        self.calFreq = 1575.42
        self.autoF = True
        self.autoL = True
        self.autoT = True

        self.showMeasure = True

        self.alert = False
        self.alertLevel = -20

        self.gps = False
        self.gpsRetry = False

        self.exportWidth = 8
        self.exportHeight = 4.5
        self.exportDpi = 600

        self.devicesRtl = []
        self.indexRtl = 0
        self.devicesGps = []
        self.indexGps = 0

        if load:
            self.__load()

    def __clear_servers(self):
        self.cfg.SetPath("/DevicesRTL")
        group = self.cfg.GetFirstGroup()
        while group[0]:
            key = "/DevicesRTL/" + group[1]
            self.cfg.SetPath(key)
            if not self.cfg.ReadBool('isDevice', True):
                self.cfg.DeleteGroup(key)
            self.cfg.SetPath("/DevicesRTL")
            group = self.cfg.GetNextGroup(group[2])

    def __load_devices_rtl(self):
        self.cfg.SetPath("/DevicesRTL")
        group = self.cfg.GetFirstGroup()
        while group[0]:
            self.cfg.SetPath("/DevicesRTL/" + group[1])
            device = DeviceRTL()
            device.name = group[1]
            device.serial = self.cfg.Read('serial', '')
            device.isDevice = self.cfg.ReadBool('isDevice', True)
            device.server = self.cfg.Read('server', 'localhost')
            device.port = self.cfg.ReadInt('port', 1234)
            device.gain = self.cfg.ReadFloat('gain', 0)
            device.calibration = self.cfg.ReadFloat('calibration', 0)
            device.lo = self.cfg.ReadFloat('lo', 0)
            device.offset = self.cfg.ReadFloat('offset', 250e3)
            device.tuner = self.cfg.ReadInt('tuner', 0)
            device.levelOff = self.cfg.ReadFloat('levelOff', 0)
            self.devicesRtl.append(device)
            self.cfg.SetPath("/DevicesRTL")
            group = self.cfg.GetNextGroup(group[2])

    def __load_devices_gps(self):
        self.devicesGps = []
        self.cfg.SetPath("/DevicesGPS")
        group = self.cfg.GetFirstGroup()
        while group[0]:
            self.cfg.SetPath("/DevicesGPS/" + group[1])
            device = DeviceGPS()
            device.name = group[1]
            device.type = self.cfg.ReadInt('type', device.type)
            device.resource = self.cfg.Read('resource', device.resource)
            device.baud = self.cfg.ReadInt('baud', device.baud)
            device.bytes = self.cfg.ReadInt('bytes', device.bytes)
            device.parity = self.cfg.Read('parity', device.parity)
            device.stops = self.cfg.ReadInt('stops', device.stops)
            device.soft = self.cfg.ReadBool('soft', device.soft)
            self.devicesGps.append(device)
            self.cfg.SetPath("/DevicesGPS")
            group = self.cfg.GetNextGroup(group[2])

    def __save_devices_rtl(self):
        self.__clear_servers()

        if self.devicesRtl:
            for device in self.devicesRtl:
                if device.isDevice:
                    name = device.name
                else:
                    name = "{}:{}".format(device.server, device.port)
                self.cfg.SetPath("/DevicesRTL/" + format_device_rtl_name(name))
                self.cfg.Write('serial', device.serial)
                self.cfg.WriteBool('isDevice', device.isDevice)
                self.cfg.Write('server', device.server)
                self.cfg.WriteInt('port', device.port)
                self.cfg.WriteFloat('gain', device.gain)
                self.cfg.WriteFloat('lo', device.lo)
                self.cfg.WriteFloat('calibration', device.calibration)
                self.cfg.WriteFloat('offset', device.offset)
                self.cfg.WriteInt('tuner', device.tuner)
                self.cfg.WriteFloat('levelOff', device.levelOff)

    def __save_devices_gps(self):
        self.cfg.DeleteGroup('/DevicesGPS')
        for device in self.devicesGps:
            self.cfg.SetPath("/DevicesGPS/" + device.name)
            self.cfg.WriteInt('type', device.type)
            self.cfg.Write('resource', device.resource)
            self.cfg.WriteInt('baud', device.baud)
            self.cfg.WriteInt('bytes', device.bytes)
            self.cfg.Write('parity', device.parity)
            self.cfg.WriteInt('stops', device.stops)
            self.cfg.WriteBool('soft', device.soft)

    def __load(self):
        self.cfg = wx.Config('rtlsdr-scanner')

        self.cfg.RenameGroup('Devices', 'DevicesRTL')

        self.display = self.cfg.ReadInt('display', self.display)
        self.saveWarn = self.cfg.ReadBool('saveWarn', self.saveWarn)
        self.backup = self.cfg.ReadBool('backup', self.backup)
        self.fileHistory.Load(self.cfg)
        self.dirScans = self.cfg.Read('dirScans', self.dirScans)
        self.dirExport = self.cfg.Read('dirExport', self.dirExport)
        self.annotate = self.cfg.ReadBool('annotate', self.annotate)
        self.peaks = self.cfg.ReadBool('peaks', self.peaks)
        self.peaksThres = self.cfg.ReadInt('peaksThres', self.peaksThres)
        self.retainScans = self.cfg.ReadBool('retainScans', self.retainScans)
        self.fadeScans = self.cfg.ReadBool('fadeScans', self.fadeScans)
        self.lineWidth = self.cfg.ReadFloat('lineWidth', self.lineWidth)
        self.retainMax = self.cfg.ReadInt('retainMax', self.retainMax)
        self.colourMapUse = self.cfg.ReadBool('colourMapUse', self.colourMapUse)
        self.colourMap = self.cfg.Read('colourMap', self.colourMap)
        self.background = self.cfg.Read('background', self.background)
        self.wireframe = self.cfg.ReadBool('wireframe', self.wireframe)
        self.pointsLimit = self.cfg.ReadBool('pointsLimit', self.pointsLimit)
        self.pointsMax = self.cfg.ReadInt('pointsMax', self.pointsMax)
        self.grid = self.cfg.ReadBool('grid', self.grid)
        self.plotFunc = self.cfg.ReadInt('plotFunc', self.plotFunc)
        self.smoothFunc = self.cfg.Read('smoothFunc', self.smoothFunc)
        self.smoothRatio = self.cfg.ReadInt('smoothRatio', self.smoothRatio)
        self.clickTune = self.cfg.ReadBool('clickTune', self.clickTune)
        self.precisionFreq = self.cfg.ReadInt('precisionFreq', self.precisionFreq)
        self.precisionLevel = self.cfg.ReadInt('precisionLevel', self.precisionLevel)
        self.compareOne = self.cfg.ReadBool('compareOne', self.compareOne)
        self.compareTwo = self.cfg.ReadBool('compareTwo', self.compareTwo)
        self.compareDiff = self.cfg.ReadBool('compareDiff', self.compareDiff)
        self.start = self.cfg.ReadInt('start', self.start)
        self.stop = self.cfg.ReadInt('stop', self.stop)
        self.mode = self.cfg.ReadInt('mode', self.mode)
        self.dwell = self.cfg.ReadFloat('dwell', self.dwell)
        self.nfft = self.cfg.ReadInt('nfft', self.nfft)
        self.scanDelay = self.cfg.ReadInt('scanDelay', self.scanDelay)
        self.overlap = self.cfg.ReadFloat('overlap', self.overlap)
        self.winFunc = self.cfg.Read('winFunc', self.winFunc)
        self.startOption = self.cfg.ReadInt('startOption', self.startOption)
        self.stopOption = self.cfg.ReadInt('stopOption', self.stopOption)
        self.liveUpdate = self.cfg.ReadBool('liveUpdate', self.liveUpdate)
        self.calFreq = self.cfg.ReadFloat('calFreq', self.calFreq)
        self.autoF = self.cfg.ReadBool('autoF', self.autoF)
        self.autoL = self.cfg.ReadBool('autoL', self.autoL)
        self.autoT = self.cfg.ReadBool('autoT', self.autoT)
        self.showMeasure = self.cfg.ReadBool('showMeasure', self.showMeasure)
        self.alert = self.cfg.ReadBool('alert', self.alert)
        self.alertLevel = self.cfg.ReadFloat('alertLevel', self.alertLevel)
        self.gps = self.cfg.ReadBool('gps', self.gps)
        self.gpsRetry = self.cfg.ReadBool('gpsRetry', self.gpsRetry)
        self.exportWidth = self.cfg.ReadFloat('exportWidth', self.exportWidth)
        self.exportHeight = self.cfg.ReadFloat('exportHeight', self.exportHeight)
        self.exportDpi = self.cfg.ReadInt('exportDpi', self.exportDpi)
        self.indexRtl = self.cfg.ReadInt('index', self.indexRtl)
        self.indexRtl = self.cfg.ReadInt('indexRtl', self.indexRtl)
        self.indexGps = self.cfg.ReadInt('indexGps', self.indexGps)
        self.__load_devices_rtl()
        self.__load_devices_gps()

    def __check_conf_serial(self, device):
        if device.type not in range(len(DeviceGPS.TYPE)):
            return 'Type "{}" should be between 0 and {}'.format(device.type,
                                                                 len(DeviceGPS.TYPE) - 1)

        if device.type == 0:
            if device.baud not in device.get_bauds():
                return 'Baud "{}" should be one of:\n  {}'.format(device.baud,
                                                                  device.get_bauds())
            if device.bytes not in DeviceGPS.BYTES:
                return 'Bits "{}" should be one of:\n  {}'.format(device.bytes,
                                                                  DeviceGPS.BYTES)
            if device.parity not in DeviceGPS.PARITIES:
                return 'Parity "{}" should be one of:\n  {}'.format(device.parity,
                                                                    DeviceGPS.PARITIES)
            if device.stops not in DeviceGPS.STOPS:
                return 'Stops "{}" should be one of:\n  {}'.format(device.stops,
                                                                   DeviceGPS.STOPS)

    def load_conf(self, filename):
        config = ConfigParser.SafeConfigParser()
        try:
            config.read(filename)
            sections = config.sections()
            if len(sections):
                device = DeviceGPS()
                device.name = sections[0]
                device.type = config.getint(sections[0], 'type')
                device.resource = config.get(sections[0], 'resource')
                if device.type == DeviceGPS.NMEA_SERIAL:
                    if config.has_option(sections[0], 'baud'):
                        device.baud = config.getint(sections[0], 'baud')
                    if config.has_option(sections[0], 'bits'):
                        device.bytes = config.getint(sections[0], 'bits')
                    if config.has_option(sections[0], 'parity'):
                        device.parity = config.get(sections[0], 'parity')
                    if config.has_option(sections[0], 'stops'):
                        device.stops = config.getint(sections[0], 'stops')
                    if config.has_option(sections[0], 'soft'):
                        device.soft = config.getboolean(sections[0], 'soft')

                self.devicesGps.append(device)
                return self.__check_conf_serial(device)

        except ConfigParser.Error as e:
            return e.message
        except ValueError as e:
            return e.message

    def save(self):
        self.cfg.SetPath("/")
        self.cfg.WriteInt('display', self.display)
        self.cfg.WriteBool('saveWarn', self.saveWarn)
        self.cfg.WriteBool('backup', self.backup)
        self.fileHistory.Save(self.cfg)
        self.cfg.Write('dirScans', self.dirScans)
        self.cfg.Write('dirExport', self.dirExport)
        self.cfg.WriteBool('annotate', self.annotate)
        self.cfg.WriteBool('peaks', self.peaks)
        self.cfg.WriteInt('peaksThres', self.peaksThres)
        self.cfg.WriteBool('retainScans', self.retainScans)
        self.cfg.WriteBool('fadeScans', self.fadeScans)
        self.cfg.WriteFloat('lineWidth', self.lineWidth)
        self.cfg.WriteInt('retainMax', self.retainMax)
        self.cfg.WriteBool('colourMapUse', self.colourMapUse)
        self.cfg.Write('colourMap', self.colourMap)
        self.cfg.Write('background', self.background)
        self.cfg.WriteBool('wireframe', self.wireframe)
        self.cfg.WriteBool('pointsLimit', self.pointsLimit)
        self.cfg.WriteInt('pointsMax', self.pointsMax)
        self.cfg.WriteBool('grid', self.grid)
        self.cfg.WriteInt('plotFunc', self.plotFunc)
        self.cfg.WriteInt('smoothRatio', self.smoothRatio)
        self.cfg.Write('smoothFunc', self.smoothFunc)
        self.cfg.WriteBool('clickTune', self.clickTune)
        self.cfg.WriteInt('precisionFreq', self.precisionFreq)
        self.cfg.WriteInt('precisionLevel', self.precisionLevel)
        self.cfg.WriteBool('compareOne', self.compareOne)
        self.cfg.WriteBool('compareTwo', self.compareTwo)
        self.cfg.WriteBool('compareDiff', self.compareDiff)
        self.cfg.WriteInt('start', self.start)
        self.cfg.WriteInt('stop', self.stop)
        self.cfg.WriteInt('mode', self.mode)
        self.cfg.WriteFloat('dwell', self.dwell)
        self.cfg.WriteInt('nfft', self.nfft)
        self.cfg.WriteInt('scanDelay', self.scanDelay)
        self.cfg.WriteFloat('overlap', self.overlap)
        self.cfg.Write("winFunc", self.winFunc)
        self.cfg.WriteInt('startOption', self.startOption)
        self.cfg.WriteInt('stopOption', self.stopOption)
        self.cfg.WriteBool('liveUpdate', self.liveUpdate)
        self.cfg.WriteFloat('calFreq', self.calFreq)
        self.cfg.WriteBool('autoF', self.autoF)
        self.cfg.WriteBool('autoL', self.autoL)
        self.cfg.WriteBool('autoT', self.autoT)
        self.cfg.WriteBool('showMeasure', self.showMeasure)
        self.cfg.WriteBool('alert', self.alert)
        self.cfg.WriteFloat('alertLevel', self.alertLevel)
        self.cfg.WriteBool('gps', self.gps)
        self.cfg.WriteBool('gpsRetry', self.gpsRetry)
        self.cfg.WriteFloat('exportWidth', self.exportWidth)
        self.cfg.WriteFloat('exportHeight', self.exportHeight)
        self.cfg.WriteInt('exportDpi', self.exportDpi)
        self.cfg.WriteInt('indexRtl', self.indexRtl)
        self.cfg.WriteInt('indexGps', self.indexGps)
        self.__save_devices_rtl()
        self.__save_devices_gps()

        self.cfg.DeleteEntry('autoScale')
        self.cfg.DeleteEntry('yMax')
        self.cfg.DeleteEntry('yMin')
        self.cfg.DeleteEntry('average')
        self.cfg.DeleteEntry('index')

    def reset(self):
        self.cfg.SetPath("/")
        self.cfg.DeleteAll()
        self.__init__()
import Queue
import os
import sys
import threading
import time
from collections import OrderedDict
from threading import Thread
from urlparse import urlparse
from rtlsdr_scanner.constants import SAMPLE_RATE
from rtlsdr_scanner.devices import DeviceRTL, get_devices_rtl
from rtlsdr_scanner.events import Event
from rtlsdr_scanner.file import save_plot, export_plot, ScanInfo, File
from rtlsdr_scanner.location import ThreadLocation
from rtlsdr_scanner.misc import nearest, calc_real_dwell, next_2_to_pow, get_dwells
from rtlsdr_scanner.scan import ThreadScan, update_spectrum, ThreadProcess
from rtlsdr_scanner.settings import Settings

        self.stepsTotal = 0
        self.steps = 0

        self.spectrum = OrderedDict()
        self.locations = OrderedDict()
        self.settings = Settings(load=False)

        self.queueNotify = Queue.Queue()
        self.queueScan = Queue.Queue()
        self.queueLocation = Queue.Queue()

        self.threadLocation = None

        error = None

        if end <= start:
            error = "Start should be lower than end"
        elif dwell <= 0:
            error = "Dwell should be positive"
        elif dwell > max(get_dwells()[1::2]):
            error = "Dwell should equal lower than {}s".format(max(get_dwells()[1::2]))
        elif nfft <= 0:
            error = "FFT bins should be positive"
        elif ext != ".rfs" and File.get_type_index(ext) == -1:
            error = "File extension should be "
            error += File.get_type_pretty(File.Types.SAVE)
            error += File.get_type_pretty(File.Types.PLOT)
        else:
            device = DeviceRTL()
            if remote is None:
                self.settings.devicesRtl = get_devices_rtl()
                count = len(self.settings.devicesRtl)
                if index > count - 1:
                    error = "Device not found ({} devices in total):\n".format(count)
                    for device in self.settings.devicesRtl:
                        error += "\t{}: {}\n".format(device.indexRtl,
                                                     device.name)
            else:
                device.isDevice = False
                url = urlparse('//' + remote)
                if url.hostname is not None:
                    device.server = url.hostname
                else:
                    error = "Invalid hostname"
                if url.port is not None:
                    device.port = url.port
                else:
                    device.port = 1234
                self.settings.devicesRtl.append(device)
                index = len(self.settings.devicesRtl) - 1

            if args.conf is not None:
                if os.path.exists(args.conf):
                    error = self.settings.load_conf(args.conf)
                    if error is not None:
                        error = 'GPS configuration - ' + error
                else:
                    error = 'Cannot find {}'.format(args.conf)

            if end - 1 < start:
                end = start + 1
            if remote is None:
                if len(self.settings.devicesRtl):
                    gain = nearest(gain, self.settings.devicesRtl[index].gains)
                else:
                    error = 'No devices found'

        if error is not None:
            print "Error: {}".format(error)
            exit(1)

        self.settings.start = start
        self.settings.stop = end
        self.settings.dwell = calc_real_dwell(dwell)
        self.settings.scanDelay = args.delay
        self.settings.nfft = nfft
        self.settings.devicesRtl[index].gain = gain
        self.settings.devicesRtl[index].lo = lo

        print "{} - {}MHz".format(start, end)
        print "{} Sweeps".format(sweeps)
        print "{}dB Gain".format(gain)
        print "{}s Dwell".format(self.settings.dwell)
        print "{} FFT points".format(nfft)
        print "{}MHz LO".format(lo)
        if remote is not None:
            print remote
        else:
            print self.settings.devicesRtl[index].name

        if len(self.settings.devicesGps):
            print 'Using GPS configuration \'{}\''.format(self.settings.devicesGps[0].name)
            self.threadLocation = ThreadLocation(self.queueLocation,
                                                 self.settings.devicesGps[0])
            if not self.__gps_wait():
                self.__gps_stop()
                exit(1)

        self.__scan(sweeps, self.settings, index)

        fullName = os.path.join(directory, filename)
        if ext == ".rfs":
            scanInfo = ScanInfo()
            scanInfo.set_from_settings(self.settings)

            save_plot(fullName, scanInfo, self.spectrum, self.locations)
        else:
            exportType = File.get_type_index(ext)
            export_plot(fullName, exportType, self.spectrum)

        self.__gps_stop()
        print "Done"

    def __gps_wait(self):
        print '\nWaiting for GPS fix: {}'.format(self.settings.devicesGps[0].get_desc())

        while True:
            if not self.queueLocation.empty():
                status = self.__process_event(self.queueLocation)

                if status == Event.LOC: