Completed
Push — master ( 06942a...cb25ba )
by Jeffrey
04:08
created

sigint_handler()   A

Complexity

Conditions 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 4
rs 10
cc 2
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.pluginloader import getPlugins
35
from apex.pluginloader import loadPlugin
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
53
def find_config(config_paths, verbose):
54
    config_file = 'apex.conf'
55
    rc_file = '.apexrc'
56
    cur_path = os.path.join(os.curdir, config_file)
57
    home_path = os.path.join(os.path.expanduser("~"), rc_file)
58
    etc_path = os.path.join('etc', config_file)
59
    if config_paths is None:
60
        config_paths = [cur_path, home_path, etc_path]
61
    elif isinstance(config_paths, str):
62
        config_paths = [config_paths]
63
    elif not isinstance(config_paths, list):
64
        raise TypeError('config_paths argument was neither a string nor a list')
65
66
    if verbose:
67
        click.echo('Searching for configuration file in the following locations: %s' % repr(config_paths))
68
69
    config = configparser.ConfigParser()
70
    for config_path in config_paths:
71
        try:
72
            if len(config.read(config_path)) > 0:
73
                return config
74
        except IOError:
75
            pass
76
77
    return None
78
79
80
@click.command(context_settings=dict(auto_envvar_prefix='APEX'))
81
@click.option('-c',
82
              '--configfile',
83
              type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True),
84
              help='Configuration file for APEX.')
85
@click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode.')
86
def main(verbose, configfile):
87
88
    port_map = {}
89
    config = find_config(configfile, verbose)
90
    if config is None:
91
        click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
92
                   click.style('No apex configuration found, can not continue.', bold=True))
93
        return
94
    for section in config.sections():
95
        if section.startswith("TNC "):
96
            tnc_name = section.split(" ")[1]
97
            if config.has_option(section, 'com_port') and config.has_option(section, 'baud'):
98
                com_port = config.get(section, 'com_port')
99
                baud = config.get(section, 'baud')
100
                kiss_tnc = apex.aprs.AprsKiss(com_port=com_port, baud=baud)
101
            elif config.has_option(section, 'tcp_host') and config.has_option(section, 'tcp_port'):
102
                tcp_host = config.get(section, 'tcp_host')
103
                tcp_port = config.get(section, 'tcp_port')
104
                kiss_tnc = apex.aprs.AprsKiss(host=tcp_host, tcp_port=tcp_port)
105
            else:
106
                click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
107
                           click.style("""Invalid configuration, must have both com_port and baud set or tcp_host and
108
                           tcp_port set in TNC sections of configuration file""", bold=True))
109
                return
110
111
            if not config.has_option(section, 'kiss_init'):
112
                click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
113
                           click.style("""Invalid configuration, must have kiss_init set in TNC sections of
114
                           configuration file""", bold=True))
115
                return
116
            kiss_init_string = config.get(section, 'kiss_init')
117
            if kiss_init_string == 'MODE_INIT_W8DED':
118
                kiss_tnc.start(kissConstants.MODE_INIT_W8DED)
119
            elif kiss_init_string == 'MODE_INIT_KENWOOD_D710':
120
                kiss_tnc.start(kissConstants.MODE_INIT_KENWOOD_D710)
121
            elif kiss_init_string == 'NONE':
122
                kiss_tnc.start()
123
            else:
124
                click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
125
                           click.style('Invalid configuration, value assigned to kiss_init was not recognized: %s'
126
                                       % kiss_init_string, bold=True))
127
                return
128
            for port in range(1, 1 + int(config.get(section, 'port_count'))):
129
                port_name = tnc_name + '-' + str(port)
130
                port_section = 'PORT ' + port_name
131
                port_identifier = config.get(port_section, 'identifier')
132
                port_net = config.get(port_section, 'net')
133
                tnc_port = int(config.get(port_section, 'tnc_port'))
134
                port_map[port_name] = {'identifier': port_identifier, 'net': port_net, 'tnc': kiss_tnc,
135
                                       'tnc_port': tnc_port}
136
    if config.has_section('APRS-IS'):
137
        aprsis_callsign = config.get('APRS-IS', 'callsign')
138
        if config.has_option('APRS-IS', 'password'):
139
            aprsis_password = config.get('APRS-IS', 'password')
140
        else:
141
            aprsis_password = -1
142
        aprsis_server = config.get('APRS-IS', 'server')
143
        aprsis_server_port = config.get('APRS-IS', 'server_port')
144
        aprsis = apex.aprs.AprsInternetService(aprsis_callsign, aprsis_password)
145
        aprsis.connect(aprsis_server, int(aprsis_server_port))
146
147
    def sigint_handler(signal, frame):
148
        for port in port_map.values():
149
            port['tnc'].close()
150
        sys.exit(0)
151
152
    signal.signal(signal.SIGINT, sigint_handler)
153
154
    click.echo("Press ctrl + c at any time to exit")
155
156
    packet_cache = cachetools.TTLCache(10000, 5)
157
    # start the plugins
158
    plugins = []
159
    try:
160
        plugin_loaders = getPlugins()
161
        if not len(plugin_loaders):
162
            click.echo(click.style('Warning: ', fg='yellow') +
163
                       click.style("No plugins were able to be discovered, will only display incoming messages."))
164
        for plugin_loader in plugin_loaders:
165
            if verbose:
166
                click.echo('Plugin found at the following location: %s' % repr(plugin_loader))
167
            loaded_plugin = loadPlugin(plugin_loader)
168
            plugins.append(loaded_plugin)
169
            threading.Thread(target=loaded_plugin.start, args=(config, port_map, packet_cache, aprsis)).start()
170
    except IOError:
171
        click.echo(click.style('Warning: ', fg='yellow') +
172
                   click.style('plugin directory not found, will only display incoming messages.'))
173
174
    if verbose:
175
        click.echo('Starting packet processing...')
176
    while 1:
177
        something_read = False
178
        try:
179
            for port_name in port_map.keys():
180
                port = port_map[port_name]
181
                frame = port['tnc'].read()
182
                if frame:
183
                    formatted_aprs = '>'.join([click.style(frame['source'], fg='green'), click.style(frame['destination'], fg='blue')])
184
                    paths = []
185
                    for path in frame['path']
186
                        paths += click.style(path, fg='magenta')
187
                    paths = ','.join(paths)
188
                    if frame['path']:
189
                        formatted_aprs = ','.join([formatted_aprs, paths])
190
                    formatted_aprs += ':'
191
                    formatted_aprs += frame['text']
192
                    click.echo(click.style(port_name + ' << ', fg='magenta') + formatted_aprs)
193
194
                    for plugin in plugins:
195
                        something_read = True
196
                        plugin.handle_packet(frame, port, port_name)
197
        except Exception as ex:
198
            # We want to keep this thread alive so long as the application runs.
199
            traceback.print_exc(file=sys.stdout)
200
            click.echo(click.style('Error: ', fg='red', bold=True, blink=True) +
201
                       click.style('Caught exception while reading packet: %s' % str(ex), bold=True))
202
203
        if something_read is False:
204
            time.sleep(1)
205