mirror of
https://github.com/craigerl/aprsd.git
synced 2025-06-24 21:15:18 -04:00
commit
5e383dbee8
790
aprsd/main.py
790
aprsd/main.py
@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/python -u
|
#!/usr/bin/python -u
|
||||||
#
|
#
|
||||||
# Listen on amateur radio aprs-is network for messages and respond to them.
|
# Listen on amateur radio aprs-is network for messages and respond to them.
|
||||||
# You must have an amateur radio callsign to use this software. Put your
|
# You must have an amateur radio callsign to use this software. You must
|
||||||
# callsign in the "USER" variable and update your aprs-is password in "PASS".
|
# create an ~/.aprsd/config.yml file with all of the required settings. To
|
||||||
# You must also have an imap email account available for polling.
|
# generate an example config.yml, just run aprsd, then copy the sample config
|
||||||
|
# to ~/.aprsd/config.yml and edit the settings.
|
||||||
#
|
#
|
||||||
# APRS messages:
|
# APRS messages:
|
||||||
# l(ocation) = descriptive location of calling station
|
# l(ocation) = descriptive location of calling station
|
||||||
@ -21,23 +22,24 @@
|
|||||||
|
|
||||||
# python included libs
|
# python included libs
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
|
||||||
import urllib
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import telnetlib
|
|
||||||
import time
|
|
||||||
import re
|
|
||||||
from random import randint
|
|
||||||
import smtplib
|
|
||||||
from email.mime.text import MIMEText
|
|
||||||
import subprocess
|
|
||||||
import datetime
|
import datetime
|
||||||
import calendar
|
|
||||||
import email
|
import email
|
||||||
import threading
|
import json
|
||||||
import signal
|
import logging
|
||||||
|
import os
|
||||||
import pprint
|
import pprint
|
||||||
|
import re
|
||||||
|
import signal
|
||||||
|
import smtplib
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import telnetlib
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
# external lib imports
|
# external lib imports
|
||||||
from imapclient import IMAPClient, SEEN
|
from imapclient import IMAPClient, SEEN
|
||||||
@ -46,87 +48,60 @@ from imapclient import IMAPClient, SEEN
|
|||||||
from aprsd.fuzzyclock import fuzzy
|
from aprsd.fuzzyclock import fuzzy
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
# setup the global logger
|
||||||
|
LOG = logging.getLogger('APRSD')
|
||||||
|
|
||||||
|
# global for the config yaml
|
||||||
|
CONFIG = None
|
||||||
|
|
||||||
# localization, please edit:
|
# localization, please edit:
|
||||||
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 which we send email
|
||||||
shortcuts = {
|
# shortcuts = {
|
||||||
"aa" : "5551239999@vtext.com",
|
# "aa" : "5551239999@vtext.com",
|
||||||
"cl" : "craiglamparter@somedomain.org",
|
# "cl" : "craiglamparter@somedomain.org",
|
||||||
"wb" : "5553909472@vtext.com"
|
# "wb" : "5553909472@vtext.com"
|
||||||
}
|
# }
|
||||||
|
|
||||||
# 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}
|
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}
|
ack_dict = {} # message_nubmer:ack combos so we stop sending a message after an ack from radio {int:int}
|
||||||
message_number = 0 # current aprs radio message number, increments for each message we send over rf {int}
|
message_number = 0 # current aprs radio message number, increments for each message we send over rf {int}
|
||||||
|
|
||||||
|
# global telnet connection object
|
||||||
|
tn = None
|
||||||
|
|
||||||
# command line args
|
# command line args
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--user",
|
parser.add_argument("--loglevel",
|
||||||
metavar="<user>",
|
default='DEBUG',
|
||||||
default=utils.env("APRS_USER"),
|
choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'],
|
||||||
help="The callsign of this ARPS client with SSID"
|
help="The log level to use for aprsd.log")
|
||||||
" Default=env[APRS_USER]")
|
parser.add_argument("--quiet",
|
||||||
|
action='store_true',
|
||||||
parser.add_argument("--host",
|
help="Don't log to stdout")
|
||||||
metavar="<host>",
|
|
||||||
default=utils.env("APRS_HOST"),
|
|
||||||
help="The aprs host to use Default=env[APRS_HOST]")
|
|
||||||
parser.add_argument("--password",
|
|
||||||
metavar="<password>",
|
|
||||||
default=utils.env("APRS_PASSWORD"),
|
|
||||||
help="The aprs password Default=env[APRS_PASSWORD]")
|
|
||||||
parser.add_argument("--callsign",
|
|
||||||
metavar="<callsign>",
|
|
||||||
default=utils.env("APRS_CALLSIGN"),
|
|
||||||
help="The callsign of radio in the field to which we send "
|
|
||||||
"email Default=env[APRS_CALLSIGN]")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if not args.user:
|
|
||||||
print("Missing the aprs user (env[APRS_USER])")
|
|
||||||
parser.print_help()
|
|
||||||
parser.exit()
|
|
||||||
else:
|
|
||||||
USER = args.user
|
|
||||||
|
|
||||||
if not args.password:
|
|
||||||
print("Missing the aprs password (env[APRS_PASSWORD])")
|
|
||||||
parser.print_help()
|
|
||||||
parser.exit()
|
|
||||||
else:
|
|
||||||
PASS = args.password
|
|
||||||
|
|
||||||
if not args.callsign:
|
|
||||||
print("Missing the aprs callsign (env[APRS_CALLSIGN])")
|
|
||||||
parser.print_help()
|
|
||||||
parser.exit()
|
|
||||||
else:
|
|
||||||
BASECALLSIGN = args.callsign
|
|
||||||
|
|
||||||
|
|
||||||
# Now read the ~/.aprds/config.yml
|
def setup_connection():
|
||||||
config = utils.get_config()
|
global tn
|
||||||
if 'shortcuts' in config:
|
host = CONFIG['aprs']['host']
|
||||||
shortcuts = config['shortcuts']
|
LOG.debug("Setting up telnet connection to '%s'" % host)
|
||||||
else:
|
try:
|
||||||
print("missing 'shortcuts' section of config.yml")
|
tn = telnetlib.Telnet(host, 14580)
|
||||||
sys.exit(-1)
|
except Exception, e:
|
||||||
|
LOG.critical("Telnet session failed.\n", e)
|
||||||
try:
|
sys.exit(-1)
|
||||||
tn = telnetlib.Telnet(HOST, 14580)
|
|
||||||
except Exception, e:
|
|
||||||
print "Telnet session failed.\n"
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(signal, frame):
|
def signal_handler(signal, frame):
|
||||||
print("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)
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
|
||||||
### end signal_handler
|
### end signal_handler
|
||||||
|
|
||||||
def parse_email(msgid, data, server):
|
def parse_email(msgid, data, server):
|
||||||
@ -172,107 +147,116 @@ def parse_email(msgid, data, server):
|
|||||||
|
|
||||||
|
|
||||||
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 = str(day) + "-" + month + "-" + str(year)
|
||||||
|
|
||||||
global shortcuts
|
shortcuts = CONFIG['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)
|
LOG.debug("resend_email: Connect to IMAP host '%s' with user '%s'" %
|
||||||
server.login('KM6XXX@yourdomain.org', 'yourpassword')
|
(CONFIG['imap']['host'],
|
||||||
select_info = server.select_folder('INBOX')
|
CONFIG['imap']['login']))
|
||||||
|
server = IMAPClient(CONFIG['imap']['host'], use_uid=True)
|
||||||
|
server.login(CONFIG['imap']['login'], CONFIG['imap']['password'])
|
||||||
|
# select_info = server.select_folder('INBOX')
|
||||||
|
|
||||||
messages = server.search(['SINCE', today])
|
messages = server.search(['SINCE', today])
|
||||||
#print("%d messages received today" % len(messages))
|
LOG.debug("%d messages received today" % len(messages))
|
||||||
|
|
||||||
msgexists = False
|
msgexists = False
|
||||||
|
|
||||||
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
|
server.remove_flags(msgid, [SEEN]) # unset seen flag, will stay bold in email client
|
||||||
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 # asterisk indicates a resend
|
reply = "-" + from_addr + " * " + body # asterisk indicates a resend
|
||||||
send_message(fromcall, reply)
|
send_message(fromcall, reply)
|
||||||
msgexists = True
|
msgexists = True
|
||||||
|
|
||||||
if msgexists is not True:
|
if msgexists is not True:
|
||||||
stm = time.localtime()
|
stm = time.localtime()
|
||||||
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 thinking this is a duplicate message.
|
||||||
# The FT1XDR pretty much ignores the aprs message number in this regard. The FTM400 gets it right.
|
# The FT1XDR pretty much ignores the aprs message number in this regard. The FTM400 gets it right.
|
||||||
reply = "No new msg " + str(h).zfill(2) + ":" + str(m).zfill(2) + ":" + str(s).zfill(2)
|
reply = "No new msg " + 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."
|
||||||
|
# return
|
||||||
|
|
||||||
# print "Email thread disabled."
|
LOG.debug("Starting Email thread")
|
||||||
# return
|
threading.Timer(55, check_email_thread).start() # how do we skip first run?
|
||||||
|
|
||||||
threading.Timer(55, check_email_thread).start() # how do we skip first run?
|
shortcuts = CONFIG['shortcuts']
|
||||||
|
shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value
|
||||||
|
|
||||||
global shortcuts
|
date = datetime.datetime.now()
|
||||||
shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value
|
month = date.strftime("%B")[:3] # Nov, Mar, Apr
|
||||||
|
day = date.day
|
||||||
|
year = date.year
|
||||||
|
today = str(day) + "-" + month + "-" + str(year)
|
||||||
|
|
||||||
date = datetime.datetime.now()
|
LOG.debug("Connect to IMAP host '%s' with user '%s'" %
|
||||||
month = date.strftime("%B")[:3] # Nov, Mar, Apr
|
(CONFIG['imap']['host'],
|
||||||
day = date.day
|
CONFIG['imap']['login']))
|
||||||
year = date.year
|
|
||||||
today = str(day) + "-" + month + "-" + str(year)
|
|
||||||
|
|
||||||
server = IMAPClient('imap.yourdomain.com', use_uid=True)
|
server = IMAPClient(CONFIG['imap']['host'], use_uid=True)
|
||||||
server.login('KM6XXX@yourdomain.org', 'yourpassword')
|
server.login(CONFIG['imap']['login'], CONFIG['imap']['password'])
|
||||||
select_info = server.select_folder('INBOX')
|
# select_info = server.select_folder('INBOX')
|
||||||
|
|
||||||
messages = server.search(['SINCE', today])
|
messages = server.search(['SINCE', today])
|
||||||
#print("%d messages received today" % len(messages))
|
LOG.debug("%d messages received today" % 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']
|
||||||
#print('ID:%d "%s" (%s)' % (msgid, envelope.subject.decode(), envelope.date ))
|
LOG.debug('ID:%d "%s" (%s)' %
|
||||||
f = re.search('([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)', str(envelope.from_[0]) )
|
(msgid, envelope.subject.decode(), envelope.date))
|
||||||
if f is not None:
|
f = re.search('([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)',
|
||||||
from_addr = f.group(1)
|
str(envelope.from_[0]) )
|
||||||
else:
|
if f is not None:
|
||||||
from_addr = "noaddr"
|
from_addr = f.group(1)
|
||||||
|
else:
|
||||||
|
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]: #if msg not flagged as sent via aprs
|
||||||
m = server.fetch([msgid], ['RFC822'])
|
m = 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
|
server.remove_flags(msgid, [SEEN]) # unset seen flag, will stay bold in email client
|
||||||
|
|
||||||
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(BASECALLSIGN, reply) #radio
|
send_message(CONFIG['ham']['callsign'], reply) #radio
|
||||||
server.add_flags(msgid, ['APRS']) #flag message as sent via aprs
|
server.add_flags(msgid, ['APRS']) #flag message as sent via aprs
|
||||||
server.remove_flags(msgid, [SEEN]) #unset seen flag, will stay bold in email client
|
server.remove_flags(msgid, [SEEN]) #unset seen flag, will stay bold in email client
|
||||||
|
|
||||||
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 = USER + ">APRS::" + tocall + ":ack" + str(ack) + "\n"
|
line = CONFIG['aprs']['login'] + ">APRS::" + tocall + ":ack" + str(ack) + "\n"
|
||||||
for i in range(retry_count, 0, -1):
|
for i in range(retry_count, 0, -1):
|
||||||
print "Sending ack __________________ Tx(" + str(i) + ")"
|
LOG.info("Sending ack __________________ Tx(" + str(i) + ")")
|
||||||
print "Raw : " + line,
|
LOG.info("Raw : " + line)
|
||||||
print "To : " + tocall
|
LOG.info("To : " + tocall)
|
||||||
print "Ack number : " + str(ack)
|
LOG.info("Ack number : " + str(ack))
|
||||||
tn.write(line)
|
tn.write(line)
|
||||||
time.sleep(31) # aprs duplicate detection is 30 secs? (21 only sends first, 28 skips middle)
|
time.sleep(31) # aprs duplicate detection is 30 secs? (21 only sends first, 28 skips middle)
|
||||||
return()
|
return()
|
||||||
@ -289,15 +273,17 @@ 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 = USER + ">APRS::" + tocall + ":" + message + "{" + str(this_message_number) + "\n"
|
line = (CONFIG['aprs']['login'] + ">APRS::" + tocall + ":" + message +
|
||||||
|
"{" + str(this_message_number) + "\n")
|
||||||
for i in range(retry_count, 0, -1):
|
for i in range(retry_count, 0, -1):
|
||||||
print "DEBUG: send_message_thread msg:ack combos are: "
|
LOG.debug("DEBUG: send_message_thread msg:ack combos are: ")
|
||||||
pprint.pprint(ack_dict)
|
LOG.debug(pprint.pformat(ack_dict))
|
||||||
if ack_dict[this_message_number] != 1:
|
if ack_dict[this_message_number] != 1:
|
||||||
print "Sending message_______________ " + str(this_message_number) + "(Tx" + str(i) + ")"
|
LOG.info("Sending message_______________ " +
|
||||||
print "Raw : " + line,
|
str(this_message_number) + "(Tx" + str(i) + ")")
|
||||||
print "To : " + tocall
|
LOG.info("Raw : " + line)
|
||||||
print "Message : " + message
|
LOG.info("To : " + tocall)
|
||||||
|
LOG.info("Message : " + message)
|
||||||
tn.write(line)
|
tn.write(line)
|
||||||
sleeptime = (retry_count - i + 1) * 31 # decaying repeats, 31 to 93 second intervals
|
sleeptime = (retry_count - i + 1) * 31 # decaying repeats, 31 to 93 second intervals
|
||||||
time.sleep(sleeptime)
|
time.sleep(sleeptime)
|
||||||
@ -308,242 +294,318 @@ def send_message_thread(tocall, message, this_message_number, retry_count):
|
|||||||
|
|
||||||
|
|
||||||
def send_message(tocall, message):
|
def send_message(tocall, message):
|
||||||
global message_number
|
global message_number
|
||||||
global ack_dict
|
global ack_dict
|
||||||
retry_count = 3
|
retry_count = 3
|
||||||
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: # 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."
|
LOG.debug("DEBUG: Length of ack dictionary is big at " + str(len(ack_dict)) + " clearing.")
|
||||||
ack_dict.clear()
|
ack_dict.clear()
|
||||||
pprint.pprint(ack_dict)
|
LOG.debug(pprint.pformat(ack_dict))
|
||||||
print "DEBUG: Cleared ack dictionary, ack_dict length is now " + str(len(ack_dict)) + "."
|
LOG.debug("DEBUG: Cleared ack dictionary, ack_dict length is now " + str(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
|
message = message[:67] # max? ftm400 displays 64, raw msg shows 74
|
||||||
# and ftm400-send is max 64. setting this to
|
# and ftm400-send is max 64. setting this to
|
||||||
# 67 displays 64 on the ftm400. (+3 {01 suffix)
|
# 67 displays 64 on the ftm400. (+3 {01 suffix)
|
||||||
# feature req: break long ones into two msgs
|
# feature req: break long ones into two msgs
|
||||||
thread = threading.Thread(target = send_message_thread, args = (tocall, message, message_number, retry_count))
|
thread = threading.Thread(
|
||||||
thread.start()
|
target = send_message_thread,
|
||||||
return()
|
args = (tocall, message, message_number, retry_count))
|
||||||
|
thread.start()
|
||||||
|
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 = '::' + USER + '[ ]*:(.*)' # verify this, callsign is padded out with spaces to colon
|
searchstring = '::' + 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) # ack formats include: {1, {AB}, {12
|
||||||
if ack_attached: # "{##" suffix means radio wants an ack back
|
if ack_attached: # "{##" suffix means radio wants an ack back
|
||||||
message = ack_attached.group(1) # message content
|
message = ack_attached.group(1) # message content
|
||||||
ack_num = ack_attached.group(2) # suffix number to use in ack
|
ack_num = ack_attached.group(2) # suffix number to use in ack
|
||||||
else:
|
else:
|
||||||
message = fullmessage
|
message = fullmessage
|
||||||
ack_num = "0" # ack not requested, but lets send one as 0
|
ack_num = "0" # ack not requested, but lets send one as 0
|
||||||
|
|
||||||
print "Received message______________"
|
LOG.info("Received message______________")
|
||||||
print "Raw : " + line
|
LOG.info("Raw : " + line)
|
||||||
print "From : " + fromcall
|
LOG.info("From : " + fromcall)
|
||||||
print "Message : " + message
|
LOG.info("Message : " + message)
|
||||||
print "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):
|
||||||
print "Sending Email_________________"
|
LOG.info("Sending Email_________________")
|
||||||
global shortcuts
|
shortcuts = CONFIG['shortcuts']
|
||||||
if to_addr in shortcuts:
|
if to_addr in shortcuts:
|
||||||
print "To : " + to_addr ,
|
LOG.info("To : " + to_addr)
|
||||||
to_addr = shortcuts[to_addr]
|
to_addr = shortcuts[to_addr]
|
||||||
print " (" + to_addr + ")"
|
LOG.info(" (" + to_addr + ")")
|
||||||
subject = BASECALLSIGN
|
subject = CONFIG['ham']['callsign']
|
||||||
#content = content + "\n\n(NOTE: reply with one line)"
|
# content = content + "\n\n(NOTE: reply with one line)"
|
||||||
print "Subject : " + subject
|
LOG.info("Subject : " + subject)
|
||||||
print "Body : " + content
|
LOG.info("Body : " + content)
|
||||||
|
|
||||||
msg = MIMEText(content)
|
msg = MIMEText(content)
|
||||||
msg['Subject'] = subject
|
msg['Subject'] = subject
|
||||||
msg['From'] = "KM6XXX@yourdomain.org"
|
msg['From'] = "KM6XXX@yourdomain.org"
|
||||||
msg['To'] = to_addr
|
msg['To'] = to_addr
|
||||||
s = smtplib.SMTP_SSL('smtp.yourdomain.com', 465)
|
s = smtplib.SMTP_SSL('smtp.yourdomain.com', 465)
|
||||||
s.login("KM6XXX@yourdomain.org", "yourpassword")
|
s.login("KM6XXX@yourdomain.org", "yourpassword")
|
||||||
try:
|
try:
|
||||||
s.sendmail("KM6XXX@yourdomain.org", [to_addr], msg.as_string())
|
s.sendmail("KM6XXX@yourdomain.org", [to_addr], msg.as_string())
|
||||||
except Exception, e:
|
except Exception:
|
||||||
print "Sendmail Error!!!!!!!!!"
|
LOG.exception("Sendmail Error!!!!!!!!!")
|
||||||
s.quit()
|
s.quit()
|
||||||
return(-1)
|
return(-1)
|
||||||
s.quit()
|
s.quit()
|
||||||
return(0)
|
return(0)
|
||||||
### end send_email
|
### end send_email
|
||||||
|
|
||||||
|
|
||||||
|
# Setup the logging faciility
|
||||||
|
# to disable logging to stdout, but still log to file
|
||||||
|
# use the --quiet option on the cmdln
|
||||||
|
def setup_logging(args):
|
||||||
|
global LOG
|
||||||
|
levels = {
|
||||||
|
'CRITICAL': logging.CRITICAL,
|
||||||
|
'ERROR': logging.ERROR,
|
||||||
|
'WARNING': logging.WARNING,
|
||||||
|
'INFO': logging.INFO,
|
||||||
|
'DEBUG': logging.DEBUG}
|
||||||
|
log_level = levels[args.loglevel]
|
||||||
|
|
||||||
|
LOG.setLevel(log_level)
|
||||||
|
log_format = ("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]"
|
||||||
|
" %(message)s")
|
||||||
|
date_format = '%m/%d/%Y %I:%M:%S %p'
|
||||||
|
log_formatter = logging.Formatter(fmt=log_format,
|
||||||
|
datefmt=date_format)
|
||||||
|
fh = RotatingFileHandler('aprsd.log',
|
||||||
|
maxBytes=(10248576*5),
|
||||||
|
backupCount=4)
|
||||||
|
fh.setFormatter(log_formatter)
|
||||||
|
LOG.addHandler(fh)
|
||||||
|
|
||||||
|
if not args.quiet:
|
||||||
|
sh = logging.StreamHandler(sys.stdout)
|
||||||
|
sh.setFormatter(log_formatter)
|
||||||
|
LOG.addHandler(sh)
|
||||||
|
|
||||||
|
# This method tries to parse the config yaml file
|
||||||
|
# and consume the settings.
|
||||||
|
# If the required params don't exist,
|
||||||
|
# it will look in the environment
|
||||||
|
def parse_config(args):
|
||||||
|
# for now we still use globals....ugh
|
||||||
|
global CONFIG, LOG
|
||||||
|
|
||||||
|
def fail(msg):
|
||||||
|
LOG.critical(msg)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
def check_option(config, section, name=None):
|
||||||
|
if section in config:
|
||||||
|
if name and name not in config[section]:
|
||||||
|
fail("'%s' was not in '%s' section of config file" %
|
||||||
|
(name, section))
|
||||||
|
else:
|
||||||
|
fail("'%s' section wasn't in config file" % section)
|
||||||
|
|
||||||
|
# Now read the ~/.aprds/config.yml
|
||||||
|
config = utils.get_config()
|
||||||
|
check_option(config, 'shortcuts')
|
||||||
|
check_option(config, 'ham', 'callsign')
|
||||||
|
check_option(config, 'aprs', 'login')
|
||||||
|
check_option(config, 'aprs', 'password')
|
||||||
|
check_option(config, 'aprs', 'host')
|
||||||
|
check_option(config, 'imap', 'host')
|
||||||
|
check_option(config, 'imap', 'login')
|
||||||
|
check_option(config, 'imap', 'password')
|
||||||
|
|
||||||
|
CONFIG = config
|
||||||
|
LOG.info("aprsd config loaded")
|
||||||
|
|
||||||
|
|
||||||
### main() ###
|
### main() ###
|
||||||
def main():
|
def main(args=args):
|
||||||
|
setup_logging(args)
|
||||||
|
|
||||||
time.sleep(2)
|
LOG.info("APRSD Started")
|
||||||
|
parse_config(args)
|
||||||
|
LOG.debug("Signal handler setup")
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
|
||||||
tn.write("user " + USER + " pass " + PASS + " vers aprsd 0.99\n" )
|
time.sleep(2)
|
||||||
|
setup_connection()
|
||||||
|
|
||||||
time.sleep(2)
|
user = CONFIG['aprs']['login']
|
||||||
|
password = CONFIG['aprs']['password']
|
||||||
|
LOG.info("LOGIN to APRSD with user '%s'" % user)
|
||||||
|
tn.write("user " + user + " pass " + password + " vers aprsd 0.99\n" )
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
check_email_thread() # start email reader thread
|
check_email_thread() # start email reader thread
|
||||||
|
|
||||||
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', '')
|
||||||
print line
|
LOG.info(line)
|
||||||
searchstring = '::' + USER
|
searchstring = '::' + user
|
||||||
if re.search(searchstring, line): # is aprs message to us, not beacon, status, etc
|
# is aprs message to us, not beacon, status, etc
|
||||||
(fromcall, message, ack) = process_message(line)
|
if re.search(searchstring, line):
|
||||||
else:
|
(fromcall, message, ack) = process_message(line)
|
||||||
message = "noise"
|
else:
|
||||||
continue
|
message = "noise"
|
||||||
|
continue
|
||||||
|
|
||||||
# ACK (ack##)
|
# ACK (ack##)
|
||||||
if re.search('^ack[0-9]+', message):
|
if re.search('^ack[0-9]+', message):
|
||||||
a = 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
|
||||||
ack_dict.update({int(a.group(1)):1})
|
a = re.search('^ack([0-9]+)', message)
|
||||||
continue
|
ack_dict.update({int(a.group(1)):1})
|
||||||
|
continue
|
||||||
|
|
||||||
# EMAIL (-)
|
# EMAIL (-)
|
||||||
elif re.search('^-.*', message): # is email command
|
elif re.search('^-.*', message): # is email command
|
||||||
searchstring = '^' + BASECALLSIGN + '.*'
|
searchstring = '^' + CONFIG['ham']['callsign'] + '.*'
|
||||||
if re.search(searchstring, fromcall): # only I can do email
|
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
|
r = re.search('^-([0-9])[0-9]*$', message) # digits only, first one is number of emails to resend
|
||||||
if r is not None:
|
if r is not None:
|
||||||
resend_email(r.group(1))
|
resend_email(r.group(1))
|
||||||
elif re.search('^-([A-Za-z0-9_\-\.@]+) (.*)', message): # -user@address.com body of email
|
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)
|
a = re.search('^-([A-Za-z0-9_\-\.@]+) (.*)', message) # (same search again)
|
||||||
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
|
if content == 'mapme': # send recipient link to aprs.fi map
|
||||||
content = "Click for my location: http://aprs.fi/" + BASECALLSIGN
|
content = "Click for my location: http://aprs.fi/" + 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
|
if ack in email_sent_dict: # see if we sent this msg number recently
|
||||||
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
|
||||||
print "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()
|
||||||
email_sent_dict[ack] = now
|
email_sent_dict[ack] = now
|
||||||
|
else:
|
||||||
|
LOG.info("Email for message number " + ack + " recently sent, not sending again.")
|
||||||
else:
|
else:
|
||||||
print "\nEmail for message number " + ack + " recently sent, not sending again.\n"
|
send_message(fromcall, "Bad email address")
|
||||||
else:
|
|
||||||
send_message(fromcall, "Bad email address")
|
|
||||||
|
|
||||||
# TIME (t)
|
# TIME (t)
|
||||||
elif re.search('^t', message):
|
elif re.search('^t', message):
|
||||||
stm = time.localtime()
|
stm = time.localtime()
|
||||||
h = stm.tm_hour
|
h = stm.tm_hour
|
||||||
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, args = (fromcall, reply))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
# FORTUNE (f)
|
# FORTUNE (f)
|
||||||
elif re.search('^f', message):
|
elif re.search('^f', message):
|
||||||
process = subprocess.Popen(['/usr/games/fortune', '-s', '-n 60'], stdout=subprocess.PIPE)
|
process = subprocess.Popen(['/usr/games/fortune', '-s', '-n 60'], stdout=subprocess.PIPE)
|
||||||
reply = process.communicate()[0]
|
reply = process.communicate()[0]
|
||||||
send_message(fromcall, reply.rstrip())
|
send_message(fromcall, reply.rstrip())
|
||||||
|
|
||||||
# PING (p)
|
# PING (p)
|
||||||
elif re.search('^p', message):
|
elif re.search('^p', message):
|
||||||
stm = time.localtime()
|
stm = time.localtime()
|
||||||
h = stm.tm_hour
|
h = stm.tm_hour
|
||||||
m = stm.tm_min
|
m = stm.tm_min
|
||||||
s = stm.tm_sec
|
s = stm.tm_sec
|
||||||
reply = "Pong! " + str(h).zfill(2) + ":" + str(m).zfill(2) + ":" + str(s).zfill(2)
|
reply = "Pong! " + str(h).zfill(2) + ":" + str(m).zfill(2) + ":" + str(s).zfill(2)
|
||||||
send_message(fromcall, reply.rstrip())
|
send_message(fromcall, reply.rstrip())
|
||||||
|
|
||||||
# LOCATION (l) "8 Miles E Auburn CA 1771' 38.91547,-120.99500 0.1h ago"
|
# LOCATION (l) "8 Miles E Auburn CA 1771' 38.91547,-120.99500 0.1h ago"
|
||||||
elif re.search('^l', message):
|
elif re.search('^l', message):
|
||||||
# get my last location, get descriptive name from weather service
|
# get my last location, get descriptive name 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?name=" + fromcall + "&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:
|
||||||
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 = 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?)"
|
||||||
send_message(fromcall, reply.rstrip())
|
send_message(fromcall, reply.rstrip())
|
||||||
|
|
||||||
# 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?name=" + fromcall + "&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']
|
||||||
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 = 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 = 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
|
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?)"
|
||||||
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
|
time.sleep(1) # let any threads do their thing, then ack
|
||||||
send_ack(fromcall, ack) # send an ack last
|
send_ack(fromcall, ack) # send an ack last
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print "Error in mainline loop:"
|
LOG.error("Error in mainline loop:")
|
||||||
print "%s" % str(e)
|
LOG.error("%s" % str(e))
|
||||||
print "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
|
||||||
tn.close()
|
tn.close()
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main(args)
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
"""Utilities and helper functions."""
|
"""Utilities and helper functions."""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import pprint
|
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
# an example of what should be in the ~/.aprsd/config.yml
|
# an example of what should be in the ~/.aprsd/config.yml
|
||||||
example_config = '''
|
example_config = '''
|
||||||
|
ham:
|
||||||
|
callsign: KFART
|
||||||
|
|
||||||
|
aprs:
|
||||||
|
login: someusername
|
||||||
|
password: password
|
||||||
|
host: noam.aprs2.net
|
||||||
|
|
||||||
shortcuts:
|
shortcuts:
|
||||||
'aa': '5551239999@vtext.com'
|
'aa': '5551239999@vtext.com'
|
||||||
'cl': 'craiglamparter@somedomain.org'
|
'cl': 'craiglamparter@somedomain.org'
|
||||||
@ -19,12 +27,10 @@ smtp:
|
|||||||
imap:
|
imap:
|
||||||
login: imapuser
|
login: imapuser
|
||||||
password: something dumb
|
password: something dumb
|
||||||
|
|
||||||
ham:
|
|
||||||
callsign: something
|
|
||||||
basename: somebasename
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
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
|
||||||
@ -44,6 +50,6 @@ def get_config():
|
|||||||
config = yaml.load(stream)
|
config = yaml.load(stream)
|
||||||
return config
|
return config
|
||||||
else:
|
else:
|
||||||
print("%s is missing, please create a config file" % config_file)
|
log.critical("%s is missing, please create a config file" % config_file)
|
||||||
print("example config is\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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user