diff --git a/aprsd.py b/aprsd.py index 83d2e88..0f96780 100644 --- a/aprsd.py +++ b/aprsd.py @@ -8,7 +8,7 @@ # APRS messages: # l(ocation) = descriptive location of calling station # w(eather) = temp, (hi/low) forecast, later forecast -# t(ime) = respond with the current time +# t(ime) = respond with the current time # f(ortune) = respond with a short fortune # -email_addr email text = send an email # -2 = display the last 2 emails received @@ -33,7 +33,7 @@ from email.mime.text import MIMEText import subprocess import datetime import calendar -from imapclient import IMAPClient, SEEN +from imapclient import IMAPClient, SEEN import email import threading import signal @@ -114,8 +114,8 @@ def resend_email(count): today = str(day) + "-" + month + "-" + str(year) global shortcuts - shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value - + shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value + server = IMAPClient('imap.yourdomain.com', use_uid=True) server.login('KM6XXX@yourdomain.org', 'yourpassword') select_info = server.select_folder('INBOX') @@ -158,18 +158,18 @@ def check_email_thread(): threading.Timer(55, check_email_thread).start() # how do we skip first run? global shortcuts - shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value - + shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value + date = datetime.datetime.now() month = date.strftime("%B")[:3] # Nov, Mar, Apr day = date.day year = date.year today = str(day) + "-" + month + "-" + str(year) - + server = IMAPClient('imap.yourdomain.com', use_uid=True) server.login('KM6XXX@yourdomain.org', 'yourpassword') select_info = server.select_folder('INBOX') - + messages = server.search(['SINCE', today]) #print("%d messages received today" % len(messages)) @@ -226,20 +226,20 @@ def send_message_thread(tocall, message, this_message_number, retry_count): global ack_dict line = USER + ">APRS::" + tocall + ":" + message + "{" + str(this_message_number) + "\n" for i in range(retry_count, 0, -1): - print "DEBUG: send_message_thread msg:ack combos are: " + print "DEBUG: send_message_thread msg:ack combos are: " pprint.pprint(ack_dict) if ack_dict[this_message_number] != 1: print "Sending message_______________ " + str(this_message_number) + "(Tx" + str(i) + ")" print "Raw : " + line, print "To : " + tocall print "Message : " + message - tn.write(line) + tn.write(line) sleeptime = (retry_count - i + 1) * 31 # decaying repeats, 31 to 93 second intervals time.sleep(sleeptime) else: break return -### end send_message_thread +### end send_message_thread def send_message(tocall, message): @@ -248,7 +248,7 @@ def send_message(tocall, message): retry_count = 3 if message_number > 98: # global message_number = 0 - message_number += 1 + message_number += 1 if len(ack_dict) > 90: # empty ack dict if it's really big, could result in key error later print "DEBUG: Length of ack dictionary is big at " + str(len(ack_dict)) + " clearing." ack_dict.clear() @@ -273,21 +273,21 @@ def process_message(line): m = re.search(searchstring, line) fullmessage = m.group(1) - ack_attached = re.search('(.*){([0-9A-Z]+)', fullmessage) # ack formats include: {1, {AB}, {12 + ack_attached = re.search('(.*){([0-9A-Z]+)', fullmessage) # ack formats include: {1, {AB}, {12 if ack_attached: # "{##" suffix means radio wants an ack back message = ack_attached.group(1) # message content ack_num = ack_attached.group(2) # suffix number to use in ack else: message = fullmessage ack_num = "0" # ack not requested, but lets send one as 0 - + print "Received message______________" print "Raw : " + line print "From : " + fromcall print "Message : " + message print "Msg number : " + str(ack_num) - return (fromcall, message, ack_num) + return (fromcall, message, ack_num) ### end process_message() @@ -295,24 +295,24 @@ def send_email(to_addr, content): print "Sending Email_________________" global shortcuts if to_addr in shortcuts: - print "To : " + to_addr , + print "To : " + to_addr , to_addr = shortcuts[to_addr] print " (" + to_addr + ")" subject = BASECALLSIGN #content = content + "\n\n(NOTE: reply with one line)" print "Subject : " + subject - print "Body : " + content + print "Body : " + content msg = MIMEText(content) - msg['Subject'] = subject + msg['Subject'] = subject msg['From'] = "KM6XXX@yourdomain.org" msg['To'] = to_addr s = smtplib.SMTP_SSL('smtp.yourdomain.com', 465) s.login("KM6XXX@yourdomain.org", "yourpassword") - try: + try: s.sendmail("KM6XXX@yourdomain.org", [to_addr], msg.as_string()) except Exception, e: - print "Sendmail Error!!!!!!!!!" + print "Sendmail Error!!!!!!!!!" s.quit() return(-1) s.quit() @@ -323,165 +323,167 @@ def send_email(to_addr, content): ### main() ### -try: - tn = telnetlib.Telnet(HOST, 14580) -except Exception, e: - print "Telnet session failed.\n" - sys.exit(-1) +def main(): + try: + tn = telnetlib.Telnet(HOST, 14580) + except Exception, e: + print "Telnet session failed.\n" + sys.exit(-1) -time.sleep(2) + time.sleep(2) -tn.write("user " + USER + " pass " + PASS + " vers aprsd 0.99\n" ) + tn.write("user " + USER + " pass " + PASS + " vers aprsd 0.99\n" ) -time.sleep(2) + time.sleep(2) -check_email_thread() # start email reader thread + check_email_thread() # start email reader thread -while True: - line = "" - try: - for char in tn.read_until("\n",100): - line = line + char - line = line.replace('\n', '') - print line - searchstring = '::' + USER - if re.search(searchstring, line): # is aprs message to us, not beacon, status, etc - (fromcall, message, ack) = process_message(line) - else: - message = "noise" - continue - - # ACK (ack##) - if re.search('^ack[0-9]+', message): - a = re.search('^ack([0-9]+)', message) # put message_number:1 in dict to record the ack - ack_dict.update({int(a.group(1)):1}) - continue - - # EMAIL (-) - elif re.search('^-.*', message): # is email command - searchstring = '^' + BASECALLSIGN + '.*' - if re.search(searchstring, fromcall): # only I can do email - r = re.search('^-([0-9])[0-9]*$', message) # digits only, first one is number of emails to resend - if r is not None: - resend_email(r.group(1)) - elif re.search('^-([A-Za-z0-9_\-\.@]+) (.*)', message): # -user@address.com body of email - a = re.search('^-([A-Za-z0-9_\-\.@]+) (.*)', message) # (same search again) - if a is not None: - to_addr = a.group(1) - content = a.group(2) - if content == 'mapme': # send recipient link to aprs.fi map - content = "Click for my location: http://aprs.fi/" + BASECALLSIGN - too_soon = 0 - now = time.time() - if ack in email_sent_dict: # see if we sent this msg number recently - timedelta = now - email_sent_dict[ack] - if ( timedelta < 300 ): # five minutes - too_soon = 1 - if not too_soon or ack == 0: - send_result = send_email(to_addr, content) - if send_result != 0: - send_message(fromcall, "-" + to_addr + " failed") - else: - #send_message(fromcall, "-" + to_addr + " sent") - if len(email_sent_dict) > 98: # clear email sent dictionary if somehow goes over 100 - print "DEBUG: email_sent_dict is big (" + str(len(email_sent_dict)) + ") clearing out." - email_sent_dict.clear() - email_sent_dict[ack] = now - else: - print "\nEmail for message number " + ack + " recently sent, not sending again.\n" - else: - send_message(fromcall, "Bad email address") + while True: + line = "" + try: + for char in tn.read_until("\n",100): + line = line + char + line = line.replace('\n', '') + print line + searchstring = '::' + USER + if re.search(searchstring, line): # is aprs message to us, not beacon, status, etc + (fromcall, message, ack) = process_message(line) + else: + message = "noise" + continue - # TIME (t) - elif re.search('^t', message): - stm = time.localtime() - h = stm.tm_hour - m = stm.tm_min - cur_time = fuzzy(h, m, 1) - reply = cur_time + " (" + str(h) + ":" + str(m).rjust(2, '0') + "PDT)" + " (" + message.rstrip() + ")" - thread = threading.Thread(target = send_message, args = (fromcall, reply)) - thread.start() - - # FORTUNE (f) - elif re.search('^f', message): - process = subprocess.Popen(['/usr/games/fortune', '-s', '-n 60'], stdout=subprocess.PIPE) - reply = process.communicate()[0] - send_message(fromcall, reply.rstrip()) + # ACK (ack##) + if re.search('^ack[0-9]+', message): + a = re.search('^ack([0-9]+)', message) # put message_number:1 in dict to record the ack + ack_dict.update({int(a.group(1)):1}) + continue - # PING (p) - elif re.search('^p', message): - stm = time.localtime() - h = stm.tm_hour - m = stm.tm_min - s = stm.tm_sec - reply = "Pong! " + str(h).zfill(2) + ":" + str(m).zfill(2) + ":" + str(s).zfill(2) - send_message(fromcall, reply.rstrip()) + # EMAIL (-) + elif re.search('^-.*', message): # is email command + searchstring = '^' + BASECALLSIGN + '.*' + if re.search(searchstring, fromcall): # only I can do email + r = re.search('^-([0-9])[0-9]*$', message) # digits only, first one is number of emails to resend + if r is not None: + resend_email(r.group(1)) + elif re.search('^-([A-Za-z0-9_\-\.@]+) (.*)', message): # -user@address.com body of email + a = re.search('^-([A-Za-z0-9_\-\.@]+) (.*)', message) # (same search again) + if a is not None: + to_addr = a.group(1) + content = a.group(2) + if content == 'mapme': # send recipient link to aprs.fi map + content = "Click for my location: http://aprs.fi/" + BASECALLSIGN + too_soon = 0 + now = time.time() + if ack in email_sent_dict: # see if we sent this msg number recently + timedelta = now - email_sent_dict[ack] + if ( timedelta < 300 ): # five minutes + too_soon = 1 + if not too_soon or ack == 0: + send_result = send_email(to_addr, content) + if send_result != 0: + send_message(fromcall, "-" + to_addr + " failed") + else: + #send_message(fromcall, "-" + to_addr + " sent") + if len(email_sent_dict) > 98: # clear email sent dictionary if somehow goes over 100 + print "DEBUG: email_sent_dict is big (" + str(len(email_sent_dict)) + ") clearing out." + email_sent_dict.clear() + email_sent_dict[ack] = now + else: + print "\nEmail for message number " + ack + " recently sent, not sending again.\n" + else: + send_message(fromcall, "Bad email address") - # LOCATION (l) "8 Miles E Auburn CA 1771' 38.91547,-120.99500 0.1h ago" - elif re.search('^l', message): - # get my last location, get descriptive name from weather service - try: - url = "http://api.aprs.fi/api/get?name=" + fromcall + "&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json" - response = urllib.urlopen(url) - aprs_data = json.loads(response.read()) - lat = aprs_data['entries'][0]['lat'] - lon = aprs_data['entries'][0]['lng'] - try: # altitude not always provided - alt = aprs_data['entries'][0]['altitude'] - except: - alt = 0 - altfeet = int(alt * 3.28084) - aprs_lasttime_seconds = aprs_data['entries'][0]['lasttime'] - aprs_lasttime_seconds = aprs_lasttime_seconds.encode('ascii',errors='ignore') #unicode to ascii - delta_seconds = time.time() - int(aprs_lasttime_seconds) - delta_hours = delta_seconds / 60 / 60 - url2 = "https://forecast.weather.gov/MapClick.php?lat=" + str(lat) + "&lon=" + str(lon) + "&FcstType=json" - response2 = urllib.urlopen(url2) - wx_data = json.loads(response2.read()) - reply = wx_data['location']['areaDescription'] + " " + str(altfeet) + "' " + str(lat) + "," + str(lon) + " " + str("%.1f" % round(delta_hours,1)) + "h ago" - reply = reply.encode('ascii',errors='ignore') # unicode to ascii - send_message(fromcall, reply.rstrip()) - except: - reply = "Unable to find you (send beacon?)" + # TIME (t) + elif re.search('^t', message): + stm = time.localtime() + h = stm.tm_hour + m = stm.tm_min + cur_time = fuzzy(h, m, 1) + reply = cur_time + " (" + str(h) + ":" + str(m).rjust(2, '0') + "PDT)" + " (" + message.rstrip() + ")" + thread = threading.Thread(target = send_message, args = (fromcall, reply)) + thread.start() + + # FORTUNE (f) + elif re.search('^f', message): + process = subprocess.Popen(['/usr/games/fortune', '-s', '-n 60'], stdout=subprocess.PIPE) + reply = process.communicate()[0] send_message(fromcall, reply.rstrip()) - # WEATHER (w) "42F(68F/48F) Haze. Tonight, Haze then Chance Rain." - elif re.search('^w', message): - # get my last location from aprsis then get weather from weather service - try: - url = "http://api.aprs.fi/api/get?name=" + fromcall + "&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json" - response = urllib.urlopen(url) - aprs_data = json.loads(response.read()) - lat = aprs_data['entries'][0]['lat'] - lon = aprs_data['entries'][0]['lng'] - url2 = "https://forecast.weather.gov/MapClick.php?lat=" + str(lat) + "&lon=" + str(lon) + "&FcstType=json" - response2 = urllib.urlopen(url2) - wx_data = json.loads(response2.read()) - reply = wx_data['currentobservation']['Temp'] + "F(" + wx_data['data']['temperature'][0] + "F/" + wx_data['data']['temperature'][1] + "F) " + wx_data['data']['weather'][0] + ". " + wx_data['time']['startPeriodName'][1] + ", " + wx_data['data']['weather'][1] + "." - reply = reply.encode('ascii',errors='ignore') # unicode to ascii + # PING (p) + elif re.search('^p', message): + stm = time.localtime() + h = stm.tm_hour + m = stm.tm_min + s = stm.tm_sec + reply = "Pong! " + str(h).zfill(2) + ":" + str(m).zfill(2) + ":" + str(s).zfill(2) send_message(fromcall, reply.rstrip()) - except: - reply = "Unable to find you (send beacon?)" + + # LOCATION (l) "8 Miles E Auburn CA 1771' 38.91547,-120.99500 0.1h ago" + elif re.search('^l', message): + # get my last location, get descriptive name from weather service + try: + url = "http://api.aprs.fi/api/get?name=" + fromcall + "&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json" + response = urllib.urlopen(url) + aprs_data = json.loads(response.read()) + lat = aprs_data['entries'][0]['lat'] + lon = aprs_data['entries'][0]['lng'] + try: # altitude not always provided + alt = aprs_data['entries'][0]['altitude'] + except: + alt = 0 + altfeet = int(alt * 3.28084) + aprs_lasttime_seconds = aprs_data['entries'][0]['lasttime'] + aprs_lasttime_seconds = aprs_lasttime_seconds.encode('ascii',errors='ignore') #unicode to ascii + delta_seconds = time.time() - int(aprs_lasttime_seconds) + delta_hours = delta_seconds / 60 / 60 + url2 = "https://forecast.weather.gov/MapClick.php?lat=" + str(lat) + "&lon=" + str(lon) + "&FcstType=json" + response2 = urllib.urlopen(url2) + wx_data = json.loads(response2.read()) + reply = wx_data['location']['areaDescription'] + " " + str(altfeet) + "' " + str(lat) + "," + str(lon) + " " + str("%.1f" % round(delta_hours,1)) + "h ago" + reply = reply.encode('ascii',errors='ignore') # unicode to ascii + send_message(fromcall, reply.rstrip()) + except: + reply = "Unable to find you (send beacon?)" + send_message(fromcall, reply.rstrip()) + + # WEATHER (w) "42F(68F/48F) Haze. Tonight, Haze then Chance Rain." + elif re.search('^w', message): + # get my last location from aprsis then get weather from weather service + try: + url = "http://api.aprs.fi/api/get?name=" + fromcall + "&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json" + response = urllib.urlopen(url) + aprs_data = json.loads(response.read()) + lat = aprs_data['entries'][0]['lat'] + lon = aprs_data['entries'][0]['lng'] + url2 = "https://forecast.weather.gov/MapClick.php?lat=" + str(lat) + "&lon=" + str(lon) + "&FcstType=json" + response2 = urllib.urlopen(url2) + wx_data = json.loads(response2.read()) + reply = wx_data['currentobservation']['Temp'] + "F(" + wx_data['data']['temperature'][0] + "F/" + wx_data['data']['temperature'][1] + "F) " + wx_data['data']['weather'][0] + ". " + wx_data['time']['startPeriodName'][1] + ", " + wx_data['data']['weather'][1] + "." + reply = reply.encode('ascii',errors='ignore') # unicode to ascii + send_message(fromcall, reply.rstrip()) + except: + reply = "Unable to find you (send beacon?)" + send_message(fromcall, reply) + + # USAGE + else: + reply = "usage: time, fortune, loc, weath, -emailaddr emailbody, -#(resend)" send_message(fromcall, reply) + time.sleep(1) # let any threads do their thing, then ack + send_ack(fromcall, ack) # send an ack last - # USAGE - else: - reply = "usage: time, fortune, loc, weath, -emailaddr emailbody, -#(resend)" - send_message(fromcall, reply) + except Exception, e: + print "Error in mainline loop:" + print "%s" % str(e) + print "Exiting." + #sys.exit(1) # merely a suggestion + os._exit(1) - time.sleep(1) # let any threads do their thing, then ack - send_ack(fromcall, ack) # send an ack last + # end while True + tn.close() + exit() - except Exception, e: - print "Error in mainline loop:" - print "%s" % str(e) - print "Exiting." - #sys.exit(1) # merely a suggestion - os._exit(1) - -# end while True -tn.close() - -exit() +if __name__ == "__main__": + main()