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
|
||||
###############################################################################
|
||||
|
||||
# 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
|
||||
# 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 networks and which are
|
||||
# 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 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).
|
||||
# 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.
|
||||
@ -53,7 +53,11 @@ from time import time
|
||||
from pprint import pprint
|
||||
|
||||
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'
|
||||
__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'
|
||||
|
||||
|
||||
# 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
|
||||
#
|
||||
TS_CLEAR_TIME = .2
|
||||
@ -103,9 +98,9 @@ for _ipsc in RULES_FILE:
|
||||
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
|
||||
_rule['TIMEOUT']= _rule['TIMEOUT']*60
|
||||
_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')
|
||||
for _ipsc in NETWORK:
|
||||
for _ipsc in NETWORKS:
|
||||
if _ipsc not in RULES_FILE:
|
||||
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']:
|
||||
_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.
|
||||
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
|
||||
@ -322,7 +317,7 @@ class bridgeIPSC(IPSC):
|
||||
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
||||
|
||||
# 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
|
||||
#
|
||||
@ -411,36 +406,36 @@ class bridgeIPSC(IPSC):
|
||||
|
||||
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
|
||||
# Re-Write the IPSC SRC to match the target network's ID
|
||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
||||
|
||||
# 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):
|
||||
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']:
|
||||
|
||||
if self.BRIDGE == True or networks[target].BRIDGE == True:
|
||||
if self.BRIDGE == True or systems[target].BRIDGE == True:
|
||||
_tmp_data = _data
|
||||
# Re-Write the IPSC SRC to match the target network's ID
|
||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
||||
|
||||
# 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__':
|
||||
logger.info('DMRlink \'bridge.py\' (c) 2013-2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
|
||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = bridgeIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
||||
for ipsc_network in NETWORKS:
|
||||
if NETWORKS[ipsc_network]['LOCAL']['ENABLED']:
|
||||
systems[ipsc_network] = bridgeIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORKS[ipsc_network]['LOCAL']['PORT'], systems[ipsc_network], interface=NETWORKS[ipsc_network]['LOCAL']['IP'])
|
||||
|
||||
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
||||
if REPORTS['REPORT_NETWORKS']:
|
||||
|
240
dmrlink.py
240
dmrlink.py
@ -40,13 +40,14 @@ from binascii import b2a_hex as h
|
||||
from hashlib import sha1
|
||||
from socket import inet_ntoa as IPAddr
|
||||
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 twisted.internet.protocol import DatagramProtocol
|
||||
from twisted.internet import reactor
|
||||
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'
|
||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
@ -58,57 +59,60 @@ __email__ = 'n0mjs@me.com'
|
||||
# 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)')
|
||||
|
||||
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
|
||||
#************************************************
|
||||
|
||||
NETWORK = {}
|
||||
networks = {}
|
||||
systems = {}
|
||||
|
||||
def build_config(_config_file):
|
||||
config = ConfigParser.ConfigParser()
|
||||
|
||||
if not cli_args.CFG_FILE:
|
||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
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...')
|
||||
if not config.read(_config_file):
|
||||
sys.exit('Configuration file \''+_config_file+'\' is not a valid configuration file! Exiting...')
|
||||
|
||||
CONFIG = {}
|
||||
CONFIG['GLOBAL'] = {}
|
||||
CONFIG['REPORTS'] = {}
|
||||
CONFIG['LOGGER'] = {}
|
||||
CONFIG['SYSTEMS'] = {}
|
||||
|
||||
try:
|
||||
for section in config.sections():
|
||||
if section == 'GLOBAL':
|
||||
# Process GLOBAL items in the configuration
|
||||
PATH = config.get(section, 'PATH')
|
||||
CONFIG['GLOBAL'].update({
|
||||
'PATH': config.get(section, 'PATH')
|
||||
})
|
||||
|
||||
elif section == 'REPORTS':
|
||||
# Process REPORTS items in the configuration
|
||||
REPORTS = {
|
||||
CONFIG['REPORTS'].update({
|
||||
'REPORT_NETWORKS': config.get(section, 'REPORT_NETWORKS'),
|
||||
'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')
|
||||
}
|
||||
})
|
||||
|
||||
elif section == 'LOGGER':
|
||||
# Process LOGGER items in the configuration
|
||||
LOGGER = {
|
||||
CONFIG['LOGGER'].update({
|
||||
'LOG_FILE': config.get(section, 'LOG_FILE'),
|
||||
'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'),
|
||||
'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({
|
||||
CONFIG['SYSTEMS'].update({section: {'LOCAL': {}, 'MASTER': {}, 'PEERS': {}}})
|
||||
|
||||
CONFIG['SYSTEMS'][section]['LOCAL'].update({
|
||||
# In case we want to keep config, but not actually connect to the network
|
||||
'ENABLED': config.getboolean(section, 'ENABLED'),
|
||||
|
||||
@ -141,7 +145,7 @@ try:
|
||||
'NUM_PEERS': 0,
|
||||
})
|
||||
# Master means things we need to know about the master peer of the network
|
||||
NETWORK[section]['MASTER'].update({
|
||||
CONFIG['SYSTEMS'][section]['MASTER'].update({
|
||||
'RADIO_ID': '\x00\x00\x00\x00',
|
||||
'MODE': '\x00',
|
||||
'MODE_DECODE': '',
|
||||
@ -159,8 +163,8 @@ try:
|
||||
'IP': '',
|
||||
'PORT': ''
|
||||
})
|
||||
if not NETWORK[section]['LOCAL']['MASTER_PEER']:
|
||||
NETWORK[section]['MASTER'].update({
|
||||
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')
|
||||
})
|
||||
@ -171,52 +175,58 @@ try:
|
||||
FLAG_2 = 0
|
||||
|
||||
# Construct and store the MODE field
|
||||
if NETWORK[section]['LOCAL']['PEER_OPER']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['PEER_OPER']:
|
||||
MODE_BYTE |= 1 << 6
|
||||
if NETWORK[section]['LOCAL']['IPSC_MODE'] == 'ANALOG':
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['IPSC_MODE'] == 'ANALOG':
|
||||
MODE_BYTE |= 1 << 4
|
||||
elif NETWORK[section]['LOCAL']['IPSC_MODE'] == 'DIGITAL':
|
||||
elif CONFIG['SYSTEMS'][section]['LOCAL']['IPSC_MODE'] == 'DIGITAL':
|
||||
MODE_BYTE |= 1 << 5
|
||||
if NETWORK[section]['LOCAL']['TS1_LINK']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['TS1_LINK']:
|
||||
MODE_BYTE |= 1 << 3
|
||||
else:
|
||||
MODE_BYTE |= 1 << 2
|
||||
if NETWORK[section]['LOCAL']['TS2_LINK']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['TS2_LINK']:
|
||||
MODE_BYTE |= 1 << 1
|
||||
else:
|
||||
MODE_BYTE |= 1 << 0
|
||||
NETWORK[section]['LOCAL']['MODE'] = chr(MODE_BYTE)
|
||||
CONFIG['SYSTEMS'][section]['LOCAL']['MODE'] = chr(MODE_BYTE)
|
||||
|
||||
# Construct and store the FLAGS field
|
||||
if NETWORK[section]['LOCAL']['CSBK_CALL']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['CSBK_CALL']:
|
||||
FLAG_1 |= 1 << 7
|
||||
if NETWORK[section]['LOCAL']['RCM']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['RCM']:
|
||||
FLAG_1 |= 1 << 6
|
||||
if NETWORK[section]['LOCAL']['CON_APP']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['CON_APP']:
|
||||
FLAG_1 |= 1 << 5
|
||||
if NETWORK[section]['LOCAL']['XNL_CALL']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL']:
|
||||
FLAG_2 |= 1 << 7
|
||||
if NETWORK[section]['LOCAL']['XNL_CALL'] and NETWORK[section]['LOCAL']['XNL_MASTER']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL'] and CONFIG['SYSTEMS'][section]['LOCAL']['XNL_MASTER']:
|
||||
FLAG_2 |= 1 << 6
|
||||
elif NETWORK[section]['LOCAL']['XNL_CALL'] and not NETWORK[section]['LOCAL']['XNL_MASTER']:
|
||||
elif CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL'] and not CONFIG['SYSTEMS'][section]['LOCAL']['XNL_MASTER']:
|
||||
FLAG_2 |= 1 << 5
|
||||
if NETWORK[section]['LOCAL']['AUTH_ENABLED']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['AUTH_ENABLED']:
|
||||
FLAG_2 |= 1 << 4
|
||||
if NETWORK[section]['LOCAL']['DATA_CALL']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['DATA_CALL']:
|
||||
FLAG_2 |= 1 << 3
|
||||
if NETWORK[section]['LOCAL']['VOICE_CALL']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['VOICE_CALL']:
|
||||
FLAG_2 |= 1 << 2
|
||||
if NETWORK[section]['LOCAL']['MASTER_PEER']:
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']:
|
||||
FLAG_2 |= 1 << 0
|
||||
NETWORK[section]['LOCAL']['FLAGS'] = '\x00\x00'+chr(FLAG_1)+chr(FLAG_2)
|
||||
except:
|
||||
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
|
||||
#************************************************
|
||||
|
||||
def config_logging(_logger):
|
||||
dictConfig({
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
@ -251,12 +261,12 @@ dictConfig({
|
||||
'file': {
|
||||
'class': 'logging.FileHandler',
|
||||
'formatter': 'simple',
|
||||
'filename': LOGGER['LOG_FILE'],
|
||||
'filename': CONFIG['LOGGER']['LOG_FILE'],
|
||||
},
|
||||
'file-timed': {
|
||||
'class': 'logging.FileHandler',
|
||||
'formatter': 'timed',
|
||||
'filename': LOGGER['LOG_FILE'],
|
||||
'filename': CONFIG['LOGGER']['LOG_FILE'],
|
||||
},
|
||||
'syslog': {
|
||||
'class': 'logging.handlers.SysLogHandler',
|
||||
@ -264,14 +274,16 @@ dictConfig({
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
LOGGER['LOG_NAME']: {
|
||||
'handlers': LOGGER['LOG_HANDLERS'].split(','),
|
||||
'level': LOGGER['LOG_LEVEL'],
|
||||
CONFIG['LOGGER']['LOG_NAME']: {
|
||||
'handlers': CONFIG['LOGGER']['LOG_HANDLERS'].split(','),
|
||||
'level': CONFIG['LOGGER']['LOG_LEVEL'],
|
||||
'propagate': True,
|
||||
}
|
||||
}
|
||||
})
|
||||
logger = logging.getLogger(LOGGER['LOG_NAME'])
|
||||
return logging.getLogger(CONFIG['LOGGER']['LOG_NAME'])
|
||||
|
||||
logger = config_logging(CONFIG['LOGGER'])
|
||||
|
||||
|
||||
#************************************************
|
||||
@ -281,7 +293,7 @@ logger = logging.getLogger(LOGGER['LOG_NAME'])
|
||||
# Import IPSC message types and version information
|
||||
#
|
||||
try:
|
||||
from ipsc.ipsc_message_types import *
|
||||
from ipsc.ipsc_const import *
|
||||
except ImportError:
|
||||
sys.exit('IPSC message types file not found or invalid')
|
||||
|
||||
@ -303,7 +315,7 @@ talkgroup_ids = {}
|
||||
def reread_peers():
|
||||
global peer_ids
|
||||
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=',')
|
||||
peer_ids = {}
|
||||
for row in peers:
|
||||
@ -314,7 +326,7 @@ def reread_peers():
|
||||
def reread_talkgroups():
|
||||
global talkgroup_ids
|
||||
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=',')
|
||||
talkgroup_ids = {}
|
||||
for row in talkgroups:
|
||||
@ -326,7 +338,7 @@ def reread_talkgroups():
|
||||
def reread_subscribers():
|
||||
global subscriber_ids
|
||||
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=',')
|
||||
subscriber_ids = {}
|
||||
for row in subscribers:
|
||||
@ -350,7 +362,6 @@ def get_subscriber_info(_src_sub):
|
||||
#
|
||||
def hex_str_2(_int_id):
|
||||
try:
|
||||
#return hex(_int_id)[2:].rjust(4,'0').decode('hex')
|
||||
return format(_int_id,'x').rjust(4,'0').decode('hex')
|
||||
except TypeError:
|
||||
logger.error('hex_str_2: invalid integer length')
|
||||
@ -359,7 +370,6 @@ def hex_str_2(_int_id):
|
||||
#
|
||||
def hex_str_3(_int_id):
|
||||
try:
|
||||
#return hex(_int_id)[2:].rjust(6,'0').decode('hex')
|
||||
return format(_int_id,'x').rjust(6,'0').decode('hex')
|
||||
except TypeError:
|
||||
logger.error('hex_str_3: invalid integer length')
|
||||
@ -368,7 +378,6 @@ def hex_str_3(_int_id):
|
||||
#
|
||||
def hex_str_4(_int_id):
|
||||
try:
|
||||
#return hex(_int_id)[2:].rjust(8,'0').decode('hex')
|
||||
return format(_int_id,'x').rjust(8,'0').decode('hex')
|
||||
except TypeError:
|
||||
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
|
||||
#
|
||||
def valid_master(_network, _peerid):
|
||||
if NETWORK[_network]['MASTER']['RADIO_ID'] == _peerid:
|
||||
if CONFIG['SYSTEMS'][_network]['MASTER']['RADIO_ID'] == _peerid:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -412,8 +421,8 @@ def valid_master(_network, _peerid):
|
||||
#
|
||||
def de_register_peer(_network, _peerid):
|
||||
# Iterate for the peer in our data
|
||||
if _peerid in NETWORK[_network]['PEERS'].keys():
|
||||
del NETWORK[_network]['PEERS'][_peerid]
|
||||
if _peerid in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
|
||||
del CONFIG['SYSTEMS'][_network]['PEERS'][_peerid]
|
||||
logger.info('(%s) Peer De-Registration Requested for: %s', _network, int_id(_peerid))
|
||||
return
|
||||
else:
|
||||
@ -491,8 +500,8 @@ def process_peer_list(_data, _network):
|
||||
# Determine the length of the peer list for the parsing iterator
|
||||
_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)
|
||||
NETWORK[_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'])
|
||||
CONFIG['SYSTEMS'][_network]['LOCAL']['NUM_PEERS'] = _peer_list_length/11
|
||||
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.
|
||||
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
|
||||
# in case this was a re-registration with a different mode, flags, etc.
|
||||
if _hex_radio_id in NETWORK[_network]['PEERS'].keys():
|
||||
NETWORK[_network]['PEERS'][_hex_radio_id]['IP'] = _ip_address
|
||||
NETWORK[_network]['PEERS'][_hex_radio_id]['PORT'] = _port
|
||||
NETWORK[_network]['PEERS'][_hex_radio_id]['MODE'] = _hex_mode
|
||||
NETWORK[_network]['PEERS'][_hex_radio_id]['MODE_DECODE'] = _decoded_mode
|
||||
NETWORK[_network]['PEERS'][_hex_radio_id]['FLAGS'] = ''
|
||||
NETWORK[_network]['PEERS'][_hex_radio_id]['FLAGS_DECODE'] = ''
|
||||
logger.debug('(%s) Peer Updated: %s', _network, NETWORK[_network]['PEERS'][_hex_radio_id])
|
||||
if _hex_radio_id in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
|
||||
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['IP'] = _ip_address
|
||||
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['PORT'] = _port
|
||||
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['MODE'] = _hex_mode
|
||||
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['MODE_DECODE'] = _decoded_mode
|
||||
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['FLAGS'] = ''
|
||||
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['FLAGS_DECODE'] = ''
|
||||
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 _hex_radio_id not in NETWORK[_network]['PEERS'].keys():
|
||||
NETWORK[_network]['PEERS'][_hex_radio_id] = {
|
||||
if _hex_radio_id not in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
|
||||
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id] = {
|
||||
'IP': _ip_address,
|
||||
'PORT': _port,
|
||||
'MODE': _hex_mode,
|
||||
@ -539,11 +548,11 @@ def process_peer_list(_data, _network):
|
||||
'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
|
||||
# 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:
|
||||
de_register_peer(_network, 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.
|
||||
#
|
||||
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))
|
||||
|
||||
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('')
|
||||
return
|
||||
@ -579,18 +588,18 @@ def print_peer_list(_network):
|
||||
_this_peer = _peers[peer]
|
||||
_this_peer_stat = _this_peer['STATUS']
|
||||
|
||||
if peer == NETWORK[_network]['LOCAL']['RADIO_ID']:
|
||||
if peer == CONFIG['SYSTEMS'][_network]['LOCAL']['RADIO_ID']:
|
||||
me = '(self)'
|
||||
else:
|
||||
me = ''
|
||||
|
||||
print('\tRADIO ID: {} {}' .format(int_id(peer), me))
|
||||
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:')
|
||||
for name, value in _this_peer['MODE_DECODE'].items():
|
||||
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:')
|
||||
for name, value in _this_peer['FLAGS_DECODE'].items():
|
||||
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.
|
||||
#
|
||||
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)
|
||||
else:
|
||||
_master = NETWORK[_network]['MASTER']
|
||||
_master = CONFIG['SYSTEMS'][_network]['MASTER']
|
||||
print('Master for %s' % _network)
|
||||
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:')
|
||||
for name, value in _master['MODE_DECODE'].items():
|
||||
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:')
|
||||
for name, value in _master['FLAGS_DECODE'].items():
|
||||
print('\t\t\t{}: {}' .format(name, value))
|
||||
@ -623,21 +632,21 @@ def print_master(_network):
|
||||
# Timed loop used for reporting IPSC status
|
||||
#
|
||||
# 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():
|
||||
logger.debug('Periodic Reporting Loop Started (PICKLE)')
|
||||
try:
|
||||
with open(REPORTS['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
|
||||
pickle_dump(NETWORK, file, 2)
|
||||
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 REPORTS['REPORT_NETWORKS'] == 'PRINT':
|
||||
elif CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PRINT':
|
||||
def reporting_loop():
|
||||
logger.debug('Periodic Reporting Loop Started (PRINT)')
|
||||
print_master(NETWORK)
|
||||
print_peer_list(NETWORK)
|
||||
print_master(CONFIG['SYSTEMS'])
|
||||
print_peer_list(CONFIG['SYSTEMS'])
|
||||
|
||||
else:
|
||||
def reporting_loop():
|
||||
@ -649,9 +658,9 @@ else:
|
||||
def handler(_signal, _frame):
|
||||
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||
|
||||
for network in networks:
|
||||
this_ipsc = networks[network]
|
||||
logger.info('De-Registering from IPSC %s', network)
|
||||
for system in systems:
|
||||
this_ipsc = systems[system]
|
||||
logger.info('De-Registering from IPSC %s', system)
|
||||
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||
|
||||
@ -661,26 +670,13 @@ def handler(_signal, _frame):
|
||||
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||
signal.signal(sig, handler)
|
||||
|
||||
#************************************************
|
||||
#******** ***********
|
||||
#******** IPSC Network 'Engine' ***********
|
||||
#******** ***********
|
||||
#************************************************
|
||||
|
||||
|
||||
#************************************************
|
||||
# Base Class (used nearly all of the time)
|
||||
# IPSC CLASS
|
||||
#************************************************
|
||||
|
||||
|
||||
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):
|
||||
if len(args) == 1:
|
||||
# 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.
|
||||
#
|
||||
self._network = args[0]
|
||||
self._config = NETWORK[self._network]
|
||||
self._config = CONFIG['SYSTEMS'][self._network]
|
||||
#
|
||||
self._local = self._config['LOCAL']
|
||||
self._local_id = self._local['RADIO_ID']
|
||||
@ -847,7 +843,7 @@ class IPSC(DatagramProtocol):
|
||||
|
||||
# OUR MASTER HAS SENT US A PEER LIST - PROCESS IT
|
||||
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:
|
||||
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'])
|
||||
@ -1157,11 +1153,6 @@ class IPSC(DatagramProtocol):
|
||||
if _packettype == GROUP_VOICE:
|
||||
self.reset_keep_alive(_peerid)
|
||||
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
|
||||
|
||||
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...')
|
||||
|
||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||
networks = {}
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = IPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
||||
for ipsc_network in CONFIG['SYSTEMS']:
|
||||
if CONFIG['SYSTEMS'][ipsc_network]['LOCAL']['ENABLED']:
|
||||
systems[ipsc_network] = IPSC(ipsc_network)
|
||||
reactor.listenUDP(CONFIG['SYSTEMS'][ipsc_network]['LOCAL']['PORT'], systems[ipsc_network], interface=CONFIG['SYSTEMS'][ipsc_network]['LOCAL']['IP'])
|
||||
|
||||
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
||||
if REPORTS['REPORT_NETWORKS']:
|
||||
if CONFIG['REPORTS']['REPORT_NETWORKS']:
|
||||
reporting = task.LoopingCall(reporting_loop)
|
||||
reporting.start(REPORTS['REPORT_INTERVAL'])
|
||||
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
|
||||
|
||||
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_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_VER = LINK_TYPE_IPSC + IPSC_VER_17 + LINK_TYPE_IPSC + IPSC_VER_16
|
||||
|
Loading…
Reference in New Issue
Block a user