Merge branch 'socket-reporting'

This commit is contained in:
Cort Buffington 2017-05-27 08:59:21 -05:00
commit fa8e53bd81
23 changed files with 742 additions and 64765 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ sub_acl.py
*.config *.config
*.json *.json
*.pickle *.pickle
*.csv

View File

@ -29,7 +29,7 @@ from bitstring import BitArray
import sys, socket, ConfigParser, thread, traceback import sys, socket, ConfigParser, thread, traceback
import cPickle as pickle import cPickle as pickle
from dmrlink import IPSC, systems from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, build_aliases, config_reports
from dmr_utils.utils import int_id, hex_str_3, hex_str_4, get_alias, get_info from dmr_utils.utils import int_id, hex_str_3, hex_str_4, get_alias, get_info
from time import time, sleep, clock, localtime, strftime from time import time, sleep, clock, localtime, strftime
@ -93,8 +93,8 @@ class ambeIPSC(IPSC):
#_d = None #_d = None
###### DEBUGDEBUGDEBUG ###### DEBUGDEBUGDEBUG
def __init__(self, _name, _config, _logger): def __init__(self, _name, _config, _logger, _report):
IPSC.__init__(self, _name, _config, _logger) IPSC.__init__(self, _name, _config, _logger, _report)
self.CALL_DATA = [] self.CALL_DATA = []
# #
@ -622,13 +622,12 @@ def get_subscriber_info(_src_sub):
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
import os
import sys import sys
import os
import signal import signal
from dmr_utils.utils import try_download, mk_id_dict
import dmrlink_log from ipsc.dmrlink_config import build_config
import dmrlink_config from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -644,60 +643,36 @@ if __name__ == '__main__':
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'
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = dmrlink_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
if cli_args.LOG_LEVEL: if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS: if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = dmrlink_log.config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
logger.info('DMRlink \'ambe_audio.py\' (c) 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
# ID ALIAS CREATION # Set signal handers so that we can gracefully exit if need be
# Download
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, reportFactory)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC # Build ID Aliases
for system in CONFIG['SYSTEMS']: peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = ambeIPSC(system, CONFIG, logger) # INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP']) systems = mk_ipsc_systems(CONFIG, logger, systems, ambeIPSC, report_server)
reactor.run()
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run()

View File

@ -54,7 +54,7 @@ import sys
from dmr_utils.utils import hex_str_3, hex_str_4, int_id from dmr_utils.utils import hex_str_3, hex_str_4, int_id
from dmrlink import IPSC, systems, config_reports from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, REPORT_OPCODES, build_aliases, config_reports
from ipsc.ipsc_const import BURST_DATA_TYPE from ipsc.ipsc_const import BURST_DATA_TYPE
@ -70,6 +70,7 @@ __email__ = 'n0mjs@me.com'
# #
TS_CLEAR_TIME = .2 TS_CLEAR_TIME = .2
# Import Bridging rules # Import Bridging rules
# Note: A stanza *must* exist for any IPSC configured in the main # Note: A stanza *must* exist for any IPSC configured in the main
# configuration file and listed as "active". It can be empty, # configuration file and listed as "active". It can be empty,
@ -158,7 +159,7 @@ def build_acl(_sub_acl):
return ACL return ACL
# Run this every minute for rule timer updates # Run this every minute for rule timer updates
def rule_timer_loop(): def rule_timer_loop():
logger.debug('(ALL IPSC) Rule timer loop started') logger.debug('(ALL IPSC) Rule timer loop started')
@ -186,9 +187,9 @@ def rule_timer_loop():
class bridgeIPSC(IPSC): class bridgeIPSC(IPSC):
def __init__(self, _name, _config, _logger, _bridges): def __init__(self, _name, _config, _logger, report):
IPSC.__init__(self, _name, _config, _logger) IPSC.__init__(self, _name, _config, _logger, report)
self.BRIDGES = _bridges self.BRIDGES = BRIDGES
if self.BRIDGES: if self.BRIDGES:
self._logger.info('(%s) Initializing backup/polite bridging', self._system) self._logger.info('(%s) Initializing backup/polite bridging', self._system)
self.BRIDGE = False self.BRIDGE = False
@ -438,15 +439,14 @@ class bridgeIPSC(IPSC):
# Send the packet to all peers in the target IPSC # Send the packet to all peers in the target IPSC
systems[target].send_to_ipsc(_tmp_data) systems[target].send_to_ipsc(_tmp_data)
if __name__ == '__main__':
if __name__ == '__main__':
import argparse import argparse
import sys
import os import os
import signal import signal
from dmr_utils.utils import try_download, mk_id_dict
import dmrlink_log from ipsc.dmrlink_config import build_config
import dmrlink_config from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -460,39 +460,32 @@ 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'
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = dmrlink_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
if cli_args.LOG_LEVEL: if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS: if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = dmrlink_log.config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Call the external routing to start the system logger
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
config_reports(CONFIG) # Set signal handers so that we can gracefully exit if need be
logger.info('DMRlink \'bridge.py\' (c) 2013-2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# BRIDGE.PY SPECIFIC ITEMS GO HERE:
# Build the routing rules file # Build the routing rules file
RULES = build_rules('bridge_rules') RULES = build_rules('bridge_rules')
@ -501,22 +494,25 @@ if __name__ == '__main__':
# Build the Access Control List # Build the Access Control List
ACL = build_acl('sub_acl') ACL = build_acl('sub_acl')
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = bridgeIPSC(system, CONFIG, logger, BRIDGES)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
if CONFIG['REPORTS']['REPORT_NETWORKS']:
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED # INITIALIZE THE REPORTING LOOP IF CONFIGURED
rule_timer = task.LoopingCall(rule_timer_loop) rule_timer = task.LoopingCall(rule_timer_loop)
rule_timer.start(60) rule_timer.start(60)
# MAIN INITIALIZATION ITEMS HERE
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, reportFactory)
# Build ID Aliases
peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGURED IPSC
systems = mk_ipsc_systems(CONFIG, logger, systems, bridgeIPSC, report_server)
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run() reactor.run()

View File

