# -*- coding: UTF-8 -*-
# Copyright (C) 2008, Jack Zielke <jack@linuxcoffee.com>
# Copyright (C) 2008, One Laptop Per Child
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk
import pango
import time
import socket
import random
import gobject
from gettext import gettext as _
from sugar import profile
from sugar.activity import activity
from sugar.bundle.activitybundle import ActivityBundle
from sugar.graphics.menuitem import MenuItem
from sugar.graphics.toolbutton import ToolButton
TESTING = False
HOST = 'rotate.aprs2.net'
#HOST = '192.168.50.6'
PORT = 14580
RECV_BUFFER = 4096
MAXLINES = 50
MAXRETRIES = 15
bundle = ActivityBundle(activity.get_bundle_path())
VERSION = bundle.get_activity_version()
del bundle
class APRSActivity(activity.Activity):
def __init__(self, handle):
activity.Activity.__init__(self, handle)
self.set_title(_('APRS-XO Activity'))
self.sock = None
self.location = "home"
self.site = None
self.sent_acks = {}
self.recv_acks = {}
self.timers = []
self.bulletins = ["AIR", "DGPS", "QST", "TEL", "ALL", "DRILL", "QTH", "TEST", "AP", "DX", "RTCM", "TLM", "BEACON", "ID", "SKY", "WX", "CQ", "JAVA", "SPACE", "ZIP", "GPS", "MAIL", "SPC", "DF", "MICE", "SYM", "BLN", "NWS", "NTS"]
self.validating = False
self.help = True
self.messagebox = False
self.sequence = random.randrange(0, 7000)
titlefont = pango.FontDescription('Sans bold 8')
mediumfont = pango.FontDescription('Sans 6.5')
smallfont = pango.FontDescription('Sans 6')
verysmallfont = pango.FontDescription('Sans 4')
firstName = profile.get_nick_name().split(None, 1)[0].capitalize()
toolbox = activity.ActivityToolbox(self)
self.set_toolbox(toolbox)
activity_toolbar = toolbox.get_activity_toolbar()
activity_toolbar.share.props.visible = False
activity_toolbar.keep.props.visible = False
toolbox.show()
win = gtk.HBox(False, 10)
self.set_canvas(win)
leftwin = gtk.VBox(False, 10)
# Top 'about' box
aboutbox = gtk.VBox(False, 11)
aboutbox.set_border_width(10)
topaboutbox = gtk.VBox(False, 0)
titlebox = gtk.HBox(False, 0)
titlelabel = gtk.Label("APRS-XO:")
titlelabel.set_alignment(0, 0)
titlelabel.modify_font(titlefont)
titlebox.pack_start(titlelabel, False, False, 0)
titlelabel.show()
aboutlabel1 = gtk.Label("This amateur radio program will update your")
aboutlabel1.set_alignment(0, 0.8)
# aboutlabel1.modify_font(mediumfont)
titlebox.pack_start(aboutlabel1, False, False, 0)
aboutlabel1.show()
topaboutbox.pack_start(titlebox, False, False, 0)
titlebox.show()
aboutlabel2 = gtk.Label("positon & status on all of the global APRS web pages once\nevery 10 minutes.")
aboutlabel2.set_alignment(0, 0)
# aboutlabel2.modify_font(mediumfont)
topaboutbox.pack_start(aboutlabel2, False, False, 0)
aboutlabel2.show()
aboutbox.pack_start(topaboutbox, False, False, 0)
topaboutbox.show()
sitebox = gtk.HBox(False, 10)
sitelabel = gtk.Label("Select an APRS site:")
sitelabel.set_alignment(0, 0.4)
sitebox.pack_start(sitelabel, False, False, 0)
sitelabel.show()
findubutton = gtk.Button()
findubutton.set_label("FINDU.COM")
findubutton.connect("clicked", self.set_site, "http://www.findu.com/cgi-bin/symbol.cgi?icon=XA&limit=200")
sitebox.pack_start(findubutton, False, False, 0)
findubutton.show()
aprsworldbutton = gtk.Button()
aprsworldbutton.set_label("APRSworld")
aprsworldbutton.connect("clicked", self.set_site, "http://aprsworld.net/")
sitebox.pack_start(aprsworldbutton, False, False, 0)
aprsworldbutton.show()
otherbutton = gtk.Button()
otherbutton.set_label("About")
otherbutton.connect("clicked", self.set_site, "http://aprs.org/")
sitebox.pack_start(otherbutton, False, False, 0)
otherbutton.show()
aboutbox.pack_start(sitebox, False, False, 0)
sitebox.show()
# infobox = gtk.VBox(False, 4)
## 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.")
# infolabel = gtk.Label("With a ham license you can communicate worldwide with wireless and radio.")
# infolabel.set_alignment(0, 0)
# infolabel.modify_font(smallfont)
# infobox.pack_start(infolabel, False, False, 0)
# infolabel.show()
# aboutbox.pack_start(infobox, False, False, 0)
# infobox.show()
leftwin.pack_start(aboutbox, False, False, 0)
aboutbox.show()
separator = gtk.HSeparator()
leftwin.pack_start(separator, False, False, 0)
separator.show()
# identifiers box
identbox = gtk.VBox(False, 4)
identbox.set_border_width(10)
identlabel = gtk.Label("Identifiers")
identlabel.set_alignment(0, 0)
identlabel.modify_font(titlefont)
identbox.pack_start(identlabel, False, False, 0)
identlabel.show()
bottomidentbox = gtk.HBox(False, 10)
calllabel1 = gtk.Label("Callsign: ")
calllabel1.set_alignment(0, 0.5)
bottomidentbox.pack_start(calllabel1, False, False, 0)
calllabel1.show()
self.calltext = gtk.Entry()
self.calltext.set_max_length(9)
self.calltext.set_width_chars(9)
self.calltext.connect("changed", self.disable_beacon)
bottomidentbox.pack_start(self.calltext, False, False, 0)
self.calltext.show()
passlabel1 = gtk.Label("Password: ")
passlabel1.set_alignment(0, 0.5)
bottomidentbox.pack_start(passlabel1, False, False, 0)
passlabel1.show()
self.passtext = gtk.Entry()
self.passtext.set_max_length(5)
self.passtext.set_width_chars(5)
self.passtext.set_invisible_char("x")
self.passtext.set_visibility(False)
bottomidentbox.pack_start(self.passtext, False, False, 0)
self.passtext.show()
identbox.pack_start(bottomidentbox, False, False, 0)
bottomidentbox.show()
passlabel2 = gtk.Label("optional")
passlabel2.set_alignment(0.74, 0)
passlabel2.modify_font(smallfont)
identbox.pack_start(passlabel2, False, False, 0)
passlabel2.show()
leftwin.pack_start(identbox, False, False, 0)
identbox.show()
separator = gtk.HSeparator()
leftwin.pack_start(separator, False, False, 0)
separator.show()
# station box
stationbox = gtk.VBox(False, 11)
stationbox.set_border_width(10)
stationlabel = gtk.Label("Station Comment")
stationlabel.set_alignment(0, 0)
stationlabel.modify_font(titlefont)
stationbox.pack_start(stationlabel, False, False, 0)
stationlabel.show()
# so the text box does not fill all Horizontal space
stationtextbox = gtk.HBox()
self.stationtext = gtk.Entry()
self.stationtext.set_max_length(43)
self.stationtext.set_width_chars(43)
self.stationtext.set_text("%s's XO at home." % firstName)
self.stationtext.connect("changed", self.disable_beacon)
stationtextbox.pack_start(self.stationtext, False, False, 0)
self.stationtext.show()
stationbox.pack_start(stationtextbox, False, False, 0)
stationtextbox.show()
stationhelp = gtk.Label("Optional description of current position, status,\nor destination. Enter up to 43 characters, most\nimportant first since some displays can only see\nthe first 20 or 28.")
stationhelp.set_alignment(0, 0)
# stationhelp.modify_font(mediumfont)
stationbox.pack_start(stationhelp, False, False, 0)
stationhelp.show()
leftwin.pack_start(stationbox, False, False, 0)
stationbox.show()
separator = gtk.HSeparator()
leftwin.pack_start(separator, False, False, 0)
separator.show()
# position box
positbox = gtk.VBox(False, 11)
positbox.set_border_width(10)
toppositbox = gtk.HBox(False, 0)
topleftpositbox = gtk.VBox(False, 4)
positlabel1 = gtk.Label("Position Data")
positlabel1.set_alignment(0, 0)
positlabel1.modify_font(titlefont)
topleftpositbox.pack_start(positlabel1, False, False, 0)
positlabel1.show()
latpositbox = gtk.HBox(False, 4)
latlabel1 = gtk.Label("Latitude: ")
latlabel1.set_alignment(0, 0.5)
latpositbox.pack_start(latlabel1, False, False, 0)
latlabel1.show()
self.latDDtext = gtk.Entry()
self.latDDtext.set_max_length(2)
self.latDDtext.set_width_chars(4)
self.latDDtext.set_text("DD")
self.latDDtext.select_region(1,2)
self.latDDtext.connect("changed", self.disable_beacon)
latpositbox.pack_start(self.latDDtext, False, False, 0)
self.latDDtext.show()
self.latMMtext = gtk.Entry()
self.latMMtext.set_max_length(2)
self.latMMtext.set_width_chars(3)
self.latMMtext.set_text("MM")
self.latMMtext.select_region(1,2)
self.latMMtext.connect("changed", self.disable_beacon)
latpositbox.pack_start(self.latMMtext, False, False, 0)
self.latMMtext.show()
latlabel2 = gtk.Label(".")
latlabel2.set_alignment(0, 1)
latpositbox.pack_start(latlabel2, False, False, 0)
latlabel2.show()
self.latmmtext = gtk.Entry()
self.latmmtext.set_max_length(2)
self.latmmtext.set_width_chars(3)
self.latmmtext.set_text("mm")
self.latmmtext.select_region(1,2)
self.latmmtext.connect("changed", self.disable_beacon)
latpositbox.pack_start(self.latmmtext, False, False, 0)
self.latmmtext.show()
self.latcombo = gtk.combo_box_new_text()
self.latcombo.append_text("N")
self.latcombo.append_text("S")
self.latcombo.set_active(0)
latpositbox.pack_start(self.latcombo, False, False, 0)
self.latcombo.show()
topleftpositbox.pack_start(latpositbox, False, False, 0)
latpositbox.show()
lonpositbox = gtk.HBox(False, 4)
lonlabel1 = gtk.Label("Longitude: ")
lonlabel1.set_alignment(0, 0.5)
lonpositbox.pack_start(lonlabel1, False, False, 0)
lonlabel1.show()
self.lonDDDtext = gtk.Entry()
self.lonDDDtext.set_max_length(3)
self.lonDDDtext.set_width_chars(4)
self.lonDDDtext.set_text("DDD")
self.lonDDDtext.select_region(1,2)
self.lonDDDtext.connect("changed", self.disable_beacon)
lonpositbox.pack_start(self.lonDDDtext, False, False, 0)
self.lonDDDtext.show()
self.lonMMtext = gtk.Entry()
self.lonMMtext.set_max_length(2)
self.lonMMtext.set_width_chars(3)
self.lonMMtext.set_text("MM")
self.lonMMtext.select_region(1,2)
self.lonMMtext.connect("changed", self.disable_beacon)
lonpositbox.pack_start(self.lonMMtext, False, False, 0)
self.lonMMtext.show()
lonlabel2 = gtk.Label(".")
lonlabel2.set_alignment(0, 1)
lonpositbox.pack_start(lonlabel2, False, False, 0)
lonlabel2.show()
self.lonmmtext = gtk.Entry()
self.lonmmtext.set_max_length(2)
self.lonmmtext.set_width_chars(3)
self.lonmmtext.set_text("mm")
self.lonmmtext.select_region(1,2)
self.lonmmtext.connect("changed", self.disable_beacon)
lonpositbox.pack_start(self.lonmmtext, False, False, 0)
self.lonmmtext.show()
self.loncombo = gtk.combo_box_new_text()
self.loncombo.append_text("W")
self.loncombo.append_text("E")
self.loncombo.set_active(0)
lonpositbox.pack_start(self.loncombo, False, False, 0)
self.loncombo.show()
topleftpositbox.pack_start(lonpositbox, False, False, 0)
lonpositbox.show()
toppositbox.pack_start(topleftpositbox, False, False, 0)
topleftpositbox.show()
toprightpositbox = gtk.VBox(False, 4)
toprightpositbox.set_border_width(10)
loclabel = gtk.Label("-OR- Zip Code:")
loclabel.set_alignment(0.3, 0)
toprightpositbox.pack_start(loclabel, False, False, 0)
loclabel.show()
self.ziptext = gtk.Entry()
self.ziptext.set_max_length(5)
self.ziptext.set_width_chars(5)
self.ziptext.connect("changed", self.disable_beacon)
toprightpositbox.pack_start(self.ziptext, False, False, 0)
self.ziptext.show()
toppositbox.pack_start(toprightpositbox, False, False, 0)
toprightpositbox.show()
positbox.pack_start(toppositbox, False, False, 0)
toppositbox.show()
positlabel2 = gtk.Label("If you do not know your LAT/LONG then your zip code will\nbe used to place you on the map.")
positlabel2.set_alignment(0, 0)
# positlabel2.modify_font(mediumfont)
positbox.pack_start(positlabel2, False, False, 0)
positlabel2.show()
leftwin.pack_start(positbox, False, False, 0)
positbox.show()
separator = gtk.HSeparator()
leftwin.pack_start(separator, False, False, 0)
separator.show()
# defined here so clear and connect buttons have access
self.statusview = gtk.TextView()
self.statusbuffer = self.statusview.get_buffer()
self.messageview = gtk.TextView()
self.messagebuffer = self.messageview.get_buffer()
connectbutton = gtk.Button()
connectbutton.set_label("Connect")
connectbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
connectbutton.connect("clicked", self.connect_aprs)
leftwin.pack_start(connectbutton, False, False, 12)
connectbutton.show()
win.pack_start(leftwin, False, False, 0)
leftwin.show()
rightwin = gtk.VBox(False, 4)
rightwin.set_border_width(4)
rightwintopbox = gtk.HBox(False, 4)
clearbutton = gtk.Button()
clearbutton.set_label(" Clear ")
clearbutton.connect("clicked", self.clear_message)
rightwintopbox.pack_start(clearbutton, False, False, 20)
clearbutton.show()
self.beaconbutton = gtk.CheckButton("Beacon every 10 minutes")
self.beaconbutton.set_active(True)
self.beaconbutton.connect("toggled", self.enable_beacon, "beacon")
rightwintopbox.pack_start(self.beaconbutton, False, False, 20)
self.beaconbutton.show()
rightwin.pack_start(rightwintopbox, False, False, 0)
rightwintopbox.show()
self.messageview.set_editable(False)
self.messageview.set_cursor_visible(True)
self.messageview.set_wrap_mode(gtk.WRAP_CHAR)
self.messageview.set_justification(gtk.JUSTIFY_LEFT)
self.messageview.modify_font(smallfont)
self.messagebuffer.set_text("Welcome to APRS-XO.\n\nThis program sends your position information to a server that will\nthen display your location on a webpage. This program requires an\nactive 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 location\ninformation on the website.")
self.messagewindow = gtk.ScrolledWindow()
self.messagewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
self.messagewindow.add_with_viewport(self.messageview)
self.messageview.show()
rightwin.pack_start(self.messagewindow, True, True, 0)
self.messagewindow.show()
messagebox = gtk.HBox(False, 4)
self.messagecombo = gtk.combo_box_entry_new_text()
self.messagecombo.append_text("ALL")
self.messagecombo.append_text("BEACON")
self.messagecombo.append_text("CQ")
self.messagecombo.append_text("QST")
self.messagecombo.set_active(-1)
self.messagedest = self.messagecombo.get_child()
self.messagedest.set_max_length(9)
self.messagedest.set_width_chars(5)
self.messagedest.modify_font(smallfont)
messagebox.pack_start(self.messagecombo, False, False, 0)
self.messagecombo.show()
self.messagetext = gtk.Entry()
# TODO should this be lower with acks?
self.messagetext.set_max_length(67)
self.messagetext.set_width_chars(32)
self.messagetext.modify_font(smallfont)
self.messagetext.connect("activate", self.send_message, self.messagetext)
messagebox.pack_start(self.messagetext, False, False, 0)
self.messagetext.show()
messagebutton = gtk.Button()
messagebutton.set_label("Send")
messagebutton.connect("clicked", self.send_message, "message")
messagebox.pack_start(messagebutton, False, False, 0)
messagebutton.show()
rightwin.pack_start(messagebox, False, False, 0)
messagebox.show()
self.statusview.set_editable(False)
self.statusview.set_cursor_visible(True)
self.statusview.set_wrap_mode(gtk.WRAP_CHAR)
self.statusview.set_justification(gtk.JUSTIFY_LEFT)
self.statusview.modify_font(smallfont)
self.statusbuffer.set_text("Position\nEnter your lat/long. You may leave the decimal minutes (mm)\nblank for position ambiguity. If you do not know your lat/long,\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 completion.\n\nThis message will self destruct when you press Connect.\n\nAPRS Copyright (c) Bob Bruninga WB4APR\n\n")
self.statuswindow = gtk.ScrolledWindow()
self.statuswindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
self.statuswindow.add_with_viewport(self.statusview)
self.statusview.show()
rightwin.pack_start(self.statuswindow, True, True, 0)
self.statuswindow.show()
self.rawbox = gtk.HBox(False, 6)
self.rawtext = gtk.Entry()
self.rawtext.set_max_length(128)
self.rawtext.set_width_chars(43)
self.rawtext.modify_font(smallfont)
self.rawtext.connect("activate", self.raw_send)
self.rawbox.pack_start(self.rawtext, False, False, 0)
self.rawtext.show()
rawbutton = gtk.Button()
rawbutton.set_label("Send")
rawbutton.connect("clicked", self.raw_send)
self.rawbox.pack_start(rawbutton, False, False, 0)
rawbutton.show()
rightwin.pack_start(self.rawbox, False, False, 0)
self.rawbox.show()
win.pack_start(rightwin, False, False, 0)
rightwin.show()
# for quick testing
if (TESTING):
self.calltext.set_text("kg4gjy")
self.passtext.set_text("18107")
self.latDDtext.set_text("35")
self.latMMtext.set_text("7")
self.latmmtext.set_text("42")
self.latcombo.set_active(0)
self.lonDDDtext.set_text("85")
self.lonMMtext.set_text("6")
self.lonmmtext.set_text("93")
self.loncombo.set_active(0)
# self.stationtext.set_text("")
self.ziptext.set_text("")
self.beaconbutton.set_active(True)
win.show()
self.calltext.grab_focus()
def clear_status(self, button=None):
# self.statusbuffer.delete(self.statusbuffer.get_start_iter(), self.statusbuffer.get_end_iter())
self.statusbuffer.set_text("")
def clear_message(self, button=None):
# self.messagebuffer.delete(self.messagebuffer.get_start_iter(), self.messagebuffer.get_end_iter())
self.messagebuffer.set_text("")
def connect_aprs(self, button):
if (self.sock == None):
if (self.help):
self.clear_status()
self.messagebuffer.set_text("Message Window")
self.messagebox = True
self.help = False
if (self.validate_data() == False):
return False
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.status_write("Connecting ")
iplist = socket.gethostbyname_ex(HOST)[2]
server = random.choice(iplist)
self.status_write("to %s\n" % server)
try:
self.sock.connect((server, PORT))
except socket.error, msg:
self.status_write(msg[1])
self.sock = None
return
self.status_write("Connected\n")
button.set_label("Disconnect")
button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#e6000a"))
response = self.sock.recv(RECV_BUFFER)
self.status_write("%s" % response)
if (response.find("javAPRSSrvr") == -1):
self.status_write("invalid response.\n")
self.disconnect_aprs(button)
if (response.find("Port Full") != -1):
self.status_write("Port Full.\n")
self.disconnect_aprs(button)
if (self.calltext.get_text() != "" and self.passtext.get_text() != ""):
sendme = "user %s pass %s vers aprs_xo %d filter m/300\n" % (self.calltext.get_text(), self.passtext.get_text(), VERSION)
else:
sendme = "user %s vers aprs_xo %d\n" % (self.calltext.get_text(), VERSION)
self.sock.sendall(sendme)
self.status_write("%s" % sendme)
response = self.sock.recv(RECV_BUFFER)
self.status_write("%s" % response)
if (response.find("# logresp") == -1):
self.status_write("invalid response.\n")
self.disconnect_aprs(button)
# if (response.find("unverified") == -1):
# self.rawbox.show()
self.status_write("\n")
self.input_watch = gobject.io_add_watch(self.sock, gobject.IO_IN, self.recv_data)
# send banner
sendme = "%s>APRS-XO v%s\n" % (self.calltext.get_text(), VERSION)
self.sock.sendall(sendme)
self.status_write("%s>\n" % self.calltext.get_text())
self.status_write("APRS-XO v%s\n\n" % VERSION)
self.send_beacon()
self.output_watch = gobject.timeout_add(10 * 60 * 1000, self.send_beacon)
else:
self.disconnect_aprs(button)
gobject.source_remove(self.input_watch)
gobject.source_remove(self.output_watch)
def disconnect(self):
# for test server only
if(HOST == "192.168.50.6"):
self.sock.sendall("q")
self.sock.close()
self.sock = None
def disconnect_aprs(self, button):
self.disconnect()
self.status_write("Disconnected\n")
button.set_label("Connect")
button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
# self.rawbox.hide()
def recv_data(self, sock, condition):
while 1:
try:
recv_data = sock.recv(RECV_BUFFER)
except:
self.status_write("Server closed connection.\n")
return False
if not recv_data:
self.status_write("Server closed connection.\n")
return False
else:
# find uri's http or else www
webaddy = recv_data.lower().find("http")
if (webaddy != -1):
recv_data = ("%s\n%s" % (recv_data[:webaddy], recv_data[webaddy:]))
else:
webaddy = recv_data.lower().find("www")
if (webaddy != -1):
recv_data = ("%s\n%s" % (recv_data[:webaddy], recv_data[webaddy:]))
if (recv_data[0] == "#"):
self.status_write("%s\n\n" % recv_data)
else:
cuthere = recv_data.find(":")
self.status_write("%s\n" % recv_data[:cuthere+1])
# some incoming lines end in \n and some do not
if (recv_data[-1:] == "\n"):
self.status_write("%s\n" % recv_data[cuthere+1:])
else:
self.status_write("%s\n\n" % recv_data[cuthere+1:])
self.msg_check(recv_data)
return True
def send_beacon(self):
if (self.beaconbutton.get_active()):
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())
if (self.send_data(beacon)):
return True
else:
self.status_write("\nProblem sending beacon - STOPPED")
return False
def send_data(self, msg):
path = "%s>APOLPC:" % self.calltext.get_text()
try:
self.sock.sendall("%s%s\n" % (path, msg))
except:
self.status_write("Problem sending packet\n")
self.sock = None
return False
self.status_write("%s\n" % path)
self.status_write("%s\n\n" % msg)
return True
def send_ack(self, msg):
self.send_data(msg)
return False
def status_write(self, text):
self.statusbuffer.insert(self.statusbuffer.get_end_iter(), text)
statuslines = self.statusbuffer.get_line_count()
if (statuslines > MAXLINES):
deletehere = self.statusbuffer.get_iter_at_line(statuslines - MAXLINES)
self.statusbuffer.delete(self.statusbuffer.get_start_iter(), deletehere)
if (not self.statusview.is_focus()):
adjustment = self.statuswindow.get_vadjustment()
adjustment.set_value(adjustment.upper)
def message_write(self, text):
if (self.messagebox):
self.clear_message()
self.messagebox = False
self.messagebuffer.insert(self.messagebuffer.get_end_iter(), text)
if (not self.messageview.is_focus()):
adjustment = self.messagewindow.get_vadjustment()
adjustment.set_value(adjustment.upper)
def set_location(self, widget, data=None):
self.location = data
def set_site(self, widget, data=None):
self.site = data
self.clipboard()
def validate_data(self):
stop_here = False
self.validating = True
beaconchecked = self.beaconbutton.get_active()
if (self.calltext.get_text() == "" and self.ziptext.get_text() == ""):
self.status_write("A callsign or zip code must be provided.\n")
return False
# convert zip to position
if (self.ziptext.get_text() != ""):
self.status_write("Attempting to set location from zip code\n")
self.ziptext.set_text(self.ziptext.get_text().zfill(5))
try:
zipsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
self.status_write("[ERROR] %s\n" % msg[1])
self.validating = False
return False
try:
zipsock.connect(('geocoder.us', 80))
except socket.error, msg:
self.status_write("[ERROR] %s\n" % msg[1])
self.validating = False
return False
try:
zipsock.sendall("GET /service/csv/geocode?zip=%s\n" % self.ziptext.get_text())
except socket.error, msg:
self.status_write("[ERROR] %s\n" % msg[1])
self.validating = False
return False
zipsock.shutdown(1)
try:
response = zipsock.recv(RECV_BUFFER)
except socket.error, msg:
self.status_write("[ERROR] %s\n" % msg[1])
self.validating = False
return False
A = random.randrange(0, 9)
O = random.randrange(0, 9)
if (self.calltext.get_text() == ""):
self.calltext.set_text("X%s-%d%d" % (self.ziptext.get_text(), A, O))
try:
(lat, lon, junk) = response.split(',', 2)
except:
self.status_write("%s\n" % response)
lat = "0"
lon = "0"
lat = float(lat.strip())
lon = float(lon.strip())
if (lat < 0):
self.latcombo.set_active(1) # S
else:
self.latcombo.set_active(0) # N
lat = abs(lat)
if (lon < 0):
self.loncombo.set_active(0) # W
else:
self.loncombo.set_active(1) # E
lon = abs(lon)
self.latDDtext.set_text("%02d" % int(lat))
self.latMMtext.set_text("%02d" % int((lat - int(lat)) * 60 + 0.5))
self.latmmtext.set_text("%d " % A)
self.lonDDDtext.set_text("%03d" % int(lon))
self.lonMMtext.set_text("%02d" % int((lon - int(lon)) * 60 + 0.5))
self.lonmmtext.set_text("%d " % O)
self.ziptext.set_text("")
# end set loc by zip code
if (self.latDDtext.get_text() == "DD" or self.latDDtext.get_text() == ""):
self.status_write("Latitude Degrees are required.\n")
stop_here = True
if (self.latMMtext.get_text() == "MM" or self.latMMtext.get_text() == ""):
self.status_write("Latitude Minutes are required.\n")
stop_here = True
if (self.lonDDDtext.get_text() == "DDD" or self.lonDDDtext.get_text() == ""):
self.status_write("Longitude Degrees are required.\n")
stop_here = True
if (self.lonMMtext.get_text() == "MM" or self.lonMMtext.get_text() == ""):
self.status_write("Longitude Minutes are required.\n")
stop_here = True
if (stop_here):
self.status_write("Latitude and Longitude must be complete.\nFor position ambiguity omit decimal Minutes (mm).\nIf you do not know your lat/long, leave the letters\n(DD, MM, etc) and provide your zip code.\n")
self.validating = False
return False
if (not self.latDDtext.get_text().isdigit()):
self.status_write("Latitude Degrees must be a number.\n")
stop_here = True
if (not self.latMMtext.get_text().isdigit()):
self.status_write("Latitude Minutes must be a number.\n")
stop_here = True
if (not self.lonDDDtext.get_text().isdigit()):
self.status_write("Longitude Degrees must be a number.\n")
stop_here = True
if (not self.lonMMtext.get_text().isdigit()):
self.status_write("Longitude Minutes must be a number.\n")
stop_here = True
if (stop_here):
self.status_write("Invalid Position.\n")
self.validating = False
return False
if (int(self.latDDtext.get_text()) < 0 or int(self.latDDtext.get_text()) > 90):
self.status_write("Latitude Degrees must be between 0 and 90.\n")
stop_here = True
if (int(self.latMMtext.get_text()) < 0 or int(self.latMMtext.get_text()) > 60):
self.status_write("Latitude Minutes must be between 0 and 60.\n")
stop_here = True
if (int(self.lonDDDtext.get_text()) < 0 or int(self.lonDDDtext.get_text()) > 180):
self.status_write("Longitude Degrees must be between 0 and 180.\n")
stop_here = True
if (int(self.lonMMtext.get_text()) < 0 or int(self.lonMMtext.get_text()) > 60):
self.status_write("Longitude Minutes must be between 0 and 60.\n")
stop_here = True
if (stop_here):
self.status_write("Invalid Position.\n")
self.validating = False
return False
# clean up entries
self.latDDtext.set_text(self.latDDtext.get_text().zfill(2))
self.latMMtext.set_text(self.latMMtext.get_text().zfill(2))
self.lonDDDtext.set_text(self.lonDDDtext.get_text().zfill(3))
self.lonMMtext.set_text(self.lonMMtext.get_text().zfill(2))
self.latmmtext.set_text(self.latmmtext.get_text().ljust(2))
self.lonmmtext.set_text(self.lonmmtext.get_text().ljust(2))
if (self.latmmtext.get_text() == "mm"):
self.latmmtext.set_text(" ")
if (self.lonmmtext.get_text() == "mm"):
self.lonmmtext.set_text(" ")
self.calltext.set_text(self.calltext.get_text().upper())
self.beaconbutton.set_active(beaconchecked)
self.validating = False
def close( self ):
self.hide()
if (self.sock != None):
self.disconnect()
activity.Activity.close( self )
def write_file_broken(self, filename):
# does not appear to run
# from self.save() returns:
# TypeError: object of type 'dbus.Int32' has no len()
self.metadata['mime_type'] = 'text/plain'
self.metadata['callsign'] = self.calltext.get_text()
self.metadata['password'] = self.passtext.get_text()
self.metadata['latDD'] = self.latDDtext.get_text()
self.metadata['latMM'] = self.latMMtext.get_text()
self.metadata['latmm'] = self.latmmtext.get_text()
self.metadata['lat'] = self.latcombo.get_active()
self.metadata['lonDDD'] = self.lonDDDtext.get_text()
self.metadata['lonMM'] = self.lonMMtext.get_text()
self.metadata['lonmm'] = self.lonmmtext.get_text()
self.metadata['lon'] = self.loncombo.get_active()
# self.metadata['nick_name'] = self.nametext.get_text()
# self.metadata['location'] = self.location
self.metadata['stationtext'] = self.stationtext.get_text()
# self.metadata['zip'] = self.ziptext.get_text()
self.metadata['beacon'] = self.beaconbutton.get_active()
# should save list of callsigns used in outgoing messages
def read_file_broken(self, filename):
# does not appear to run
self.calltext.set_text(self.metadata.get('callsign', ""))
self.passtext.set_text(self.metadata.get('password', ""))
self.latDDtext.set_text(self.metadata.get('latDD', "00"))
self.latMMtext.set_text(self.metadata.get('latMM', "00"))
self.latmmtext.set_text(self.metadata.get('latmm', "00"))
# self.latcombo.set_active(self.metadata.get('lat', ""))
self.lonDDDtext.set_text(self.metadata.get('lonDDD', "000"))
self.lonMMtext.set_text(self.metadata.get('lonMM', "00"))
self.lonmmtext.set_text(self.metadata.get('lonmm', "00"))
# self.loncombo.set_active(self.metadata.get('lon', ""))
# self.nametext.set_text(self.metadata.get('nick_name', "Joe"))
# self.locbutton.set_active(self.metadata.get('location', ""))
self.stationtext.set_text(self.metadata.get('stationtext', "test text"))
self.ziptext.set_text(self.metadata.get('zip', ""))
# self.beaconbutton.set_active(self.metadata.get('beacon', ""))
def msg_check(self, data):
# a better message decoder
firstcheck = data.find("::")
secondcheck = data[firstcheck+11:firstcheck+12]
thirdcheck = data.find("{")
if (firstcheck != -1 and secondcheck == ":"):
tocall = data[firstcheck+2:firstcheck+11].upper()
strippedtocall = tocall.strip()
fromcall = data[:data.find(">")].upper()
isbulletin = self.bulletin_check(strippedtocall)
if (isbulletin):
message = data[firstcheck+12:-1]
# TODO add BLN, etc to tocall dropdown list?
# self.add_callsign(fromcall, False)
# for now, display them every time
self.message_write("%s %s.%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, strippedtocall, message))
else:
if (strippedtocall == self.calltext.get_text()):
self.add_callsign(fromcall, False)
if (thirdcheck != -1):
sequence_end = data.find("}")
if (sequence_end == -1):
sequence = data[thirdcheck+1:-1]
else:
sequence = data[thirdcheck+1:sequence_end]
replyack = data[sequence_end+1:-1]
if (replyack[0:3] == "ack"):
self.recv_acks["%s-%s" % (fromcall, replyack[3:])] = 1
message = data[firstcheck+12:thirdcheck]
ackmessage = ":%s:ack%s" % (fromcall, sequence)
self.send_data(ackmessage)
id = "%s-%s" % (fromcall, sequence)
if (id in self.sent_acks):
self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, ackmessage))
self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, ackmessage))
self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, ackmessage))
else:
self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message))
self.sent_acks[id] = time.time() # to help a cleanup thread later
self.sent_acks[fromcall] = sequence # to help with reply acks later
else:
message = data[firstcheck+12:-1]
if (message[0:3] == "ack"):
self.recv_acks["%s-%s" % (fromcall, message[3:])] = 1
else:
self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message))
def msg_check_old(self, data):
# a quick "does it look like a message?" check
# this check will only find messages that require acks
firstcheck = data.find("::")
secondcheck = data.find("{")
# FIX this will ignore BLN, ALL, QST, CQ, etc with { in message
# FIXME actually a BLN with a { freezes the status window..
if (firstcheck != -1 and secondcheck != -1):
# great, it looks kinda like a message, is it to me?
tocall = data[firstcheck+2:firstcheck+11].upper()
strippedtocall = tocall.strip()
if (strippedtocall == self.calltext.get_text()):
sequence_end = data.find("}")
if (sequence_end == -1):
sequence = data[secondcheck+1:-1]
else:
sequence = data[secondcheck+1:sequence_end]
fromcall = data[:data.find(">")].upper()
message = data[firstcheck+12:secondcheck]
self.add_callsign(fromcall, False)
ackmessage = ":%s:ack%s" % (fromcall, sequence)
self.send_data(ackmessage)
id = "%s-%s" % (fromcall, sequence)
if (id in self.sent_acks):
self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, ackmessage))
self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, ackmessage))
self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, ackmessage))
else:
self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message))
self.sent_acks[id] = time.time() # to help a cleanup thread later
self.sent_acks[fromcall] = sequence # to help with reply acks later
else:
# is it a message that does not get acked?
if (firstcheck != -1):
tocall = data[firstcheck+2:firstcheck+11].upper()
strippedtocall = tocall.strip()
fromcall = data[:data.find(">")].upper()
message = data[firstcheck+12:-1]
if (strippedtocall == "ALL" or strippedtocall == "QST" or strippedtocall == "CQ" or strippedtocall[:3] == "BLN" or strippedtocall[:3] == "NWS"):
# for now, display them every time
self.message_write("%s %s:%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, strippedtocall, message))
else:
# is it an ack (to me)?
if (strippedtocall == self.calltext.get_text()):
if (message[0:3] == "ack"):
# record the message as acked to stop repeating it
self.recv_acks["%s-%s" % (fromcall, message[3:])] = 1
else:
# no ack and addressed to me.
self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message))
def disable_beacon(self, widget, data=None):
if (self.sock != None):
self.beaconbutton.set_active(False)
def enable_beacon(self, widget, data=None):
if (not self.validating and widget.get_active()):
self.validate_data()
def raw_send(self, widget, data=None):
msg = "%s\n" % self.rawtext.get_text()
try:
self.sock.sendall(msg)
except:
self.status_write("Problem sending message\n")
self.sock = None
return False
self.status_write("%s\n" % msg)
self.rawtext.set_text("")
return True
def send_message(self, widget, data=None):
tocall = self.messagedest.get_text().upper()
message = self.messagetext.get_text()
isbulletin = self.bulletin_check(tocall)
# check for illegal characters before clearing message
if (isbulletin):
if (message.find("|") != -1 or message.find("~") != -1):
self.message_write("Bulletins can not contain the following characters: | ~")
return False
else:
if (message.find("|") != -1 or message.find("~") != -1 or message.find("{") != -1):
self.message_write("Messages can not contain the following characters: | ~ {")
return False
self.messagetext.set_text("")
# add callsign to list if just entered
if (self.messagecombo.get_active() == -1):
self.add_callsign(tocall, True)
# get a sequence number
if (isbulletin):
# don't waste a number on a bulletin
sequence = ""
else:
sequence = "%s" % self.b90()
# TODO
# check to see if there is an unack-ed message to same callsign
# too many to the same call? give an error instead
# too many in general? give an error instead
# print message + QUEUED add to the queue...
# cancel message option - menu popup
# TODO save location for repeat/ack counter, queued and cancelled locations too
# put the message in the message window
# self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), self.calltext.get_text(), message))
self.message_write("%s To:%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), tocall, message))
# send the message
if (isbulletin):
self.send_data(":%s:%s" % (tocall.ljust(9), message))
# TODO what are the correct timings for bulletins?
gobject.timeout_add(600 * 1000, self.msg_timer, tocall, message, "", 2, 600)
else:
replyack = self.replyack(tocall)
self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack))
self.recv_acks["%s-%s" % (tocall, sequence)] = 0
gobject.timeout_add(7 * 1000, self.msg_timer, tocall, message, sequence, 2, 7)
def b90(self):
if (self.sequence > 8099):
self.sequence = 0
first = ((self.sequence / 90) % 90) + 33
second = (self.sequence % 90) + 33
output = "%c%c" % (first, second)
self.sequence += 1
return output
def msg_timer(self, tocall, message, sequence, count, delay):
if (self.recv_acks["%s-%s" % (tocall, sequence)] == 1):
return False
else:
replyack = self.replyack(tocall)
self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack))
# start the next timer
count += 1
if (count > MAXRETRIES):
return False
delay *= 2
if (delay > 600):
delay = 600
gobject.timeout_add(delay * 1000, self.msg_timer, tocall, message, sequence, count, delay)
# and stop this timer
return False
def clipboard(self):
clipboard = gtk.clipboard_get()
target = [("text/uri-list", 0, 0)]
clipboard.set_with_data(target, self.clipboard_get, self.clipboard_clear, (self.site))
def clipboard_get(self, clipboard, selection, info, data):
selection.set_uris([data])
def clipboard_clear(self, clipboard, data):
pass
def add_callsign(self, callsign, activate):
model = self.messagecombo.get_model()
notfound = True
iter = model.get_iter_first()
while iter:
currentcall = model.get(iter, 0)[0]
if (currentcall == callsign):
notfound = False
break
iter = model.iter_next(iter)
if (notfound):
self.messagecombo.prepend_text(callsign)
if (activate):
self.messagecombo.set_active(0)
def bulletin_check(self, callsign):
for currentcall in self.bulletins:
length = len(currentcall)
if (currentcall == callsign[0:length]):
return True
return False
def replyack(self, tocall):
replyack = ""
if (tocall in self.sent_acks):
id = "%s-%s" % (tocall, self.sent_acks[tocall])
if (time.time() - self.sent_acks[id] < 5400):
# less than 90 minutes ago
replyack = "ack%s" % self.sent_acks[tocall]
return replyack