1
|
|
|
""" |
2
|
|
|
Module that contains the command line app. |
3
|
|
|
|
4
|
|
|
Why does this file exist, and why not put this in __main__? |
5
|
|
|
|
6
|
|
|
You might be tempted to import things from __main__ later, but that will cause |
7
|
|
|
problems: the code will get executed twice: |
8
|
|
|
|
9
|
|
|
- When you run `python -mapex` python will execute |
10
|
|
|
``__main__.py`` as a script. That means there won't be any |
11
|
|
|
``apex.__main__`` in ``sys.modules``. |
12
|
|
|
- When you import __main__ it will get executed again (as a module) because |
13
|
|
|
there's no ``apex.__main__`` in ``sys.modules``. |
14
|
|
|
|
15
|
|
|
Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration |
16
|
|
|
""" |
17
|
|
|
|
18
|
|
|
# These imports are for python3 compatability inside python2 |
19
|
|
|
from __future__ import absolute_import |
20
|
|
|
from __future__ import division |
21
|
|
|
from __future__ import print_function |
22
|
|
|
from __future__ import unicode_literals |
23
|
|
|
|
24
|
|
|
import os |
25
|
|
|
import signal |
26
|
|
|
import sys |
27
|
|
|
import threading |
28
|
|
|
import time |
29
|
|
|
import traceback |
30
|
|
|
import cachetools |
31
|
|
|
import click |
32
|
|
|
|
33
|
|
|
import apex.aprs |
34
|
|
|
from apex.kiss import constants as kissConstants |
35
|
|
|
from apex.pluginloader import getPlugins |
36
|
|
|
from apex.pluginloader import loadPlugin |
37
|
|
|
|
38
|
|
|
if sys.version_info < (3, 0): |
39
|
|
|
import ConfigParser # noqa: F401 |
40
|
|
|
elif sys.version_info >= (3, 0): |
41
|
|
|
import configparser |
42
|
|
|
|
43
|
|
|
__author__ = 'Jeffrey Phillips Freeman (WI2ARD)' |
44
|
|
|
__maintainer__ = "Jeffrey Phillips Freeman (WI2ARD)" |
45
|
|
|
__email__ = "[email protected]" |
46
|
|
|
__license__ = 'Apache License, Version 2.0' |
47
|
|
|
__copyright__ = 'Copyright 2016, Syncleus, Inc. and contributors' |
48
|
|
|
__credits__ = [] |
49
|
|
|
|
50
|
|
|
def find_config(config_paths): |
51
|
|
|
config_file = 'apex.conf' |
52
|
|
|
rc_file = '.apexrc' |
53
|
|
|
cur_path = os.path.join(os.curdir, config_file) |
54
|
|
|
home_path = os.path.join(os.path.expanduser("~"), rc_file) |
55
|
|
|
etc_path = os.path.join('etc', config_file) |
56
|
|
|
if config_paths is None: |
57
|
|
|
config_paths = [cur_path, home_path, etc_path] |
58
|
|
|
elif isinstance(config_paths, str): |
59
|
|
|
config_paths = [config_paths] |
60
|
|
|
elif not isinstance(config_paths, list): |
61
|
|
|
raise TypeError('config_paths argument was neither a string nor a list') |
62
|
|
|
|
63
|
|
|
config = configparser.ConfigParser() |
64
|
|
|
for config_path in config_paths: |
65
|
|
|
try: |
66
|
|
|
if len(config.read(config_path)) > 0: |
67
|
|
|
return config |
68
|
|
|
except IOError: |
69
|
|
|
pass |
70
|
|
|
|
71
|
|
|
@click.command(context_settings=dict(auto_envvar_prefix='APEX')) |
72
|
|
|
@click.option('--configfile', |
73
|
|
|
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True), |
74
|
|
|
help='Configuration file for APEX.') |
75
|
|
|
@click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode.') |
76
|
|
|
def main(verbose, configfile): |
77
|
|
|
click.echo("verbosity: " + repr(verbose) + " | configfile: " + repr(configfile)) |
78
|
|
|
|
79
|
|
|
port_map = {} |
80
|
|
|
config = find_config(configfile) |
81
|
|
|
for section in config.sections(): |
82
|
|
|
if section.startswith("TNC "): |
83
|
|
|
tnc_name = section.split(" ")[1] |
84
|
|
|
if config.has_option(section, 'com_port') and config.has_option(section, 'baud'): |
85
|
|
|
com_port = config.get(section, 'com_port') |
86
|
|
|
baud = config.get(section, 'baud') |
87
|
|
|
kiss_tnc = apex.aprs.AprsKiss(com_port=com_port, baud=baud) |
88
|
|
|
elif config.has_option(section, 'tcp_host') and config.has_option(section, 'tcp_port'): |
89
|
|
|
tcp_host = config.get(section, 'tcp_host') |
90
|
|
|
tcp_port = config.get(section, 'tcp_port') |
91
|
|
|
kiss_tnc = apex.aprs.AprsKiss(host=tcp_host, tcp_port=tcp_port) |
92
|
|
|
else: |
93
|
|
|
raise Exception( |
94
|
|
|
"Must have either both com_port and baud set or tcp_host and tcp_port set in configuration file") |
95
|
|
|
kiss_init_string = config.get(section, 'kiss_init') |
96
|
|
|
if kiss_init_string == 'MODE_INIT_W8DED': |
97
|
|
|
kiss_tnc.start(kissConstants.MODE_INIT_W8DED) |
98
|
|
|
elif kiss_init_string == 'MODE_INIT_KENWOOD_D710': |
99
|
|
|
kiss_tnc.start(kissConstants.MODE_INIT_KENWOOD_D710) |
100
|
|
|
elif kiss_init_string == 'NONE': |
101
|
|
|
kiss_tnc.start() |
102
|
|
|
else: |
103
|
|
|
raise Exception("KISS init mode not specified") |
104
|
|
|
for port in range(1, 1 + int(config.get(section, 'port_count'))): |
105
|
|
|
port_name = tnc_name + '-' + str(port) |
106
|
|
|
port_section = 'PORT ' + port_name |
107
|
|
|
port_identifier = config.get(port_section, 'identifier') |
108
|
|
|
port_net = config.get(port_section, 'net') |
109
|
|
|
tnc_port = int(config.get(port_section, 'tnc_port')) |
110
|
|
|
port_map[port_name] = {'identifier': port_identifier, 'net': port_net, 'tnc': kiss_tnc, |
111
|
|
|
'tnc_port': tnc_port} |
112
|
|
|
if config.has_section('APRS-IS'): |
113
|
|
|
aprsis_callsign = config.get('APRS-IS', 'callsign') |
114
|
|
|
if config.has_option('APRS-IS', 'password'): |
115
|
|
|
aprsis_password = config.get('APRS-IS', 'password') |
116
|
|
|
else: |
117
|
|
|
aprsis_password = -1 |
118
|
|
|
aprsis_server = config.get('APRS-IS', 'server') |
119
|
|
|
aprsis_server_port = config.get('APRS-IS', 'server_port') |
120
|
|
|
aprsis = apex.aprs.AprsInternetService(aprsis_callsign, aprsis_password) |
121
|
|
|
aprsis.connect(aprsis_server, int(aprsis_server_port)) |
122
|
|
|
|
123
|
|
|
def sigint_handler(signal, frame): |
124
|
|
|
for port in port_map.values(): |
125
|
|
|
port['tnc'].close() |
126
|
|
|
sys.exit(0) |
127
|
|
|
|
128
|
|
|
signal.signal(signal.SIGINT, sigint_handler) |
129
|
|
|
|
130
|
|
|
print("Press ctrl + c at any time to exit") |
131
|
|
|
|
132
|
|
|
packet_cache = cachetools.TTLCache(10000, 5) |
133
|
|
|
# start the plugins |
134
|
|
|
plugins = [] |
135
|
|
|
try: |
136
|
|
|
plugin_loaders = getPlugins() |
137
|
|
|
for plugin_loader in plugin_loaders: |
138
|
|
|
loaded_plugin = loadPlugin(plugin_loader) |
139
|
|
|
plugins.append(loaded_plugin) |
140
|
|
|
threading.Thread(target=loaded_plugin.start, args=(config, port_map, packet_cache, aprsis)).start() |
141
|
|
|
except FileNotFoundError: |
142
|
|
|
print("The plugin directory doesnt exist, without plugins this program has nothing to do, so it will exit now.") |
143
|
|
|
return |
144
|
|
|
|
145
|
|
|
while 1: |
146
|
|
|
something_read = False |
147
|
|
|
try: |
148
|
|
|
for port_name in port_map.keys(): |
149
|
|
|
port = port_map[port_name] |
150
|
|
|
frame = port['tnc'].read() |
151
|
|
|
if frame: |
152
|
|
|
formatted_aprs = apex.aprs.util.format_aprs_frame(frame) |
153
|
|
|
print(port_name + " << " + formatted_aprs) |
154
|
|
|
for plugin in plugins: |
155
|
|
|
something_read = True |
156
|
|
|
plugin.handle_packet(frame, port, port_name) |
157
|
|
|
except Exception as ex: |
158
|
|
|
# We want to keep this thread alive so long as the application runs. |
159
|
|
|
traceback.print_exc(file=sys.stdout) |
160
|
|
|
print("caught exception while reading packet: " + str(ex)) |
161
|
|
|
|
162
|
|
|
if something_read is False: |
163
|
|
|
time.sleep(1) |
164
|
|
|
|