@ -44,20 +44,21 @@
# Use to make test strings: #print('PKT:', "\\x".join("{:02x}".format(ord(c)) for c in _data)) # Use to make test strings: #print('PKT:', "\\x".join("{:02x}".format(ord(c)) for c in _data))
from __future__ import print_function from __future__ import print_function
from twisted.internet.protocol import Factory, Protocol from twisted.internet.protocol import Factory, Protocol
from twisted.protocols.basic import NetstringReceiver from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet import task from twisted.internet import task
from binascii import b2a_hex as ahex from binascii import b2a_hex as ahex
from time import time from time import time
from importlib import import_module from importlib import import_module
import cPickle as pickle
import sys import cPickle as pickle
from dmr_utils.utils import hex_str_3, hex_str_4, int_id from dmr_utils.utils import hex_str_3, hex_str_4, int_id
from dmrlink import IPSC, systems, config_reports, hmac_new, sha1 from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, REPORT_OPCODES, build_aliases
from ipsc.ipsc_const import BURST_DATA_TYPE from ipsc.ipsc_const import BURST_DATA_TYPE
@ -73,6 +74,47 @@ __email__ = 'n0mjs@me.com'
# #
TS_CLEAR_TIME = .2 TS_CLEAR_TIME = .2
# Declare this here so that we can define functions around it
#
BRIDGES = {}
# Timed loop used for reporting IPSC status
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
def config_reports(_config, _logger, _factory):
if _config['REPORTS']['REPORT_NETWORKS'] == 'PRINT':
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (PRINT)')
for system in _config['SYSTEMS']:
print_master(_config, system)
print_peer_list(_config, system)
reporting = task.LoopingCall(reporting_loop, _logger)
reporting.start(_config['REPORTS']['REPORT_INTERVAL'])
report_server = False
elif _config['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
def reporting_loop(_logger, _server):
_logger.debug('Periodic Reporting Loop Started (NETWORK)')
_server.send_config()
_server.send_bridge()
_logger.info('DMRlink TCP reporting server starting')
report_server = _factory(_config, _logger)
report_server.clients = []
reactor.listenTCP(_config['REPORTS']['REPORT_PORT'], report_server)
reporting = task.LoopingCall(reporting_loop, _logger, report_server)
reporting.start(_config['REPORTS']['REPORT_INTERVAL'])
else:
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (NULL)')
report_server = False
return report_server
# Build the conference bridging structure from the bridge file. # Build the conference bridging structure from the bridge file.
# #
def make_bridge_config(_confbridge_rules): def make_bridge_config(_confbridge_rules):
@ -87,9 +129,7 @@ def make_bridge_config(_confbridge_rules):
# #
for _bridge in bridge_file.BRIDGES: for _bridge in bridge_file.BRIDGES:
for _system in bridge_file.BRIDGES[_bridge]: for _system in bridge_file.BRIDGES[_bridge]:
from pprint import pprint
if _system['SYSTEM'] not in CONFIG['SYSTEMS']: if _system['SYSTEM'] not in CONFIG['SYSTEMS']:
print(_system['SYSTEM'])
sys.exit('ERROR: Conference bridges found for system not configured main configuration') sys.exit('ERROR: Conference bridges found for system not configured main configuration')
_system['TGID'] = hex_str_3(_system['TGID']) _system['TGID'] = hex_str_3(_system['TGID'])
@ -140,39 +180,7 @@ def build_acl(_sub_acl):
return True return True
return ACL return ACL
# Timed loop used for reporting IPSC status
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
def config_reports(_config):
if _config['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
def reporting_loop(_logger):
_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):
_logger.debug('Periodic Reporting Loop Started (PRINT)')
for system in _config['SYSTEMS']:
print_master(_config, system)
print_peer_list(_config, system)
elif _config['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
def reporting_loop(_logger, _server):
_logger.debug('Periodic Reporting Loop Started (NETWORK)')
_server.send_config()
_server.send_bridge()
else:
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (NULL)')
return reporting_loop
# Run this every minute for rule timer updates # Run this every minute for rule timer updates
def rule_timer_loop(): def rule_timer_loop():
@ -204,20 +212,13 @@ def rule_timer_loop():
else: else:
logger.debug('Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) logger.debug('Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']))
if BRIDGE_CONF['REPORT'] == 'pickle': if BRIDGE_CONF['REPORT'] == 'network':
try:
with open(CONFIG['REPORTS']['REPORT_PATH']+'confbridge_stats.pickle', 'wb') as file:
pickle_dump(BRIDGES, file, 2)
file.close()
except IOError as detail:
_logger.error('I/O Error: %s', detail)
elif BRIDGE_CONF['REPORT'] == 'network':
report_server.send_clients('bridge updated') report_server.send_clients('bridge updated')
class confbridgeIPSC(IPSC): class confbridgeIPSC(IPSC):
def __init__(self, _name, _config, _logger): def __init__(self, _name, _config, _logger, _report):
IPSC.__init__(self, _name, _config, _logger) IPSC.__init__(self, _name, _config, _logger, _report)
self.STATUS = { self.STATUS = {
1: {'RX_TGID':'\x00', 'TX_TGID':'\x00', 'RX_TIME':0, 'TX_TIME':0, 'RX_SRC_SUB':'\x00', 'TX_SRC_SUB':'\x00'}, 1: {'RX_TGID':'\x00', 'TX_TGID':'\x00', 'RX_TIME':0, 'TX_TIME':0, 'RX_SRC_SUB':'\x00', 'TX_SRC_SUB':'\x00'},
@ -346,18 +347,25 @@ class confbridgeIPSC(IPSC):
# Action happens on key up # Action happens on key up
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
if self.last_seq_id != _seq_id: if self.last_seq_id != _seq_id or (self.call_start + TS_CLEAR_TIME) < now:
self.last_seq_id = _seq_id self.last_seq_id = _seq_id
self.call_start = time() self.call_start = now
self._logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)) self._logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
if self._CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
self._report.send_bridgeEvent('({}) GROUP VOICE START: CallID: {} PEER: {}, SUB: {}, TS: {}, TGID: {}'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
# Action happens on un-key # Action happens on un-key
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']: if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
if self.last_seq_id == _seq_id: if self.last_seq_id == _seq_id:
self.call_duration = time() - self.call_start self.call_duration = now - self.call_start
self._logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration) self._logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
if self._CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
self._report.send_bridgeEvent('({}) GROUP VOICE END: CallID: {} PEER: {}, SUB: {}, TS: {}, TGID: {} Duration: {:.2f}s'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration))
else: else:
self._logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),) self._logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
if self._CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
self._report.send_bridgeEvent('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
# Iterate the rules dictionary # Iterate the rules dictionary
for _bridge in BRIDGES: for _bridge in BRIDGES:
@ -402,66 +410,25 @@ class confbridgeIPSC(IPSC):
# END IN-BAND SIGNALLING # END IN-BAND SIGNALLING
# #
class confbridgeReportFactory(reportFactory):
#
# Socket-based reporting section
#
class report(NetstringReceiver):
def __init__(self):
pass
def connectionMade(self):
report_server.clients.append(self)
logger.info('DMRlink reporting client connected: %s', self.transport.getPeer())
def connectionLost(self, reason):
logger.info('DMRlink reporting client disconnected: %s', self.transport.getPeer())
report_server.clients.remove(self)
def stringReceived(self, data):
self.process_message(data)
def process_message(self, _message):
opcode = _message[:1]
if opcode == REP_OPC['CONFIG_REQ']:
logger.info('DMRlink reporting client sent \'CONFIG_REQ\': %s', self.transport.getPeer())
self.send_config()
else:
print('got unknown opcode')
class reportFactory(Factory):
def __init__(self):
pass
def buildProtocol(self, addr):
if (addr.host) in CONFIG['REPORTS']['REPORT_CLIENTS']:
return report()
else:
return None
def send_clients(self, _message):
for client in report_server.clients:
client.sendString(_message)
def send_config(self):
serialized = pickle.dumps(CONFIG['SYSTEMS'], protocol=pickle.HIGHEST_PROTOCOL)
self.send_clients(REP_OPC['CONFIG_SND']+serialized)
def send_bridge(self): def send_bridge(self):
serialized = pickle.dumps(BRIDGES, protocol=pickle.HIGHEST_PROTOCOL) serialized = pickle.dumps(BRIDGES, protocol=pickle.HIGHEST_PROTOCOL)
self.send_clients(REP_OPC['BRIDGE_SND']+serialized) self.send_clients(REPORT_OPCODES['BRIDGE_SND']+serialized)
def send_bridgeEvent(self, _data):
self.send_clients(REPORT_OPCODES['BRDG_EVENT']+_data)
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
import sys
import os import os
import signal import signal
from dmr_utils.utils import try_download, mk_id_dict
from ipsc.dmrlink_config import build_config
import dmrlink_log from ipsc.dmrlink_log import config_logging
import dmrlink_config
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -474,58 +441,40 @@ 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'
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = dmrlink_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
if cli_args.LOG_LEVEL: if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS: if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = dmrlink_log.config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
config_reports(CONFIG) # Set signal handers so that we can gracefully exit if need be
logger.info('DMRlink \'confbridge.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# ID ALIAS CREATION # INITIALIZE THE REPORTING LOOP
# Download report_server = config_reports(CONFIG, logger, confbridgeReportFactory)
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE']) # Build ID Aliases
if talkgroup_ids: peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGURED IPSC
systems = mk_ipsc_systems(CONFIG, logger, systems, confbridgeIPSC, report_server)
# CONFBRIDGE.PY SPECIFIC ITEMS GO HERE:
# Build the routing rules and other configuration # Build the routing rules and other configuration
CONFIG_DICT = make_bridge_config('confbridge_rules') CONFIG_DICT = make_bridge_config('confbridge_rules')
@ -534,34 +483,10 @@ if __name__ == '__main__':
# Build the Access Control List # Build the Access Control List
ACL = build_acl('sub_acl') ACL = build_acl('sub_acl')
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = confbridgeIPSC(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
if CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PRINT' or CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE NETWORK-BASED REPORTING SERVER
if CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
logger.info('(confbridge.py) TCP reporting server starting')
from ipsc.reporting_const import REPORT_OPCODES as REP_OPC
report_server = reportFactory()
report_server.clients = []
reactor.listenTCP(CONFIG['REPORTS']['REPORT_PORT'], reportFactory())
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger, report_server)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
rule_timer = task.LoopingCall(rule_timer_loop)
rule_timer.start(60)
# Initialize the rule timer loop
rule_timer = task.LoopingCall(rule_timer_loop)
rule_timer.start(60)
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run() reactor.run()

View File

@ -25,17 +25,11 @@
from __future__ import print_function from __future__ import print_function
import ConfigParser # Full imports
import argparse
import sys
import csv
import os
import logging import logging
import signal
import cPickle as pickle import cPickle as pickle
from logging.config import dictConfig # Function Imports
from hmac import new as hmac_new from hmac import new as hmac_new
from binascii import b2a_hex as ahex from binascii import b2a_hex as ahex
from binascii import a2b_hex as bhex from binascii import a2b_hex as bhex
@ -44,15 +38,18 @@ from socket import inet_ntoa as IPAddr
from socket import inet_aton as IPHexStr from socket import inet_aton as IPHexStr
from time import time from time import time
# Twisted Imports
from twisted.internet.protocol import DatagramProtocol, Factory, Protocol from twisted.internet.protocol import DatagramProtocol, Factory, Protocol
from twisted.protocols.basic import NetstringReceiver from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task from twisted.internet import reactor, task
# Imports files in the dmrlink subdirectory (these things shouldn't change often)
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 ipsc.reporting_const import *
from dmrlink_log import config_logging
from dmr_utils.utils import hex_str_2, hex_str_3, hex_str_4, int_id # Imports from DMR Utilities package
from dmr_utils.utils import hex_str_2, hex_str_3, hex_str_4, int_id, try_download, mk_id_dict
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
@ -66,37 +63,83 @@ __email__ = 'n0mjs@me.com'
# Global variables used whether we are a module or __main__ # Global variables used whether we are a module or __main__
systems = {} systems = {}
# Timed loop used for reporting IPSC status # Timed loop used for reporting IPSC status
# #
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE # REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
def config_reports(_config): def config_reports(_config, _logger, _factory):
if _config['REPORTS']['REPORT_NETWORKS'] == 'PICKLE': if _config['REPORTS']['REPORT_NETWORKS'] == 'PRINT':
def reporting_loop(_logger):
_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): def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (PRINT)') _logger.debug('Periodic Reporting Loop Started (PRINT)')
for system in _config['SYSTEMS']: for system in _config['SYSTEMS']:
print_master(_config, system) print_master(_config, system)
print_peer_list(_config, system) print_peer_list(_config, system)
reporting = task.LoopingCall(reporting_loop, _logger)
reporting.start(_config['REPORTS']['REPORT_INTERVAL'])
report_server = False
elif _config['REPORTS']['REPORT_NETWORKS'] == 'NETWORK': elif _config['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
def reporting_loop(_logger, _server): def reporting_loop(_logger, _server):
_logger.debug('Periodic Reporting Loop Started (NETWORK)') _logger.debug('Periodic Reporting Loop Started (NETWORK)')
_server.send_config() _server.send_config()
_logger.info('DMRlink TCP reporting server starting')
report_server = _factory(_config, _logger)
report_server.clients = []
reactor.listenTCP(_config['REPORTS']['REPORT_PORT'], report_server)
reporting = task.LoopingCall(reporting_loop, _logger, report_server)
reporting.start(_config['REPORTS']['REPORT_INTERVAL'])
else: else:
def reporting_loop(_logger): def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (NULL)') _logger.debug('Periodic Reporting Loop Started (NULL)')
report_server = False
return reporting_loop return report_server
# ID ALIAS CREATION
# Download
def build_aliases(_config, _logger):
if _config['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(_config['ALIASES']['PATH'], _config['ALIASES']['PEER_FILE'], _config['ALIASES']['PEER_URL'], _config['ALIASES']['STALE_TIME'])
_logger.info(result)
# Try updating subscriber aliases file
result = try_download(_config['ALIASES']['PATH'], _config['ALIASES']['SUBSCRIBER_FILE'], _config['ALIASES']['SUBSCRIBER_URL'], _config['ALIASES']['STALE_TIME'])
_logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['PEER_FILE'])
if peer_ids:
_logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
_logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['TGID_FILE'])
if talkgroup_ids:
_logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
local_ids = mk_id_dict(_config['ALIASES']['PATH'], _config['ALIASES']['LOCAL_FILE'])
if local_ids:
_logger.info('ID ALIAS MAPPER: local_ids dictionary is available')
return(peer_ids, subscriber_ids, talkgroup_ids, local_ids)
# Make the IPSC systems from the config and the class used to build them.
#
def mk_ipsc_systems(_config, _logger, _systems, _ipsc, _report_server):
for system in _config['SYSTEMS']:
if _config['SYSTEMS'][system]['LOCAL']['ENABLED']:
_systems[system] = _ipsc(system, _config, _logger, _report_server)
reactor.listenUDP(_config['SYSTEMS'][system]['LOCAL']['PORT'], _systems[system], interface=_config['SYSTEMS'][system]['LOCAL']['IP'])
return _systems
# Process the MODE byte in registration/peer list packets for determining master and peer capabilities # Process the MODE byte in registration/peer list packets for determining master and peer capabilities
# #
@ -236,7 +279,7 @@ def print_master(_config, _network):
#************************************************ #************************************************
class IPSC(DatagramProtocol): class IPSC(DatagramProtocol):
def __init__(self, _name, _config, _logger): def __init__(self, _name, _config, _logger, _report):
# Housekeeping: create references to the configuration and status data for this IPSC instance. # Housekeeping: create references to the configuration and status data for this IPSC instance.
# Some configuration objects that are used frequently and have lengthy names are shortened # Some configuration objects that are used frequently and have lengthy names are shortened
@ -246,7 +289,9 @@ class IPSC(DatagramProtocol):
self._system = _name self._system = _name
self._CONFIG = _config self._CONFIG = _config
self._logger = _logger self._logger = _logger
self._report = _report
self._config = self._CONFIG['SYSTEMS'][self._system] self._config = self._CONFIG['SYSTEMS'][self._system]
self._rcm = self._CONFIG['REPORTS']['REPORT_RCM'] and self._report
# #
self._local = self._config['LOCAL'] self._local = self._config['LOCAL']
self._local_id = self._local['RADIO_ID'] self._local_id = self._local['RADIO_ID']
@ -320,7 +365,13 @@ class IPSC(DatagramProtocol):
else: else:
self._logger.warning('(%s) Peer De-Registration Requested for: %s, but we don\'t have a listing for this peer', self._system, int_id(_peerid)) self._logger.warning('(%s) Peer De-Registration Requested for: %s, but we don\'t have a listing for this peer', self._system, int_id(_peerid))
pass pass
# De-register ourselves from the IPSC
def de_register_self(self):
self._logger.info('(%s) De-Registering self from the IPSC system', self._system)
de_reg_req_pkt = self.hashed_packet(self._local['AUTH_KEY'], self.DE_REG_REQ_PKT)
self.send_to_ipsc(de_reg_req_pkt)
# Take a received peer list and the network it belongs to, process and populate the # Take a received peer list and the network it belongs to, process and populate the
# data structure in my_ipsc_config with the results, and return a simple list of peers. # data structure in my_ipsc_config with the results, and return a simple list of peers.
# #
@ -391,15 +442,23 @@ class IPSC(DatagramProtocol):
#************************************************ #************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES # CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************ #************************************************
# If RCM reporting and reporting is network-based in the global configuration,
# send the RCM packet to the monitoring server
def call_mon_status(self, _data): def call_mon_status(self, _data):
self._logger.debug('(%s) Repeater Call Monitor Origin Packet Received: %s', self._system, ahex(_data)) self._logger.debug('(%s) Repeater Call Monitor Origin Packet Received: %s', self._system, ahex(_data))
if self._rcm:
self._report.send_rcm(self._system + ','+ _data)
def call_mon_rpt(self, _data): def call_mon_rpt(self, _data):
self._logger.debug('(%s) Repeater Call Monitor Repeating Packet Received: %s', self._system, ahex(_data)) self._logger.debug('(%s) Repeater Call Monitor Repeating Packet Received: %s', self._system, ahex(_data))
if self._rcm:
self._report.send_rcm(self._system + ',' + _data)
def call_mon_nack(self, _data): def call_mon_nack(self, _data):
self._logger.debug('(%s) Repeater Call Monitor NACK Packet Received: %s', self._system, ahex(_data)) self._logger.debug('(%s) Repeater Call Monitor NACK Packet Received: %s', self._system, ahex(_data))
if self._rcm:
self._report.send_rcm(self._system + ',' + _data)
def xcmp_xnl(self, _data): def xcmp_xnl(self, _data):
self._logger.debug('(%s) XCMP/XNL Packet Received: %s', self._system, ahex(_data)) self._logger.debug('(%s) XCMP/XNL Packet Received: %s', self._system, ahex(_data))
@ -938,45 +997,51 @@ class IPSC(DatagramProtocol):
# Socket-based reporting section # Socket-based reporting section
# #
class report(NetstringReceiver): class report(NetstringReceiver):
def __init__(self): def __init__(self, factory):
pass self._factory = factory
def connectionMade(self): def connectionMade(self):
report_server.clients.append(self) self._factory.clients.append(self)
logger.info('DMRlink reporting client connected: %s', self.transport.getPeer()) self._factory._logger.info('DMRlink reporting client connected: %s', self.transport.getPeer())
def connectionLost(self, reason): def connectionLost(self, reason):
logger.info('DMRlink reporting client disconnected: %s', self.transport.getPeer()) self._factory._logger.info('DMRlink reporting client disconnected: %s', self.transport.getPeer())
report_server.clients.remove(self) self._factory.clients.remove(self)
def stringReceived(self, data): def stringReceived(self, data):
self.process_message(data) self.process_message(data)
def process_message(self, _message): def process_message(self, _message):
opcode = _message[:1] opcode = _message[:1]
if opcode == REP_OPC['CONFIG_REQ']: if opcode == REPORT_OPCODES['CONFIG_REQ']:
logger.info('DMRlink reporting client sent \'CONFIG_REQ\': %s', self.transport.getPeer()) self._factory._logger.info('DMRlink reporting client sent \'CONFIG_REQ\': %s', self.transport.getPeer())
self.send_config() self.send_config()
else: else:
print('got unknown opcode') print('got unknown opcode')
class reportFactory(Factory): class reportFactory(Factory):
def __init__(self): def __init__(self, config, logger):
pass self._config = config
self._logger = logger
def buildProtocol(self, addr): def buildProtocol(self, addr):
if (addr.host) in CONFIG['REPORTS']['REPORT_CLIENTS']: if (addr.host) in self._config['REPORTS']['REPORT_CLIENTS'] or '*' in self._config['REPORTS']['REPORT_CLIENTS']:
return report() self._logger.debug('Permitting report server connection attempt from: %s:%s', addr.host, addr.port)
return report(self)
else: else:
self._logger.error('Invalid report server connection attempt from: %s:%s', addr.host, addr.port)
return None return None
def send_clients(self, _message): def send_clients(self, _message):
for client in report_server.clients: for client in self.clients:
client.sendString(_message) client.sendString(_message)
def send_config(self): def send_config(self):
serialized = pickle.dumps(CONFIG['SYSTEMS'], protocol=pickle.HIGHEST_PROTOCOL) serialized = pickle.dumps(self._config['SYSTEMS'], protocol=pickle.HIGHEST_PROTOCOL)
self.send_clients(REP_OPC['CONFIG_SND']+serialized) self.send_clients(REPORT_OPCODES['CONFIG_SND']+serialized)
def send_rcm(self, _data):
self.send_clients(REPORT_OPCODES['RCM_SND']+_data)
#************************************************ #************************************************
@ -984,6 +1049,13 @@ class reportFactory(Factory):
#************************************************ #************************************************
if __name__ == '__main__': if __name__ == '__main__':
import argparse
import sys
import os
import signal
from ipsc.dmrlink_config import build_config
from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -1007,50 +1079,28 @@ if __name__ == '__main__':
if cli_args.LOG_HANDLERS: if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
config_reports(CONFIG)
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. # Set signal handers so that we can gracefully exit if need be
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC # INITIALIZE THE REPORTING LOOP
for system in CONFIG['SYSTEMS']: report_server = config_reports(CONFIG, logger, reportFactory)
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = IPSC(system, CONFIG, logger) # Build ID Aliases
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP']) peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
if CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PRINT' or CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE NETWORK-BASED REPORTING SERVER # INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
elif CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK': systems = mk_ipsc_systems(CONFIG, logger, systems, IPSC, report_server)
logger.info('(confbridge.py) TCP reporting server starting')
from ipsc.reporting_const import REPORT_OPCODES as REP_OPC
report_server = reportFactory() # INITIALIZATION COMPLETE -- START THE REACTOR
report_server.clients = []
reactor.listenTCP(CONFIG['REPORTS']['REPORT_PORT'], reportFactory())
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger, report_server)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
reactor.run() reactor.run()

