1
0
mirror of https://github.com/craigerl/aprsd.git synced 2025-06-25 05:25:21 -04:00

Merge pull request #11 from hemna/py3

Fixed all pep8 errors and some py3 errors
This commit is contained in:
Craig Lamparter 2020-12-05 08:13:27 -08:00 committed by GitHub
commit 901b17a94c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 304 additions and 267 deletions

View File

@ -1,18 +1,10 @@
#!/bin/python
import argparse import argparse
import logging import logging
import os
import select
import signal
import socket
import sys import sys
import time import time
import threading import socketserver
import Queue
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from telnetsrv.green import TelnetHandler, command
from aprsd import utils from aprsd import utils
@ -34,34 +26,9 @@ parser.add_argument("--ip",
default='127.0.0.1', default='127.0.0.1',
help="The IP to listen on ") help="The IP to listen on ")
args = parser.parse_args()
CONFIG = None CONFIG = None
LOG = logging.getLogger('ARPSSERVER') LOG = logging.getLogger('ARPSSERVER')
class MyAPRSServer(TelnetHandler):
@command('echo')
def command_echo(self, params):
LOG.debug("ECHO %s" % params)
self.writeresponse(' '.join(params))
@command('user')
def command_user(self, params):
LOG.debug("User auth command")
self.writeresponse('')
@command('quit')
def command_quit(self, params):
LOG.debug("quit called")
self.writeresponse('quitting')
os.kill(os.getpid(), signal.SIGINT)
def signal_handler(signal, frame):
LOG.info("Ctrl+C, exiting.")
#sys.exit(0) # thread ignores this
os._exit(0)
# Setup the logging faciility # Setup the logging faciility
# to disable logging to stdout, but still log to file # to disable logging to stdout, but still log to file
@ -83,7 +50,7 @@ def setup_logging(args):
log_formatter = logging.Formatter(fmt=log_format, log_formatter = logging.Formatter(fmt=log_format,
datefmt=date_format) datefmt=date_format)
fh = RotatingFileHandler('aprs-server.log', fh = RotatingFileHandler('aprs-server.log',
maxBytes=(10248576*5), maxBytes=(10248576 * 5),
backupCount=4) backupCount=4)
fh.setFormatter(log_formatter) fh.setFormatter(log_formatter)
LOG.addHandler(fh) LOG.addHandler(fh)
@ -94,98 +61,33 @@ def setup_logging(args):
LOG.addHandler(sh) LOG.addHandler(sh)
class ClientThread(threading.Thread): class MyAPRSTCPHandler(socketserver.BaseRequestHandler):
def __init__(self, msg_q, ip, port, conn, *args, **kwargs):
super(ClientThread, self).__init__()
self.msg_q = msg_q
self.ip = ip
self.port = port
self.conn = conn
LOG.info("[+] New thread started for %s:%s" % (ip, port))
def send_command(self, msg): def handle(self):
LOG.info("Sending command '%s'" % msg) # self.request is the TCP socket connected to the client
self.conn.send(msg) self.data = self.request.recv(1024).strip()
LOG.debug("{} wrote:".format(self.client_address[0]))
def run(self): LOG.debug(self.data)
while True: # just send back the same data, but upper-cased
LOG.debug("Wait for data") self.request.sendall(self.data.upper())
readable, writeable, exceptional = select.select([self.conn],
[], [],
1)
LOG.debug("select returned %s" % readable)
if readable:
data = self.conn.recv(2048)
LOG.info("got data '%s'" % data)
else:
try:
msg = self.msg_q.get(True, 0.05)
if msg:
LOG.info("Sending message '%s'" % msg)
self.conn.send(msg+"\n")
except Queue.Empty:
pass
class InputThread(threading.Thread): def main():
def __init__(self, msg_q): global CONFIG
super(InputThread, self).__init__() args = parser.parse_args()
self.msg_q = msg_q
LOG.info("User input thread started")
def run(self):
while True:
text = raw_input("Prompt> ")
LOG.debug("Got input '%s'" % text)
if text == 'quit':
LOG.info("Quitting Input Thread")
sys.exit(0)
else:
LOG.info("add '%s' to message Q" % text)
self.msg_q.put(text)
threads = []
def main(args):
global CONFIG, threads
setup_logging(args) setup_logging(args)
LOG.info("Test APRS server starting.") LOG.info("Test APRS server starting.")
time.sleep(1) time.sleep(1)
signal.signal(signal.SIGINT, signal_handler)
CONFIG = utils.parse_config(args) CONFIG = utils.parse_config(args)
msg_q = Queue.Queue()
tcpsock = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
tcpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
ip = CONFIG['aprs']['host'] ip = CONFIG['aprs']['host']
port = CONFIG['aprs']['port'] port = CONFIG['aprs']['port']
LOG.info("Start server listening on %s:%s" % (args.ip, args.port)) LOG.info("Start server listening on %s:%s" % (args.ip, args.port))
tcpsock.bind((ip,port))
in_t = None with socketserver.TCPServer((ip, port), MyAPRSTCPHandler) as server:
while True: server.serve_forever()
tcpsock.listen(4)
LOG.info("Waiting for incomming connections....")
(conn, (ip, port)) = tcpsock.accept()
newthread = ClientThread(msg_q, ip, port, conn)
newthread.start()
threads.append(newthread)
if not in_t:
in_t = InputThread(msg_q)
in_t.daemon = True
in_t.start()
in_t.join()
for t in threads:
t.join()
if __name__ == "__main__": if __name__ == "__main__":
main(args) main()

