Completed
Push — master ( b8f070...484cc4 )
by Jeffrey
03:38
created

sigint_handler()   B

Complexity

Conditions 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 5
dl 0
loc 20
rs 8.5454
c 6
b 0
f 0
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 compatibility inside python2
19
from __future__ import absolute_import
20
from __future__ import division
21
from __future__ import print_function
22
23
import os
24
import signal
25
import sys
26
import threading
27
import time
28
import traceback
29
import cachetools
30
import click
31
32
import apex.aprs
33
from apex.kiss import constants as kissConstants
34
from apex.plugin_loader import get_plugins
35
from apex.plugin_loader import load_plugin
36
37
configparser = None
38
if sys.version_info < (3, 0):
39
    import ConfigParser  # noqa: F401
40
    if configparser is None:
41
        configparser = ConfigParser
42
elif sys.version_info >= (3, 0):
43
    import configparser
44
45
__author__ = 'Jeffrey Phillips Freeman (WI2ARD)'
46
__maintainer__ = 'Jeffrey Phillips Freeman (WI2ARD)'
47
__email__ = '[email protected]'
48
__license__ = 'Apache License, Version 2.0'
49
__copyright__ = 'Copyright 2016, Syncleus, Inc. and contributors'
50
__credits__ = []
51
52
port_map = {}
53
running = True
54
plugin_modules = []
55
plugin_threads = []
56
57
58
def find_config(config_paths, verbose):
59
    config_file = 'apex.conf'
60
    rc_file = '.apexrc'
61
    cur_path = os.path.join(os.curdir, config_file)
62
    home_path = os.path.join(os.path.expanduser('~'), rc_file)
63
    etc_path = os.path.join('etc', config_file)
64
    if config_paths is None:
65
        config_paths = [cur_path, home_path, etc_path]
66
    elif isinstance(config_paths, str):
67
        config_paths = [config_paths]
68
    elif not isinstance(config_paths, list):
69
        raise TypeError('config_paths argument was neither a string nor a list')
70
71
    if verbose:
72
        click.echo('Searching for configuration file in the following locations: %s' % repr(config_paths))
73
74
    config = configparser.ConfigParser()
75
    for config_path in config_paths:
76
        try:
77
            if len(config.read(config_path)) > 0:
78
                return config
79
        except IOError:
80
            pass
81
82
    return None
83
84
85
@click.command(context_settings=dict(auto_envvar_prefix='APEX'))
86
@click.option('-c',
87
              '--configfile',
88
              type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True),
89
              help='Configuration file for APEX.')
90
@click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode.')
91
def main(verbose, configfile):
92
93
    config = find_config(configfile, verbose)
94
    if config is None:
95
        click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
96
                   click.style('No apex configuration found, can not continue.', bold=True))
97
        return
98
    for section in config.sections():
99
        if section.startswith("TNC "):
100
            tnc_name = section.split(" ")[1]
101
            if config.has_option(section, 'com_port') and config.has_option(section, 'baud'):
102
                com_port = config.get(section, 'com_port')
103
                baud = config.get(section, 'baud')
104
                kiss_tnc = apex.kiss.KissSerial(com_port=com_port, baud=baud)
105
            elif config.has_option(section, 'tcp_host') and config.has_option(section, 'tcp_port'):
106
                tcp_host = config.get(section, 'tcp_host')
107
                tcp_port = config.get(section, 'tcp_port')
108
                kiss_tnc = apex.kiss.KissTcp(host=tcp_host, tcp_port=tcp_port)
109
            else:
110
                click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
111
                           click.style("""Invalid configuration, must have both com_port and baud set or tcp_host and
112
                           tcp_port set in TNC sections of configuration file""", bold=True))
113
                return
114
115
            if not config.has_option(section, 'kiss_init'):
116
                click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
117
                           click.style("""Invalid configuration, must have kiss_init set in TNC sections of
118
                           configuration file""", bold=True))
119
                return
120
            kiss_init_string = config.get(section, 'kiss_init')
121
            if kiss_init_string == 'MODE_INIT_W8DED':
122
                kiss_tnc.connect(kissConstants.MODE_INIT_W8DED)
123
            elif kiss_init_string == 'MODE_INIT_KENWOOD_D710':
124
                kiss_tnc.connect(kissConstants.MODE_INIT_KENWOOD_D710)
125
            elif kiss_init_string == 'NONE':
126
                kiss_tnc.connect()
127
            else:
128
                click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