View File

@ -16,9 +16,8 @@ PATH: /opt/dmrlink/
# NETWORK REPORTING CONFIGURATION # NETWORK REPORTING CONFIGURATION
# Enabling "REPORT_NETWORKS" will cause a reporting action for # Enabling "REPORT_NETWORKS" will cause a reporting action for
# IPSC each time the periodic reporting loop runs, that period is # IPSC each time the periodic reporting loop runs, that period is
# specifiec by "REPORT_INTERVAL" in seconds. Possible values # specified by "REPORT_INTERVAL" in seconds. Possible values
# for "REPORT_NETWORKS" are: # for "REPORT_NETWORKS" are:
# PICKLE - a Python pickle file of the network's data structure
# #
# PRINT - a pretty print (STDOUT) of the data structure # PRINT - a pretty print (STDOUT) of the data structure
# "PRINT_PEERS_INC_MODE" - Boolean to include mode bits # "PRINT_PEERS_INC_MODE" - Boolean to include mode bits
@ -31,20 +30,23 @@ PATH: /opt/dmrlink/
# goal here is a web dashboard that doesn't live on the # goal here is a web dashboard that doesn't live on the
# dmrlink machine itself. # dmrlink machine itself.
# #
# PRINT is the odd man out because it sends prettily formatted stuff # PRINT should only be used for debugging; it sends prettily formatted
# to STDOUT. The others send the internal data structure of the IPSC # stuff to STDOUT. The others send the internal data structure of the
# instance and let some program on the other end sort it out. # IPSC instance and let some program on the other end sort it out.
# #
# REPORT_RCM - If True, and REPORT_NETWORKS = 'NETWORK', will send RCM
# Packets to connected reporting clients. This also requires
# individual IPSC systems to have RCM and CON_APP both set 'True'
#
# REPORT_INTERVAL - Seconds between reports # REPORT_INTERVAL - Seconds between reports
# REPORT_PATH - Absolute path save data (pickle and json)
# REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK # REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK
# REPORT_CLIENTS - comma separated list of IPs you will allow clients # REPORT_CLIENTS - comma separated list of IPs you will allow clients
# to connect on. # to connect on.
# #
[REPORTS] [REPORTS]
REPORT_NETWORKS: REPORT_NETWORKS:
REPORT_RCM:
REPORT_INTERVAL: 60 REPORT_INTERVAL: 60
REPORT_PATH:
REPORT_PORT: 4321 REPORT_PORT: 4321
REPORT_CLIENTS: 127.0.0.1, 192.168.1.1 REPORT_CLIENTS: 127.0.0.1, 192.168.1.1
PRINT_PEERS_INC_MODE: 0 PRINT_PEERS_INC_MODE: 0
@ -97,13 +99,13 @@ STALE_DAYS: 7
# #
# [NAME] The name you want to use to identify the IPSC instance (use # [NAME] The name you want to use to identify the IPSC instance (use
# something better than "IPSC1"...) # something better than "IPSC1"...)
# ENABLED: Should we communiate with this network? Handy if you need to # ENABLED: Should we communicate with this network? Handy if you need to
# shut one down but don't want to lose the config # shut one down but don't want to lose the config
# RADIO_ID: This is the radio ID that DMRLink should use to communicate # RADIO_ID: This is the radio ID that DMRLink should use to communicate
# IP: This is the local IPv4 address to listen on. It may be left # IP: This is the local IPv4 address to listen on. It may be left
# blank if you do not need or wish to specify. It is mostly # blank if you do not need or wish to specify. It is mostly
# useful when DMRlink uses multiple interfaces to serve as an # useful when DMRlink uses multiple interfaces to serve as an
# application gatway/proxy from private and/or VPN networks # application gateway/proxy from private and/or VPN networks
# to the real world. # to the real world.
# PORT: This is the UDP source port for DMRLink to use for this # PORT: This is the UDP source port for DMRLink to use for this
# PSC network, must be unique!!! # PSC network, must be unique!!!
@ -117,9 +119,10 @@ STALE_DAYS: 7
# CSBK_CALL: Should be False, we cannot process these, but may be useful # CSBK_CALL: Should be False, we cannot process these, but may be useful
# for debugging. # for debugging.
# RCM: Repeater Call Monitoring - don't unable unless you plan to # RCM: Repeater Call Monitoring - don't unable unless you plan to
# actually use it, this craetes extra network traffic. # actually use it, this creates extra network traffic.
# CON_APP: Third Party Console App - exactly what DMRlink is, should # CON_APP: Third Party Console App - exactly what DMRlink is, should
# be set to True. # be set to True, and must be if you intend to process RCM
# packets (like with network-based reporting)
# XNL_CALL: Can cause problems if not set to False, DMRlink does not # XNL_CALL: Can cause problems if not set to False, DMRlink does not
# process XCMP/XNL calls. # process XCMP/XNL calls.
# XNL_MASTER: Obviously, should also be False, see XNL_CALL. # XNL_MASTER: Obviously, should also be False, see XNL_CALL.
@ -129,7 +132,7 @@ STALE_DAYS: 7
# AUTH_ENABLED: Do we use authenticated IPSC? # AUTH_ENABLED: Do we use authenticated IPSC?
# AUTH_KEY: The Authentication key (up to 40 hex characters) # AUTH_KEY: The Authentication key (up to 40 hex characters)
# MASTER_IP: IP address of the IPSC master (ignored if DMRlink is the master) # MASTER_IP: IP address of the IPSC master (ignored if DMRlink is the master)
# MASTER_PORT: UDP port of the IPSC master (ignored if DMRlinkn is the master) # MASTER_PORT: UDP port of the IPSC master (ignored if DMRlink is the master)
# GROUP_HANGTIME: Group hangtime, per DMR configuration # GROUP_HANGTIME: Group hangtime, per DMR configuration
# #
# ...Repeat the block for each IPSC network to join. # ...Repeat the block for each IPSC network to join.

