Example #1
0
class EnumSetting(Setting):
  values = List()
  traits_view = View(
    VGroup(
      Item('full_name', label='Name', style='readonly'),
      Item('value', editor=EnumEditor(name='values')),
      Item('description', style='readonly'),
      Item('default_value', style='readonly'),
            UItem('notes', label="Notes", height=-1,
            editor=MultilineTextEditor(TextEditor(multi_line=True)), style='readonly',
            show_label=True, resizable=True),
      show_border=True,
      label='Setting',
    ),
  )

  def __init__(self, name, section, value, ordering, values, **kwargs):
    self.values = values
    Setting.__init__(self, name, section, value, ordering, **kwargs)
Example #2
0
class Setting(SettingBase):
    full_name = Str()
    section = Str()

    traits_view = View(
        VGroup(
            Item('full_name', label='Name', style='readonly'),
            Item('value', editor=TextEditor(auto_set=False, enter_set=True)),
            Item('description', style='readonly'),
            Item('units', style='readonly'),
            Item('default_value', style='readonly'),
            UItem('notes',
                  label="Notes",
                  height=-1,
                  editor=MultilineTextEditor(TextEditor(multi_line=True)),
                  style='readonly',
                  show_label=True,
                  resizable=True),
            show_border=True,
            label='Setting',
        ), )

    def __init__(self, name, section, value, ordering, settings):
        self.name = name
        self.section = section
        self.full_name = "%s.%s" % (section, name)
        self.value = value
        self.ordering = ordering
        self.settings = settings
        self.expert = settings.settings_yaml.get_field(section, name, 'expert')
        self.description = settings.settings_yaml.get_field(
            section, name, 'Description')
        self.units = settings.settings_yaml.get_field(section, name, 'units')
        self.notes = settings.settings_yaml.get_field(section, name, 'Notes')
        self.default_value = settings.settings_yaml.get_field(
            section, name, 'default value')

    def _value_changed(self, name, old, new):
        if (old != new and old is not Undefined and new is not Undefined):
            if type(self.value) == unicode:
                self.value = self.value.encode('ascii', 'replace')
            self.settings.set(self.section, self.name, self.value)
Example #3
0
class SbpRelayView(HasTraits):
    """
  UDP Relay view- Class allows user to specify port, IP address, and message set
  to relay over UDP
  """
    running = Bool(False)
    configured = Bool(False)
    broadcasting = Bool(False)
    msg_enum = Enum('Observations', 'All')
    ip_ad = String(DEFAULT_UDP_ADDRESS)
    port = Int(DEFAULT_UDP_PORT)
    information = String(
        'UDP Streaming\n\nBroadcast SBP information received by'
        ' the console to other machines or processes over UDP. With the \'Observations\''
        ' radio button selected, the console will broadcast the necessary information'
        ' for a rover Piksi to acheive an RTK solution.'
        '\n\nThis can be used to stream observations to a remote Piksi through'
        ' aircraft telemetry via ground control software such as MAVProxy or'
        ' Mission Planner.')
    http_information = String(
        'Skylark - Experimental Piksi Networking\n\n'
        "Skylark is Swift Navigation's Internet service for connecting Piksi receivers without the use of a radio. To receive GPS observations from the closest nearby Piksi base station (within 5km), click Connect to Skylark.\n\n"
    )
    start = Button(label='Start', toggle=True, width=32)
    stop = Button(label='Stop', toggle=True, width=32)
    connected_rover = Bool(False)
    connect_rover = Button(label='Connect to Skylark', toggle=True, width=32)
    disconnect_rover = Button(label='Disconnect from Skylark',
                              toggle=True,
                              width=32)
    base_pragma = String()
    rover_pragma = String()
    base_device_uid = String()
    rover_device_uid = String()
    toggle = True
    view = View(
        VGroup(
            spring,
            HGroup(
                VGroup(
                    Item('running',
                         show_label=True,
                         style='readonly',
                         visible_when='running'),
                    Item('msg_enum',
                         label="Messages to broadcast",
                         style='custom',
                         enabled_when='not running'),
                    Item('ip_ad',
                         label='IP Address',
                         enabled_when='not running'),
                    Item('port', label="Port", enabled_when='not running'),
                    HGroup(
                        spring,
                        UItem('start',
                              enabled_when='not running',
                              show_label=False),
                        UItem('stop', enabled_when='running',
                              show_label=False), spring)),
                VGroup(
                    Item('information',
                         label="Notes",
                         height=10,
                         editor=MultilineTextEditor(
                             TextEditor(multi_line=True)),
                         style='readonly',
                         show_label=False,
                         resizable=True,
                         padding=15),
                    spring,
                ),
            ), spring,
            HGroup(
                VGroup(
                    HGroup(
                        spring,
                        UItem('connect_rover',
                              enabled_when='not connected_rover',
                              show_label=False),
                        UItem('disconnect_rover',
                              enabled_when='connected_rover',
                              show_label=False), spring),
                    HGroup(spring, Item('base_pragma', label='Base option '),
                           Item('base_device_uid', label='Base device '),
                           spring),
                    HGroup(spring, Item('rover_pragma', label='Rover option'),
                           Item('rover_device_uid', label='Rover device'),
                           spring),
                ),
                VGroup(
                    Item('http_information',
                         label="Notes",
                         height=10,
                         editor=MultilineTextEditor(
                             TextEditor(multi_line=True)),
                         style='readonly',
                         show_label=False,
                         resizable=True,
                         padding=15),
                    spring,
                ),
            ), spring))

    def __init__(self,
                 link,
                 device_uid=None,
                 base=DEFAULT_BASE,
                 whitelist=None):
        """
    Traits tab with UI for UDP broadcast of SBP.

    Parameters
    ----------
    link : sbp.client.handler.Handler
      Link for SBP transfer to/from Piksi.
    device_uid : str
      Piksi Device UUID (defaults to None)
    base : str
      HTTP endpoint
    whitelist : [int] | None
      Piksi Device UUID (defaults to None)

    """
        self.link = link
        self.http = HTTPDriver(None, base)
        self.net_link = None
        self.fwd = None
        self.func = None
        # Whitelist used for UDP broadcast view
        self.msgs = OBS_MSGS
        # register a callback when the msg_enum trait changes
        self.on_trait_change(self.update_msgs, 'msg_enum')
        # Whitelist used for Skylark broadcasting
        self.whitelist = whitelist
        self.device_uid = None
        self.python_console_cmds = {'update': self}

    def update_msgs(self):
        """Updates the instance variable msgs which store the msgs that we
    will send over UDP.

    """
        if self.msg_enum == 'Observations':
            self.msgs = OBS_MSGS
        elif self.msg_enum == 'All':
            self.msgs = [None]
        else:
            raise NotImplementedError

    def set_route(self, serial_id, channel=CHANNEL_UUID):
        """Sets serial_id hash for HTTP headers.

    Parameters
    ----------
    serial_id : int
      Piksi device ID
    channel : str
      UUID namespace for device UUID

    """
        device_uid = str(get_uuid(channel, serial_id))
        self.device_uid = device_uid
        if self.http:
            self.http.device_uid = device_uid

    def _prompt_networking_error(self, text):
        """Nonblocking prompt for a networking error.

    Parameters
    ----------
    text : str
      Helpful error message for the user

    """
        prompt = CallbackPrompt(title="Networking Error",
                                actions=[close_button])
        prompt.text = text
        prompt.run(block=False)

    def _prompt_setting_error(self, text):
        """Nonblocking prompt for a device setting error.

    Parameters
    ----------
    text : str
      Helpful error message for the user

    """
        prompt = CallbackPrompt(title="Setting Error", actions=[close_button])
        prompt.text = text
        prompt.run(block=False)

    def _retry_read(self):
        """Retry read connections. Intended to be called by
    _connect_rover_fired.

    """
        i = 0
        repeats = 5
        _rover_pragma = self.rover_pragma
        _rover_device_uid = self.rover_device_uid or self.device_uid
        while self.http and not self.http.connect_read(
                device_uid=_rover_device_uid, pragma=_rover_pragma):
            print "Attempting to read observation from Skylark..."
            time.sleep(0.1)
            i += 1
            if i >= repeats:
                msg = (
                    "\nUnable to receive observations from Skylark!\n\n"
                    "Please check that:\n"
                    " - you have a network connection\n"
                    " - your Piksi has a single-point position\n"
                    " - a Skylark-connected Piksi receiver \n   is nearby (within 5km)"
                )
                self._prompt_networking_error(msg)
                self.http.read_close()
                return
        print "Connected as a rover!"
        with Handler(Framer(self.http.read, self.http.write)) as net_link:
            self.net_link = net_link
            self.fwd = Forwarder(net_link, swriter(self.link))
            self.fwd.start()
            while True:
                time.sleep(1)
                if not net_link.is_alive():
                    sys.stderr.write(
                        "Network observation stream disconnected!")
                    break
        # Unless the user has initiated a reconnection, assume here that the rover
        # still wants to be connected, so if we break out of the handler loop,
        # cleanup rover connection and try reconnecting.
        if self.connected_rover:
            sys.stderr.write("Going for a networking reconnection!")
            self._disconnect_rover_fired()
            self._connect_rover_fired()

    def _connect_rover_fired(self):
        """Handle callback for HTTP rover connections.

    """
        if not self.device_uid:
            msg = "\nDevice ID not found!\n\nConnection requires a valid Piksi device ID."
            self._prompt_setting_error(msg)
            return
        if not self.http:
            self._prompt_networking_error("\nNetworking disabled!")
            return
        try:
            _base_pragma = self.base_pragma
            _base_device_uid = self.base_device_uid or self.device_uid
            if not self.http.connect_write(self.link,
                                           self.whitelist,
                                           device_uid=_base_device_uid,
                                           pragma=_base_pragma):
                msg = ("\nUnable to connect to Skylark!\n\n"
                       "Please check that you have a network connection.")
                self._prompt_networking_error(msg)
                self.http.close()
                self.connected_rover = False
                return
            self.connected_rover = True
            print "Connected as a base station!"
            executor = ThreadPoolExecutor(max_workers=2)
            executor.submit(self._retry_read)
        except:
            self.connected_rover = False
            import traceback
            print traceback.format_exc()

    def _disconnect_rover_fired(self):
        """Handle callback for HTTP rover disconnects.

    """
        if not self.device_uid:
            msg = "\nDevice ID not found!\n\nConnection requires a valid Piksi device ID."
            self._prompt_setting_error(msg)
            return
        if not self.http:
            self._prompt_networking_error("\nNetworking disabled!")
            return
        try:
            if self.connected_rover:
                self.http.close()
                self.connected_rover = False
                if self.fwd and self.net_link:
                    self.net_link.stop()
        except:
            self.connected_rover = False
            import traceback
            print traceback.format_exc()

    def _start_fired(self):
        """Handle start udp broadcast button. Registers callbacks on
    self.link for each of the self.msgs If self.msgs is None, it
    registers one generic callback for all messages.

    """
        self.running = True
        try:
            self.func = UdpLogger(self.ip_ad, self.port)
            self.link.add_callback(self.func, self.msgs)
        except:
            import traceback
            print traceback.format_exc()

    def _stop_fired(self):
        """Handle the stop udp broadcast button. It uses the self.funcs and
    self.msgs to remove the callbacks that were registered when the
    start button was pressed.

    """
        try:
            self.link.remove_callback(self.func, self.msgs)
            self.func.__exit__()
            self.func = None
            self.running = False
        except:
            import traceback
            print traceback.format_exc()
