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