View File

@ -1,35 +0,0 @@
#! /bin/sh
###################################################
# #
# Download Subscriber and Peer ID's from DMR-MARC #
# #
###################################################
# To provide more readable output from DMRlink with current subscriber and repeater IDs, we download the CSV files from DMR-MARC
# If you are going to use this in a cron task, don't run it more then once a day.
# It might be good to find alternale a source as a backup.
# <http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi>
# wget -O users.csv -q "http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=users&format=csv&header=0"
# Options are:
# table { users | repeaters }
# format { table | csv | csvq | json }
# header { 0 | 1 } (only applies to table and csv formats)
# id { nnnnnn } (query an individual record)
# Get the user IDs.
wget -O subscriber_ids.csv -q "http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=users&format=csv&header=0"
# Get the peer IDs
wget -O peer_ids.csv -q "http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=repeaters&format=csv&header=0"
# Tell ambe_audio to re-read the files
echo -n "reread_subscribers" | nc 127.0.0.1 31002

View File

@ -1,150 +0,0 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2017 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of tde GNU General Public License as published by
# the Free Software Foundation; eitder 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
from cPickle import load
from pprint import pprint
from time import time, strftime, localtime
from twisted.internet import reactor
from twisted.internet import task
from binascii import b2a_hex as h
from dmr_utils.utils import int_id, get_alias
from os.path import getmtime
__autdor__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2017 Cortney T. Buffington, N0MJS'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
# This is the only user-configuration necessary
# Tell the program where the pickle file is
# Tell the program where to write the html table file
# Tell the program how often to print a report -- should match dmrlink report period
stat_file = '../confbridge_stats.pickle'
html_table_file = '../confbridge_stats.html'
frequency = 10
def read_dict():
try:
with open(stat_file, 'rb') as file:
BRIDGES = load(file)
return BRIDGES
except IOError as detail:
print('I/O Error: {}'.format(detail))
except EOFError:
print('EOFError')
def write_file(_string):
try:
with open(html_table_file, 'w') as file:
file.write(_string)
file.close()
except IOError as detail:
print('I/O Error: {}'.format(detail))
except EOFError:
print('EOFError')
def build_table():
_now = time()
_last_update = strftime('%Y-%m-%d %H:%M:%S', localtime(getmtime(stat_file)))
_cnow = strftime('%Y-%m-%d %H:%M:%S', localtime(_now))
BRIDGES = read_dict()
if BRIDGES != 'None':
stuff = 'Table Generated: {}<br>'.format(_cnow)
stuff += 'Last Stat Data Recieved: {}<br>'.format(_last_update)
#style="font: 10pt arial, sans-serif;"
for bridge in BRIDGES:
stuff += '<style>table, td, th {border: .5px solid black; padding: 2px; border-collapse: collapse}</style>'
stuff += '<table style="width:90%; font: 10pt arial, sans-serif">'
stuff += '<colgroup>\
<col style="width: 20%" />\
<col style="width: 5%" />\
<col style="width: 5%" />\
<col style="width: 10%" />\
<col style="width: 10%" />\
<col style="width: 10%" />\
<col style="width: 10%" />\
<col style="width: 10%" />\
<col style="width: 10%" />\
</colgroup>'
stuff += '<caption>{}</caption>'.format(bridge)
stuff += '<tr><th>System</th>\
<th>Slot</th>\
<th>TGID</th>\
<th>Status</th>\
<th>Timeout</th>\
<th>Timeout Action</th>\
<th>ON Triggers</th>\
<th>OFF Triggers</th></tr>'
for system in BRIDGES[bridge]:
on = ''
off = ''
active = '<td bgcolor="#FFFF00">Unknown</td>'
if system['TO_TYPE'] == 'ON' or system['TO_TYPE'] == 'OFF':
if system['TIMER'] - _now > 0:
exp_time = int(system['TIMER'] - _now)
else:
exp_time = 'Expired'
if system['TO_TYPE'] == 'ON':
to_action = 'Turn OFF'
else:
to_action = 'Turn ON'
else:
exp_time = 'N/A'
to_action = 'None'
if system['ACTIVE'] == True:
active = '<td bgcolor="#00FF00">Connected</td>'
elif system['ACTIVE'] == False:
active = '<td bgcolor="#FF0000">Disconnected</td>'
for trigger in system['ON']:
on += str(int_id(trigger)) + ' '
for trigger in system['OFF']:
off += str(int_id(trigger)) + ' '
stuff += '<tr> <td>{}</td> <td>{}</td> <td>{}</td> {} <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> </tr>'.format(\
system['SYSTEM'],\
system['TS'],\
int_id(system['TGID']),\
active,\
exp_time,\
to_action,\
on,\
off)
stuff += '</table><br>'
write_file(stuff)
if __name__ == '__main__':
output_stats = task.LoopingCall(build_table)
output_stats.start(frequency)
reactor.run()

