#!/usr/bin/env python # # by # Robin Wittler (speedometer mode) # and # Chen Wei (nautical mode) # # BSD terms apply: see the file COPYING in the distribution root for details. # This code runs compatibly under Python 2 and 3.x for x >= 2. # Preserve this property! from __future__ import absolute_import, print_function, division import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import Gdk import cairo from gi.repository import GObject import gps from math import pi from math import cos from math import sin from math import sqrt from math import radians from socket import error as SocketError class Speedometer(Gtk.DrawingArea): def __init__(self, speed_unit=None): GObject.GObject.__init__(self) self.MPH_UNIT_LABEL = 'mph' self.KPH_UNIT_LABEL = 'kmh' self.KNOTS_UNIT_LABEL = 'knots' self.conversions = { self.MPH_UNIT_LABEL: gps.MPS_TO_MPH, self.KPH_UNIT_LABEL: gps.MPS_TO_KPH, self.KNOTS_UNIT_LABEL: gps.MPS_TO_KNOTS } self.speed_unit = speed_unit or self.MPH_UNIT_LABEL if self.speed_unit not in self.conversions: raise TypeError( '%s is not a valid speed unit' % (repr(speed_unit)) ) class LandSpeedometer(Speedometer): def __init__(self, speed_unit=None): Speedometer.__init__(self, speed_unit) self.connect('size-allocate', self.on_size_allocate) self.width = self.height = 0 self.connect('draw', self.draw) self.long_ticks = (2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8) self.short_ticks = (0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9) self.long_inset = lambda x: 0.1 * x self.middle_inset = lambda x: self.long_inset(x) / 1.5 self.short_inset = lambda x: self.long_inset(x) / 3 self.res_div = 10.0 self.res_div_mul = 1 self.last_speed = 0 self.nums = { -8: 0, -7: 10, -6: 20, -5: 30, -4: 40, -3: 50, -2: 60, -1: 70, 0: 80, 1: 90, 2: 100 } def on_size_allocate(self, _unused, allocation): self.width = allocation.width self.height = allocation.height def draw(self, _unused, _event, _empty=None): self.cr = self.get_window().cairo_create() self.cr.rectangle(0, 0, self.width, self.height) self.cr.clip() x, y = self.get_x_y() width, height = self.get_window().get_geometry()[2:4] radius = self.get_radius(width, height) self.cr.set_line_width(radius / 100) self.draw_arc_and_ticks(width, height, radius, x, y) self.draw_needle(self.last_speed, radius, x, y) self.draw_speed_text(self.last_speed, radius, x, y) def draw_arc_and_ticks(self, width, height, radius, x, y): self.cr.set_source_rgb(1.0, 1.0, 1.0) self.cr.rectangle(0, 0, width, height) self.cr.fill() self.cr.set_source_rgb(0.0, 0.0, 0.0) # draw the speedometer arc self.cr.arc_negative(x, y, radius, radians(60), radians(120)) self.cr.stroke() long_inset = self.long_inset(radius) middle_inset = self.middle_inset(radius) short_inset = self.short_inset(radius) # draw the ticks for i in self.long_ticks: self.cr.move_to( x + (radius - long_inset) * cos(i * pi / 6.0), y + (radius - long_inset) * sin(i * pi / 6.0) ) self.cr.line_to( x + (radius + (self.cr.get_line_width() / 2)) * cos(i * pi / 6.0), y + (radius + (self.cr.get_line_width() / 2)) * sin(i * pi / 6.0) ) self.cr.select_font_face( 'Georgia', cairo.FONT_SLANT_NORMAL, ) self.cr.set_font_size(radius / 10) self.cr.save() _num = str(self.nums.get(i) * self.res_div_mul) ( _x_bearing, _y_bearing, t_width, t_height, _x_advance, _y_advance ) = self.cr.text_extents(_num) if i in (-8, -7, -6, -5, -4): self.cr.move_to( (x + (radius - long_inset - (t_width / 2)) * cos(i * pi / 6.0)), (y + (radius - long_inset - (t_height * 2)) * sin(i * pi / 6.0)) ) elif i in (-2, -1, 0, 2, 1): self.cr.move_to( (x + (radius - long_inset - (t_width * 1.5)) * cos(i * pi / 6.0)), (y + (radius - long_inset - (t_height * 2)) * sin(i * pi / 6.0)) ) elif i in (-3,): self.cr.move_to( (x - t_width / 2), (y - radius + self.long_inset(radius) * 2 + t_height) ) self.cr.show_text(_num) self.cr.restore() if i != self.long_ticks[0]: self.cr.move_to( x + (radius - middle_inset) * cos((i + 0.5) * pi / 6.0), y + (radius - middle_inset) * sin((i + 0.5) * pi / 6.0) ) self.cr.line_to( x + (radius + (self.cr.get_line_width() / 2)) * cos((i + 0.5) * pi / 6.0), y + (radius + (self.cr.get_line_width() / 2)) * sin((i + 0.5) * pi / 6.0) ) for z in self.short_ticks: if i < 0: self.cr.move_to( x + (radius - short_inset) * cos((i + z) * pi / 6.0), y + (radius - short_inset) * sin((i + z) * pi / 6.0) ) self.cr.line_to( x + (radius + (self.cr.get_line_width() / 2)) * cos((i + z) * pi / 6.0), y + (radius + (self.cr.get_line_width() / 2)) * sin((i + z) * pi / 6.0) ) else: self.cr.move_to( x + (radius - short_inset) * cos((i - z) * pi / 6.0), y + (radius - short_inset) * sin((i - z) * pi / 6.0) ) self.cr.line_to( x + (radius + (self.cr.get_line_width() / 2)) * cos((i - z) * pi / 6.0), y + (radius + (self.cr.get_line_width() / 2)) * sin((i - z) * pi / 6.0) ) self.cr.stroke() def draw_needle(self, speed, radius, x, y): self.cr.save() inset = self.long_inset(radius) speed = speed * self.conversions.get(self.speed_unit) speed = speed / (self.res_div * self.res_div_mul) actual = self.long_ticks[-1] + speed if actual > self.long_ticks[0]: self.res_div_mul += 1 speed = speed / (self.res_div * self.res_div_mul) actual = self.long_ticks[-1] + speed self.cr.move_to(x, y) self.cr.line_to( x + (radius - (2 * inset)) * cos(actual * pi / 6.0), y + (radius - (2 * inset)) * sin(actual * pi / 6.0) ) self.cr.stroke() self.cr.restore() def draw_speed_text(self, speed, radius, x, y): self.cr.save() speed = '%.2f %s' % ( speed * self.conversions.get(self.speed_unit), self.speed_unit ) self.cr.select_font_face( 'Georgia', cairo.FONT_SLANT_NORMAL, # cairo.FONT_WEIGHT_BOLD ) self.cr.set_font_size(radius / 10) _x_bearing, _y_bearing, t_width, _t_height = self.cr.text_extents(speed)[:4] self.cr.move_to((x - t_width / 2), (y + radius) - self.long_inset(radius)) self.cr.show_text(speed) self.cr.restore() def get_x_y(self): rect = self.get_allocation() x = (rect.x + rect.width / 2.0) y = (rect.y + rect.height / 2.0) - 20 return x, y def get_radius(self, width, height): return min(width / 2.0, height / 2.0) - 20 class NauticalSpeedometer(Speedometer): HEADING_SAT_GAP = 0.8 SAT_SIZE = 10 # radius of the satellite circle in skyview def __init__(self, speed_unit=None, maxspeed=100, rotate=0.0): Speedometer.__init__(self, speed_unit) self.connect('size-allocate', self.on_size_allocate) self.width = self.height = 0 self.connect('draw', self.draw) self.long_inset = lambda x: 0.05 * x self.mid_inset = lambda x: self.long_inset(x) / 1.5 self.short_inset = lambda x: self.long_inset(x) / 3 self.last_speed = 0 self.satellites = [] self.last_heading = 0 self.maxspeed = int(maxspeed) self.rotate = radians(rotate) def polar2xy(self, radius, angle, polex, poley): '''convert Polar coordinate to Cartesian coordinate system the y axis in pygtk points downward Args: radius: angle: azimuth from from Polar coordinate system, in radian polex and poley are the Cartesian coordinate of the pole return a tuple contains (x, y)''' angle = (angle + self.rotate) % (pi * 2) # Note reversed sense return (polex + cos(angle) * radius, poley - sin(angle) * radius) def on_size_allocate(self, _unused, allocation): self.width = allocation.width self.height = allocation.height def draw(self, _unused, _event, _empty=None): self.cr = self.get_window().cairo_create() self.cr.rectangle(0, 0, self.width, self.height) self.cr.clip() x, y = self.get_x_y() width, height = self.get_window().get_geometry()[2:4] radius = self.get_radius(width, height) self.cr.set_line_width(radius / 100) self.draw_arc_and_ticks(width, height, radius, x, y) self.draw_heading(20, self.last_heading, radius, x, y) for sat in self.satellites: self.draw_sat(sat, radius * NauticalSpeedometer.HEADING_SAT_GAP, x, y) self.draw_speed(radius, x, y) def draw_text(self, x, y, text, fontsize=10): '''draw text at given location Args: x, y is the center of textbox''' txt = str(text) self.cr.new_sub_path() self.cr.set_source_rgba(0, 0, 0) self.cr.select_font_face('Sans', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) self.cr.set_font_size(fontsize) (_x_bearing, _y_bearing, t_width, t_height) = self.cr.text_extents(txt)[:4] # set the center of textbox self.cr.move_to(x - t_width / 2, y + t_height / 2) self.cr.show_text(txt) def draw_arc_and_ticks(self, width, height, radius, x, y): '''Draw a serial of circle, with ticks in outmost circle''' self.cr.set_source_rgb(1.0, 1.0, 1.0) self.cr.rectangle(0, 0, width, height) self.cr.fill() self.cr.set_source_rgba(0, 0, 0) # draw the speedmeter arc rspeed = radius + 50 self.cr.arc(x, y, rspeed, 2 * pi / 3, 7 * pi / 3) self.cr.set_source_rgba(0, 0, 0, 1.0) self.cr.stroke() s_long = self.long_inset(rspeed) s_middle = self.mid_inset(radius) s_short = self.short_inset(radius) for i in range(11): # draw the large ticks alpha = (8 - i) * pi / 6 self.cr.move_to(*self.polar2xy(rspeed, alpha, x, y)) self.cr.set_line_width(radius / 100) self.cr.line_to(*self.polar2xy(rspeed - s_long, alpha, x, y)) self.cr.stroke() self.cr.set_line_width(radius / 200) xf, yf = self.polar2xy(rspeed + 10, alpha, x, y) stxt = (self.maxspeed // 10) * i self.draw_text(xf, yf, stxt, fontsize=radius / 15) for i in range(1, 11): # middle tick alpha = (8 - i) * pi / 6 beta = (17 - 2 * i) * pi / 12 self.cr.move_to(*self.polar2xy(rspeed, beta, x, y)) self.cr.line_to(*self.polar2xy(rspeed - s_middle, beta, x, y)) # short tick for n in range(10): gamma = alpha + n * pi / 60 self.cr.move_to(*self.polar2xy(rspeed, gamma, x, y)) self.cr.line_to(*self.polar2xy(rspeed - s_short, gamma, x, y)) # draw the heading arc self.cr.new_sub_path() self.cr.arc(x, y, radius, 0, 2 * pi) self.cr.stroke() self.cr.arc(x, y, radius - 20, 0, 2 * pi) self.cr.set_source_rgba(0, 0, 0, 0.20) self.cr.fill() self.cr.set_source_rgba(0, 0, 0) # heading label 90/180/270 for n in range(0, 4): label = str(n * 90) # self.cr.set_source_rgba(0, 1, 0) # radius * (1 + NauticalSpeedometer.HEADING_SAT_GAP), tbox_x, tbox_y = self.polar2xy( radius * 0.88, (1 - n) * pi / 2, x, y) self.draw_text(tbox_x, tbox_y, label, fontsize=radius / 20) # draw the satellite arcs skyradius = radius * NauticalSpeedometer.HEADING_SAT_GAP self.cr.set_line_width(radius / 200) self.cr.set_source_rgba(0, 0, 0) self.cr.arc(x, y, skyradius, 0, 2 * pi) self.cr.set_source_rgba(1, 1, 1) self.cr.fill() self.cr.set_source_rgba(0, 0, 0) self.cr.arc(x, y, skyradius * 2 / 3, 0, 2 * pi) self.cr.move_to(x + skyradius / 3, y) # Avoid line connecting circles self.cr.arc(x, y, skyradius / 3, 0, 2 * pi) # draw the cross hair self.cr.move_to(*self.polar2xy(skyradius, 1.5 * pi, x, y)) self.cr.line_to(*self.polar2xy(skyradius, 0.5 * pi, x, y)) self.cr.move_to(*self.polar2xy(skyradius, 0.0, x, y)) self.cr.line_to(*self.polar2xy(skyradius, pi, x, y)) self.cr.set_line_width(radius / 200) self.cr.stroke() long_inset = self.long_inset(radius) mid_inset = self.mid_inset(radius) short_inset = self.short_inset(radius) # draw the large ticks for i in range(12): agllong = i * pi / 6 self.cr.move_to(*self.polar2xy(radius - long_inset, agllong, x, y)) self.cr.line_to(*self.polar2xy(radius, agllong, x, y)) self.cr.set_line_width(radius / 100) self.cr.stroke() self.cr.set_line_width(radius / 200) # middle tick aglmid = (i + 0.5) * pi / 6 self.cr.move_to(*self.polar2xy(radius - mid_inset, aglmid, x, y)) self.cr.line_to(*self.polar2xy(radius, aglmid, x, y)) # short tick for n in range(1, 10): aglshrt = agllong + n * pi / 60 self.cr.move_to(*self.polar2xy(radius - short_inset, aglshrt, x, y)) self.cr.line_to(*self.polar2xy(radius, aglshrt, x, y)) self.cr.stroke() def draw_heading(self, trig_height, heading, radius, x, y): hypo = trig_height * 2 / sqrt(3) h = pi / 2 - radians(heading) # to xyz self.cr.set_line_width(2) self.cr.set_source_rgba(0, 0.3, 0.2, 0.8) # the triangle pointer x0 = x + radius * cos(h) y0 = y - radius * sin(h) x1 = x0 + hypo * cos(7 * pi / 6 + h) y1 = y0 - hypo * sin(7 * pi / 6 + h) x2 = x0 + hypo * cos(5 * pi / 6 + h) y2 = y0 - hypo * sin(5 * pi / 6 + h) self.cr.move_to(x0, y0) self.cr.line_to(x1, y1) self.cr.line_to(x2, y2) self.cr.line_to(x0, y0) self.cr.close_path() self.cr.fill() self.cr.stroke() # heading text (tbox_x, tbox_y) = self.polar2xy(radius * 1.1, h, x, y) self.draw_text(tbox_x, tbox_y, int(heading), fontsize=radius / 15) # the ship shape, based on test and try shiplen = radius * NauticalSpeedometer.HEADING_SAT_GAP / 4 xh, yh = self.polar2xy(shiplen * 2.3, h, x, y) xa, ya = self.polar2xy(shiplen * 2.2, h + pi - 0.3, x, y) xb, yb = self.polar2xy(shiplen * 2.2, h + pi + 0.3, x, y) xc, yc = self.polar2xy(shiplen * 1.4, h - pi / 5, x, y) xd, yd = self.polar2xy(shiplen * 1.4, h + pi / 5, x, y) self.cr.set_source_rgba(0, 0.3, 0.2, 0.5) self.cr.move_to(xa, ya) self.cr.line_to(xb, yb) self.cr.line_to(xc, yc) self.cr.line_to(xh, yh) self.cr.line_to(xd, yd) self.cr.close_path() self.cr.fill() # self.cr.stroke() def set_color(self, spec): '''Set foreground color for drawing.''' gdkcolor = Gdk.color_parse(spec) r = gdkcolor.red / 65535.0 g = gdkcolor.green / 65535.0 b = gdkcolor.blue / 65535.0 self.cr.set_source_rgb(r, g, b) def draw_sat(self, satsoup, radius, x, y): """Given a sat's elevation, azimuth, SNR, draw it on the skyview Arg: satsoup: a dictionary {'el': xx, 'az': xx, 'ss': xx} """ el, az = satsoup['el'], satsoup['az'] if el == 0 and az == 0: return # Skip satellites with unknown position h = pi / 2 - radians(az) # to xy self.cr.set_line_width(2) self.cr.set_source_rgb(0, 0, 0) x0, y0 = self.polar2xy(radius * (90 - el) // 90, h, x, y) self.cr.new_sub_path() if gps.is_sbas(satsoup['PRN']): self.cr.rectangle(x0 - NauticalSpeedometer.SAT_SIZE, y0 - NauticalSpeedometer.SAT_SIZE, NauticalSpeedometer.SAT_SIZE * 2, NauticalSpeedometer.SAT_SIZE * 2) else: self.cr.arc(x0, y0, NauticalSpeedometer.SAT_SIZE, 0, pi * 2.0) if satsoup['ss'] < 10: self.set_color('Gray') elif satsoup['ss'] < 30: self.set_color('Red') elif satsoup['ss'] < 35: self.set_color('Yellow') elif satsoup['ss'] < 40: self.set_color('Green3') else: self.set_color('Green1') if satsoup['used']: self.cr.fill() else: self.cr.stroke() self.draw_text(x0, y0, satsoup['PRN'], fontsize=15) def draw_speed(self, radius, x, y): self.cr.new_sub_path() self.cr.set_line_width(20) self.cr.set_source_rgba(0, 0, 0, 0.5) speed = self.last_speed * self.conversions.get(self.speed_unit) # cariol arc angle start at polar 0, going clockwise alpha = 4 * pi / 3 beta = 2 * pi - alpha theta = 5 * pi * speed / (self.maxspeed * 3) self.cr.arc(x, y, radius + 40, beta, beta + theta) self.cr.stroke() # self.cr.close_path() # self.cr.fill() label = '%.2f %s' % (speed, self.speed_unit) self.draw_text(x, y + radius + 40, label, fontsize=20) def get_x_y(self): rect = self.get_allocation() x = (rect.x + rect.width / 2.0) y = (rect.y + rect.height / 2.0) - 20 return x, y def get_radius(self, width, height): return min(width / 2.0, height / 2.0) - 70 class Main(object): def __init__(self, host='localhost', port=gps.GPSD_PORT, device=None, debug=0, speed_unit=None, maxspeed=0, nautical=False, rotate=0.0, target=[]): self.host = host self.port = port self.device = device self.debug = debug self.speed_unit = speed_unit self.maxspeed = maxspeed self.nautical = nautical self.rotate = rotate self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL) if not self.window.get_display(): raise Exception("Can't open display") self.window.set_title(' '.join(['xgpsspeed'] + target)) if self.nautical: self.window.set_size_request(500, 550) self.widget = NauticalSpeedometer( speed_unit=self.speed_unit, maxspeed=self.maxspeed, rotate=self.rotate) else: self.widget = LandSpeedometer(speed_unit=self.speed_unit) self.window.connect('delete_event', self.delete_event) self.window.connect('destroy', self.destroy) self.widget.show() vbox = Gtk.VBox(False, 0) self.window.add(vbox) self.window.present() self.uimanager = Gtk.UIManager() self.accelgroup = self.uimanager.get_accel_group() self.window.add_accel_group(self.accelgroup) self.actiongroup = Gtk.ActionGroup('gpsspeed-ng') self.actiongroup.add_actions( [ ('Quit', Gtk.STOCK_QUIT, '_Quit', None, 'Quit the Program', lambda unused: Gtk.main_quit()), ('File', None, '_File'), ('Units', None, '_Units')] ) self.actiongroup.add_radio_actions( [ ('Imperial', None, '_Imperial', 'i', 'Imperial Units', 0), ('Metric', None, '_Metric', 'm', 'Metrical Units', 1), ('Nautical', None, '_Nautical', 'n', 'Nautical Units', 2) ], 0, lambda a, unused: setattr(self.widget, 'speed_unit', ['mph', 'kmh', 'knots'][a.get_current_value()]) ) self.uimanager.insert_action_group(self.actiongroup, 0) self.uimanager.add_ui_from_string(''' ''') self.active_unit_map = { 'mph': '/MenuBar/Units/Imperial', 'kmh': '/MenuBar/Units/Metric', 'knots': '/MenuBar/Units/Nautical' } menubar = self.uimanager.get_widget('/MenuBar') self.uimanager.get_widget( self.active_unit_map.get(self.speed_unit) ).set_active(True) vbox.pack_start(menubar, False, False, 0) vbox.add(self.widget) self.window.show_all() def watch(self, daemon, device): self.daemon = daemon self.device = device GObject.io_add_watch(daemon.sock, GObject.IO_IN, self.handle_response) GObject.io_add_watch(daemon.sock, GObject.IO_ERR, self.handle_hangup) GObject.io_add_watch(daemon.sock, GObject.IO_HUP, self.handle_hangup) return True def handle_response(self, source, condition): if self.daemon.read() == -1: self.handle_hangup(source, condition) if self.daemon.data['class'] == 'TPV': self.update_speed(self.daemon.data) if self.nautical and self.daemon.data['class'] == 'SKY': self.update_skyview(self.daemon.data) return True def handle_hangup(self, _dummy, _unused): w = Gtk.MessageDialog( parent=self.window, type=Gtk.MessageType.ERROR, flags=Gtk.DialogFlags.DESTROY_WITH_PARENT, buttons=Gtk.ButtonsType.OK ) w.connect("destroy", lambda unused: Gtk.main_quit()) w.set_title('gpsd error') w.set_markup("gpsd has stopped sending data.") w.run() Gtk.main_quit() return True def update_speed(self, data): if hasattr(data, 'speed'): self.widget.last_speed = data.speed self.widget.queue_draw() if self.nautical and hasattr(data, 'track'): self.widget.last_heading = data.track self.widget.queue_draw() # Used for NauticalSpeedometer only def update_skyview(self, data): "Update the satellite list and skyview." if hasattr(data, 'satellites'): self.widget.satellites = data.satellites self.widget.queue_draw() def delete_event(self, _widget, _event, _data=None): # Someday, handle all cleanup operations here return False def destroy(self, _unused, _empty=None): Gtk.main_quit() def run(self): try: daemon = gps.gps( host=self.host, port=self.port, mode=gps.WATCH_ENABLE | gps.WATCH_JSON | gps.WATCH_SCALED, verbose=self.debug ) self.watch(daemon, self.device) Gtk.main() except SocketError: w = Gtk.MessageDialog( parent=self.window, type=Gtk.MessageType.ERROR, flags=Gtk.DialogFlags.DESTROY_WITH_PARENT, buttons=Gtk.ButtonsType.OK ) w.set_title('socket error') w.set_markup( "could not connect to gpsd socket. make sure gpsd is running." ) w.run() w.destroy() except KeyboardInterrupt: self.window.emit('delete_event', Gdk.Event(Gdk.NOTHING)) if __name__ == '__main__': import sys from os.path import basename from optparse import OptionParser prog = basename(sys.argv[0]) usage = ('%s [-V|--version] [-h|--help] [--debug] [--host] ' + '[--port] [--device] [--speedunits {[mph] [kmh] [knots]}] ' + '[host [:port [:device]]]') % (prog) epilog = 'BSD terms apply: see the file COPYING in the distribution root for details.' parser = OptionParser(usage=usage, epilog=epilog) parser.add_option( '--host', dest='host', default=None, help='The host to connect. [Default localhost]' ) parser.add_option( '--port', dest='port', default=None, help='The port to connect. [Default %s]' % gps.GPSD_PORT ) parser.add_option( '--device', dest='device', default=None, help='The device to connect. [Default None]' ) parser.add_option( '--speedunits', dest='speedunits', default='mph', help='The unit of speed. Possible units are: mph, kmh, knots. [Default mph]' ) parser.add_option( '--maxspeed', dest='maxspeed', default='50', help='Max speed of the speedmeter [Default 50]' ) parser.add_option( '--nautical', dest='nautical', default=True, action='store_true', help='Enable nautical-style speed and track display.' ) parser.add_option( '--landspeed', dest='nautical', default=True, action='store_false', help='Enable dashboard-style speedometer.' ) parser.add_option( '--debug', dest='debug', default=0, action='store', type='int', help='Set level of debug. Must be integer. [Default 0]' ) parser.add_option( '--rotate', dest='rotate', default=0, action='store', type='float', help='Rotation of skyview ("up" direction) in degrees. [Default 0]' ) (options, args) = parser.parse_args() if args: arg = args[0].split(':') len_arg = len(arg) if len_arg == 1: (options.host,) = arg elif len_arg == 2: (options.host, options.port) = arg elif len_arg == 3: (options.host, options.port, options.device) = arg else: parser.print_help() sys.exit(0) target = args[0:] elif options.host or options.port or options.device: target = [options.host or 'localhost'] if options.port or options.device: target += [options.port or ''] if options.device: target += [options.device] target = [':'.join(target)] else: target = [] Main( host=options.host or 'localhost', port=options.port or gps.GPSD_PORT, device=options.device, speed_unit=options.speedunits, maxspeed=options.maxspeed, nautical=options.nautical, debug=options.debug, rotate=options.rotate, target=target, ).run()