View File

@ -61,10 +61,10 @@ def fuzzy(hour, minute, degree=1):
if dmin == 0: if dmin == 0:
s1 = f1 s1 = f1
pos = pos - 1 pos = pos - 1
elif dmin <= base/2: elif dmin <= base / 2:
s1 = f2 s1 = f2
if minute < 30: if minute < 30:
pos = pos-1 pos = pos - 1
else: else:
s1 = f0 s1 = f0
if minute > 30: if minute > 30:
@ -118,4 +118,6 @@ def main():
print(fuzzy(h, m, deg)) print(fuzzy(h, m, deg))
return return
main()
if __name__ == "__main__":
main()

View File

@ -31,21 +31,22 @@ import socket
import pprint import pprint
import re import re
import signal import signal
import six
import smtplib import smtplib
import subprocess import subprocess
import sys import sys
#import telnetlib # import telnetlib
import threading import threading
import time import time
import urllib import urllib
from email.mime.text import MIMEText from email.mime.text import MIMEText
import imapclient
import imaplib
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
# external lib imports
from imapclient import IMAPClient, SEEN
# local imports here # local imports here
import aprsd
from aprsd.fuzzyclock import fuzzy from aprsd.fuzzyclock import fuzzy
from aprsd import utils from aprsd import utils
@ -96,7 +97,7 @@ parser.add_argument("--quiet",
args = parser.parse_args() args = parser.parse_args()
#def setup_connection(): # def setup_connection():
# global tn # global tn
# host = CONFIG['aprs']['host'] # host = CONFIG['aprs']['host']
# port = CONFIG['aprs']['port'] # port = CONFIG['aprs']['port']
@ -115,15 +116,16 @@ def setup_connection():
try: try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((CONFIG['aprs']['host'], 14580)) sock.connect((CONFIG['aprs']['host'], 14580))
sock.settimeout(300)
sock.settimeout(300)
connected = True connected = True
except Exception, e: except Exception as e:
print "Unable to connect to APRS-IS server.\n" print("Unable to connect to APRS-IS server.\n")
print str(e) print(str(e))
time.sleep(5) time.sleep(5)
continue continue
#os._exit(1) # os._exit(1)
sock_file = sock.makefile(mode='r', bufsize=0 ) sock_file = sock.makefile(mode='r')
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # disable nagle algorithm sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # disable nagle algorithm
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 512) # buffer size sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 512) # buffer size
@ -134,51 +136,153 @@ def signal_handler(signal, frame):
# sys.exit(0) # thread ignores this # sys.exit(0) # thread ignores this
os._exit(0) os._exit(0)
# end signal_handler
# end signal_handler
def parse_email(msgid, data, server): def parse_email(msgid, data, server):
envelope = data[b'ENVELOPE'] envelope = data[b'ENVELOPE']
f = re.search('([\.\w_-]+@[\.\w_-]+)', str(envelope.from_[0]) ) # email address match # email address match
# use raw string to avoid invalid escape secquence errors r"string here"
f = re.search(r"([\.\w_-]+@[\.\w_-]+)", str(envelope.from_[0]))
if f is not None: if f is not None:
from_addr = f.group(1) from_addr = f.group(1)
else: else:
from_addr = "noaddr" from_addr = "noaddr"
LOG.debug("Got a message from '{}'".format(from_addr))
m = server.fetch([msgid], ['RFC822']) m = server.fetch([msgid], ['RFC822'])
msg = email.message_from_string(m[msgid]['RFC822']) msg = email.message_from_string(m[msgid][b'RFC822'].decode())
if msg.is_multipart(): if msg.is_multipart():
text = "" text = ""
html = None html = None
body = "* unreadable msg received" # default in case body somehow isn't set below - happened once # default in case body somehow isn't set below - happened once
for part in msg.get_payload(): body = "* unreadable msg received"
if part.get_content_charset() is None: for part in msg.get_payload():
# We cannot know the character set, so return decoded "something" if part.get_content_charset() is None:
text = part.get_payload(decode=True) # We cannot know the character set,
continue # so return decoded "something"
text = part.get_payload(decode=True)
charset = part.get_content_charset() continue
if part.get_content_type() == 'text/plain':
text = unicode(part.get_payload(decode=True), str(charset), "ignore").encode('utf8', 'replace')
if part.get_content_type() == 'text/html':
html = unicode(part.get_payload(decode=True), str(charset), "ignore").encode('utf8', 'replace')
if text is not None:
body = text.strip() # strip removes white space fore and aft of string
else:
body = html.strip()
else:
if msg.get_content_charset() == None: # email.uscc.net sends no charset, blows up unicode function below
text = unicode(msg.get_payload(decode=True), 'US-ASCII', 'ignore').encode('utf8', 'replace')
else:
text = unicode(msg.get_payload(decode=True), msg.get_content_charset(), 'ignore').encode('utf8', 'replace')
body = text.strip()
body = re.sub('<[^<]+?>', '', body) # strip all html tags
body = body.replace("\n", " ").replace("\r", " ") # strip CR/LF, make it one line, .rstrip fails at this
return(body, from_addr)
## end parse_email
charset = part.get_content_charset()
if part.get_content_type() == 'text/plain':
text = six.text_type(
part.get_payload(decode=True), str(charset),
"ignore").encode('utf8', 'replace')
if part.get_content_type() == 'text/html':
html = six.text_type(
part.get_payload(decode=True),
str(charset),
"ignore").encode('utf8', 'replace')
if text is not None:
# strip removes white space fore and aft of string
body = text.strip()
else:
body = html.strip()
else:
# email.uscc.net sends no charset, blows up unicode function below
if msg.get_content_charset() is None:
text = six.text_type(
msg.get_payload(decode=True),
'US-ASCII',
'ignore').encode('utf8', 'replace')
else:
text = six.text_type(
msg.get_payload(decode=True),
msg.get_content_charset(),
'ignore').encode('utf8', 'replace')
body = text.strip()
# strip all html tags
body = body.decode()
body = re.sub('<[^<]+?>', '', body)
# strip CR/LF, make it one line, .rstrip fails at this
body = body.replace("\n", " ").replace("\r", " ")
return(body, from_addr)
# end parse_email
def _imap_connect():
imap_port = CONFIG['imap'].get('port', 143)
use_ssl = CONFIG['imap'].get('use_ssl', False)
host = CONFIG['imap']['host']
msg = ("{}{}:{}".format(
'TLS ' if use_ssl else '',
host,
imap_port
))
LOG.debug("Connect to IMAP host {} with user '{}'".
format(msg, CONFIG['imap']['login']))
try:
server = imapclient.IMAPClient(CONFIG['imap']['host'], port=imap_port,
use_uid=True, ssl=use_ssl)
except Exception:
LOG.error("Failed to connect IMAP server")
return
LOG.debug("Connected to IMAP host {}".format(msg))
try:
server.login(CONFIG['imap']['login'], CONFIG['imap']['password'])
except (imaplib.IMAP4.error, Exception) as e:
msg = getattr(e, 'message', repr(e))
LOG.error("Failed to login {}".format(msg))
return
LOG.debug("Logged in to IMAP, selecting INBOX")
server.select_folder('INBOX')
return server
def _smtp_connect():
host = CONFIG['smtp']['host']
smtp_port = CONFIG['smtp']['port']
use_ssl = CONFIG['smtp'].get('use_ssl', False)
msg = ("{}{}:{}".format(
'SSL ' if use_ssl else '',
host,
smtp_port
))
LOG.debug("Connect to SMTP host {} with user '{}'".
format(msg, CONFIG['imap']['login']))
try:
if use_ssl:
server = smtplib.SMTP_SSL(host=host, port=smtp_port)
else:
server = smtplib.SMTP(host=host, port=smtp_port)
except Exception:
LOG.error("Couldn't connect to SMTP Server")
return
LOG.debug("Connected to smtp host {}".format(msg))
try:
server.login(CONFIG['smtp']['login'], CONFIG['smtp']['password'])
except Exception:
LOG.error("Couldn't connect to SMTP Server")
return
LOG.debug("Logged into SMTP server {}".format(msg))
return server
def validate_email():
"""function to simply ensure we can connect to email services.
This helps with failing early during startup.
"""
LOG.info("Checking IMAP configuration")
imap_server = _imap_connect()
LOG.info("Checking SMTP configuration")
smtp_server = _smtp_connect()
if imap_server and smtp_server:
return True
else:
return False
def resend_email(count, fromcall): def resend_email(count, fromcall):
@ -192,12 +296,11 @@ def resend_email(count, fromcall):
# swap key/value # swap key/value
shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()]) shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()])
LOG.debug("resend_email: Connect to IMAP host '%s' with user '%s'" % try:
(CONFIG['imap']['host'], server = _imap_connect()
CONFIG['imap']['login'])) except Exception as e:
server = IMAPClient(CONFIG['imap']['host'], use_uid=True) LOG.exception("Failed to Connect to IMAP. Cannot resend email ", e)
server.login(CONFIG['imap']['login'], CONFIG['imap']['password']) return
select_info = server.select_folder('INBOX')
messages = server.search(['SINCE', today]) messages = server.search(['SINCE', today])
LOG.debug("%d messages received today" % len(messages)) LOG.debug("%d messages received today" % len(messages))
@ -211,7 +314,7 @@ def resend_email(count, fromcall):
# one at a time, otherwise order is random # one at a time, otherwise order is random
(body, from_addr) = parse_email(msgid, data, server) (body, from_addr) = parse_email(msgid, data, server)
# unset seen flag, will stay bold in email client # unset seen flag, will stay bold in email client
server.remove_flags(msgid, [SEEN]) server.remove_flags(msgid, [imapclient.SEEN])
if from_addr in shortcuts_inverted: if from_addr in shortcuts_inverted:
# reverse lookup of a shortcut # reverse lookup of a shortcut
from_addr = shortcuts_inverted[from_addr] from_addr = shortcuts_inverted[from_addr]
@ -242,53 +345,49 @@ def check_email_thread(check_email_delay):
while True: while True:
# threading.Timer(55, check_email_thread).start() # threading.Timer(55, check_email_thread).start()
LOG.debug("Top of check_email_thread.")
time.sleep(check_email_delay) time.sleep(check_email_delay)
shortcuts = CONFIG['shortcuts'] shortcuts = CONFIG['shortcuts']
# swap key/value # swap key/value
shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()]) shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()])
date = datetime.datetime.now() date = datetime.datetime.now()
month = date.strftime("%B")[:3] # Nov, Mar, Apr month = date.strftime("%B")[:3] # Nov, Mar, Apr
day = date.day day = date.day
year = date.year year = date.year
today = "%s-%s-%s" % (day, month, year) today = "%s-%s-%s" % (day, month, year)
LOG.debug("Connect to IMAP host '%s' with user '%s'" % server = None
(CONFIG['imap']['host'],
CONFIG['imap']['login']))
try: try:
server = IMAPClient(CONFIG['imap']['host'], use_uid=True, timeout=5) server = _imap_connect()
server.login(CONFIG['imap']['login'], CONFIG['imap']['password']) except Exception as e:
except Exception: LOG.exception("Failed to get IMAP server Can't check email.", e)
LOG.exception("Failed to login with IMAP server")
#return if not server:
continue continue
server.select_folder('INBOX')
messages = server.search(['SINCE', today]) messages = server.search(['SINCE', today])
LOG.debug("%d messages received today" % len(messages)) LOG.debug("{} messages received today".format(len(messages)))
for msgid, data in server.fetch(messages, ['ENVELOPE']).items(): for msgid, data in server.fetch(messages, ['ENVELOPE']).items():
envelope = data[b'ENVELOPE'] envelope = data[b'ENVELOPE']
LOG.debug('ID:%d "%s" (%s)' % LOG.debug('ID:%d "%s" (%s)' %
(msgid, envelope.subject.decode(), envelope.date)) (msgid, envelope.subject.decode(), envelope.date))
f = re.search('([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)', f = re.search(r"'([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)",
str(envelope.from_[0])) str(envelope.from_[0]))
if f is not None: if f is not None:
from_addr = f.group(1) from_addr = f.group(1)
else: else:
from_addr = "noaddr" from_addr = "noaddr"
if "APRS" not in server.get_flags(msgid)[msgid]: if "APRS" not in server.get_flags(msgid)[msgid]:
# if msg not flagged as sent via aprs # if msg not flagged as sent via aprs
server.fetch([msgid], ['RFC822']) server.fetch([msgid], ['RFC822'])
(body, from_addr) = parse_email(msgid, data, server) (body, from_addr) = parse_email(msgid, data, server)
# unset seen flag, will stay bold in email client # unset seen flag, will stay bold in email client
server.remove_flags(msgid, [SEEN]) server.remove_flags(msgid, [imapclient.SEEN])
if from_addr in shortcuts_inverted: if from_addr in shortcuts_inverted:
# reverse lookup of a shortcut # reverse lookup of a shortcut
@ -301,7 +400,7 @@ def check_email_thread(check_email_delay):
# flag message as sent via aprs # flag message as sent via aprs
server.add_flags(msgid, ['APRS']) server.add_flags(msgid, ['APRS'])
# unset seen flag, will stay bold in email client # unset seen flag, will stay bold in email client
server.remove_flags(msgid, [SEEN]) server.remove_flags(msgid, [imapclient.SEEN])
server.logout() server.logout()
@ -310,14 +409,15 @@ def check_email_thread(check_email_delay):
def send_ack_thread(tocall, ack, retry_count): def send_ack_thread(tocall, ack, retry_count):
tocall = tocall.ljust(9) # pad to nine chars tocall = tocall.ljust(9) # pad to nine chars
line = "%s>APRS::%s:ack%s\n" % (CONFIG['aprs']['login'], tocall, ack) line = ("{}>APRS::{}:ack{}\n".format(
CONFIG['aprs']['login'], tocall, ack))
for i in range(retry_count, 0, -1): for i in range(retry_count, 0, -1):
LOG.info("Sending ack __________________ Tx(%s)" % i) LOG.info("Sending ack __________________ Tx({})".format(i))
LOG.info("Raw : %s" % line) LOG.info("Raw : {}".format(line))
LOG.info("To : %s" % tocall) LOG.info("To : {}".format(tocall))
LOG.info("Ack number : %s" % ack) LOG.info("Ack number : {}".format(ack))
#tn.write(line) # tn.write(line)
sock.send(line) sock.send(line.encode())
# aprs duplicate detection is 30 secs? # aprs duplicate detection is 30 secs?
# (21 only sends first, 28 skips middle) # (21 only sends first, 28 skips middle)
time.sleep(31) time.sleep(31)
@ -328,6 +428,7 @@ def send_ack_thread(tocall, ack, retry_count):
def send_ack(tocall, ack): def send_ack(tocall, ack):
retry_count = 3 retry_count = 3
thread = threading.Thread(target=send_ack_thread, thread = threading.Thread(target=send_ack_thread,
name="send_ack",
args=(tocall, ack, retry_count)) args=(tocall, ack, retry_count))
thread.start() thread.start()
return() return()
@ -336,19 +437,27 @@ def send_ack(tocall, ack):
def send_message_thread(tocall, message, this_message_number, retry_count): def send_message_thread(tocall, message, this_message_number, retry_count):
global ack_dict global ack_dict
line = (CONFIG['aprs']['login'] + ">APRS::" + tocall + ":" + message + # line = (CONFIG['aprs']['login'] + ">APRS::" + tocall + ":" + message
"{" + str(this_message_number) + "\n") # + "{" + str(this_message_number) + "\n")
line = ("{}>APRS::{}:{}{{{}\n".format(
CONFIG['aprs']['login'],
tocall, message,
str(this_message_number),
))
for i in range(retry_count, 0, -1): for i in range(retry_count, 0, -1):
LOG.debug("DEBUG: send_message_thread msg:ack combos are: ") LOG.debug("DEBUG: send_message_thread msg:ack combos are: ")
LOG.debug(pprint.pformat(ack_dict)) LOG.debug(pprint.pformat(ack_dict))
if ack_dict[this_message_number] != 1: if ack_dict[this_message_number] != 1:
LOG.info("Sending message_______________ " + LOG.info("Sending message_______________ {}(Tx{})"
str(this_message_number) + "(Tx" + str(i) + ")") .format(
LOG.info("Raw : " + line) str(this_message_number),
LOG.info("To : " + tocall) str(i)
LOG.info("Message : " + message) ))
#tn.write(line) LOG.info("Raw : {}".format(line))
sock.send(line) LOG.info("To : {}".format(tocall))
LOG.info("Message : {}".format(message))
# tn.write(line)
sock.send(line.encode())
# decaying repeats, 31 to 93 second intervals # decaying repeats, 31 to 93 second intervals
sleeptime = (retry_count - i + 1) * 31 sleeptime = (retry_count - i + 1) * 31
time.sleep(sleeptime) time.sleep(sleeptime)
@ -383,6 +492,7 @@ def send_message(tocall, message):
message = message[:67] message = message[:67]
thread = threading.Thread( thread = threading.Thread(
target=send_message_thread, target=send_message_thread,
name="send_message",
args=(tocall, message, message_number, retry_count)) args=(tocall, message, message_number, retry_count))
thread.start() thread.start()
return() return()
@ -436,18 +546,17 @@ def send_email(to_addr, content):
msg['Subject'] = subject msg['Subject'] = subject
msg['From'] = CONFIG['smtp']['login'] msg['From'] = CONFIG['smtp']['login']
msg['To'] = to_addr msg['To'] = to_addr
s = smtplib.SMTP_SSL(CONFIG['smtp']['host'], server = _smtp_connect()
CONFIG['smtp']['port']) if server:
s.login(CONFIG['smtp']['login'], try:
CONFIG['smtp']['password']) server.sendmail(CONFIG['smtp']['login'], [to_addr], msg.as_string())
try: except Exception as e:
s.sendmail(CONFIG['smtp']['login'], [to_addr], msg.as_string()) msg = getattr(e, 'message', repr(e))
except Exception: LOG.error("Sendmail Error!!!! '{}'", msg)
LOG.exception("Sendmail Error!!!!!!!!!") server.quit()
s.quit() return(-1)
return(-1) server.quit()
s.quit() return(0)
return(0)
# end send_email # end send_email
@ -465,13 +574,13 @@ def setup_logging(args):
log_level = levels[args.loglevel] log_level = levels[args.loglevel]
LOG.setLevel(log_level) LOG.setLevel(log_level)
log_format = ("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]" log_format = ("%(asctime)s [%(threadName)-12s] [%(levelname)-5.5s]"
" %(message)s") " %(message)s")
date_format = '%m/%d/%Y %I:%M:%S %p' date_format = '%m/%d/%Y %I:%M:%S %p'
log_formatter = logging.Formatter(fmt=log_format, log_formatter = logging.Formatter(fmt=log_format,
datefmt=date_format) datefmt=date_format)
fh = RotatingFileHandler(CONFIG['aprs']['logfile'], fh = RotatingFileHandler(CONFIG['aprs']['logfile'],
maxBytes=(10248576*5), maxBytes=(10248576 * 5),
backupCount=4) backupCount=4)
fh.setFormatter(log_formatter) fh.setFormatter(log_formatter)
LOG.addHandler(fh) LOG.addHandler(fh)
@ -488,23 +597,31 @@ def main(args=args):
CONFIG = utils.parse_config(args) CONFIG = utils.parse_config(args)
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
LOG.info("APRSD Started")
LOG.debug(CONFIG)
setup_logging(args) setup_logging(args)
LOG.info("APRSD Started version: {}".format(aprsd.__version__))
time.sleep(2) time.sleep(2)
setup_connection() setup_connection()
valid = validate_email()
if not valid:
LOG.error("Failed to validate email config options")
sys.exit(-1)
user = CONFIG['aprs']['login'] user = CONFIG['aprs']['login']
password = CONFIG['aprs']['password'] password = CONFIG['aprs']['password']
LOG.info("LOGIN to APRSD with user '%s'" % user)
#tn.write("user %s pass %s vers aprsd 0.99\n" % (user, password)) LOG.debug("LOGIN to APRSD with user '%s'" % user)
sock.send("user %s pass %s vers https://github.com/craigerl/aprsd 2.00\n" % (user, password)) msg = ("user {} pass {} vers aprsd {}\n".format(user, password,
aprsd.__version__))
sock.send(msg.encode())
time.sleep(2) time.sleep(2)
check_email_delay = 60 # initial email check interval check_email_delay = 60 # initial email check interval
checkemailthread = threading.Thread(target=check_email_thread, args=(check_email_delay, )) # args must be tuple checkemailthread = threading.Thread(target=check_email_thread,
name="check_email",
args=(check_email_delay, )) # args must be tuple
checkemailthread.start() checkemailthread.start()
LOG.info("Start main loop") LOG.info("Start main loop")
@ -512,7 +629,8 @@ def main(args=args):
line = "" line = ""
try: try:
line = sock_file.readline().strip() line = sock_file.readline().strip()
LOG.info(line) if line:
LOG.info(line)
searchstring = '::%s' % user searchstring = '::%s' % user
# is aprs message to us, not beacon, status, etc # is aprs message to us, not beacon, status, etc
if re.search(searchstring, line): if re.search(searchstring, line):
@ -529,31 +647,40 @@ def main(args=args):
continue continue
# EMAIL (-) # EMAIL (-)
elif re.search('^-.*', message): # is email command # is email command
elif re.search('^-.*', message):
searchstring = '^' + CONFIG['ham']['callsign'] + '.*' searchstring = '^' + CONFIG['ham']['callsign'] + '.*'
if re.search(searchstring, fromcall): # only I can do email # only I can do email
r = re.search('^-([0-9])[0-9]*$', message) # digits only, first one is number of emails to resend if re.search(searchstring, fromcall):
# digits only, first one is number of emails to resend
r = re.search('^-([0-9])[0-9]*$', message)
if r is not None: if r is not None:
resend_email(r.group(1), fromcall) resend_email(r.group(1), fromcall)
elif re.search('^-([A-Za-z0-9_\-\.@]+) (.*)', message): # -user@address.com body of email # -user@address.com body of email
a = re.search('^-([A-Za-z0-9_\-\.@]+) (.*)', message) # (same search again) elif re.search(r"^-([A-Za-z0-9_\-\.@]+) (.*)", message):
# (same search again)
a = re.search(r"^-([A-Za-z0-9_\-\.@]+) (.*)", message)
if a is not None: if a is not None:
to_addr = a.group(1) to_addr = a.group(1)
content = a.group(2) content = a.group(2)
if content == 'mapme': # send recipient link to aprs.fi map # send recipient link to aprs.fi map
content = "Click for my location: http://aprs.fi/" + CONFIG['ham']['callsign'] if content == 'mapme':
content = (
"Click for my location: http://aprs.fi/{}".
format(CONFIG['ham']['callsign']))
too_soon = 0 too_soon = 0
now = time.time() now = time.time()
if ack in email_sent_dict: # see if we sent this msg number recently # see if we sent this msg number recently
if ack in email_sent_dict:
timedelta = now - email_sent_dict[ack] timedelta = now - email_sent_dict[ack]
if ( timedelta < 300 ): # five minutes if (timedelta < 300): # five minutes
too_soon = 1 too_soon = 1
if not too_soon or ack == 0: if not too_soon or ack == 0:
send_result = send_email(to_addr, content) send_result = send_email(to_addr, content)
if send_result != 0: if send_result != 0:
send_message(fromcall, "-" + to_addr + " failed") send_message(fromcall, "-" + to_addr + " failed")
else: else:
#send_message(fromcall, "-" + to_addr + " sent") # send_message(fromcall, "-" + to_addr + " sent")
if len(email_sent_dict) > 98: # clear email sent dictionary if somehow goes over 100 if len(email_sent_dict) > 98: # clear email sent dictionary if somehow goes over 100
LOG.debug("DEBUG: email_sent_dict is big (" + str(len(email_sent_dict)) + ") clearing out.") LOG.debug("DEBUG: email_sent_dict is big (" + str(len(email_sent_dict)) + ") clearing out.")
email_sent_dict.clear() email_sent_dict.clear()
@ -570,7 +697,9 @@ def main(args=args):
m = stm.tm_min m = stm.tm_min
cur_time = fuzzy(h, m, 1) cur_time = fuzzy(h, m, 1)
reply = cur_time + " (" + str(h) + ":" + str(m).rjust(2, '0') + "PDT)" + " (" + message.rstrip() + ")" reply = cur_time + " (" + str(h) + ":" + str(m).rjust(2, '0') + "PDT)" + " (" + message.rstrip() + ")"
thread = threading.Thread(target = send_message, args = (fromcall, reply)) thread = threading.Thread(target=send_message,
name="send_message",
args=(fromcall, reply))
thread.start() thread.start()
# FORTUNE (f) # FORTUNE (f)
@ -592,33 +721,33 @@ def main(args=args):
elif re.search('^[lL]', message): elif re.search('^[lL]', message):
# get last location of a callsign, get descriptive name from weather service # get last location of a callsign, get descriptive name from weather service
try: try:
a = re.search('^.*\s+(.*)', message) # optional second argument is a callsign to search a = re.search(r"'^.*\s+(.*)", message) # optional second argument is a callsign to search
if a is not None: if a is not None:
searchcall = a.group(1) searchcall = a.group(1)
searchcall = searchcall.upper() searchcall = searchcall.upper()
else: else:
searchcall = fromcall # if no second argument, search for calling station searchcall = fromcall # if no second argument, search for calling station
url = "http://api.aprs.fi/api/get?name=" + searchcall + "&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json" url = "http://api.aprs.fi/api/get?name=" + searchcall + "&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json"
response = urllib.urlopen(url) response = urllib.urlopen(url)
aprs_data = json.loads(response.read()) aprs_data = json.loads(response.read())
lat = aprs_data['entries'][0]['lat'] lat = aprs_data['entries'][0]['lat']
lon = aprs_data['entries'][0]['lng'] lon = aprs_data['entries'][0]['lng']
try: # altitude not always provided try: # altitude not always provided
alt = aprs_data['entries'][0]['altitude'] alt = aprs_data['entries'][0]['altitude']
except: except Exception:
alt = 0 alt = 0
altfeet = int(alt * 3.28084) altfeet = int(alt * 3.28084)
aprs_lasttime_seconds = aprs_data['entries'][0]['lasttime'] aprs_lasttime_seconds = aprs_data['entries'][0]['lasttime']
aprs_lasttime_seconds = aprs_lasttime_seconds.encode('ascii',errors='ignore') #unicode to ascii aprs_lasttime_seconds = aprs_lasttime_seconds.encode('ascii', errors='ignore') # unicode to ascii
delta_seconds = time.time() - int(aprs_lasttime_seconds) delta_seconds = time.time() - int(aprs_lasttime_seconds)
delta_hours = delta_seconds / 60 / 60 delta_hours = delta_seconds / 60 / 60
url2 = "https://forecast.weather.gov/MapClick.php?lat=" + str(lat) + "&lon=" + str(lon) + "&FcstType=json" url2 = "https://forecast.weather.gov/MapClick.php?lat=" + str(lat) + "&lon=" + str(lon) + "&FcstType=json"
response2 = urllib.urlopen(url2) response2 = urllib.urlopen(url2)
wx_data = json.loads(response2.read()) wx_data = json.loads(response2.read())
reply = searchcall + ": " + wx_data['location']['areaDescription'] + " " + str(altfeet) + "' " + str(lat) + "," + str(lon) + " " + str("%.1f" % round(delta_hours,1)) + "h ago" reply = searchcall + ": " + 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 reply = reply.encode('ascii', errors='ignore') # unicode to ascii
send_message(fromcall, reply.rstrip()) send_message(fromcall, reply.rstrip())
except: except Exception:
reply = "Unable to find station " + searchcall + ". Sending beacons?" reply = "Unable to find station " + searchcall + ". Sending beacons?"
send_message(fromcall, reply.rstrip()) send_message(fromcall, reply.rstrip())
@ -666,20 +795,22 @@ def main(args=args):
except Exception as e: except Exception as e:
LOG.error("Error in mainline loop:") LOG.error("Error in mainline loop:")
LOG.error("%s" % str(e)) LOG.error("%s" % str(e))
if str(e) == "timed out" or str(e) == "Temporary failure in name resolution" or str(e) == "Network is unreachable":
LOG.error("Attempting to reconnect.") if (str(e) == "timed out" or str(e) == "Temporary failure in name resolution" or str(e) == "Network is unreachable"):
sock.shutdown(0) LOG.error("Attempting to reconnect.")
sock.close() sock.shutdown(0)
setup_connection() sock.close()
sock.send("user %s pass %s vers https://github.com/craigerl/aprsd 2.00\n" % (user, password)) setup_connection()
continue sock.send("user %s pass %s vers https://github.com/craigerl/aprsd 2.00\n" % (user, password))
#LOG.error("Exiting.") continue
#os._exit(1) # LOG.error("Exiting.")
# os._exit(1)
time.sleep(5) time.sleep(5)
continue # don't know what failed, so wait and then continue main loop again continue # don't know what failed, so wait and then continue main loop again
# end while True # end while True
#tn.close() # tn.close()
sock.shutdown(0) sock.shutdown(0)
sock.close() sock.close()