View File

@ -1,139 +0,0 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2017 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of tde GNU General Public License as published by
# the Free Software Foundation; eitder 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
from cPickle import load
from pprint import pprint
from time import time, strftime, localtime
from twisted.internet import reactor
from twisted.internet import task
from binascii import b2a_hex as h
from dmr_utils.utils import int_id, get_alias
__autdor__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2017 Cortney T. Buffington, N0MJS'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
# This is the only user-configuration necessary
# Tell the program where the pickle file is
# Tell the program where to write the html table file
# Tell the program how often to print a report -- should match dmrlink report period
stat_file = '../dmrlink_stats.pickle'
html_table_file = '../stats.html'
frequency = 30
def read_dict():
try:
with open(stat_file, 'rb') as file:
NETWORK = load(file)
return NETWORK
except IOError as detail:
print('I/O Error: {}'.format(detail))
except EOFError:
print('EOFError')
def write_file(_string):
try:
with open(html_table_file, 'w') as file:
file.write(_string)
file.close()
except IOError as detail:
print('I/O Error: {}'.format(detail))
except EOFError:
print('EOFError')
def build_table():
NETWORK = read_dict()
if NETWORK != 'None':
_cnow = strftime('%Y-%m-%d %H:%M:%S', localtime(time()))
stuff = 'Last Update: {}'.format(_cnow)
stuff += '<style>table, td, th {border: .5px solid black; padding: 2px; border-collapse: collapse}</style>'
for ipsc in NETWORK:
stat = NETWORK[ipsc]['MASTER']['STATUS']
master = NETWORK[ipsc]['LOCAL']['MASTER_PEER']
stuff += '<table style="width:90%; font: 10pt arial, sans-serif">'
stuff += '<colgroup>\
<col style="width: 10%" />\
<col style="width: 20%" />\
<col style="width: 20%" />\
<col style="width: 10%" />\
<col style="width: 15%" />\
<col style="width: 15%" />\
<col style="width: 10%" />\
</colgroup>'
stuff += '<caption>{} '.format(ipsc)
if master:
stuff += '(master)'
else:
stuff += '(peer)'
stuff +='</caption>'
stuff += '<tr><th rowspan="2">Type</th>\
<th rowspan="2">Radio ID</th>\
<th rowspan="2">IP Address</th>\
<th rowspan="2">Connected</th>\
<th colspan="3">Keep Alives</th></tr>\
<tr><th>Sent</th><th>Received</th><th>Missed</th></tr>'
if not master:
stuff += '<tr><td>Master</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>'.format(\
str(int_id(NETWORK[ipsc]['MASTER']['RADIO_ID'])).rjust(8,'0'),\
NETWORK[ipsc]['MASTER']['IP'],\
stat['CONNECTED'],\
stat['KEEP_ALIVES_SENT'],\
stat['KEEP_ALIVES_RECEIVED'],\
stat['KEEP_ALIVES_MISSED'],)
if master:
for peer in NETWORK[ipsc]['PEERS']:
stat = NETWORK[ipsc]['PEERS'][peer]['STATUS']
stuff += '<tr><td>Peer</td><td>{}</td><td>{}</td><td>{}</td><td>n/a</td><td>{}</td><td>n/a</td></tr>'.format(\
str(int_id(peer)).rjust(8,'0'),\
NETWORK[ipsc]['PEERS'][peer]['IP'],\
stat['CONNECTED'],\
stat['KEEP_ALIVES_RECEIVED'])
else:
for peer in NETWORK[ipsc]['PEERS']:
stat = NETWORK[ipsc]['PEERS'][peer]['STATUS']
if peer != NETWORK[ipsc]['LOCAL']['RADIO_ID']:
stuff += '<tr><td>Peer</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>'.format(\
str(int_id(peer)).rjust(8,'0'),\
NETWORK[ipsc]['PEERS'][peer]['IP'],\
stat['CONNECTED'],\
stat['KEEP_ALIVES_SENT'],\
stat['KEEP_ALIVES_RECEIVED'],\
stat['KEEP_ALIVES_MISSED'])
stuff += '</table><br>'
write_file(stuff)
if __name__ == '__main__':
output_stats = task.LoopingCall(build_table)
output_stats.start(frequency)
reactor.run()

