aprs.py
  1. # Copyright (C) 2008, Jack Zielke <jack@linuxcoffee.com>
  2. # Copyright (C) 2008, One Laptop Per Child
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  17.  
  18. import gtk
  19. import pango
  20. import time
  21. import socket
  22. import random
  23. import gobject
  24.  
  25. from gettext import gettext as _
  26.  
  27. from sugar import profile
  28. from sugar.activity import activity
  29. from sugar.bundle.activitybundle import ActivityBundle
  30. from sugar.graphics.toolbutton import ToolButton
  31.  
  32. HOST = 'rotate.aprs2.net'
  33. #HOST = '192.168.50.6'
  34. PORT = 14580
  35. RECV_BUFFER = 4096
  36.  
  37. bundle = ActivityBundle(activity.get_bundle_path())
  38. VERSION = bundle.get_activity_version()
  39. del bundle
  40.  
  41. class APRSActivity(activity.Activity):
  42.     def __init__(self, handle):
  43.         activity.Activity.__init__(self, handle)
  44.         self.set_title(_('APRS Activity'))
  45.  
  46.         self.connect('destroy', self.onDestroy)
  47.  
  48.         self.sock = None
  49.         self.location = "home"
  50.         self.site = None
  51.         self.sent_acks = {}
  52.         self.timers = []
  53.         self.validating = False
  54.  
  55.         titlefont = pango.FontDescription('Sans bold 8')
  56.         smallfont = pango.FontDescription('Sans 6')
  57.         verysmallfont = pango.FontDescription('Sans 3')
  58.         firstName = profile.get_nick_name().split(None, 1)[0].capitalize()
  59.  
  60.         toolbox = activity.ActivityToolbox(self)
  61.         self.set_toolbox(toolbox)
  62.  
  63.         activity_toolbar = toolbox.get_activity_toolbar()
  64.         activity_toolbar.share.props.visible = False
  65.         activity_toolbar.keep.props.visible = False
  66.  
  67.         toolbox.show()
  68.  
  69.         win = gtk.HBox(False, 10)
  70.         self.set_canvas(win)
  71.  
  72.         leftwin = gtk.VBox(False, 10)
  73.  
  74.         # Top 'about' box
  75.         aboutbox = gtk.VBox(False, 10)
  76.         aboutbox.set_border_width(10)
  77.  
  78.         topaboutbox = gtk.VBox(False, 0)
  79.  
  80.         titlebox = gtk.HBox(False, 0)
  81.         titlelabel = gtk.Label("APRS-XO:")
  82.         titlelabel.set_alignment(0, 0)
  83.         titlelabel.modify_font(titlefont)
  84.         titlebox.pack_start(titlelabel, False, False, 0)
  85.         titlelabel.show()
  86.         aboutlabel1 = gtk.Label("This amateur radio program will update your position")
  87.         aboutlabel1.set_alignment(0, 0.8)
  88.         titlebox.pack_start(aboutlabel1, False, False, 0)
  89.         aboutlabel1.show()
  90.         topaboutbox.pack_start(titlebox, False, False, 0)
  91.         titlebox.show()
  92.         aboutlabel2 = gtk.Label("& status on all of the global APRS web pages once every 10 minutes.")
  93.         aboutlabel2.set_alignment(0, 0)
  94.         topaboutbox.pack_start(aboutlabel2, False, False, 0)
  95.         aboutlabel2.show()
  96.         aboutbox.pack_start(topaboutbox, False, False, 0)
  97.         topaboutbox.show()
  98.  
  99.         sitebox = gtk.HBox(False, 10)
  100.         sitelabel = gtk.Label("Select an APRS site:")
  101.         sitelabel.set_alignment(0, 0.4)
  102.         sitebox.pack_start(sitelabel, False, False, 0)
  103.         sitelabel.show()
  104.  
  105.         findubutton = gtk.Button()
  106.         findubutton.set_label("FINDU.COM")
  107.         findubutton.connect("clicked", self.set_site, "http://www.findu.com/cgi-bin/symbol.cgi?icon=XA&limit=200")
  108.         sitebox.pack_start(findubutton, False, False, 0)
  109.         findubutton.show()
  110.  
  111.         aprsworldbutton = gtk.Button()
  112.         aprsworldbutton.set_label("APRSworld")
  113.         aprsworldbutton.connect("clicked", self.set_site, "http://aprsworld.net/")
  114.         sitebox.pack_start(aprsworldbutton, False, False, 0)
  115.         aprsworldbutton.show()
  116.  
  117.         otherbutton = gtk.Button()
  118.         otherbutton.set_label("About")
  119.         otherbutton.connect("clicked", self.set_site, "http://aprs.org/")
  120.         sitebox.pack_start(otherbutton, False, False, 0)
  121.         otherbutton.show()
  122.  
  123.         aboutbox.pack_start(sitebox, False, False, 0)
  124.         sitebox.show()
  125.  
  126.         infobox = gtk.VBox(False, 4)
  127.         # more info radio box?
  128.  
  129.         infolabel = gtk.Label("Without a ham license you can only communicate with other XO's on the Internet.\nWith a ham license you can communicate worldwide with wireless and radio.")
  130.         infolabel.set_alignment(0, 0)
  131.         infolabel.modify_font(smallfont)
  132.         infobox.pack_start(infolabel, False, False, 0)
  133.         infolabel.show()
  134.  
  135.         aboutbox.pack_start(infobox, False, False, 0)
  136.         infobox.show()
  137.  
  138.         leftwin.pack_start(aboutbox, False, False, 0)
  139.         aboutbox.show()
  140.  
  141.         separator = gtk.HSeparator()
  142.         leftwin.pack_start(separator, False, False, 0)
  143.         separator.show()
  144.  
  145.         # identifiers box
  146.         identbox = gtk.HBox(False, 10)
  147.  
  148.         leftidentbox = gtk.VBox(False, 10)
  149.         leftidentbox.set_border_width(10)
  150.         identlabel = gtk.Label("Identifiers")
  151.         identlabel.set_alignment(0, 0)
  152.         identlabel.modify_font(titlefont)
  153.         leftidentbox.pack_start(identlabel, False, False, 0)
  154.         identlabel.show()
  155.  
  156.         spacerlabel = gtk.Label(" ")
  157.         spacerlabel.modify_font(verysmallfont)
  158.         leftidentbox.pack_start(spacerlabel, False, False, 0)
  159.         spacerlabel.show()
  160.  
  161.         bottomleftidentbox = gtk.HBox(False, 0)
  162.  
  163.         calllabel1 = gtk.Label("Callsign: ")
  164.         calllabel1.set_alignment(0, 0.2)
  165.         bottomleftidentbox.pack_start(calllabel1, False, False, 0)
  166.         calllabel1.show()
  167.  
  168.         callbox = gtk.VBox(False, 4)
  169.         self.calltext = gtk.Entry()
  170.         self.calltext.set_max_length(9)
  171.         self.calltext.set_width_chars(6)
  172.         self.calltext.connect("changed", self.disable_beacon)
  173.         callbox.pack_start(self.calltext, False, False, 0)
  174.         self.calltext.show()
  175.  
  176.         calllabel2 = gtk.Label("If a ham radio operator")
  177.         calllabel2.set_alignment(0, 0)
  178.         calllabel2.modify_font(smallfont)
  179.         callbox.pack_start(calllabel2, False, False, 0)
  180.         calllabel2.show()
  181.  
  182.         bottomleftidentbox.pack_start(callbox, False, False, 0)
  183.         callbox.show()
  184.  
  185.         leftidentbox.pack_start(bottomleftidentbox, False, False, 0)
  186.         bottomleftidentbox.show()
  187.  
  188.         identbox.pack_start(leftidentbox, False, False, 0)
  189.         leftidentbox.show()
  190.  
  191.         rightidentbox = gtk.VBox(False, 10)
  192.         rightidentbox.set_border_width(10)
  193.  
  194.         toprightidentbox = gtk.HBox(False, 4)
  195.         namelabel = gtk.Label("First Name: ")
  196.         namelabel.set_alignment(0, 0.5)
  197.         toprightidentbox.pack_start(namelabel, False, False, 0)
  198.         namelabel.show()
  199.  
  200.         self.nametext = gtk.Entry()
  201.         self.nametext.set_max_length(36)
  202.         self.nametext.set_width_chars(15)
  203.         self.nametext.set_text(firstName)
  204.         self.nametext.connect("changed", self.disable_beacon)
  205.         toprightidentbox.pack_start(self.nametext, False, False, 0)
  206.         self.nametext.show()
  207.         rightidentbox.pack_start(toprightidentbox, False, False, 0)
  208.         toprightidentbox.show()
  209.  
  210.         bottomrightidentbox = gtk.HBox(False, 0)
  211.  
  212.         ziplabel1 = gtk.Label("Zip Code: ")
  213.         ziplabel1.set_alignment(0, 0.5)
  214.         bottomrightidentbox.pack_start(ziplabel1, False, False, 0)
  215.         ziplabel1.show()
  216.  
  217.         self.ziptext = gtk.Entry()
  218.         self.ziptext.set_max_length(5)
  219.         self.ziptext.set_width_chars(5)
  220.         bottomrightidentbox.pack_start(self.ziptext, False, False, 0)
  221.         self.ziptext.show()
  222.  
  223.         rightidentbox.pack_start(bottomrightidentbox, False, False, 0)
  224.         bottomrightidentbox.show()
  225.  
  226.         ziplabel2 = gtk.Label("otherwise your call will be XZZZZZ")
  227.         ziplabel2.set_alignment(0, 0)
  228.         ziplabel2.modify_font(smallfont)
  229.         rightidentbox.pack_start(ziplabel2, False, False, 0)
  230.         ziplabel2.show()
  231.  
  232.         identbox.pack_start(rightidentbox, False, False, 0)
  233.         rightidentbox.show()
  234.  
  235.         leftwin.pack_start(identbox, False, False, 0)
  236.         identbox.show()
  237.  
  238.         separator = gtk.HSeparator()
  239.         leftwin.pack_start(separator, False, False, 0)
  240.         separator.show()
  241.  
  242.         # station box
  243.         stationbox = gtk.VBox(False, 10)
  244.         stationbox.set_border_width(10)
  245.  
  246.         topstationbox = gtk.HBox(False, 10)
  247.  
  248.         stationlabel = gtk.Label("Station Text")
  249.         stationlabel.set_alignment(0, 0.5)
  250.         stationlabel.modify_font(titlefont)
  251.         topstationbox.pack_start(stationlabel, False, False, 0)
  252.         stationlabel.show()
  253.  
  254.         self.stationtext = gtk.Entry()
  255.         self.stationtext.set_max_length(36)
  256.         self.stationtext.set_width_chars(36)
  257.         self.stationtext.connect("changed", self.disable_beacon)
  258.         topstationbox.pack_start(self.stationtext, False, False, 0)
  259.         self.stationtext.show()
  260.  
  261.         stationbox.pack_start(topstationbox, False, False, 0)
  262.         topstationbox.show()
  263.  
  264.         stationhelp = gtk.Label("Enter up to 36 characters, most important first since some displays\ncan only see the first 20 or 28.")
  265.         stationhelp.set_alignment(0, 0)
  266.         stationbox.pack_start(stationhelp, False, False, 0)
  267.         stationhelp.show()
  268.  
  269.         leftwin.pack_start(stationbox, False, False, 0)
  270.         stationbox.show()
  271.  
  272.         separator = gtk.HSeparator()
  273.         leftwin.pack_start(separator, False, False, 0)
  274.         separator.show()
  275.  
  276.         # position box
  277.         positbox = gtk.VBox(False, 10)
  278.         positbox.set_border_width(10)
  279.  
  280.         toppositbox = gtk.HBox(False, 30)
  281.  
  282.         topleftpositbox = gtk.VBox(False, 4)
  283.  
  284.         positlabel1 = gtk.Label("Position Data")
  285.         positlabel1.set_alignment(0, 0)
  286.         positlabel1.modify_font(titlefont)
  287.         topleftpositbox.pack_start(positlabel1, False, False, 0)
  288.         positlabel1.show()
  289.  
  290.         latpositbox = gtk.HBox(False, 4)
  291.         latlabel1 = gtk.Label("Latitude:    ")
  292.         latlabel1.set_alignment(0, 0.5)
  293.         latpositbox.pack_start(latlabel1, False, False, 0)
  294.         latlabel1.show()
  295.  
  296.         self.latDDtext = gtk.Entry()
  297.         self.latDDtext.set_max_length(2)
  298.         self.latDDtext.set_width_chars(4)
  299.         self.latDDtext.set_text("DD")
  300.         self.latDDtext.select_region(1,2)
  301.         self.latDDtext.connect("changed", self.disable_beacon)
  302.         latpositbox.pack_start(self.latDDtext, False, False, 0)
  303.         self.latDDtext.show()
  304.  
  305.         self.latMMtext = gtk.Entry()
  306.         self.latMMtext.set_max_length(2)
  307.         self.latMMtext.set_width_chars(3)
  308.         self.latMMtext.set_text("MM")
  309.         self.latMMtext.select_region(1,2)
  310.         self.latMMtext.connect("changed", self.disable_beacon)
  311.         latpositbox.pack_start(self.latMMtext, False, False, 0)
  312.         self.latMMtext.show()
  313.  
  314.         latlabel2 = gtk.Label(".")
  315.         latlabel2.set_alignment(0, 1)
  316.         latpositbox.pack_start(latlabel2, False, False, 0)
  317.         latlabel2.show()
  318.  
  319.         self.latmmtext = gtk.Entry()
  320.         self.latmmtext.set_max_length(2)
  321.         self.latmmtext.set_width_chars(3)
  322.         self.latmmtext.set_text("mm")
  323.         self.latmmtext.select_region(1,2)
  324.         self.latmmtext.connect("changed", self.disable_beacon)
  325.         latpositbox.pack_start(self.latmmtext, False, False, 0)
  326.         self.latmmtext.show()
  327.  
  328.         self.latcombo = gtk.combo_box_new_text()
  329.         self.latcombo.append_text("N")
  330.         self.latcombo.append_text("S")
  331.         self.latcombo.set_active(0)
  332.         latpositbox.pack_start(self.latcombo, False, False, 0)
  333.         self.latcombo.show()
  334.  
  335.         topleftpositbox.pack_start(latpositbox, False, False, 0)
  336.         latpositbox.show()
  337.  
  338.         lonpositbox = gtk.HBox(False, 4)
  339.         lonlabel1 = gtk.Label("Longitude: ")
  340.         lonlabel1.set_alignment(0, 0.5)
  341.         lonpositbox.pack_start(lonlabel1, False, False, 0)
  342.         lonlabel1.show()
  343.  
  344.         self.lonDDDtext = gtk.Entry()
  345.         self.lonDDDtext.set_max_length(3)
  346.         self.lonDDDtext.set_width_chars(4)
  347.         self.lonDDDtext.set_text("DDD")
  348.         self.lonDDDtext.select_region(1,2)
  349.         self.lonDDDtext.connect("changed", self.disable_beacon)
  350.         lonpositbox.pack_start(self.lonDDDtext, False, False, 0)
  351.         self.lonDDDtext.show()
  352.  
  353.         self.lonMMtext = gtk.Entry()
  354.         self.lonMMtext.set_max_length(2)
  355.         self.lonMMtext.set_width_chars(3)
  356.         self.lonMMtext.set_text("MM")
  357.         self.lonMMtext.select_region(1,2)
  358.         self.lonMMtext.connect("changed", self.disable_beacon)
  359.         lonpositbox.pack_start(self.lonMMtext, False, False, 0)
  360.         self.lonMMtext.show()
  361.  
  362.         lonlabel2 = gtk.Label(".")
  363.         lonlabel2.set_alignment(0, 1)
  364.         lonpositbox.pack_start(lonlabel2, False, False, 0)
  365.         lonlabel2.show()
  366.  
  367.         self.lonmmtext = gtk.Entry()
  368.         self.lonmmtext.set_max_length(2)
  369.         self.lonmmtext.set_width_chars(3)
  370.         self.lonmmtext.set_text("mm")
  371.         self.lonmmtext.select_region(1,2)
  372.         self.lonmmtext.connect("changed", self.disable_beacon)
  373.         lonpositbox.pack_start(self.lonmmtext, False, False, 0)
  374.         self.lonmmtext.show()
  375.  
  376.         self.loncombo = gtk.combo_box_new_text()
  377.         self.loncombo.append_text("W")
  378.         self.loncombo.append_text("E")
  379.         self.loncombo.set_active(0)
  380.         lonpositbox.pack_start(self.loncombo, False, False, 0)
  381.         self.loncombo.show()
  382.  
  383.         topleftpositbox.pack_start(lonpositbox, False, False, 0)
  384.         lonpositbox.show()
  385.  
  386.         toppositbox.pack_start(topleftpositbox, False, False, 0)
  387.         topleftpositbox.show()
  388.  
  389.         toprightpositbox = gtk.VBox(False, 4)
  390.  
  391.         loclabel = gtk.Label("Location Type:")
  392.         loclabel.set_alignment(0, 0)
  393.         toprightpositbox.pack_start(loclabel, False, False, 0)
  394.         loclabel.show()
  395.  
  396.         locbox = gtk.HBox(False, 4)
  397.  
  398.         leftlocbox = gtk.VBox(False, 4)
  399.  
  400.         locbutton = gtk.RadioButton(None, "home")
  401.         locbutton.connect("toggled", self.set_location, "home")
  402.         locbutton.set_active(True)
  403.         leftlocbox.pack_start(locbutton, False, False, 0)
  404.         locbutton.show()
  405.  
  406.         locbutton = gtk.RadioButton(locbutton, "work")
  407.         locbutton.connect("toggled", self.set_location, "work")
  408.         leftlocbox.pack_start(locbutton, False, False, 0)
  409.         locbutton.show()
  410.  
  411.         locbutton = gtk.RadioButton(locbutton, "school")
  412.         locbutton.connect("toggled", self.set_location, "school")
  413.         leftlocbox.pack_start(locbutton, False, False, 0)
  414.         locbutton.show()
  415.  
  416.         locbox.pack_start(leftlocbox, False, False, 0)
  417.         leftlocbox.show()
  418.  
  419.         rightlocbox = gtk.VBox(False, 4)
  420.  
  421.         locbutton = gtk.RadioButton(locbutton, "club")
  422.         locbutton.connect("toggled", self.set_location, "club")
  423.         rightlocbox.pack_start(locbutton, False, False, 0)
  424.         locbutton.show()
  425.  
  426.         locbutton = gtk.RadioButton(locbutton, "visiting")
  427.         locbutton.connect("toggled", self.set_location, "visiting")
  428.         rightlocbox.pack_start(locbutton, False, False, 0)
  429.         locbutton.show()
  430.  
  431.         locbutton = gtk.RadioButton(locbutton, "other")
  432.         locbutton.connect("toggled", self.set_location, "other")
  433.         rightlocbox.pack_start(locbutton, False, False, 0)
  434.         locbutton.show()
  435.  
  436.         locbox.pack_start(rightlocbox, False, False, 0)
  437.         rightlocbox.show()
  438.  
  439.         toprightpositbox.pack_start(locbox, False, False, 0)
  440.         locbox.show()
  441.  
  442.         toppositbox.pack_start(toprightpositbox, False, False, 0)
  443.         toprightpositbox.show()
  444.  
  445.         positbox.pack_start(toppositbox, False, False, 0)
  446.         toppositbox.show()
  447.  
  448.         positlabel2 = gtk.Label("If you do not know your LAT/LONG then your\nzip code will be used to place you on the map.")
  449.         positlabel2.set_alignment(0, 0)
  450.         positbox.pack_start(positlabel2, False, False, 0)
  451.         positlabel2.show()
  452.  
  453.         leftwin.pack_start(positbox, False, False, 0)
  454.         positbox.show()
  455.  
  456.         separator = gtk.HSeparator()
  457.         leftwin.pack_start(separator, False, False, 0)
  458.         separator.show()
  459.  
  460.         # defined here so clear and connect buttons have access
  461.         statusview = gtk.TextView()
  462.         self.statusbuffer = statusview.get_buffer()
  463.  
  464.         connectbutton = gtk.Button()
  465.         connectbutton.set_label("Connect")
  466.         connectbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
  467.         connectbutton.connect("clicked", self.connect_aprs)
  468.         leftwin.pack_start(connectbutton, False, False, 12)
  469.         connectbutton.show()
  470.  
  471.         win.pack_start(leftwin, False, False, 0)
  472.         leftwin.show()
  473.  
  474.         rightwin = gtk.VBox(False, 4)
  475.  
  476.         rightwintopbox = gtk.HBox(False, 4)
  477.  
  478.         clearbutton = gtk.Button()
  479.         clearbutton.set_label("  Clear  ")
  480.         clearbutton.connect("clicked", self.clear_status)
  481.         rightwintopbox.pack_start(clearbutton, False, False, 20)
  482.         clearbutton.show()
  483.  
  484.         self.beaconbutton = gtk.CheckButton("Beacon every 10 minutes")
  485.         self.beaconbutton.set_active(True)
  486.         self.beaconbutton.connect("toggled", self.enable_beacon, "beacon")
  487.         rightwintopbox.pack_start(self.beaconbutton, False, False, 20)
  488.         self.beaconbutton.show()
  489.  
  490.         rightwin.pack_start(rightwintopbox, False, False, 0)
  491.         rightwintopbox.show()
  492.  
  493.         statusview.set_editable(False)
  494.         statusview.set_cursor_visible(True)
  495.         statusview.set_wrap_mode(gtk.WRAP_CHAR)
  496.         statusview.set_justification(gtk.JUSTIFY_LEFT)
  497.         statusview.modify_font(smallfont)
  498.  
  499.         self.statusbuffer.set_text("Welcome to APRS-XO.\n\nThis program sends your position information to a\nserver that will then display your location on\na webpage.  This program requires an active\nInternet connection to work.\n\nSelect an APRS Site\nSelecting a button will copy the URI to the\nclipboard.\n\nIndentifiers\nIf you are not a ham radio operator enter your\nfirst name and zip code.\n\nIf you are a ham radio operator enter your first\nname, call sign, and position data.  If position\nis unknown, leave the call sign field blank and\nenter your 5 digit zip code.\n\nStation Text\nData in the Station Text field will appear\nafter your location information on the website.\n\nBeacon\nWhen selected, sends location data every 10\nminutes.  The beacon will automatically stop\nwhen you edit personal information.  Must be\nmanually reselected after edit completion.\n\nAPRS Copyright (c) Bob Bruninga WB4APR\n\n")
  500.  
  501.         self.statuswindow = gtk.ScrolledWindow()
  502.         self.statuswindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  503.         self.statuswindow.add_with_viewport(statusview)
  504.         statusview.show()
  505.         rightwin.pack_start(self.statuswindow, True, True, 0)
  506.         self.statuswindow.show()
  507.  
  508.         # when messaging is enabled, put a textview for messages above the status textview
  509.         # move the messagebox between them.
  510.         messagebox = gtk.HBox(False, 4)
  511.  
  512.         self.messagecombo = gtk.combo_box_entry_new_text()
  513.         self.messagecombo.append_text("ALL")
  514.         self.messagecombo.append_text("BEACON")
  515.         self.messagecombo.append_text("CQ")
  516.         self.messagecombo.append_text("QST")
  517.         self.messagecombo.set_active(-1)
  518.         self.messagedest = self.messagecombo.get_child()
  519.         self.messagedest.set_max_length(9)
  520.         self.messagedest.set_width_chars(5)
  521.         self.messagedest.modify_font(smallfont)
  522.         messagebox.pack_start(self.messagecombo, False, False, 0)
  523.         self.messagecombo.show()
  524.  
  525.         self.messagetext = gtk.Entry()
  526.         self.messagetext.set_max_length(67)
  527.         self.messagetext.set_width_chars(23)
  528.         self.messagetext.modify_font(smallfont)
  529.         self.messagetext.connect("activate", self.send_message, self.messagetext)
  530.         messagebox.pack_start(self.messagetext, False, False, 0)
  531.         self.messagetext.show()
  532.  
  533.         messagebutton = gtk.Button()
  534.         messagebutton.set_label("Send")
  535.         messagebutton.connect("clicked", self.send_message, "message")
  536.         messagebox.pack_start(messagebutton, False, False, 0)
  537.         messagebutton.show()
  538.  
  539.         rightwin.pack_start(messagebox, False, False, 0)
  540.         messagebox.show()
  541.  
  542.         self.rawbox = gtk.HBox(False, 6)
  543.  
  544.         self.rawtext = gtk.Entry()
  545.         self.rawtext.set_max_length(128)
  546.         self.rawtext.set_width_chars(34)
  547.         self.rawtext.modify_font(smallfont)
  548.         self.rawtext.connect("activate", self.raw_send)
  549.         self.rawbox.pack_start(self.rawtext, False, False, 0)
  550.         self.rawtext.show()
  551.  
  552.         rawbutton = gtk.Button()
  553.         rawbutton.set_label("Send")
  554.         rawbutton.connect("clicked", self.raw_send)
  555.         self.rawbox.pack_start(rawbutton, False, False, 0)
  556.         rawbutton.show()
  557.  
  558.         rightwin.pack_start(self.rawbox, False, False, 0)
  559.         # only show if aprsd says you are verified
  560.         #rawbox.show()
  561.  
  562.         win.pack_start(rightwin, False, False, 0)
  563.         rightwin.show()
  564.  
  565.         win.show()
  566.  
  567.     def clear_status(self, button):
  568.         self.statusbuffer.delete(self.statusbuffer.get_start_iter(), self.statusbuffer.get_end_iter())
  569.  
  570.     def connect_aprs(self, button):
  571.         if (self.validate_data() == False):
  572.             return False
  573.         if (self.sock == None):
  574.             self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  575.             self.status_write("Connecting ")
  576.             iplist = socket.gethostbyname_ex(HOST)[2]
  577.             server = random.choice(iplist)
  578.             self.status_write("to %s\n" % server)
  579.             try:
  580.                 self.sock.connect((server, PORT))
  581.             except socket.error, msg:
  582.                 self.status_write(msg[1])
  583.                 self.sock = None
  584.                 return
  585.  
  586.             self.status_write("Connected\n")
  587.             button.set_label("Disconnect")
  588.             button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#e6000a"))
  589.        
  590.             response = self.sock.recv(RECV_BUFFER)
  591.             self.status_write("%s" % response)
  592.             if (response.find("javAPRSSrvr") == -1):
  593.                 self.status_write("invalid response.")
  594.                 self.disconnect_aprs(button)
  595.  
  596.             # hidden feature
  597.             # if you have callsign, zip code and position - send zip as password
  598.             if (self.calltext.get_text() != "" and self.ziptext.get_text() != "" and self.latDDtext.get_text() != "DD"):
  599.                 sendme = "user %s pass %s vers aprs_xo %d\n" % (self.calltext.get_text(), self.ziptext.get_text(), VERSION)
  600.             else:
  601.                 sendme = "user %s vers aprs_xo %d\n" % (self.calltext.get_text(), VERSION)
  602.             self.sock.sendall(sendme)
  603.             self.status_write("%s" % sendme)
  604.  
  605.             response = self.sock.recv(RECV_BUFFER)
  606.             self.status_write("%s" % response)
  607.             if (response.find("# logresp") == -1):
  608.                 self.status_write("invalid response.")
  609.                 self.disconnect_aprs(button)
  610.             if (response.find("unverified") == -1):
  611.                 self.rawbox.show()
  612.  
  613.             self.input_watch = gobject.io_add_watch(self.sock, gobject.IO_IN, self.recv_data)
  614.  
  615.             # send banner
  616.             sendme = "%s>APRS-XO v%s\n" % (self.calltext.get_text(), VERSION)
  617.             self.sock.sendall(sendme)
  618.  
  619.             self.send_beacon()
  620.             self.output_watch = gobject.timeout_add(10 * 60 * 1000, self.send_beacon)
  621.         else:
  622.             self.disconnect_aprs(button)
  623.             gobject.source_remove(self.input_watch)
  624.             gobject.source_remove(self.output_watch)
  625.  
  626.     def disconnect(self):
  627.         # for test server only
  628.         if(HOST == "192.168.50.6"):
  629.             self.sock.sendall("q")
  630.  
  631.         self.sock.close()
  632.         self.sock = None
  633.  
  634.     def disconnect_aprs(self, button):
  635.         self.disconnect()
  636.  
  637.         self.status_write("Disconnected\n")
  638.         button.set_label("Connect")
  639.         button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
  640.         self.rawbox.hide()
  641.  
  642.     def recv_data(self, sock, condition):
  643.         while 1:
  644.             try:
  645.                 recv_data = sock.recv(RECV_BUFFER)
  646.             except:
  647.                 self.status_write("Server closed connection.\n")
  648.                 return False
  649.             if not recv_data:
  650.                 self.status_write("Server closed connection.\n")
  651.                 return False
  652.             else:
  653.                 self.status_write(recv_data)
  654.                 self.msg_check(recv_data)
  655.                 return True
  656.  
  657.     def send_beacon(self):
  658.         if (self.beaconbutton.get_active()):
  659.             beacon = "%s>APOLPC:=%s%s.%s%sX%s%s.%s%sA%s's XO at %s. %s\n" % (self.calltext.get_text(), self.latDDtext.get_text(), self.latMMtext.get_text(), self.latmmtext.get_text(), self.latcombo.get_active_text(), self.lonDDDtext.get_text(), self.lonMMtext.get_text(), self.lonmmtext.get_text(), self.loncombo.get_active_text(), self.nametext.get_text(), self.location, self.stationtext.get_text())
  660.             try:
  661.                 self.sock.sendall(beacon)
  662.             except:
  663.                 self.status_write("\nProblem sending beacon - STOPPED")
  664.                 return False
  665.             self.status_write("%s" % beacon)
  666.             return True
  667.         else:
  668.             return True
  669.  
  670.     def send_data(self, msg):
  671.         text = "%s>APOLPC:%s\n" % (self.calltext.get_text(), msg)
  672.         try:
  673.             self.sock.sendall(text)
  674.         except:
  675.             self.status_write("Problem sending message\n")
  676.             self.sock = None
  677.             return False
  678.         self.status_write("%s" % text)
  679.         return True
  680.  
  681.     def send_ack(self, msg):
  682.         self.send_data(msg)
  683.         return False
  684.  
  685.     def status_write(self, text):
  686.         self.statusbuffer.insert(self.statusbuffer.get_end_iter(), text)
  687.         adjustment = self.statuswindow.get_vadjustment()
  688.         adjustment.set_value(adjustment.upper)
  689.  
  690.     def set_location(self, widget, data=None):
  691.         self.location = data
  692.  
  693.     def set_site(self, widget, data=None):
  694.         self.site = data
  695.         self.clipboard()
  696.  
  697.     def validate_data(self):
  698.         stop_here = False
  699.         self.validating = True
  700.         beaconchecked = self.beaconbutton.get_active()
  701.  
  702.         if (self.nametext.get_text() == ""):
  703.             self.status_write("First Name is required\n")
  704.             stop_here = True
  705.  
  706.         if (self.calltext.get_text() == "" and self.ziptext.get_text() == ""):
  707.             self.status_write("A callsign or zip code must be provided.\n")
  708.             self.validating = False
  709.             return False
  710.  
  711.         if (stop_here):
  712.             self.validating = False
  713.             return False
  714.  
  715.         # convert zip to position
  716.         if (self.calltext.get_text() == "" and self.ziptext.get_text() != "" and self.latDDtext.get_text() == "DD"):
  717.             self.status_write("Attempting to set location from zip code\n")
  718.             self.ziptext.set_text(self.ziptext.get_text().zfill(5))
  719.             try:
  720.                 zipsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  721.             except socket.error, msg:
  722.                 self.status_write("[ERROR] %s\n" % msg[1])
  723.                 self.validating = False
  724.                 return False
  725.             try:
  726.                 zipsock.connect(('geocoder.us', 80))
  727.             except socket.error, msg:
  728.                 self.status_write("[ERROR] %s\n" % msg[1])
  729.                 self.validating = False
  730.                 return False
  731.             try:
  732.                 zipsock.sendall("GET /service/csv/geocode?zip=%s\n" % self.ziptext.get_text())
  733.             except socket.error, msg:
  734.                 self.status_write("[ERROR] %s\n" % msg[1])
  735.                 self.validating = False
  736.                 return False
  737.             zipsock.shutdown(1)
  738.             try:
  739.                 response = zipsock.recv(RECV_BUFFER)
  740.             except socket.error, msg:
  741.                 self.status_write("[ERROR] %s\n" % msg[1])
  742.                 self.validating = False
  743.                 return False
  744.             A = random.randrange(0,9)
  745.             O = random.randrange(0,9)
  746.             self.calltext.set_text("X%s-%d%d" % (self.ziptext.get_text(), A, O))
  747.             try:
  748.                 (lat, lon, junk) = response.split(',', 2)
  749.             except:
  750.                 self.status_write("%s\n" % response)
  751.                 lat = "0"
  752.                 lon = "0"
  753.             lat = float(lat.strip())
  754.             lon = float(lon.strip())
  755.             if (lat < 0):
  756.                 self.latcombo.set_active(1)     # S
  757.             else:
  758.                 self.latcombo.set_active(0)     # N
  759.             lat = abs(lat)
  760.             if (lon < 0):
  761.                 self.loncombo.set_active(0)     # W
  762.             else:
  763.                 self.loncombo.set_active(1)     # E
  764.             lon = abs(lon)
  765.             self.latDDtext.set_text("%02d" % int(lat))
  766.             self.latMMtext.set_text("%02d" % int((lat - int(lat)) * 60 + 0.5))
  767.             self.latmmtext.set_text("%d " % A)
  768.             self.lonDDDtext.set_text("%03d" % int(lon))
  769.             self.lonMMtext.set_text("%02d" % int((lon - int(lon)) * 60 + 0.5))
  770.             self.lonmmtext.set_text("%d " % O)
  771.             self.ziptext.set_text("")
  772.         # end set loc by zip code
  773.  
  774.         if (self.latDDtext.get_text() == "DD" or self.latDDtext.get_text() == ""):
  775.             self.status_write("Latitude Degrees are required.\n")
  776.             stop_here = True
  777.         if (self.latMMtext.get_text() == "MM" or self.latMMtext.get_text() == ""):
  778.             self.status_write("Latitude Minutes are required.\n")
  779.             stop_here = True
  780.         if (self.lonDDDtext.get_text() == "DDD" or self.lonDDDtext.get_text() == ""):
  781.             self.status_write("Longitude Degrees are required.\n")
  782.             stop_here = True
  783.         if (self.lonMMtext.get_text() == "MM" or self.lonMMtext.get_text() == ""):
  784.             self.status_write("Longitude Minutes are required.\n")
  785.             stop_here = True
  786.  
  787.         if (stop_here):
  788.             self.status_write("Latitude and Longitude must be complete.\nFor position ambiguity omit decimal Minutes (mm).\nIf you do not know your lat/lon, leave the letters\n(DD, MM, etc) and provide your zip code.\n")
  789.             self.validating = False
  790.             return False
  791.  
  792.         if (not self.latDDtext.get_text().isdigit()):
  793.             self.status_write("Latitude Degrees must be a number.\n")
  794.             stop_here = True
  795.         if (not self.latMMtext.get_text().isdigit()):
  796.             self.status_write("Latitude Minutes must be a number.\n")
  797.             stop_here = True
  798.         if (not self.lonDDDtext.get_text().isdigit()):
  799.             self.status_write("Longitude Degrees must be a number.\n")
  800.             stop_here = True
  801.         if (not self.lonMMtext.get_text().isdigit()):
  802.             self.status_write("Longitude Minutes must be a number.\n")
  803.             stop_here = True
  804.  
  805.         if (stop_here):
  806.             self.status_write("Invalid Position.\n")
  807.             self.validating = False
  808.             return False
  809.  
  810.         if (int(self.latDDtext.get_text()) < 0  or int(self.latDDtext.get_text()) > 90):
  811.             self.status_write("Latitude Degrees must be between 0 and 90.\n")
  812.             stop_here = True
  813.         if (int(self.latMMtext.get_text()) < 0  or int(self.latMMtext.get_text()) > 60):
  814.             self.status_write("Latitude Minutes must be between 0 and 60.\n")
  815.             stop_here = True
  816.         if (int(self.lonDDDtext.get_text()) < 0  or int(self.lonDDDtext.get_text()) > 180):
  817.             self.status_write("Longitude Degrees must be between 0 and 180.\n")
  818.             stop_here = True
  819.         if (int(self.lonMMtext.get_text()) < 0  or int(self.lonMMtext.get_text()) > 60):
  820.             self.status_write("Longitude Minutes must be between 0 and 60.\n")
  821.             stop_here = True
  822.  
  823.         if (stop_here):
  824.             self.status_write("Invalid Position.\n")
  825.             self.validating = False
  826.             return False
  827.  
  828.         # clean up entries
  829.         self.latDDtext.set_text(self.latDDtext.get_text().zfill(2))
  830.         self.latMMtext.set_text(self.latMMtext.get_text().zfill(2))
  831.         self.lonDDDtext.set_text(self.lonDDDtext.get_text().zfill(3))
  832.         self.lonMMtext.set_text(self.lonMMtext.get_text().zfill(2))
  833.         self.latmmtext.set_text(self.latmmtext.get_text().ljust(2))
  834.         self.lonmmtext.set_text(self.lonmmtext.get_text().ljust(2))
  835.         if (self.latmmtext.get_text() == "mm"):
  836.             self.latmmtext.set_text("  ")
  837.         if (self.lonmmtext.get_text() == "mm"):
  838.             self.lonmmtext.set_text("  ")
  839.         self.calltext.set_text(self.calltext.get_text().upper())
  840.         self.beaconbutton.set_active(beaconchecked)
  841.         self.validating = False
  842.  
  843.     def onDestroy(self, junk=None ):
  844.         if (self.sock != None):
  845.             self.disconnect()
  846.  
  847. #    def write_file(self, filename):
  848. #        # does not appear to run so commented out for now
  849. #        self.metadata['callsign'] = self.calltext.get_text()
  850. #        self.metadata['latDD'] = self.latDDtext.get_text()
  851. #        self.metadata['latMM'] = self.latMMtext.get_text()
  852. #        self.metadata['latmm'] = self.latmmtext.get_text()
  853. #        self.metadata['lat'] = self.latcombo.get_active()
  854. #        self.metadata['lonDDD'] = self.lonDDDtext.get_text()
  855. #        self.metadata['lonMM'] = self.lonMMtext.get_text()
  856. #        self.metadata['lonmm'] = self.lonmmtext.get_text()
  857. #        self.metadata['lon'] = self.loncombo.get_active()
  858. #        self.metadata['nick_name'] = self.nametext.get_text()
  859. #        self.metadata['location'] = self.location
  860. #        self.metadata['stationtext'] = self.stationtext.get_text()
  861. #        self.metadata['site'] = self.site
  862. #        self.metadata['zip'] = self.ziptext.get_text()
  863. #        self.metadata['beacon'] = self.beaconbutton.get_active()
  864. #        # should save list of callsigns used in outgoing messages
  865.  
  866. #    def read_file(self, filename):
  867. #        # does not appear to run so commented out for now
  868. #        self.calltext.set_text(self.metadata.get('callsign', ""))
  869.  
  870.     def msg_check(self, data):
  871.         # a quick "does it look like a message?" check
  872.         firstcheck = data.find("::")
  873.         secondcheck = data.find("{")
  874.         if (firstcheck != -1 and secondcheck != -1):
  875.             # great, it looks kinda like a message, is it to me?
  876.             tocall = data[firstcheck+2:firstcheck+11]
  877.             if (tocall.strip().upper() == self.calltext.get_text()):
  878.                 sequence_end = data.find("}")
  879.                 if (sequence_end == -1):
  880.                     sequence = data[secondcheck+1:-1]
  881.                 else:
  882.                     sequence = data[secondcheck+1:sequence_end]
  883.                 fromcall = data[:data.find(">")]
  884.                 message = ":%s:ack%s" % (fromcall, sequence)
  885.                 self.send_data(message)
  886.                 id = "%s-%s" % (fromcall, sequence)
  887.                 if (id in self.sent_acks):
  888.                     self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, message))
  889.                     self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, message))
  890.                     self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, message))
  891.  
  892.                 self.sent_acks[id] = time.time() # to help a cleanup thread later
  893.                 self.sent_acks[fromcall] = sequence # to help with reply acks later
  894.  
  895.     def disable_beacon(self, widget, data=None):
  896.         if (self.sock != None):
  897.             self.beaconbutton.set_active(False)
  898.  
  899.     def enable_beacon(self, widget, data=None):
  900.         if (not self.validating and widget.get_active()):
  901.             self.validate_data()
  902.  
  903.     def raw_send(self, widget, data=None):
  904.         msg = "%s\n" % self.rawtext.get_text()
  905.         try:
  906.             self.sock.sendall(msg)
  907.         except:
  908.             self.status_write("Problem sending message\n")
  909.             self.sock = None
  910.             return False
  911.         self.status_write(msg)
  912.         self.rawtext.set_text("")
  913.         return True
  914.  
  915.     def send_message(self, widget, data=None):
  916.         self.status_write("This version of APRS-XO can not send messages.\n")
  917.         self.messagetext.set_text("")
  918.  
  919.     def clipboard(self):
  920.         clipboard = gtk.clipboard_get()
  921.         target = [("text/uri-list", 0, 0)]
  922.         clipboard.set_with_data(target, self.clipboard_get, self.clipboard_clear, (self.site))
  923.  
  924.     def clipboard_get(self, clipboard, selection, info, data):
  925.         selection.set_uris([data])
  926.  
  927.     def clipboard_clear(self, clipboard, data):
  928.         pass
  929.