Merge remote-tracking branch 'origin/modularization' into modularization

# Conflicts:
#	dmrlink.py
#	dmrlink_config.py
This commit is contained in:
Cort Buffington 2016-12-15 13:08:51 -06:00
commit 23354574d1
3 changed files with 294 additions and 9 deletions

114
dmr_utils.py Executable file
View File

@ -0,0 +1,114 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
###############################################################################
from __future__ import print_function
import os
from time import time
from urllib import URLopener
from csv import reader as csv_reader
from binascii import b2a_hex as ahex
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
#************************************************
# STRING UTILITY FUNCTIONS
#************************************************
# Create a 2 byte hex string from an integer
def hex_str_2(_int_id):
try:
return format(_int_id,'x').rjust(4,'0').decode('hex')
except TypeError:
raise
# Create a 3 byte hex string from an integer
def hex_str_3(_int_id):
try:
return format(_int_id,'x').rjust(6,'0').decode('hex')
except TypeError:
raise
# Create a 4 byte hex string from an integer
def hex_str_4(_int_id):
try:
return format(_int_id,'x').rjust(8,'0').decode('hex')
except TypeError:
raise
# Convert a hex string to an int (radio ID, etc.)
def int_id(_hex_string):
return int(ahex(_hex_string), 16)
#************************************************
# ID ALIAS FUNCTIONS
#************************************************
# Download and build dictionaries for mapping number to aliases
# Used by applications. These lookups take time, please do not shove them
# into this file everywhere and send a pull request!!!
# Download a new file if it doesn't exist, or is older than the stale time
def try_download(_path, _file, _url, _stale,):
now = time()
url = URLopener()
file_exists = os.path.isfile(_path+_file) == True
if file_exists:
file_old = (os.path.getmtime(_path+_file) + _stale) < now
if not file_exists or (file_exists and file_old):
try:
url.retrieve(_url, _path+_file)
result = 'ID ALIAS MAPPER: \'{}\' successfully downloaded'.format(_file)
except IOError:
result = 'ID ALIAS MAPPER: \'{}\' could not be downloaded'.format(_file)
else:
result = 'ID ALIAS MAPPER: \'{}\' is current, not downloaded'.format(_file)
url.close()
return result
def mk_id_dict(_path, _file):
dict = {}
try:
with open(_path+_file, 'rU') as _handle:
ids = csv_reader(_handle, dialect='excel', delimiter=',')
for row in ids:
dict[int(row[0])] = (row[1])
_handle.close
return dict
except IOError:
return dict
def get_info(_id, _dict):
if _id in _dict:
return _dict[_id]
return _id
def get_alias(_id, _dict):
_int_id = int_id(_id)
if _int_id in _dict:
return _dict[_int_id]
return _int_id

View File

