Progress - maybe works
This commit is contained in:
parent
477a00887a
commit
fbac51b3e9
47
bridge.py
47
bridge.py
|
@ -18,18 +18,18 @@
|
||||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# This is a sample application to bridge traffic between IPSC networks. it uses
|
# 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
|
# one required (bridge_rules.py) and one optional (known_bridges.py) additional
|
||||||
# configuration files. Both files have their own documentation for use.
|
# configuration files. Both files have their own documentation for use.
|
||||||
#
|
#
|
||||||
# "bridge_rules" contains the IPSC network, Timeslot and TGID matching rules to
|
# "bridge_rules" contains the IPSC network, Timeslot and TGID matching rules to
|
||||||
# determine which voice calls are bridged between IPSC networks and which are
|
# determine which voice calls are bridged between IPSC systems and which are
|
||||||
# not.
|
# not.
|
||||||
#
|
#
|
||||||
# "known_bridges" contains DMR radio ID numbers of known bridges. This file is
|
# "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
|
# 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
|
# a known bridge exists in either a source OR target IPSC network, then no
|
||||||
# bridging between those IPSC networks will take place. This behavior is
|
# bridging between those IPSC systems will take place. This behavior is
|
||||||
# dynamic and updates each keep-alive interval (main configuration file).
|
# dynamic and updates each keep-alive interval (main configuration file).
|
||||||
# For faster failover, configure a short keep-alive time and a low number of
|
# 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.
|
# missed keep-alives before timout. I recommend 5 sec keep-alive and 3 missed.
|
||||||
|
@ -53,7 +53,11 @@ from time import time
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from dmrlink import IPSC, NETWORK, networks, REPORTS, reporting_loop, dmr_nat, logger, hex_str_3, hex_str_4, int_id
|
from dmrlink import IPSC, systems, reporting_loop, dmr_nat, logger, hex_str_3, hex_str_4, int_id
|
||||||
|
from dmrlink import CONFIG
|
||||||
|
|
||||||
|
NETWORKS = CONFIG['SYSTEMS']
|
||||||
|
REPORTS = CONFIG['REPORTS']
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
@ -63,15 +67,6 @@ __maintainer__ = 'Cort Buffington, N0MJS'
|
||||||
__email__ = 'n0mjs@me.com'
|
__email__ = 'n0mjs@me.com'
|
||||||
|
|
||||||
|
|
||||||
# Constants for this application
|
|
||||||
#
|
|
||||||
BURST_DATA_TYPE = {
|
|
||||||
'VOICE_HEAD': '\x01',
|
|
||||||
'VOICE_TERM': '\x02',
|
|
||||||
'SLOT1_VOICE': '\x0A',
|
|
||||||
'SLOT2_VOICE': '\x8A'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Minimum time between different subscribers transmitting on the same TGID
|
# Minimum time between different subscribers transmitting on the same TGID
|
||||||
#
|
#
|
||||||
TS_CLEAR_TIME = .2
|
TS_CLEAR_TIME = .2
|
||||||
|
@ -103,9 +98,9 @@ for _ipsc in RULES_FILE:
|
||||||
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
|
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
|
||||||
_rule['TIMEOUT']= _rule['TIMEOUT']*60
|
_rule['TIMEOUT']= _rule['TIMEOUT']*60
|
||||||
_rule['TIMER'] = time() + _rule['TIMEOUT']
|
_rule['TIMER'] = time() + _rule['TIMEOUT']
|
||||||
if _ipsc not in NETWORK:
|
if _ipsc not in NETWORKS:
|
||||||
sys.exit('ERROR: Bridge rules found for an IPSC network not configured in main configuration')
|
sys.exit('ERROR: Bridge rules found for an IPSC network not configured in main configuration')
|
||||||
for _ipsc in NETWORK:
|
for _ipsc in NETWORKS:
|
||||||
if _ipsc not in RULES_FILE:
|
if _ipsc not in RULES_FILE:
|
||||||
sys.exit('ERROR: Bridge rules not found for all IPSC network configured')
|
sys.exit('ERROR: Bridge rules not found for all IPSC network configured')
|
||||||
|
|
||||||
|
@ -247,10 +242,10 @@ class bridgeIPSC(IPSC):
|
||||||
|
|
||||||
for rule in RULES[_network]['GROUP_VOICE']:
|
for rule in RULES[_network]['GROUP_VOICE']:
|
||||||
_target = rule['DST_NET'] # Shorthand to reduce length and make it easier to read
|
_target = rule['DST_NET'] # Shorthand to reduce length and make it easier to read
|
||||||
_status = networks[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
|
_status = systems[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
|
||||||
|
|
||||||
# This is the primary rule match to determine if the call will be routed.
|
# This is the primary rule match to determine if the call will be routed.
|
||||||
if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or networks[_target].BRIDGE == True):
|
if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or systems[_target].BRIDGE == True):
|
||||||
|
|
||||||
#
|
#
|
||||||
# BEGIN CONTENTION HANDLING
|
# BEGIN CONTENTION HANDLING
|
||||||
|
@ -322,7 +317,7 @@ class bridgeIPSC(IPSC):
|
||||||
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
||||||
|
|
||||||
# Send the packet to all peers in the target IPSC
|
# Send the packet to all peers in the target IPSC
|
||||||
networks[_target].send_to_ipsc(_tmp_data)
|
systems[_target].send_to_ipsc(_tmp_data)
|
||||||
#
|
#
|
||||||
# END FRAME FORWARDING
|
# END FRAME FORWARDING
|
||||||
#
|
#
|
||||||
|
@ -411,36 +406,36 @@ class bridgeIPSC(IPSC):
|
||||||
|
|
||||||
for target in RULES[_network]['GROUP_DATA']:
|
for target in RULES[_network]['GROUP_DATA']:
|
||||||
|
|
||||||
if self.BRIDGE == True or networks[target].BRIDGE == True:
|
if self.BRIDGE == True or systems[target].BRIDGE == True:
|
||||||
_tmp_data = _data
|
_tmp_data = _data
|
||||||
# Re-Write the IPSC SRC to match the target network's ID
|
# Re-Write the IPSC SRC to match the target network's ID
|
||||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
||||||
|
|
||||||
# Send the packet to all peers in the target IPSC
|
# Send the packet to all peers in the target IPSC
|
||||||
networks[target].send_to_ipsc(_tmp_data)
|
systems[target].send_to_ipsc(_tmp_data)
|
||||||
|
|
||||||
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
||||||
|
|
||||||
for target in RULES[_network]['PRIVATE_DATA']:
|
for target in RULES[_network]['PRIVATE_DATA']:
|
||||||
|
|
||||||
if self.BRIDGE == True or networks[target].BRIDGE == True:
|
if self.BRIDGE == True or systems[target].BRIDGE == True:
|
||||||
_tmp_data = _data
|
_tmp_data = _data
|
||||||
# Re-Write the IPSC SRC to match the target network's ID
|
# Re-Write the IPSC SRC to match the target network's ID
|
||||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
||||||
|
|
||||||
# Send the packet to all peers in the target IPSC
|
# Send the packet to all peers in the target IPSC
|
||||||
networks[target].send_to_ipsc(_tmp_data)
|
systems[target].send_to_ipsc(_tmp_data)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
logger.info('DMRlink \'bridge.py\' (c) 2013-2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'bridge.py\' (c) 2013-2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
|
|
||||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
for ipsc_network in NETWORK:
|
for ipsc_network in NETWORKS:
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
if NETWORKS[ipsc_network]['LOCAL']['ENABLED']:
|
||||||
networks[ipsc_network] = bridgeIPSC(ipsc_network)
|
systems[ipsc_network] = bridgeIPSC(ipsc_network)
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
reactor.listenUDP(NETWORKS[ipsc_network]['LOCAL']['PORT'], systems[ipsc_network], interface=NETWORKS[ipsc_network]['LOCAL']['IP'])
|
||||||
|
|
||||||
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
||||||
if REPORTS['REPORT_NETWORKS']:
|
if REPORTS['REPORT_NETWORKS']:
|
||||||
|
|
516
dmrlink.py
516
dmrlink.py
|
@ -40,13 +40,14 @@ from binascii import b2a_hex as h
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from socket import inet_ntoa as IPAddr
|
from socket import inet_ntoa as IPAddr
|
||||||
from socket import inet_aton as IPHexStr
|
from socket import inet_aton as IPHexStr
|
||||||
|
from random import randint
|
||||||
|
from time import time
|
||||||
|
from cPickle import dump as pickle_dump
|
||||||
|
|
||||||
from socket import gethostbyname
|
from socket import gethostbyname
|
||||||
from twisted.internet.protocol import DatagramProtocol
|
from twisted.internet.protocol import DatagramProtocol
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
from random import randint
|
|
||||||
from time import time
|
|
||||||
from cPickle import dump as pickle_dump
|
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
@ -58,220 +59,231 @@ __email__ = 'n0mjs@me.com'
|
||||||
# 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])))
|
||||||
|
|
||||||
|
# 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)')
|
||||||
|
|
||||||
cli_args = parser.parse_args()
|
cli_args = parser.parse_args()
|
||||||
|
|
||||||
|
if not cli_args.CFG_FILE:
|
||||||
|
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# PARSE THE CONFIG FILE AND BUILD STRUCTURE
|
# PARSE THE CONFIG FILE AND BUILD STRUCTURE
|
||||||
#************************************************
|
#************************************************
|
||||||
|
|
||||||
NETWORK = {}
|
systems = {}
|
||||||
networks = {}
|
|
||||||
config = ConfigParser.ConfigParser()
|
|
||||||
|
|
||||||
if not cli_args.CFG_FILE:
|
def build_config(_config_file):
|
||||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
config = ConfigParser.ConfigParser()
|
||||||
try:
|
|
||||||
if not config.read(cli_args.CFG_FILE):
|
|
||||||
sys.exit('Configuration file \''+cli_args.CFG_FILE+'\' is not a valid configuration file! Exiting...')
|
|
||||||
except:
|
|
||||||
sys.exit('Configuration file \''+cli_args.CFG_FILE+'\' is not a valid configuration file! Exiting...')
|
|
||||||
|
|
||||||
try:
|
if not config.read(_config_file):
|
||||||
for section in config.sections():
|
sys.exit('Configuration file \''+_config_file+'\' is not a valid configuration file! Exiting...')
|
||||||
if section == 'GLOBAL':
|
|
||||||
# Process GLOBAL items in the configuration
|
|
||||||
PATH = config.get(section, 'PATH')
|
|
||||||
|
|
||||||
elif section == 'REPORTS':
|
CONFIG = {}
|
||||||
# Process REPORTS items in the configuration
|
CONFIG['GLOBAL'] = {}
|
||||||
REPORTS = {
|
CONFIG['REPORTS'] = {}
|
||||||
'REPORT_NETWORKS': config.get(section, 'REPORT_NETWORKS'),
|
CONFIG['LOGGER'] = {}
|
||||||
'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'),
|
CONFIG['SYSTEMS'] = {}
|
||||||
'REPORT_PATH': config.get(section, 'REPORT_PATH'),
|
|
||||||
'PRINT_PEERS_INC_MODE': config.getboolean(section, 'PRINT_PEERS_INC_MODE'),
|
|
||||||
'PRINT_PEERS_INC_FLAGS': config.getboolean(section, 'PRINT_PEERS_INC_FLAGS')
|
|
||||||
}
|
|
||||||
|
|
||||||
elif section == 'LOGGER':
|
try:
|
||||||
# Process LOGGER items in the configuration
|
for section in config.sections():
|
||||||
LOGGER = {
|
if section == 'GLOBAL':
|
||||||
'LOG_FILE': config.get(section, 'LOG_FILE'),
|
CONFIG['GLOBAL'].update({
|
||||||
'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'),
|
'PATH': config.get(section, 'PATH')
|
||||||
'LOG_LEVEL': config.get(section, 'LOG_LEVEL'),
|
|
||||||
'LOG_NAME': config.get(section, 'LOG_NAME')
|
|
||||||
}
|
|
||||||
elif config.getboolean(section, 'ENABLED'):
|
|
||||||
# All other sections define indiviual IPSC Networks we connect to
|
|
||||||
# Each IPSC network config will contain the following three sections
|
|
||||||
NETWORK.update({section: {'LOCAL': {}, 'MASTER': {}, 'PEERS': {}}})
|
|
||||||
# LOCAL means we need to know this stuff to be a peer in the network
|
|
||||||
NETWORK[section]['LOCAL'].update({
|
|
||||||
# In case we want to keep config, but not actually connect to the network
|
|
||||||
'ENABLED': config.getboolean(section, 'ENABLED'),
|
|
||||||
|
|
||||||
# These items are used to create the MODE byte
|
|
||||||
'PEER_OPER': config.getboolean(section, 'PEER_OPER'),
|
|
||||||
'IPSC_MODE': config.get(section, 'IPSC_MODE'),
|
|
||||||
'TS1_LINK': config.getboolean(section, 'TS1_LINK'),
|
|
||||||
'TS2_LINK': config.getboolean(section, 'TS2_LINK'),
|
|
||||||
'MODE': '',
|
|
||||||
|
|
||||||
# These items are used to create the multi-byte FLAGS field
|
|
||||||
'AUTH_ENABLED': config.getboolean(section, 'AUTH_ENABLED'),
|
|
||||||
'CSBK_CALL': config.getboolean(section, 'CSBK_CALL'),
|
|
||||||
'RCM': config.getboolean(section, 'RCM'),
|
|
||||||
'CON_APP': config.getboolean(section, 'CON_APP'),
|
|
||||||
'XNL_CALL': config.getboolean(section, 'XNL_CALL'),
|
|
||||||
'XNL_MASTER': config.getboolean(section, 'XNL_MASTER'),
|
|
||||||
'DATA_CALL': config.getboolean(section, 'DATA_CALL'),
|
|
||||||
'VOICE_CALL': config.getboolean(section, 'VOICE_CALL'),
|
|
||||||
'MASTER_PEER': config.getboolean(section, 'MASTER_PEER'),
|
|
||||||
'FLAGS': '',
|
|
||||||
|
|
||||||
# Things we need to know to connect and be a peer in this IPSC
|
|
||||||
'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'),
|
|
||||||
'IP': gethostbyname(config.get(section, 'IP')),
|
|
||||||
'PORT': config.getint(section, 'PORT'),
|
|
||||||
'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'),
|
|
||||||
'MAX_MISSED': config.getint(section, 'MAX_MISSED'),
|
|
||||||
'AUTH_KEY': (config.get(section, 'AUTH_KEY').rjust(40,'0')).decode('hex'),
|
|
||||||
'NUM_PEERS': 0,
|
|
||||||
})
|
|
||||||
# Master means things we need to know about the master peer of the network
|
|
||||||
NETWORK[section]['MASTER'].update({
|
|
||||||
'RADIO_ID': '\x00\x00\x00\x00',
|
|
||||||
'MODE': '\x00',
|
|
||||||
'MODE_DECODE': '',
|
|
||||||
'FLAGS': '\x00\x00\x00\x00',
|
|
||||||
'FLAGS_DECODE': '',
|
|
||||||
'STATUS': {
|
|
||||||
'CONNECTED': False,
|
|
||||||
'PEER_LIST': False,
|
|
||||||
'KEEP_ALIVES_SENT': 0,
|
|
||||||
'KEEP_ALIVES_MISSED': 0,
|
|
||||||
'KEEP_ALIVES_OUTSTANDING': 0,
|
|
||||||
'KEEP_ALIVES_RECEIVED': 0,
|
|
||||||
'KEEP_ALIVE_RX_TIME': 0
|
|
||||||
},
|
|
||||||
'IP': '',
|
|
||||||
'PORT': ''
|
|
||||||
})
|
|
||||||
if not NETWORK[section]['LOCAL']['MASTER_PEER']:
|
|
||||||
NETWORK[section]['MASTER'].update({
|
|
||||||
'IP': gethostbyname(config.get(section, 'MASTER_IP')),
|
|
||||||
'PORT': config.getint(section, 'MASTER_PORT')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Temporary locations for building MODE and FLAG data
|
elif section == 'REPORTS':
|
||||||
MODE_BYTE = 0
|
CONFIG['REPORTS'].update({
|
||||||
FLAG_1 = 0
|
'REPORT_NETWORKS': config.get(section, 'REPORT_NETWORKS'),
|
||||||
FLAG_2 = 0
|
'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'),
|
||||||
|
'REPORT_PATH': config.get(section, 'REPORT_PATH'),
|
||||||
|
'PRINT_PEERS_INC_MODE': config.getboolean(section, 'PRINT_PEERS_INC_MODE'),
|
||||||
|
'PRINT_PEERS_INC_FLAGS': config.getboolean(section, 'PRINT_PEERS_INC_FLAGS')
|
||||||
|
})
|
||||||
|
|
||||||
# Construct and store the MODE field
|
elif section == 'LOGGER':
|
||||||
if NETWORK[section]['LOCAL']['PEER_OPER']:
|
CONFIG['LOGGER'].update({
|
||||||
MODE_BYTE |= 1 << 6
|
'LOG_FILE': config.get(section, 'LOG_FILE'),
|
||||||
if NETWORK[section]['LOCAL']['IPSC_MODE'] == 'ANALOG':
|
'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'),
|
||||||
MODE_BYTE |= 1 << 4
|
'LOG_LEVEL': config.get(section, 'LOG_LEVEL'),
|
||||||
elif NETWORK[section]['LOCAL']['IPSC_MODE'] == 'DIGITAL':
|
'LOG_NAME': config.get(section, 'LOG_NAME')
|
||||||
MODE_BYTE |= 1 << 5
|
})
|
||||||
if NETWORK[section]['LOCAL']['TS1_LINK']:
|
|
||||||
MODE_BYTE |= 1 << 3
|
|
||||||
else:
|
|
||||||
MODE_BYTE |= 1 << 2
|
|
||||||
if NETWORK[section]['LOCAL']['TS2_LINK']:
|
|
||||||
MODE_BYTE |= 1 << 1
|
|
||||||
else:
|
|
||||||
MODE_BYTE |= 1 << 0
|
|
||||||
NETWORK[section]['LOCAL']['MODE'] = chr(MODE_BYTE)
|
|
||||||
|
|
||||||
# Construct and store the FLAGS field
|
elif config.getboolean(section, 'ENABLED'):
|
||||||
if NETWORK[section]['LOCAL']['CSBK_CALL']:
|
CONFIG['SYSTEMS'].update({section: {'LOCAL': {}, 'MASTER': {}, 'PEERS': {}}})
|
||||||
FLAG_1 |= 1 << 7
|
|
||||||
if NETWORK[section]['LOCAL']['RCM']:
|
CONFIG['SYSTEMS'][section]['LOCAL'].update({
|
||||||
FLAG_1 |= 1 << 6
|
# In case we want to keep config, but not actually connect to the network
|
||||||
if NETWORK[section]['LOCAL']['CON_APP']:
|
'ENABLED': config.getboolean(section, 'ENABLED'),
|
||||||
FLAG_1 |= 1 << 5
|
|
||||||
if NETWORK[section]['LOCAL']['XNL_CALL']:
|
# These items are used to create the MODE byte
|
||||||
FLAG_2 |= 1 << 7
|
'PEER_OPER': config.getboolean(section, 'PEER_OPER'),
|
||||||
if NETWORK[section]['LOCAL']['XNL_CALL'] and NETWORK[section]['LOCAL']['XNL_MASTER']:
|
'IPSC_MODE': config.get(section, 'IPSC_MODE'),
|
||||||
FLAG_2 |= 1 << 6
|
'TS1_LINK': config.getboolean(section, 'TS1_LINK'),
|
||||||
elif NETWORK[section]['LOCAL']['XNL_CALL'] and not NETWORK[section]['LOCAL']['XNL_MASTER']:
|
'TS2_LINK': config.getboolean(section, 'TS2_LINK'),
|
||||||
FLAG_2 |= 1 << 5
|
'MODE': '',
|
||||||
if NETWORK[section]['LOCAL']['AUTH_ENABLED']:
|
|
||||||
FLAG_2 |= 1 << 4
|
# These items are used to create the multi-byte FLAGS field
|
||||||
if NETWORK[section]['LOCAL']['DATA_CALL']:
|
'AUTH_ENABLED': config.getboolean(section, 'AUTH_ENABLED'),
|
||||||
FLAG_2 |= 1 << 3
|
'CSBK_CALL': config.getboolean(section, 'CSBK_CALL'),
|
||||||
if NETWORK[section]['LOCAL']['VOICE_CALL']:
|
'RCM': config.getboolean(section, 'RCM'),
|
||||||
FLAG_2 |= 1 << 2
|
'CON_APP': config.getboolean(section, 'CON_APP'),
|
||||||
if NETWORK[section]['LOCAL']['MASTER_PEER']:
|
'XNL_CALL': config.getboolean(section, 'XNL_CALL'),
|
||||||
FLAG_2 |= 1 << 0
|
'XNL_MASTER': config.getboolean(section, 'XNL_MASTER'),
|
||||||
NETWORK[section]['LOCAL']['FLAGS'] = '\x00\x00'+chr(FLAG_1)+chr(FLAG_2)
|
'DATA_CALL': config.getboolean(section, 'DATA_CALL'),
|
||||||
except:
|
'VOICE_CALL': config.getboolean(section, 'VOICE_CALL'),
|
||||||
sys.exit('Could not parse configuration file, exiting...')
|
'MASTER_PEER': config.getboolean(section, 'MASTER_PEER'),
|
||||||
|
'FLAGS': '',
|
||||||
|
|
||||||
|
# Things we need to know to connect and be a peer in this IPSC
|
||||||
|
'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'),
|
||||||
|
'IP': gethostbyname(config.get(section, 'IP')),
|
||||||
|
'PORT': config.getint(section, 'PORT'),
|
||||||
|
'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'),
|
||||||
|
'MAX_MISSED': config.getint(section, 'MAX_MISSED'),
|
||||||
|
'AUTH_KEY': (config.get(section, 'AUTH_KEY').rjust(40,'0')).decode('hex'),
|
||||||
|
'NUM_PEERS': 0,
|
||||||
|
})
|
||||||
|
# Master means things we need to know about the master peer of the network
|
||||||
|
CONFIG['SYSTEMS'][section]['MASTER'].update({
|
||||||
|
'RADIO_ID': '\x00\x00\x00\x00',
|
||||||
|
'MODE': '\x00',
|
||||||
|
'MODE_DECODE': '',
|
||||||
|
'FLAGS': '\x00\x00\x00\x00',
|
||||||
|
'FLAGS_DECODE': '',
|
||||||
|
'STATUS': {
|
||||||
|
'CONNECTED': False,
|
||||||
|
'PEER_LIST': False,
|
||||||
|
'KEEP_ALIVES_SENT': 0,
|
||||||
|
'KEEP_ALIVES_MISSED': 0,
|
||||||
|
'KEEP_ALIVES_OUTSTANDING': 0,
|
||||||
|
'KEEP_ALIVES_RECEIVED': 0,
|
||||||
|
'KEEP_ALIVE_RX_TIME': 0
|
||||||
|
},
|
||||||
|
'IP': '',
|
||||||
|
'PORT': ''
|
||||||
|
})
|
||||||
|
if not CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']:
|
||||||
|
CONFIG['SYSTEMS'][section]['MASTER'].update({
|
||||||
|
'IP': gethostbyname(config.get(section, 'MASTER_IP')),
|
||||||
|
'PORT': config.getint(section, 'MASTER_PORT')
|
||||||
|
})
|
||||||
|
|
||||||
|
# Temporary locations for building MODE and FLAG data
|
||||||
|
MODE_BYTE = 0
|
||||||
|
FLAG_1 = 0
|
||||||
|
FLAG_2 = 0
|
||||||
|
|
||||||
|
# Construct and store the MODE field
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['PEER_OPER']:
|
||||||
|
MODE_BYTE |= 1 << 6
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['IPSC_MODE'] == 'ANALOG':
|
||||||
|
MODE_BYTE |= 1 << 4
|
||||||
|
elif CONFIG['SYSTEMS'][section]['LOCAL']['IPSC_MODE'] == 'DIGITAL':
|
||||||
|
MODE_BYTE |= 1 << 5
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['TS1_LINK']:
|
||||||
|
MODE_BYTE |= 1 << 3
|
||||||
|
else:
|
||||||
|
MODE_BYTE |= 1 << 2
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['TS2_LINK']:
|
||||||
|
MODE_BYTE |= 1 << 1
|
||||||
|
else:
|
||||||
|
MODE_BYTE |= 1 << 0
|
||||||
|
CONFIG['SYSTEMS'][section]['LOCAL']['MODE'] = chr(MODE_BYTE)
|
||||||
|
|
||||||
|
# Construct and store the FLAGS field
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['CSBK_CALL']:
|
||||||
|
FLAG_1 |= 1 << 7
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['RCM']:
|
||||||
|
FLAG_1 |= 1 << 6
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['CON_APP']:
|
||||||
|
FLAG_1 |= 1 << 5
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL']:
|
||||||
|
FLAG_2 |= 1 << 7
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL'] and CONFIG['SYSTEMS'][section]['LOCAL']['XNL_MASTER']:
|
||||||
|
FLAG_2 |= 1 << 6
|
||||||
|
elif CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL'] and not CONFIG['SYSTEMS'][section]['LOCAL']['XNL_MASTER']:
|
||||||
|
FLAG_2 |= 1 << 5
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['AUTH_ENABLED']:
|
||||||
|
FLAG_2 |= 1 << 4
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['DATA_CALL']:
|
||||||
|
FLAG_2 |= 1 << 3
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['VOICE_CALL']:
|
||||||
|
FLAG_2 |= 1 << 2
|
||||||
|
if CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']:
|
||||||
|
FLAG_2 |= 1 << 0
|
||||||
|
CONFIG['SYSTEMS'][section]['LOCAL']['FLAGS'] = '\x00\x00'+chr(FLAG_1)+chr(FLAG_2)
|
||||||
|
|
||||||
|
except ConfigParser.Error, err:
|
||||||
|
sys.exit('Could not parse configuration file, exiting...')
|
||||||
|
|
||||||
|
return CONFIG
|
||||||
|
|
||||||
|
CONFIG = build_config(cli_args.CFG_FILE)
|
||||||
|
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# CONFIGURE THE SYSTEM LOGGER
|
# CONFIGURE THE SYSTEM LOGGER
|
||||||
#************************************************
|
#************************************************
|
||||||
|
|
||||||
dictConfig({
|
def config_logging(_logger):
|
||||||
'version': 1,
|
dictConfig({
|
||||||
'disable_existing_loggers': False,
|
'version': 1,
|
||||||
'filters': {
|
'disable_existing_loggers': False,
|
||||||
},
|
'filters': {
|
||||||
'formatters': {
|
|
||||||
'verbose': {
|
|
||||||
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
|
|
||||||
},
|
},
|
||||||
'timed': {
|
'formatters': {
|
||||||
'format': '%(levelname)s %(asctime)s %(message)s'
|
'verbose': {
|
||||||
|
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
|
||||||
|
},
|
||||||
|
'timed': {
|
||||||
|
'format': '%(levelname)s %(asctime)s %(message)s'
|
||||||
|
},
|
||||||
|
'simple': {
|
||||||
|
'format': '%(levelname)s %(message)s'
|
||||||
|
},
|
||||||
|
'syslog': {
|
||||||
|
'format': '%(name)s (%(process)d): %(levelname)s %(message)s'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'simple': {
|
'handlers': {
|
||||||
'format': '%(levelname)s %(message)s'
|
'null': {
|
||||||
|
'class': 'logging.NullHandler'
|
||||||
|
},
|
||||||
|
'console': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'formatter': 'simple'
|
||||||
|
},
|
||||||
|
'console-timed': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'formatter': 'timed'
|
||||||
|
},
|
||||||
|
'file': {
|
||||||
|
'class': 'logging.FileHandler',
|
||||||
|
'formatter': 'simple',
|
||||||
|
'filename': CONFIG['LOGGER']['LOG_FILE'],
|
||||||
|
},
|
||||||
|
'file-timed': {
|
||||||
|
'class': 'logging.FileHandler',
|
||||||
|
'formatter': 'timed',
|
||||||
|
'filename': CONFIG['LOGGER']['LOG_FILE'],
|
||||||
|
},
|
||||||
|
'syslog': {
|
||||||
|
'class': 'logging.handlers.SysLogHandler',
|
||||||
|
'formatter': 'syslog',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'syslog': {
|
'loggers': {
|
||||||
'format': '%(name)s (%(process)d): %(levelname)s %(message)s'
|
CONFIG['LOGGER']['LOG_NAME']: {
|
||||||
|
'handlers': CONFIG['LOGGER']['LOG_HANDLERS'].split(','),
|
||||||
|
'level': CONFIG['LOGGER']['LOG_LEVEL'],
|
||||||
|
'propagate': True,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
'handlers': {
|
return logging.getLogger(CONFIG['LOGGER']['LOG_NAME'])
|
||||||
'null': {
|
|
||||||
'class': 'logging.NullHandler'
|
logger = config_logging(CONFIG['LOGGER'])
|
||||||
},
|
|
||||||
'console': {
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'simple'
|
|
||||||
},
|
|
||||||
'console-timed': {
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'timed'
|
|
||||||
},
|
|
||||||
'file': {
|
|
||||||
'class': 'logging.FileHandler',
|
|
||||||
'formatter': 'simple',
|
|
||||||
'filename': LOGGER['LOG_FILE'],
|
|
||||||
},
|
|
||||||
'file-timed': {
|
|
||||||
'class': 'logging.FileHandler',
|
|
||||||
'formatter': 'timed',
|
|
||||||
'filename': LOGGER['LOG_FILE'],
|
|
||||||
},
|
|
||||||
'syslog': {
|
|
||||||
'class': 'logging.handlers.SysLogHandler',
|
|
||||||
'formatter': 'syslog',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'loggers': {
|
|
||||||
LOGGER['LOG_NAME']: {
|
|
||||||
'handlers': LOGGER['LOG_HANDLERS'].split(','),
|
|
||||||
'level': LOGGER['LOG_LEVEL'],
|
|
||||||
'propagate': True,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
logger = logging.getLogger(LOGGER['LOG_NAME'])
|
|
||||||
|
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
|
@ -281,7 +293,7 @@ logger = logging.getLogger(LOGGER['LOG_NAME'])
|
||||||
# Import IPSC message types and version information
|
# Import IPSC message types and version information
|
||||||
#
|
#
|
||||||
try:
|
try:
|
||||||
from ipsc.ipsc_message_types import *
|
from ipsc.ipsc_const import *
|
||||||
except ImportError:
|
except ImportError:
|
||||||
sys.exit('IPSC message types file not found or invalid')
|
sys.exit('IPSC message types file not found or invalid')
|
||||||
|
|
||||||
|
@ -303,7 +315,7 @@ talkgroup_ids = {}
|
||||||
def reread_peers():
|
def reread_peers():
|
||||||
global peer_ids
|
global peer_ids
|
||||||
try:
|
try:
|
||||||
with open(PATH+'peer_ids.csv', 'rU') as peer_ids_csv:
|
with open(CONFIG['GLOBAL']['PATH']+'peer_ids.csv', 'rU') as peer_ids_csv:
|
||||||
peers = csv.reader(peer_ids_csv, dialect='excel', delimiter=',')
|
peers = csv.reader(peer_ids_csv, dialect='excel', delimiter=',')
|
||||||
peer_ids = {}
|
peer_ids = {}
|
||||||
for row in peers:
|
for row in peers:
|
||||||
|
@ -314,7 +326,7 @@ def reread_peers():
|
||||||
def reread_talkgroups():
|
def reread_talkgroups():
|
||||||
global talkgroup_ids
|
global talkgroup_ids
|
||||||
try:
|
try:
|
||||||
with open(PATH+'talkgroup_ids.csv', 'rU') as talkgroup_ids_csv:
|
with open(CONFIG['GLOBAL']['PATH']+'talkgroup_ids.csv', 'rU') as talkgroup_ids_csv:
|
||||||
talkgroups = csv.reader(talkgroup_ids_csv, dialect='excel', delimiter=',')
|
talkgroups = csv.reader(talkgroup_ids_csv, dialect='excel', delimiter=',')
|
||||||
talkgroup_ids = {}
|
talkgroup_ids = {}
|
||||||
for row in talkgroups:
|
for row in talkgroups:
|
||||||
|
@ -326,7 +338,7 @@ def reread_talkgroups():
|
||||||
def reread_subscribers():
|
def reread_subscribers():
|
||||||
global subscriber_ids
|
global subscriber_ids
|
||||||
try:
|
try:
|
||||||
with open(PATH+'subscriber_ids.csv', 'rU') as subscriber_ids_csv:
|
with open(CONFIG['GLOBAL']['PATH']+'subscriber_ids.csv', 'rU') as subscriber_ids_csv:
|
||||||
subscribers = csv.reader(subscriber_ids_csv, dialect='excel', delimiter=',')
|
subscribers = csv.reader(subscriber_ids_csv, dialect='excel', delimiter=',')
|
||||||
subscriber_ids = {}
|
subscriber_ids = {}
|
||||||
for row in subscribers:
|
for row in subscribers:
|
||||||
|
@ -350,7 +362,6 @@ def get_subscriber_info(_src_sub):
|
||||||
#
|
#
|
||||||
def hex_str_2(_int_id):
|
def hex_str_2(_int_id):
|
||||||
try:
|
try:
|
||||||
#return hex(_int_id)[2:].rjust(4,'0').decode('hex')
|
|
||||||
return format(_int_id,'x').rjust(4,'0').decode('hex')
|
return format(_int_id,'x').rjust(4,'0').decode('hex')
|
||||||
except TypeError:
|
except TypeError:
|
||||||
logger.error('hex_str_2: invalid integer length')
|
logger.error('hex_str_2: invalid integer length')
|
||||||
|
@ -359,7 +370,6 @@ def hex_str_2(_int_id):
|
||||||
#
|
#
|
||||||
def hex_str_3(_int_id):
|
def hex_str_3(_int_id):
|
||||||
try:
|
try:
|
||||||
#return hex(_int_id)[2:].rjust(6,'0').decode('hex')
|
|
||||||
return format(_int_id,'x').rjust(6,'0').decode('hex')
|
return format(_int_id,'x').rjust(6,'0').decode('hex')
|
||||||
except TypeError:
|
except TypeError:
|
||||||
logger.error('hex_str_3: invalid integer length')
|
logger.error('hex_str_3: invalid integer length')
|
||||||
|
@ -368,7 +378,6 @@ def hex_str_3(_int_id):
|
||||||
#
|
#
|
||||||
def hex_str_4(_int_id):
|
def hex_str_4(_int_id):
|
||||||
try:
|
try:
|
||||||
#return hex(_int_id)[2:].rjust(8,'0').decode('hex')
|
|
||||||
return format(_int_id,'x').rjust(8,'0').decode('hex')
|
return format(_int_id,'x').rjust(8,'0').decode('hex')
|
||||||
except TypeError:
|
except TypeError:
|
||||||
logger.error('hex_str_4: invalid integer length')
|
logger.error('hex_str_4: invalid integer length')
|
||||||
|
@ -402,7 +411,7 @@ def valid_peer(_peer_list, _peerid):
|
||||||
# Determine if the provided master ID is valid for the provided network
|
# Determine if the provided master ID is valid for the provided network
|
||||||
#
|
#
|
||||||
def valid_master(_network, _peerid):
|
def valid_master(_network, _peerid):
|
||||||
if NETWORK[_network]['MASTER']['RADIO_ID'] == _peerid:
|
if CONFIG['SYSTEMS'][_network]['MASTER']['RADIO_ID'] == _peerid:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -412,8 +421,8 @@ def valid_master(_network, _peerid):
|
||||||
#
|
#
|
||||||
def de_register_peer(_network, _peerid):
|
def de_register_peer(_network, _peerid):
|
||||||
# Iterate for the peer in our data
|
# Iterate for the peer in our data
|
||||||
if _peerid in NETWORK[_network]['PEERS'].keys():
|
if _peerid in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
|
||||||
del NETWORK[_network]['PEERS'][_peerid]
|
del CONFIG['SYSTEMS'][_network]['PEERS'][_peerid]
|
||||||
logger.info('(%s) Peer De-Registration Requested for: %s', _network, int_id(_peerid))
|
logger.info('(%s) Peer De-Registration Requested for: %s', _network, int_id(_peerid))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -491,8 +500,8 @@ def process_peer_list(_data, _network):
|
||||||
# Determine the length of the peer list for the parsing iterator
|
# Determine the length of the peer list for the parsing iterator
|
||||||
_peer_list_length = int(h(_data[5:7]), 16)
|
_peer_list_length = int(h(_data[5:7]), 16)
|
||||||
# Record the number of peers in the data structure... we'll use it later (11 bytes per peer entry)
|
# Record the number of peers in the data structure... we'll use it later (11 bytes per peer entry)
|
||||||
NETWORK[_network]['LOCAL']['NUM_PEERS'] = _peer_list_length/11
|
CONFIG['SYSTEMS'][_network]['LOCAL']['NUM_PEERS'] = _peer_list_length/11
|
||||||
logger.info('(%s) Peer List Received from Master: %s peers in this IPSC', _network, NETWORK[_network]['LOCAL']['NUM_PEERS'])
|
logger.info('(%s) Peer List Received from Master: %s peers in this IPSC', _network, CONFIG['SYSTEMS'][_network]['LOCAL']['NUM_PEERS'])
|
||||||
|
|
||||||
# Iterate each peer entry in the peer list. Skip the header, then pull the next peer, the next, etc.
|
# Iterate each peer entry in the peer list. Skip the header, then pull the next peer, the next, etc.
|
||||||
for i in range(7, _peer_list_length +7, 11):
|
for i in range(7, _peer_list_length +7, 11):
|
||||||
|
@ -512,18 +521,18 @@ def process_peer_list(_data, _network):
|
||||||
|
|
||||||
# If this entry WAS already in our list, update everything except the stats
|
# If this entry WAS already in our list, update everything except the stats
|
||||||
# in case this was a re-registration with a different mode, flags, etc.
|
# in case this was a re-registration with a different mode, flags, etc.
|
||||||
if _hex_radio_id in NETWORK[_network]['PEERS'].keys():
|
if _hex_radio_id in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
|
||||||
NETWORK[_network]['PEERS'][_hex_radio_id]['IP'] = _ip_address
|
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['IP'] = _ip_address
|
||||||
NETWORK[_network]['PEERS'][_hex_radio_id]['PORT'] = _port
|
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['PORT'] = _port
|
||||||
NETWORK[_network]['PEERS'][_hex_radio_id]['MODE'] = _hex_mode
|
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['MODE'] = _hex_mode
|
||||||
NETWORK[_network]['PEERS'][_hex_radio_id]['MODE_DECODE'] = _decoded_mode
|
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['MODE_DECODE'] = _decoded_mode
|
||||||
NETWORK[_network]['PEERS'][_hex_radio_id]['FLAGS'] = ''
|
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['FLAGS'] = ''
|
||||||
NETWORK[_network]['PEERS'][_hex_radio_id]['FLAGS_DECODE'] = ''
|
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['FLAGS_DECODE'] = ''
|
||||||
logger.debug('(%s) Peer Updated: %s', _network, NETWORK[_network]['PEERS'][_hex_radio_id])
|
logger.debug('(%s) Peer Updated: %s', _network, CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id])
|
||||||
|
|
||||||
# If this entry was NOT already in our list, add it.
|
# If this entry was NOT already in our list, add it.
|
||||||
if _hex_radio_id not in NETWORK[_network]['PEERS'].keys():
|
if _hex_radio_id not in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
|
||||||
NETWORK[_network]['PEERS'][_hex_radio_id] = {
|
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id] = {
|
||||||
'IP': _ip_address,
|
'IP': _ip_address,
|
||||||
'PORT': _port,
|
'PORT': _port,
|
||||||
'MODE': _hex_mode,
|
'MODE': _hex_mode,
|
||||||
|
@ -539,11 +548,11 @@ def process_peer_list(_data, _network):
|
||||||
'KEEP_ALIVE_RX_TIME': 0
|
'KEEP_ALIVE_RX_TIME': 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.debug('(%s) Peer Added: %s', _network, NETWORK[_network]['PEERS'][_hex_radio_id])
|
logger.debug('(%s) Peer Added: %s', _network, CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id])
|
||||||
|
|
||||||
# Finally, check to see if there's a peer already in our list that was not in this peer list
|
# Finally, check to see if there's a peer already in our list that was not in this peer list
|
||||||
# and if so, delete it.
|
# and if so, delete it.
|
||||||
for peer in NETWORK[_network]['PEERS'].keys():
|
for peer in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
|
||||||
if peer not in _temp_peers:
|
if peer not in _temp_peers:
|
||||||
de_register_peer(_network, peer)
|
de_register_peer(_network, peer)
|
||||||
logger.warning('(%s) Peer Deleted (not in new peer list): %s', _network, int_id(peer))
|
logger.warning('(%s) Peer Deleted (not in new peer list): %s', _network, int_id(peer))
|
||||||
|
@ -564,12 +573,12 @@ def build_peer_list(_peers):
|
||||||
# Gratuitous print-out of the peer list.. Pretty much debug stuff.
|
# Gratuitous print-out of the peer list.. Pretty much debug stuff.
|
||||||
#
|
#
|
||||||
def print_peer_list(_network):
|
def print_peer_list(_network):
|
||||||
_peers = NETWORK[_network]['PEERS']
|
_peers = CONFIG['SYSTEMS'][_network]['PEERS']
|
||||||
|
|
||||||
_status = NETWORK[_network]['MASTER']['STATUS']['PEER_LIST']
|
_status = CONFIG['SYSTEMS'][_network]['MASTER']['STATUS']['PEER_LIST']
|
||||||
#print('Peer List Status for {}: {}' .format(_network, _status))
|
#print('Peer List Status for {}: {}' .format(_network, _status))
|
||||||
|
|
||||||
if _status and not NETWORK[_network]['PEERS']:
|
if _status and not CONFIG['SYSTEMS'][_network]['PEERS']:
|
||||||
print('We are the only peer for: %s' % _network)
|
print('We are the only peer for: %s' % _network)
|
||||||
print('')
|
print('')
|
||||||
return
|
return
|
||||||
|
@ -579,18 +588,18 @@ def print_peer_list(_network):
|
||||||
_this_peer = _peers[peer]
|
_this_peer = _peers[peer]
|
||||||
_this_peer_stat = _this_peer['STATUS']
|
_this_peer_stat = _this_peer['STATUS']
|
||||||
|
|
||||||
if peer == NETWORK[_network]['LOCAL']['RADIO_ID']:
|
if peer == CONFIG['SYSTEMS'][_network]['LOCAL']['RADIO_ID']:
|
||||||
me = '(self)'
|
me = '(self)'
|
||||||
else:
|
else:
|
||||||
me = ''
|
me = ''
|
||||||
|
|
||||||
print('\tRADIO ID: {} {}' .format(int_id(peer), me))
|
print('\tRADIO ID: {} {}' .format(int_id(peer), me))
|
||||||
print('\t\tIP Address: {}:{}' .format(_this_peer['IP'], _this_peer['PORT']))
|
print('\t\tIP Address: {}:{}' .format(_this_peer['IP'], _this_peer['PORT']))
|
||||||
if _this_peer['MODE_DECODE'] and REPORTS['PRINT_PEERS_INC_MODE']:
|
if _this_peer['MODE_DECODE'] and CONFIG['REPORTS']['PRINT_PEERS_INC_MODE']:
|
||||||
print('\t\tMode Values:')
|
print('\t\tMode Values:')
|
||||||
for name, value in _this_peer['MODE_DECODE'].items():
|
for name, value in _this_peer['MODE_DECODE'].items():
|
||||||
print('\t\t\t{}: {}' .format(name, value))
|
print('\t\t\t{}: {}' .format(name, value))
|
||||||
if _this_peer['FLAGS_DECODE'] and REPORTS['PRINT_PEERS_INC_FLAGS']:
|
if _this_peer['FLAGS_DECODE'] and CONFIG['REPORTS']['PRINT_PEERS_INC_FLAGS']:
|
||||||
print('\t\tService Flags:')
|
print('\t\tService Flags:')
|
||||||
for name, value in _this_peer['FLAGS_DECODE'].items():
|
for name, value in _this_peer['FLAGS_DECODE'].items():
|
||||||
print('\t\t\t{}: {}' .format(name, value))
|
print('\t\t\t{}: {}' .format(name, value))
|
||||||
|
@ -602,17 +611,17 @@ def print_peer_list(_network):
|
||||||
# Gratuitous print-out of Master info.. Pretty much debug stuff.
|
# Gratuitous print-out of Master info.. Pretty much debug stuff.
|
||||||
#
|
#
|
||||||
def print_master(_network):
|
def print_master(_network):
|
||||||
if NETWORK[_network]['LOCAL']['MASTER_PEER']:
|
if CONFIG['SYSTEMS'][_network]['LOCAL']['MASTER_PEER']:
|
||||||
print('DMRlink is the Master for %s' % _network)
|
print('DMRlink is the Master for %s' % _network)
|
||||||
else:
|
else:
|
||||||
_master = NETWORK[_network]['MASTER']
|
_master = CONFIG['SYSTEMS'][_network]['MASTER']
|
||||||
print('Master for %s' % _network)
|
print('Master for %s' % _network)
|
||||||
print('\tRADIO ID: {}' .format(int(h(_master['RADIO_ID']), 16)))
|
print('\tRADIO ID: {}' .format(int(h(_master['RADIO_ID']), 16)))
|
||||||
if _master['MODE_DECODE'] and REPORTS['PRINT_PEERS_INC_MODE']:
|
if _master['MODE_DECODE'] and CONFIG['REPORTS']['PRINT_PEERS_INC_MODE']:
|
||||||
print('\t\tMode Values:')
|
print('\t\tMode Values:')
|
||||||
for name, value in _master['MODE_DECODE'].items():
|
for name, value in _master['MODE_DECODE'].items():
|
||||||
print('\t\t\t{}: {}' .format(name, value))
|
print('\t\t\t{}: {}' .format(name, value))
|
||||||
if _master['FLAGS_DECODE'] and REPORTS['PRINT_PEERS_INC_FLAGS']:
|
if _master['FLAGS_DECODE'] and CONFIG['REPORTS']['PRINT_PEERS_INC_FLAGS']:
|
||||||
print('\t\tService Flags:')
|
print('\t\tService Flags:')
|
||||||
for name, value in _master['FLAGS_DECODE'].items():
|
for name, value in _master['FLAGS_DECODE'].items():
|
||||||
print('\t\t\t{}: {}' .format(name, value))
|
print('\t\t\t{}: {}' .format(name, value))
|
||||||
|
@ -623,21 +632,21 @@ def print_master(_network):
|
||||||
# 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
|
||||||
if REPORTS['REPORT_NETWORKS'] == 'PICKLE':
|
if CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
|
||||||
def reporting_loop():
|
def reporting_loop():
|
||||||
logger.debug('Periodic Reporting Loop Started (PICKLE)')
|
logger.debug('Periodic Reporting Loop Started (PICKLE)')
|
||||||
try:
|
try:
|
||||||
with open(REPORTS['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
|
with open(CONFIG['REPORTS']['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
|
||||||
pickle_dump(NETWORK, file, 2)
|
pickle_dump(CONFIG['SYSTEMS'], file, 2)
|
||||||
file.close()
|
file.close()
|
||||||
except IOError as detail:
|
except IOError as detail:
|
||||||
logger.error('I/O Error: %s', detail)
|
logger.error('I/O Error: %s', detail)
|
||||||
|
|
||||||
elif REPORTS['REPORT_NETWORKS'] == 'PRINT':
|
elif CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PRINT':
|
||||||
def reporting_loop():
|
def reporting_loop():
|
||||||
logger.debug('Periodic Reporting Loop Started (PRINT)')
|
logger.debug('Periodic Reporting Loop Started (PRINT)')
|
||||||
print_master(NETWORK)
|
print_master(CONFIG['SYSTEMS'])
|
||||||
print_peer_list(NETWORK)
|
print_peer_list(CONFIG['SYSTEMS'])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
def reporting_loop():
|
def reporting_loop():
|
||||||
|
@ -649,9 +658,9 @@ else:
|
||||||
def handler(_signal, _frame):
|
def handler(_signal, _frame):
|
||||||
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
|
|
||||||
for network in networks:
|
for system in systems:
|
||||||
this_ipsc = networks[network]
|
this_ipsc = systems[system]
|
||||||
logger.info('De-Registering from IPSC %s', network)
|
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)
|
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)
|
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||||
|
|
||||||
|
@ -661,26 +670,13 @@ def handler(_signal, _frame):
|
||||||
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
signal.signal(sig, handler)
|
signal.signal(sig, handler)
|
||||||
|
|
||||||
#************************************************
|
|
||||||
#******** ***********
|
|
||||||
#******** IPSC Network 'Engine' ***********
|
|
||||||
#******** ***********
|
|
||||||
#************************************************
|
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# Base Class (used nearly all of the time)
|
# IPSC CLASS
|
||||||
#************************************************
|
#************************************************
|
||||||
|
|
||||||
|
|
||||||
class IPSC(DatagramProtocol):
|
class IPSC(DatagramProtocol):
|
||||||
|
|
||||||
#************************************************
|
|
||||||
# IPSC INSTANCE INSTANTIATION
|
|
||||||
#************************************************
|
|
||||||
|
|
||||||
# Modify the initializer to set up our environment and build the packets
|
|
||||||
# we need to maintain connections
|
|
||||||
#
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
# 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.
|
||||||
|
@ -689,7 +685,7 @@ class IPSC(DatagramProtocol):
|
||||||
# Note that many of them reference each other... this is the Pythonic way.
|
# Note that many of them reference each other... this is the Pythonic way.
|
||||||
#
|
#
|
||||||
self._network = args[0]
|
self._network = args[0]
|
||||||
self._config = NETWORK[self._network]
|
self._config = CONFIG['SYSTEMS'][self._network]
|
||||||
#
|
#
|
||||||
self._local = self._config['LOCAL']
|
self._local = self._config['LOCAL']
|
||||||
self._local_id = self._local['RADIO_ID']
|
self._local_id = self._local['RADIO_ID']
|
||||||
|
@ -847,7 +843,7 @@ class IPSC(DatagramProtocol):
|
||||||
|
|
||||||
# OUR MASTER HAS SENT US A PEER LIST - PROCESS IT
|
# OUR MASTER HAS SENT US A PEER LIST - PROCESS IT
|
||||||
def peer_list_reply(self, _data, _peerid):
|
def peer_list_reply(self, _data, _peerid):
|
||||||
NETWORK[self._network]['MASTER']['STATUS']['PEER_LIST'] = True
|
CONFIG['SYSTEMS'][self._network]['MASTER']['STATUS']['PEER_LIST'] = True
|
||||||
if len(_data) > 18:
|
if len(_data) > 18:
|
||||||
process_peer_list(_data, self._network)
|
process_peer_list(_data, self._network)
|
||||||
logger.debug('(%s) Peer List Reply Received From Master %s, %s:%s', self._network, int_id(_peerid), self._master['IP'], self._master['PORT'])
|
logger.debug('(%s) Peer List Reply Received From Master %s, %s:%s', self._network, int_id(_peerid), self._master['IP'], self._master['PORT'])
|
||||||
|
@ -1157,11 +1153,6 @@ class IPSC(DatagramProtocol):
|
||||||
if _packettype == GROUP_VOICE:
|
if _packettype == GROUP_VOICE:
|
||||||
self.reset_keep_alive(_peerid)
|
self.reset_keep_alive(_peerid)
|
||||||
self.group_voice(self._network, _src_sub, _dst_sub, _ts, _end, _peerid, data)
|
self.group_voice(self._network, _src_sub, _dst_sub, _ts, _end, _peerid, data)
|
||||||
|
|
||||||
# Loop timing test, uncomment the next two lines. Use for testing only.
|
|
||||||
#_pkt_proc_time = (time() - _pkt_time) * 1000
|
|
||||||
#logger.info('TIMING: Group voice packet ID %s took %s ms', _pkt_id, _pkt_proc_time)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
elif _packettype == PVT_VOICE:
|
elif _packettype == PVT_VOICE:
|
||||||
|
@ -1305,15 +1296,14 @@ if __name__ == '__main__':
|
||||||
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...')
|
||||||
|
|
||||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
networks = {}
|
for ipsc_network in CONFIG['SYSTEMS']:
|
||||||
for ipsc_network in NETWORK:
|
if CONFIG['SYSTEMS'][ipsc_network]['LOCAL']['ENABLED']:
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
systems[ipsc_network] = IPSC(ipsc_network)
|
||||||
networks[ipsc_network] = IPSC(ipsc_network)
|
reactor.listenUDP(CONFIG['SYSTEMS'][ipsc_network]['LOCAL']['PORT'], systems[ipsc_network], interface=CONFIG['SYSTEMS'][ipsc_network]['LOCAL']['IP'])
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
|
||||||
|
|
||||||
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
||||||
if REPORTS['REPORT_NETWORKS']:
|
if CONFIG['REPORTS']['REPORT_NETWORKS']:
|
||||||
reporting = task.LoopingCall(reporting_loop)
|
reporting = task.LoopingCall(reporting_loop)
|
||||||
reporting.start(REPORTS['REPORT_INTERVAL'])
|
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
|
||||||
|
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
|
@ -45,6 +45,14 @@ IPSC_VER_22 = '\x04'
|
||||||
# Link Type Values - assumed that cap+, etc. are different, this is all I can confirm
|
# Link Type Values - assumed that cap+, etc. are different, this is all I can confirm
|
||||||
LINK_TYPE_IPSC = '\x04'
|
LINK_TYPE_IPSC = '\x04'
|
||||||
|
|
||||||
|
# Burst Data Types
|
||||||
|
BURST_DATA_TYPE = {
|
||||||
|
'VOICE_HEAD': '\x01',
|
||||||
|
'VOICE_TERM': '\x02',
|
||||||
|
'SLOT1_VOICE': '\x0A',
|
||||||
|
'SLOT2_VOICE': '\x8A'
|
||||||
|
}
|
||||||
|
|
||||||
# IPSC Version and Link Type are Used for a 4-byte version field in registration packets
|
# IPSC Version and Link Type are Used for a 4-byte version field in registration packets
|
||||||
IPSC_VER = LINK_TYPE_IPSC + IPSC_VER_17 + LINK_TYPE_IPSC + IPSC_VER_16
|
IPSC_VER = LINK_TYPE_IPSC + IPSC_VER_17 + LINK_TYPE_IPSC + IPSC_VER_16
|
||||||
|
|
Loading…
Reference in New Issue