mirror of
https://github.com/craigerl/aprsd.git
synced 2025-06-24 21:15:18 -04:00
commit
3e555746ca
@ -17,98 +17,105 @@
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
def fuzzy(hour, minute, degree=1):
|
def fuzzy(hour, minute, degree=1):
|
||||||
'''Implements the fuzzy clock.
|
'''Implements the fuzzy clock.
|
||||||
returns the the string that spells out the time - hour:minute
|
returns the the string that spells out the time - hour:minute
|
||||||
Supports two degrees of fuzziness. Set with degree = 1 or degree = 2
|
Supports two degrees of fuzziness. Set with degree = 1 or degree = 2
|
||||||
When degree = 1, time is in quantum of 5 minutes.
|
When degree = 1, time is in quantum of 5 minutes.
|
||||||
When degree = 2, time is in quantum of 15 minutes.'''
|
When degree = 2, time is in quantum of 15 minutes.'''
|
||||||
|
|
||||||
if degree<=0 or degree>2:
|
if degree <= 0 or degree > 2:
|
||||||
print 'Please use a degree of 1 or 2. Using fuzziness degree=1'
|
print('Please use a degree of 1 or 2. Using fuzziness degree=1')
|
||||||
degree = 1
|
degree = 1
|
||||||
|
|
||||||
begin = 'It\'s '
|
begin = 'It\'s '
|
||||||
|
|
||||||
f0 = 'almost '
|
f0 = 'almost '
|
||||||
f1 = 'exactly '
|
f1 = 'exactly '
|
||||||
f2 = 'around '
|
f2 = 'around '
|
||||||
|
|
||||||
b0 = ' past '
|
b0 = ' past '
|
||||||
b1 = ' to '
|
b1 = ' to '
|
||||||
|
|
||||||
hourList = ('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve')
|
hourList = ('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight',
|
||||||
|
'Nine', 'Ten', 'Eleven', 'Twelve')
|
||||||
|
|
||||||
s1 = s2 = s3 = s4 = ''
|
s1 = s2 = s3 = s4 = ''
|
||||||
base = 5
|
base = 5
|
||||||
|
|
||||||
if degree == 1:
|
if degree == 1:
|
||||||
base = 5
|
base = 5
|
||||||
val = ('Five', 'Ten', 'Quarter', 'Twenty', 'Twenty-Five', 'Half')
|
val = ('Five', 'Ten', 'Quarter', 'Twenty', 'Twenty-Five', 'Half')
|
||||||
elif degree == 2:
|
elif degree == 2:
|
||||||
base = 15
|
base = 15
|
||||||
val = ('Quarter', 'Half')
|
val = ('Quarter', 'Half')
|
||||||
|
|
||||||
dmin = minute % base # to find whether we have to use 'almost', 'exactly' or 'around'
|
# to find whether we have to use 'almost', 'exactly' or 'around'
|
||||||
if minute > 30:
|
dmin = minute % base
|
||||||
pos = int((60 - minute) / base) #position in the tuple 'val'
|
if minute > 30:
|
||||||
else:
|
pos = int((60 - minute) / base) # position in the tuple 'val'
|
||||||
pos = int(minute / base)
|
else:
|
||||||
|
pos = int(minute / base)
|
||||||
|
|
||||||
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:
|
||||||
pos = pos -1
|
pos = pos - 1
|
||||||
|
|
||||||
s2 = val[pos]
|
s2 = val[pos]
|
||||||
|
|
||||||
if minute <= base/2: # Case like "It's around/exactly Ten"
|
if minute <= base / 2:
|
||||||
s2 = s3 = ''
|
# Case like "It's around/exactly Ten"
|
||||||
s4 = hourList[hour - 12 - 1]
|
s2 = s3 = ''
|
||||||
elif minute >= 60-base/2: # Case like "It's almost Ten"
|
s4 = hourList[hour - 12 - 1]
|
||||||
s2 = s3 = ''
|
elif minute >= 60 - base / 2:
|
||||||
s4 = hourList[hour - 12]
|
# Case like "It's almost Ten"
|
||||||
else: # Other cases with all words, like "It's around Quarter past One"
|
s2 = s3 = ''
|
||||||
if minute>30:
|
s4 = hourList[hour - 12]
|
||||||
s3 = b1 # to
|
else:
|
||||||
s4 = hourList[hour - 12]
|
# Other cases with all words, like "It's around Quarter past One"
|
||||||
else:
|
if minute > 30:
|
||||||
s3 = b0 # past
|
s3 = b1 # to
|
||||||
s4 = hourList[hour - 12 - 1]
|
s4 = hourList[hour - 12]
|
||||||
|
else:
|
||||||
|
s3 = b0 # past
|
||||||
|
s4 = hourList[hour - 12 - 1]
|
||||||
|
|
||||||
|
return begin + s1 + s2 + s3 + s4
|
||||||
|
|
||||||
return begin + s1 + s2 + s3 + s4
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
deg = 1
|
deg = 1
|
||||||
stm = time.localtime()
|
stm = time.localtime()
|
||||||
h = stm.tm_hour
|
h = stm.tm_hour
|
||||||
m = stm.tm_min
|
m = stm.tm_min
|
||||||
|
|
||||||
if len(sys.argv)>=2:
|
if len(sys.argv) >= 2:
|
||||||
try:
|
try:
|
||||||
deg = int(sys.argv[1])
|
deg = int(sys.argv[1])
|
||||||
except:
|
except Exception:
|
||||||
print 'Please use a degree of 1 or 2. Using fuzziness degree=1'
|
print('Please use a degree of 1 or 2. Using fuzziness degree=1')
|
||||||
|
|
||||||
if len(sys.argv)>=3:
|
if len(sys.argv) >= 3:
|
||||||
tm = sys.argv[2].split(':')
|
tm = sys.argv[2].split(':')
|
||||||
try:
|
try:
|
||||||
h = int(tm[0])
|
h = int(tm[0])
|
||||||
m = int(tm[1])
|
m = int(tm[1])
|
||||||
if h<0 or h>23 or m<0 or m>59:
|
if h < 0 or h > 23 or m < 0 or m > 59:
|
||||||
raise Exception
|
raise Exception
|
||||||
except:
|
except Exception:
|
||||||
print 'Bad time entered. Using the system time.'
|
print('Bad time entered. Using the system time.')
|
||||||
h = stm.tm_hour
|
h = stm.tm_hour
|
||||||
m = stm.tm_min
|
m = stm.tm_min
|
||||||
print fuzzy(h, m, deg)
|
print(fuzzy(h, m, deg))
|
||||||
return
|
return
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
353
aprsd/main.py
353
aprsd/main.py
@ -46,7 +46,7 @@ from imapclient import IMAPClient, SEEN
|
|||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
from aprsd.fuzzyclock import fuzzy
|
from aprsd.fuzzyclock import fuzzy
|
||||||
import utils
|
from aprsd import utils
|
||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
LOG = logging.getLogger('APRSD')
|
LOG = logging.getLogger('APRSD')
|
||||||
@ -58,7 +58,7 @@ CONFIG = None
|
|||||||
# HOST = "noam.aprs2.net" # north america tier2 servers round robin
|
# HOST = "noam.aprs2.net" # north america tier2 servers round robin
|
||||||
# USER = "KM6XXX-9" # callsign of this aprs client with SSID
|
# USER = "KM6XXX-9" # callsign of this aprs client with SSID
|
||||||
# PASS = "99999" # google how to generate this
|
# PASS = "99999" # google how to generate this
|
||||||
# BASECALLSIGN = "KM6XXX" # callsign of radio in the field to which we send email
|
# BASECALLSIGN = "KM6XXX" # callsign of radio in the field to send email
|
||||||
# shortcuts = {
|
# shortcuts = {
|
||||||
# "aa" : "5551239999@vtext.com",
|
# "aa" : "5551239999@vtext.com",
|
||||||
# "cl" : "craiglamparter@somedomain.org",
|
# "cl" : "craiglamparter@somedomain.org",
|
||||||
@ -66,9 +66,18 @@ CONFIG = None
|
|||||||
# }
|
# }
|
||||||
|
|
||||||
# globals - tell me a better way to update data being used by threads
|
# globals - tell me a better way to update data being used by threads
|
||||||
email_sent_dict = {} # message_number:time combos so we don't resend the same email in five mins {int:int}
|
|
||||||
ack_dict = {} # message_nubmer:ack combos so we stop sending a message after an ack from radio {int:int}
|
# message_number:time combos so we don't resend the same email in
|
||||||
message_number = 0 # current aprs radio message number, increments for each message we send over rf {int}
|
# five mins {int:int}
|
||||||
|
email_sent_dict = {}
|
||||||
|
|
||||||
|
# message_nubmer:ack combos so we stop sending a message after an
|
||||||
|
# ack from radio {int:int}
|
||||||
|
ack_dict = {}
|
||||||
|
|
||||||
|
# current aprs radio message number, increments for each message we
|
||||||
|
# send over rf {int}
|
||||||
|
message_number = 0
|
||||||
|
|
||||||
# global telnet connection object
|
# global telnet connection object
|
||||||
tn = None
|
tn = None
|
||||||
@ -93,69 +102,82 @@ def setup_connection():
|
|||||||
LOG.debug("Setting up telnet connection to '%s:%s'" % (host, port))
|
LOG.debug("Setting up telnet connection to '%s:%s'" % (host, port))
|
||||||
try:
|
try:
|
||||||
tn = telnetlib.Telnet(host, port)
|
tn = telnetlib.Telnet(host, port)
|
||||||
except Exception, e:
|
except Exception:
|
||||||
LOG.exception("Telnet session failed.")
|
LOG.exception("Telnet session failed.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(signal, frame):
|
def signal_handler(signal, frame):
|
||||||
LOG.info("Ctrl+C, exiting.")
|
LOG.info("Ctrl+C, exiting.")
|
||||||
#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']
|
||||||
#print('ID:%d "%s" (%s)' % (msgid, envelope.subject.decode(), envelope.date ))
|
# print('ID:%d "%s" (%s)' %
|
||||||
f = re.search('([\.\w_-]+@[\.\w_-]+)', str(envelope.from_[0]) ) # email address match
|
# (msgid, envelope.subject.decode(), envelope.date))
|
||||||
if f is not None:
|
# email address match
|
||||||
from_addr = f.group(1)
|
f = re.search('([\.\w_-]+@[\.\w_-]+)', str(envelope.from_[0]))
|
||||||
else:
|
if f is not None:
|
||||||
from_addr = "noaddr"
|
from_addr = f.group(1)
|
||||||
m = server.fetch([msgid], ['RFC822'])
|
else:
|
||||||
msg = email.message_from_string(m[msgid]['RFC822'])
|
from_addr = "noaddr"
|
||||||
if msg.is_multipart():
|
m = server.fetch([msgid], ['RFC822'])
|
||||||
text = ""
|
msg = email.message_from_string(m[msgid]['RFC822'])
|
||||||
html = None
|
if msg.is_multipart():
|
||||||
body = "* unreadable msg received" # default in case body somehow isn't set below - happened once
|
text = ""
|
||||||
|
html = None
|
||||||
|
|
||||||
|
# default in case body somehow isn't set below - happened once
|
||||||
|
body = "* unreadable msg received"
|
||||||
for part in msg.get_payload():
|
for part in msg.get_payload():
|
||||||
if part.get_content_charset() is None:
|
if part.get_content_charset() is None:
|
||||||
# We cannot know the character set, so return decoded "something"
|
# We cannot know the character set, so return decoded "something"
|
||||||
text = part.get_payload(decode=True)
|
text = part.get_payload(decode=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
charset = part.get_content_charset()
|
charset = part.get_content_charset()
|
||||||
|
|
||||||
if part.get_content_type() == 'text/plain':
|
if part.get_content_type() == 'text/plain':
|
||||||
text = unicode(part.get_payload(decode=True), str(charset), "ignore").encode('utf8', 'replace')
|
text = unicode(part.get_payload(decode=True), str(charset),
|
||||||
|
"ignore").encode('utf8', 'replace')
|
||||||
|
|
||||||
if part.get_content_type() == 'text/html':
|
if part.get_content_type() == 'text/html':
|
||||||
html = unicode(part.get_payload(decode=True), str(charset), "ignore").encode('utf8', 'replace')
|
html = unicode(part.get_payload(decode=True), str(charset),
|
||||||
|
"ignore").encode('utf8', 'replace')
|
||||||
|
|
||||||
if text is not None:
|
if text is not None:
|
||||||
body = text.strip() # strip removes white space fore and aft of string
|
# strip removes white space fore and aft of string
|
||||||
else:
|
body = text.strip()
|
||||||
body = html.strip()
|
else:
|
||||||
else:
|
body = html.strip()
|
||||||
text = unicode(msg.get_payload(decode=True), msg.get_content_charset(), 'ignore').encode('utf8', 'replace')
|
else:
|
||||||
body = text.strip()
|
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
|
# strip all html tags
|
||||||
body = body.replace("\n", " ").replace("\r", " ") # strip CR/LF, make it one line, .rstrip fails at this
|
body = re.sub('<[^<]+?>', '', body)
|
||||||
return(body, from_addr)
|
|
||||||
## end parse_email
|
# 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 resend_email(count):
|
def resend_email(count):
|
||||||
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 = str(day) + "-" + month + "-" + str(year)
|
today = "%s-%s-%s" % (day, month, year)
|
||||||
|
|
||||||
shortcuts = CONFIG['shortcuts']
|
shortcuts = CONFIG['shortcuts']
|
||||||
shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value
|
# swap key/value
|
||||||
|
shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()])
|
||||||
|
|
||||||
LOG.debug("resend_email: Connect to IMAP host '%s' with user '%s'" %
|
LOG.debug("resend_email: Connect to IMAP host '%s' with user '%s'" %
|
||||||
(CONFIG['imap']['host'],
|
(CONFIG['imap']['host'],
|
||||||
@ -172,12 +194,16 @@ def resend_email(count):
|
|||||||
messages.sort(reverse=True)
|
messages.sort(reverse=True)
|
||||||
del messages[int(count):] # only the latest "count" messages
|
del messages[int(count):] # only the latest "count" messages
|
||||||
for message in messages:
|
for message in messages:
|
||||||
for msgid, data in list(server.fetch(message, ['ENVELOPE']).items()): # one at a time, otherwise order is random
|
for msgid, data in list(server.fetch(message, ['ENVELOPE']).items()):
|
||||||
|
# one at a time, otherwise order is random
|
||||||
(body, from_addr) = parse_email(msgid, data, server)
|
(body, from_addr) = parse_email(msgid, data, server)
|
||||||
server.remove_flags(msgid, [SEEN]) # unset seen flag, will stay bold in email client
|
# unset seen flag, will stay bold in email client
|
||||||
if from_addr in shortcuts_inverted: # reverse lookup of a shortcut
|
server.remove_flags(msgid, [SEEN])
|
||||||
|
if from_addr in shortcuts_inverted:
|
||||||
|
# reverse lookup of a shortcut
|
||||||
from_addr = shortcuts_inverted[from_addr]
|
from_addr = shortcuts_inverted[from_addr]
|
||||||
reply = "-" + from_addr + " * " + body # asterisk indicates a resend
|
# asterisk indicates a resend
|
||||||
|
reply = "-" + from_addr + " * " + body
|
||||||
send_message(fromcall, reply)
|
send_message(fromcall, reply)
|
||||||
msgexists = True
|
msgexists = True
|
||||||
|
|
||||||
@ -186,29 +212,36 @@ def resend_email(count):
|
|||||||
h = stm.tm_hour
|
h = stm.tm_hour
|
||||||
m = stm.tm_min
|
m = stm.tm_min
|
||||||
s = stm.tm_sec
|
s = stm.tm_sec
|
||||||
# append time as a kind of serial number to prevent FT1XDR from thinking this is a duplicate message.
|
# append time as a kind of serial number to prevent FT1XDR from
|
||||||
# The FT1XDR pretty much ignores the aprs message number in this regard. The FTM400 gets it right.
|
# thinking this is a duplicate message.
|
||||||
reply = "No new msg " + str(h).zfill(2) + ":" + str(m).zfill(2) + ":" + str(s).zfill(2)
|
# The FT1XDR pretty much ignores the aprs message number in this
|
||||||
|
# regard. The FTM400 gets it right.
|
||||||
|
reply = "No new msg %s:%s:%s" % (str(h).zfill(2),
|
||||||
|
str(m).zfill(2),
|
||||||
|
str(s).zfill(2))
|
||||||
send_message(fromcall, reply)
|
send_message(fromcall, reply)
|
||||||
|
|
||||||
server.logout()
|
server.logout()
|
||||||
### end resend_email()
|
# end resend_email()
|
||||||
|
|
||||||
|
|
||||||
def check_email_thread():
|
def check_email_thread():
|
||||||
# print "Email thread disabled."
|
# print "Email thread disabled."
|
||||||
# return
|
# return
|
||||||
|
|
||||||
LOG.debug("Starting Email thread")
|
LOG.debug("Starting Email thread")
|
||||||
threading.Timer(55, check_email_thread).start() # how do we skip first run?
|
# how do we skip first run?
|
||||||
|
threading.Timer(55, check_email_thread).start()
|
||||||
|
|
||||||
shortcuts = CONFIG['shortcuts']
|
shortcuts = CONFIG['shortcuts']
|
||||||
shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value
|
# swap key/value
|
||||||
|
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 = str(day) + "-" + month + "-" + str(year)
|
today = "%s-%s-%s" % (day, month, year)
|
||||||
|
|
||||||
LOG.debug("Connect to IMAP host '%s' with user '%s'" %
|
LOG.debug("Connect to IMAP host '%s' with user '%s'" %
|
||||||
(CONFIG['imap']['host'],
|
(CONFIG['imap']['host'],
|
||||||
@ -232,72 +265,82 @@ def check_email_thread():
|
|||||||
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('([[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 msg not flagged as sent via aprs
|
if "APRS" not in server.get_flags(msgid)[msgid]:
|
||||||
m = server.fetch([msgid], ['RFC822'])
|
# if msg not flagged as sent via aprs
|
||||||
|
server.fetch([msgid], ['RFC822'])
|
||||||
(body, from_addr) = parse_email(msgid, data, server)
|
(body, from_addr) = parse_email(msgid, data, server)
|
||||||
server.remove_flags(msgid, [SEEN]) # unset seen flag, will stay bold in email client
|
# unset seen flag, will stay bold in email client
|
||||||
|
server.remove_flags(msgid, [SEEN])
|
||||||
|
|
||||||
if from_addr in shortcuts_inverted: # reverse lookup of a shortcut
|
if from_addr in shortcuts_inverted:
|
||||||
|
# reverse lookup of a shortcut
|
||||||
from_addr = shortcuts_inverted[from_addr]
|
from_addr = shortcuts_inverted[from_addr]
|
||||||
|
|
||||||
reply = "-" + from_addr + " " + body
|
reply = "-" + from_addr + " " + body
|
||||||
#print "Sending message via aprs: " + reply
|
# print "Sending message via aprs: " + reply
|
||||||
send_message(CONFIG['ham']['callsign'], reply) #radio
|
# radio
|
||||||
server.add_flags(msgid, ['APRS']) #flag message as sent via aprs
|
send_message(CONFIG['ham']['callsign'], reply)
|
||||||
server.remove_flags(msgid, [SEEN]) #unset seen flag, will stay bold in email client
|
# flag message as sent via aprs
|
||||||
|
server.add_flags(msgid, ['APRS'])
|
||||||
|
# unset seen flag, will stay bold in email client
|
||||||
|
server.remove_flags(msgid, [SEEN])
|
||||||
|
|
||||||
server.logout()
|
server.logout()
|
||||||
### end check_email()
|
# end check_email()
|
||||||
|
|
||||||
|
|
||||||
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 = CONFIG['aprs']['login'] + ">APRS::" + tocall + ":ack" + str(ack) + "\n"
|
line = "%s>APRS::%s:ack%s\n" % (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(" + str(i) + ")")
|
LOG.info("Sending ack __________________ Tx(%s)" % i)
|
||||||
LOG.info("Raw : " + line)
|
LOG.info("Raw : %s" % line)
|
||||||
LOG.info("To : " + tocall)
|
LOG.info("To : %s" % tocall)
|
||||||
LOG.info("Ack number : " + str(ack))
|
LOG.info("Ack number : %s" % ack)
|
||||||
tn.write(line)
|
tn.write(line)
|
||||||
time.sleep(31) # aprs duplicate detection is 30 secs? (21 only sends first, 28 skips middle)
|
# aprs duplicate detection is 30 secs?
|
||||||
return()
|
# (21 only sends first, 28 skips middle)
|
||||||
### end_send_ack_thread
|
time.sleep(31)
|
||||||
|
return()
|
||||||
|
# end_send_ack_thread
|
||||||
|
|
||||||
|
|
||||||
def send_ack(tocall, ack):
|
def send_ack(tocall, ack):
|
||||||
retry_count = 3
|
retry_count = 3
|
||||||
thread = threading.Thread(target = send_ack_thread, args = (tocall, ack, retry_count))
|
thread = threading.Thread(target=send_ack_thread,
|
||||||
thread.start()
|
args=(tocall, ack, retry_count))
|
||||||
return()
|
thread.start()
|
||||||
### end send_ack()
|
return()
|
||||||
|
# end send_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")
|
||||||
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_______________ " +
|
||||||
str(this_message_number) + "(Tx" + str(i) + ")")
|
str(this_message_number) + "(Tx" + str(i) + ")")
|
||||||
LOG.info("Raw : " + line)
|
LOG.info("Raw : " + line)
|
||||||
LOG.info("To : " + tocall)
|
LOG.info("To : " + tocall)
|
||||||
LOG.info("Message : " + message)
|
LOG.info("Message : " + message)
|
||||||
tn.write(line)
|
tn.write(line)
|
||||||
sleeptime = (retry_count - i + 1) * 31 # decaying repeats, 31 to 93 second intervals
|
# decaying repeats, 31 to 93 second intervals
|
||||||
time.sleep(sleeptime)
|
sleeptime = (retry_count - i + 1) * 31
|
||||||
else:
|
time.sleep(sleeptime)
|
||||||
break
|
else:
|
||||||
return
|
break
|
||||||
### end send_message_thread
|
return
|
||||||
|
# end send_message_thread
|
||||||
|
|
||||||
|
|
||||||
def send_message(tocall, message):
|
def send_message(tocall, message):
|
||||||
@ -307,39 +350,50 @@ def send_message(tocall, message):
|
|||||||
if message_number > 98: # global
|
if message_number > 98: # global
|
||||||
message_number = 0
|
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
|
if len(ack_dict) > 90:
|
||||||
LOG.debug("DEBUG: Length of ack dictionary is big at " + str(len(ack_dict)) + " clearing.")
|
# empty ack dict if it's really big, could result in key error later
|
||||||
|
LOG.debug("DEBUG: Length of ack dictionary is big at %s clearing." %
|
||||||
|
len(ack_dict))
|
||||||
ack_dict.clear()
|
ack_dict.clear()
|
||||||
LOG.debug(pprint.pformat(ack_dict))
|
LOG.debug(pprint.pformat(ack_dict))
|
||||||
LOG.debug("DEBUG: Cleared ack dictionary, ack_dict length is now " + str(len(ack_dict)) + ".")
|
LOG.debug("DEBUG: Cleared ack dictionary, ack_dict length is now %s." %
|
||||||
|
len(ack_dict))
|
||||||
ack_dict[message_number] = 0 # clear ack for this message number
|
ack_dict[message_number] = 0 # clear ack for this message number
|
||||||
tocall = tocall.ljust(9) # pad to nine chars
|
tocall = tocall.ljust(9) # pad to nine chars
|
||||||
message = message[:67] # max? ftm400 displays 64, raw msg shows 74
|
|
||||||
# and ftm400-send is max 64. setting this to
|
# max? ftm400 displays 64, raw msg shows 74
|
||||||
# 67 displays 64 on the ftm400. (+3 {01 suffix)
|
# and ftm400-send is max 64. setting this to
|
||||||
# feature req: break long ones into two msgs
|
# 67 displays 64 on the ftm400. (+3 {01 suffix)
|
||||||
|
# feature req: break long ones into two msgs
|
||||||
|
message = message[:67]
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(
|
||||||
target = send_message_thread,
|
target=send_message_thread,
|
||||||
args = (tocall, message, message_number, retry_count))
|
args=(tocall, message, message_number, retry_count))
|
||||||
thread.start()
|
thread.start()
|
||||||
return()
|
return()
|
||||||
### end send_message()
|
# end send_message()
|
||||||
|
|
||||||
|
|
||||||
def process_message(line):
|
def process_message(line):
|
||||||
f = re.search('^(.*)>', line)
|
f = re.search('^(.*)>', line)
|
||||||
fromcall = f.group(1)
|
fromcall = f.group(1)
|
||||||
searchstring = '::' + CONFIG['aprs']['login'] + '[ ]*:(.*)' # verify this, callsign is padded out with spaces to colon
|
searchstring = '::%s[ ]*:(.*)' % CONFIG['aprs']['login']
|
||||||
|
# verify this, callsign is padded out with spaces to colon
|
||||||
m = re.search(searchstring, line)
|
m = re.search(searchstring, line)
|
||||||
fullmessage = m.group(1)
|
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)
|
||||||
if ack_attached: # "{##" suffix means radio wants an ack back
|
# ack formats include: {1, {AB}, {12
|
||||||
message = ack_attached.group(1) # message content
|
if ack_attached:
|
||||||
ack_num = ack_attached.group(2) # suffix number to use in ack
|
# "{##" suffix means radio wants an ack back
|
||||||
|
# message content
|
||||||
|
message = ack_attached.group(1)
|
||||||
|
# suffix number to use in ack
|
||||||
|
ack_num = ack_attached.group(2)
|
||||||
else:
|
else:
|
||||||
message = fullmessage
|
message = fullmessage
|
||||||
ack_num = "0" # ack not requested, but lets send one as 0
|
# ack not requested, but lets send one as 0
|
||||||
|
ack_num = "0"
|
||||||
|
|
||||||
LOG.info("Received message______________")
|
LOG.info("Received message______________")
|
||||||
LOG.info("Raw : " + line)
|
LOG.info("Raw : " + line)
|
||||||
@ -348,7 +402,7 @@ def process_message(line):
|
|||||||
LOG.info("Msg number : " + str(ack_num))
|
LOG.info("Msg number : " + str(ack_num))
|
||||||
|
|
||||||
return (fromcall, message, ack_num)
|
return (fromcall, message, ack_num)
|
||||||
### end process_message()
|
# end process_message()
|
||||||
|
|
||||||
|
|
||||||
def send_email(to_addr, content):
|
def send_email(to_addr, content):
|
||||||
@ -379,7 +433,7 @@ def send_email(to_addr, content):
|
|||||||
return(-1)
|
return(-1)
|
||||||
s.quit()
|
s.quit()
|
||||||
return(0)
|
return(0)
|
||||||
### end send_email
|
# end send_email
|
||||||
|
|
||||||
|
|
||||||
# Setup the logging faciility
|
# Setup the logging faciility
|
||||||
@ -413,7 +467,7 @@ def setup_logging(args):
|
|||||||
LOG.addHandler(sh)
|
LOG.addHandler(sh)
|
||||||
|
|
||||||
|
|
||||||
### main() ###
|
# main() ###
|
||||||
def main(args=args):
|
def main(args=args):
|
||||||
global CONFIG
|
global CONFIG
|
||||||
|
|
||||||
@ -429,7 +483,7 @@ def main(args=args):
|
|||||||
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)
|
LOG.info("LOGIN to APRSD with user '%s'" % user)
|
||||||
tn.write("user %s pass %s vers aprsd 0.99\n" % (user, password) )
|
tn.write("user %s pass %s vers aprsd 0.99\n" % (user, password))
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
check_email_thread() # start email reader thread
|
check_email_thread() # start email reader thread
|
||||||
@ -438,11 +492,11 @@ def main(args=args):
|
|||||||
while True:
|
while True:
|
||||||
line = ""
|
line = ""
|
||||||
try:
|
try:
|
||||||
for char in tn.read_until("\n",100):
|
for char in tn.read_until("\n", 100):
|
||||||
line = line + char
|
line = line + char
|
||||||
line = line.replace('\n', '')
|
line = line.replace('\n', '')
|
||||||
LOG.info(line)
|
LOG.info(line)
|
||||||
searchstring = '::' + 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):
|
||||||
(fromcall, message, ack) = process_message(line)
|
(fromcall, message, ack) = process_message(line)
|
||||||
@ -454,7 +508,7 @@ def main(args=args):
|
|||||||
if re.search('^ack[0-9]+', message):
|
if re.search('^ack[0-9]+', message):
|
||||||
# put message_number:1 in dict to record the ack
|
# put message_number:1 in dict to record the ack
|
||||||
a = re.search('^ack([0-9]+)', message)
|
a = re.search('^ack([0-9]+)', message)
|
||||||
ack_dict.update({int(a.group(1)):1})
|
ack_dict.update({int(a.group(1)): 1})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# EMAIL (-)
|
# EMAIL (-)
|
||||||
@ -539,7 +593,7 @@ def main(args=args):
|
|||||||
response2 = urllib.urlopen(url2)
|
response2 = urllib.urlopen(url2)
|
||||||
wx_data = json.loads(response2.read())
|
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 = 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:
|
||||||
reply = "Unable to find you (send beacon?)"
|
reply = "Unable to find you (send beacon?)"
|
||||||
@ -547,36 +601,51 @@ def main(args=args):
|
|||||||
|
|
||||||
# WEATHER (w) "42F(68F/48F) Haze. Tonight, Haze then Chance Rain."
|
# WEATHER (w) "42F(68F/48F) Haze. Tonight, Haze then Chance Rain."
|
||||||
elif re.search('^w', message):
|
elif re.search('^w', message):
|
||||||
# get my last location from aprsis then get weather from weather service
|
# get my last location from aprsis then get weather from
|
||||||
|
# weather service
|
||||||
try:
|
try:
|
||||||
url = "http://api.aprs.fi/api/get?name=" + fromcall + "&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json"
|
url = ("http://api.aprs.fi/api/get?"
|
||||||
|
"&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json"
|
||||||
|
"&name=%s" % fromcall)
|
||||||
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']
|
||||||
url2 = "https://forecast.weather.gov/MapClick.php?lat=" + str(lat) + "&lon=" + str(lon) + "&FcstType=json"
|
url2 = ("https://forecast.weather.gov/MapClick.php?lat=%s"
|
||||||
|
"&lon=%s&FcstType=json" % (lat, lon))
|
||||||
response2 = urllib.urlopen(url2)
|
response2 = urllib.urlopen(url2)
|
||||||
wx_data = json.loads(response2.read())
|
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 = "%sF(%sF/%sF) %s. %s, %s." % (
|
||||||
reply = reply.encode('ascii',errors='ignore') # unicode to ascii
|
wx_data['currentobservation']['Temp'],
|
||||||
|
wx_data['data']['temperature'][0],
|
||||||
|
wx_data['data']['temperature'][1],
|
||||||
|
wx_data['data']['weather'][0],
|
||||||
|
wx_data['time']['startPeriodName'][1],
|
||||||
|
wx_data['data']['weather'][1])
|
||||||
|
|
||||||
|
# unicode to ascii
|
||||||
|
reply = reply.encode('ascii', errors='ignore')
|
||||||
send_message(fromcall, reply.rstrip())
|
send_message(fromcall, reply.rstrip())
|
||||||
except:
|
except Exception:
|
||||||
reply = "Unable to find you (send beacon?)"
|
reply = "Unable to find you (send beacon?)"
|
||||||
send_message(fromcall, reply)
|
send_message(fromcall, reply)
|
||||||
|
|
||||||
# USAGE
|
# USAGE
|
||||||
else:
|
else:
|
||||||
reply = "usage: time, fortune, loc, weath, -emailaddr emailbody, -#(resend)"
|
reply = ("usage: time, fortune, loc, weath, -emailaddr "
|
||||||
|
"emailbody, -#(resend)")
|
||||||
send_message(fromcall, reply)
|
send_message(fromcall, reply)
|
||||||
|
|
||||||
time.sleep(1) # let any threads do their thing, then ack
|
# let any threads do their thing, then ack
|
||||||
send_ack(fromcall, ack) # send an ack last
|
time.sleep(1)
|
||||||
|
# send an ack last
|
||||||
|
send_ack(fromcall, ack)
|
||||||
|
|
||||||
except Exception, 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))
|
||||||
LOG.error("Exiting.")
|
LOG.error("Exiting.")
|
||||||
#sys.exit(1) # merely a suggestion
|
# sys.exit(1) # merely a suggestion
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
# end while True
|
# end while True
|
||||||
|
@ -36,6 +36,7 @@ imap:
|
|||||||
|
|
||||||
log = logging.getLogger('APRSD')
|
log = logging.getLogger('APRSD')
|
||||||
|
|
||||||
|
|
||||||
def env(*vars, **kwargs):
|
def env(*vars, **kwargs):
|
||||||
"""This returns the first environment variable set.
|
"""This returns the first environment variable set.
|
||||||
if none are non-empty, defaults to '' or keyword arg default
|
if none are non-empty, defaults to '' or keyword arg default
|
||||||
@ -55,10 +56,12 @@ def get_config():
|
|||||||
config = yaml.load(stream)
|
config = yaml.load(stream)
|
||||||
return config
|
return config
|
||||||
else:
|
else:
|
||||||
log.critical("%s is missing, please create a config file" % config_file)
|
log.critical("%s is missing, please create config file" % config_file)
|
||||||
print("\nCopy to ~/.aprsd/config.yml and edit\n\nSample config:\n %s" % example_config)
|
print("\nCopy to ~/.aprsd/config.yml and edit\n\nSample config:\n %s"
|
||||||
|
% example_config)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
# This method tries to parse the config yaml file
|
# This method tries to parse the config yaml file
|
||||||
# and consume the settings.
|
# and consume the settings.
|
||||||
# If the required params don't exist,
|
# If the required params don't exist,
|
||||||
|
1
test-requirements.txt
Normal file
1
test-requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
flake8
|
25
tools/fast8.sh
Executable file
25
tools/fast8.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
NUM_COMMITS=${FAST8_NUM_COMMITS:-1}
|
||||||
|
|
||||||
|
if [[ $NUM_COMMITS = "smart" ]]; then
|
||||||
|
# Run on all commits not submitted yet
|
||||||
|
# (sort of -- only checks vs. "master" since this is easy)
|
||||||
|
NUM_COMMITS=$(git cherry master | wc -l)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Checking last $NUM_COMMITS commits."
|
||||||
|
|
||||||
|
cd $(dirname "$0")/..
|
||||||
|
CHANGED=$(git diff --name-only HEAD~${NUM_COMMITS} | tr '\n' ' ')
|
||||||
|
|
||||||
|
# Skip files that don't exist
|
||||||
|
# (have been git rm'd)
|
||||||
|
CHECK=""
|
||||||
|
for FILE in $CHANGED; do
|
||||||
|
if [ -f "$FILE" ]; then
|
||||||
|
CHECK="$CHECK $FILE"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
diff -u --from-file /dev/null $CHECK | flake8 --diff
|
39
tox.ini
Normal file
39
tox.ini
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
[tox]
|
||||||
|
minversion = 1.6
|
||||||
|
skipdist = True
|
||||||
|
envlist = py27,py36,py37,fast8,pep8,cover,docs
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
usedevelop = True
|
||||||
|
install_command = pip install {opts} {packages}
|
||||||
|
|
||||||
|
deps = -r{toxinidir}/requirements.txt
|
||||||
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
commands =
|
||||||
|
pytest {posargs}
|
||||||
|
|
||||||
|
[testenv:cover]
|
||||||
|
commands =
|
||||||
|
pytest --cov=aprsd
|
||||||
|
|
||||||
|
[testenv:docs]
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = sphinx-build -b html docs/source docs/html
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
commands =
|
||||||
|
flake8 {posargs} aprsd test
|
||||||
|
|
||||||
|
[testenv:fast8]
|
||||||
|
basepython = python3
|
||||||
|
# Use same environment directory as pep8 env to save space and install time
|
||||||
|
envdir = {toxworkdir}/pep8
|
||||||
|
commands =
|
||||||
|
{toxinidir}/tools/fast8.sh
|
||||||
|
passenv = FAST8_NUM_COMMITS
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
show-source = True
|
||||||
|
ignore = E713
|
||||||
|
exclude = .venv,.git,.tox,dist,doc,.ropeproject
|
Loading…
x
Reference in New Issue
Block a user