Example #4
0
class SolutionView(HasTraits):
    python_console_cmds = Dict()
    # we need to doubleup on Lists to store the psuedo absolutes separately
    # without rewriting everything
    lats = List()
    lngs = List()
    alts = List()

    lats_psuedo_abs = List()
    lngs_psuedo_abs = List()
    alts_psuedo_abs = List()

    table_spp = List()
    table_psuedo_abs = List()
    dops_table = List()
    pos_table_spp = List()
    vel_table = List()

    rtk_pos_note = Str(
        "It is necessary to enter the \"Surveyed Position\" settings for the base station in order to view the psuedo-absolute RTK Positions in this tab."
    )

    plot = Instance(Plot)
    plot_data = Instance(ArrayPlotData)
    # Store plots we care about for legend

    running = Bool(True)
    zoomall = Bool(False)
    position_centered = Bool(False)

    clear_button = SVGButton(label='',
                             tooltip='Clear',
                             filename=os.path.join(determine_path(), 'images',
                                                   'iconic', 'x.svg'),
                             width=16,
                             height=16)
    zoomall_button = SVGButton(label='',
                               tooltip='Zoom All',
                               toggle=True,
                               filename=os.path.join(determine_path(),
                                                     'images', 'iconic',
                                                     'fullscreen.svg'),
                               width=16,
                               height=16)
    center_button = SVGButton(label='',
                              tooltip='Center on Solution',
                              toggle=True,
                              filename=os.path.join(determine_path(), 'images',
                                                    'iconic', 'target.svg'),
                              width=16,
                              height=16)
    paused_button = SVGButton(label='',
                              tooltip='Pause',
                              toggle_tooltip='Run',
                              toggle=True,
                              filename=os.path.join(determine_path(), 'images',
                                                    'iconic', 'pause.svg'),
                              toggle_filename=os.path.join(
                                  determine_path(), 'images', 'iconic',
                                  'play.svg'),
                              width=16,
                              height=16)

    traits_view = View(
        HSplit(
            Tabbed(
                VGroup(Item('',
                            label='Single Point Position (SPP)',
                            emphasized=True),
                       Item('table_spp',
                            style='readonly',
                            editor=TabularEditor(adapter=SimpleAdapter()),
                            show_label=False,
                            width=0.3),
                       label='Single Point Position'),
                VGroup(Item('', label='RTK Position', emphasized=True),
                       Item('table_psuedo_abs',
                            style='readonly',
                            editor=TabularEditor(adapter=SimpleAdapter()),
                            show_label=False,
                            width=0.3,
                            height=0.9),
                       Item('rtk_pos_note',
                            show_label=False,
                            resizable=True,
                            editor=MultilineTextEditor(
                                TextEditor(multi_line=True)),
                            style='readonly',
                            width=0.3,
                            height=-40),
                       label='RTK Position')),
            VGroup(
                HGroup(
                    Item('paused_button', show_label=False),
                    Item('clear_button', show_label=False),
                    Item('zoomall_button', show_label=False),
                    Item('center_button', show_label=False),
                ),
                Item('plot',
                     show_label=False,
                     editor=ComponentEditor(bgcolor=(0.8, 0.8, 0.8))),
            )))

    def _zoomall_button_fired(self):
        self.zoomall = not self.zoomall

    def _center_button_fired(self):
        self.position_centered = not self.position_centered

    def _paused_button_fired(self):
        self.running = not self.running

    def _clear_button_fired(self):
        self.lats = []
        self.lngs = []
        self.alts = []
        self.lats_psuedo_abs = []
        self.lngs_psuedo_abs = []
        self.alts_psuedo_abs = []
        self.plot_data.set_data('lat', [])
        self.plot_data.set_data('lng', [])
        self.plot_data.set_data('alt', [])
        self.plot_data.set_data('t', [])
        self.plot_data.set_data('lat_ps', [])
        self.plot_data.set_data('lng_ps', [])
        self.plot_data.set_data('alt_ps', [])
        self.plot_data.set_data('t_ps', [])

    def _pos_llh_callback(self, sbp_msg, **metadata):
        # Updating an ArrayPlotData isn't thread safe (see chaco issue #9), so
        # actually perform the update in the UI thread.
        if self.running:
            GUI.invoke_later(self.pos_llh_callback, sbp_msg)

    def update_table(self):
        self._table_list = self.table_spp.items()

    def pos_llh_callback(self, sbp_msg, **metadata):
        soln = MsgPosLLH(sbp_msg)
        masked_flag = soln.flags & 0x7
        if masked_flag == 0:
            psuedo_absolutes = False
        else:
            psuedo_absolutes = True
        pos_table = []

        if self.log_file is None:
            self.log_file = open(
                time.strftime("position_log_%Y%m%d-%H%M%S.csv"), 'w')
            self.log_file.write(
                "time,latitude(degrees),longitude(degrees),altitude(meters),n_sats,flags\n"
            )
        tow = soln.tow * 1e-3
        if self.nsec is not None:
            tow += self.nsec * 1e-9

        if self.week is not None:
            t = datetime.datetime(1980, 1, 6) + \
                datetime.timedelta(weeks=self.week) + \
                datetime.timedelta(seconds=tow)
            pos_table.append(('GPS Time', t))
            pos_table.append(('GPS Week', str(self.week)))

            self.log_file.write('%s,%.10f,%.10f,%.4f,%d,%d\n' %
                                (str(t), soln.lat, soln.lon, soln.height,
                                 soln.n_sats, soln.flags))
            self.log_file.flush()

        pos_table.append(('GPS ToW', tow))

        pos_table.append(('Num. sats', soln.n_sats))

        pos_table.append(('Lat', soln.lat))
        pos_table.append(('Lng', soln.lon))
        pos_table.append(('Alt', soln.height))
        pos_table.append(('Flags', '0x%02x' % soln.flags))
        if (soln.flags & 0xff) == 0:
            pos_table.append(('Mode', 'SPP (single point position)'))
        elif (soln.flags & 0xff) == 1:
            pos_table.append(('Mode', 'Fixed RTK'))
        elif (soln.flags & 0xff) == 2:
            pos_table.append(('Mode', 'Float RTK'))
        else:
            pos_table.append(('Mode', 'Unknown'))

        if psuedo_absolutes:
            # setup_plot variables
            self.lats_psuedo_abs.append(soln.lat)
            self.lngs_psuedo_abs.append(soln.lon)
            self.alts_psuedo_abs.append(soln.height)

            self.lats_psuedo_abs = self.lats_psuedo_abs[-1000:]
            self.lngs_psuedo_abs = self.lngs_psuedo_abs[-1000:]
            self.alts_psuedo_abs = self.alts_psuedo_abs[-1000:]

            self.plot_data.set_data('lat_ps', self.lats_psuedo_abs)
            self.plot_data.set_data('lng_ps', self.lngs_psuedo_abs)
            self.plot_data.set_data('alt_ps', self.alts_psuedo_abs)
            self.plot_data.set_data('cur_lat_ps', [soln.lat])
            self.plot_data.set_data('cur_lng_ps', [soln.lon])
            t_psuedo_abs = range(len(self.lats))
            self.plot_data.set_data('t', t)
            self.plot_data.set_data('t_ps', t_psuedo_abs)
            # set-up table variables
            self.table_psuedo_abs = pos_table

        else:
            # setup_plot variables
            self.lats.append(soln.lat)
            self.lngs.append(soln.lon)
            self.alts.append(soln.height)

            self.lats = self.lats[-1000:]
            self.lngs = self.lngs[-1000:]
            self.alts = self.alts[-1000:]

            self.plot_data.set_data('lat', self.lats)
            self.plot_data.set_data('lng', self.lngs)
            self.plot_data.set_data('alt', self.alts)
            self.plot_data.set_data('cur_lat', [soln.lat])
            self.plot_data.set_data('cur_lng', [soln.lon])
            t = range(len(self.lats))
            self.plot_data.set_data('t', t)

            # set-up table variables
            self.pos_table_spp = pos_table
            self.table_spp = self.pos_table_spp + self.vel_table + self.dops_table
            # TODO: figure out how to center the graph now that we have two separate messages
            # when we selectivtely send only SPP, the centering function won't work anymore
            if self.position_centered:
                d = (self.plot.index_range.high -
                     self.plot.index_range.low) / 2.
                self.plot.index_range.set_bounds(soln.lon - d, soln.lon + d)
                d = (self.plot.value_range.high -
                     self.plot.value_range.low) / 2.
                self.plot.value_range.set_bounds(soln.lat - d, soln.lat + d)
        if self.zoomall:
            plot_square_axes(self.plot, 'lng', 'lat')

    def dops_callback(self, sbp_msg, **metadata):
        dops = MsgDops(sbp_msg)
        self.dops_table = [('PDOP', '%.1f' % (dops.pdop * 0.01)),
                           ('GDOP', '%.1f' % (dops.gdop * 0.01)),
                           ('TDOP', '%.1f' % (dops.tdop * 0.01)),
                           ('HDOP', '%.1f' % (dops.hdop * 0.01)),
                           ('VDOP', '%.1f' % (dops.vdop * 0.01))]
        self.table_spp = self.pos_table_spp + self.vel_table + self.dops_table

    def vel_ned_callback(self, sbp_msg, **metadata):
        vel_ned = MsgVelNED(sbp_msg)

        if self.vel_log_file is None:
            self.vel_log_file = open(
                time.strftime("velocity_log_%Y%m%d-%H%M%S.csv"), 'w')
            self.vel_log_file.write(
                'time,north(m/s),east(m/s),down(m/s),speed(m/s),num_sats\n')

        tow = vel_ned.tow * 1e-3
        if self.nsec is not None:
            tow += self.nsec * 1e-9

        if self.week is not None:
            t = datetime.datetime(1980, 1, 6) + \
                datetime.timedelta(weeks=self.week) + \
                datetime.timedelta(seconds=tow)

            self.vel_log_file.write(
                '%s,%.6f,%.6f,%.6f,%.6f,%d\n' %
                (str(t), vel_ned.n * 1e-3, vel_ned.e * 1e-3, vel_ned.d * 1e-3,
                 math.sqrt(vel_ned.n * vel_ned.n + vel_ned.e * vel_ned.e) *
                 1e-3, vel_ned.n_sats))
            self.vel_log_file.flush()

        self.vel_table = [
            ('Vel. N', '% 8.4f' % (vel_ned.n * 1e-3)),
            ('Vel. E', '% 8.4f' % (vel_ned.e * 1e-3)),
            ('Vel. D', '% 8.4f' % (vel_ned.d * 1e-3)),
        ]
        self.table_spp = self.pos_table_spp + self.vel_table + self.dops_table

    def gps_time_callback(self, sbp_msg, **metadata):
        self.week = MsgGPSTime(sbp_msg).wn
        self.nsec = MsgGPSTime(sbp_msg).ns

    def __init__(self, link):
        super(SolutionView, self).__init__()

        self.log_file = None
        self.vel_log_file = None

        self.plot_data = ArrayPlotData(lat=[],
                                       lng=[],
                                       alt=[],
                                       t=[],
                                       cur_lat=[],
                                       cur_lng=[],
                                       cur_lat_ps=[],
                                       cur_lng_ps=[],
                                       lat_ps=[],
                                       lng_ps=[],
                                       alt_ps=[],
                                       t_ps=[])
        self.plot = Plot(self.plot_data)

        # 1000 point buffer
        self.plot.plot(('lng', 'lat'),
                       type='line',
                       name='',
                       color=(0, 0, 0.9, 0.1))
        self.plot.plot(('lng', 'lat'),
                       type='scatter',
                       name='',
                       color='blue',
                       marker='dot',
                       line_width=0.0,
                       marker_size=1.0)
        self.plot.plot(('lng_ps', 'lat_ps'),
                       type='line',
                       name='',
                       color=(1, 0.4, 0, 0.1))
        self.plot.plot(('lng_ps', 'lat_ps'),
                       type='scatter',
                       name='',
                       color='orange',
                       marker='diamond',
                       line_width=0.0,
                       marker_size=1.0)
        # current values
        spp = self.plot.plot(('cur_lng', 'cur_lat'),
                             type='scatter',
                             name='SPP',
                             color='blue',
                             marker='plus',
                             line_width=1.5,
                             marker_size=5.0)
        rtk = self.plot.plot(('cur_lng_ps', 'cur_lat_ps'),
                             type='scatter',
                             name='RTK',
                             color='orange',
                             marker='plus',
                             line_width=1.5,
                             marker_size=5.0)
        plot_labels = ['SPP', 'RTK']
        plots_legend = dict(zip(plot_labels, [spp, rtk]))
        self.plot.legend.plots = plots_legend
        self.plot.legend.visible = True

        self.plot.index_axis.tick_label_position = 'inside'
        self.plot.index_axis.tick_label_color = 'gray'
        self.plot.index_axis.tick_color = 'gray'
        self.plot.index_axis.title = 'Longitude (degrees)'
        self.plot.index_axis.title_spacing = 5
        self.plot.value_axis.tick_label_position = 'inside'
        self.plot.value_axis.tick_label_color = 'gray'
        self.plot.value_axis.tick_color = 'gray'
        self.plot.value_axis.title = 'Latitude (degrees)'
        self.plot.value_axis.title_spacing = 5
        self.plot.padding = (25, 25, 25, 25)

        self.plot.tools.append(PanTool(self.plot))
        zt = ZoomTool(self.plot,
                      zoom_factor=1.1,
                      tool_mode="box",
                      always_on=False)
        self.plot.overlays.append(zt)

        self.link = link
        self.link.add_callback(self._pos_llh_callback, SBP_MSG_POS_LLH)
        self.link.add_callback(self.vel_ned_callback, SBP_MSG_VEL_NED)
        self.link.add_callback(self.dops_callback, SBP_MSG_DOPS)
        self.link.add_callback(self.gps_time_callback, SBP_MSG_GPS_TIME)

        self.week = None
        self.nsec = 0

        self.python_console_cmds = {
            'solution': self,
        }