View File

@ -54,8 +54,8 @@ def build_config(_config_file):
elif section == 'REPORTS': elif section == 'REPORTS':
CONFIG['REPORTS'].update({ CONFIG['REPORTS'].update({
'REPORT_NETWORKS': config.get(section, 'REPORT_NETWORKS'), 'REPORT_NETWORKS': config.get(section, 'REPORT_NETWORKS'),
'REPORT_RCM': config.get(section, 'REPORT_RCM'),
'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'), 'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'),
'REPORT_PATH': config.get(section, 'REPORT_PATH'),
'REPORT_PORT': config.get(section, 'REPORT_PORT'), 'REPORT_PORT': config.get(section, 'REPORT_PORT'),
'REPORT_CLIENTS': config.get(section, 'REPORT_CLIENTS').split(','), 'REPORT_CLIENTS': config.get(section, 'REPORT_CLIENTS').split(','),
'PRINT_PEERS_INC_MODE': config.getboolean(section, 'PRINT_PEERS_INC_MODE'), 'PRINT_PEERS_INC_MODE': config.getboolean(section, 'PRINT_PEERS_INC_MODE'),
@ -63,6 +63,8 @@ def build_config(_config_file):
}) })
if CONFIG['REPORTS']['REPORT_PORT']: if CONFIG['REPORTS']['REPORT_PORT']:
CONFIG['REPORTS']['REPORT_PORT'] = int(CONFIG['REPORTS']['REPORT_PORT']) CONFIG['REPORTS']['REPORT_PORT'] = int(CONFIG['REPORTS']['REPORT_PORT'])
if CONFIG['REPORTS']['REPORT_RCM']:
CONFIG['REPORTS']['REPORT_RCM'] = bool(CONFIG['REPORTS']['REPORT_RCM'])
elif section == 'LOGGER': elif section == 'LOGGER':
CONFIG['LOGGER'].update({ CONFIG['LOGGER'].update({
@ -79,6 +81,7 @@ def build_config(_config_file):
'PEER_FILE': config.get(section, 'PEER_FILE'), 'PEER_FILE': config.get(section, 'PEER_FILE'),
'SUBSCRIBER_FILE': config.get(section, 'SUBSCRIBER_FILE'), 'SUBSCRIBER_FILE': config.get(section, 'SUBSCRIBER_FILE'),
'TGID_FILE': config.get(section, 'TGID_FILE'), 'TGID_FILE': config.get(section, 'TGID_FILE'),
'LOCAL_FILE': config.get(section, 'LOCAL_FILE'),
'PEER_URL': config.get(section, 'PEER_URL'), 'PEER_URL': config.get(section, 'PEER_URL'),
'SUBSCRIBER_URL': config.get(section, 'SUBSCRIBER_URL'), 'SUBSCRIBER_URL': config.get(section, 'SUBSCRIBER_URL'),
'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400, 'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400,

View File

@ -26,5 +26,6 @@ REPORT_OPCODES = {
'CONFIG_UPD': '\x04', 'CONFIG_UPD': '\x04',
'BRIDGE_UPD': '\x05', 'BRIDGE_UPD': '\x05',
'LINK_EVENT': '\x06', 'LINK_EVENT': '\x06',
'BRDG_EVENT': '\x07' 'BRDG_EVENT': '\x07',
'RCM_SND': '\x08'
} }

79
log.py
View File

@ -25,7 +25,9 @@ from twisted.internet import reactor
from binascii import b2a_hex as h from binascii import b2a_hex as h
import time import time
from dmrlink import IPSC, systems
from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, build_aliases, config_reports
from dmr_utils.utils import hex_str_3, hex_str_4, int_id, get_alias from dmr_utils.utils import hex_str_3, hex_str_4, int_id, get_alias
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
@ -37,8 +39,8 @@ __email__ = 'n0mjs@me.com'
class logIPSC(IPSC): class logIPSC(IPSC):
def __init__(self, _name, _config, _logger): def __init__(self, _name, _config, _logger, _report):
IPSC.__init__(self, _name, _config, _logger) IPSC.__init__(self, _name, _config, _logger, _report)
self.ACTIVE_CALLS = [] self.ACTIVE_CALLS = []
#************************************************ #************************************************
@ -89,13 +91,12 @@ class logIPSC(IPSC):
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
import os
import sys import sys
import os
import signal import signal
from dmr_utils.utils import try_download, mk_id_dict
import dmrlink_log from ipsc.dmrlink_config import build_config
import dmrlink_config from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -103,62 +104,44 @@ if __name__ == '__main__':
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)') parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
parser.add_argument('-ll', '--log_level', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
parser.add_argument('-lh', '--log_handle', action='store', dest='LOG_HANDLERS', help='Override config file logging handler.')
cli_args = parser.parse_args() cli_args = parser.parse_args()
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'
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = dmrlink_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 = dmrlink_log.config_logging(CONFIG['LOGGER']) if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
logger.info('DMRlink \'log.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...') if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
# ID ALIAS CREATION # Set signal handers so that we can gracefully exit if need be
# Download
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, reportFactory)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC # Build ID Aliases
for system in CONFIG['SYSTEMS']: peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = logIPSC(system, CONFIG, logger) # INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP']) systems = mk_ipsc_systems(CONFIG, logger, systems, logIPSC, report_server)
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run() reactor.run()

File diff suppressed because it is too large Load Diff

View File

@ -1,110 +0,0 @@
#!/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
from cPickle import load
from pprint import pprint
from time import ctime
from twisted.internet import reactor
from twisted.internet import task
from binascii import b2a_hex as h
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2015 Cortney T. Buffington, N0MJS and the K0USY Group'
__credits__ = 'Adam Fast, KC0YLK, Dave Kierzkowski, KD8EYF'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
# This is the only user-configuration necessary
# Tell the program where the pickle file is
# Tell the program how often to print a report
stat_file = '../dmrlink_stats.pickle'
frequency = 30
def int_id(_hex_string):
return int(h(_hex_string), 16)
def read_dict():
try:
with open(stat_file, 'rb') as file:
NETWORK = load(file)
return NETWORK
except IOError as detail:
print('I/O Error: {}'.format(detail))
except EOFError:
print('EOFError')
def print_stats():
NETWORK = read_dict()
if NETWORK != "None":
print('NETWORK STATISTICS REPORT:', ctime())
for ipsc in NETWORK:
stat = NETWORK[ipsc]['MASTER']['STATUS']
master = NETWORK[ipsc]['LOCAL']['MASTER_PEER']
print(ipsc)
if master:
print(' MASTER Information:')
print(' RADIO ID: {} (self)'.format(str(int_id(NETWORK[ipsc]['LOCAL']['RADIO_ID'])).rjust(8,'0')))
else:
print(' MASTER Information:')
print(' RADIO ID: {} CONNECTED: {}, KEEP ALIVES: SENT {} RECEIVED {} MISSED {} ({})'.format(\
str(int_id(NETWORK[ipsc]['MASTER']['RADIO_ID'])).rjust(8,'0'),\
stat['CONNECTED'],stat['KEEP_ALIVES_SENT'],\
stat['KEEP_ALIVES_RECEIVED'],\
stat['KEEP_ALIVES_MISSED'],\
NETWORK[ipsc]['MASTER']['IP']))
print(' PEER Information:')
if master:
for peer in NETWORK[ipsc]['PEERS']:
stat = NETWORK[ipsc]['PEERS'][peer]['STATUS']
print(' RADIO ID: {} CONNECTED: {}, KEEP ALIVES: RECEIVED {} ({})'.format(\
str(int_id(peer)).rjust(8,'0'),\
stat['CONNECTED'],\
stat['KEEP_ALIVES_RECEIVED'],\
NETWORK[ipsc]['PEERS'][peer]['IP']))
else:
for peer in NETWORK[ipsc]['PEERS']:
stat = NETWORK[ipsc]['PEERS'][peer]['STATUS']
if peer == NETWORK[ipsc]['LOCAL']['RADIO_ID']:
print(' RADIO ID: {} (self)'.format(str(int_id(peer)).rjust(8,'0')))
else:
print(' RADIO ID: {} CONNECTED: {}, KEEP ALIVES: SENT {} RECEIVED {} MISSED {} ({})'.format(\
str(int_id(peer)).rjust(8,'0'),\
stat['CONNECTED'],\
stat['KEEP_ALIVES_SENT'],\
stat['KEEP_ALIVES_RECEIVED'],\
stat['KEEP_ALIVES_MISSED'],\
NETWORK[ipsc]['PEERS'][peer]['IP']))
print()
print()
if __name__ == '__main__':
output_stats = task.LoopingCall(print_stats)
output_stats.start(frequency)
reactor.run()

View File

@ -36,7 +36,7 @@ from twisted.internet import reactor
import sys, time import sys, time
import cPickle as pickle import cPickle as pickle
from dmrlink import IPSC, systems from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, build_aliases, config_reports
from dmr_utils.utils import int_id, hex_str_3 from dmr_utils.utils import int_id, hex_str_3
from ipsc.ipsc_const import BURST_DATA_TYPE from ipsc.ipsc_const import BURST_DATA_TYPE
@ -61,8 +61,8 @@ trigger_groups_1 = ['\x00\x00\x01', '\x00\x00\x0D', '\x00\x00\x64']
trigger_groups_2 = ['\x00\x0C\x30',] trigger_groups_2 = ['\x00\x0C\x30',]
class playIPSC(IPSC): class playIPSC(IPSC):
def __init__(self, _name, _config, _logger): def __init__(self, _name, _config, _logger,_report):
IPSC.__init__(self, _name, _config, _logger) IPSC.__init__(self, _name, _config, _logger, _report)
self.CALL_DATA = [] self.CALL_DATA = []
self.event_id = 1 self.event_id = 1
@ -134,12 +134,12 @@ class playIPSC(IPSC):
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
import os
import sys import sys
import os
import signal import signal
import dmrlink_log from ipsc.dmrlink_config import build_config
import dmrlink_config from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -153,39 +153,38 @@ 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'
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = dmrlink_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
if cli_args.LOG_LEVEL: if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS: if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = dmrlink_log.config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Shut ourselves down gracefully with the IPSC peers. # Set signal handers so that we can gracefully exit if need be
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, reportFactory)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC # Build ID Aliases
for system in CONFIG['SYSTEMS']: peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = playIPSC(system, CONFIG, logger) # INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP']) systems = mk_ipsc_systems(CONFIG, logger, systems, playIPSC, report_server)
reactor.run()
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run()

View File

@ -25,7 +25,7 @@ from twisted.internet import reactor
from binascii import b2a_hex as ahex from binascii import b2a_hex as ahex
import sys, time import sys, time
from dmrlink import IPSC, systems from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, build_aliases, config_reports
from dmr_utils.utils import int_id, hex_str_3 from dmr_utils.utils import int_id, hex_str_3
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
@ -46,8 +46,8 @@ HEX_SUB = hex_str_3(SUB)
BOGUS_SUB = '\xFF\xFF\xFF' BOGUS_SUB = '\xFF\xFF\xFF'
class playbackIPSC(IPSC): class playbackIPSC(IPSC):
def __init__(self, _name, _config, _logger): def __init__(self, _name, _config, _logger, _report):
IPSC.__init__(self, _name, _config, _logger) IPSC.__init__(self, _name, _config, _logger, _report)
self.CALL_DATA = [] self.CALL_DATA = []
if GROUP_SRC_SUB: if GROUP_SRC_SUB:
@ -115,12 +115,12 @@ class playbackIPSC(IPSC):
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
import os
import sys import sys
import os
import signal import signal
import dmrlink_log from ipsc.dmrlink_config import build_config
import dmrlink_config from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -134,39 +134,38 @@ 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'
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = dmrlink_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
if cli_args.LOG_LEVEL: if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS: if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = dmrlink_log.config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
logger.info('DMRlink \'playback.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Shut ourselves down gracefully with the IPSC peers. # Set signal handers so that we can gracefully exit if need be
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, reportFactory)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC # Build ID Aliases
for system in CONFIG['SYSTEMS']: peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = playbackIPSC(system, CONFIG, logger) # INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP']) systems = mk_ipsc_systems(CONFIG, logger, systems, playbackIPSC, report_server)
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run() reactor.run()

240
proxy.py Executable file
View File

@ -0,0 +1,240 @@
#!/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
###############################################################################
# This is a sample application to bridge traffic between IPSC systems. it uses
# one required (bridge_rules.py) and one optional (known_bridges.py) additional
# configuration files. Both files have their own documentation for use.
#
# "bridge_rules" contains the IPSC network, Timeslot and TGID matching rules to
# determine which voice calls are bridged between IPSC systems and which are
# not.
#
# "known_bridges" contains DMR radio ID numbers of known bridges. This file is
# used when you want bridge.py to be "polite" or serve as a backup bridge. If
# a known bridge exists in either a source OR target IPSC network, then no
# bridging between those IPSC systems will take place. This behavior is
# dynamic and updates each keep-alive interval (main configuration file).
# For faster failover, configure a short keep-alive time and a low number of
# missed keep-alives before timout. I recommend 5 sec keep-alive and 3 missed.
# That gives a worst-case scenario of 15 seconds to fail over. Recovery will
# typically happen with a single "blip" in the transmission up to about 5
# seconds.
#
# While this file is listed as Beta status, K0USY Group depends on this code
# for the bridigng of it's many repeaters. We consider it reliable, but you
# get what you pay for... as usual, no guarantees.
#
# Use to make test strings: #print('PKT:', "\\x".join("{:02x}".format(ord(c)) for c in _data))
from __future__ import print_function
from twisted.internet import reactor
from twisted.internet import task
from binascii import b2a_hex as ahex
from time import time
from importlib import import_module
import sys
from dmr_utils.utils import hex_str_3, hex_str_4, int_id
from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, REPORT_OPCODES, build_aliases, config_reports
from ipsc.ipsc_const import BURST_DATA_TYPE
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2017 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'
# Import subscriber ACL
# ACL may be a single list of subscriber IDs
# Global action is to allow or deny them. Multiple lists with different actions and ranges
# are not yet implemented.
def build_acl(_sub_acl):
try:
acl_file = import_module(_sub_acl)
for i, e in enumerate(acl_file.ACL):
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
logger.info('ACL file found and ACL entries imported')
ACL_ACTION = acl_file.ACL_ACTION
ACL = acl_file.ACL_ACTION
except ImportError:
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
ACL_ACTION = 'NONE'
ACL = []
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
# define a differnet function to be used to check the ACL
global allow_sub
if ACL_ACTION == 'PERMIT':
def allow_sub(_sub):
if _sub in ACL:
return True
else:
return False
elif ACL_ACTION == 'DENY':
def allow_sub(_sub):
if _sub not in ACL:
return True
else:
return False
else:
def allow_sub(_sub):
return True
return ACL
class proxyIPSC(IPSC):
def __init__(self, _name, _config, _logger, report):
IPSC.__init__(self, _name, _config, _logger, report)
self.last_seq_id = '\x00'
self.call_start = 0
#************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************
#
def group_voice(self, _src_sub, _dst_group, _ts, _end, _peerid, _data):
# Check for ACL match, and return if the subscriber is not allowed
if allow_sub(_src_sub) == False:
self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
return
# Process the packet
self._logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
_burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types)
_seq_id = _data[5]
for system in systems:
if system != self._system:
#
# BEGIN FRAME FORWARDING
#
# Make a copy of the payload
_tmp_data = _data
# Re-Write the IPSC SRC to match the target network's ID
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG['SYSTEMS'][system]['LOCAL']['RADIO_ID'])
# Send the packet to all peers in the target IPSC
systems[system].send_to_ipsc(_tmp_data)
#
# END FRAME FORWARDING
#
#
# BEGIN IN-BAND SIGNALING BASED ON TGID & VOICE TERMINATOR FRAME
#
# Action happens on key up
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
if self.last_seq_id != _seq_id:
self.last_seq_id = _seq_id
self.call_start = time()
self._logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
self._report.send_proxyEvent('({}) GROUP VOICE START: CallID: {} PEER: {}, SUB: {}, TS: {}, TGID: {}'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
# Action happens on un-key
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
if self.last_seq_id == _seq_id:
self.call_duration = time() - self.call_start
self._logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
self._report.send_proxyEvent('({}) GROUP VOICE END: CallID: {} PEER: {}, SUB: {}, TS: {}, TGID: {} Duration: {:.2f}s'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration))
else:
self._logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),)
self._report.send_proxyEvent('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
class proxyReportFactory(reportFactory):
def send_proxyEvent(self, _data):
self.send_clients(REPORT_OPCODES['BRDG_EVENT']+_data)
if __name__ == '__main__':
import argparse
import sys
import os
import signal
from ipsc.dmrlink_config import build_config
from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
parser.add_argument('-ll', '--log_level', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
parser.add_argument('-lh', '--log_handle', action='store', dest='LOG_HANDLERS', help='Override config file logging handler.')
cli_args = parser.parse_args()
if not cli_args.CFG_FILE:
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
# Call the external routine to build the configuration dictionary
CONFIG = build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Set signal handers so that we can gracefully exit if need be
def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems:
systems[system].de_register_self()
reactor.stop()
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# PROXY.PY SPECIFIC ITEMS GO HERE:
# Build the Access Control List
ACL = build_acl('sub_acl')
# MAIN INITIALIZATION ITEMS HERE
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, proxyReportFactory)
# Build ID Aliases
peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGURED IPSC
systems = mk_ipsc_systems(CONFIG, logger, systems, proxyIPSC, report_server)
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run()

81
rcm.py
View File

@ -32,8 +32,9 @@ import datetime
import binascii import binascii
import dmrlink import dmrlink
import sys import sys
from dmrlink import IPSC, systems from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, build_aliases, config_reports
from dmr_utils.utils import get_alias, int_id from dmr_utils.utils import get_alias, int_id
from ipsc.ipsc_const import *
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -43,18 +44,13 @@ __maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com' __email__ = 'n0mjs@me.com'
try:
from ipsc.ipsc_const import *
except ImportError:
sys.exit('IPSC message types file not found or invalid')
status = True status = True
rpt = True rpt = True
nack = True nack = True
class rcmIPSC(IPSC): class rcmIPSC(IPSC):
def __init__(self, _name, _config, _logger): def __init__(self, _name, _config, _logger, _report):
IPSC.__init__(self, _name, _config, _logger) IPSC.__init__(self, _name, _config, _logger, _report)
#************************************************ #************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES # CALLBACK FUNCTIONS FOR USER PACKET TYPES
@ -149,13 +145,12 @@ class rcmIPSC(IPSC):
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
import os
import sys import sys
import os
import signal import signal
from dmr_utils.utils import try_download, mk_id_dict
import dmrlink_log from ipsc.dmrlink_config import build_config
import dmrlink_config from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -169,62 +164,38 @@ 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'
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = dmrlink_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
if cli_args.LOG_LEVEL: if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS: if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = dmrlink_log.config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
logger.info('DMRlink \'rcm.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
# ID ALIAS CREATION # Set signal handers so that we can gracefully exit if need be
# Download
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, reportFactory)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC # Build ID Aliases
for system in CONFIG['SYSTEMS']: peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = rcmIPSC(system, CONFIG, logger) # INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP']) systems = mk_ipsc_systems(CONFIG, logger, systems, rcmIPSC, report_server)
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run() reactor.run()

View File

@ -44,7 +44,9 @@ from twisted.internet import task
import pymysql import pymysql
import dmrlink import dmrlink
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, logger from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, build_aliases, config_reports
from ipsc.ipsc_const import *
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -70,10 +72,6 @@ db_name = 'dmrlink'
# #
#************************************ #************************************
try:
from ipsc.ipsc_message_types import *
except ImportError:
sys.exit('IPSC message types file not found or invalid')
class rcmIPSC(IPSC): class rcmIPSC(IPSC):
@ -110,9 +108,58 @@ class rcmIPSC(IPSC):
if __name__ == '__main__': if __name__ == '__main__':
logger.info('DMRlink \'rcm_db_log.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...') import argparse
for ipsc_network in NETWORK: import sys
if NETWORK[ipsc_network]['LOCAL']['ENABLED']: import os
networks[ipsc_network] = rcmIPSC(ipsc_network) import signal
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
from ipsc.dmrlink_config import build_config
from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
parser.add_argument('-ll', '--log_level', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
parser.add_argument('-lh', '--log_handle', action='store', dest='LOG_HANDLERS', help='Override config file logging handler.')
cli_args = parser.parse_args()
if not cli_args.CFG_FILE:
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
# Call the external routine to build the configuration dictionary
CONFIG = build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Set signal handers so that we can gracefully exit if need be
def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems:
systems[system].de_register_self()
reactor.stop()
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, reportFactory)
# Build ID Aliases
peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
systems = mk_ipsc_systems(CONFIG, logger, systems, rcmIPSC, report_server)
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run() reactor.run()

View File

@ -27,7 +27,7 @@ from binascii import b2a_hex as h
import sys import sys
import cPickle as pickle import cPickle as pickle
from dmrlink import IPSC, systems from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, build_aliases, config_reports
from dmr_utils.utils import hex_str_3, int_id from dmr_utils.utils import hex_str_3, int_id
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
@ -65,8 +65,8 @@ id = hex_str_3(id)
filename = raw_input('Filename to use for this recording? ') filename = raw_input('Filename to use for this recording? ')
class recordIPSC(IPSC): class recordIPSC(IPSC):
def __init__(self, _name, _config, _logger): def __init__(self, _name, _config, _logger, _report):
IPSC.__init__(self, _name, _config, _logger) IPSC.__init__(self, _name, _config, _logger, _report)
self.CALL_DATA = [] self.CALL_DATA = []
#************************************************ #************************************************
@ -106,12 +106,12 @@ class recordIPSC(IPSC):
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
import os
import sys import sys
import os
import signal import signal
import dmrlink_log from ipsc.dmrlink_config import build_config
import dmrlink_config from ipsc.dmrlink_log import config_logging
# Change the current directory to the location of the application # Change the current directory to the location of the application
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
@ -125,44 +125,38 @@ 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'
# Call the external routine to build the configuration dictionary # Call the external routine to build the configuration dictionary
CONFIG = dmrlink_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
if cli_args.LOG_LEVEL: if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
if cli_args.LOG_HANDLERS: if cli_args.LOG_HANDLERS:
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
logger = dmrlink_log.config_logging(CONFIG['LOGGER']) logger = config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger # Set signal handers so that we can gracefully exit if need be
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame): def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems: for system in systems:
this_ipsc = systems[system] systems[system].de_register_self()
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() reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]: for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler) signal.signal(sig, sig_handler)
# INITIALIZE THE REPORTING LOOP
report_server = config_reports(CONFIG, logger, reportFactory)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC # Build ID Aliases
for system in CONFIG['SYSTEMS']: peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = recordIPSC(system, CONFIG, logger) # INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP']) systems = mk_ipsc_systems(CONFIG, logger, systems, recordIPSC, report_server)
reactor.run()
# INITIALIZATION COMPLETE -- START THE REACTOR
reactor.run()

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
1,Worldwide 2,Local 3,North America 9,BrandMeister 13,Worldwide English 310,TAC 310 3100,DCI Bridge 2 3160,DCI 1 3169,Midwest 3172,Northeast 3174,Southeast 3112,Flordia 3120,Kansas Statewide 3125,Massachussetts 3129,Missouri 31201,BYRG KC 3777215,DCI Comm 1 9998,Echo Server
1 1 Worldwide 2 Local 3 North America 9 BrandMeister 13 Worldwide English 310 TAC 310 3100 DCI Bridge 2 3160 DCI 1 3169 Midwest 3172 Northeast 3174 Southeast 3112 Flordia 3120 Kansas Statewide 3125 Massachussetts 3129 Missouri 31201 BYRG KC 3777215 DCI Comm 1 9998 Echo Server