View File

@ -53,7 +53,7 @@ def get_config():
config_file = os.path.expanduser("~/.aprsd/config.yml") config_file = os.path.expanduser("~/.aprsd/config.yml")
if os.path.exists(config_file): if os.path.exists(config_file):
with open(config_file, "r") as stream: with open(config_file, "r") as stream:
config = yaml.load(stream) config = yaml.load(stream, Loader=yaml.FullLoader)
return config return config
else: else:
log.critical("%s is missing, please create config file" % config_file) log.critical("%s is missing, please create config file" % config_file)

View File

@ -1,3 +1,4 @@
pbr pbr
imapclient imapclient
pyyaml pyyaml
six

View File

@ -21,6 +21,7 @@ packages =
[entry_points] [entry_points]
console_scripts = console_scripts =
aprsd = aprsd.main:main aprsd = aprsd.main:main
fake_aprs = aprsd.fake_aprs:main
[build_sphinx] [build_sphinx]
source-dir = doc/source source-dir = doc/source

View File

@ -23,7 +23,7 @@ commands = sphinx-build -b html docs/source docs/html
[testenv:pep8] [testenv:pep8]
commands = commands =
flake8 {posargs} aprsd test flake8 {posargs} aprsd
[testenv:fast8] [testenv:fast8]
basepython = python3 basepython = python3
@ -35,5 +35,5 @@ passenv = FAST8_NUM_COMMITS
[flake8] [flake8]
show-source = True show-source = True
ignore = E713 ignore = E713,E501
exclude = .venv,.git,.tox,dist,doc,.ropeproject exclude = .venv,.git,.tox,dist,doc,.ropeproject