129
                           click.style('Invalid configuration, value assigned to kiss_init was not recognized: %s'
130
                                       % kiss_init_string, bold=True))
131
                return
132
133
            aprs_tnc = apex.aprs.Aprs(data_stream=kiss_tnc)
134
135
            for port in range(1, 1 + int(config.get(section, 'port_count'))):
136
                port_name = tnc_name + '-' + str(port)
137
                port_section = 'PORT ' + port_name
138
                port_identifier = config.get(port_section, 'identifier')
139
                port_net = config.get(port_section, 'net')
140
                tnc_port = int(config.get(port_section, 'tnc_port'))
141
                port_map[port_name] = {'identifier': port_identifier, 'net': port_net, 'tnc': aprs_tnc,
142
                                       'tnc_port': tnc_port}
143
144
    aprsis = None
145
    if config.has_section('APRS-IS'):
146
        aprsis_callsign = config.get('APRS-IS', 'callsign')
147
        if config.has_option('APRS-IS', 'password'):
148
            aprsis_password = config.get('APRS-IS', 'password')
149
        else:
150
            aprsis_password = -1
151
        aprsis_server = config.get('APRS-IS', 'server')
152
        aprsis_server_port = config.get('APRS-IS', 'server_port')
153
        aprsis = apex.aprs.ReconnectingPacketBuffer(apex.aprs.IGate(aprsis_callsign, aprsis_password))
154
        aprsis.connect(aprsis_server, int(aprsis_server_port))
155
156
    click.echo("Press ctrl + c at any time to exit")
157
158
    packet_cache = cachetools.TTLCache(10000, 5)
159
    # start the plugins
160
    try:
161
        plugin_loaders = get_plugins()
162
        if not len(plugin_loaders):
163
            click.echo(click.style('Warning: ', fg='yellow') +
164
                       click.style('No plugins were able to be discovered, will only display incoming messages.'))
165
        for plugin_loader in plugin_loaders:
166
            if verbose:
167
                click.echo('Plugin found at the following location: %s' % repr(plugin_loader))
168
            loaded_plugin = load_plugin(plugin_loader)
169
            plugin_modules.append(loaded_plugin)
170
            new_thread = threading.Thread(target=loaded_plugin.start, args=(config, port_map, packet_cache, aprsis))
171
            new_thread.start()
172
            plugin_threads.append(new_thread)
173
    except IOError:
174
        click.echo(click.style('Warning: ', fg='yellow') +
175
                   click.style('plugin directory not found, will only display incoming messages.'))
176
177
    def sigint_handler(signal, frame):
178
        global running
179
180
        running = False
181
182
        click.echo()
183
        click.echo('SIGINT caught, shutting down..')
184
185
        for plugin_module in plugin_modules:
186
            plugin_module.stop()
187
        if aprsis:
188
            aprsis.close()
189
        # Lets wait until all the plugins successfully end
190
        for plugin_thread in plugin_threads:
191
            plugin_thread.join()
192
        for port in port_map.values():
193
            port['tnc'].data_stream.close()
194
195
        click.echo('APEX successfully shutdown.')
196
        sys.exit(0)
197
198
    signal.signal(signal.SIGINT, sigint_handler)
199
200
    if verbose:
201
        click.echo('Starting packet processing...')
202
    while running:
203
        something_read = False
204
        try:
205
            for port_name in port_map.keys():
206
                port = port_map[port_name]
207
                frame = port['tnc'].read()
208
                if frame:
209
                    formatted_aprs = '>'.join([click.style(frame['source'], fg='green'), click.style(frame['destination'], fg='blue')])
210
                    paths = []
211
                    for path in frame['path']:
212
                        paths.append(click.style(path, fg='cyan'))
213
                    paths = ','.join(paths)
214
                    if frame['path']:
215
                        formatted_aprs = ','.join([formatted_aprs, paths])
216
                    formatted_aprs += ':'
217
                    formatted_aprs += frame['text']
218
                    click.echo(click.style(port_name + ' << ', fg='magenta') + formatted_aprs)
219
220
                    for plugin_module in plugin_modules:
221
                        something_read = True
222
                        plugin_module.handle_packet(frame, port, port_name)
223
        except Exception as ex:
224
            # We want to keep this thread alive so long as the application runs.
225
            traceback.print_exc(file=sys.stdout)
226
            click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
227
                       click.style('Caught exception while reading packet: %s' % str(ex), bold=True))
228
229
        if something_read is False:
230
            time.sleep(1)
231