@ -48,11 +48,16 @@ from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet import task from twisted.internet import task
<<<<<<< HEAD
from ipsc.ipsc_const import * from ipsc.ipsc_const import *
from ipsc.ipsc_mask import * from ipsc.ipsc_mask import *
from dmrlink_config import build_config from dmrlink_config import build_config
from dmrlink_log import config_logging from dmrlink_log import config_logging
=======
from dmr_utils import hex_str_2, hex_str_3, hex_str_4, int_id
from dmrlink_config import build_config
>>>>>>> origin/modularization
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -61,6 +66,7 @@ __license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS' __maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com' __email__ = 'n0mjs@me.com'
<<<<<<< HEAD
# Global variables used whether we are a module or __main__ # Global variables used whether we are a module or __main__
systems = {} systems = {}
@ -92,6 +98,95 @@ def config_reporting_loop(_type):
def reporting_loop(): def reporting_loop():
logger.debug('Periodic Reporting Loop Started (NULL)') logger.debug('Periodic Reporting Loop Started (NULL)')
=======
# Global variables for all class instances
systems = {}
#************************************************
# CONFIGURE THE SYSTEM LOGGER
#************************************************
def config_logging(_logger):
dictConfig({
'version': 1,
'disable_existing_loggers': False,
'filters': {
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'timed': {
'format': '%(levelname)s %(asctime)s %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'
},
'syslog': {
'format': '%(name)s (%(process)d): %(levelname)s %(message)s'
}
},
'handlers': {
'null': {
'class': 'logging.NullHandler'
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'console-timed': {
'class': 'logging.StreamHandler',
'formatter': 'timed'
},
'file': {
'class': 'logging.FileHandler',
'formatter': 'simple',
'filename': _logger['LOG_FILE'],
},
'file-timed': {
'class': 'logging.FileHandler',
'formatter': 'timed',
'filename': _logger['LOG_FILE'],
},
'syslog': {
'class': 'logging.handlers.SysLogHandler',
'formatter': 'syslog',
}
},
'loggers': {
_logger['LOG_NAME']: {
'handlers': _logger['LOG_HANDLERS'].split(','),
'level': _logger['LOG_LEVEL'],
'propagate': True,
}
}
})
return logging.getLogger(_logger['LOG_NAME'])
#************************************************
# IMPORTING OTHER FILES - '#include'
#************************************************
# Import IPSC message types and version information
#
try:
from ipsc.ipsc_const import *
except ImportError:
sys.exit('IPSC message types file not found or invalid')
# Import IPSC flag mask values
#
try:
from ipsc.ipsc_mask import *
except ImportError:
sys.exit('IPSC mask values file not found or invalid')
#************************************************
# UTILITY FUNCTIONS FOR INTERNAL USE
#************************************************
>>>>>>> origin/modularization
# Determine if the provided peer ID is valid for the provided network # Determine if the provided peer ID is valid for the provided network
# #
@ -321,6 +416,56 @@ def print_master(_network):
print('\t\tStatus: {}, KeepAlives Sent: {}, KeepAlives Outstanding: {}, KeepAlives Missed: {}' .format(_master['STATUS']['CONNECTED'], _master['STATUS']['KEEP_ALIVES_SENT'], _master['STATUS']['KEEP_ALIVES_OUTSTANDING'], _master['STATUS']['KEEP_ALIVES_MISSED'])) print('\t\tStatus: {}, KeepAlives Sent: {}, KeepAlives Outstanding: {}, KeepAlives Missed: {}' .format(_master['STATUS']['CONNECTED'], _master['STATUS']['KEEP_ALIVES_SENT'], _master['STATUS']['KEEP_ALIVES_OUTSTANDING'], _master['STATUS']['KEEP_ALIVES_MISSED']))
print('\t\t KeepAlives Received: {}, Last KeepAlive Received at: {}' .format(_master['STATUS']['KEEP_ALIVES_RECEIVED'], _master['STATUS']['KEEP_ALIVE_RX_TIME'])) print('\t\t KeepAlives Received: {}, Last KeepAlive Received at: {}' .format(_master['STATUS']['KEEP_ALIVES_RECEIVED'], _master['STATUS']['KEEP_ALIVE_RX_TIME']))
<<<<<<< HEAD
=======
# Timed loop used for reporting IPSC status
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
def config_reports(_config):
global reporting_loop
if _config['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (PICKLE)')
try:
with open(_config['REPORTS']['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
pickle_dump(_config['SYSTEMS'], file, 2)
file.close()
except IOError as detail:
logger.error('I/O Error: %s', detail)
elif _config['REPORTS']['REPORT_NETWORKS'] == 'PRINT':
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (PRINT)')
for system in _config['SYSTEMS']:
print_master(system)
print_peer_list(system)
else:
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (NULL)')
# Shut ourselves down gracefully with the IPSC peers.
#
def handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems:
this_ipsc = systems[system]
logger.info('De-Registering from IPSC %s', system)
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
this_ipsc.send_to_ipsc(de_reg_req_pkt)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, handler)
>>>>>>> origin/modularization
#************************************************ #************************************************
# IPSC CLASS # IPSC CLASS
@ -816,11 +961,13 @@ class IPSC(DatagramProtocol):
return return
return return
# MOTOROLA XCMP/XNL CONTROL PROTOCOL: We don't process these (yet) # MOTOROLA XCMP/XNL CONTROL PROTOCOL: We don't process these (yet)
elif _packettype == XCMP_XNL: elif _packettype == XCMP_XNL:
self.xcmp_xnl(self._network, data) self.xcmp_xnl(self._network, data)
return return
# ORIGINATED BY PEERS, NOT IPSC MAINTENANCE: Call monitoring is all we've found here so far # ORIGINATED BY PEERS, NOT IPSC MAINTENANCE: Call monitoring is all we've found here so far
elif _packettype == CALL_MON_STATUS: elif _packettype == CALL_MON_STATUS:
self.call_mon_status(self._network, data) self.call_mon_status(self._network, data)
@ -834,6 +981,7 @@ class IPSC(DatagramProtocol):
self.call_mon_nack(self._network, data) self.call_mon_nack(self._network, data)
return return
# IPSC CONNECTION MAINTENANCE MESSAGES # IPSC CONNECTION MAINTENANCE MESSAGES
elif _packettype == DE_REG_REQ: elif _packettype == DE_REG_REQ:
de_register_peer(self._network, _peerid) de_register_peer(self._network, _peerid)
@ -850,9 +998,8 @@ class IPSC(DatagramProtocol):
return return
return return
#
# THE FOLLOWING PACKETS ARE RECEIVED ONLY IF WE ARE OPERATING AS A PEER # THE FOLLOWING PACKETS ARE RECEIVED ONLY IF WE ARE OPERATING AS A PEER
#
# ONLY ACCEPT FROM A PREVIOUSLY VALIDATED PEER # ONLY ACCEPT FROM A PREVIOUSLY VALIDATED PEER
if _packettype in PEER_REQUIRED: if _packettype in PEER_REQUIRED:
@ -898,7 +1045,6 @@ class IPSC(DatagramProtocol):
return return
return return
# THIS MEANS WE HAVE SUCCESSFULLY REGISTERED TO OUR MASTER - RECORD MASTER INFORMATION # THIS MEANS WE HAVE SUCCESSFULLY REGISTERED TO OUR MASTER - RECORD MASTER INFORMATION
elif _packettype == MASTER_REG_REPLY: elif _packettype == MASTER_REG_REPLY:
self.master_reg_reply(data, _peerid) self.master_reg_reply(data, _peerid)
@ -906,7 +1052,6 @@ class IPSC(DatagramProtocol):
# THE FOLLOWING PACKETS ARE RECEIVED ONLLY IF WE ARE OPERATING AS A MASTER # THE FOLLOWING PACKETS ARE RECEIVED ONLLY IF WE ARE OPERATING AS A MASTER
# REQUESTS FROM PEERS: WE MUST REPLY IMMEDIATELY FOR IPSC MAINTENANCE # REQUESTS FROM PEERS: WE MUST REPLY IMMEDIATELY FOR IPSC MAINTENANCE
# REQUEST TO REGISTER TO THE IPSC # REQUEST TO REGISTER TO THE IPSC
@ -924,7 +1069,10 @@ class IPSC(DatagramProtocol):
self.peer_list_req(_peerid) self.peer_list_req(_peerid)
return return
<<<<<<< HEAD
=======
>>>>>>> origin/modularization
# PACKET IS OF AN UNKNOWN TYPE. LOG IT AND IDENTTIFY IT! # PACKET IS OF AN UNKNOWN TYPE. LOG IT AND IDENTTIFY IT!
else: else:
self.unknown_message(self._network, _packettype, _peerid, data) self.unknown_message(self._network, _packettype, _peerid, data)
@ -949,11 +1097,20 @@ if __name__ == '__main__':
if not cli_args.CFG_FILE: if not cli_args.CFG_FILE:
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg' cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
<<<<<<< HEAD
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = build_config(cli_args.CFG_FILE) CONFIG = build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger # Call the external routing to start the system logger
logger = config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
=======
CONFIG = build_config(cli_args.CFG_FILE)
logger = config_logging(CONFIG['LOGGER'])
config_reports(CONFIG)
>>>>>>> origin/modularization
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...') logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Shut ourselves down gracefully with the IPSC peers. # Shut ourselves down gracefully with the IPSC peers.

View File

@ -23,6 +23,7 @@ import sys
from socket import gethostbyname from socket import gethostbyname
<<<<<<< HEAD
# Does anybody read this stuff? There's a PEP somewhere that says I should do this. # Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -30,6 +31,14 @@ __license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS' __maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com' __email__ = 'n0mjs@me.com'
=======
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
__credits__ = 'Adam Fast, KC0YLK; Dave Kierzkowski, KD8EYF; Steve Zingman, N4IRS; Mike Zingman, N4IRR'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
>>>>>>> origin/modularization
def build_config(_config_file): def build_config(_config_file):
config = ConfigParser.ConfigParser() config = ConfigParser.ConfigParser()
@ -178,6 +187,7 @@ def build_config(_config_file):
return CONFIG return CONFIG
<<<<<<< HEAD
# Used to run this file direclty and print the config, # Used to run this file direclty and print the config,
# which might be useful for debugging # which might be useful for debugging
if __name__ == '__main__': if __name__ == '__main__':
@ -201,3 +211,7 @@ if __name__ == '__main__':
pprint(build_config(cli_args.CONFIG_FILE)) pprint(build_config(cli_args.CONFIG_FILE))
=======
if __name__ == '__main__':
pass
>>>>>>> origin/modularization