Example #5
0
class SolutionView(HasTraits):
  python_console_cmds = Dict()
  # we need to doubleup on Lists to store the psuedo absolutes separately
  # without rewriting everything

  """
  logging_v : toggle logging for velocity files
  directory_name_v : location and name of velocity files
  logging_p : toggle logging for position files
  directory_name_p : location and name of velocity files
  """
  plot_history_max = Int(1000)
  logging_v = Bool(False)
  directory_name_v = File

  logging_p = Bool(False)
  directory_name_p = File

  lats_psuedo_abs = List()
  lngs_psuedo_abs = List()
  alts_psuedo_abs = List()

  table = List()
  dops_table = List()
  pos_table = List()
  vel_table = List()

  rtk_pos_note = Str("It is necessary to enter the \"Surveyed Position\" settings for the base station in order to view the RTK Positions in this tab.")

  plot = Instance(Plot)
  plot_data = Instance(ArrayPlotData)
  # Store plots we care about for legend

  running = Bool(True)
  zoomall = Bool(False)
  position_centered = Bool(False)

  clear_button = SVGButton(
    label='', tooltip='Clear',
    filename=os.path.join(determine_path(), 'images', 'iconic', 'x.svg'),
    width=16, height=16
  )
  zoomall_button = SVGButton(
    label='', tooltip='Zoom All', toggle=True,
    filename=os.path.join(determine_path(), 'images', 'iconic', 'fullscreen.svg'),
    width=16, height=16
  )
  center_button = SVGButton(
    label='', tooltip='Center on Solution', toggle=True,
    filename=os.path.join(determine_path(), 'images', 'iconic', 'target.svg'),
    width=16, height=16
  )
  paused_button = SVGButton(
    label='', tooltip='Pause', toggle_tooltip='Run', toggle=True,
    filename=os.path.join(determine_path(), 'images', 'iconic', 'pause.svg'),
    toggle_filename=os.path.join(determine_path(), 'images', 'iconic', 'play.svg'),
    width=16, height=16
  )

  traits_view = View(
    HSplit(
        VGroup(
          Item('table', style='readonly',
                editor=TabularEditor(adapter=SimpleAdapter()),
                show_label=False, width=0.3),
          Item('rtk_pos_note', show_label=False, resizable=True,
            editor=MultilineTextEditor(TextEditor(multi_line=True)), 
            style='readonly', width=0.3, height=-40),
          ),
      VGroup(
        HGroup(
          Item('paused_button', show_label=False),
          Item('clear_button', show_label=False),
          Item('zoomall_button', show_label=False),
          Item('center_button', show_label=False),
        ),
        Item('plot',
          show_label=False,
          editor=ComponentEditor(bgcolor=(0.8,0.8,0.8))),
      )
    )
  )

  def _zoomall_button_fired(self):
    self.zoomall = not self.zoomall

  def _center_button_fired(self):
    self.position_centered = not self.position_centered

  def _paused_button_fired(self):
    self.running = not self.running

  def _reset_remove_current(self):
    self.plot_data.set_data('cur_lat_spp', [])
    self.plot_data.set_data('cur_lng_spp', [])
    self.plot_data.set_data('cur_alt_spp', [])
    self.plot_data.set_data('cur_lat_dgnss', [])
    self.plot_data.set_data('cur_lng_dgnss', [])
    self.plot_data.set_data('cur_alt_dgnss', [])
    self.plot_data.set_data('cur_lat_float', [])
    self.plot_data.set_data('cur_lng_float', [])
    self.plot_data.set_data('cur_alt_float', [])
    self.plot_data.set_data('cur_lat_fixed', [])
    self.plot_data.set_data('cur_lng_fixed', [])
    self.plot_data.set_data('cur_alt_fixed', [])

  def _clear_button_fired(self):
    self.tows = np.empty(self.plot_history_max)
    self.lats = np.empty(self.plot_history_max)
    self.lngs = np.empty(self.plot_history_max)
    self.alts = np.empty(self.plot_history_max)
    self.modes = np.empty(self.plot_history_max)
    self.plot_data.set_data('lat_spp', [])
    self.plot_data.set_data('lng_spp', [])
    self.plot_data.set_data('alt_spp', [])
    self.plot_data.set_data('lat_dgnss', [])
    self.plot_data.set_data('lng_dgnss', [])
    self.plot_data.set_data('alt_dgnss', [])
    self.plot_data.set_data('lat_float', [])
    self.plot_data.set_data('lng_float', [])
    self.plot_data.set_data('alt_float', [])
    self.plot_data.set_data('lat_fixed', [])
    self.plot_data.set_data('lng_fixed', [])
    self.plot_data.set_data('alt_fixed', [])
    self._reset_remove_current()


  def _pos_llh_callback(self, sbp_msg, **metadata):
    # Updating an ArrayPlotData isn't thread safe (see chaco issue #9), so
    # actually perform the update in the UI thread.
    if self.running:
      GUI.invoke_later(self.pos_llh_callback, sbp_msg)

  def update_table(self):
    self._table_list = self.table_spp.items()

  def auto_survey(self):
    if self.counter < 1000:
      self.counter = self.counter + 1
    self.latitude_list.append(self.last_soln.lat)
    self.longitude_list.append(self.last_soln.lon)
    self.altitude_list.append(self.last_soln.height)
    self.latitude_list = self.latitude_list[-1000:]
    self.longitude_list = self.longitude_list[-1000:]
    self.altitude_list = self.altitude_list[-1000:]
    self.latitude = (sum(self.latitude_list))/self.counter
    self.altitude = (sum(self.altitude_list))/self.counter
    self.longitude = (sum(self.longitude_list))/self.counter

  def pos_llh_callback(self, sbp_msg, **metadata):
    if sbp_msg.msg_type == SBP_MSG_POS_LLH_DEP_A:
      soln = MsgPosLLHDepA(sbp_msg)
    else:
      soln = MsgPosLLH(sbp_msg)
    self.last_soln = soln

    self.last_pos_mode = get_mode(soln)
    pos_table = []
    soln.h_accuracy *= 1e-3
    soln.v_accuracy *= 1e-3

    tow = soln.tow * 1e-3
    if self.nsec is not None:
      tow += self.nsec * 1e-9

    if self.week is not None:
      t = datetime.datetime(1980, 1, 6) + \
          datetime.timedelta(weeks=self.week) + \
          datetime.timedelta(seconds=tow)
      tstr = t.strftime('%Y-%m-%d %H:%M')
      secs = t.strftime('%S.%f')
     
      if(self.directory_name_p == ''):
        filepath_p = time.strftime("position_log_%Y%m%d-%H%M%S.csv")
      else:
        filepath_p = os.path.join(self.directory_name_p, time.strftime("position_log_%Y%m%d-%H%M%S.csv"))

      if self.logging_p ==  False:
        self.log_file = None

      if self.logging_p:
        if self.log_file is None:
          self.log_file = open(filepath_p, 'w')
          self.log_file.write("time,latitude(degrees),longitude(degrees),altitude(meters),"
                              "h_accuracy(meters),v_accuracy(meters),n_sats,flags\n")

        self.log_file.write('%s,%.10f,%.10f,%.4f,%.4f,%.4f,%d,%d\n' % (
          str(t),
          soln.lat, soln.lon, soln.height,
          soln.h_accuracy, soln.v_accuracy,
          soln.n_sats, soln.flags)
        )
        self.log_file.flush()

    
    if self.last_pos_mode == 0:
      pos_table.append(('GPS Time', EMPTY_STR))
      pos_table.append(('GPS Week', EMPTY_STR))
      pos_table.append(('GPS TOW', EMPTY_STR))
      pos_table.append(('Num. Signals', EMPTY_STR))
      pos_table.append(('Lat', EMPTY_STR))
      pos_table.append(('Lng', EMPTY_STR))
      pos_table.append(('Height', EMPTY_STR))
      pos_table.append(('h_accuracy', EMPTY_STR))
      pos_table.append(('v_accuracy', EMPTY_STR))
    else:
      self.last_stime_update = time.time()
      if self.week is not None:
        pos_table.append(('GPS Time', "{0}:{1:06.3f}".format(tstr, float(secs))))
        pos_table.append(('GPS Week', str(self.week)))
      pos_table.append(('GPS TOW', "{:.3f}".format(tow)))
      pos_table.append(('Num. Sats', soln.n_sats))
      pos_table.append(('Lat', soln.lat))
      pos_table.append(('Lng', soln.lon))
      pos_table.append(('Height', soln.height))
      pos_table.append(('h_accuracy', soln.h_accuracy))
      pos_table.append(('v_accuracy', soln.v_accuracy))

    pos_table.append(('Pos Flags', '0x%03x' % soln.flags))
    pos_table.append(('Pos Fix Mode', mode_dict[self.last_pos_mode]))

    self.auto_survey()

    # setup_plot variables
    self.lats[1:] = self.lats[:-1]
    self.lngs[1:] = self.lngs[:-1]
    self.alts[1:] = self.alts[:-1]
    self.tows[1:] = self.tows[:-1]
    self.modes[1:] = self.modes[:-1]

    self.lats[0] = soln.lat
    self.lngs[0] = soln.lon
    self.alts[0] = soln.height
    self.tows[0] = soln.tow
    self.modes[0] = self.last_pos_mode

    self.lats = self.lats[-self.plot_history_max:]
    self.lngs = self.lngs[-self.plot_history_max:]
    self.alts = self.alts[-self.plot_history_max:]
    self.tows = self.tows[-self.plot_history_max:]
    self.modes = self.modes[-self.plot_history_max:]

    # SPP
    spp_indexer, dgnss_indexer, float_indexer, fixed_indexer = None, None, None, None
    if np.any(self.modes):
      spp_indexer = (self.modes == SPP_MODE)
      dgnss_indexer = (self.modes == DGNSS_MODE)
      float_indexer = (self.modes == FLOAT_MODE)
      fixed_indexer = (self.modes == FIXED_MODE)
    
    # make sure that there is at least one true in indexer before setting
      if any(spp_indexer):
        self.plot_data.set_data('lat_spp', self.lats[spp_indexer])
        self.plot_data.set_data('lng_spp', self.lngs[spp_indexer])
        self.plot_data.set_data('alt_spp', self.alts[spp_indexer])
      if any(dgnss_indexer):
        self.plot_data.set_data('lat_dgnss', self.lats[dgnss_indexer])
        self.plot_data.set_data('lng_dgnss', self.lngs[dgnss_indexer])
        self.plot_data.set_data('alt_dgnss', self.alts[dgnss_indexer])
      if any(float_indexer):
        self.plot_data.set_data('lat_float', self.lats[float_indexer])
        self.plot_data.set_data('lng_float', self.lngs[float_indexer])
        self.plot_data.set_data('alt_float', self.alts[float_indexer])
      if any(fixed_indexer):
        self.plot_data.set_data('lat_fixed', self.lats[fixed_indexer])
        self.plot_data.set_data('lng_fixed', self.lngs[fixed_indexer])
        self.plot_data.set_data('alt_fixed', self.alts[fixed_indexer])

    self._reset_remove_current()
    if self.last_pos_mode == SPP_MODE:
      self.plot_data.set_data('cur_lat_spp', [soln.lat])
      self.plot_data.set_data('cur_lng_spp', [soln.lon])
    if self.last_pos_mode == DGNSS_MODE:
      self.plot_data.set_data('cur_lat_dgnss', [soln.lat])
      self.plot_data.set_data('cur_lng_dgnss', [soln.lon])
    if self.last_pos_mode == FLOAT_MODE:
      self.plot_data.set_data('cur_lat_float', [soln.lat])
      self.plot_data.set_data('cur_lng_float', [soln.lon])
    if self.last_pos_mode == FIXED_MODE:
      self.plot_data.set_data('cur_lat_fixed', [soln.lat])
      self.plot_data.set_data('cur_lng_fixed', [soln.lon])

    # set-up table variables
    self.pos_table = pos_table
    self.table = self.pos_table + self.vel_table + self.dops_table

    # TODO: figure out how to center the graph now that we have two separate messages
    # when we selectively send only SPP, the centering function won't work anymore

    if not self.zoomall and self.position_centered:
      d = (self.plot.index_range.high - self.plot.index_range.low) / 2.
      self.plot.index_range.set_bounds(soln.lon - d, soln.lon + d)
      d = (self.plot.value_range.high - self.plot.value_range.low) / 2.
      self.plot.value_range.set_bounds(soln.lat - d, soln.lat + d)
    if self.zoomall:
      plot_square_axes(self.plot, ('lng_spp', 'lng_dgnss', 'lng_float','lng_fixed'), 
                        ('lat_spp', 'lat_dgnss', 'lat_float','lat_fixed'))

  def dops_callback(self, sbp_msg, **metadata):
    flags = 0
    if sbp_msg.msg_type == SBP_MSG_DOPS_DEP_A:
      dops = MsgDopsDepA(sbp_msg)
      flags = 1
    else:
      dops = MsgDops(sbp_msg)
      flags = dops.flags
    if flags != 0:
      self.dops_table = [
        ('PDOP', '%.1f' % (dops.pdop * 0.01)),
        ('GDOP', '%.1f' % (dops.gdop * 0.01)),
        ('TDOP', '%.1f' % (dops.tdop * 0.01)),
        ('HDOP', '%.1f' % (dops.hdop * 0.01)),
        ('VDOP', '%.1f' % (dops.vdop * 0.01))
      ]
    else:
      self.dops_table = [
        ('PDOP', EMPTY_STR),
        ('GDOP', EMPTY_STR),
        ('TDOP', EMPTY_STR),
        ('HDOP', EMPTY_STR),
        ('VDOP', EMPTY_STR)
      ]
    
    self.dops_table.append(('DOPS Flags', '0x%03x' % flags))
    self.table = self.pos_table + self.vel_table + self.dops_table

  def vel_ned_callback(self, sbp_msg, **metadata):
    flags = 0
    if sbp_msg.msg_type == SBP_MSG_VEL_NED_DEP_A:
      vel_ned = MsgVelNEDDepA(sbp_msg)
      flags = 1
    else:
      vel_ned = MsgVelNED(sbp_msg)
      flags = vel_ned.flags
    tow = vel_ned.tow * 1e-3
    if self.nsec is not None:
      tow += self.nsec * 1e-9

    if self.week is not None:
      t = datetime.datetime(1980, 1, 6) + \
          datetime.timedelta(weeks=self.week) + \
          datetime.timedelta(seconds=tow)
     
      if self.directory_name_v == '':
          filepath_v = time.strftime("velocity_log_%Y%m%d-%H%M%S.csv")
      else:
          filepath_v = os.path.join(self.directory_name_v,time.strftime("velocity_log_%Y%m%d-%H%M%S.csv"))

      if self.logging_v ==  False:
        self.vel_log_file = None

      if self.logging_v:

        if self.vel_log_file is None:
          self.vel_log_file = open(filepath_v, 'w')
          self.vel_log_file.write('time,north(m/s),east(m/s),down(m/s),speed(m/s),flags,num_signals\n')

        self.vel_log_file.write('%s,%.6f,%.6f,%.6f,%.6f,%d,%d\n' % (
          str(t),
          vel_ned.n * 1e-3, vel_ned.e * 1e-3, vel_ned.d * 1e-3,
          math.sqrt(vel_ned.n*vel_ned.n + vel_ned.e*vel_ned.e) * 1e-3,
          flags,
          vel_ned.n_sats)
        )
        self.vel_log_file.flush()
    if flags != 0: 
      self.vel_table = [
        ('Vel. N', '% 8.4f' % (vel_ned.n * 1e-3)),
        ('Vel. E', '% 8.4f' % (vel_ned.e * 1e-3)),
        ('Vel. D', '% 8.4f' % (vel_ned.d * 1e-3)),
      ]
    else:
      self.vel_table = [
        ('Vel. N', EMPTY_STR),
        ('Vel. E', EMPTY_STR),
        ('Vel. D', EMPTY_STR),
      ]
    self.vel_table.append(('Vel Flags', '0x%03x' % flags))
    self.table = self.pos_table + self.vel_table + self.dops_table

  def gps_time_callback(self, sbp_msg, **metadata):
    if sbp_msg.msg_type == SBP_MSG_GPS_TIME_DEP_A:
      time_msg = MsgGPSTimeDepA(sbp_msg)
      flags = 1
    elif sbp_msg.msg_type == SBP_MSG_GPS_TIME:
      time_msg = MsgGPSTime(sbp_msg)
      flags = time_msg.flags
      if flags != 0:
        self.week = time_msg.wn
        self.nsec = time_msg.ns

  def __init__(self, link, dirname=''):
    super(SolutionView, self).__init__()

    self.lats = np.zeros(self.plot_history_max)
    self.lngs = np.zeros(self.plot_history_max)
    self.alts = np.zeros(self.plot_history_max)
    self.tows = np.zeros(self.plot_history_max)
    self.modes = np.zeros(self.plot_history_max)
    self.log_file = None
    self.directory_name_v = dirname
    self.directory_name_p = dirname
    self.vel_log_file = None
    self.last_stime_update = 0
    self.last_soln = None

    self.counter = 0
    self.latitude_list = []
    self.longitude_list = []
    self.altitude_list = []
    self.altitude = 0
    self.longitude = 0
    self.latitude = 0
    self.last_pos_mode = 0

    self.plot_data = ArrayPlotData(lat_spp=[], lng_spp=[], alt_spp=[],
      cur_lat_spp=[], cur_lng_spp=[], lat_dgnss=[], lng_dgnss=[], alt_dgnss=[],
      cur_lat_dgnss=[], cur_lng_dgnss=[], lat_float=[], lng_float=[], alt_float=[],
      cur_lat_float=[], cur_lng_float=[], lat_fixed=[], lng_fixed=[], alt_fixed=[],
      cur_lat_fixed=[], cur_lng_fixed=[])
    self.plot = Plot(self.plot_data)

    # 1000 point buffer
    self.plot.plot(('lng_spp', 'lat_spp'), type='line', line_width=0.1, name='', color=color_dict[SPP_MODE])
    self.plot.plot(('lng_spp', 'lat_spp'), type='scatter',  name='', color=color_dict[SPP_MODE],
      marker='dot', line_width=0.0, marker_size=1.0)
    self.plot.plot(('lng_dgnss', 'lat_dgnss'), type='line',  line_width=0.1, name='', color=color_dict[DGNSS_MODE])
    self.plot.plot(('lng_dgnss', 'lat_dgnss'), type='scatter', name='', color=color_dict[DGNSS_MODE],
      marker='dot', line_width=0.0, marker_size=1.0)
    self.plot.plot(('lng_float', 'lat_float'), type='line',  line_width=0.1, name='', color=color_dict[FLOAT_MODE])
    self.plot.plot(('lng_float', 'lat_float'), type='scatter', name='', color=color_dict[FLOAT_MODE],
      marker='dot', line_width=0.0, marker_size=1.0)
    self.plot.plot(('lng_fixed', 'lat_fixed'), type='line',  line_width=0.1, name='', color=color_dict[FIXED_MODE])
    self.plot.plot(('lng_fixed', 'lat_fixed'), type='scatter', name='', color=color_dict[FIXED_MODE],
      marker='dot', line_width=0.0, marker_size=1.0)
    # current values
    spp = self.plot.plot(('cur_lng_spp', 'cur_lat_spp'), type='scatter', name=mode_dict[SPP_MODE],
      color=color_dict[SPP_MODE], marker='plus', line_width=1.5, marker_size=5.0)
    dgnss = self.plot.plot(('cur_lng_dgnss', 'cur_lat_dgnss'), type='scatter', name=mode_dict[DGNSS_MODE],
      color=color_dict[DGNSS_MODE], marker='plus', line_width=1.5, marker_size=5.0)
    rtkfloat = self.plot.plot(('cur_lng_float', 'cur_lat_float'), type='scatter', name=mode_dict[FLOAT_MODE],
      color=color_dict[FLOAT_MODE], marker='plus', line_width=1.5, marker_size=5.0)
    rtkfix = self.plot.plot(('cur_lng_fixed', 'cur_lat_fixed'), type='scatter', name=mode_dict[FIXED_MODE],
      color=color_dict[FIXED_MODE], marker='plus', line_width=1.5, marker_size=5.0)
    plot_labels = ['SPP', 'DGPS', "RTK float", "RTK fixed"]
    plots_legend = dict(zip(plot_labels, [spp, dgnss, rtkfloat, rtkfix]))
    self.plot.legend.plots = plots_legend
    self.plot.legend.labels = plot_labels # sets order
    self.plot.legend.visible = True

    self.plot.index_axis.tick_label_position = 'inside'
    self.plot.index_axis.tick_label_color = 'gray'
    self.plot.index_axis.tick_color = 'gray'
    self.plot.index_axis.title='Longitude (degrees)'
    self.plot.index_axis.title_spacing = 5
    self.plot.value_axis.tick_label_position = 'inside'
    self.plot.value_axis.tick_label_color = 'gray'
    self.plot.value_axis.tick_color = 'gray'
    self.plot.value_axis.title='Latitude (degrees)'
    self.plot.value_axis.title_spacing = 5
    self.plot.padding = (25, 25, 25, 25)

    self.plot.tools.append(PanTool(self.plot))
    zt = ZoomTool(self.plot, zoom_factor=1.1, tool_mode="box", always_on=False)
    self.plot.overlays.append(zt)

    self.link = link
    self.link.add_callback(self._pos_llh_callback, [SBP_MSG_POS_LLH_DEP_A, SBP_MSG_POS_LLH])
    self.link.add_callback(self.vel_ned_callback, [SBP_MSG_VEL_NED_DEP_A, SBP_MSG_VEL_NED])
    self.link.add_callback(self.dops_callback, [SBP_MSG_DOPS_DEP_A, SBP_MSG_DOPS])
    self.link.add_callback(self.gps_time_callback, [SBP_MSG_GPS_TIME_DEP_A, SBP_MSG_GPS_TIME])

    self.week = None
    self.nsec = 0

    self.python_console_cmds = {
      'solution': self,
    }
