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