diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7707d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +.eggs +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml +.testrepository +.venv + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Complexity +output/*.html +output/*/index.html + +# Sphinx +doc/build + +# pbr generates these +AUTHORS +ChangeLog + +# Editors +*~ +.*.swp +.*sw? +.ropeproject diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c978a52 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include AUTHORS +include ChangeLog +exclude .gitignore +exclude .gitreview + +global-exclude *.pyc diff --git a/aprsd/__init__.py b/aprsd/__init__.py new file mode 100644 index 0000000..ecc74b1 --- /dev/null +++ b/aprsd/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import pbr.version + + +__version__ = pbr.version.VersionInfo( + 'aprsd').version_string() diff --git a/fuzzyclock.py b/aprsd/fuzzyclock.py similarity index 98% rename from fuzzyclock.py rename to aprsd/fuzzyclock.py index 85f8f1e..aae0e2c 100644 --- a/fuzzyclock.py +++ b/aprsd/fuzzyclock.py @@ -23,38 +23,38 @@ def fuzzy(hour, minute, degree=1): Supports two degrees of fuzziness. Set with degree = 1 or degree = 2 When degree = 1, time is in quantum of 5 minutes. When degree = 2, time is in quantum of 15 minutes.''' - + if degree<=0 or degree>2: print 'Please use a degree of 1 or 2. Using fuzziness degree=1' - degree = 1 - + degree = 1 + begin = 'It\'s ' - + f0 = 'almost ' f1 = 'exactly ' f2 = 'around ' - + b0 = ' past ' b1 = ' to ' - + hourList = ('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve') - + s1 = s2 = s3 = s4 = '' base = 5 - + if degree == 1: base = 5 val = ('Five', 'Ten', 'Quarter', 'Twenty', 'Twenty-Five', 'Half') elif degree == 2: base = 15 val = ('Quarter', 'Half') - + dmin = minute % base # to find whether we have to use 'almost', 'exactly' or 'around' if minute > 30: pos = int((60 - minute) / base) #position in the tuple 'val' else: pos = int(minute / base) - + if dmin == 0: s1 = f1 pos = pos - 1 @@ -66,9 +66,9 @@ def fuzzy(hour, minute, degree=1): s1 = f0 if minute > 30: pos = pos -1 - + s2 = val[pos] - + if minute <= base/2: # Case like "It's around/exactly Ten" s2 = s3 = '' s4 = hourList[hour - 12 - 1] @@ -82,7 +82,7 @@ def fuzzy(hour, minute, degree=1): else: s3 = b0 # past s4 = hourList[hour - 12 - 1] - + return begin + s1 + s2 + s3 + s4 def main(): @@ -90,13 +90,13 @@ def main(): stm = time.localtime() h = stm.tm_hour m = stm.tm_min - + if len(sys.argv)>=2: try: deg = int(sys.argv[1]) except: print 'Please use a degree of 1 or 2. Using fuzziness degree=1' - + if len(sys.argv)>=3: tm = sys.argv[2].split(':') try: diff --git a/aprsd.py b/aprsd/main.py similarity index 92% rename from aprsd.py rename to aprsd/main.py index 5bdd1c8..5192c33 100644 --- a/aprsd.py +++ b/aprsd/main.py @@ -19,7 +19,8 @@ # License GPLv2 # -from fuzzyclock import fuzzy +# python included libs +import argparse import json import urllib import sys @@ -33,12 +34,18 @@ from email.mime.text import MIMEText import subprocess import datetime import calendar -from imapclient import IMAPClient, SEEN import email import threading import signal import pprint +# external lib imports +from imapclient import IMAPClient, SEEN + +# local imports here +from fuzzyclock import fuzzy +import utils + # localization, please edit: HOST = "noam.aprs2.net" # north america tier2 servers round robin USER = "KM6XXX-9" # callsign of this aprs client with SSID @@ -54,6 +61,52 @@ shortcuts = { 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 = 0 # current aprs radio message number, increments for each message we send over rf {int} + +# command line args +parser = argparse.ArgumentParser() +parser.add_argument("--user", + metavar="", + default=utils.env("APRS_USER"), + help="The callsign of this ARPS client with SSID" + " Default=env[APRS_USER]") + +parser.add_argument("--host", + metavar="", + default=utils.env("APRS_HOST"), + help="The aprs host to use Default=env[APRS_HOST]") +parser.add_argument("--password", + metavar="", + default=utils.env("APRS_PASSWORD"), + help="The aprs password Default=env[APRS_PASSWORD]") +parser.add_argument("--callsign", + metavar="", + 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() +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 + + try: tn = telnetlib.Telnet(HOST, 14580) except Exception, e: diff --git a/aprsd/utils.py b/aprsd/utils.py new file mode 100644 index 0000000..c51b982 --- /dev/null +++ b/aprsd/utils.py @@ -0,0 +1,16 @@ +"""Utilities and helper functions.""" + +import os +import pprint +import sys + + +def env(*vars, **kwargs): + """This returns the first environment variable set. + if none are non-empty, defaults to '' or keyword arg default + """ + for v in vars: + value = os.environ.get(v, None) + if value: + return value + return kwargs.get('default', '') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bd92dae --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pbr +imapclient diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..aa29c81 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,31 @@ +[metadata] +name = aprsd +summary = Amateur radio APRS daemon which listens for messages and responds +description-file = + README.md +author = Craig Lamparter +author-email = something@somewhere.com +classifier = + Topic :: Communications :: Ham Radio + Operating System :: POSIX :: Linux + Programming Language :: Python + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[files] +packages = + aprsd + +[entry_points] +console_scripts = + aprsd = aprsd.main:main + +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all_files = 1 + +[upload_sphinx] +upload-dir = doc/build/html diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..056c16c --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr'], + pbr=True)