Example #6
0
class SbpRelayView(HasTraits):
  """
  SBP Relay view- Class allows user to specify port, IP address, and message set
  to relay over UDP and to configure a skylark connection
  """
  running = Bool(False)
  configured = Bool(False)
  broadcasting = Bool(False)
  msg_enum = Enum('Observations', 'All')
  ip_ad = String(DEFAULT_UDP_ADDRESS)
  port = Int(DEFAULT_UDP_PORT)
  information = String('UDP Streaming\n\nBroadcast SBP information received by'
    ' the console to other machines or processes over UDP. With the \'Observations\''
    ' radio button selected, the console will broadcast the necessary information'
    ' for a rover Piksi to acheive an RTK solution.'
    '\n\nThis can be used to stream observations to a remote Piksi through'
    ' aircraft telemetry via ground control software such as MAVProxy or'
    ' Mission Planner.')
  http_information = String('Skylark - Experimental Piksi Networking\n\n'
                            "Skylark is Swift Navigation's Internet service for connecting Piksi receivers without the use of a radio. To receive GPS observations from the closest nearby Piksi base station (within 5km), click Connect to Skylark.\n\n")
  start = Button(label='Start', toggle=True, width=32)
  stop = Button(label='Stop', toggle=True, width=32)
  connected_rover = Bool(False)
  connect_rover = Button(label='Connect to Skylark', toggle=True, width=32)
  disconnect_rover = Button(label='Disconnect from Skylark', toggle=True, width=32)
  skylark_url = String()
  base_pragma = String()
  rover_pragma = String()
  base_device_uid = String()
  rover_device_uid = String()
  toggle=True
  view = View(
           VGroup(
             spring,
             HGroup(
               VGroup(
                 Item('running', show_label=True, style='readonly', visible_when='running'),
                 Item('msg_enum', label="Messages to broadcast",
                      style='custom', enabled_when='not running'),
                 Item('ip_ad', label='IP Address', enabled_when='not running'),
                 Item('port', label="Port", enabled_when='not running'),
                 HGroup(
                   spring,
                   UItem('start', enabled_when='not running', show_label=False),
                   UItem('stop', enabled_when='running', show_label=False),
                   spring)),
               VGroup(
                 Item('information', label="Notes", height=10,
                      editor=MultilineTextEditor(TextEditor(multi_line=True)), style='readonly',
                      show_label=False, resizable=True, padding=15),
                 spring,
               ),
             ),
             spring,
             HGroup(
               VGroup(
                 HGroup(
                   spring,
                   UItem('connect_rover', enabled_when='not connected_rover', show_label=False),
                   UItem('disconnect_rover', enabled_when='connected_rover', show_label=False),
                   spring),
                   HGroup(Spring(springy=False, width=2),
                          Item('skylark_url', enabled_when='not connected_rover', show_label=True),
                          Spring(springy=False, width=2)
                          ),
                 HGroup(spring,
                        Item('base_pragma',  label='Base option '),
                        Item('base_device_uid',  label='Base device '),
                        spring),
                 HGroup(spring,
                        Item('rover_pragma', label='Rover option'),
                        Item('rover_device_uid',  label='Rover device'),
                        spring),),
               VGroup(
                 Item('http_information', label="Notes", height=10,
                      editor=MultilineTextEditor(TextEditor(multi_line=True)), style='readonly',
                      show_label=False, resizable=True, padding=15),
                 spring,
               ),
             ),
             spring
           )
  )

  def __init__(self, link, device_uid=None, base=DEFAULT_BASE, 
               whitelist=None, rover_pragma='', base_pragma='', rover_uuid='', base_uuid='',
               connect=False, verbose=False):
    """
    Traits tab with UI for UDP broadcast of SBP.

    Parameters
    ----------
    link : sbp.client.handler.Handler
      Link for SBP transfer to/from Piksi.
    device_uid : str
      Piksi Device UUID (defaults to None)
    base : str
      HTTP endpoint
    whitelist : [int] | None
      Piksi Device UUID (defaults to None)

    """
    self.link = link
    # Whitelist used for UDP broadcast view
    self.msgs = OBS_MSGS
    # register a callback when the msg_enum trait changes
    self.on_trait_change(self.update_msgs, 'msg_enum')
    # Whitelist used for Skylark broadcasting
    self.whitelist = whitelist
    self.device_uid = None
    self.python_console_cmds = {'update': self}
    self.rover_pragma = rover_pragma
    self.base_pragma = base_pragma
    self.rover_device_uid = rover_uuid
    self.base_device_uid = base_uuid
    self.verbose = verbose
    self.skylark_watchdog_thread = None
    self.skylark_url = base
    if connect:
      self.connect_when_uuid_received = True
    else:
      self.connect_when_uuid_received = False

  def update_msgs(self):
    """Updates the instance variable msgs which store the msgs that we
    will send over UDP.

    """
    if self.msg_enum == 'Observations':
      self.msgs = OBS_MSGS
    elif self.msg_enum == 'All':
      self.msgs = [None]
    else:
      raise NotImplementedError

  def set_route(self, uuid=None, serial_id=None, channel=CHANNEL_UUID):
    """Sets serial_id hash for HTTP headers.

    Parameters
    ----------
    uuid: str
      real uuid of device
    serial_id : int
      Piksi device ID
    channel : str
      UUID namespace for device UUID

    """
    if uuid:
      device_uid = uuid
    elif serial_id:
      device_uid = str(get_uuid(channel, serial_id % 1000))
    else:
      print("Improper call of set_route, either a serial number or UUID should be passed")
      device_uid = str(get_uuid(channel, 1234))
      print(("Setting UUID to default value of {0}".format(device_uid)))
    self.device_uid = device_uid

  def _prompt_setting_error(self, text):
    """Nonblocking prompt for a device setting error.

    Parameters
    ----------
    text : str
      Helpful error message for the user

    """
    prompt = CallbackPrompt(title="Setting Error", actions=[close_button])
    prompt.text = text
    prompt.run(block=False)

  def _disconnect_rover_fired(self):
    """Handle callback for HTTP rover disconnects.

    """
    try:
      if isinstance(self.skylark_watchdog_thread, threading.Thread) and \
         not self.skylark_watchdog_thread.stopped():
        self.skylark_watchdog_thread.stop()
      else:
        print((("Unable to disconnect: Skylark watchdog thread "
               "inititalized at {0} and connected since {1} has " 
               "already been stopped").format(self.skylark_watchdog_thread.get_init_time(),
                                              self.skylark_watchdog_thread.get_connect_time())))
      self.connected_rover = False
    except:
      self.connected_rover = False
      import traceback
      print((traceback.format_exc()))

  def _connect_rover_fired(self):
    """Handle callback for HTTP rover connections.  Launches an instance of skylark_watchdog_thread.
    """
    if not self.device_uid:
      msg = "\nDevice ID not found!\n\nConnection requires a valid Piksi device ID."
      self._prompt_setting_error(msg)
      return
    try:
      _base_device_uid = self.base_device_uid or self.device_uid
      _rover_device_uid = self.rover_device_uid or self.device_uid
      config = SkylarkConsoleConnectConfig(self.link, self.device_uid, 
               self.skylark_url, self.whitelist, self.rover_pragma, 
               self.base_pragma, _rover_device_uid, _base_device_uid)
      self.skylark_watchdog_thread = SkylarkWatchdogThread(link=self.link, skylark_config=config, 
                                        stopped_callback=self._disconnect_rover_fired,
                                        verbose=self.verbose)
      self.connected_rover = True
      self.skylark_watchdog_thread.start()
    except:
      if isinstance(self.skylark_watchdog_thread, threading.Thread) \
         and self.skylark_watchdog_thread.stopped():
        self.skylark_watchdog_thread.stop()
      self.connected_rover = False
      import traceback
      print((traceback.format_exc()))

  def _start_fired(self):
    """Handle start udp broadcast button. Registers callbacks on
    self.link for each of the self.msgs If self.msgs is None, it
    registers one generic callback for all messages.

    """
    self.running = True
    try:
      self.func = UdpLogger(self.ip_ad, self.port)
      self.link.add_callback(self.func, self.msgs)
    except:
      import traceback
      print((traceback.format_exc()))

  def _stop_fired(self):
    """Handle the stop udp broadcast button. It uses the self.funcs and
    self.msgs to remove the callbacks that were registered when the
    start button was pressed.

    """
    try:
      self.link.remove_callback(self.func, self.msgs)
      self.func.__exit__()
      self.func = None
      self.running = False
    except:
      import traceback
      print((traceback.format_exc()))