Exemple #1
0
 def __on_start(self):
     self.__enable_controls(False)
     if self._receive is None:
         self._receive = Receive(self,
                                 self._toolbar.get_freq(),
                                 self._toolbar.get_gain(),
                                 self._toolbar.get_cal())
Exemple #2
0
    def __start(self, freq, gain, cal):
        self.__std_out('Monitoring...')

        timestamp = time.time()
        for monitor in self._monitors:
            monitor.start_period(timestamp)
        self._receive = Receive(self._queue,
                                freq,
                                gain,
                                cal)
Exemple #3
0
 def __on_start(self):
     self.__enable_controls(False)
     if self._receive is None:
         self._receive = Receive(self,
                                 self._toolbar.get_freq(),
                                 self._toolbar.get_gain(),
                                 self._toolbar.get_cal())
Exemple #4
0
class FrameMain(wx.Frame):
    def __init__(self):
        self._monitors = []
        self._freqs = []
        self._levels = numpy.zeros(BINS, dtype=numpy.float32)
        self._settings = Settings()
        self._filename = None
        self._receive = None
        self._dialogTimeline = None
        self._dialogSpectrum = None
        self._gps = None
        self._location = None

        self._ui = load_ui('FrameMain.xrc')

        handlerToolbar = XrcHandlerToolbar()
        self._ui.AddHandler(handlerToolbar)

        self._frame = self._ui.LoadFrame(None, 'FrameMain')
        self.__set_title()

        self._window = xrc.XRCCTRL(self._frame, 'window')
        self._status = xrc.XRCCTRL(self._frame, 'statusBar')
        self._toolbar = xrc.XRCCTRL(self._frame, 'PanelToolbar')

        self._sizerWindow = self._window.GetSizer()

        sdr = RtlSdr()
        gains = sdr.get_gains()
        gains = [float(gain) / 10. for gain in gains]
        sdr.close()

        self._toolbar.set_callbacks(self.__on_freq,
                                    self.__on_start,
                                    self.__on_rec,
                                    self.__on_stop,
                                    self.__on_add)
        self._toolbar.enable_start(True)
        self._toolbar.set_freq(self._settings.get_freq())
        self._toolbar.set_gains(gains)
        self._toolbar.set_gain(self._settings.get_gain())

        self.__add_monitors()

        self._server = Server(self._frame)

        self.__start_gps()

        self._menu = self._frame.GetMenuBar()

        idOpen = xrc.XRCID('menuOpen')
        self._menuOpen = self._menu.FindItemById(idOpen)
        self._frame.Bind(wx.EVT_MENU, self.__on_open, id=idOpen)
        idSave = xrc.XRCID('menuSave')
        self._menuSave = self._menu.FindItemById(idSave)
        self._frame.Bind(wx.EVT_MENU, self.__on_save, id=idSave)
        idSaveAs = xrc.XRCID('menuSaveAs')
        self._menuSaveAs = self._menu.FindItemById(idSaveAs)
        self._frame.Bind(wx.EVT_MENU, self.__on_save_as, id=idSaveAs)
        idClear = xrc.XRCID('menuClear')
        self._menuClear = self._menu.FindItemById(idClear)
        self._frame.Bind(wx.EVT_MENU, self.__on_clear, id=idClear)
        idGps = xrc.XRCID('menuGps')
        self._menuGps = self._menu.FindItemById(idGps)
        self._frame.Bind(wx.EVT_MENU, self.__on_gps, id=idGps)
        idTimeline = xrc.XRCID('menuTimeline')
        self._frame.Bind(wx.EVT_MENU, self.__on_timeline, id=idTimeline)
        self._menuTimeline = self._menu.FindItemById(idTimeline)
        idSpectrum = xrc.XRCID('menuSpectrum')
        self._frame.Bind(wx.EVT_MENU, self.__on_spectrum, id=idSpectrum)
        self._menuSpectrum = self._menu.FindItemById(idSpectrum)
        idExit = xrc.XRCID('menuExit')
        self._menuExit = self._menu.FindItemById(idExit)
        self._frame.Bind(wx.EVT_MENU, self.__on_exit, id=idExit)
        idAbout = xrc.XRCID('menuAbout')
        self._frame.Bind(wx.EVT_MENU, self.__on_about, id=idAbout)

        self._frame.Bind(EVT_TIMELINE_CLOSE, self.__on_timeline_close)
        self._frame.Bind(EVT_SPECTRUM_CLOSE, self.__on_spectrum_close)

        self._frame.Bind(wx.EVT_CLOSE, self.__on_exit)

        self._frame.Connect(-1, -1, EVENT_THREAD, self.__on_event)

        self._frame.Show()

    def __on_freq(self, freq):
        _l, freqs = psd(numpy.zeros(2, dtype=numpy.complex64),
                        BINS, SAMPLE_RATE)
        freqs /= 1e6
        freqs += freq
        self._freqs = freqs.tolist()

    def __on_start(self):
        self.__enable_controls(False)
        if self._receive is None:
            self._receive = Receive(self._frame,
                                    self._toolbar.get_freq(),
                                    self._toolbar.get_gain())

    def __on_rec(self, recording):
        if recording:
            self.__on_start()

        for monitor in self._monitors:
            monitor.set_recording(recording)

    def __on_stop(self):
        self.__enable_controls(True)
        if self._receive is not None:
            self._receive.stop()
            self._receive = None
        for monitor in self._monitors:
            monitor.set_level(LEVEL_MIN, 0, None)
        if self._dialogSpectrum is not None:
            self._dialogSpectrum.clear_spectrum()

    def __on_add(self):
        monitor = PanelMonitor(self._window)
        monitor.set_callback(self.__on_del)
        monitor.set_freqs(self._freqs)
        self.__add_monitor(monitor)

        self._frame.Layout()

    def __on_del(self, monitor):
        index = self._monitors.index(monitor)
        self._sizerWindow.Hide(index)
        self._sizerWindow.Remove(index)
        self._frame.Layout()

        self._monitors.remove(monitor)

        self._toolbar.enable_freq(not len(self._monitors))

    def __on_open(self, _event):
        if not self.__save_warning():
            return

        defDir, defFile = '', ''
        if self._filename is not None:
            defDir, defFile = os.path.split(self._filename)
        dlg = wx.FileDialog(self._frame,
                            'Open File',
                            defDir, defFile,
                            'rfmon files (*.rfmon)|*.rfmon',
                            wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
        if dlg.ShowModal() == wx.ID_CANCEL:
            return

        self.open(dlg.GetPath())

    def __on_save(self, _event):
        self.__save(False)

    def __on_save_as(self, _event):
        self.__save(True)

    def __on_clear(self, _event):
        resp = wx.MessageBox('Clear recorded data?', 'Warning',
                             wx.OK | wx.CANCEL | wx.ICON_WARNING)
        if resp != wx.OK:
            return

        for monitor in self._monitors:
            monitor.clear_signals()

    def __on_gps(self, _event):
        dlg = DialogGps(self._frame, self._settings.get_gps())
        if dlg.ShowModal() == wx.ID_OK:
            self.__stop_gps()
            self.__start_gps()

    def __on_timeline(self, event):
        if event.IsChecked() and self._dialogTimeline is None:
            self._dialogTimeline = DialogTimeline(self._frame)
            self._dialogTimeline.set_signals(self.__get_signals())
            self._dialogTimeline.Show()
        elif self._dialogTimeline is not None:
            self._dialogTimeline.Destroy()
            self._dialogTimeline = None

    def __on_timeline_close(self, _event):
        self._menuTimeline.Check(False)
        self._dialogTimeline = None

    def __on_spectrum(self, event):
        if event.IsChecked() and self._dialogSpectrum is None:
            self._dialogSpectrum = DialogSpectrum(self._frame)
            self._dialogSpectrum.Show()
        elif self._dialogSpectrum is not None:
            self._dialogSpectrum.Destroy()
            self._dialogSpectrum = None

    def __on_spectrum_close(self, _event):
        self._menuSpectrum.Check(False)
        self._dialogSpectrum = None

    def __on_about(self, _event):
        dlg = DialogAbout(self._frame)
        dlg.ShowModal()

    def __on_exit(self, _event):
        if not self.__save_warning():
            return

        self.__on_stop()

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

        self.__stop_gps()

        self.__update_settings()
        self._settings.save()

        self._frame.Destroy()

    def __on_event(self, event):
        if event.type == Events.SCAN_ERROR:
            self.__on_scan_error(event.data)

        elif event.type == Events.SCAN_DATA:
            self.__on_scan_data(event.data)
        elif event.type == Events.SERVER_ERROR:
            self.__on_server_error(event.data)
        elif event.type == Events.GPS_ERROR:
            self._status.SetStatusText(event.data['msg'], 1)
            self.__restart_gps()
        elif event.type == Events.GPS_WARN:
            self._status.SetStatusText(event.data['msg'], 1)
        elif event.type == Events.GPS_TIMEOUT:
            self._status.SetStatusText(event.data['msg'], 1)
            self.__restart_gps()
        elif event.type == Events.GPS_LOC:
            self._location = event.data['loc']
            loc = '{:9.5f}, {:9.5f}'.format(*self._location)
            self._status.SetStatusText(loc, 1)

    def __on_scan_error(self, event):
        wx.MessageBox(event['msg'],
                      'Error', wx.OK | wx.ICON_ERROR)
        self._toolbar.enable_start(True)

    def __on_scan_data(self, event):
        levels = numpy.log10(event['l'])
        levels *= 10

        self._levels += levels
        self._levels /= 2.

        updated = False
        for monitor in self._monitors:
            freq = monitor.get_freq()
            if monitor.is_enabled():
                index = numpy.where(freq == event['f'])[0]
                update = monitor.set_level(levels[index][0],
                                           event['timestamp'],
                                           self._location)
                if update:
                    updated = True
                    if self._server is not None:
                        recording = format_recording(freq, update)
                        self._server.send(recording)

        if self._dialogTimeline is not None and updated:
            self._dialogTimeline.set_signals(self.__get_signals())

        if self._dialogSpectrum is not None:
            self._dialogSpectrum.set_spectrum(self._freqs,
                                              self._levels,
                                              event['timestamp'])

    def __on_server_error(self, event):
        sys.stderr.write(event['msg'])
        self._server = None

    def __set_title(self):
        title = APP_NAME
        if self._filename is not None:
            _head, tail = os.path.split(self._filename)
            title += ' - ' + tail
        self._frame.SetTitle(title)

    def __update_settings(self):
        self._settings.set_freq(self._toolbar.get_freq())
        self._settings.set_gain(self._toolbar.get_gain())
        self._settings.clear_monitors()
        for monitor in self._monitors:
            self._settings.add_monitor(monitor.is_enabled(),
                                       monitor.get_freq(),
                                       monitor.get_threshold(),
                                       monitor.get_signals())

    def __save(self, prompt):
        if prompt or self._filename is None:
            defDir, defFile = '', ''
            if self._filename is not None:
                defDir, defFile = os.path.split(self._filename)
            dlg = wx.FileDialog(self._frame,
                                'Save File',
                                defDir, defFile,
                                'rfmon files (*.rfmon)|*.rfmon',
                                wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
            if dlg.ShowModal() == wx.ID_CANCEL:
                return
            self._filename = dlg.GetPath()

        self.__update_settings()
        save_recordings(self._filename,
                        self._settings)
        self.__set_title()

        for monitor in self._monitors:
            monitor.set_saved()

    def __save_warning(self):
        if not self.__is_saved():
            resp = wx.MessageBox('Data is not saved, continue?', 'Warning',
                                 wx.OK | wx.CANCEL | wx.ICON_WARNING)
            if resp != wx.OK:
                return False

        return True

    def open(self, filename):
        load_recordings(filename,
                        self._settings)

        self._filename = filename
        self.__set_title()
        self._toolbar.set_freq(self._settings.get_freq())
        self.__clear_monitors()
        self.__add_monitors()

        if self._dialogTimeline is not None:
            self._dialogTimeline.set_signals(self.__get_signals())

    def __enable_controls(self, enable):
        isSaved = self.__is_saved()

        self._menuOpen.Enable(enable)
        self._menuSave.Enable(enable)
        self._menuSaveAs.Enable(enable)
        self._menuClear.Enable(enable and isSaved)
        self._menuGps.Enable(enable)
        self._menuExit.Enable(enable)

    def __add_monitors(self):
        for monitor in self._settings.get_monitors():
            panelMonitor = PanelMonitor(self._window)
            panelMonitor.set_callback(self.__on_del)
            panelMonitor.set_freqs(self._freqs)
            panelMonitor.set_enabled(monitor.enabled)
            panelMonitor.set_freq(monitor.freq)
            panelMonitor.set_threshold(monitor.threshold)
            panelMonitor.set_signals(monitor.signals)
            self.__add_monitor(panelMonitor)

        self._frame.Layout()

    def __add_monitor(self, monitor):
        self._toolbar.enable_freq(False)

        self._monitors.append(monitor)
        self._sizerWindow.Add(monitor, 0, wx.ALL | wx.EXPAND, 5)

    def __clear_monitors(self):
        for _i in range(len(self._monitors)):
            self._sizerWindow.Hide(0)
            self._sizerWindow.Remove(0)

        self._frame.Layout()

        self._monitors = []
        self._toolbar.enable_freq(True)

    def __get_signals(self):
        return [(monitor.get_freq(),
                 monitor.get_signals())
                for monitor in self._monitors]

    def __is_saved(self):
        for monitor in self._monitors:
            if not monitor.get_saved():
                return False

        return True

    def __start_gps(self):
        if self._gps is None and self._settings.get_gps().enabled:
            self._status.SetStatusText('Staring GPS...', 1)
            self._gps = Gps(self._frame, self._settings.get_gps())

    def __stop_gps(self):
        if self._gps is not None:
            self._gps.stop()
            self._gps = None

    def __restart_gps(self):
        self.__stop_gps()
        wx.CallLater(GPS_RETRY * 1000, self.__start_gps)
Exemple #5
0
class Cli(wx.EvtHandler):
    def __init__(self, args):
        wx.EvtHandler.__init__(self)
        self._freq = None
        self._monitors = []
        self._freqs = []
        self._location = None
        self._filename = args.file
        self._server = None
        self._gps = None
        self._gpsPort = args.port
        self._gpsBaud = args.baud
        self._json = args.json
        self._pushUri = args.web
        self._warnedPush = False

        self._receive = None
        self._cancel = False

        self._queue = Queue.Queue()

        try:
            freq, gain, cal, dynP, monitors = load_recordings(self._filename)
        except ValueError:
            msg = '\'' + os.path.split(self._filename)[1] + '\' is corrupt.'
            self.__std_err(msg)
            self.__stop(None, None, None, None)
            exit(1)

        self._dynP = dynP
        enabled = [monitor for monitor in monitors
                   if monitor.get_enabled()]
        if not len(enabled):
            self.__std_err('No monitors enabled')
            self.__stop(None, None, None, None)
            exit(1)

        self.__std_out('Frequency: {}MHz'.format(freq))
        self.__std_out('Gain: {}dB\n'.format(gain))

        self.__add_monitors(monitors)

        self._signalCount = self.__count_signals()

        self._signal = signal.signal(signal.SIGINT, self.__on_exit)

        self._push = Push(self._queue)
        self._server = Server(self._queue)

        self.__start_gps()

        self.__start(freq, gain, cal)

        while not self._cancel:
            if not self._queue.empty():
                self.__on_event()
            else:
                try:
                    time.sleep(0.001)
                except IOError:
                    pass

        self.__stop(freq, gain, cal, dynP)

    def __save(self, freq, gain, cal, dynP):
        save_recordings(self._filename,
                        freq,
                        gain,
                        cal,
                        dynP,
                        self._monitors)

    def __is_saved(self):
        for monitor in self._monitors:
            if not monitor.get_saved():
                return False

        return True

    def __add_monitors(self, monitors):
        freqs = []
        for monitor in monitors:
            freq = monitor.get_frequency()
            freqs.append(freq)
            cliMonitor = CliMonitor(monitor.get_colour(),
                                    monitor.get_enabled(),
                                    monitor.get_alert(),
                                    freq,
                                    monitor.get_threshold(),
                                    monitor.get_dynamic(),
                                    monitor.get_signals(),
                                    monitor.get_periods())
            self._monitors.append(cliMonitor)

        freqs = map(str, freqs)
        self.__std_out('Monitors:')
        self.__std_out(', '.join(freqs) + 'MHz\n')

    def __count_signals(self):
        signals = 0
        for monitor in self._monitors:
            signals += len(monitor.get_signals())

        return signals

    def __start_gps(self):
        gpsDevice = GpsDevice()
        if self._gpsPort is not None:
            gpsDevice.port = self._gpsPort
        if self._gpsBaud is not None:
            gpsDevice.baud = self._gpsBaud
        if self._gpsPort is not None:
            self._gps = Gps(self._queue, gpsDevice)

    def __stop_gps(self):
        if self._gps is not None:
            self._gps.stop()

    def __restart_gps(self):
        timer = Timer(GPS_RETRY, self.__start_gps)
        timer.start()

    def __start(self, freq, gain, cal):
        self.__std_out('Monitoring...')

        timestamp = time.time()
        for monitor in self._monitors:
            monitor.start_period(timestamp)
        self._receive = Receive(self._queue,
                                freq,
                                gain,
                                cal)

    def __stop(self, freq, gain, cal, dynP):
        self.__std_out('\nStopping...')

        if self._receive is not None:
            self._receive.stop()
        timestamp = time.time()
        for monitor in self._monitors:
            monitor.set_level(None, timestamp, None)
            monitor.end_period(timestamp)

        self.__stop_gps()
        if self._server is not None:
            self._server.stop()

        if not self.__is_saved():
            self.__std_out('Saving..')
            self.__save(freq, gain, cal, dynP)

        while self._push.hasFailed():
            self.__std_out('Web push has failed, retry (Y/n)?')
            resp = ''
            while resp not in ['y', 'n', '\r', '\n']:
                resp = getch()
            if resp in ['y', '\r', '\n']:
                self.__std_out('Pushing...')
                self._push.send_failed(self._pushUri)
            else:
                self._push.clear_failed()

        self.__std_out('Finished')

    def __std_out(self, message, lf=True):
        if not self._json:
            if lf:
                message += '\n'
            sys.stdout.write(message)

    def __std_err(self, message):
        if not self._json:
            sys.stderr.write(message + '\n')

    def __on_exit(self, _signal=None, _frame=None):
        signal.signal(signal.SIGINT, self._signal)
        self._cancel = True

    def __on_event(self):
        event = self._queue.get()
        if event.type == Events.SCAN_ERROR:
            self.__on_scan_error(event.data)
        elif event.type == Events.SCAN_DATA:
            self.__on_scan_data(event.data)
        elif event.type == Events.SERVER_ERROR:
            self.__on_server_error(event.data)
        elif event.type == Events.GPS_ERROR:
            self.__std_err(event.data['msg'])
            self.__restart_gps()
        elif event.type == Events.GPS_WARN:
            self.__std_err(event.data['msg'])
        elif event.type == Events.GPS_TIMEOUT:
            self.__std_err(event.data['msg'])
            self.__restart_gps()
        elif event.type == Events.GPS_LOC:
            self._location = event.data['loc']
        elif event.type == Events.PUSH_ERROR:
            if not self._warnedPush:
                self._warnedPush = True
                self.__std_err('Push failed:\n\t' + event.data['msg'])
        else:
            time.sleep(0.01)

    def __on_scan_error(self, event):
        self.__std_err(event['msg'])
        self._cancel = True

    def __on_scan_data(self, event):
        levels = numpy.log10(event['l'])
        levels *= 10

        noise = numpy.percentile(levels,
                                 self._dynP)

        for monitor in self._monitors:
            freq = monitor.get_frequency()
            if monitor.get_enabled():
                monitor.set_noise(noise)
                index = numpy.where(freq == event['f'])[0]
                signal = monitor.set_level(levels[index][0],
                                           event['timestamp'],
                                           self._location)

                if signal is not None:
                    signals = 'Signals: {}\r'.format(self.__count_signals() -
                                                     self._signalCount)
                    self.__std_out(signals, False)
                    if signal.end is not None:
                        recording = format_recording(freq, signal)
                        if self._pushUri is not None:
                            self._push.send(self._pushUri,
                                            recording)
                        if self._server is not None:
                            self._server.send(recording)
                        if self._json:
                            sys.stdout.write(recording + '\n')

    def __on_server_error(self, event):
        self.__std_err(event['msg'])
        self._server = None
Exemple #6
0
class FrameMain(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None)
        self._monitors = []
        self._freqs = []
        self._levels = numpy.zeros(BINS, dtype=numpy.float32)
        self._settings = Settings()
        self._filename = None
        self._receive = None
        self._dialogTimeline = None
        self._dialogSpectrum = None
        self._gps = None
        self._location = None
        self._isSaved = True
        self._warnedPush = False

        cmap = cm.get_cmap('Set1')
        self._colours = [cmap(float(i) / COLOURS) for i in range(COLOURS)]

        self._ui = load_ui('FrameMain.xrc')

        self._menu = self._ui.LoadMenuBar('menuBar')
        self._panelMonitors = self._ui.LoadObject(self, 'scrolled', 'wxScrolledWindow')
        self._status = self._ui.LoadObject(self, 'statusBar', 'wxStatusBar')
        self._status.SetMinHeight(25)
        self._rssi = WidgetMeter(self._status)
        self._rssi.SetToolTipString('Max and mean signal strength (dB)')
        self._rssi.Show(self._settings.get_show_rssi())

        self._sizerMonitors = self._panelMonitors.GetSizer()
        self._toolbar = Toolbar(self)

        self.SetMenuBar(self._menu)
        self.SetStatusBar(self._status)

        self._mgr = aui.AuiManager(self)
        self._mgr.AddPane(self._panelMonitors, aui.AuiPaneInfo().
                          Centre().
                          CentrePane())
        self._mgr.AddPane(self._toolbar, aui.AuiPaneInfo().
                          Bottom().
                          ToolbarPane().
                          LeftDockable(False).
                          RightDockable(False).
                          NotebookDockable(False).
                          MinSize(self._toolbar.GetMinSize()))
        self._mgr.Update()

        width = self._toolbar.GetBestSize().GetWidth()
        self.SetSize((width, -1))
        self.SetMinSize((width, 300))

        self.__set_icon()

        try:
            sdr = RtlSdr()
            gains = sdr.get_gains()
            gains = [float(gain) / 10. for gain in gains]
            sdr.close()
        except IOError:
            wx.MessageBox('No radio found', APP_NAME, wx.OK | wx.ICON_ERROR)
            exit(1)

        self._toolbar.set_callbacks(self.__on_freq,
                                    self.__on_start,
                                    self.__on_rec,
                                    self.__on_stop,
                                    self.__on_add)
        self._toolbar.enable_start(True)
        self._toolbar.set_freq(self._settings.get_freq())
        self._toolbar.set_gains(gains)
        self._toolbar.set_gain(self._settings.get_gain())
        self._toolbar.set_cal(self._settings.get_cal())
        self._toolbar.set_dynamic_percentile(self._settings.
                                             get_dynamic_percentile())

        self.__on_freq(self._settings.get_freq())

        self._push = Push(self)
        self._server = Server(self)

        self.__start_gps()

        idOpen = xrc.XRCID('menuOpen')
        self._menuOpen = self._menu.FindItemById(idOpen)
        self.Bind(wx.EVT_MENU, self.__on_open, id=idOpen)
        idSave = xrc.XRCID('menuSave')
        self._menuSave = self._menu.FindItemById(idSave)
        self.Bind(wx.EVT_MENU, self.__on_save, id=idSave)
        idSaveAs = xrc.XRCID('menuSaveAs')
        self.Bind(wx.EVT_MENU, self.__on_save_as, id=idSaveAs)
        idClear = xrc.XRCID('menuClear')
        self._menuClear = self._menu.FindItemById(idClear)
        self.Bind(wx.EVT_MENU, self.__on_clear, id=idClear)
        idGps = xrc.XRCID('menuGps')
        self._menuGps = self._menu.FindItemById(idGps)
        self.Bind(wx.EVT_MENU, self.__on_gps, id=idGps)
        idRssi = xrc.XRCID('menuRssi')
        self.Bind(wx.EVT_MENU, self.__on_rssi, id=idRssi)
        self._menuRssi = self._menu.FindItemById(idRssi)
        self._menuRssi.Check(self._settings.get_show_rssi())
        idTimeline = xrc.XRCID('menuTimeline')
        self.Bind(wx.EVT_MENU, self.__on_timeline, id=idTimeline)
        self._menuTimeline = self._menu.FindItemById(idTimeline)
        idSpectrum = xrc.XRCID('menuSpectrum')
        self.Bind(wx.EVT_MENU, self.__on_spectrum, id=idSpectrum)
        self._menuSpectrum = self._menu.FindItemById(idSpectrum)
        idExit = xrc.XRCID('menuExit')
        self._menuExit = self._menu.FindItemById(idExit)
        self.Bind(wx.EVT_MENU, self.__on_exit, id=idExit)
        idPush = xrc.XRCID('menuPush')
        self.Bind(wx.EVT_MENU, self.__on_push, id=idPush)
        idAbout = xrc.XRCID('menuAbout')
        self.Bind(wx.EVT_MENU, self.__on_about, id=idAbout)

        self._alert = load_sound('alert.wav')
        self._alertLast = 0

        self.__set_title()
        self.__enable_controls(True)

        self._status.Bind(wx.EVT_SIZE, self.__on_size)

        self.Bind(EVT_TIMELINE_CLOSE, self.__on_timeline_close)
        self.Bind(EVT_SPECTRUM_CLOSE, self.__on_spectrum_close)

        self.Bind(wx.EVT_CLOSE, self.__on_exit)

        self.Connect(-1, -1, EVENT_THREAD, self.__on_event)

        self.Show()

        self.__clear_levels()

    def __on_freq(self, freq):
        _l, freqs = psd(numpy.zeros(2, dtype=numpy.complex64),
                        BINS, SAMPLE_RATE)
        freqs /= 1e6
        freqs += freq
        self._freqs = freqs.tolist()

        if self._receive is not None:
            self._receive.set_frequency(freq)

    def __on_start(self):
        self.__enable_controls(False)
        if self._receive is None:
            self._receive = Receive(self,
                                    self._toolbar.get_freq(),
                                    self._toolbar.get_gain(),
                                    self._toolbar.get_cal())

    def __on_rec(self, recording):
        timestamp = time.time()
        for monitor in self._monitors:
            if not recording:
                monitor.set_level(None, timestamp, None)
            monitor.set_recording(recording, timestamp)

        if recording:
            self.__on_start()
        else:
            while self._push.hasFailed():
                resp = wx.MessageBox('Web push has failed, retry?', 'Warning',
                                     wx.OK | wx.CANCEL | wx.ICON_WARNING)
                if resp == wx.OK:
                    busy = wx.BusyInfo('Pushing...', self)
                    self._push.send_failed(self._settings.get_push_uri())
                    del busy
                else:
                    self._push.clear_failed()

        self._warnedPush = False
        self.__set_timeline()

    def __on_stop(self):
        self.__clear_rssi()
        self.__enable_controls(True)
        if self._receive is not None:
            self._receive.stop()
            self._receive = None
        for monitor in self._monitors:
            monitor.set_noise(None)
            monitor.set_level(LEVEL_MIN, 0, None)
        self.__clear_levels()

    def __on_add(self):
        monitor = PanelMonitor(self._panelMonitors, self)
        monitor.set_callback(self.__on_del)
        monitor.set_freqs(self._freqs)
        monitor.set_colours(self._colours)
        monitor.set_recording(self._toolbar.is_recording(),
                              time.time())
        monitor.set_dynamic(False)
        self.__add_monitor(monitor)

        self._toolbar.enable_freq(False)

        self.__set_timeline()
        self.__set_spectrum()
        self._isSaved = False
        self.__set_title()

        scroll = self._panelMonitors.GetScrollRange(wx.VERTICAL)
        self._panelMonitors.Scroll(0, scroll)

    def __on_del(self, monitor):
        index = self._monitors.index(monitor)
        self._sizerMonitors.Hide(index)
        self._sizerMonitors.Remove(index)
        self.Layout()

        self._monitors.remove(monitor)

        self._toolbar.enable_freq(not len(self._monitors))

        self.__set_timeline()
        self.__set_spectrum()
        self._isSaved = False
        self.__set_title()

    def __on_open(self, _event):
        if not self.__save_warning():
            return

        defDir, defFile = '', ''
        if self._filename is not None:
            defDir, defFile = os.path.split(self._filename)
        dlg = wx.FileDialog(self,
                            'Open File',
                            defDir, defFile,
                            'rfmon files (*.rfmon)|*.rfmon',
                            wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
        if dlg.ShowModal() == wx.ID_CANCEL:
            return

        self.open(dlg.GetPath())

        self._isSaved = True
        self.__set_title()

    def __on_save(self, _event):
        self.__save(False)

    def __on_save_as(self, _event):
        self.__save(True)

    def __on_clear(self, _event):
        resp = wx.MessageBox('Clear recorded data?', 'Warning',
                             wx.OK | wx.CANCEL | wx.ICON_WARNING)
        if resp != wx.OK:
            return

        for monitor in self._monitors:
            monitor.clear()

        self.__set_timeline()
        self._isSaved = False
        self.__set_title()

    def __on_gps(self, _event):
        dlg = DialogGps(self, self._settings.get_gps())
        if dlg.ShowModal() == wx.ID_OK:
            self.__stop_gps()
            self.__start_gps()

    def __on_rssi(self, event):
        checked = event.IsChecked()
        self._rssi.Show(checked)
        self._settings.set_show_rssi(checked)

    def __on_timeline(self, event):
        if event.IsChecked() and self._dialogTimeline is None:
            self._dialogTimeline = DialogTimeline(self)
            self.__set_timeline()
            self._dialogTimeline.Show()
        elif self._dialogTimeline is not None:
            self._dialogTimeline.Destroy()
            self._dialogTimeline = None

    def __on_timeline_close(self, _event):
        self._menuTimeline.Check(False)
        self._dialogTimeline = None

    def __on_spectrum(self, event):
        if event.IsChecked() and self._dialogSpectrum is None:
            self._dialogSpectrum = DialogSpectrum(self,
                                                  self._freqs)
            self.__set_spectrum()
            self._dialogSpectrum.Show()
        elif self._dialogSpectrum is not None:
            self._dialogSpectrum.Destroy()
            self._dialogSpectrum = None

    def __on_spectrum_close(self, _event):
        self._menuSpectrum.Check(False)
        self._dialogSpectrum = None

    def __on_push(self, _event):
        dlg = DialogPush(self, self._settings)
        dlg.ShowModal()

    def __on_about(self, _event):
        dlg = DialogAbout(self)
        dlg.ShowModal()

    def __on_size(self, _event):
        rect = self._status.GetFieldRect(2)
        self._rssi.SetPosition((rect.x, rect.y))
        self._rssi.SetSize((rect.width, rect.height))

    def __on_exit(self, _event):
        if not self.__save_warning():
            return

        self.__on_rec(False)
        self.__on_stop()

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

        self.__stop_gps()

        self.__update_settings()
        self._settings.save()

        self._mgr.UnInit()
        self.Destroy()

    def __on_event(self, event):
        if event.type == Events.SCAN_ERROR:
            self.__on_scan_error(event.data)
        elif event.type == Events.SCAN_DATA:
            self.__on_scan_data(event.data)
        elif event.type == Events.SERVER_ERROR:
            self.__on_server_error(event.data)
        elif event.type == Events.GPS_ERROR:
            self._status.SetStatusText(event.data['msg'], 1)
            self.__restart_gps()
        elif event.type == Events.GPS_WARN:
            self._status.SetStatusText(event.data['msg'], 1)
        elif event.type == Events.GPS_TIMEOUT:
            self._status.SetStatusText(event.data['msg'], 1)
            self.__restart_gps()
        elif event.type == Events.GPS_LOC:
            self._location = event.data['loc']
            loc = '{:9.5f}, {:9.5f}'.format(*self._location)
            self._status.SetStatusText(loc, 1)
        elif event.type == Events.MON_ALERT:
            now = time.time()
            if now - self._alertLast >= ALERT_LENGTH:
                self._alertLast = now
                self._alert.Play()
        elif event.type == Events.CHANGED:
            self._isSaved = False
            self.__set_timeline()
            self.__set_spectrum()
            self.__set_title()
        elif event.type == Events.PUSH_ERROR:
            if not self._warnedPush:
                self._warnedPush = True
                wx.MessageBox('Error:\n\t' + event.data['msg'],
                              'Push failed', wx.OK | wx.ICON_ERROR)

    def __on_scan_error(self, event):
        self.__clear_rssi()
        wx.MessageBox(event['msg'],
                      'Error', wx.OK | wx.ICON_ERROR)
        self._toolbar.enable_start(True)

    def __on_scan_data(self, event):
        levels = numpy.log10(event['l'])
        levels *= 10
        self._levels = levels

        noise = numpy.percentile(levels,
                                 self._toolbar.get_dynamic_percentile())

        updated = False
        for monitor in self._monitors:
            freq = monitor.get_frequency()
            if monitor.get_enabled():
                monitor.set_noise(noise)
                index = numpy.where(freq == event['f'])[0]
                signal = monitor.set_level(levels[index][0],
                                           event['timestamp'],
                                           self._location)
                if signal is not None:
                    updated = True
                    if signal.end is not None:
                        recording = format_recording(freq, signal)
                        if self._settings.get_push_enable():
                            self._push.send(self._settings.get_push_uri(),
                                            recording)
                        if self._server is not None:
                            self._server.send(recording)

        if updated:
            if self._isSaved:
                self._isSaved = False
                self.__set_title()
                self.__set_timeline()

        self.__set_spectrum(noise)
        self._rssi.set_noise(numpy.mean(levels))
        self._rssi.set_level(numpy.max(levels))

    def __on_server_error(self, event):
        sys.stderr.write(event['msg'])
        self._server = None

    def __set_title(self):
        title = APP_NAME
        if self._filename is not None:
            _head, tail = os.path.split(self._filename)
            title += ' - ' + tail
            self._menuSave.Enable(not self._isSaved)
        else:
            self._menuSave.Enable(False)

        if not self._isSaved:
            title += '*'
        self.SetTitle(title)

    def __set_icon(self):
        icon = load_icon('logo.png')
        self.SetIcon(icon)

        if os.name == 'nt':
            import ctypes
            appId = u'com.eartoearoak.0.0'
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appId)

    def __update_settings(self):
        self._settings.set_freq(self._toolbar.get_freq())
        self._settings.set_gain(self._toolbar.get_gain())
        self._settings.set_cal(self._toolbar.get_cal())
        self._settings.set_dynamic_percentile(self._toolbar.
                                              get_dynamic_percentile())

    def __save(self, prompt):
        if prompt or self._filename is None:
            defDir, defFile = '', ''
            if self._filename is not None:
                defDir, defFile = os.path.split(self._filename)
            dlg = wx.FileDialog(self,
                                'Save File',
                                defDir, defFile,
                                'rfmon files (*.rfmon)|*.rfmon',
                                wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
            if dlg.ShowModal() == wx.ID_CANCEL:
                return
            self._filename = dlg.GetPath()

        self.__update_settings()
        save_recordings(self._filename,
                        self._settings.get_freq(),
                        self._settings.get_gain(),
                        self._settings.get_cal(),
                        self._settings.get_dynamic_percentile(),
                        self._monitors)
        self.__set_title()

        self._isSaved = True
        self.__set_title()

    def __save_warning(self):
        if not self._isSaved:
            resp = wx.MessageBox('Not saved, continue?', 'Warning',
                                 wx.OK | wx.CANCEL | wx.ICON_WARNING)
            if resp != wx.OK:
                return False

        return True

    def open(self, filename):
        try:
            freq, gain, cal, dynP, monitors = load_recordings(filename)
        except ValueError:
            msg = '\'' + os.path.split(filename)[1] + '\' is corrupt.'
            wx.MessageBox(msg, 'Error',
                          wx.OK | wx.ICON_ERROR)
            return

        self._filename = filename
        self.__set_title()
        self._toolbar.set_freq(freq)
        self._toolbar.set_gain(gain)
        self._toolbar.set_cal(cal)
        self._toolbar.set_dynamic_percentile(dynP)
        self.__clear_monitors()
        self.__add_monitors(monitors)
        self.__enable_controls(True)
        self.__set_timeline()
        self.__set_spectrum()
        self._isSaved = True
        self._warnedPush = False

    def __enable_controls(self, enable):
        self._menuOpen.Enable(enable)
        self._menuClear.Enable(enable and self.__has_recordings())
        self._menuGps.Enable(enable)
        self._menuExit.Enable(enable)

    def __add_monitors(self, monitors):
        for monitor in monitors:
            panelMonitor = PanelMonitor(self._panelMonitors, self)
            panelMonitor.set_callback(self.__on_del)
            panelMonitor.set_freqs(self._freqs)
            panelMonitor.set_colour(monitor.get_colour())
            panelMonitor.set_enabled(monitor.get_enabled())
            panelMonitor.set_alert(monitor.get_alert())
            panelMonitor.set_frequency(monitor.get_frequency())
            panelMonitor.set_dynamic(monitor.get_dynamic())
            panelMonitor.set_threshold(monitor.get_threshold())
            panelMonitor.set_signals(monitor.get_signals())
            panelMonitor.set_periods(monitor.get_periods())
            self.__add_monitor(panelMonitor)

        self.__set_spectrum()

    def __add_monitor(self, monitor):
        if monitor.get_colour() is None:
            colours = self.__get_used_colours()
            if len(colours):
                colour = colours[0]
            else:
                index = len(self._monitors) % COLOURS
                colour = self._colours[index]
            monitor.set_colour(colour)

        self._toolbar.enable_freq(False)

        self._monitors.append(monitor)
        self._sizerMonitors.Add(monitor, 0, wx.ALL | wx.EXPAND, 5)
        self.Layout()

    def __get_used_colours(self):
        colours = []
        for monitor in self._monitors:
            colours.append(monitor.get_colour())
        return [x for x in self._colours if x not in colours]

    def __clear_monitors(self):
        for _i in range(len(self._monitors)):
            self._sizerMonitors.Hide(0)
            self._sizerMonitors.Remove(0)

        self.Layout()

        self._monitors = []
        self._toolbar.enable_freq(True)
        self._isSaved = False

    def __has_recordings(self):
        for monitor in self._monitors:
            if len(monitor.get_signals()):
                return True

        return False

    def __set_timeline(self):
        monitors = [monitor for monitor in self._monitors
                    if monitor.get_enabled()]
        if self._dialogTimeline is not None:
            self._dialogTimeline.set_monitors(monitors,
                                              self._toolbar.is_recording())

    def __set_spectrum(self, noise=None):
        if self._dialogSpectrum is not None:
            monitors = [monitor for monitor in self._monitors
                        if monitor.get_enabled()]
            self._dialogSpectrum.set_spectrum(self._freqs,
                                              self._levels,
                                              monitors,
                                              noise)

    def __clear_levels(self):
        self._levels.fill(numpy.NaN)
        self.__set_spectrum()

    def __clear_rssi(self):
        self._rssi.set_noise(LEVEL_MIN)
        self._rssi.set_level(LEVEL_MIN)

    def __start_gps(self):
        if self._gps is None and self._settings.get_gps().enabled:
            self._status.SetStatusText('Staring GPS...', 1)
            self._gps = Gps(self, self._settings.get_gps())

    def __stop_gps(self):
        if self._gps is not None:
            self._gps.stop()
            self._gps = None

    def __restart_gps(self):
        self.__stop_gps()
        wx.CallLater(GPS_RETRY * 1000, self.__start_gps)
Exemple #7
0
class FrameMain(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None)
        self._monitors = []
        self._freqs = []
        self._levels = numpy.zeros(BINS, dtype=numpy.float32)
        self._settings = Settings()
        self._filename = None
        self._receive = None
        self._dialogTimeline = None
        self._dialogSpectrum = None
        self._gps = None
        self._location = None
        self._isSaved = True
        self._warnedPush = False

        cmap = cm.get_cmap('Set1')
        self._colours = [cmap(float(i) / COLOURS) for i in range(COLOURS)]

        self._ui = load_ui('FrameMain.xrc')

        self._menu = self._ui.LoadMenuBar('menuBar')
        self._panelMonitors = self._ui.LoadObject(self, 'scrolled', 'wxScrolledWindow')
        self._status = self._ui.LoadObject(self, 'statusBar', 'wxStatusBar')
        self._sizerMonitors = self._panelMonitors.GetSizer()
        self._toolbar = Toolbar(self)

        self.SetMenuBar(self._menu)
        self.SetStatusBar(self._status)

        self._mgr = aui.AuiManager(self)
        self._mgr.AddPane(self._panelMonitors, aui.AuiPaneInfo().
                          Centre().
                          CentrePane())
        self._mgr.AddPane(self._toolbar, aui.AuiPaneInfo().
                          Bottom().
                          ToolbarPane().
                          LeftDockable(False).
                          RightDockable(False).
                          NotebookDockable(False).
                          MinSize(self._toolbar.GetMinSize()))
        self._mgr.Update()

        width = self._toolbar.GetBestSize().GetWidth()
        self.SetSize((width, -1))
        self.SetMinSize((width, 300))

        self.__set_icon()

        try:
            sdr = RtlSdr()
            gains = sdr.get_gains()
            gains = [float(gain) / 10. for gain in gains]
            sdr.close()
        except IOError:
            wx.MessageBox('No radio found', APP_NAME, wx.OK | wx.ICON_ERROR)
            exit(1)

        self._toolbar.set_callbacks(self.__on_freq,
                                    self.__on_start,
                                    self.__on_rec,
                                    self.__on_stop,
                                    self.__on_add)
        self._toolbar.enable_start(True)
        self._toolbar.set_freq(self._settings.get_freq())
        self._toolbar.set_gains(gains)
        self._toolbar.set_gain(self._settings.get_gain())
        self._toolbar.set_cal(self._settings.get_cal())
        self._toolbar.set_dynamic_percentile(self._settings.
                                             get_dynamic_percentile())

        self.__on_freq(self._settings.get_freq())

        self._push = Push(self)
        self._server = Server(self)

        self.__start_gps()

        idOpen = xrc.XRCID('menuOpen')
        self._menuOpen = self._menu.FindItemById(idOpen)
        self.Bind(wx.EVT_MENU, self.__on_open, id=idOpen)
        idSave = xrc.XRCID('menuSave')
        self._menuSave = self._menu.FindItemById(idSave)
        self.Bind(wx.EVT_MENU, self.__on_save, id=idSave)
        idSaveAs = xrc.XRCID('menuSaveAs')
        self.Bind(wx.EVT_MENU, self.__on_save_as, id=idSaveAs)
        idClear = xrc.XRCID('menuClear')
        self._menuClear = self._menu.FindItemById(idClear)
        self.Bind(wx.EVT_MENU, self.__on_clear, id=idClear)
        idGps = xrc.XRCID('menuGps')
        self._menuGps = self._menu.FindItemById(idGps)
        self.Bind(wx.EVT_MENU, self.__on_gps, id=idGps)
        idTimeline = xrc.XRCID('menuTimeline')
        self.Bind(wx.EVT_MENU, self.__on_timeline, id=idTimeline)
        self._menuTimeline = self._menu.FindItemById(idTimeline)
        idSpectrum = xrc.XRCID('menuSpectrum')
        self.Bind(wx.EVT_MENU, self.__on_spectrum, id=idSpectrum)
        self._menuSpectrum = self._menu.FindItemById(idSpectrum)
        idExit = xrc.XRCID('menuExit')
        self._menuExit = self._menu.FindItemById(idExit)
        self.Bind(wx.EVT_MENU, self.__on_exit, id=idExit)
        idPush = xrc.XRCID('menuPush')
        self.Bind(wx.EVT_MENU, self.__on_push, id=idPush)
        idAbout = xrc.XRCID('menuAbout')
        self.Bind(wx.EVT_MENU, self.__on_about, id=idAbout)

        self._alert = load_sound('alert.wav')
        self._alertLast = 0

        self.__set_title()
        self.__enable_controls(True)

        self.Bind(EVT_TIMELINE_CLOSE, self.__on_timeline_close)
        self.Bind(EVT_SPECTRUM_CLOSE, self.__on_spectrum_close)

        self.Bind(wx.EVT_CLOSE, self.__on_exit)

        self.Connect(-1, -1, EVENT_THREAD, self.__on_event)

        self.Show()

        self.__clear_levels()

    def __on_freq(self, freq):
        _l, freqs = psd(numpy.zeros(2, dtype=numpy.complex64),
                        BINS, SAMPLE_RATE)
        freqs /= 1e6
        freqs += freq
        self._freqs = freqs.tolist()

        if self._receive is not None:
            self._receive.set_frequency(freq)

    def __on_start(self):
        self.__enable_controls(False)
        if self._receive is None:
            self._receive = Receive(self,
                                    self._toolbar.get_freq(),
                                    self._toolbar.get_gain(),
                                    self._toolbar.get_cal())

    def __on_rec(self, recording):
        timestamp = time.time()
        for monitor in self._monitors:
            if not recording:
                monitor.set_level(None, timestamp, None)
            monitor.set_recording(recording, timestamp)

        if recording:
            self.__on_start()
        else:
            while self._push.hasFailed():
                resp = wx.MessageBox('Web push has failed, retry?', 'Warning',
                                     wx.OK | wx.CANCEL | wx.ICON_WARNING)
                if resp == wx.OK:
                    busy = wx.BusyInfo('Pushing...', self)
                    self._push.send_failed(self._settings.get_push_uri())
                    del busy
                else:
                    self._push.clear_failed()

        self._warnedPush = False
        self.__set_timeline()

    def __on_stop(self):
        self.__enable_controls(True)
        if self._receive is not None:
            self._receive.stop()
            self._receive = None
        for monitor in self._monitors:
            monitor.set_noise(None)
            monitor.set_level(LEVEL_MIN, 0, None)
        self.__clear_levels()

    def __on_add(self):
        monitor = PanelMonitor(self._panelMonitors, self)
        monitor.set_callback(self.__on_del)
        monitor.set_freqs(self._freqs)
        monitor.set_colours(self._colours)
        monitor.set_recording(self._toolbar.is_recording(),
                              time.time())
        monitor.set_dynamic(False)
        self.__add_monitor(monitor)

        self._toolbar.enable_freq(False)

        self.__set_timeline()
        self.__set_spectrum()
        self._isSaved = False
        self.__set_title()

        scroll = self._panelMonitors.GetScrollRange(wx.VERTICAL)
        self._panelMonitors.Scroll(0, scroll)

    def __on_del(self, monitor):
        index = self._monitors.index(monitor)
        self._sizerMonitors.Hide(index)
        self._sizerMonitors.Remove(index)
        self.Layout()

        self._monitors.remove(monitor)

        self._toolbar.enable_freq(not len(self._monitors))

        self.__set_timeline()
        self.__set_spectrum()
        self._isSaved = False
        self.__set_title()

    def __on_open(self, _event):
        if not self.__save_warning():
            return

        defDir, defFile = '', ''
        if self._filename is not None:
            defDir, defFile = os.path.split(self._filename)
        dlg = wx.FileDialog(self,
                            'Open File',
                            defDir, defFile,
                            'rfmon files (*.rfmon)|*.rfmon',
                            wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
        if dlg.ShowModal() == wx.ID_CANCEL:
            return

        self.open(dlg.GetPath())

        self._isSaved = True
        self.__set_title()

    def __on_save(self, _event):
        self.__save(False)

    def __on_save_as(self, _event):
        self.__save(True)

    def __on_clear(self, _event):
        resp = wx.MessageBox('Clear recorded data?', 'Warning',
                             wx.OK | wx.CANCEL | wx.ICON_WARNING)
        if resp != wx.OK:
            return

        for monitor in self._monitors:
            monitor.clear()

        self.__set_timeline()
        self._isSaved = False
        self.__set_title()

    def __on_gps(self, _event):
        dlg = DialogGps(self, self._settings.get_gps())
        if dlg.ShowModal() == wx.ID_OK:
            self.__stop_gps()
            self.__start_gps()

    def __on_timeline(self, event):
        if event.IsChecked() and self._dialogTimeline is None:
            self._dialogTimeline = DialogTimeline(self)
            self.__set_timeline()
            self._dialogTimeline.Show()
        elif self._dialogTimeline is not None:
            self._dialogTimeline.Destroy()
            self._dialogTimeline = None

    def __on_timeline_close(self, _event):
        self._menuTimeline.Check(False)
        self._dialogTimeline = None

    def __on_spectrum(self, event):
        if event.IsChecked() and self._dialogSpectrum is None:
            self._dialogSpectrum = DialogSpectrum(self,
                                                  self._freqs)
            self.__set_spectrum()
            self._dialogSpectrum.Show()
        elif self._dialogSpectrum is not None:
            self._dialogSpectrum.Destroy()
            self._dialogSpectrum = None

    def __on_spectrum_close(self, _event):
        self._menuSpectrum.Check(False)
        self._dialogSpectrum = None

    def __on_push(self, _event):
        dlg = DialogPush(self, self._settings)
        dlg.ShowModal()

    def __on_about(self, _event):
        dlg = DialogAbout(self)
        dlg.ShowModal()

    def __on_exit(self, _event):
        if not self.__save_warning():
            return

        self.__on_rec(False)
        self.__on_stop()

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

        self.__stop_gps()

        self.__update_settings()
        self._settings.save()

        self._mgr.UnInit()
        self.Destroy()

    def __on_event(self, event):
        if event.type == Events.SCAN_ERROR:
            self.__on_scan_error(event.data)
        elif event.type == Events.SCAN_DATA:
            self.__on_scan_data(event.data)
        elif event.type == Events.SERVER_ERROR:
            self.__on_server_error(event.data)
        elif event.type == Events.GPS_ERROR:
            self._status.SetStatusText(event.data['msg'], 1)
            self.__restart_gps()
        elif event.type == Events.GPS_WARN:
            self._status.SetStatusText(event.data['msg'], 1)
        elif event.type == Events.GPS_TIMEOUT:
            self._status.SetStatusText(event.data['msg'], 1)
            self.__restart_gps()
        elif event.type == Events.GPS_LOC:
            self._location = event.data['loc']
            loc = '{:9.5f}, {:9.5f}'.format(*self._location)
            self._status.SetStatusText(loc, 1)
        elif event.type == Events.MON_ALERT:
            now = time.time()
            if now - self._alertLast >= ALERT_LENGTH:
                self._alertLast = now
                self._alert.Play()
        elif event.type == Events.CHANGED:
            self._isSaved = False
            self.__set_timeline()
            self.__set_spectrum()
            self.__set_title()
        elif event.type == Events.PUSH_ERROR:
            if not self._warnedPush:
                self._warnedPush = True
                wx.MessageBox('Error:\n\t' + event.data['msg'],
                              'Push failed', wx.OK | wx.ICON_ERROR)

    def __on_scan_error(self, event):
        wx.MessageBox(event['msg'],
                      'Error', wx.OK | wx.ICON_ERROR)
        self._toolbar.enable_start(True)

    def __on_scan_data(self, event):
        levels = numpy.log10(event['l'])
        levels *= 10
        self._levels = levels

        noise = numpy.percentile(levels,
                                 self._toolbar.get_dynamic_percentile())

        updated = False
        for monitor in self._monitors:
            freq = monitor.get_frequency()
            if monitor.get_enabled():
                monitor.set_noise(noise)
                index = numpy.where(freq == event['f'])[0]
                signal = monitor.set_level(levels[index][0],
                                           event['timestamp'],
                                           self._location)
                if signal is not None:
                    updated = True
                    if signal.end is not None:
                        recording = format_recording(freq, signal)
                        if self._settings.get_push_enable():
                            self._push.send(self._settings.get_push_uri(),
                                            recording)
                        if self._server is not None:
                            self._server.send(recording)

        if updated:
            if self._isSaved:
                self._isSaved = False
                self.__set_title()
                self.__set_timeline()

        self.__set_spectrum(noise)

    def __on_server_error(self, event):
        sys.stderr.write(event['msg'])
        self._server = None

    def __set_title(self):
        title = APP_NAME
        if self._filename is not None:
            _head, tail = os.path.split(self._filename)
            title += ' - ' + tail
            self._menuSave.Enable(not self._isSaved)
        else:
            self._menuSave.Enable(False)

        if not self._isSaved:
            title += '*'
        self.SetTitle(title)

    def __set_icon(self):
        icon = load_icon('logo.png')
        self.SetIcon(icon)

        if os.name == 'nt':
            import ctypes
            appId = u'com.eartoearoak.0.0'
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appId)

    def __update_settings(self):
        self._settings.set_freq(self._toolbar.get_freq())
        self._settings.set_gain(self._toolbar.get_gain())
        self._settings.set_cal(self._toolbar.get_cal())
        self._settings.set_dynamic_percentile(self._toolbar.
                                              get_dynamic_percentile())

    def __save(self, prompt):
        if prompt or self._filename is None:
            defDir, defFile = '', ''
            if self._filename is not None:
                defDir, defFile = os.path.split(self._filename)
            dlg = wx.FileDialog(self,
                                'Save File',
                                defDir, defFile,
                                'rfmon files (*.rfmon)|*.rfmon',
                                wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
            if dlg.ShowModal() == wx.ID_CANCEL:
                return
            self._filename = dlg.GetPath()

        self.__update_settings()
        save_recordings(self._filename,
                        self._settings.get_freq(),
                        self._settings.get_gain(),
                        self._settings.get_cal(),
                        self._settings.get_dynamic_percentile(),
                        self._monitors)
        self.__set_title()

        self._isSaved = True
        self.__set_title()

    def __save_warning(self):
        if not self._isSaved:
            resp = wx.MessageBox('Not saved, continue?', 'Warning',
                                 wx.OK | wx.CANCEL | wx.ICON_WARNING)
            if resp != wx.OK:
                return False

        return True

    def open(self, filename):
        try:
            freq, gain, cal, dynP, monitors = load_recordings(filename)
        except ValueError:
            msg = '\'' + os.path.split(filename)[1] + '\' is corrupt.'
            wx.MessageBox(msg, 'Error',
                          wx.OK | wx.ICON_ERROR)
            return

        self._filename = filename
        self.__set_title()
        self._toolbar.set_freq(freq)
        self._toolbar.set_gain(gain)
        self._toolbar.set_cal(cal)
        self._toolbar.set_dynamic_percentile(dynP)
        self.__clear_monitors()
        self.__add_monitors(monitors)
        self.__enable_controls(True)
        self.__set_timeline()
        self.__set_spectrum()
        self._isSaved = True
        self._warnedPush = False

    def __enable_controls(self, enable):
        self._menuOpen.Enable(enable)
        self._menuClear.Enable(enable and self.__has_recordings())
        self._menuGps.Enable(enable)
        self._menuExit.Enable(enable)

    def __add_monitors(self, monitors):
        for monitor in monitors:
            panelMonitor = PanelMonitor(self._panelMonitors, self)
            panelMonitor.set_callback(self.__on_del)
            panelMonitor.set_freqs(self._freqs)
            panelMonitor.set_colour(monitor.get_colour())
            panelMonitor.set_enabled(monitor.get_enabled())
            panelMonitor.set_alert(monitor.get_alert())
            panelMonitor.set_frequency(monitor.get_frequency())
            panelMonitor.set_dynamic(monitor.get_dynamic())
            panelMonitor.set_threshold(monitor.get_threshold())
            panelMonitor.set_signals(monitor.get_signals())
            panelMonitor.set_periods(monitor.get_periods())
            self.__add_monitor(panelMonitor)

        self.__set_spectrum()

    def __add_monitor(self, monitor):
        if monitor.get_colour() is None:
            colours = self.__get_used_colours()
            if len(colours):
                colour = colours[0]
            else:
                index = len(self._monitors) % COLOURS
                colour = self._colours[index]
            monitor.set_colour(colour)

        self._toolbar.enable_freq(False)

        self._monitors.append(monitor)
        self._sizerMonitors.Add(monitor, 0, wx.ALL | wx.EXPAND, 5)
        self.Layout()

    def __get_used_colours(self):
        colours = []
        for monitor in self._monitors:
            colours.append(monitor.get_colour())
        return [x for x in self._colours if x not in colours]

    def __clear_monitors(self):
        for _i in range(len(self._monitors)):
            self._sizerMonitors.Hide(0)
            self._sizerMonitors.Remove(0)

        self.Layout()

        self._monitors = []
        self._toolbar.enable_freq(True)
        self._isSaved = False

    def __has_recordings(self):
        for monitor in self._monitors:
            if len(monitor.get_signals()):
                return True

        return False

    def __set_timeline(self):
        monitors = [monitor for monitor in self._monitors
                    if monitor.get_enabled()]
        if self._dialogTimeline is not None:
            self._dialogTimeline.set_monitors(monitors,
                                              self._toolbar.is_recording())

    def __set_spectrum(self, noise=None):
        if self._dialogSpectrum is not None:
            monitors = [monitor for monitor in self._monitors
                        if monitor.get_enabled()]
            self._dialogSpectrum.set_spectrum(self._freqs,
                                              self._levels,
                                              monitors,
                                              noise)

    def __clear_levels(self):
        self._levels.fill(numpy.NaN)
        self.__set_spectrum()

    def __start_gps(self):
        if self._gps is None and self._settings.get_gps().enabled:
            self._status.SetStatusText('Staring GPS...', 1)
            self._gps = Gps(self, self._settings.get_gps())

    def __stop_gps(self):
        if self._gps is not None:
            self._gps.stop()
            self._gps = None

    def __restart_gps(self):
        self.__stop_gps()
        wx.CallLater(GPS_RETRY * 1000, self.__start_gps)