# -*- coding: UTF-8 -*- # Copyright (C) 2008, Jack Zielke # 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 MAX_MSG_QUEUE = 10 MAX_PER_CALL_QUEUE = 2 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) self.queue_list = {} self.message_list = {} self.seen_bulletins = {} self.message_marks = {} 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(1, 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(1, 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.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.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.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.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.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.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\ndisplay 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() 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() 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") self.sock = None return False if not recv_data: self.status_write("Server closed connection.\n") self.sock = None return False else: # sometimes more than one packet comes in for packet in recv_data.split("\n"): if (len(packet) < 2): break # find uri's http or else www webaddy = packet.lower().find("http") if (webaddy != -1): packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:])) else: webaddy = packet.lower().find("www") if (webaddy != -1): packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:])) if (packet[0] == "#"): self.status_write("%s\n\n" % packet) else: cuthere = packet.find(":") self.status_write("%s\n" % packet[:cuthere+1]) self.status_write("%s\n\n" % packet[cuthere+1:]) self.msg_check(packet) 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, tocall, sequence): # should check for duplicate messages in the next 30 seconds and drop them # aprsd does this for us - but should do it here as well if (tocall in self.sent_acks): currentack = self.sent_acks[tocall] if (currentack == sequence): ackmessage = ":%s:ack%s" % (tocall, sequence) self.send_data(ackmessage) 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:] self.add_callsign(fromcall, False) bln_id = "%s-%s" % (fromcall, strippedtocall) if (not bln_id in self.seen_bulletins): self.seen_bulletins[bln_id] = 1 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:] else: sequence = data[thirdcheck + 1:sequence_end] replyack = data[sequence_end + 1:] if (len(replyack) > 1): ackid = "%s-%s" % (fromcall, replyack) if (ackid in self.recv_acks): if (self.recv_acks[ackid] == 0): count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid]) count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY) self.messagebuffer.delete(count_start, count_end[0]) self.send_msg_queue(fromcall) self.recv_acks[ackid] = 1 message = data[firstcheck+12:thirdcheck] # ackmessage = ":%s:ack%s" % (fromcall, sequence) ackmessage = ":%s:ack%s" % (fromcall, data[thirdcheck + 1:]) 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, tocall, sequence)) self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, tocall, sequence)) self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, tocall, sequence)) else: # TODO beep? 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:] if (message[0:3] == "ack"): sequence_end = message.find("}") if (sequence_end == -1): sequence = message[3:] else: sequence = message[3:sequence_end] ackid = "%s-%s" % (fromcall, sequence) if (ackid in self.recv_acks): if (self.recv_acks[ackid] == 0): count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid]) count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY) self.messagebuffer.delete(count_start, count_end[0]) self.send_msg_queue(fromcall) self.recv_acks[ackid] = 1 else: # TODO beep? 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): sendnow = False tocall = self.messagedest.get_text().upper() message = self.messagetext.get_text() if (message == ""): return False 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 # 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 # cancel message option - menu popup # is there a queue for this callsign? if (tocall not in self.queue_list): sendnow = True # add the message to the queue if (not self.msg_queue(tocall, sequence, message)): if (self.sequence > 0): self.sequence -= 1 return False # clear the message text box self.messagetext.set_text("") if (sendnow): self.send_msg_queue(tocall) 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): id = "%s-%s" % (tocall, sequence) if (self.recv_acks[id] == 1): return False else: replyack = self.replyack(tocall) self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack)) count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) count_end.forward_chars(len(str(count - 1)) + 1) self.messagebuffer.delete(count_start, count_end) iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) self.messagebuffer.insert(iter, " %i" % count) # start the next timer count += 1 if (count > MAXRETRIES): self.recv_acks[id] = 1 count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) count_end.forward_chars(len(str(count - 1)) + len(str(MAXRETRIES)) + 2) self.messagebuffer.delete(count_start, count_end) line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) line_end.forward_to_line_end() self.messagebuffer.insert(line_end, " * TIMEOUT *") self.send_msg_queue(tocall) 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[: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 = self.sent_acks[tocall] return replyack def msg_queue(self, call, sequence, message): if (len(self.message_list) >= MAX_MSG_QUEUE): self.message_write("Too many messages in queue.\n") return False id = "%s-%s" % (call, sequence) if (call in self.queue_list): if (len(self.queue_list[call]) >= MAX_PER_CALL_QUEUE): self.message_write("Too many messages to %s in queue.\n" % call) return False self.queue_list[call].append(sequence) self.message_list[id] = message else: self.queue_list[call] = [] self.queue_list[call].append(sequence) self.message_list[id] = message self.message_write("%s To:%s> %s * QUEUED *\n" % (time.strftime("%m/%d %H:%M", time.localtime()), call, message)) # if a message is received before the next lines are run there will be problems... iter = self.messagebuffer.get_iter_at_line(self.messagebuffer.get_line_count() - 2) iter.forward_chars(15 + len(call)) self.message_marks[id] = self.messagebuffer.create_mark(None, iter, True) # self.message_marks[id].set_visible(True) return True def send_msg_queue(self, call): if (call in self.queue_list): if (self.queue_list[call] == []): del self.queue_list[call] return False sequence = self.queue_list[call].pop(0) id = "%s-%s" % (call, sequence) message = self.message_list[id] del self.message_list[id] # send message isbulletin = self.bulletin_check(call) if (isbulletin): self.send_data(":%s:%s" % (call.ljust(9), message)) # TODO what are the correct timings for bulletins? gobject.timeout_add(600 * 1000, self.msg_timer, call, message, "", 2, 600) else: replyack = self.replyack(call) self.send_data(":%s:%s{%s}%s" % (call.ljust(9), message, sequence, replyack)) self.recv_acks["%s-%s" % (call, sequence)] = 0 gobject.timeout_add(7 * 1000, self.msg_timer, call, message, sequence, 2, 7) # clear "QUEUED" and add the counter line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) line_end.forward_to_line_end() queue_start.forward_to_line_end() queue_start.backward_chars(11) self.messagebuffer.delete(queue_start, line_end) iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) self.messagebuffer.insert(iter, " 1/%s" % MAXRETRIES)